From 8ab18d43daeaead759761c95c420ec3aa37de195 Mon Sep 17 00:00:00 2001 From: Bharath B Date: Fri, 3 Jul 2026 16:58:23 +0530 Subject: [PATCH] OAPE-802: OpenSpec Integration commit Signed-off-by: Bharath B --- .cursor/commands/analyze-rfe.md | 273 +++ .cursor/commands/api-generate-tests.md | 368 ++++ .cursor/commands/api-generate.md | 655 +++++++ .cursor/commands/api-implement.md | 1500 +++++++++++++++++ .cursor/commands/e2e-generate.md | 579 +++++++ .cursor/commands/eval-loop.md | 115 ++ .cursor/commands/implement-review-fixes.md | 212 +++ .cursor/commands/init.md | 226 +++ .cursor/commands/opsx-apply.md | 156 ++ .cursor/commands/opsx-archive.md | 157 ++ .cursor/commands/opsx-continue.md | 89 + .cursor/commands/opsx-explore.md | 173 ++ .cursor/commands/opsx-new.md | 49 + .cursor/commands/opsx-propose.md | 109 ++ .cursor/commands/opsx-sync.md | 143 ++ .cursor/commands/predict-regressions.md | 661 ++++++++ .cursor/commands/review.md | 226 +++ .../e2e-test-generator/docs/e2e-patterns.md | 194 +++ .../fixtures/e2e-important-scenarios.md | 60 + ...-sample-controller-runtime_test.go.example | 143 ++ .../e2e-sample-library-go_test.sh.example | 145 ++ .cursor/skills/e2e-test-generator/SKILL.md | 158 ++ .cursor/skills/effective-go/SKILL.md | 347 ++++ .cursor/skills/eval-loop/SKILL.md | 58 + .cursor/skills/openspec-apply-change/SKILL.md | 60 + .../skills/openspec-archive-change/SKILL.md | 114 ++ .../skills/openspec-continue-change/SKILL.md | 164 ++ .cursor/skills/openspec-explore/SKILL.md | 288 ++++ .cursor/skills/openspec-new-change/SKILL.md | 15 + .cursor/skills/openspec-propose/SKILL.md | 113 ++ .cursor/skills/openspec-sync-specs/SKILL.md | 147 ++ AGENTS.md | 354 ++++ CLAUDE.md | 51 + README.md | 19 + constitution.md | 142 ++ evals/README.md | 175 ++ evals/baseline/README.md | 13 + evals/baseline/evals-registry.yaml | 24 + evals/baseline/evals/code-generation/.gitkeep | 0 evals/baseline/evals/constitution/.gitkeep | 0 evals/baseline/evals/implementation/.gitkeep | 0 evals/baseline/evals/plan/.gitkeep | 0 evals/baseline/evals/repo-assessment/.gitkeep | 2 + evals/baseline/evals/tasks/.gitkeep | 0 evals/baseline/refinement-changelog.md | 3 + evals/baseline/rounds/.gitkeep | 0 evals/baseline/routing-learnings.md | 3 + evals/epic-bug-analysis/SYSTEM_PROMPT.md | 130 ++ evals/eval-generation/SYSTEM_PROMPT.md | 237 +++ .../stage-samples/code-generation/README.md | 26 + .../stage-samples/constitution/README.md | 9 + .../stage-samples/implementation/README.md | 9 + .../stage-samples/plan/README.md | 9 + .../stage-samples/repo-assessment/README.md | 10 + .../stage-samples/tasks/README.md | 9 + evals/eval-generation/template-inventory.yaml | 90 + evals/inputs/01-ep-ard.md | 3 + evals/inputs/02-jira-epic.md | 3 + evals/inputs/03-original-repo.md | 3 + evals/inputs/04-user-stories.md | 3 + evals/inputs/05-repo-prs.md | 3 + evals/inputs/bugs/index.yaml | 4 + evals/inputs/feature-meta.yaml | 3 + evals/inputs/scope-constraints.example.md | 16 + evals/outputs/epic-bug-analysis/.gitkeep | 0 evals/outputs/eval-generation/.gitkeep | 0 .../refinement-patches/.gitkeep | 0 evals/pipeline.yaml | 211 +++ evals/refined-templates/.gitkeep | 0 evals/refined-templates/tasks-modes/.gitkeep | 0 evals/round-state.yaml | 4 + evals/stages/code-generation/eval-spec.yaml | 84 + evals/stages/constitution/eval-spec.yaml | 45 + evals/stages/implementation/eval-spec.yaml | 43 + evals/stages/plan/eval-spec.yaml | 43 + evals/stages/repo-assessment/eval-spec.yaml | 50 + evals/stages/tasks/eval-spec.yaml | 43 + openspec/config.yaml | 53 + .../schemas/openspec-agile-workflow/agents.md | 354 ++++ .../openspec-agile-workflow/evals/README.md | 20 + .../evals/code-generation_eval.yaml | 4 + .../evals/constitution_eval.yaml | 3 + .../evals/implementation_eval.yaml | 3 + .../evals/plan_eval.yaml | 3 + .../evals/repo-assessment_eval.yaml | 3 + .../stages/code-generation/eval-spec.yaml | 96 ++ .../evals/stages/constitution/eval-spec.yaml | 45 + .../stages/implementation/eval-spec.yaml | 43 + .../evals/stages/plan/eval-spec.yaml | 43 + .../stages/repo-assessment/eval-spec.yaml | 50 + .../evals/stages/tasks/eval-spec.yaml | 43 + .../evals/tasks_eval.yaml | 3 + .../feedback_stage_artifacts/README.md | 66 + .../openspec-agile-workflow/inputs/agents.md | 354 ++++ .../inputs/constitution.md | 142 ++ .../openspec-agile-workflow/schema.yaml | 1472 ++++++++++++++++ .../stage-gate/CODE_GENERATION_EVAL_PROMPT.md | 350 ++++ .../stage-gate/SYSTEM_PROMPT.md | 227 +++ .../stage-gate/USER_FEEDBACK_PROMPT.md | 196 +++ .../stage-gate/artifact-eval-map.yaml | 85 + .../templates/adrs-template.md | 19 + .../templates/code-generation-template.md | 131 ++ .../templates/constitution-template.md | 130 ++ .../templates/design-bundle-template.md | 118 ++ .../implementation-checklist-template.md | 40 + .../implementation-report-template.md | 49 + .../implementation-task-report-template.md | 70 + .../templates/implementation-template.md | 57 + .../templates/plan-template.md | 256 +++ .../templates/repo-assessment-template.md | 445 +++++ .../templates/spec-template.md | 201 +++ .../tasks-modes/impact_assessment-template.md | 43 + .../tasks-modes/orchestration-template.md | 23 + .../tasks-modes/payload_revision-template.md | 25 + .../tasks-modes/payloads-template.md | 26 + .../templates/tasks-modes/single-template.md | 63 + .../tasks-modes/skeleton-template.md | 83 + .../templates/tasks-template.md | 312 ++++ .../templates/validation-template.md | 307 ++++ 119 files changed, 15629 insertions(+) create mode 100644 .cursor/commands/analyze-rfe.md create mode 100644 .cursor/commands/api-generate-tests.md create mode 100644 .cursor/commands/api-generate.md create mode 100644 .cursor/commands/api-implement.md create mode 100644 .cursor/commands/e2e-generate.md create mode 100644 .cursor/commands/eval-loop.md create mode 100644 .cursor/commands/implement-review-fixes.md create mode 100644 .cursor/commands/init.md create mode 100644 .cursor/commands/opsx-apply.md create mode 100644 .cursor/commands/opsx-archive.md create mode 100644 .cursor/commands/opsx-continue.md create mode 100644 .cursor/commands/opsx-explore.md create mode 100644 .cursor/commands/opsx-new.md create mode 100644 .cursor/commands/opsx-propose.md create mode 100644 .cursor/commands/opsx-sync.md create mode 100644 .cursor/commands/predict-regressions.md create mode 100644 .cursor/commands/review.md create mode 100644 .cursor/e2e-test-generator/docs/e2e-patterns.md create mode 100644 .cursor/e2e-test-generator/fixtures/e2e-important-scenarios.md create mode 100644 .cursor/e2e-test-generator/fixtures/e2e-sample-controller-runtime_test.go.example create mode 100644 .cursor/e2e-test-generator/fixtures/e2e-sample-library-go_test.sh.example create mode 100644 .cursor/skills/e2e-test-generator/SKILL.md create mode 100644 .cursor/skills/effective-go/SKILL.md create mode 100644 .cursor/skills/eval-loop/SKILL.md create mode 100644 .cursor/skills/openspec-apply-change/SKILL.md create mode 100644 .cursor/skills/openspec-archive-change/SKILL.md create mode 100644 .cursor/skills/openspec-continue-change/SKILL.md create mode 100644 .cursor/skills/openspec-explore/SKILL.md create mode 100644 .cursor/skills/openspec-new-change/SKILL.md create mode 100644 .cursor/skills/openspec-propose/SKILL.md create mode 100644 .cursor/skills/openspec-sync-specs/SKILL.md create mode 100644 AGENTS.md create mode 100644 CLAUDE.md create mode 100644 constitution.md create mode 100644 evals/README.md create mode 100644 evals/baseline/README.md create mode 100644 evals/baseline/evals-registry.yaml create mode 100644 evals/baseline/evals/code-generation/.gitkeep create mode 100644 evals/baseline/evals/constitution/.gitkeep create mode 100644 evals/baseline/evals/implementation/.gitkeep create mode 100644 evals/baseline/evals/plan/.gitkeep create mode 100644 evals/baseline/evals/repo-assessment/.gitkeep create mode 100644 evals/baseline/evals/tasks/.gitkeep create mode 100644 evals/baseline/refinement-changelog.md create mode 100644 evals/baseline/rounds/.gitkeep create mode 100644 evals/baseline/routing-learnings.md create mode 100644 evals/epic-bug-analysis/SYSTEM_PROMPT.md create mode 100644 evals/eval-generation/SYSTEM_PROMPT.md create mode 100644 evals/eval-generation/stage-samples/code-generation/README.md create mode 100644 evals/eval-generation/stage-samples/constitution/README.md create mode 100644 evals/eval-generation/stage-samples/implementation/README.md create mode 100644 evals/eval-generation/stage-samples/plan/README.md create mode 100644 evals/eval-generation/stage-samples/repo-assessment/README.md create mode 100644 evals/eval-generation/stage-samples/tasks/README.md create mode 100644 evals/eval-generation/template-inventory.yaml create mode 100644 evals/inputs/01-ep-ard.md create mode 100644 evals/inputs/02-jira-epic.md create mode 100644 evals/inputs/03-original-repo.md create mode 100644 evals/inputs/04-user-stories.md create mode 100644 evals/inputs/05-repo-prs.md create mode 100644 evals/inputs/bugs/index.yaml create mode 100644 evals/inputs/feature-meta.yaml create mode 100644 evals/inputs/scope-constraints.example.md create mode 100644 evals/outputs/epic-bug-analysis/.gitkeep create mode 100644 evals/outputs/eval-generation/.gitkeep create mode 100644 evals/outputs/eval-generation/refinement-patches/.gitkeep create mode 100644 evals/pipeline.yaml create mode 100644 evals/refined-templates/.gitkeep create mode 100644 evals/refined-templates/tasks-modes/.gitkeep create mode 100644 evals/round-state.yaml create mode 100644 evals/stages/code-generation/eval-spec.yaml create mode 100644 evals/stages/constitution/eval-spec.yaml create mode 100644 evals/stages/implementation/eval-spec.yaml create mode 100644 evals/stages/plan/eval-spec.yaml create mode 100644 evals/stages/repo-assessment/eval-spec.yaml create mode 100644 evals/stages/tasks/eval-spec.yaml create mode 100644 openspec/config.yaml create mode 100644 openspec/schemas/openspec-agile-workflow/agents.md create mode 100644 openspec/schemas/openspec-agile-workflow/evals/README.md create mode 100644 openspec/schemas/openspec-agile-workflow/evals/code-generation_eval.yaml create mode 100644 openspec/schemas/openspec-agile-workflow/evals/constitution_eval.yaml create mode 100644 openspec/schemas/openspec-agile-workflow/evals/implementation_eval.yaml create mode 100644 openspec/schemas/openspec-agile-workflow/evals/plan_eval.yaml create mode 100644 openspec/schemas/openspec-agile-workflow/evals/repo-assessment_eval.yaml create mode 100644 openspec/schemas/openspec-agile-workflow/evals/stages/code-generation/eval-spec.yaml create mode 100644 openspec/schemas/openspec-agile-workflow/evals/stages/constitution/eval-spec.yaml create mode 100644 openspec/schemas/openspec-agile-workflow/evals/stages/implementation/eval-spec.yaml create mode 100644 openspec/schemas/openspec-agile-workflow/evals/stages/plan/eval-spec.yaml create mode 100644 openspec/schemas/openspec-agile-workflow/evals/stages/repo-assessment/eval-spec.yaml create mode 100644 openspec/schemas/openspec-agile-workflow/evals/stages/tasks/eval-spec.yaml create mode 100644 openspec/schemas/openspec-agile-workflow/evals/tasks_eval.yaml create mode 100644 openspec/schemas/openspec-agile-workflow/feedback_stage_artifacts/README.md create mode 100644 openspec/schemas/openspec-agile-workflow/inputs/agents.md create mode 100644 openspec/schemas/openspec-agile-workflow/inputs/constitution.md create mode 100644 openspec/schemas/openspec-agile-workflow/schema.yaml create mode 100644 openspec/schemas/openspec-agile-workflow/stage-gate/CODE_GENERATION_EVAL_PROMPT.md create mode 100644 openspec/schemas/openspec-agile-workflow/stage-gate/SYSTEM_PROMPT.md create mode 100644 openspec/schemas/openspec-agile-workflow/stage-gate/USER_FEEDBACK_PROMPT.md create mode 100644 openspec/schemas/openspec-agile-workflow/stage-gate/artifact-eval-map.yaml create mode 100644 openspec/schemas/openspec-agile-workflow/templates/adrs-template.md create mode 100644 openspec/schemas/openspec-agile-workflow/templates/code-generation-template.md create mode 100644 openspec/schemas/openspec-agile-workflow/templates/constitution-template.md create mode 100644 openspec/schemas/openspec-agile-workflow/templates/design-bundle-template.md create mode 100644 openspec/schemas/openspec-agile-workflow/templates/implementation-checklist-template.md create mode 100644 openspec/schemas/openspec-agile-workflow/templates/implementation-report-template.md create mode 100644 openspec/schemas/openspec-agile-workflow/templates/implementation-task-report-template.md create mode 100644 openspec/schemas/openspec-agile-workflow/templates/implementation-template.md create mode 100644 openspec/schemas/openspec-agile-workflow/templates/plan-template.md create mode 100644 openspec/schemas/openspec-agile-workflow/templates/repo-assessment-template.md create mode 100644 openspec/schemas/openspec-agile-workflow/templates/spec-template.md create mode 100644 openspec/schemas/openspec-agile-workflow/templates/tasks-modes/impact_assessment-template.md create mode 100644 openspec/schemas/openspec-agile-workflow/templates/tasks-modes/orchestration-template.md create mode 100644 openspec/schemas/openspec-agile-workflow/templates/tasks-modes/payload_revision-template.md create mode 100644 openspec/schemas/openspec-agile-workflow/templates/tasks-modes/payloads-template.md create mode 100644 openspec/schemas/openspec-agile-workflow/templates/tasks-modes/single-template.md create mode 100644 openspec/schemas/openspec-agile-workflow/templates/tasks-modes/skeleton-template.md create mode 100644 openspec/schemas/openspec-agile-workflow/templates/tasks-template.md create mode 100644 openspec/schemas/openspec-agile-workflow/templates/validation-template.md diff --git a/.cursor/commands/analyze-rfe.md b/.cursor/commands/analyze-rfe.md new file mode 100644 index 000000000..7c4a8b896 --- /dev/null +++ b/.cursor/commands/analyze-rfe.md @@ -0,0 +1,273 @@ +--- +name: /oape:analyze-rfe +id: oape-analyze-rfe +category: OAPE +description: Analyze an RFE and output meaningful EPIC, user stories, and their outcomes +argument-hint: +--- + +## Name +oape:analyze-rfe + +## Synopsis +``` +/oape:analyze-rfe +``` + +## Description + +The `oape:analyze-rfe` command analyzes a Request for Enhancement (RFE) from Jira and generates a comprehensive breakdown of Epics, user stories, and their outcomes with technical feasibility assessment. This helps product and engineering teams transform RFEs into actionable, well-estimated implementation plans. + +This command is particularly useful for: +- Breaking down complex RFEs into implementable work items +- Planning sprints and releases from RFE requests with effort estimates +- Identifying technical risks, dependencies, and blockers early +- Creating epics and stories that align with RFE scope +- Understanding the full scope and outcomes of an RFE before implementation +- Preparing for refinement or planning sessions with comprehensive analysis +- Discovering related work to prevent duplication and enable reuse + +The command performs deep analysis of: +- The RFE's nature and description +- Current limitations and desired behavior +- Business requirements and use cases +- Affected packages and components +- **Technical complexity and risks**: Assesses implementation difficulty, identifies blockers +- **Cross-component dependencies**: Maps integration points and team handoffs +- **Effort estimation**: Provides t-shirt sizing (XS/S/M/L/XL) for sprint planning +- **Related work discovery**: Searches Jira for related RFEs, epics, bugs to prevent duplication +- **Workspace context** (optional): When the workspace contains `context.md` files (e.g., `docs/component-context/context.md`), the command loads component Purpose, Scope, and Key Areas to enrich the breakdown +- **Comprehensive component context**: Deep repository analysis including: + - Repository discovery (downstream/upstream/related repos) + - Codebase structure analysis (architecture patterns, key packages, API types) + - Implementation logic understanding (reconciliation flows, integration patterns) + - Historical context (relevant PRs, design discussions, ADRs, lessons learned) + - Synthesized insights (design principles, risk factors, recommended approach) + +## Implementation + +This command invokes the `analyze-rfe` skill to: + +1. **Fetch RFE** - Retrieve the RFE from Jira via REST API (e.g. `fetch_rfe.py`) +2. **Parse Structure** - Extract nature, description, business requirements, affected components +3. **Discover Related Work** - Search Jira for related RFEs, epics, bugs; identify reuse opportunities +4. **Gather Workspace Context** - Search for `context.md` files in the workspace (e.g., `docs/component-context/context.md`). When components match the RFE, load Purpose, Scope, Key Areas, and use them to enrich epics and stories +5. **Comprehensive Component Context** - For affected components (via `gather_component_context.py` or equivalent): + - Discover repositories (downstream, upstream, related) + - Analyze codebase structure (architecture, packages, API types) + - Understand implementation logic (patterns, integration points) + - Gather historical context (PRs, design discussions, ADRs, lessons learned) + - Synthesize insights (design principles, recommended approach, risks) +6. **Generate EPIC(s)** - Break down the RFE into one or more epics with scope and acceptance criteria +7. **Assess Technical Complexity** - Analyze complexity, identify risks, blockers, and tech debt impact +8. **Map Dependencies** - Identify epic dependencies, integration points, and cross-team handoffs +9. **Generate User Stories** - Create user stories for each epic in proper "As a... I want... So that..." format +10. **Estimate Effort** - Provide t-shirt sizing (XS=1 sprint, S=2 sprints, M=3 sprints, L=4 sprints, XL=5 sprints; 1 sprint = 3 weeks) and map story dependencies (calibrated using historical PR analysis when available) +11. **Define Outcomes** - Specify the measurable outcomes and value each story delivers +12. **Generate Implementation Summary** - Aggregate effort, dependencies, and risks across all epics + +For detailed implementation, see: +- `plugins/oape/skills/analyze-rfe/SKILL.md` + +## Arguments + +- **$1 – rfe-key** *(required)* + Jira issue key for the RFE (e.g., `RFE-1234`). + Can also accept a full Jira URL; the key will be extracted automatically. + +## Return Value + +- **Comprehensive Markdown Report** including: + - RFE Summary + - Related Work (Jira search results) + - Component Context (from workspace, if available) + - Comprehensive Component Context (repository analysis, when performed) + - EPIC(s) with objective, scope, acceptance criteria, complexity, risks, dependencies + - User stories with acceptance criteria, effort estimates, dependencies + - Implementation summary with effort overview and risk aggregation + - Outcomes mapping (what each story delivers) +- **Optional**: Output can be saved to `.work/jira/analyze-rfe//breakdown.md` + +## Output Format + +```markdown +# RFE Analysis: [RFE-KEY] - [Title] + +## RFE Summary +- **Source**: [link] +- **Key Capability**: ... +- **Business Driver**: ... +- **Affected Components**: ... + +## Related Work +| Issue | Relationship | Recommendation | +|-------|--------------|----------------| +| [KEY-123] - [Title] | Duplicate/Overlap | Coordinate with | + +**Reuse Opportunities**: [Libraries/components to leverage] + +## Component Context (from workspace) +*[If context.md found - basic component purpose and scope]* + +## Comprehensive Component Context +*[If repository analysis performed - deep technical understanding]* + +### Component: [component-name] + +**Repositories**: +- Downstream: openshift/{repo} +- Upstream: {org}/{upstream-repo} +- Related: {related repos} + +**What it does**: {concise description} +**Why it exists**: {purpose and value} +**How it works**: +- Architecture: {pattern (e.g., Kubernetes Operator)} +- Key packages: {important code areas} +- Integration: {external systems} + +**Key Implementation Patterns**: +1. {Pattern}: {description} + +**Historical Context**: +- PR #{number} ({date}): {key design decision or lesson} +- ADR: {architecture decision reference} +- Lesson from Issue #{number}: {anti-pattern to avoid} + +**Risk Factors**: +- {Risk type}: {description and mitigation} + +**Recommended Approach for RFE**: +- {Guidance based on component analysis} +- Reuse: {reference to specific PR or code to leverage} +- Follow: {design principles to respect} +- Avoid: {pitfalls from historical analysis} + +## EPIC(s) + +### EPIC 1: [Epic Title] +**Objective**: ... +**Scope**: ... +**Acceptance Criteria**: ... +**Technical Complexity**: High/Medium/Low - [justification] +**Key Risks**: +- [Risk 1] +- [Risk 2] +**Blockers/Unknowns**: [Items requiring resolution] +**Tech Debt Impact**: Positive/Neutral/Negative - [explanation] +**Dependencies**: [Epic/external dependencies] +**Integration Points**: [External systems] +**Critical Path**: Yes/No + +## User Stories + +### Epic 1 → Story 1.1: [Title] +**As a** [role], **I want** [action], **so that** [value]. +**Acceptance Criteria**: ... +**Outcome**: ... +**Effort**: M (~3 sprints / 9 weeks) +**Confidence**: High +**Depends On**: None + +## Implementation Summary + +### Effort Overview +| Epic | Stories | Total Effort | +|------|---------|--------------| +| Epic 1 | 5 stories | ~10-12 sprints (30-36 weeks) | + +### Critical Dependencies +- [Blocking items] + +### Key Risks +1. [Highest priority risk] +2. [Second priority risk] + +## Outcomes Summary +| Story | Outcome | Effort | +|-------|---------|--------| +| 1.1 | [Outcome] | S (~2 sprints) | +| 1.2 | [Outcome] | M (~3 sprints) | +| 1.3 | [Outcome] | L (~4 sprints) | + +--- +*Generated by `/oape:analyze-rfe` on [timestamp]* + +## Next Steps +1. Review with product/engineering teams +2. Use your Jira workflow to create epics and stories from this breakdown +3. Schedule spikes for unknowns +``` + +## Examples + +### Basic Usage + +Analyze an RFE and generate the breakdown: +``` +/oape:analyze-rfe RFE-1234 +``` + +### With URL + +The command accepts Jira URLs: +``` +/oape:analyze-rfe https://issues.redhat.com/browse/RFE-1234 +``` + +## Error Handling + +- **Issue Not Found**: Verify RFE key and Jira permissions +- **Not an RFE**: If the issue is not from the RFE project or not a Feature Request type, warn the user but proceed with analysis +- **Sparse RFE**: If the RFE lacks sufficient detail, indicate gaps and suggest what information would improve the breakdown + +## Prerequisites + +Before running this command, you must complete the following setup: + +### Required Setup + +1. **Jira Personal Access Token** + - Visit: https://issues.redhat.com/secure/ViewProfile.jspa?selectedTab=com.atlassian.pats.pats-plugin:jira-user-personal-access-tokens + - Click "Create token" + - Give it a name (e.g., "Claude Code - analyze-rfe") + - Copy the token immediately (you won't see it again) + +2. **Set Environment Variables** + ```bash + export JIRA_PERSONAL_TOKEN="your_token_here" + export JIRA_URL="https://issues.redhat.com" # Optional, defaults to this + ``` + +3. **Verify Setup** + Test your token works: + ```bash + curl -H "Authorization: Bearer $JIRA_PERSONAL_TOKEN" \ + "$JIRA_URL/rest/api/2/myself" + ``` + You should see your user profile JSON. + +4. **Install Python Dependencies** + ```bash + pip install requests aiohttp + ``` + +5. **Optional for deep component context**: GitHub CLI (`gh`) installed and authenticated for repository/PR analysis. Install from: https://cli.github.com/ + +### Required Permissions + +- Read access to the RFE project +- Read access to OCPBUGS project (for related work discovery) +- Read access to component-specific projects (OCPSTRAT, HOSTEDCP, etc.) + +### Network Requirements + +- Network access to Jira (e.g., https://issues.redhat.com) +- No VPN restrictions blocking Jira API access + +## See Also + +- `plugins/oape/skills/analyze-rfe/SKILL.md` - Full step-by-step implementation +- `plugins/oape/skills/analyze-rfe/scripts/README.md` - Scripts used for component context +- `oape:review` - Code review against Jira requirements +- `oape:api-implement` - Generate controller code from enhancement proposal diff --git a/.cursor/commands/api-generate-tests.md b/.cursor/commands/api-generate-tests.md new file mode 100644 index 000000000..3bc8a9cc8 --- /dev/null +++ b/.cursor/commands/api-generate-tests.md @@ -0,0 +1,368 @@ +--- +name: /oape:api-generate-tests +id: oape-api-generate-tests +category: OAPE +description: Generate integration test suites (.testsuite.yaml) for OpenShift API type definitions +argument-hint: +--- + +## Name +oape:api-generate-tests + +## Synopsis +```shell +/oape:api-generate-tests +``` + +## Description +The `oape:api-generate-tests` command generates `.testsuite.yaml` integration test files for +OpenShift API type definitions. It reads the Go type definitions, CRD manifests, and validation +markers to produce comprehensive test suites covering create, update, validation, and error +scenarios. + +The generated tests use the YAML-based test suite format consumed by the envtest-based integration +test runner (Ginkgo + controller-runtime envtest). + +**This command should be run AFTER API types and CRD manifests have been generated.** + +## Skills + +Read and follow **effective-go** (`.cursor/skills/effective-go/SKILL.md`) when generating or reviewing test-related artifacts. + +## Implementation + +### Phase 0: Prechecks + +All prechecks must pass before proceeding. If ANY precheck fails, STOP immediately and report. + +#### Precheck 1 — Verify Repository and Tools + +```bash +if ! git rev-parse --is-inside-work-tree &> /dev/null 2>&1; then + echo "PRECHECK FAILED: Not inside a git repository." + exit 1 +fi + +REPO_ROOT=$(git rev-parse --show-toplevel) + +if [ ! -f "$REPO_ROOT/go.mod" ]; then + echo "PRECHECK FAILED: No go.mod found at repository root." + exit 1 +fi + +GO_MODULE=$(head -1 "$REPO_ROOT/go.mod" | awk '{print $2}') +echo "Repository root: $REPO_ROOT" +echo "Go module: $GO_MODULE" +``` + +#### Precheck 2 — Identify Target API Types + +The user MUST provide a path to a types file or API directory. If no argument is provided, STOP +and ask the user to specify one. + +```bash +TARGET_PATH="$ARGUMENTS" + +if [ -z "$TARGET_PATH" ]; then + echo "PRECHECK FAILED: No target path provided." + echo "Usage: /oape:api-generate-tests " + exit 1 +fi +``` + +```thinking +I need to determine which API types to generate tests for: +1. User provided a specific types file path → use it directly +2. User provided an API directory → find all types files in it + +Once I have the target types file(s), I need to extract: +- The API group, version, kind, and resource (plural) +- All fields with their types, validation markers, and godoc +- Whether this is a new CRD or modifications to existing types +``` + +#### Precheck 3 — Verify CRD Manifests Exist + +The test suite references CRD manifests. Verify they have been generated: + +```bash +# For openshift/api repos: check zz_generated.crd-manifests/ +find "$REPO_ROOT" -type d -name 'zz_generated.crd-manifests' -not -path '*/vendor/*' | head -5 + +# For operator repos: check config/crd/bases/ +find "$REPO_ROOT" -type d -name 'bases' -path '*/crd/*' -not -path '*/vendor/*' | head -5 +``` + +If no CRD manifests are found, warn the user: +``` +WARNING: No CRD manifests found. Run 'make update' or 'make manifests' first to generate CRDs. +Test suites reference CRD manifests — tests will fail without them. +``` + +--- + +### Phase 1: Read API Types and CRD Manifests + +Read the target Go types file(s) and extract all information needed for test generation: + +```thinking +From the Go types, I need to extract every field, type, marker, and validation rule that could +produce a testable scenario. This includes but is not limited to: +1. Top-level CRD types (structs with +kubebuilder:object:root=true or +genclient) +2. For each CRD type: kind, API group, version, resource plural, scope, singleton constraints +3. Every spec and status field: name, type, optional/required, pointer semantics +4. All validation markers: enums, min/max, minLength/maxLength, minItems/maxItems, pattern, + format, XValidation CEL rules, exclusiveMinimum/Maximum +5. Enum types and their allowed values +6. Discriminated unions: discriminator field, member types, required members per discriminator +7. Immutable fields (XValidation rules referencing oldSelf) +8. Default values and defaulting behavior +9. Feature gate annotations (+openshift:enable:FeatureGate) +10. Nested object validation (embedded structs, list item validation) +11. Map key/value constraints +12. Any other kubebuilder or OpenShift marker that implies validation behavior + +I must read the FULL set of markers on every field — the list above is guidance, not exhaustive. +If a marker or annotation exists that I haven't seen before, I should still extract it and +generate appropriate test cases for it. +``` + +Also read the corresponding CRD manifest(s) to get: +- The full CRD name (`.`) +- The OpenAPI v3 schema (for understanding the full validation tree) +- Feature set annotations (Default, TechPreviewNoUpgrade, etc.) + +### Phase 2: Identify Test Directory and Existing Tests + +Determine where test files should be placed based on the repository layout: + +**openshift/api:** +```text +//tests/./ +``` + +**Operator repos:** +```text +api//tests/./ +``` +or +```text +api///tests/./ +``` + +Check for existing test files in the target directory. If tests already exist, read them to +understand the existing coverage and avoid duplicating tests. + +### Phase 3: Generate Test Suites + +Generate `.testsuite.yaml` files covering the following categories. For each category, derive +the specific test cases from the types and validation rules read in Phase 1. + +#### Category 1 — Minimal Valid Create + +Every test suite MUST include at least one test that creates a minimal valid instance of the +resource with only required fields populated. + +#### Category 2 — Valid Field Values + +For each field in the spec: +- Test that valid values are accepted and persisted correctly +- For enum fields: test each allowed enum value +- For optional fields: test that the resource is valid both with and without the field +- For fields with defaults: verify the default is applied correctly + +#### Category 3 — Invalid Field Values (Validation Failures) + +For each field with validation rules: +- Enum fields: test a value not in the allowed set → `expectedError` +- Pattern fields: test a value that doesn't match the regex → `expectedError` +- Min/max constraints: test values at and beyond boundaries → `expectedError` +- Required fields: test omission → `expectedError` +- CEL validation rules: test inputs that violate each rule → `expectedError` + +#### Category 4 — Update Scenarios + +For fields that can be updated: +- Test valid updates (change field value) → `expected` +- For immutable fields: test that updates are rejected → `expectedError` +- For fields with update-specific validation: test boundary cases + +#### Category 5 — Singleton Name Validation + +If the CRD is a cluster-scoped singleton (name must be "cluster"): +- Test creation with `resourceName: cluster` → success +- Test creation with `resourceName: not-cluster` → `expectedError` + +#### Category 6 — Discriminated Unions + +If the type uses discriminated unions: +- Test each valid discriminator + corresponding member combination → `expected` +- Test mismatched discriminator + member → `expectedError` +- Test missing required member for a given discriminator → `expectedError` + +#### Category 7 — Feature-Gated Fields + +If fields are gated behind a FeatureGate: +- In the stable/default test suite: test that setting the gated field is rejected → `expectedError` +- In the techpreview test suite (if applicable): test that the gated field is accepted → `expected` + +#### Category 8 — Status Subresource + +If the type has a status subresource: +- Test valid status updates +- Test invalid status updates → `expectedStatusError` + +#### Category 9 — Additional Coverage + +```thinking +I have generated tests for the 8 standard categories above. Now I must re-examine every marker, +annotation, CEL rule, godoc comment, and structural detail extracted in Phase 1 and ask: is there +any validation behavior or edge case NOT already covered by Categories 1–8? + +Examples of scenarios that may fall outside the standard categories: +- Cross-field dependencies (e.g., field B is required only when field A is set) +- Mutually exclusive fields outside of formal discriminated unions +- Nested object validation (deeply embedded structs with their own constraints) +- List item uniqueness constraints (listType=map, listMapKey) +- Map key or value constraints +- String format validations (IP, CIDR, DNS, URI, etc.) +- Complex CEL rules spanning multiple fields +- Defaulting interactions (e.g., a default on one field affecting validation of another) +- Metadata constraints (labels, annotations, finalizers if enforced) +- Edge cases around zero values vs nil for pointer fields +- Any custom OpenShift markers or annotations not covered above + +For each uncovered scenario I find, I will generate appropriate test cases. +If everything is already covered, I will note that no additional tests are needed. +``` + +Generate test cases for any validation behavior, marker, or edge case discovered in the types +that does not fit neatly into Categories 1–8. This is a catch-all to ensure no testable scenario +is missed. + +### Phase 4: Write Test Suite Files + +Write the `.testsuite.yaml` file(s) following this format: + +```yaml +apiVersion: apiextensions.k8s.io/v1 +name: "" +crdName: . +tests: + onCreate: + - name: Should be able to create a minimal + initial: | + apiVersion: / + kind: + spec: {} + expected: | + apiVersion: / + kind: + spec: {} + - name: Should reject with invalid + initial: | + apiVersion: / + kind: + spec: + : + expectedError: "" + onUpdate: + - name: Should not allow changing immutable field + initial: | + apiVersion: / + kind: + spec: + : + updated: | + apiVersion: / + kind: + spec: + : + expectedError: "" +``` + +#### File Naming Conventions + +Derive file names from existing patterns in the repository: + +**openshift/api repos:** +- `stable..testsuite.yaml` — tests for the default/stable CRD +- `techpreview..testsuite.yaml` — tests for TechPreview-gated fields +- `stable...testsuite.yaml` — platform-specific or scenario-specific tests + +**Operator repos:** +- `.testsuite.yaml` — single test suite per kind (when no feature gating) + +If the repo uses feature-gated CRD manifests (`zz_generated.featuregated-crd-manifests/`), +ensure each CRD variant has a corresponding test file. + +### Phase 5: Output Summary + +```text +=== API Test Generation Summary === + +Target API: / +CRD Name: . + +Generated Test Files: + - , + +Test Coverage: + onCreate: + - Minimal valid create + - : valid values ( tests) + - : invalid values ( tests) + - Singleton name validation + - ... + onUpdate: + - Immutable field : rejected + - Valid update for + - ... + +Next Steps: + 1. Review the generated test suites for correctness + 2. Run the integration tests + 3. Verify test coverage: make verify (runs hack/verify-integration-tests.sh) + 4. Add additional edge-case tests as needed +``` + +--- + +## Behavioral Rules + +1. **Derive from source**: All test values, error messages, and validation expectations MUST be + derived from the actual Go types, validation markers, and CRD manifests — never hardcode + assumed validation behavior. +2. **Match existing style**: If the repo already has test suites, match their naming, formatting, + and level of detail exactly. +3. **Comprehensive but focused**: Generate tests for every field and validation rule found in the + types, but don't invent scenarios not supported by the schema. +4. **Error messages**: For `expectedError` fields, use substrings from the actual CRD validation + rules. Read the CRD OpenAPI schema to determine the exact error message format. +5. **Minimal YAML**: In test `initial`/`expected`/`updated` blocks, include only the fields + relevant to that specific test case. Don't include unrelated fields. +6. **Surgical additions**: When adding tests to an existing suite file, preserve all existing + tests and only append new ones for newly added fields or types. + +## Arguments + +- ``: Required path to a types file or API directory + - Examples: + - `api/v1alpha1/myresource_types.go` + - `api/v1alpha1/` + - `config/v1/types_infrastructure.go` + +## Prerequisites + +- API type definitions must already exist (run `/oape:api-generate` first if needed) +- CRD manifests should be generated (`make update` or `make manifests`) +- Must be run from within an OpenShift operator repository + +## Exit Conditions + +- **Success**: Test suite files generated with a coverage summary +- **Failure Scenarios**: + - Not inside a valid repository + - No API types found at the specified path + - No CRD manifests found (warning, not fatal) + - Cannot determine CRD name or resource plural from the types diff --git a/.cursor/commands/api-generate.md b/.cursor/commands/api-generate.md new file mode 100644 index 000000000..eb0dc796a --- /dev/null +++ b/.cursor/commands/api-generate.md @@ -0,0 +1,655 @@ +--- +name: /oape:api-generate +id: oape-api-generate +category: OAPE +description: Generate OpenShift API type definitions from an enhancement proposal PR and/or design document, following OpenShift and Kubernetes API conventions +argument-hint: [] [--design-doc ] +--- + +## Name +oape:api-generate + +## Synopsis +```shell +# Both EP and design document +/oape:api-generate --design-doc + +# EP only (original behavior) +/oape:api-generate + +# Design document only +/oape:api-generate --design-doc +``` + +## Description +The `oape:api-generate` command reads an OpenShift enhancement proposal PR and/or a design document (GitHub Gist), extracts the required API changes, and generates compliant Go type definitions in the correct paths of the current OpenShift operator repository. + +**Input Sources:** +- **Enhancement Proposal (EP)**: High-level requirements, constraints, and context from an openshift/enhancements PR +- **Design Document (Gist)**: Detailed implementation specifications including exact field definitions, validation rules, and code structure + +When both sources are provided, the design document takes precedence for implementation details while the EP provides high-level context. + +It refreshes its knowledge of API conventions from the authoritative sources on every run, analyzes the input sources, and generates or modifies Go types that strictly follow both OpenShift and Kubernetes API conventions. + +**You MUST follow ALL conventions strictly. If any precheck fails, you MUST stop immediately and report the failure.** + +## Skills + +Read and follow **effective-go** (`.cursor/skills/effective-go/SKILL.md`) for all generated Go code. + +## Implementation + +### Phase 0: Prechecks + +All prechecks must pass before proceeding. If ANY precheck fails, STOP immediately and report the failure. + +#### Precheck 1 — Parse and Validate Input Arguments + +The command accepts an Enhancement Proposal URL and/or a design document (gist) URL. At least one must be provided. + +```bash +ARGS="$ARGUMENTS" +ENHANCEMENT_PR="" +DESIGN_DOC_URL="" +ENHANCEMENT_PR_NUMBER="" + +# Extract --design-doc argument if present +if echo "$ARGS" | grep -q '\-\-design-doc'; then + DESIGN_DOC_URL=$(echo "$ARGS" | sed -n 's/.*--design-doc[[:space:]]\+\([^[:space:]]\+\).*/\1/p') + # Remove --design-doc and its value from ARGS to get EP URL + ENHANCEMENT_PR=$(echo "$ARGS" | sed 's/--design-doc[[:space:]]\+[^[:space:]]\+//' | xargs) +else + ENHANCEMENT_PR="$ARGS" +fi + +# Validate at least one input is provided +if [ -z "$ENHANCEMENT_PR" ] && [ -z "$DESIGN_DOC_URL" ]; then + echo "PRECHECK FAILED: No input provided." + echo "Usage:" + echo " /oape:api-generate [--design-doc ]" + echo " /oape:api-generate --design-doc " + echo "" + echo "Examples:" + echo " /oape:api-generate https://github.com/openshift/enhancements/pull/1234" + echo " /oape:api-generate https://github.com/openshift/enhancements/pull/1234 --design-doc https://gist.github.com/user/abc123" + echo " /oape:api-generate --design-doc https://gist.github.com/user/abc123" + exit 1 +fi + +# Validate Enhancement PR URL if provided +if [ -n "$ENHANCEMENT_PR" ]; then + if ! echo "$ENHANCEMENT_PR" | grep -qE '^https://github\.com/openshift/enhancements/pull/[0-9]+/?$'; then + echo "PRECHECK FAILED: Invalid enhancement PR URL." + echo "Expected format: https://github.com/openshift/enhancements/pull/" + echo "Got: $ENHANCEMENT_PR" + exit 1 + fi + ENHANCEMENT_PR_NUMBER=$(echo "$ENHANCEMENT_PR" | grep -oE '[0-9]+$') + echo "Enhancement PR #$ENHANCEMENT_PR_NUMBER validated." +else + echo "No Enhancement PR provided. Using design document only." +fi + +# Validate Design Document if provided (Gist URL or local file — OpenSpec design-bundle.md) +DESIGN_DOC_LOCAL=false +if [ -n "$DESIGN_DOC_URL" ]; then + if [ -f "$DESIGN_DOC_URL" ]; then + DESIGN_DOC_LOCAL=true + echo "Design document (local file) validated: $DESIGN_DOC_URL" + elif echo "$DESIGN_DOC_URL" | grep -qE '^https://gist\.github(usercontent)?\.com/'; then + echo "Design document URL validated: $DESIGN_DOC_URL" + else + echo "PRECHECK FAILED: Invalid design document path or URL." + echo "Expected: local file path OR https://gist.github.com/[username/]" + echo "Got: $DESIGN_DOC_URL" + exit 1 + fi +else + echo "No design document provided. Using Enhancement PR only." +fi + +echo "" +echo "=== Input Sources ===" +[ -n "$ENHANCEMENT_PR" ] && echo " Enhancement PR: $ENHANCEMENT_PR" +[ -n "$DESIGN_DOC_URL" ] && echo " Design Document: $DESIGN_DOC_URL" +echo "=====================" +``` + +#### Precheck 2 — Verify Required Tools + +```bash +MISSING_TOOLS="" + +# Check gh CLI +if ! command -v gh &> /dev/null; then + MISSING_TOOLS="$MISSING_TOOLS gh(GitHub CLI)" +fi + +# Check Go +if ! command -v go &> /dev/null; then + MISSING_TOOLS="$MISSING_TOOLS go" +fi + +# Check git +if ! command -v git &> /dev/null; then + MISSING_TOOLS="$MISSING_TOOLS git" +fi + +if [ -n "$MISSING_TOOLS" ]; then + echo "PRECHECK FAILED: Missing required tools:$MISSING_TOOLS" + echo "Please install the missing tools and try again." + exit 1 +fi + +# Check gh auth status +if ! gh auth status &> /dev/null 2>&1; then + echo "PRECHECK FAILED: GitHub CLI is not authenticated." + echo "Run 'gh auth login' to authenticate." + exit 1 +fi + +echo "All required tools are available and authenticated." +``` + +#### Precheck 3 — Verify Current Repository is a Valid OpenShift Operator Repo + +```bash +# Must be in a git repository +if ! git rev-parse --is-inside-work-tree &> /dev/null 2>&1; then + echo "PRECHECK FAILED: Not inside a git repository." + echo "This command must be run from within an OpenShift operator repository." + exit 1 +fi + +REPO_ROOT=$(git rev-parse --show-toplevel) +echo "Repository root: $REPO_ROOT" + +# Must have a go.mod file +if [ ! -f "$REPO_ROOT/go.mod" ]; then + echo "PRECHECK FAILED: No go.mod found at repository root." + echo "This command must be run from within a Go-based OpenShift operator repository." + exit 1 +fi + +# Identify the Go module name +GO_MODULE=$(head -1 "$REPO_ROOT/go.mod" | awk '{print $2}') +echo "Go module: $GO_MODULE" + +# Check if this repo vendors or imports openshift/api +if grep -q "github.com/openshift/api" "$REPO_ROOT/go.mod"; then + echo "Confirmed: Repository depends on github.com/openshift/api" +elif echo "$GO_MODULE" | grep -q "github.com/openshift/api"; then + echo "Confirmed: This IS the openshift/api repository." +else + echo "PRECHECK FAILED: This repository does not appear to be an OpenShift operator repository." + echo "go.mod does not reference github.com/openshift/api." + echo "Module: $GO_MODULE" + exit 1 +fi +``` + +#### Precheck 4 — Verify Enhancement PR is Accessible (if provided) + +```bash +PR_TITLE="" +PR_STATE="" + +if [ -n "$ENHANCEMENT_PR_NUMBER" ]; then + echo "Fetching enhancement PR #$ENHANCEMENT_PR_NUMBER details..." + + PR_STATE=$(gh pr view "$ENHANCEMENT_PR_NUMBER" --repo openshift/enhancements --json state --jq '.state' 2>/dev/null) + + if [ -z "$PR_STATE" ]; then + echo "PRECHECK FAILED: Unable to access enhancement PR #$ENHANCEMENT_PR_NUMBER." + echo "Ensure the PR exists and you have access to the openshift/enhancements repository." + exit 1 + fi + + echo "Enhancement PR #$ENHANCEMENT_PR_NUMBER state: $PR_STATE" + + # Get the PR title and body for context + PR_TITLE=$(gh pr view "$ENHANCEMENT_PR_NUMBER" --repo openshift/enhancements --json title --jq '.title') + echo "Enhancement title: $PR_TITLE" +else + echo "Skipping Enhancement PR validation (not provided)." +fi +``` + +#### Precheck 5 — Verify Design Document is Accessible (if provided) + +```bash +if [ -n "$DESIGN_DOC_URL" ] && [ "$DESIGN_DOC_LOCAL" = true ]; then + echo "Local design document ready (skip gist verification)." +elif [ -n "$DESIGN_DOC_URL" ]; then + echo "Verifying design document accessibility..." + + # Extract gist ID from URL (handles various formats) + GIST_ID=$(echo "$DESIGN_DOC_URL" | grep -oE '[a-f0-9]{32}' | head -1) + + if [ -z "$GIST_ID" ]; then + # Try extracting from end of URL for short gist IDs + GIST_ID=$(echo "$DESIGN_DOC_URL" | sed 's|.*/||' | sed 's|[?#].*||') + fi + + if [ -z "$GIST_ID" ]; then + echo "PRECHECK FAILED: Could not extract gist ID from URL." + echo "URL: $DESIGN_DOC_URL" + exit 1 + fi + + # Verify gist is accessible + GIST_INFO=$(gh api "gists/$GIST_ID" --jq '.description // "Untitled"' 2>/dev/null) + + if [ -z "$GIST_INFO" ]; then + echo "PRECHECK FAILED: Unable to access design document gist." + echo "Gist ID: $GIST_ID" + echo "Ensure the gist exists and is public (or you have access)." + exit 1 + fi + + echo "Design document gist verified: $GIST_INFO" + echo "Gist ID: $GIST_ID" +else + echo "Skipping design document validation (not provided)." +fi +``` + +#### Precheck 6 — Verify Clean Working Tree (Warning) + +```bash +if ! git diff --quiet || ! git diff --cached --quiet; then + echo "WARNING: Uncommitted changes detected in the working tree." + echo "It is recommended to commit or stash changes before generating API types." + echo "Proceeding anyway..." + git status --short +else + echo "Working tree is clean." +fi +``` + +**If ALL prechecks above passed, proceed to Phase 1.** +**If ANY precheck FAILED (exit 1), STOP. Do NOT proceed further. Report the failure to the user.** + +--- + +### Phase 1: Refresh Knowledge — Fetch Latest API Conventions + +You MUST fetch and read both of these documents in full BEFORE analyzing the enhancement proposal +or generating any code. Never rely on cached knowledge — the freshly fetched versions are the +single source of truth. + +1. **OpenShift API Conventions**: `https://raw.githubusercontent.com/openshift/enhancements/master/dev-guide/api-conventions.md` +2. **Kubernetes API Conventions**: `https://raw.githubusercontent.com/kubernetes/community/master/contributors/devel/sig-architecture/api-conventions.md` + +```thinking +I must now read both fetched convention documents in full and extract every rule that applies to +API type generation — field markers, naming, documentation, validation, pointers, unions, enums, +TechPreview gating, etc. I will NOT rely on any pre-built checklist; the fetched documents are the +single source of truth. If the conventions have been updated since this command was written, the +freshly fetched versions take precedence. I will carry all extracted rules forward into the code +generation steps. +``` + +### Phase 2: Fetch and Analyze Input Sources + +Fetch content from all provided input sources (Enhancement Proposal and/or Design Document). + +#### 2.1 Fetch Enhancement Proposal (if provided) + +```bash +if [ -n "$ENHANCEMENT_PR_NUMBER" ]; then + echo "Fetching files changed in enhancement PR #$ENHANCEMENT_PR_NUMBER..." + gh pr view "$ENHANCEMENT_PR_NUMBER" --repo openshift/enhancements --json files --jq '.files[].path' +fi +``` + +Fetch the full content of each proposal file. Use the PR ref (`refs/pull//head`) which +GitHub always maintains, even if the fork branch has been deleted: + +```bash +# For each enhancement .md file found in the file list above, fetch its full content: +gh api "repos/openshift/enhancements/contents/?ref=refs/pull/$ENHANCEMENT_PR_NUMBER/head" --jq '.content' | base64 -d +``` + +If the above fails, try fetching the raw file via curl: + +```bash +curl -sL "https://raw.githubusercontent.com/openshift/enhancements/refs/pull/$ENHANCEMENT_PR_NUMBER/head/" +``` + +As a last resort, fall back to reading the diff which contains the full proposed content: + +```bash +gh pr diff "$ENHANCEMENT_PR_NUMBER" --repo openshift/enhancements +``` + +#### 2.2 Fetch Design Document (if provided) + +```bash +if [ -n "$DESIGN_DOC_URL" ] && [ "$DESIGN_DOC_LOCAL" = true ]; then + echo "Reading local design document: $DESIGN_DOC_URL" + cat "$DESIGN_DOC_URL" +elif [ -n "$GIST_ID" ]; then + echo "Fetching design document from gist $GIST_ID..." + gh api "gists/$GIST_ID" --jq '.files | to_entries[] | "=== FILE: \(.key) ===\n\(.value.content)\n"' +fi +``` + +If the gist `gh api` command fails, try fetching via curl: + +```bash +curl -sL "https://api.github.com/gists/$GIST_ID" | jq -r '.files | to_entries[] | "=== FILE: \(.key) ===\n\(.value.content)\n"' +``` + +#### 2.3 Analyze and Merge Requirements + +```thinking +I need to analyze the input source(s) and extract API requirements. The approach depends on what was provided: + +**If BOTH Enhancement Proposal AND Design Document are provided:** +- The EP provides high-level context: motivation, constraints, affected components +- The Design Document provides implementation details: exact field definitions, types, validation +- When both specify the same information, the Design Document takes precedence +- Extract from EP: operator/component context, FeatureGate requirements, general constraints +- Extract from Design Document: exact API fields, types, validation rules, code structure + +**If only Enhancement Proposal is provided:** +- Extract all requirements from the EP (original behavior) + +**If only Design Document is provided:** +- The Design Document must be comprehensive enough to generate API types +- It should specify: API group, version, kind, all fields with types and validation + +From the combined sources, I must extract: + a. Which OpenShift operator/component is being modified + b. The API group and version (e.g., config.openshift.io/v1, operator.openshift.io/v1) + c. Whether this is a NEW CRD or modifications to an EXISTING CRD + d. Whether this is a Configuration API or Workload API + e. The specific fields/types being added or modified + f. Validation requirements (enums, patterns, min/max, cross-field) + g. Whether fields should be TechPreview-gated + h. Any discriminated unions + i. Defaulting behavior + j. Immutability requirements + k. Status fields and conditions + l. The FeatureGate name to use + +If there are conflicts between sources, I will: +1. Prefer Design Document specifics over EP generalizations +2. Document any conflicts in my analysis +3. Ask the user for clarification if conflicts are ambiguous +``` + +### Phase 3: Identify Target API Paths in Current Repository + +Different OpenShift repositories organize API types differently. Explore the current repo to +determine which layout pattern is in use, then map the enhancement proposal's API changes to +the correct file paths. + +#### Known Layout Patterns + +**Pattern 1 — openshift/api repository:** +```text +//types_.go +//doc.go +//register.go +//tests//*.testsuite.yaml +features/features.go +``` +- File naming: `types_.go` +- Registration: `doc.go` + `register.go` +- FeatureGates: `features/features.go` + +**Pattern 2 — Operator repo with group subdirectory (e.g., external-secrets-operator):** +```text +api///_types.go +api///groupversion_info.go +api///doc.go +api///zz_generated.deepcopy.go +``` +- File naming: `_types.go` +- Registration: `groupversion_info.go` with `SchemeBuilder` +- Each types file has `init()` calling `SchemeBuilder.Register()` + +**Pattern 3 — Operator repo with flat version directory (e.g., external-secrets-operator):** +```text +api//_types.go +api//groupversion_info.go +api//tests//*.testsuite.yaml +api//zz_generated.deepcopy.go +``` +- File naming: `_types.go` +- Registration: `groupversion_info.go` with `SchemeBuilder` + +#### Detect the Pattern + +Run these commands to identify which layout the current repo uses: + +```bash +# Find type definition files +find "$REPO_ROOT" -type f \( -name 'types*.go' -o -name '*_types.go' \) -not -path '*/vendor/*' -not -path '*/_output/*' -not -path '*/zz_generated*' | head -40 + +# Find registration files +find "$REPO_ROOT" -type f \( -name 'doc.go' -o -name 'register.go' -o -name 'groupversion_info.go' \) -not -path '*/vendor/*' -not -path '*/_output/*' | head -40 + +# Find CRD manifests +find "$REPO_ROOT" -type f -name '*.crd.yaml' -not -path '*/vendor/*' | head -20 + +# Find test suites +find "$REPO_ROOT" -type f -name '*.testsuite.yaml' -not -path '*/vendor/*' | head -20 + +# Find feature gate definitions +find "$REPO_ROOT" -type f -name 'features.go' -not -path '*/vendor/*' | head -10 +``` + +### Phase 4: Read Existing API Types for Context + +Before generating new code, read the existing types in the target API package to understand: +- The existing struct layout and naming patterns +- Import conventions used +- Existing markers and annotations +- How other fields in the same struct are documented + +```thinking +I must read the existing types file(s) in the target package to: +1. Match the coding style exactly +2. Understand existing struct hierarchy +3. Know where to insert new fields or add new types +4. Identify existing fields/types that need to be modified (e.g., adding new enum values, + updating validation rules, changing godoc, adding new fields to existing structs) +5. Identify existing imports that may be reused +6. See how feature gates are applied to existing fields +7. Understand the existing validation patterns +``` + +### Phase 5: Generate or Modify API Type Definitions + +Generate or modify Go type definitions based on the enhancement proposal. This may include new +types, new fields, modifications to existing fields, enum types, discriminated unions, or type +registration. + +#### Pre-generation traceability check + +Before writing any code, list every field/type being added, modified, or removed. For each one, +cite the specific sentence or section in the input sources that requires it. If a change cannot +be traced to a specific requirement, do NOT make it. + +#### File scope guard + +Only create or modify files under `api/` or type-definition directories (e.g., `features/`). +If you are about to write a file under `controllers/`, `pkg/controller/`, `internal/controller/`, +`pkg/operator/`, `cmd/`, or `bindata/`, STOP — that belongs in `api-implement`, not here. + +**Hard deny-list — NEVER create or modify files matching ANY of these patterns:** +- `controllers/**` or `pkg/controller/**` or `internal/controller/**` (controller logic) +- `pkg/operator/**` (operator logic) +- `cmd/**` or `main.go` (entrypoints and scheme registration) +- `bindata/**` (static resource manifests) +- `**/constant.go` or `**/constants.go` outside `api/` (controller constants) +- `**/networkpolicies.go`, `**/federation.go`, `**/template.go` (implementation files) + +Even if new API fields imply controller behavior (e.g., new fields need env vars, new types need +resource builders), do NOT generate those files. Only generate the types and note the implied +controller work in the summary under "Deferred to api-implement". + +#### Generation rules + +For every marker, tag, or convention applied: derive it from the fetched convention documents +(Phase 1) or the existing code (Phase 4). Conventions take precedence when both differ. Existing +patterns not covered by conventions (e.g., mechanical code-gen markers) should be replicated for +consistency. + +Determine from the enhancement proposal whether this is a **Configuration API** or **Workload API**, +as the conventions define different rules for each. + +After generating, review every changed line against the conventions. If any violation has a +convention-compliant alternative, apply it and note the deviation in the Phase 7 summary. + +### Phase 6: Add FeatureGate Registration (if applicable) + +If the repository contains a `features.go` file (found in Phase 3), read it to learn the existing +FeatureGate registration pattern, then add a new FeatureGate following that pattern. + +If no `features.go` exists and the enhancement requires a FeatureGate, note this in the summary +and advise the user on where to register it. + +### Phase 7: Output Summary + +After generating all files, provide a summary: + +```text +=== API Generation Summary === + +Input Sources: + Enhancement PR: (if provided) + Design Document: (if provided) + Enhancement Title: (if EP provided) + +Generated/Modified Files: + - <path/to/types_resource.go> — <description of changes> + - <path/to/features/features.go> — <FeatureGate added> (if applicable) + +API Group: <group.openshift.io> +API Version: <version> +Kind: <KindName> +Resource: <resourcename> +Scope: <Cluster|Namespaced> +FeatureGate: <FeatureGateName> + +New Types Added: + - <TypeName> — <description> + +New Fields Added: + - <ParentType>.<fieldName> (<type>) — <description> + +Modified Fields/Types: + - <ParentType>.<fieldName> — <what changed and why> + +Validation Rules: + - <field>: <rule description> + +Source Conflicts Resolved: (if both EP and design doc provided) + - <field>: Used design doc specification (<reason>) + +Next Steps: + 1. Review the generated code for correctness + 2. Run 'make update' to regenerate CRDs and deep copy functions + 3. Run 'make verify' to validate all generated code + 4. Run 'make lint' to check for kube-api-linter issues + 5. If FeatureGate was added, verify it appears in the feature gate list +``` + +--- + +## Critical Failure Conditions + +The command MUST FAIL and STOP immediately if ANY of the following are true: + +1. **No input provided**: Neither an enhancement PR URL nor a design document URL was provided +2. **Invalid PR URL**: The provided EP URL is not a valid `openshift/enhancements` PR +3. **Invalid gist URL**: The provided design document URL is not a valid GitHub Gist +4. **Missing tools**: `gh`, `go`, or `git` are not installed or `gh` is not authenticated +5. **Not an operator repo**: The current directory is not a Git repository with a Go module that references `openshift/api` +6. **Input not accessible**: The enhancement PR or design document cannot be fetched (permissions, doesn't exist, etc.) +7. **No API changes found**: The input source(s) do not describe any API changes +8. **Ambiguous API target**: Cannot determine the target API group, version, or kind from the input sources + +When failing, provide a clear error message explaining: +- Which precheck failed +- What the expected state is +- How to fix the issue + +## Behavioral Rules + +1. **Never guess**: If the input sources are ambiguous about API details, STOP and ask the user for clarification rather than guessing. +2. **Design document precedence**: When both EP and design document are provided, the design document takes precedence for implementation details. +3. **Convention over proposal**: If the input sources suggest an API design that violates conventions (e.g., using a Boolean), generate the convention-compliant alternative and document the deviation. +4. **TechPreview when specified**: If the input sources indicate TechPreview gating, generate the appropriate FeatureGate markers. Follow whatever is specified regarding API maturity level. +5. **Idempotent**: Running this command multiple times with the same inputs should produce the same result (though it should warn if files already exist). +6. **Minimal changes**: Only generate what the input sources specify. Do not add extra fields, types, or features not described. +7. **Surgical edits**: When modifying existing files, only change what the input sources require. Preserve all unrelated code, comments, and formatting. For modifications to existing fields, clearly document what changed and why in the output summary. +8. **API types only — no controller code**: This command MUST only create or modify files in API-layer directories (`api/`, `features/`, type definition files). Do NOT create or modify files in controller directories (`controllers/`, `pkg/controller/`, `internal/controller/`, `pkg/operator/`, `cmd/`, `bindata/`). If the EP describes controller behavior, note it in the summary under "Deferred to api-implement" but generate zero controller code. +9. **No invented fields**: Do NOT add fields, types, or enum values that the input sources do not explicitly specify. If the EP adds field X, only add field X — do not also add a related field Y you think "should" exist. +10. **No restructuring**: Do NOT rename existing fields, change pointer-vs-value semantics on existing fields, or move fields between structs unless the input sources explicitly require it. When the EP says "remove field X", only remove field X. Do NOT modernize, reformat, or "improve" existing comments, type names, or field ordering on untouched fields. +11. **No controller-layer files**: Do NOT create files under `controllers/`, `pkg/controller/`, `bindata/`, `cmd/`, or `pkg/operator/`. This includes constants files, helper files, and resource builders that serve controller logic. If new API fields imply controller wiring, list the implied work in the summary — do not generate it. + +## Arguments + +- `<enhancement-pr-url>` (optional if design-doc provided): GitHub PR URL to the OpenShift enhancement proposal + - Format: `https://github.com/openshift/enhancements/pull/<number>` + +- `--design-doc <gist-url>` (optional if EP provided): GitHub Gist URL containing detailed API specifications + - Supported formats: + - `https://gist.github.com/username/gist_id` + - `https://gist.github.com/gist_id` + - `https://gist.githubusercontent.com/username/gist_id/raw/...` + +**At least one input source (EP or design document) must be provided.** + +## Design Document Expected Format + +When using a design document, it should contain structured implementation details: + +```markdown +# Design Document: Feature Name + +## API Specification +- Group: config.openshift.io (or operator.openshift.io, etc.) +- Version: v1 (or v1alpha1, v1beta1) +- Kind: FeatureName +- Scope: Cluster (or Namespaced) + +## Spec Fields +- `fieldName` (type): Description + - Validation: required, enum values, min/max, pattern + - Default: default value if any + - Immutable: yes/no + +## Status Fields +- `conditions`: Standard OpenShift conditions +- `observedGeneration`: int64 + +## FeatureGate +- Name: FeatureGateName +- Stage: TechPreviewNoUpgrade / Default +``` + +## Prerequisites + +- **gh** (GitHub CLI) — installed and authenticated (`gh auth login`) +- **go** — Go toolchain installed +- **git** — Git installed +- Must be run from within an OpenShift operator repository (Go module that references `github.com/openshift/api`) + +## Exit Conditions + +- **Success**: API type definitions generated/modified with a summary of all changes +- **Failure Scenarios**: + - No input provided (neither EP nor design document) + - Invalid enhancement PR URL or gist URL + - Missing required tools or unauthenticated GitHub CLI + - Not inside a valid OpenShift operator repository + - Input source(s) inaccessible + - No API changes found in the input sources + - Ambiguous API target (asks for clarification instead of guessing) diff --git a/.cursor/commands/api-implement.md b/.cursor/commands/api-implement.md new file mode 100644 index 000000000..08aa7c32b --- /dev/null +++ b/.cursor/commands/api-implement.md @@ -0,0 +1,1500 @@ +--- +name: /oape:api-implement +id: oape-api-implement +category: OAPE +description: Generate OpenShift controller/reconciler implementation code from an enhancement proposal PR and/or design document, following controller-runtime and operator-sdk conventions +argument-hint: [<enhancement-pr-url>] [--design-doc <gist-url-or-local-path>] +--- + +## Name +oape:api-implement + +## Synopsis +```shell +# Both EP and design document +/oape:api-implement <https://github.com/openshift/enhancements/pull/NNNN> --design-doc <https://gist.github.com/user/gist_id> + +# EP only (original behavior) +/oape:api-implement <https://github.com/openshift/enhancements/pull/NNNN> + +# Design document only +/oape:api-implement --design-doc <https://gist.github.com/user/gist_id> +``` + +## Description +The `oape:api-implement` command reads an OpenShift enhancement proposal PR and/or a design document (GitHub Gist), extracts the required implementation logic, and generates complete controller/reconciler code in the correct paths of the current OpenShift operator repository. + +**Input Sources:** +- **Enhancement Proposal (EP)**: High-level requirements, constraints, and context from an openshift/enhancements PR +- **Design Document (Gist)**: Detailed implementation specifications including reconciliation workflow, dependent resources, and controller behavior + +When both sources are provided, the design document takes precedence for implementation details while the EP provides high-level context. + +This command generates **production-ready code with zero TODOs** by: +1. Parsing the input sources for explicit business logic requirements +2. Detecting the operator framework in use (controller-runtime, operator-sdk, library-go) +3. Generating actual reconciliation logic, not placeholders +4. Creating dependent resource builders and reconcilers +5. Implementing cleanup/finalizer logic +6. Setting up watches for external resources + +**You MUST follow ALL conventions strictly. If any precheck fails, you MUST stop immediately and report the failure.** + +## Skills + +Read and follow **effective-go** (`.cursor/skills/effective-go/SKILL.md`) for all generated Go controller code. + +--- + +## Implementation + +### Phase 0: Prechecks + +All prechecks must pass before proceeding. If ANY precheck fails, STOP immediately and report the failure. + +#### Precheck 1 — Parse and Validate Input Arguments + +The command accepts an Enhancement Proposal URL and/or a design document (gist) URL. At least one must be provided. + +```bash +ARGS="$ARGUMENTS" +ENHANCEMENT_PR="" +DESIGN_DOC_URL="" +ENHANCEMENT_PR_NUMBER="" + +# Extract --design-doc argument if present +if echo "$ARGS" | grep -q '\-\-design-doc'; then + DESIGN_DOC_URL=$(echo "$ARGS" | sed -n 's/.*--design-doc[[:space:]]\+\([^[:space:]]\+\).*/\1/p') + # Remove --design-doc and its value from ARGS to get EP URL + ENHANCEMENT_PR=$(echo "$ARGS" | sed 's/--design-doc[[:space:]]\+[^[:space:]]\+//' | xargs) +else + ENHANCEMENT_PR="$ARGS" +fi + +# Validate at least one input is provided +if [ -z "$ENHANCEMENT_PR" ] && [ -z "$DESIGN_DOC_URL" ]; then + echo "PRECHECK FAILED: No input provided." + echo "Usage:" + echo " /oape:api-implement <EP_URL> [--design-doc <GIST_URL>]" + echo " /oape:api-implement --design-doc <GIST_URL>" + echo "" + echo "Examples:" + echo " /oape:api-implement https://github.com/openshift/enhancements/pull/1234" + echo " /oape:api-implement https://github.com/openshift/enhancements/pull/1234 --design-doc https://gist.github.com/user/abc123" + echo " /oape:api-implement --design-doc https://gist.github.com/user/abc123" + exit 1 +fi + +# Validate Enhancement PR URL if provided +if [ -n "$ENHANCEMENT_PR" ]; then + if ! echo "$ENHANCEMENT_PR" | grep -qE '^https://github\.com/openshift/enhancements/pull/[0-9]+/?$'; then + echo "PRECHECK FAILED: Invalid enhancement PR URL." + echo "Expected format: https://github.com/openshift/enhancements/pull/<number>" + echo "Got: $ENHANCEMENT_PR" + exit 1 + fi + ENHANCEMENT_PR_NUMBER=$(echo "$ENHANCEMENT_PR" | grep -oE '[0-9]+$') + echo "Enhancement PR #$ENHANCEMENT_PR_NUMBER validated." +else + echo "No Enhancement PR provided. Using design document only." +fi + +# Validate Design Document if provided (Gist URL or local file — OpenSpec design-bundle.md) +DESIGN_DOC_LOCAL=false +if [ -n "$DESIGN_DOC_URL" ]; then + if [ -f "$DESIGN_DOC_URL" ]; then + DESIGN_DOC_LOCAL=true + echo "Design document (local file) validated: $DESIGN_DOC_URL" + elif echo "$DESIGN_DOC_URL" | grep -qE '^https://gist\.github(usercontent)?\.com/'; then + echo "Design document URL validated: $DESIGN_DOC_URL" + else + echo "PRECHECK FAILED: Invalid design document path or URL." + echo "Expected: local file path OR https://gist.github.com/[username/]<gist_id>" + echo "Got: $DESIGN_DOC_URL" + exit 1 + fi +else + echo "No design document provided. Using Enhancement PR only." +fi + +echo "" +echo "=== Input Sources ===" +[ -n "$ENHANCEMENT_PR" ] && echo " Enhancement PR: $ENHANCEMENT_PR" +[ -n "$DESIGN_DOC_URL" ] && echo " Design Document: $DESIGN_DOC_URL" +echo "=====================" +``` + +#### Precheck 2 — Verify Required Tools + +```bash +MISSING_TOOLS="" + +# Check gh CLI +if ! command -v gh &> /dev/null; then + MISSING_TOOLS="$MISSING_TOOLS gh(GitHub CLI)" +fi + +# Check Go +if ! command -v go &> /dev/null; then + MISSING_TOOLS="$MISSING_TOOLS go" +fi + +# Check git +if ! command -v git &> /dev/null; then + MISSING_TOOLS="$MISSING_TOOLS git" +fi + +# Check make +if ! command -v make &> /dev/null; then + MISSING_TOOLS="$MISSING_TOOLS make" +fi + +if [ -n "$MISSING_TOOLS" ]; then + echo "PRECHECK FAILED: Missing required tools:$MISSING_TOOLS" + echo "Please install the missing tools and try again." + exit 1 +fi + +# Check gh auth status +if ! gh auth status &> /dev/null 2>&1; then + echo "PRECHECK FAILED: GitHub CLI is not authenticated." + echo "Run 'gh auth login' to authenticate." + exit 1 +fi + +echo "All required tools are available and authenticated." +``` + +#### Precheck 3 — Verify Current Repository is a Valid Operator Repo + +```bash +# Must be in a git repository +if ! git rev-parse --is-inside-work-tree &> /dev/null 2>&1; then + echo "PRECHECK FAILED: Not inside a git repository." + echo "This command must be run from within an OpenShift operator repository." + exit 1 +fi + +REPO_ROOT=$(git rev-parse --show-toplevel) +echo "Repository root: $REPO_ROOT" + +# Must have a go.mod file +if [ ! -f "$REPO_ROOT/go.mod" ]; then + echo "PRECHECK FAILED: No go.mod found at repository root." + echo "This command must be run from within a Go-based OpenShift operator repository." + exit 1 +fi + +# Identify the Go module name +GO_MODULE=$(head -1 "$REPO_ROOT/go.mod" | awk '{print $2}') +echo "Go module: $GO_MODULE" +``` + +#### Precheck 4 — Verify Enhancement PR is Accessible (if provided) + +```bash +PR_TITLE="" +PR_STATE="" + +if [ -n "$ENHANCEMENT_PR_NUMBER" ]; then + echo "Fetching enhancement PR #$ENHANCEMENT_PR_NUMBER details..." + + PR_STATE=$(gh pr view "$ENHANCEMENT_PR_NUMBER" --repo openshift/enhancements --json state --jq '.state' 2>/dev/null) + + if [ -z "$PR_STATE" ]; then + echo "PRECHECK FAILED: Unable to access enhancement PR #$ENHANCEMENT_PR_NUMBER." + echo "Ensure the PR exists and you have access to the openshift/enhancements repository." + exit 1 + fi + + echo "Enhancement PR #$ENHANCEMENT_PR_NUMBER state: $PR_STATE" + + PR_TITLE=$(gh pr view "$ENHANCEMENT_PR_NUMBER" --repo openshift/enhancements --json title --jq '.title') + echo "Enhancement title: $PR_TITLE" +else + echo "Skipping Enhancement PR validation (not provided)." +fi +``` + +#### Precheck 5 — Verify Design Document is Accessible (if provided) + +```bash +GIST_ID="" + +if [ -n "$DESIGN_DOC_URL" ] && [ "$DESIGN_DOC_LOCAL" = true ]; then + echo "Local design document ready (skip gist verification)." +elif [ -n "$DESIGN_DOC_URL" ]; then + echo "Verifying design document accessibility..." + + # Extract gist ID from URL (handles various formats) + GIST_ID=$(echo "$DESIGN_DOC_URL" | grep -oE '[a-f0-9]{32}' | head -1) + + if [ -z "$GIST_ID" ]; then + # Try extracting from end of URL for short gist IDs + GIST_ID=$(echo "$DESIGN_DOC_URL" | sed 's|.*/||' | sed 's|[?#].*||') + fi + + if [ -z "$GIST_ID" ]; then + echo "PRECHECK FAILED: Could not extract gist ID from URL." + echo "URL: $DESIGN_DOC_URL" + exit 1 + fi + + # Verify gist is accessible + GIST_INFO=$(gh api "gists/$GIST_ID" --jq '.description // "Untitled"' 2>/dev/null) + + if [ -z "$GIST_INFO" ]; then + echo "PRECHECK FAILED: Unable to access design document gist." + echo "Gist ID: $GIST_ID" + echo "Ensure the gist exists and is public (or you have access)." + exit 1 + fi + + echo "Design document gist verified: $GIST_INFO" + echo "Gist ID: $GIST_ID" +else + echo "Skipping design document validation (not provided)." +fi +``` + +#### Precheck 6 — Verify API Types Exist + +```bash +echo "Checking if API types exist in the repository..." + +API_TYPES=$(find "$REPO_ROOT" -type f \( -name 'types*.go' -o -name '*_types.go' \) -not -path '*/vendor/*' -not -path '*/_output/*' -not -path '*/zz_generated*' | head -20) + +if [ -z "$API_TYPES" ]; then + echo "PRECHECK FAILED: No API type definitions found in the repository." + echo "You MUST run /oape:api-generate first to create the API types." + echo "The controller needs types to reconcile." + exit 1 +fi + +echo "Found API types:" +echo "$API_TYPES" | head -10 +``` + +#### Precheck 7 — Verify Clean Working Tree (Warning) + +```bash +if ! git diff --quiet || ! git diff --cached --quiet; then + echo "WARNING: Uncommitted changes detected in the working tree." + echo "It is recommended to commit or stash changes before generating controller code." + echo "Proceeding anyway..." + git status --short +else + echo "Working tree is clean." +fi +``` + +**If ALL prechecks above passed, proceed to Phase 1.** +**If ANY precheck FAILED (exit 1), STOP. Do NOT proceed further. Report the failure to the user.** + +--- + +### Phase 1: Detect Operator Framework Type + +Before generating code, determine which operator framework this repository uses. This affects code generation patterns. + +```bash +echo "Detecting operator framework type..." + +# Check for operator-sdk PROJECT file (kubebuilder/operator-sdk marker) +if [ -f "$REPO_ROOT/PROJECT" ]; then + echo "Found PROJECT file - this is an operator-sdk/kubebuilder project" + cat "$REPO_ROOT/PROJECT" +fi + +# Check go.mod for framework indicators +echo "Checking go.mod for framework dependencies..." + +# Check for library-go (OpenShift operators) +if grep -q "github.com/openshift/library-go" "$REPO_ROOT/go.mod"; then + echo "Found: github.com/openshift/library-go" +fi + +# Check for controller-runtime +if grep -q "sigs.k8s.io/controller-runtime" "$REPO_ROOT/go.mod"; then + echo "Found: sigs.k8s.io/controller-runtime" +fi +``` + +```thinking +Based on the dependency analysis, I must determine the OPERATOR_TYPE: + +**Type 1: controller-runtime based (kubebuilder/operator-sdk v1+)** +Indicators: +- Has `sigs.k8s.io/controller-runtime` in go.mod +- May have PROJECT file with `layout: go.kubebuilder.io` +- Uses `ctrl.Manager`, `Reconciler` pattern +- File structure: `controllers/` or `internal/controller/` + +**Type 2: library-go based (OpenShift core operators)** +Indicators: +- Has `github.com/openshift/library-go` in go.mod +- Uses `pkg/operator/` structure +- Uses `factory.Controller` and `SyncFunc` pattern +- Has `starter.go` for controller registration +- More imperative style, less declarative + +I will set OPERATOR_TYPE to one of: +- "controller-runtime" (most common, default) +- "library-go" (OpenShift core operators) + +If neither framework is detected, STOP and ask user for clarification. + +The generated code patterns will differ based on this type. +``` + +#### Framework Detection Rules + +Apply these rules in order: + +1. **If `github.com/openshift/library-go` is in go.mod AND `pkg/operator/` directory exists:** + - OPERATOR_TYPE = "library-go" + - Use SyncFunc pattern, factory.Controller + +2. **Else if `sigs.k8s.io/controller-runtime` is in go.mod:** + - OPERATOR_TYPE = "controller-runtime" + - Use Reconciler pattern, ctrl.Manager + +3. **Else:** + - STOP and ask user for clarification + - Only controller-runtime and library-go frameworks are supported + +--- + +### Phase 2: Refresh Knowledge — Fetch Latest Operator Conventions + +Fetch and read these documents based on OPERATOR_TYPE: + +**For controller-runtime:** +1. `https://book.kubebuilder.io/cronjob-tutorial/controller-implementation` +2. `https://pkg.go.dev/sigs.k8s.io/controller-runtime` + +**For library-go:** +1. `https://github.com/openshift/library-go/tree/master/pkg/controller` +2. `https://github.com/openshift/library-go/blob/master/pkg/operator/events/recorder.go` + +**For all types:** +1. `https://raw.githubusercontent.com/openshift/enhancements/master/dev-guide/operator.md` + +```thinking +Based on OPERATOR_TYPE, I must internalize the correct patterns: + +**For controller-runtime:** +- Reconcile(ctx, req) returns (ctrl.Result, error) +- Use client.Client for API operations +- Use controllerutil for finalizers, owner refs +- Use ctrl.Log or log.FromContext for logging +- Use mgr.GetEventRecorderFor for events + +**For library-go:** +- SyncFunc(ctx, syncContext) error pattern +- Use factory.Controller with Informers +- Use events.Recorder (library-go's recorder) +- Use klog for logging +- Explicit informer cache management +- Use resourceapply for resource creation + +I will apply the correct patterns based on detected type. +``` + +--- + +### Phase 3: Fetch and Parse Input Sources + +Fetch content from all provided input sources (Enhancement Proposal and/or Design Document). + +#### 3.1 Fetch Enhancement Proposal (if provided) + +```bash +if [ -n "$ENHANCEMENT_PR_NUMBER" ]; then + echo "Fetching files changed in enhancement PR #$ENHANCEMENT_PR_NUMBER..." + gh pr view "$ENHANCEMENT_PR_NUMBER" --repo openshift/enhancements --json files --jq '.files[].path' +fi +``` + +Fetch the full content of each proposal file: + +```bash +# For each enhancement .md file, fetch content +gh api "repos/openshift/enhancements/contents/<path-to-file>?ref=refs/pull/$ENHANCEMENT_PR_NUMBER/head" --jq '.content' | base64 -d +``` + +Fallback methods if above fails: + +```bash +# Fallback 1: Raw content +curl -sL "https://raw.githubusercontent.com/openshift/enhancements/refs/pull/$ENHANCEMENT_PR_NUMBER/head/<path-to-file>" + +# Fallback 2: PR diff +gh pr diff "$ENHANCEMENT_PR_NUMBER" --repo openshift/enhancements +``` + +#### 3.2 Fetch Design Document (if provided) + +```bash +if [ -n "$DESIGN_DOC_URL" ] && [ "$DESIGN_DOC_LOCAL" = true ]; then + echo "Reading local design document: $DESIGN_DOC_URL" + cat "$DESIGN_DOC_URL" +elif [ -n "$GIST_ID" ]; then + echo "Fetching design document from gist $GIST_ID..." + gh api "gists/$GIST_ID" --jq '.files | to_entries[] | "=== FILE: \(.key) ===\n\(.value.content)\n"' +fi +``` + +If the gist `gh api` command fails, try fetching via curl: + +```bash +curl -sL "https://api.github.com/gists/$GIST_ID" | jq -r '.files | to_entries[] | "=== FILE: \(.key) ===\n\(.value.content)\n"' +``` + +#### 3.3 Extract Structured Requirements + +```thinking +I MUST extract structured information from the input source(s). The approach depends on what was provided: + +**If BOTH Enhancement Proposal AND Design Document are provided:** +- The EP provides high-level context: motivation, constraints, affected components +- The Design Document provides implementation details: reconciliation workflow, dependent resources, controller behavior +- When both specify the same information, the Design Document takes precedence +- Extract from EP: component context, FeatureGate requirements, general constraints +- Extract from Design Document: exact reconciliation steps, dependent resources, status updates, events + +**If only Enhancement Proposal is provided:** +- Extract all requirements from the EP (original behavior) + +**If only Design Document is provided:** +- The Design Document must be comprehensive enough to generate controller code +- It should specify: API details, reconciliation workflow, dependent resources, status conditions + +From the combined sources, I will extract the following. For each item, I will search for specific sections, keywords, and patterns. + +## EXTRACTION CHECKLIST + +### A. API Information (Required) +Search sections: "API", "CRD", "Custom Resource", "API Extensions" +Extract: +- [ ] API Group (e.g., config.openshift.io, operator.openshift.io) +- [ ] API Version (v1, v1alpha1, v1beta1) +- [ ] Kind name (PascalCase, e.g., IngressController) +- [ ] Resource name (plural, lowercase, e.g., ingresscontrollers) +- [ ] Scope (Cluster or Namespaced) + +### B. Spec Fields → Controller Actions (Required) +Search sections: "Spec", "Configuration", "API" tables/structs +For EACH spec field, determine: +- [ ] Field name and type +- [ ] What controller action this field triggers +- [ ] Validation to perform +- [ ] Default behavior if not set + +### C. Reconciliation Workflow (Required) +Search sections: "Implementation", "Proposal", "Workflow", "Reconciliation", "Controller" +Extract ordered steps: +- [ ] Step 1: What to do first +- [ ] Step 2: What to do next +- [ ] ... continue for all steps +- [ ] What triggers re-reconciliation + +### D. Dependent Resources (Required for complete code) +Search sections: "Implementation", "Resources", "Managed Resources" +Keywords: "create", "deploy", "manage", "ConfigMap", "Secret", "Deployment", "Service" +For EACH dependent resource: +- [ ] Resource type (ConfigMap, Secret, Deployment, Service, etc.) +- [ ] Resource name pattern +- [ ] Namespace (same as CR, or specific namespace, or cluster-scoped) +- [ ] Content/spec to set +- [ ] When to create/update/delete + +### E. External Resources / Integrations (If applicable) +Search sections: "External", "Integration", "Cloud", "API calls" +Keywords: "AWS", "Azure", "GCP", "HTTP", "API", "external service" +For EACH external integration: +- [ ] External system name +- [ ] API/SDK to use +- [ ] Operations to perform +- [ ] Credentials handling + +### F. Status Conditions (Required) +Search sections: "Status", "Conditions", "Reporting" +Standard OpenShift conditions to consider: +- [ ] Available (true when functioning) +- [ ] Progressing (true when changes in progress) +- [ ] Degraded (true when errors) +For EACH condition: +- [ ] Condition Type name +- [ ] When to set True +- [ ] When to set False +- [ ] Reason codes +- [ ] Message templates + +### G. Status Fields (Beyond conditions) +Search sections: "Status", "Observed" +For EACH status field: +- [ ] Field name +- [ ] What it represents +- [ ] How to compute/observe it + +### H. Events to Record (Required) +Search sections: "Events", "Notifications", "Audit" +Keywords: "event", "notify", "record", "log" +For EACH event: +- [ ] Event type (Normal/Warning) +- [ ] Reason (short CamelCase) +- [ ] When to emit +- [ ] Message template + +### I. Error Handling (Required) +Search sections: "Errors", "Failure", "Retry", "Edge Cases" +Keywords: "error", "fail", "retry", "backoff", "timeout" +Extract: +- [ ] Transient errors (retry with backoff) +- [ ] Permanent errors (don't retry, set Degraded) +- [ ] Specific error conditions and handling + +### J. Cleanup / Deletion (Required if external resources) +Search sections: "Cleanup", "Deletion", "Finalizer", "Garbage Collection" +Keywords: "delete", "cleanup", "remove", "finalizer" +Extract: +- [ ] What to clean up on CR deletion +- [ ] Order of cleanup +- [ ] Finalizer name to use + +### K. Watches / Triggers (Required for reactive behavior) +Search sections: "Watch", "React", "Trigger", "Events" +Keywords: "watch", "react to", "when X changes", "trigger" +For EACH watch: +- [ ] Resource type to watch +- [ ] Filter/predicate (which resources) +- [ ] How to map to primary resource + +### L. Feature Gate (If applicable) +Search sections: "Feature Gate", "TechPreview", "Graduation" +Keywords: "feature gate", "tech preview", "alpha", "beta" +Extract: +- [ ] Feature gate name +- [ ] Behavior when disabled + +### M. RBAC Requirements (Derived) +Based on all above, compute: +- [ ] Primary resource: get, list, watch, update (status) +- [ ] Dependent resources: get, list, watch, create, update, patch, delete +- [ ] External resources watched: get, list, watch +- [ ] Events: create, patch + +**Source Merging Rules:** +When both EP and Design Document are provided: +1. Design Document takes precedence for implementation-specific details +2. EP provides context and constraints +3. If there are conflicts, prefer Design Document specifics +4. Document any significant conflicts in the output summary + +If ANY required section (A, B, C, F, I) is missing or ambiguous across ALL provided sources, I MUST stop and ask the user for clarification. I will NOT guess. +``` + +--- + +### Phase 4: Identify Target Paths for Controller Code + +Explore the current repo to determine the layout pattern: + +```bash +# Find existing controller files +find "$REPO_ROOT" -type f -name '*controller*.go' -not -path '*/vendor/*' -not -path '*/_output/*' | head -30 + +# Find main.go or manager setup +find "$REPO_ROOT" -type f -name 'main.go' -not -path '*/vendor/*' -not -path '*/_output/*' | head -10 + +# Find existing reconcilers +grep -r "func.*Reconcile\|func.*Sync" "$REPO_ROOT" --include='*.go' -l | grep -v vendor | grep -v _output | head -20 + +# Find controller registration +grep -r "SetupWithManager\|AddToManager\|NewController\|factory.New" "$REPO_ROOT" --include='*.go' -l | grep -v vendor | head -20 + +# For library-go, find starter.go +find "$REPO_ROOT" -type f -name 'starter.go' -not -path '*/vendor/*' | head -5 +``` + +#### Layout Patterns by OPERATOR_TYPE + +**controller-runtime layouts:** + +| Pattern | Controller Location | Registration | +|---------|---------------------|--------------| +| Standard | `controllers/<resource>_controller.go` | `main.go` | +| Internal | `internal/controller/<resource>_controller.go` | `cmd/main.go` | +| Nested | `internal/controller/<resource>/controller.go` | `internal/controller/setup.go` | + +**library-go layouts:** + +| Pattern | Controller Location | Registration | +|---------|---------------------|--------------| +| Standard | `pkg/operator/<resource>/<resource>_controller.go` | `pkg/operator/starter.go` | +| Flat | `pkg/operator/<resource>_controller.go` | `pkg/operator/operator.go` | + +--- + +### Phase 5: Read Existing Controller Code for Context + +#### 5.1 Find existing controller for the target resource + +```bash +# Search for an existing controller that already reconciles the target resource type. +# Check Reconcile/Sync methods, SetupWithManager, and For() calls for the target Kind. +grep -r "For(&.*<TargetKind>{}\|<TargetResources>()\|Reconcile.*<TargetKind>" "$REPO_ROOT" --include='*.go' -l | grep -v vendor | grep -v _output | head -10 +``` + +```thinking +CRITICAL: If an existing controller already reconciles this resource type, I MUST modify it +rather than create a new controller file. I will: +1. Read ALL files in the existing controller's package (controller.go, constants.go, helpers, resource builders) +2. Add new logic into the existing Reconcile/Sync flow +3. Add constants to existing constant files, not new ones +4. Add resource builders to existing builder files, not new ones + +I only create a NEW controller file when NO existing controller handles this resource type. +``` + +#### 5.2 Read existing controller patterns + +```bash +# Find sample controller based on OPERATOR_TYPE +if [ "$OPERATOR_TYPE" = "library-go" ]; then + SAMPLE_CONTROLLER=$(find "$REPO_ROOT/pkg" -type f -name '*controller*.go' -not -path '*/vendor/*' -not -name '*_test.go' | head -1) +else + SAMPLE_CONTROLLER=$(find "$REPO_ROOT" -type f -name '*controller*.go' -not -path '*/vendor/*' -not -path '*/_output/*' -not -name '*_test.go' | head -1) +fi + +if [ -n "$SAMPLE_CONTROLLER" ]; then + echo "Reading sample controller: $SAMPLE_CONTROLLER" +fi +``` + +```thinking +I MUST read existing controller(s) and extract these EXACT patterns to replicate: + +1. **Package name** - What package are controllers in? +2. **Import organization** - How are imports grouped? Aliases used? +3. **Struct fields** - What fields does the reconciler/controller struct have? +4. **Constructor pattern** - New<Resource>Controller() or direct struct init? +5. **Reconcile/Sync signature** - Exact method signature used +6. **Logging** - log.FromContext? klog? What format? +7. **Event recording** - How events are recorded +8. **Status updates** - Pattern for updating status +9. **Condition helpers** - Existing helper functions for conditions +10. **Resource creation** - How dependent resources are created +11. **Error handling** - How errors are wrapped and returned +12. **Constants** - Where constants are defined (same file, separate file) +13. **Resource builder files** - Are there template.go / builder files that construct Jobs, Deployments, etc.? Read ALL files in the controller package, not just the main controller file. +14. **Function signatures** - Do builder functions accept individual params or whole spec structs? Match their style when adding new parameters. + +I will replicate these patterns EXACTLY in generated code. + +**CRITICAL pre-generation checklist:** +- For each new spec field, find the existing file where its parent resource is built/used. Modify THAT file — do not create a new file. +- If constants already exist in a constants file, add new constants there. +- If the controller already has a builder function for the resource type (e.g., getJobTemplate, buildDeployment), add parameters to that function. +- Only create new files for genuinely new resource types or controllers that have no existing equivalent. +``` + +--- + +### Phase 6: Generate Controller Code + +Based on OPERATOR_TYPE and extracted requirements, generate the appropriate controller. + +--- + +#### 6.1 For controller-runtime Based Operators + +Generate file: `<target-path>/<resource>_controller.go` + +```go +/* +Copyright <year> Red Hat, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +... +*/ + +package <package> + +import ( + "context" + "fmt" + "time" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" + + <apigroupversion> "<module>/api/<version>" +) + +const ( + // <Resource>Finalizer is the finalizer for <Resource> cleanup + <Resource>Finalizer = "<group>.<resource>-finalizer" + + // Condition type constants + ConditionTypeAvailable = "Available" + ConditionTypeProgressing = "Progressing" + ConditionTypeDegraded = "Degraded" + + // Event reason constants + ReasonReconciling = "Reconciling" + ReasonReconcileComplete = "ReconcileComplete" + ReasonReconcileFailed = "ReconcileFailed" + ReasonCreated = "Created" + ReasonUpdated = "Updated" + ReasonDeleted = "Deleted" + + // Requeue intervals + DefaultRequeueInterval = 30 * time.Second + ErrorRequeueInterval = 5 * time.Second +) +``` + +**Generate RBAC markers dynamically based on extracted requirements:** + +```go +// RBAC for primary resource +//+kubebuilder:rbac:groups=<group>,resources=<resources>,verbs=get;list;watch +//+kubebuilder:rbac:groups=<group>,resources=<resources>/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=<group>,resources=<resources>/finalizers,verbs=update + +// RBAC for dependent resources (generated per-resource from extraction) +// Example: If enhancement requires creating ConfigMaps: +//+kubebuilder:rbac:groups=core,resources=configmaps,verbs=get;list;watch;create;update;patch;delete + +// Example: If enhancement requires creating Deployments: +//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete + +// RBAC for events +//+kubebuilder:rbac:groups=core,resources=events,verbs=create;patch +``` + +**Generate Reconciler struct:** + +```go +// <Resource>Reconciler reconciles a <Resource> object +type <Resource>Reconciler struct { + client.Client + Scheme *runtime.Scheme + Recorder record.EventRecorder +} +``` + +**Generate Reconcile method with ACTUAL logic (no TODOs):** + +```go +// Reconcile performs reconciliation for <Resource> +func (r *<Resource>Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + logger := log.FromContext(ctx).WithValues("<resource>", req.NamespacedName) + logger.Info("Starting reconciliation") + + // Step 1: Fetch the <Resource> instance + instance := &<apigroupversion>.<Resource>{} + if err := r.Get(ctx, req.NamespacedName, instance); err != nil { + if errors.IsNotFound(err) { + logger.Info("<Resource> not found, likely deleted") + return ctrl.Result{}, nil + } + logger.Error(err, "Failed to get <Resource>") + return ctrl.Result{}, err + } + + // Step 2: Check for deletion + if !instance.DeletionTimestamp.IsZero() { + logger.Info("Handling deletion") + return r.reconcileDelete(ctx, instance) + } + + // Step 3: Add finalizer if needed + if !controllerutil.ContainsFinalizer(instance, <Resource>Finalizer) { + logger.Info("Adding finalizer") + controllerutil.AddFinalizer(instance, <Resource>Finalizer) + if err := r.Update(ctx, instance); err != nil { + logger.Error(err, "Failed to add finalizer") + return ctrl.Result{}, err + } + return ctrl.Result{Requeue: true}, nil + } + + // Step 4: Set Progressing condition + r.setCondition(instance, ConditionTypeProgressing, metav1.ConditionTrue, + ReasonReconciling, "Reconciliation in progress") + r.setCondition(instance, ConditionTypeDegraded, metav1.ConditionFalse, + ReasonReconciling, "") + + // Step 5: Main reconciliation logic + if err := r.reconcile(ctx, instance); err != nil { + logger.Error(err, "Reconciliation failed") + + // Set degraded condition + r.setCondition(instance, ConditionTypeDegraded, metav1.ConditionTrue, + ReasonReconcileFailed, err.Error()) + r.setCondition(instance, ConditionTypeAvailable, metav1.ConditionFalse, + ReasonReconcileFailed, "Reconciliation failed") + r.setCondition(instance, ConditionTypeProgressing, metav1.ConditionFalse, + ReasonReconcileFailed, "") + + r.Recorder.Event(instance, corev1.EventTypeWarning, ReasonReconcileFailed, err.Error()) + + if statusErr := r.Status().Update(ctx, instance); statusErr != nil { + logger.Error(statusErr, "Failed to update status") + } + + // Requeue with backoff for transient errors + return ctrl.Result{RequeueAfter: ErrorRequeueInterval}, err + } + + // Step 6: Set success conditions + r.setCondition(instance, ConditionTypeAvailable, metav1.ConditionTrue, + ReasonReconcileComplete, "Successfully reconciled") + r.setCondition(instance, ConditionTypeProgressing, metav1.ConditionFalse, + ReasonReconcileComplete, "") + r.setCondition(instance, ConditionTypeDegraded, metav1.ConditionFalse, + ReasonReconcileComplete, "") + + instance.Status.ObservedGeneration = instance.Generation + + if err := r.Status().Update(ctx, instance); err != nil { + logger.Error(err, "Failed to update status") + return ctrl.Result{}, err + } + + r.Recorder.Event(instance, corev1.EventTypeNormal, ReasonReconcileComplete, + "Successfully reconciled <Resource>") + + logger.Info("Reconciliation complete") + return ctrl.Result{RequeueAfter: DefaultRequeueInterval}, nil +} +``` + +**Generate actual reconcile() method based on EP extraction:** + +```go +// reconcile performs the core reconciliation logic +// This method is generated based on the enhancement proposal requirements +func (r *<Resource>Reconciler) reconcile(ctx context.Context, instance *<apigroupversion>.<Resource>) error { + logger := log.FromContext(ctx) + + // ============================================================ + // STEP 1: Validate Spec + // (Generated based on validation requirements from EP) + // ============================================================ + if err := r.validateSpec(instance); err != nil { + return fmt.Errorf("spec validation failed: %w", err) + } + + // ============================================================ + // STEP 2: Reconcile Dependent Resources + // (Generated for EACH dependent resource extracted from EP) + // ============================================================ + + // Example: If EP requires a ConfigMap + if err := r.reconcileConfigMap(ctx, instance); err != nil { + return fmt.Errorf("failed to reconcile ConfigMap: %w", err) + } + + // Example: If EP requires a Secret + if err := r.reconcileSecret(ctx, instance); err != nil { + return fmt.Errorf("failed to reconcile Secret: %w", err) + } + + // Example: If EP requires a Deployment + if err := r.reconcileDeployment(ctx, instance); err != nil { + return fmt.Errorf("failed to reconcile Deployment: %w", err) + } + + // Example: If EP requires a Service + if err := r.reconcileService(ctx, instance); err != nil { + return fmt.Errorf("failed to reconcile Service: %w", err) + } + + // ============================================================ + // STEP 3: Check/Update External State + // (Generated if EP has external integrations) + // ============================================================ + + // Example: If EP requires external API calls + // if err := r.syncExternalState(ctx, instance); err != nil { + // return fmt.Errorf("failed to sync external state: %w", err) + // } + + // ============================================================ + // STEP 4: Update Status with Observed State + // (Generated based on status fields from EP) + // ============================================================ + if err := r.updateObservedStatus(ctx, instance); err != nil { + return fmt.Errorf("failed to update observed status: %w", err) + } + + logger.V(1).Info("All reconciliation steps completed successfully") + return nil +} +``` + +**Generate validateSpec based on EP:** + +```go +// validateSpec validates the <Resource> spec +func (r *<Resource>Reconciler) validateSpec(instance *<apigroupversion>.<Resource>) error { + // Generated validation based on EP requirements + // Example validations: + + // Required field validation + // if instance.Spec.RequiredField == "" { + // return fmt.Errorf("spec.requiredField is required") + // } + + // Enum validation + // validValues := []string{"Value1", "Value2", "Value3"} + // if !slices.Contains(validValues, string(instance.Spec.EnumField)) { + // return fmt.Errorf("spec.enumField must be one of: %v", validValues) + // } + + // Cross-field validation + // if instance.Spec.FieldA != "" && instance.Spec.FieldB == "" { + // return fmt.Errorf("spec.fieldB is required when spec.fieldA is set") + // } + + return nil +} +``` + +**Generate dependent resource reconcilers (for EACH resource from EP):** + +```go +// reconcileConfigMap ensures the ConfigMap exists with correct data +func (r *<Resource>Reconciler) reconcileConfigMap(ctx context.Context, instance *<apigroupversion>.<Resource>) error { + logger := log.FromContext(ctx) + + desired := r.buildConfigMap(instance) + + existing := &corev1.ConfigMap{} + err := r.Get(ctx, types.NamespacedName{ + Name: desired.Name, + Namespace: desired.Namespace, + }, existing) + + if errors.IsNotFound(err) { + // Create new ConfigMap + if err := controllerutil.SetControllerReference(instance, desired, r.Scheme); err != nil { + return fmt.Errorf("failed to set owner reference: %w", err) + } + + logger.Info("Creating ConfigMap", "name", desired.Name) + if err := r.Create(ctx, desired); err != nil { + return fmt.Errorf("failed to create ConfigMap: %w", err) + } + + r.Recorder.Event(instance, corev1.EventTypeNormal, ReasonCreated, + fmt.Sprintf("Created ConfigMap %s", desired.Name)) + return nil + } else if err != nil { + return fmt.Errorf("failed to get ConfigMap: %w", err) + } + + // Update if changed + if r.configMapNeedsUpdate(existing, desired) { + existing.Data = desired.Data + existing.BinaryData = desired.BinaryData + + logger.Info("Updating ConfigMap", "name", desired.Name) + if err := r.Update(ctx, existing); err != nil { + return fmt.Errorf("failed to update ConfigMap: %w", err) + } + + r.Recorder.Event(instance, corev1.EventTypeNormal, ReasonUpdated, + fmt.Sprintf("Updated ConfigMap %s", desired.Name)) + } + + return nil +} + +// buildConfigMap constructs the desired ConfigMap +func (r *<Resource>Reconciler) buildConfigMap(instance *<apigroupversion>.<Resource>) *corev1.ConfigMap { + return &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-config", instance.Name), + Namespace: instance.Namespace, + Labels: map[string]string{ + "app.kubernetes.io/name": "<resource>", + "app.kubernetes.io/instance": instance.Name, + "app.kubernetes.io/managed-by": "<resource>-controller", + }, + }, + Data: map[string]string{ + // Generated based on EP requirements + // Map spec fields to config data + }, + } +} + +// configMapNeedsUpdate checks if the ConfigMap needs updating +func (r *<Resource>Reconciler) configMapNeedsUpdate(existing, desired *corev1.ConfigMap) bool { + // Compare relevant fields + // Use reflect.DeepEqual or field-by-field comparison + return !reflect.DeepEqual(existing.Data, desired.Data) || + !reflect.DeepEqual(existing.BinaryData, desired.BinaryData) +} +``` + +**Generate similar methods for each dependent resource type (Deployment, Service, Secret, etc.)** + +**Generate deletion handler:** + +```go +// reconcileDelete handles cleanup when <Resource> is being deleted +func (r *<Resource>Reconciler) reconcileDelete(ctx context.Context, instance *<apigroupversion>.<Resource>) (ctrl.Result, error) { + logger := log.FromContext(ctx) + + r.Recorder.Event(instance, corev1.EventTypeNormal, ReasonDeleted, "Cleaning up resources") + + // ============================================================ + // Cleanup external resources (generated from EP) + // Owner references handle Kubernetes resources automatically + // ============================================================ + + // Example: If EP requires external cleanup + // if err := r.cleanupExternalResources(ctx, instance); err != nil { + // logger.Error(err, "Failed to cleanup external resources") + // // Don't remove finalizer - will retry + // return ctrl.Result{RequeueAfter: ErrorRequeueInterval}, err + // } + + // Remove finalizer + logger.Info("Removing finalizer") + controllerutil.RemoveFinalizer(instance, <Resource>Finalizer) + if err := r.Update(ctx, instance); err != nil { + logger.Error(err, "Failed to remove finalizer") + return ctrl.Result{}, err + } + + logger.Info("Cleanup complete") + return ctrl.Result{}, nil +} +``` + +**Generate status update helper:** + +```go +// updateObservedStatus updates status fields based on observed state +func (r *<Resource>Reconciler) updateObservedStatus(ctx context.Context, instance *<apigroupversion>.<Resource>) error { + // Generated based on status fields from EP + + // Example: Count ready replicas + // instance.Status.ReadyReplicas = r.countReadyReplicas(ctx, instance) + + // Example: Collect endpoint addresses + // instance.Status.Endpoints = r.collectEndpoints(ctx, instance) + + return nil +} + +// setCondition sets a condition on the status +func (r *<Resource>Reconciler) setCondition(instance *<apigroupversion>.<Resource>, conditionType string, status metav1.ConditionStatus, reason, message string) { + meta.SetStatusCondition(&instance.Status.Conditions, metav1.Condition{ + Type: conditionType, + Status: status, + ObservedGeneration: instance.Generation, + LastTransitionTime: metav1.Now(), + Reason: reason, + Message: message, + }) +} +``` + +**Generate SetupWithManager with watches:** + +```go +// SetupWithManager sets up the controller with the Manager +func (r *<Resource>Reconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&<apigroupversion>.<Resource>{}). + // Owned resources (generated from dependent resources in EP) + Owns(&corev1.ConfigMap{}). + Owns(&corev1.Secret{}). + Owns(&appsv1.Deployment{}). + Owns(&corev1.Service{}). + // External watches (generated from EP watch requirements) + // Watches( + // &corev1.Secret{}, + // handler.EnqueueRequestsFromMapFunc(r.mapSecretTo<Resource>), + // builder.WithPredicates(r.secretPredicate()), + // ). + WithEventFilter(predicate.GenerationChangedPredicate{}). + Complete(r) +} +``` + +--- + +#### 6.2 For library-go Based Operators + +Generate file: `pkg/operator/<resource>/<resource>_controller.go` + +```go +package <resource> + +import ( + "context" + "fmt" + "time" + + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" + + operatorv1 "github.com/openshift/api/operator/v1" + "github.com/openshift/library-go/pkg/controller/factory" + "github.com/openshift/library-go/pkg/operator/events" + "github.com/openshift/library-go/pkg/operator/v1helpers" + + <client> "<module>/pkg/generated/clientset/versioned" + <informers> "<module>/pkg/generated/informers/externalversions" +) + +// <Resource>Controller manages <Resource> resources +type <Resource>Controller struct { + client <client>.Interface + kubeClient kubernetes.Interface + operatorClient v1helpers.OperatorClient + eventRecorder events.Recorder +} + +// New<Resource>Controller creates a new controller +func New<Resource>Controller( + client <client>.Interface, + kubeClient kubernetes.Interface, + operatorClient v1helpers.OperatorClient, + informers <informers>.SharedInformerFactory, + eventRecorder events.Recorder, +) factory.Controller { + c := &<Resource>Controller{ + client: client, + kubeClient: kubeClient, + operatorClient: operatorClient, + eventRecorder: eventRecorder, + } + + return factory.New(). + WithSync(c.sync). + WithInformers( + informers.<Group>().V1().<Resources>().Informer(), + // Add more informers based on EP requirements + ). + ToController("<Resource>Controller", eventRecorder.WithComponentSuffix("<resource>-controller")) +} + +// sync is the main synchronization function +func (c *<Resource>Controller) sync(ctx context.Context, syncCtx factory.SyncContext) error { + klog.V(4).InfoS("Starting sync", "controller", "<Resource>Controller") + + // Get the resource + instance, err := c.client.<Group>V1().<Resources>(<namespace>).Get(ctx, <name>, metav1.GetOptions{}) + if errors.IsNotFound(err) { + klog.V(2).InfoS("Resource not found, skipping", "controller", "<Resource>Controller") + return nil + } + if err != nil { + return fmt.Errorf("failed to get <Resource>: %w", err) + } + + // Perform reconciliation + // Generated based on EP requirements + + // Update status conditions + _, _, err = v1helpers.UpdateStatus(ctx, c.operatorClient, v1helpers.UpdateConditionFn( + operatorv1.OperatorCondition{ + Type: "<Resource>Available", + Status: operatorv1.ConditionTrue, + Reason: "SyncComplete", + Message: "Successfully synchronized", + }, + )) + if err != nil { + return fmt.Errorf("failed to update status: %w", err) + } + + klog.V(4).InfoS("Sync complete", "controller", "<Resource>Controller") + return nil +} +``` + +--- + +### Phase 7: Register Controller with Manager + +#### 7.1 For controller-runtime + +Locate `main.go` or `cmd/main.go` and add: + +```go +// Add import +import ( + "<module>/internal/controller" // or controllers package +) + +// In main(), after manager creation: +if err = (&controller.<Resource>Reconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Recorder: mgr.GetEventRecorderFor("<resource>-controller"), +}).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "<Resource>") + os.Exit(1) +} +``` + +#### 7.2 For library-go + +Locate `pkg/operator/starter.go` and add: + +```go +// Add import +import ( + "<module>/pkg/operator/<resource>" +) + +// In RunOperator() or similar: +<resource>Controller := <resource>.New<Resource>Controller( + client, + kubeClient, + operatorClient, + informers, + eventRecorder, +) +``` + +--- + +### Phase 8: Generate Feature Gate Check (if applicable) + +If the enhancement specifies a FeatureGate, add at the start of Reconcile/Sync: + +**For controller-runtime:** + +```go +func (r *<Resource>Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + // Check feature gate + if !features.FeatureGates.Enabled(features.<FeatureGateName>) { + log.FromContext(ctx).V(2).Info("Feature gate disabled, skipping reconciliation", + "featureGate", "<FeatureGateName>") + return ctrl.Result{}, nil + } + // ... rest of reconcile +} +``` + +**For library-go:** + +```go +func (c *<Resource>Controller) sync(ctx context.Context, syncCtx factory.SyncContext) error { + if !featuregates.CurrentFeatureGates().Enabled(features.<FeatureGateName>) { + klog.V(2).InfoS("Feature gate disabled, skipping sync", "featureGate", "<FeatureGateName>") + return nil + } + // ... rest of sync +} +``` + +--- + +### Phase 9: Output Summary + +After generating all files, provide a comprehensive summary: + +```text +=== Controller Implementation Summary === + +Input Sources: + Enhancement PR: <url> (if provided) + Design Document: <gist-url> (if provided) + Enhancement Title: <title> (if EP provided) +Operator Type: <controller-runtime | library-go> + +Generated Files: + - <path/to/controller.go> — Main controller implementation + - <path/to/main.go> — Updated with controller registration + +Controller Details: + Package: <package name> + API Group: <group.openshift.io> + API Version: <version> + Kind: <KindName> + Controller Name: <Resource>Reconciler (or <Resource>Controller) + +Reconciliation Workflow: + 1. Validate spec + 2. <step from EP> + 3. <step from EP> + ... + N. Update status + +Dependent Resources Managed: + - ConfigMap: <name-pattern> — <purpose> + - Secret: <name-pattern> — <purpose> + - Deployment: <name-pattern> — <purpose> + - Service: <name-pattern> — <purpose> + +Status Conditions: + - Available: Set True when <criteria> + - Progressing: Set True during reconciliation + - Degraded: Set True on errors + +Events Recorded: + - Normal/Created: When resources are created + - Normal/Updated: When resources are updated + - Normal/ReconcileComplete: On successful reconciliation + - Warning/ReconcileFailed: On errors + +RBAC Permissions Generated: + - <group>/<resource>: get, list, watch + - <group>/<resource>/status: get, update, patch + - <group>/<resource>/finalizers: update + - core/configmaps: get, list, watch, create, update, patch, delete + - core/secrets: get, list, watch, create, update, patch, delete + - apps/deployments: get, list, watch, create, update, patch, delete + - core/services: get, list, watch, create, update, patch, delete + - core/events: create, patch + +Watches Configured: + - Primary: <Kind> + - Owned: ConfigMap, Secret, Deployment, Service + - External: <if any> + +Cleanup on Deletion: + - Kubernetes resources: Handled by owner references + - External resources: <if any> + +Feature Gate: <FeatureGateName> (if applicable) + +Source Conflicts Resolved: (if both EP and design doc provided) + - <item>: Used design doc specification (<reason>) + +Next Steps: + 1. Review the generated controller code + 2. Run 'make generate' to update generated code + 3. Run 'make manifests' to update RBAC/CRD manifests + 4. Run 'make build' to verify compilation + 5. Run 'make test' to run tests + 6. Run 'make lint' to check for issues +``` + +--- + +## Critical Failure Conditions + +The command MUST FAIL and STOP immediately if ANY of the following are true: + +1. **No input provided**: Neither an enhancement PR URL nor a design document URL was provided +2. **Invalid PR URL**: The provided EP URL is not a valid `openshift/enhancements` PR +3. **Invalid gist URL**: The provided design document URL is not a valid GitHub Gist +4. **Missing tools**: `gh`, `go`, `git`, or `make` not installed +5. **Not authenticated**: `gh` not authenticated +6. **Not an operator repo**: No go.mod or not a recognized operator type +7. **No API types**: API types don't exist (run `/oape:api-generate` first) +8. **Input not accessible**: Enhancement PR or design document cannot be fetched +9. **No implementation requirements**: Input sources don't describe controller behavior +10. **Ambiguous requirements**: Cannot determine reconciliation workflow from input sources +11. **Unsupported framework**: Repository does not use controller-runtime or library-go + +## Behavioral Rules + +1. **Never guess**: If input sources are ambiguous, STOP and ask the user for clarification +2. **Design document precedence**: When both EP and design document are provided, the design document takes precedence for implementation details +3. **Zero TODOs**: Generate actual implementation code, not placeholders +4. **Convention over proposal**: Apply framework best practices even if input sources differ +5. **Match existing patterns**: Replicate patterns from existing controllers in the repo +6. **Idempotent reconciliation**: Generated Reconcile() must be idempotent +7. **Minimal changes**: Only generate what the input sources require +8. **Surgical edits**: Preserve unrelated code when modifying files +9. **Status-first**: Always use Status().Update() for status changes +10. **Finalizer safety**: Add before external resources, remove after cleanup +11. **Event recording**: Record events for user-visible state changes +12. **Integrate, don't duplicate**: Most EPs add functionality to EXISTING controllers. Before creating any new controller file, search for an existing controller that already reconciles the same resource type. If one exists, modify it to add the new behavior — add logic to existing Reconcile/Sync methods, add helpers to existing resource-builder files, add constants to existing constant files. Only create a NEW controller when no existing controller handles the target resource type, or the EP explicitly specifies a new controller. +13. **No invented logic**: Do NOT add controller behavior (constants, helper files, resource builders) that the input sources do not describe. If the EP only adds API fields, only wire those fields into the existing reconciliation — do not create standalone feature files. +14. **Wire new fields into existing flows**: When the EP adds fields to an existing type, find the existing code that builds or uses that type (e.g., Job templates, Deployment specs, container env vars) and wire the new fields there. Do not create a separate file per new field — add the logic inline where the resource is already being constructed. +15. **Update scheme and imports**: When new API types are introduced (e.g., `routev1`, `networkingv1`), update `cmd/*/main.go` or equivalent entrypoints to register the new scheme and add the import. Also update any `pkg/client/` setup files that initialize shared schemes. +16. **Update existing resource builders**: If the repo has template/builder files that construct Jobs, Deployments, or other resources, modify those files to pass through new spec fields (e.g., add parameters to existing builder functions, add env vars to existing container specs). Do not create parallel builder files. + +## Arguments + +- `<enhancement-pr-url>` (optional if design-doc provided): GitHub PR URL to the OpenShift enhancement proposal + - Format: `https://github.com/openshift/enhancements/pull/<number>` + +- `--design-doc <gist-url>` (optional if EP provided): GitHub Gist URL containing detailed implementation specifications + - Supported formats: + - `https://gist.github.com/username/gist_id` + - `https://gist.github.com/gist_id` + - `https://gist.githubusercontent.com/username/gist_id/raw/...` + +**At least one input source (EP or design document) must be provided.** + +## Design Document Expected Format + +When using a design document for controller implementation, it should contain: + +```markdown +# Design Document: Feature Name + +## API Specification +- Group: config.openshift.io +- Version: v1 +- Kind: FeatureName + +## Reconciliation Workflow +1. Validate spec +2. Create/update ConfigMap with configuration +3. Deploy application Deployment +4. Expose via Service +5. Update status with observed state + +## Dependent Resources +- ConfigMap: <name>-config — stores configuration +- Deployment: <name>-deployment — runs the workload +- Service: <name>-service — exposes the workload + +## Status Conditions +- Available: true when Deployment is ready +- Progressing: true during reconciliation +- Degraded: true on errors + +## Events +- ReconcileComplete: successful reconciliation +- ReconcileFailed: reconciliation error +- Created/Updated: resource lifecycle events + +## Cleanup +- External resources: describe what to clean up +- Finalizer: <group>.<resource>-finalizer +``` + +## Prerequisites + +- **gh** (GitHub CLI) — installed and authenticated +- **go** — Go toolchain installed +- **git** — Git installed +- **make** — Make installed +- Must be run from within an OpenShift operator repository +- API types MUST exist (run `/oape:api-generate` first) + +## Exit Conditions + +- **Success**: Complete controller code generated with summary +- **Failure**: Clear error message with fix instructions diff --git a/.cursor/commands/e2e-generate.md b/.cursor/commands/e2e-generate.md new file mode 100644 index 000000000..f9bb1491b --- /dev/null +++ b/.cursor/commands/e2e-generate.md @@ -0,0 +1,579 @@ +--- +name: /oape:e2e-generate +id: oape-e2e-generate +category: OAPE +description: Generate e2e test artifacts (test cases, execution steps, and test code) for any OpenShift operator based on git diff from a base branch +argument-hint: "<base-branch> [--output <path>]" +--- + +## Name +oape:e2e-generate + +## Synopsis +``` +/oape:e2e-generate <base-branch> [--output <path>] +``` + +## Description + +Analyzes the current OpenShift operator repository and generates **all e2e test artifacts** based on the diff between `<base-branch>` and HEAD: + +1. **test-cases.md** — Test scenarios with operator context, prerequisites, install steps, CR deployment, diff-specific test cases, verification, and cleanup. +2. **execution-steps.md** — Step-by-step procedure with executable `oc` commands. +3. **e2e test code** — Go (Ginkgo) or bash script, matching the repo's existing e2e pattern. +4. **e2e-suggestions.md** — Which scenarios apply to this diff, with recommendations. + +All files are written into **one output directory**: `<output-dir>/e2e_<repo-name>/`. Default `<output-dir>` is `output` (create if missing). + +- **Generic**: Works with any OpenShift operator repository (controller-runtime or library-go). +- **Discovery-based**: All operator structure (API types, CRDs, namespaces, install mechanism, e2e patterns) is discovered from the repo. Nothing is hardcoded. +- **Diff-driven**: Tests are focused on what changed between the base branch and HEAD. + +## Skills + +Read and follow **e2e-test-generator** (`.cursor/skills/e2e-test-generator/SKILL.md`). +Fixture templates live under `.cursor/e2e-test-generator/fixtures/` (linked as `../e2e-test-generator/fixtures/` from this command file). + +## Implementation + +### Phase 0: Prechecks + +All prechecks must pass before proceeding. If ANY precheck fails, STOP immediately and report the failure. + +#### Precheck 1 — Validate Arguments + +```bash +BASE_BRANCH="$1" + +if [ -z "$BASE_BRANCH" ]; then + echo "PRECHECK FAILED: No base branch provided." + echo "Usage: /oape:e2e-generate <base-branch> [--output <path>]" + exit 1 +fi + +# Parse optional --output +OUTPUT_DIR="output" +if echo "$@" | grep -q '\-\-output'; then + OUTPUT_DIR=$(echo "$@" | sed 's/.*--output[ =]*//' | awk '{print $1}') +fi + +echo "Base branch: $BASE_BRANCH" +echo "Output directory: $OUTPUT_DIR" +``` + +#### Precheck 2 — Verify Required Tools + +```bash +MISSING_TOOLS="" + +if ! command -v git &> /dev/null; then + MISSING_TOOLS="$MISSING_TOOLS git" +fi + +if ! command -v go &> /dev/null; then + MISSING_TOOLS="$MISSING_TOOLS go" +fi + +if [ -n "$MISSING_TOOLS" ]; then + echo "PRECHECK FAILED: Missing required tools:$MISSING_TOOLS" + exit 1 +fi + +# oc is recommended but not required for generation +if ! command -v oc &> /dev/null; then + echo "WARNING: oc not found. Generated execution steps require oc to run." +fi + +echo "Required tools available." +``` + +#### Precheck 3 — Verify Current Repository is a Valid OpenShift Operator Repo + +```bash +if ! git rev-parse --is-inside-work-tree &> /dev/null 2>&1; then + echo "PRECHECK FAILED: Not inside a git repository." + exit 1 +fi + +REPO_ROOT=$(git rev-parse --show-toplevel) +echo "Repository root: $REPO_ROOT" + +if [ ! -f "$REPO_ROOT/go.mod" ]; then + echo "PRECHECK FAILED: No go.mod found at repository root." + echo "This command must be run from within a Go-based OpenShift operator repository." + exit 1 +fi + +GO_MODULE=$(head -1 "$REPO_ROOT/go.mod" | awk '{print $2}') +REPO_NAME=$(basename "$GO_MODULE") +echo "Go module: $GO_MODULE" +echo "Repo name: $REPO_NAME" + +# Detect framework +HAS_CR=false +HAS_LIBGO=false +grep -q "sigs.k8s.io/controller-runtime" "$REPO_ROOT/go.mod" && HAS_CR=true +grep -q "github.com/openshift/library-go" "$REPO_ROOT/go.mod" && HAS_LIBGO=true + +if [ "$HAS_CR" = true ]; then + FRAMEWORK="controller-runtime" +elif [ "$HAS_LIBGO" = true ]; then + FRAMEWORK="library-go" +else + echo "PRECHECK FAILED: Cannot determine operator framework." + echo "go.mod does not reference sigs.k8s.io/controller-runtime or github.com/openshift/library-go." + exit 1 +fi + +echo "Detected framework: $FRAMEWORK" +``` + +#### Precheck 4 — Validate Git Diff is Non-Empty + +```bash +if ! git rev-parse --verify "$BASE_BRANCH" &> /dev/null 2>&1; then + echo "PRECHECK FAILED: Base branch '$BASE_BRANCH' does not exist." + echo "Available branches:" + git branch -a | head -20 + exit 1 +fi + +DIFF_STAT=$(git diff "$BASE_BRANCH"...HEAD --stat 2>/dev/null) + +if [ -z "$DIFF_STAT" ]; then + echo "PRECHECK FAILED: No changes detected between '$BASE_BRANCH' and HEAD." + exit 1 +fi + +echo "Changes detected:" +echo "$DIFF_STAT" +``` + +#### Precheck 5 — Verify Clean Working Tree (Warning) + +```bash +if ! git diff --quiet || ! git diff --cached --quiet; then + echo "WARNING: Uncommitted changes detected in the working tree." + echo "Generated tests will be based on committed changes only (diff between $BASE_BRANCH and HEAD)." + echo "Proceeding anyway..." +else + echo "Working tree is clean." +fi +``` + +**If ALL prechecks above passed, proceed to Phase 1.** +**If ANY precheck FAILED (exit 1), STOP. Do NOT proceed further.** + +--- + +### Phase 1: Framework Detection and Repository Discovery + +This phase discovers the operator's structure. All subsequent phases use this discovered information — never hardcoded values. + +#### Step 1.1: Discover API Types + +```bash +find "$REPO_ROOT" -type f \( -name '*_types.go' -o -name 'types_*.go' \) \ + -not -path '*/vendor/*' -not -path '*/_output/*' -not -path '*/zz_generated*' | head -40 +``` + +For each types file found, read it and extract: +- API group and version (from `// +groupName=...` marker or `GroupVersion` var) +- Kind names (struct names with `metav1.TypeMeta` embedded) +- Spec and Status field names +- Condition types (from constants or status field types) +- Whether CRs are cluster-scoped or namespaced (from `// +kubebuilder:resource:scope=...` marker) + +If **no types files found** in the repo (common with library-go operators like `secrets-store-csi-driver-operator`): +- Check `go.mod` for `github.com/openshift/api` dependency +- Note that API types are external +- Look in vendor directory or CRD manifests to understand the managed kinds + +```thinking +I must build a complete picture of the API types this operator manages, whether defined in-repo +or imported from external modules. This is critical for generating accurate test code that +references the correct types, fields, and conditions. +``` + +#### Step 1.2: Discover CRDs + +```bash +find "$REPO_ROOT" -type f -name '*.yaml' \( -path '*/crd/*' -o -path '*/crds/*' -o -path '*/manifests/*' \) \ + -not -path '*/vendor/*' | head -30 +``` + +For each CRD file, extract: Kind, group, plural resource name, scope (Cluster/Namespaced), served versions. + +#### Step 1.3: Discover Existing E2E Test Patterns + +```bash +# Look for Go-based e2e tests +find "$REPO_ROOT" -type f -name '*_test.go' -path '*/e2e/*' -not -path '*/vendor/*' | head -20 + +# Look for bash-based e2e tests +find "$REPO_ROOT" -type f -name '*.sh' \( -path '*/e2e/*' -o -path '*/hack/e2e*' \) -not -path '*/vendor/*' | head -10 +``` + +If Go test files found with Ginkgo imports (`onsi/ginkgo`): +- Read 1-2 existing e2e test files to understand: package name, import paths, client variable names, helper utilities used, assertion patterns, test structure +- Read `utils/constants.go` (if exists) for namespace, deployment names, timeouts +- Read `utils/utils.go` (if exists) for helper function signatures + +If bash scripts found: +- Read the script to understand: namespace usage, test structure (functions vs sequential), assertion patterns, cleanup approach + +If **no existing e2e tests found**: +- Default to Ginkgo for controller-runtime repos, bash for library-go repos +- Use the plugin's fixture examples as templates: + - [fixtures/e2e-sample-controller-runtime_test.go.example](../e2e-test-generator/fixtures/e2e-sample-controller-runtime_test.go.example) for Ginkgo + - [fixtures/e2e-sample-library-go_test.sh.example](../e2e-test-generator/fixtures/e2e-sample-library-go_test.sh.example) for bash + +#### Step 1.4: Discover Install Mechanism + +```bash +# Look for OLM manifests +find "$REPO_ROOT" -type f -name '*.yaml' \ + \( -path '*/config/manifests/*' -o -path '*/bundle/*' \) \ + -not -path '*/vendor/*' | head -20 + +# Look for deployment manifests +find "$REPO_ROOT" -type f -name '*.yaml' \ + \( -path '*/config/default/*' -o -path '*/deploy/*' \) \ + -not -path '*/vendor/*' | head -20 +``` + +From OLM manifests, extract: +- Package name (from `*.package.yaml`) +- Channel name (stable, alpha, etc.) +- CSV name and version +- Install namespace +- Subscription details + +If no OLM manifests found, note that install is manual (deployment-based). + +#### Step 1.5: Discover Sample CRs + +```bash +find "$REPO_ROOT" -type f -name '*.yaml' \ + \( -path '*/config/samples/*' -o -path '*/examples/*' \) \ + -not -path '*/vendor/*' | head -20 +``` + +Read sample CR files to understand: which CR kinds have samples, default field values, required environment variables (e.g., `${APP_DOMAIN}`). + +#### Step 1.6: Discover Operator Namespace + +Search for namespace in this order: +1. E2E constants file (`utils/constants.go` or similar): look for `Namespace` const or var +2. CSV manifest: look for `metadata.annotations["operatorframework.io/suggested-namespace"]` +3. Namespace YAML in config/manifests or deploy/ +4. If not found, note as `<operator-namespace>` placeholder + +#### Step 1.7: Discover Controllers + +```bash +find "$REPO_ROOT" -type f -name '*.go' \ + \( -name '*controller*' -o -name '*reconcile*' -o -name 'starter.go' \) \ + -not -path '*/vendor/*' -not -name '*_test.go' | head -20 +``` + +Read controller files to understand: which CRs are reconciled, what resources are managed (Deployments, StatefulSets, DaemonSets, ConfigMaps), condition update logic. + +#### Step 1.8: Build Repo Profile + +```thinking +I now have a complete repo profile. I will summarize: +- Framework: controller-runtime or library-go +- Go module: <module path> +- Repo name: <basename> +- API types: list of {Kind, group, version, scope, key fields, conditions} +- Types location: in-repo (api/) or external (openshift/api) +- CRDs: list of {Kind, group, plural, scope} +- E2E pattern: Ginkgo (package name, imports, clients, helpers) or bash (script path, test structure) +- Install mechanism: OLM (package, channel, CSV, namespace) or manual +- Samples: list of sample CR files with their kinds +- Operator namespace: discovered or placeholder +- Controllers: list of reconciled CRs and managed resources +- Managed workloads: Deployments, StatefulSets, DaemonSets managed by the operator + +This profile drives all subsequent generation phases. I will NOT use any hardcoded values. +``` + +--- + +### Phase 2: Analyze Git Diff + +```bash +# Get the diff stat +git diff "$BASE_BRANCH"...HEAD --stat + +# Get the full diff +git diff "$BASE_BRANCH"...HEAD -p + +# Get the commit log +git log "$BASE_BRANCH"...HEAD --oneline +``` + +Categorize each changed file: + +| File Pattern | Category | Test Focus | +|---|---|---| +| `api/**/*_types.go`, `types_*.go` | API Types | New/changed fields, validation, conditions | +| `config/crd/**/*.yaml` | CRD Changes | Schema updates, new versions | +| `*controller*.go`, `*reconcile*.go`, `starter.go` | Controller | Reconciliation logic, conditions, managed resources | +| `config/rbac/*.yaml` | RBAC | Permission changes | +| `config/samples/*.yaml` | Samples | Example CR usage, default values | +| `test/e2e/**` | E2E Tests | Existing test patterns (do not duplicate) | +| `assets/**` | Embedded Assets | Resource deployment changes | +| `Makefile`, `Dockerfile*` | Build | Build/deploy changes (no direct test) | + +For each changed file in the API Types and Controller categories, read the diff hunks to understand the specific changes: new fields, modified conditions, new reconciliation logic, changed status updates. + +```thinking +I must map each meaningful change to a specific test scenario. New API fields need CR create/update +tests. New conditions need condition-check tests. Controller changes need reconciliation and +recovery tests. I will focus tests on what actually changed, not generate tests for unchanged code. +``` + +--- + +### Phase 3: Generate test-cases.md + +Write **test-cases.md** into the output directory. Content must be based entirely on discovered repo profile and diff analysis. + +**Structure:** + +```markdown +# E2E Test Cases: <repo-name> + +## Operator Information +- **Repository**: <go-module> +- **Framework**: <controller-runtime|library-go> +- **API Group**: <discovered-api-group> +- **Managed CRDs**: <list of Kind names> +- **Operator Namespace**: <discovered-namespace> +- **Changes Analyzed**: git diff <base-branch>...HEAD + +## Prerequisites +- OpenShift cluster with admin access +- `oc` CLI installed and authenticated +- <any env vars discovered from samples, e.g., APP_DOMAIN> + +## Installation +<Based on discovered install mechanism: OLM subscription or manual deployment> +<Include oc apply, oc wait commands using actual discovered values> + +## CR Deployment +<Based on discovered sample CRs> +<Include oc apply commands for each sample CR> + +## Test Cases + +### <Category from diff: e.g., "API Type Changes"> +<For each change detected, describe the test scenario> +- **Test**: <what to test> +- **Steps**: <oc commands or programmatic steps> +- **Expected**: <expected outcome> + +### <Category: e.g., "Controller Changes"> +... + +## Verification +<oc get, oc wait, oc describe commands for all managed CRs> +<oc logs for operator namespace> + +## Cleanup +<Reverse order deletion of CRs> +<OLM cleanup if applicable: subscription, CSV, operatorgroup, namespace> +``` + +--- + +### Phase 4: Generate execution-steps.md + +Write **execution-steps.md** into the output directory. + +**Structure:** + +```markdown +# E2E Execution Steps: <repo-name> + +## Prerequisites + +```bash +which oc +oc version +oc whoami +oc get nodes +oc get clusterversion +<packagemanifests check if OLM install> +``` + +## Environment Variables + +```bash +<Any env vars discovered from samples or e2e constants> +<e.g., export APP_DOMAIN=apps.$(oc get dns cluster -o jsonpath='{.spec.baseDomain}')> +``` + +## Step 1: Install Operator + +```bash +<oc apply or OLM subscription commands using discovered values> +<oc wait for CSV, deployment> +<oc get pods -n <namespace>> +``` + +## Step 2: Deploy CRs + +```bash +<oc apply for each sample CR, with envsubst if needed> +``` + +## Step 3: Verify Installation + +```bash +<oc get for each managed CR kind> +<oc wait for conditions> +``` + +## Step 4: Diff-Specific Tests + +```bash +<Specific oc commands to exercise changes detected in the diff> +``` + +## Step 5: Cleanup + +```bash +<oc delete for CRs in reverse order> +<oc delete subscription, csv, operatorgroup if OLM> +<oc delete namespace> +``` +``` + +--- + +### Phase 5: Generate E2E Test Code + +Generate test code that matches the repo's existing e2e pattern. + +#### Path A — Ginkgo (controller-runtime repos) + +Generate a file named **`e2e_test.go`** in the output directory. + +Requirements: +- **Package**: Same as discovered existing e2e package (usually `e2e`) +- **Imports**: Match existing e2e import style. Include only needed imports. Use the actual operator API import path (discovered in Phase 1). +- **Clients**: Use the same client variables as existing tests (`k8sClient`, `clientset`, etc.). Do not redefine them. +- **Helpers**: Use discovered helper utilities (`utils.WaitFor*`, `utils.OperatorNamespace`, etc.). If helpers don't exist for the needed operations, write inline test logic. +- **Structure**: `Describe`/`Context`/`It` with `By("...")` steps. +- **No suite logic**: Do not include `BeforeSuite`, `TestE2E`, or client setup — only test blocks. +- **Comments**: Each `It` block prefixed with `// Diff-suggested: <reason based on diff>` for pick-and-choose. +- **Content**: Include both (a) important scenarios relevant to the diff (see [fixtures/e2e-important-scenarios.md](../e2e-test-generator/fixtures/e2e-important-scenarios.md)) and (b) diff-specific tests derived from Phase 2 analysis. + +For code style reference, see [fixtures/e2e-sample-controller-runtime_test.go.example](../e2e-test-generator/fixtures/e2e-sample-controller-runtime_test.go.example). + +#### Path B — Bash (library-go repos) + +Generate a file named **`e2e_test.sh`** in the output directory. + +Requirements: +- **Header**: `#!/usr/bin/env bash` with `set -euo pipefail` +- **Configuration**: Variables for namespace, deployment name, labels, timeout — all using discovered values +- **Test functions**: `test_<scenario>()` for each test case +- **Assertions**: `oc` commands with exit code checks +- **Cleanup**: `trap cleanup EXIT` function +- **Comments**: Each test function prefixed with `# Diff-suggested: <reason>` +- **Content**: Include operator install verification, CR lifecycle, diff-specific tests, cleanup + +For code style reference, see [fixtures/e2e-sample-library-go_test.sh.example](../e2e-test-generator/fixtures/e2e-sample-library-go_test.sh.example). + +--- + +### Phase 6: Generate e2e-suggestions.md + +Write **e2e-suggestions.md** into the output directory. + +**Content:** + +- Summary of detected operator structure (framework, managed CRDs, e2e pattern) +- List of changes detected in the diff +- For each change, which e2e scenarios are **highly recommended** +- Which additional scenarios are **optional/nice-to-have** +- Any gaps: areas of the diff that are hard to test automatically + +--- + +### Phase 7: Output Summary + +After generating all files, confirm output: + +``` +=== E2E Test Generation Summary === + +Repository: <go-module> +Framework: <controller-runtime|library-go> +Base Branch: <base-branch> +Changes Analyzed: <N files changed, M insertions, K deletions> + +Generated Files: + - <output-dir>/e2e_<repo-name>/test-cases.md + - <output-dir>/e2e_<repo-name>/execution-steps.md + - <output-dir>/e2e_<repo-name>/e2e_test.go (or e2e_test.sh) + - <output-dir>/e2e_<repo-name>/e2e-suggestions.md + +Repo Profile: + API Types: <list of Kind names or "external (openshift/api)"> + CRDs: <list of CRD names> + E2E Pattern: <Ginkgo|bash|none (generated from template)> + Operator Namespace: <namespace> + Install Mechanism: <OLM|manual> + +Next Steps: + 1. Review generated test cases and suggestions + 2. Copy e2e test code into the repo's test/e2e/ directory (or hack/) + 3. Adjust placeholder values if any remain + 4. Run tests against a live cluster +``` + +## Arguments + +- **$1 (base-branch)**: Git branch or ref to diff against — e.g., `main`, `origin/main`, `release-4.18`, `ai-staging`. Required. +- **--output**: Output base directory (optional). Default: `output`. Generated files go in `<output>/e2e_<repo-name>/`. + +## Examples + +``` +# From within an operator repo, generate e2e tests for changes since main +/oape:e2e-generate main + +# Use a specific base branch and custom output directory +/oape:e2e-generate origin/release-4.18 --output .work + +# For repos with ai-staging branches (from repos.txt) +/oape:e2e-generate ai-staging + +# With a remote tracking branch +/oape:e2e-generate origin/ai-staging-release-1.1 --output test-output +``` + +## Notes + +- **Generic**: Works with any OpenShift operator repository. No operator-specific knowledge is hardcoded. +- **Discovery-based**: All operator structure is discovered from the repository. If something cannot be discovered, it is noted as a placeholder in the generated output. +- **Diff-focused**: Only generates tests relevant to the changes between the base branch and HEAD. Does not generate tests for unchanged code. +- **Framework-aware**: Detects controller-runtime vs library-go and generates the appropriate test format. +- **No browser tools**: Works entirely locally with git and file reads. No `gh` CLI or browser navigation required. +- **Pick-and-choose**: Generated test code is commented so the user can copy only the blocks they need. +- **Existing patterns respected**: If the repo has existing e2e tests, the generated code follows the same style, imports, and conventions. + +## Behavioral Rules + +1. **Never hardcode**: All operator-specific values (namespaces, CR kinds, API groups, conditions) must be discovered from the repo. Never use hardcoded values from any specific operator. +2. **Match existing style**: If the repo has existing e2e tests, generated code must match the same package name, import style, client variables, helper usage, and assertion patterns. +3. **Diff-driven focus**: Only generate tests for code that changed. Do not generate tests for unchanged functionality. +4. **Fail on ambiguity**: If the repo structure is ambiguous (e.g., cannot determine framework, no API types and no CRDs found), STOP and ask the user for clarification. +5. **Minimal placeholders**: Replace as many placeholders as possible with discovered values. Only leave placeholders for values that genuinely cannot be determined from the repo. +6. **No duplicate suite logic**: For Ginkgo tests, do not generate BeforeSuite, TestE2E, or client setup. Only generate test blocks (Describe/Context/It). +7. **Correct cleanup order**: Always generate cleanup in reverse dependency order — CRs first, then OLM resources, then namespace. diff --git a/.cursor/commands/eval-loop.md b/.cursor/commands/eval-loop.md new file mode 100644 index 000000000..c59c5be40 --- /dev/null +++ b/.cursor/commands/eval-loop.md @@ -0,0 +1,115 @@ +--- +name: /eval-loop +id: eval-loop +category: Eval Pipeline +description: Run full retrospective eval loop for one feature bundle (Epic Bug Analysis → Eval Generation → baseline) +--- + +Run the **complete eval improvement loop** for whatever is currently in `evals/inputs/`. + +One command. One feature bundle. When done, paste the next bundle into `evals/inputs/` and run again. + +## What this command does + +``` +1. Validate evals/inputs/ (stop if PASTE_ placeholders remain) +2. Load evals/baseline/ + refined-templates/ (round 2+) +3. Epic Bug Analysis → evals/outputs/epic-bug-analysis/* +4. Eval Generation + a. Identify template gaps → evals/outputs/eval-generation/template-gaps.md + b. Apply patchable gaps → refine templates in evals/refined-templates/ + c. Merge eval cases per stage → evals/baseline/evals/<stage>/<stage>_eval.yaml + d. Create code-generation evals → evals/baseline/evals/code-generation/code-generation_eval.yaml + e. Sync flat stage evals → schemas/openspec-agile-workflow/evals/<stage>_eval.yaml + f. Update registry + round snapshot +5. Update round-state → increment round, snapshot under baseline/rounds/ +``` + +## Before running + +Fill **all** files under `evals/inputs/`: + +| File | Paste | +|------|-------| +| `feature-meta.yaml` | Feature name, epic key (optional) | +| `01-ep-ard.md` | EP link + content | +| `02-jira-epic.md` | Epic export | +| `03-original-repo.md` | Pre-feature repo pin | +| `04-user-stories.md` | Stories | +| `05-repo-prs.md` | PR links | +| `bugs/index.yaml` | Bug keys | +| `bugs/<KEY>.md` | One file per bug | + +Remove or rename `bugs/PASTE_BUG_KEY_1.md` when adding real bug files. + +## Agent instructions + +1. Read `evals/pipeline.yaml` for phase order and paths. +2. Read **`evals/epic-bug-analysis/SYSTEM_PROMPT.md`** — execute Epic Bug Analysis fully. +3. Read **`evals/eval-generation/SYSTEM_PROMPT.md`** — execute Eval Generation fully. +4. Do **not** stop between Epic Bug Analysis and Eval Generation unless the user explicitly asks. + +### Template path (eval workflow) + +| Read / write | Path | +|--------------|------| +| **Eval pipeline templates** | `evals/refined-templates/` only | +| **Do NOT use during eval** | `schemas/openspec-agile-workflow/templates/` | + +Seed `evals/refined-templates/` from `schemas/` once on round 1 if empty. All refinements go to `evals/refined-templates/`. + +### Consolidated eval files + +One YAML per stage — all cases in `evals:` list: + +- `evals/baseline/evals/repo-assessment/repo-assessment_eval.yaml` +- `evals/baseline/evals/constitution/constitution_eval.yaml` +- `evals/baseline/evals/plan/plan_eval.yaml` +- `evals/baseline/evals/tasks/tasks_eval.yaml` +- `evals/baseline/evals/implementation/implementation_eval.yaml` +- `evals/baseline/evals/code-generation/code-generation_eval.yaml` + +Also sync each merged file to **`schemas/openspec-agile-workflow/evals/<stage>_eval.yaml`** with `template: templates/<name>.md` (forward `/opsx-continue` reads from installed `openspec/schemas/.../evals/`). **code-generation** has no template — sync `code-generation_eval.yaml` for `/opsx-apply` per-task gate. + +Do **not** write scattered `eval-r001-*.yaml` per-case files. + +### Feedback loop (critical) + +| Asset | Round 1 | Round 2+ | +|-------|---------|----------| +| `evals/baseline/evals/<stage>/<stage>_eval.yaml` | Empty → populated | **Read + merge** | +| `evals/baseline/evals-registry.yaml` | Initialized | **Read + append** | +| `evals/refined-templates/` | Seed from schemas → refine | Read **refined** copies → refine again | +| `evals/baseline/routing-learnings.md` | Placeholder → updated | **Read + update** | + +Epic Bug Analysis on round 2+ must cross-check bugs against prior evals in `*_eval.yaml` files. + +## Outputs + +| Location | Content | +|----------|---------| +| `evals/outputs/epic-bug-analysis/` | pattern-analysis, rca-summary, issue-taxonomy | +| `evals/outputs/eval-generation/` | template-gaps, validation-refinements, patches | +| `evals/baseline/evals/<stage>/<stage>_eval.yaml` | Consolidated eval cases per stage | +| `schemas/openspec-agile-workflow/evals/<stage>_eval.yaml` | Forward workflow stage evals (synced from baseline) | +| `evals/baseline/rounds/round-N/` | Round snapshot | +| `evals/refined-templates/*.md` | Refined templates (eval workflow source of truth) | +| `evals/outputs/eval-generation/refinement-patches/` | Diff summary per patched template | +| `evals/baseline/refinement-changelog.md` | Append-only template change log | +| `evals/round-state.yaml` | Incremented round | + +## After completion + +Tell the user: + +> Loop complete (round N). Review `evals/baseline/` and `evals/refined-templates/`. Replace `evals/inputs/` with the next feature bundle and run `/eval-loop` again. + +## Guardrails + +- Do not use `/opsx-*` commands in this pipeline +- Do not create feature-specific case folders — only generic `evals/inputs/` +- Do not patch `schemas/openspec-agile-workflow/templates/` during eval — use `evals/refined-templates/` +- Do not mark template-gaps Fixed unless `evals/refined-templates/` was actually patched +- Write all eval cases into `<stage>_eval.yaml` — not per-case files +- Do not delete prior eval cases without explicit user approval +- Process bugs one at a time during Epic Bug Analysis diff --git a/.cursor/commands/implement-review-fixes.md b/.cursor/commands/implement-review-fixes.md new file mode 100644 index 000000000..b3b8a4c77 --- /dev/null +++ b/.cursor/commands/implement-review-fixes.md @@ -0,0 +1,212 @@ +--- +name: /oape:implement-review-fixes +id: oape-implement-review-fixes +category: OAPE +description: Automatically applies code fixes from an oape:review report, prioritized by severity +argument-hint: <review_report_json> +--- + +## Name +oape:implement-review-fixes + +## Synopsis +``` +/oape:implement-review-fixes <review_report_json> +``` + +## Description + +The `oape:implement-review-fixes` command takes the JSON report produced by `/oape:review` and automatically applies every suggested fix to the codebase. Issues are processed in severity order (CRITICAL first) so the highest-impact problems are resolved even if a later fix fails. + +This command is invoked automatically at the end of `/oape:review` when the report contains issues. It can also be run standalone by passing a review report directly. + +## Arguments + +- `$1` (review_report_json): The full JSON review report produced by `/oape:review`. **Required.** + +## Implementation + +### Step 1: Parse and Validate the Review Report + +Extract the `issues` array from the review report JSON. + +```thinking +I must parse the review report and extract: +1. summary.verdict — if "Approved" AND issues array is empty, skip all fixes +2. issues[] — the array of issues to fix + +Each issue has: +- severity: "CRITICAL" | "WARNING" | "INFO" +- module: which review module flagged this (Logic, Bash, OLM, Build, Adaptive) +- file: path to the file that needs fixing +- line: line number where the issue occurs +- description: what the problem is +- fix_prompt: instructions on how to fix it + +If the issues array is empty or missing, I will output "No issues to fix" and stop. +``` + +**Exit early if no issues:** +- If `summary.verdict` is `"Approved"` AND the `issues` array is empty, output: + ``` + Review verdict: Approved — no fixes to apply. + ``` + and STOP. Do not proceed further. + +### Step 2: Prioritize Issues by Severity + +Sort the issues array so they are processed in this order: +1. **CRITICAL** — Must-fix issues (logic errors, build drift, safety violations) +2. **WARNING** — Should-fix issues (style, complexity, missing edge cases) +3. **INFO** — Nice-to-fix issues (naming, minor improvements) + +Within the same severity level, preserve the original order from the report. + +```thinking +I will group and order the issues: +- CRITICAL issues first (these block approval) +- WARNING issues second (these degrade quality) +- INFO issues last (these are improvements) + +I will track each issue with an index so I can map fixes back to the original report in the summary. +``` + +### Step 3: Apply Fixes + +For **each** issue in the prioritized list, perform the following sub-steps: + +#### 3.1: Read the Target File + +```bash +# Read the file referenced by the issue +cat "${issue.file}" +``` + +Read enough context around `issue.line` to understand the surrounding code — at minimum 20 lines before and 20 lines after the target line. If the function containing the target line is larger, read the entire function. + +#### 3.2: Understand the Fix + +```thinking +For this issue, I must: +1. Read the `description` to understand WHAT is wrong +2. Read the `fix_prompt` to understand HOW to fix it +3. Read the surrounding code context to understand WHERE the fix goes +4. Consider interactions with other code in the same file +5. Ensure the fix follows the effective-go skill conventions: + - Proper error handling with fmt.Errorf("...: %w", err) + - Lowercase error messages without trailing punctuation + - Short, consistent receiver names + - Proper import organization + - No TODOs — generate complete implementation +``` + +#### 3.3: Apply the Code Change + +Make the edit to the file. Follow these rules: + +- **Minimal change**: Only modify what the fix requires. Do not refactor unrelated code. +- **Preserve style**: Match the existing code style (indentation, naming, import grouping). +- **Complete implementation**: Do not leave TODOs or placeholders. If the `fix_prompt` describes logic, implement it fully. +- **Error handling**: If adding error handling, use `fmt.Errorf("context: %w", err)` with lowercase messages. +- **Imports**: If the fix requires new imports, add them in the correct import group (stdlib, external, internal). + +#### 3.4: Track the Result + +For each issue, record whether the fix was: +- **Applied** — the code change was made successfully +- **Skipped** — the fix could not be applied (e.g., the code at the referenced line has already changed, or the fix is ambiguous) + +If a fix cannot be applied, log the reason and continue to the next issue. Do NOT stop on individual fix failures. + +### Step 4: Verify All Fixes + +After all fixes have been applied, run verification: + +```bash +# Step 4.1: Check that the code compiles +go build ./... +``` + +```bash +# Step 4.2: Run static analysis +go vet ./... +``` + +If compilation or vetting fails: +1. Read the error output to identify which fix introduced the failure +2. Attempt to correct the failing fix +3. Re-run `go build ./...` to confirm the correction +4. If the correction fails after one retry, revert that specific fix and note it in the summary + +**Build Consistency Check:** + +```bash +# Step 4.3: Check if any types.go files were modified +git diff --name-only +``` + +```thinking +If any file matching *types*.go or *_types.go was modified by the fixes, I must warn +the user to run: + make generate && make manifests +This ensures deep copy functions and CRD manifests are regenerated. +``` + +### Step 5: Generate Fix Summary + +Output a structured summary of all fixes applied: + +```text +=== Review Fix Summary === + +Total issues in report: <N> +Fixes applied: <N> +Fixes skipped: <N> + +CRITICAL Fixes: + [1] APPLIED — <file>:<line> — <short description of what was changed> + [2] APPLIED — <file>:<line> — <short description of what was changed> + +WARNING Fixes: + [3] APPLIED — <file>:<line> — <short description of what was changed> + [4] SKIPPED — <file>:<line> — <reason it was skipped> + +INFO Fixes: + [5] APPLIED — <file>:<line> — <short description of what was changed> + +Verification: + go build ./... : PASS | FAIL + go vet ./... : PASS | FAIL + +Post-Fix Actions Required: + - Run 'make generate && make manifests' (if types were modified) + - Run 'make test' to validate behavior +``` + +## Return Value + +Returns the fix summary text shown above. + +## Behavioral Rules + +1. **Never skip silently**: If a fix cannot be applied, always log the reason. +2. **No collateral damage**: Only change code that the issue references. Do not refactor or "improve" unrelated code. +3. **Idempotent**: Running this command twice with the same report should not break previously applied fixes. +4. **Respect existing style**: Match the coding patterns already present in the file being edited. +5. **Follow effective-go**: All generated/modified Go code must follow the `effective-go` skill conventions. +6. **One fix at a time**: Apply each fix individually so failures are isolated. +7. **Build must pass**: If a fix breaks the build, revert it rather than leaving the codebase in a broken state. + +## Critical Failure Conditions + +The command MUST FAIL and STOP immediately if: + +1. **No report provided**: The review report JSON is missing or empty +2. **Invalid report format**: The JSON does not contain the expected `summary` and `issues` fields +3. **Not in a git repository**: The current directory is not inside a git working tree + +## Exit Conditions + +- **Success**: All applicable fixes applied, build passes, summary printed +- **Partial Success**: Some fixes applied, some skipped — summary details which and why +- **Failure**: Report is invalid or not in a git repository diff --git a/.cursor/commands/init.md b/.cursor/commands/init.md new file mode 100644 index 000000000..9b02c47f6 --- /dev/null +++ b/.cursor/commands/init.md @@ -0,0 +1,226 @@ +--- +name: /oape:init +id: oape-init +category: OAPE +description: Clone a Git repository by URL into the current directory and checkout the specified base branch +argument-hint: <git-url> <base-branch> +--- + +## Name +oape:init + +## Synopsis +```shell +/oape:init <git-url> <base-branch> +``` + +## Description +The `oape:init` command clones an OpenShift operator git repository into the current working directory using the provided URL. It uses `git clone`, checks out the specified base branch, then changes into the cloned directory so that subsequent `/oape:*` commands work in the cloned directory. + +**You MUST follow ALL steps strictly. If any precheck fails, you MUST stop immediately and report the failure.** + +## Implementation + +### Phase 0: Prechecks + +All prechecks must pass before proceeding. If ANY precheck fails, STOP immediately and report the failure. + +#### Precheck 1 — Validate Arguments + +Both a git URL and a base branch MUST be provided. + +```bash +# Parse arguments: expect "<git-url> <base-branch>" +GIT_URL=$(echo "$ARGUMENTS" | awk '{print $1}') +BASE_BRANCH=$(echo "$ARGUMENTS" | awk '{print $2}') + +if [ -z "$GIT_URL" ] || [ -z "$BASE_BRANCH" ]; then + echo "PRECHECK FAILED: Both a git URL and a base branch are required." + echo "Usage: /oape:init <git-url> <base-branch>" + echo "" + echo "Example:" + echo " /oape:init https://github.com/openshift/external-secrets-operator main" + exit 1 +fi + +echo "Git URL: $GIT_URL" +echo "Base branch: $BASE_BRANCH" +``` + +#### Precheck 2 — Verify Required Tools + +```bash +MISSING_TOOLS="" + +# Check git +if ! command -v git &> /dev/null; then + MISSING_TOOLS="$MISSING_TOOLS git" +fi + +if [ -n "$MISSING_TOOLS" ]; then + echo "PRECHECK FAILED: Missing required tools:$MISSING_TOOLS" + echo "Please install the missing tools and try again." + exit 1 +fi + +echo "Required tools are available." +``` + +**If ALL prechecks above passed, proceed to Phase 1.** +**If ANY precheck FAILED (exit 1), STOP. Do NOT proceed further. Report the failure to the user.** + +--- + +### Phase 1: Derive Clone Directory + +Extract the repository name from the git URL to use as the clone directory name. + +```bash +# Extract repo name from URL (strip trailing .git and take the last path component) +CLONE_DIR=$(basename "$GIT_URL" .git) +CLONE_URL="$GIT_URL" + +echo "Clone directory: $CLONE_DIR" +echo "Clone URL: $CLONE_URL" +``` + +--- + +### Phase 2: Clone Repository + +Clone the repository into the current working directory using `git clone --filter=blob:none`. Handle the case where the target directory already exists. + +```bash +if [ -d "$CLONE_DIR" ]; then + echo "Directory '$CLONE_DIR' already exists." + + # Check if it is a git repo pointing to the same remote + EXISTING_REMOTE=$(git -C "$CLONE_DIR" remote get-url origin 2>/dev/null || true) + + if [ -n "$EXISTING_REMOTE" ]; then + # Normalize URLs for comparison (strip trailing slashes and .git suffix) + NORM_EXISTING=$(echo "$EXISTING_REMOTE" | sed 's/\.git$//' | sed 's:/$::') + NORM_CLONE=$(echo "$CLONE_URL" | sed 's/\.git$//' | sed 's:/$::') + + if [ "$NORM_EXISTING" = "$NORM_CLONE" ]; then + echo "Existing directory is already a clone of the same repository." + echo "Using existing directory as-is." + else + echo "FAILED: Directory '$CLONE_DIR' exists but points to a different remote." + echo " Expected: $CLONE_URL" + echo " Found: $EXISTING_REMOTE" + echo "" + echo "Options:" + echo " 1. Remove the directory manually: rm -rf $CLONE_DIR" + echo " 2. Use a different working directory" + exit 1 + fi + else + echo "FAILED: Directory '$CLONE_DIR' exists but is not a git repository." + echo "" + echo "Options:" + echo " 1. Remove the directory manually: rm -rf $CLONE_DIR" + echo " 2. Use a different working directory" + exit 1 + fi +else + echo "Cloning $CLONE_URL into $CLONE_DIR..." + git clone --filter=blob:none "$CLONE_URL" + + if [ $? -ne 0 ]; then + echo "FAILED: git clone failed." + echo "Check your network connection and repository access." + exit 1 + fi + + echo "Clone complete." +fi +``` + +--- + +### Phase 3: Change Directory, Checkout Base Branch, and Verify + +Change into the cloned repository directory, checkout the specified base branch, and verify it is a valid Go-based operator repository. + +```bash +cd "$CLONE_DIR" || { echo "FAILED: Cannot change to directory $CLONE_DIR"; exit 1; } + +# Checkout the specified base branch +git checkout "$BASE_BRANCH" || { echo "FAILED: Cannot checkout branch '$BASE_BRANCH'"; exit 1; } +echo "Checked out branch: $BASE_BRANCH" + +# Verify Go module +if [ -f "go.mod" ]; then + GO_MODULE=$(head -1 go.mod | awk '{print $2}') + echo "Go module: $GO_MODULE" +else + echo "WARNING: No go.mod found. This may not be a Go-based operator repository." + GO_MODULE="(not detected)" +fi + +# Detect operator framework +FRAMEWORK="unknown" +if [ -f "go.mod" ]; then + if grep -q "sigs.k8s.io/controller-runtime" go.mod 2>/dev/null; then + FRAMEWORK="controller-runtime" + elif grep -q "github.com/openshift/library-go" go.mod 2>/dev/null; then + FRAMEWORK="library-go" + fi +fi + +echo "Framework: $FRAMEWORK" +echo "Current directory: $(pwd)" +``` + +--- + +### Phase 4: Output Summary + +```text +=== Repository Init Summary === + +Repository: <clone-dir> +Clone URL: <clone-url> +Base Branch: <base-branch> +Local Path: <absolute-path-to-cloned-dir> +Go Module: <module-name> +Framework: <controller-runtime | library-go | unknown> +``` + +--- + +## Critical Failure Conditions + +The command MUST FAIL and STOP immediately if ANY of the following are true: + +1. **Missing arguments**: Git URL or base branch was not provided +2. **Missing tools**: `git` is not installed +3. **Clone failed**: The git clone command fails (network, permissions, etc.) +4. **Branch checkout failed**: The specified base branch does not exist in the repository +5. **Directory conflict**: The target directory exists but is not a clone of the expected repository + +When failing, provide a clear error message explaining: +- Which check failed +- What the expected state is +- How to fix the issue + +## Behavioral Rules + +1. **Efficient cloning**: Always use `git clone --filter=blob:none` for blobless clones. +2. **Non-destructive**: Never delete an existing directory automatically. +3. **Idempotent**: If the directory already exists and is a clone of the correct repository, use it as-is without re-cloning. + +## Arguments + +- `<git-url>`: The full Git clone URL of the repository + - Required argument + - Example: `https://github.com/openshift/external-secrets-operator` +- `<base-branch>`: The base branch to checkout after cloning + - Required argument + - Example: `main`, `master`, `release-4.18` + +## Prerequisites + +- **git** -- Git installed +- Access to the target Git repository diff --git a/.cursor/commands/opsx-apply.md b/.cursor/commands/opsx-apply.md new file mode 100644 index 000000000..59f398a60 --- /dev/null +++ b/.cursor/commands/opsx-apply.md @@ -0,0 +1,156 @@ +--- +name: /opsx:apply +id: opsx-apply +category: Workflow +description: Implement tasks via OAPE command orchestration (task-by-task with code eval gate and approval after each task) +--- + +Implement an OpenSpec change using OAPE commands, driven by a composed design bundle +(tasks.md + upstream artifacts) scoped to **one task at a time**. + +**Per-task flow:** OAPE → verify → **code-generation evals** → **refine code** → **user approves code** → task report → next task. + +**Reference:** `oape-ai-e2e/AGENTS.md`, schema `oape_routing`, `code_generation_eval_gate`, `.cursor/commands/oape-*.md`, `{schema_root}/stage-gate/CODE_GENERATION_EVAL_PROMPT.md` + +**Input**: Optionally specify a change name (e.g., `/opsx:apply eso-123`). If omitted, infer from context or prompt. + +## Steps + +1. **Select the change** + + If a name is provided, use it. Otherwise infer, auto-select if only one active change, + or run `openspec list --json` and ask the user. + + Announce: "Using change: <name>" and how to override. + +2. **Check status and get apply instructions** + + ```bash + openspec status --change "<name>" --json + openspec instructions apply --change "<name>" --json + ``` + + Handle states: + - `blocked` → suggest `/opsx:continue` + - `all_done` → suggest archive + - otherwise → proceed + +3. **Verify prerequisites** + + - OAPE commands in `.cursor/commands/` (api-generate.md, api-implement.md, etc.) + - `tasks.md`, `constitution.md`, `specs.md`, `plan.md` exist in change dir + - `gh`, `go`, `git`, `make` available; `gh auth status` OK + +4. **Fork setup** (before any OAPE command) + + - Read `openspec/changes/<name>/inputs/jira.yaml` for `fork_repo_url` + - If missing, ask user once and persist + - Clone or verify fork; create feature branch per schema `fork_repo.feature_branch` + - Record `jira_key`; **all OAPE commands run with cwd = fork root** + - Create `openspec/changes/<name>/implementation/task-reports/` if missing + +5. **Read context artifacts** + + Read every path from apply instructions `contextFiles`: + constitution.md, specs.md, plan.md, tasks.md, repo-assessment.md (if present) + +6. **Parse tasks from tasks.md** + + - Order by §2 Linear Execution Order; respect §1 DAG + - Skip tasks marked `- [x]` + +7. **Task loop** (for each pending task in §2 order) + + **Do not ask for user approval until steps 1–4 below are complete.** + + ### 1. Compose design bundle + + Write `openspec/changes/<name>/implementation/design-bundle.md` using + `schemas/openspec-agile-workflow/templates/design-bundle-template.md`: + - Include constitution, specs, plan, repo-assessment excerpts + - Include §4 payload **ONLY for the current Task ID** + - Add REVISION FEEDBACK when re-running after task rejection + + ### 2. Run OAPE command (exactly one per task) + + Resolve command: + + 1. **IF e2e task** → `/oape:e2e-generate <fork-default-branch>` + 2. **ELIF** `API_Agent` verification-only → `/oape:api-generate-tests <api-path>` + 3. **ELIF** `API_Agent` → `/oape:api-generate --design-doc <bundle>` then `make update && make verify` + 4. **ELIF** `OperatorController_Agent` → `/oape:api-implement --design-doc <bundle>` + 5. **ELIF** manual agent → read `{schema_root}/templates/code-generation-template.md` for FILE OPERATIONS format; implement task payload directly (no OAPE command) + + Read `.cursor/commands/<command_file>` and execute its full workflow in fork cwd. + + ### 3. Verify + + Run Makefile targets from **this task's** Acceptance criteria. Record pass/fail. + + ### 4. Code generation eval gate (mandatory before approval) + + Read `{schema_root}/stage-gate/CODE_GENERATION_EVAL_PROMPT.md`. + + - Load `evals/code-generation_eval.yaml`; filter by resolved `oape_command` + - Score fork working copy; write `eval-results/code-generation-<task-id>.yaml` + - **If cases fail:** fix code in fork → re-verify → re-score (up to 2 refinement passes) + - **Do not** present user approval until this loop completes + + ### 5. Present task summary + + ``` + ## Task: <TASK_ID> — <title> + Phase: <phase> + + ### OAPE Commands Executed + ### Files Touched + ### Test Results + ### Code Generation Eval Scorecard (score, cases, refinement rounds, fixes applied) + ### Deviations (if any) + ``` + + ### 6. User code approval gate + + ASK: **"Code eval score: {N}% ({pass}/{total} cases pass). Approve the code changes for task {task_id} ({task_title}) and proceed to the next task? (Approve / Reject with feedback)"** + + - **Reject** → REVISION FEEDBACK in design-bundle; re-run **this task only** from step 1 + - **Approve** → continue to step 7 + + ### 7. On approve — record and advance + + - Write `implementation/task-reports/<task-id>.md` (template: `implementation-task-report.md`) + - Mark task `- [x]` in tasks.md + - Append `implementation-phase-log.md` (link to task report) + - Advance to next task + +8. **Post-loop** (all tasks approved) + + - Write `implementation-report.md` — **aggregate all** `implementation/task-reports/*.md` + - Write `implementation-checklist.md` + - Write `adrs.md` only if deviations logged + - Commit, push feature branch, open draft PR on fork + - Present final summary with draft PR URL + +## Output During Implementation + +``` +Task 3/12: T1_3 — Implement controller reconciliation +→ /oape:api-implement --design-doc .../design-bundle.md +→ make test (PASSED) +→ code evals: 100% (2/2 pass) after 1 refinement round +→ task report: implementation/task-reports/T1_3.md + +Code eval score: 100% (2/2 cases pass). +Approve the code changes for task T1_3 (Implement controller reconciliation) and proceed? +(Approve / Reject with feedback) +``` + +## Guardrails + +- **Mandatory order:** OAPE → verify → code evals → refine code → **then** user approval +- **Never** skip code-generation evals when applicable cases exist +- **Never** advance without user **code** approval for the current task +- Invoke exactly one allowed OAPE command per task +- OAPE commands run in fork cwd only +- On reject: re-run current task only (full loop including eval gate) +- Write one task report per approved task diff --git a/.cursor/commands/opsx-archive.md b/.cursor/commands/opsx-archive.md new file mode 100644 index 000000000..e5cdd0e23 --- /dev/null +++ b/.cursor/commands/opsx-archive.md @@ -0,0 +1,157 @@ +--- +name: /opsx-archive +id: opsx-archive +category: Workflow +description: Archive a completed change in the experimental workflow +--- + +Archive a completed change in the experimental workflow. + +**Input**: Optionally specify a change name after `/opsx:archive` (e.g., `/opsx:archive add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. + +**Steps** + +1. **If no change name provided, prompt for selection** + + Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select. + + Show only active changes (not already archived). + Include the schema used for each change if available. + + **IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose. + +2. **Check artifact completion status** + + Run `openspec status --change "<name>" --json` to check artifact completion. + + Parse the JSON to understand: + - `schemaName`: The workflow being used + - `artifacts`: List of artifacts with their status (`done` or other) + + **If any artifacts are not `done`:** + - Display warning listing incomplete artifacts + - Prompt user for confirmation to continue + - Proceed if user confirms + +3. **Check task completion status** + + Read the tasks file (typically `tasks.md`) to check for incomplete tasks. + + Count tasks marked with `- [ ]` (incomplete) vs `- [x]` (complete). + + **If incomplete tasks found:** + - Display warning showing count of incomplete tasks + - Prompt user for confirmation to continue + - Proceed if user confirms + + **If no tasks file exists:** Proceed without task-related warning. + +4. **Assess delta spec sync state** + + Check for delta specs at `openspec/changes/<name>/specs/`. If none exist, proceed without sync prompt. + + **If delta specs exist:** + - Compare each delta spec with its corresponding main spec at `openspec/specs/<capability>/spec.md` + - Determine what changes would be applied (adds, modifications, removals, renames) + - Show a combined summary before prompting + + **Prompt options:** + - If changes needed: "Sync now (recommended)", "Archive without syncing" + - If already synced: "Archive now", "Sync anyway", "Cancel" + + If user chooses sync, use Task tool (subagent_type: "general-purpose", prompt: "Use Skill tool to invoke openspec-sync-specs for change '<name>'. Delta spec analysis: <include the analyzed delta spec summary>"). Proceed to archive regardless of choice. + +5. **Perform the archive** + + Create the archive directory if it doesn't exist: + ```bash + mkdir -p openspec/changes/archive + ``` + + Generate target name using current date: `YYYY-MM-DD-<change-name>` + + **Check if target already exists:** + - If yes: Fail with error, suggest renaming existing archive or using different date + - If no: Move the change directory to archive + + ```bash + mv openspec/changes/<name> openspec/changes/archive/YYYY-MM-DD-<name> + ``` + +6. **Display summary** + + Show archive completion summary including: + - Change name + - Schema that was used + - Archive location + - Spec sync status (synced / sync skipped / no delta specs) + - Note about any warnings (incomplete artifacts/tasks) + +**Output On Success** + +``` +## Archive Complete + +**Change:** <change-name> +**Schema:** <schema-name> +**Archived to:** openspec/changes/archive/YYYY-MM-DD-<name>/ +**Specs:** ✓ Synced to main specs + +All artifacts complete. All tasks complete. +``` + +**Output On Success (No Delta Specs)** + +``` +## Archive Complete + +**Change:** <change-name> +**Schema:** <schema-name> +**Archived to:** openspec/changes/archive/YYYY-MM-DD-<name>/ +**Specs:** No delta specs + +All artifacts complete. All tasks complete. +``` + +**Output On Success With Warnings** + +``` +## Archive Complete (with warnings) + +**Change:** <change-name> +**Schema:** <schema-name> +**Archived to:** openspec/changes/archive/YYYY-MM-DD-<name>/ +**Specs:** Sync skipped (user chose to skip) + +**Warnings:** +- Archived with 2 incomplete artifacts +- Archived with 3 incomplete tasks +- Delta spec sync was skipped (user chose to skip) + +Review the archive if this was not intentional. +``` + +**Output On Error (Archive Exists)** + +``` +## Archive Failed + +**Change:** <change-name> +**Target:** openspec/changes/archive/YYYY-MM-DD-<name>/ + +Target archive directory already exists. + +**Options:** +1. Rename the existing archive +2. Delete the existing archive if it's a duplicate +3. Wait until a different date to archive +``` + +**Guardrails** +- Always prompt for change selection if not provided +- Use artifact graph (openspec status --json) for completion checking +- Don't block archive on warnings - just inform and confirm +- Preserve .openspec.yaml when moving to archive (it moves with the directory) +- Show clear summary of what happened +- If sync is requested, use the Skill tool to invoke `openspec-sync-specs` (agent-driven) +- If delta specs exist, always run the sync assessment and show the combined summary before prompting diff --git a/.cursor/commands/opsx-continue.md b/.cursor/commands/opsx-continue.md new file mode 100644 index 000000000..6057783bd --- /dev/null +++ b/.cursor/commands/opsx-continue.md @@ -0,0 +1,89 @@ +--- +name: /opsx-continue +id: opsx-continue +category: Workflow +description: Continue agile-workflow change - create next artifact, eval gate, refine, approve (OPSX) +--- + +Continue working on a change by creating the **next** artifact (one per invocation), then **eval → refine artifact → user approval**. + +**Input**: Optional change name after `/opsx-continue` (e.g. `/opsx-continue eso-123`). + +## Schema package (resolve first existing path) + +| Role | Installed | Distribution | +|------|-----------|--------------| +| Schema root | `openspec/schemas/openspec-agile-workflow/` | `schemas/openspec-agile-workflow/` | +| Stage gate | `{schema_root}/stage-gate/` | same | +| Stage evals | `{schema_root}/evals/<stage>_eval.yaml` | same | +| Templates | `{schema_root}/templates/` | same | + +## Steps + +1. Select change (`openspec list --json` if name not given). +2. `openspec status --change "<name>" --json` +3. Read `openspec/changes/<name>/inputs/jira.yaml` (required). +4. **Resolve repo target before repo-assessment** (see schema `target_repo` and `working_folder_repo`): + - **Working-folder mode:** If the user directs using the working folder as the repo, + set `use_working_folder_as_repo: true` in `inputs/jira.yaml`, record + `working_folder_path`, analyze cwd — do not ask for GitHub URL or clone separately. + - **Default mode:** If the next ready artifact is `repo-assessment` (or `constitution`) and + `target_repo` is absent or empty in `jira.yaml`: + - Ask the user once: "Provide the URL of the target GitHub repository + (e.g. https://github.com/org/repo)." + - Persist `target_repo` to `inputs/jira.yaml`. + - Verify the repository is accessible before creating repo-assessment. + - **Do not** create repo-assessment or constitution until `target_repo` is recorded. + - For earlier artifacts (`validation`, `specs`), `target_repo` is not required. +5. Pick first artifact with `status: "ready"`. +6. `openspec instructions <artifact-id> --change "<name>" --json` → create artifact at `outputPath` (**v1**). + - Generation uses **`{schema_root}/templates/`** (from openspec instructions). +7. **Stage eval gate** — read **`{schema_root}/stage-gate/SYSTEM_PROMPT.md`** in full: + - Load mapping: `{schema_root}/stage-gate/artifact-eval-map.yaml` + - **Run evals** from `{schema_root}/evals/<stage>_eval.yaml` (when `gate: stage_evals`) + - Write `openspec/changes/<name>/eval-results/<artifact-id>.yaml` + - If any case fails: **refine the artifact** at `outputPath` (v2) using the **refinement context bundle** (v1 text + eval failures + openspec instructions + dependencies + inputs + failed case prompts) + - Re-score after refinement + - **Do NOT** modify `{schema_root}/templates/` or `evals/refined-templates/` +8. **STOP** — present eval scorecard + artifact summary. Ask: + + > Eval score: **X%** (N/M cases pass). Approve this artifact? **(Approve / Reject with feedback)** + + - **Approve** → artifact gate satisfied; next `/opsx-continue` may create the next ready artifact + - **Reject** → if artifact is **`specs`**: **exit workflow** (schema `exit_on_reject.specs`) — do NOT regenerate specs; STOP + - **Reject** (other artifacts) → run feedback loop (same invocation, repeat until Approve): + 1. Capture user feedback verbatim + 2. Load context: prior approved artifacts (read-only), current artifact, `{schema_root}/templates/<name>.md`, eval results, openspec instructions + 3. Update template if feedback requires structural/guidance changes + 4. Regenerate refined artifact at `outputPath` + 5. Write round summary → `openspec/changes/<name>/feedback_stage_artifacts/<artifact-id>/round-<N>.yaml` + 6. Re-run eval gate when applicable + 7. Re-present scorecard + feedback addressed → ask approval again + - Read **`{schema_root}/stage-gate/USER_FEEDBACK_PROMPT.md`** for full steps + - **Do not** use `prompts/<artifact-id>.yaml` + +## Artifact order (openspec-agile-workflow) + +validation.json → specs.md → repo-assessment.md → constitution.md → plan.md → tasks.md → … + +## Eval gate by artifact + +| Artifact | Stage eval file (under `{schema_root}/`) | +|----------|------------------------------------------| +| validation | Rubric in `templates/validation-template.md` only | +| specs | Skip (no stage eval) | +| repo-assessment | `evals/repo-assessment_eval.yaml` | +| constitution | `evals/constitution_eval.yaml` | +| plan | `evals/plan_eval.yaml` | +| tasks | `evals/tasks_eval.yaml` | +| implementation | `evals/implementation_eval.yaml` | + +## Guardrails + +- ONE artifact per invocation (includes eval + refine + approval for that artifact) +- Do not skip eval gate for artifacts with `gate: stage_evals` +- Do not skip user approval +- Do not refine **templates** during eval gate — refine the **change artifact** only +- User rejection feedback loop **may** patch `{schema_root}/templates/` when required; write summaries to `feedback_stage_artifacts/` +- `target_repo` required before repo-assessment — **not** at `/opsx-new` +- Do not create the next artifact until the user approves the current one diff --git a/.cursor/commands/opsx-explore.md b/.cursor/commands/opsx-explore.md new file mode 100644 index 000000000..f1aceece8 --- /dev/null +++ b/.cursor/commands/opsx-explore.md @@ -0,0 +1,173 @@ +--- +name: /opsx-explore +id: opsx-explore +category: Workflow +description: "Enter explore mode - think through ideas, investigate problems, clarify requirements" +--- + +Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes. + +**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) if the user asks—that's capturing thinking, not implementing. + +**This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore. + +**Input**: The argument after `/opsx:explore` is whatever the user wants to think about. Could be: +- A vague idea: "real-time collaboration" +- A specific problem: "the auth system is getting unwieldy" +- A change name: "add-dark-mode" (to explore in context of that change) +- A comparison: "postgres vs sqlite for this" +- Nothing (just enter explore mode) + +--- + +## The Stance + +- **Curious, not prescriptive** - Ask questions that emerge naturally, don't follow a script +- **Open threads, not interrogations** - Surface multiple interesting directions and let the user follow what resonates. Don't funnel them through a single path of questions. +- **Visual** - Use ASCII diagrams liberally when they'd help clarify thinking +- **Adaptive** - Follow interesting threads, pivot when new information emerges +- **Patient** - Don't rush to conclusions, let the shape of the problem emerge +- **Grounded** - Explore the actual codebase when relevant, don't just theorize + +--- + +## What You Might Do + +Depending on what the user brings, you might: + +**Explore the problem space** +- Ask clarifying questions that emerge from what they said +- Challenge assumptions +- Reframe the problem +- Find analogies + +**Investigate the codebase** +- Map existing architecture relevant to the discussion +- Find integration points +- Identify patterns already in use +- Surface hidden complexity + +**Compare options** +- Brainstorm multiple approaches +- Build comparison tables +- Sketch tradeoffs +- Recommend a path (if asked) + +**Visualize** +``` +┌─────────────────────────────────────────┐ +│ Use ASCII diagrams liberally │ +├─────────────────────────────────────────┤ +│ │ +│ ┌────────┐ ┌────────┐ │ +│ │ State │────────▶│ State │ │ +│ │ A │ │ B │ │ +│ └────────┘ └────────┘ │ +│ │ +│ System diagrams, state machines, │ +│ data flows, architecture sketches, │ +│ dependency graphs, comparison tables │ +│ │ +└─────────────────────────────────────────┘ +``` + +**Surface risks and unknowns** +- Identify what could go wrong +- Find gaps in understanding +- Suggest spikes or investigations + +--- + +## OpenSpec Awareness + +You have full context of the OpenSpec system. Use it naturally, don't force it. + +### Check for context + +At the start, quickly check what exists: +```bash +openspec list --json +``` + +This tells you: +- If there are active changes +- Their names, schemas, and status +- What the user might be working on + +If the user mentioned a specific change name, read its artifacts for context. + +### When no change exists + +Think freely. When insights crystallize, you might offer: + +- "This feels solid enough to start a change. Want me to create a proposal?" +- Or keep exploring - no pressure to formalize + +### When a change exists + +If the user mentions a change or you detect one is relevant: + +1. **Read existing artifacts for context** + - `openspec/changes/<name>/proposal.md` + - `openspec/changes/<name>/design.md` + - `openspec/changes/<name>/tasks.md` + - etc. + +2. **Reference them naturally in conversation** + - "Your design mentions using Redis, but we just realized SQLite fits better..." + - "The proposal scopes this to premium users, but we're now thinking everyone..." + +3. **Offer to capture when decisions are made** + + | Insight Type | Where to Capture | + |----------------------------|--------------------------------| + | New requirement discovered | `specs/<capability>/spec.md` | + | Requirement changed | `specs/<capability>/spec.md` | + | Design decision made | `design.md` | + | Scope changed | `proposal.md` | + | New work identified | `tasks.md` | + | Assumption invalidated | Relevant artifact | + + Example offers: + - "That's a design decision. Capture it in design.md?" + - "This is a new requirement. Add it to specs?" + - "This changes scope. Update the proposal?" + +4. **The user decides** - Offer and move on. Don't pressure. Don't auto-capture. + +--- + +## What You Don't Have To Do + +- Follow a script +- Ask the same questions every time +- Produce a specific artifact +- Reach a conclusion +- Stay on topic if a tangent is valuable +- Be brief (this is thinking time) + +--- + +## Ending Discovery + +There's no required ending. Discovery might: + +- **Flow into a proposal**: "Ready to start? I can create a change proposal." +- **Result in artifact updates**: "Updated design.md with these decisions" +- **Just provide clarity**: User has what they need, moves on +- **Continue later**: "We can pick this up anytime" + +When things crystallize, you might offer a summary - but it's optional. Sometimes the thinking IS the value. + +--- + +## Guardrails + +- **Don't implement** - Never write code or implement features. Creating OpenSpec artifacts is fine, writing application code is not. +- **Don't fake understanding** - If something is unclear, dig deeper +- **Don't rush** - Discovery is thinking time, not task time +- **Don't force structure** - Let patterns emerge naturally +- **Don't auto-capture** - Offer to save insights, don't just do it +- **Do visualize** - A good diagram is worth many paragraphs +- **Do explore the codebase** - Ground discussions in reality +- **Do question assumptions** - Including the user's and your own diff --git a/.cursor/commands/opsx-new.md b/.cursor/commands/opsx-new.md new file mode 100644 index 000000000..e7af8e95a --- /dev/null +++ b/.cursor/commands/opsx-new.md @@ -0,0 +1,49 @@ +--- +name: /opsx-new +id: opsx-new +category: Workflow +description: Start a new agile-workflow change from a Jira ticket (OPSX) +--- + +Start a new change for the **openspec-agile-workflow** pipeline. + +## Inputs — what is required when + +| Input | Required at `/opsx-new`? | Required later? | When | +|-------|--------------------------|-----------------|------| +| **Jira ticket key** | **YES** | — | Always the first input | +| **Change name** (kebab-case) | No | — | Optional; defaults to lowercase ticket slug (`PROJ-123` → `proj-123`) | +| **Target GitHub repo URL** | **NO** | **YES** | Before **repo-assessment** (`/opsx-continue` ~3rd artifact) | +| **AGENTS.md** | No | No | Optional | + +**At `/opsx-new` you only need the Jira key.** Do not ask for the repo URL unless the user includes it inline. + +## Command syntax + +``` +/opsx-new PROJ-123 +/opsx-new PROJ-123 my-change-name +/opsx-new PROJ-123 my-change-name https://github.com/openshift/external-secrets-operator +``` + +Jira key pattern: `[A-Z][A-Z0-9]+-\d+`. + +If no Jira key, ask once. Do **not** proceed without it. + +## Steps + +1. Parse Jira key (required), optional change name, optional repo URL. +2. `openspec new change "<name>"` — uses `openspec-agile-workflow` from `openspec/config.yaml`. +3. Write `openspec/changes/<name>/inputs/jira.yaml` with `jira_key`, `target_repo`, `created_at`. +4. Fetch ticket → `inputs/jira-spec.md`: + - Use Jira MCP if configured, **or** + - Ask the user to paste ticket content into `inputs/jira-spec.md`. +5. `openspec status --change "<name>"` and `openspec instructions validation --change "<name>"`. +6. **STOP** — do not create artifacts yet. + +Prompt: `/opsx-continue` to create `validation.json`. + +## Guardrails + +- Jira key required; repo URL optional at this step +- No planning artifacts in this command diff --git a/.cursor/commands/opsx-propose.md b/.cursor/commands/opsx-propose.md new file mode 100644 index 000000000..60ebb6e47 --- /dev/null +++ b/.cursor/commands/opsx-propose.md @@ -0,0 +1,109 @@ +--- +name: /opsx-propose +id: opsx-propose +category: Workflow +description: Propose a new change - create it and generate all artifacts in one step +--- + +Propose a new change - create the change and generate all artifacts in one step. + +I'll create a change with artifacts: +- proposal.md (what & why) +- design.md (how) +- tasks.md (implementation steps) + +When ready to implement, run /opsx:apply + +--- + +**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root. + +**Input**: The argument after `/opsx:propose` is the change name (kebab-case), OR a description of what the user wants to build. + +**Steps** + +1. **If no input provided, ask what they want to build** + + Use the **AskUserQuestion tool** (open-ended, no preset options) to ask: + > "What change do you want to work on? Describe what you want to build or fix." + + From their description, derive a kebab-case name (e.g., "add user authentication" → `add-user-auth`). + + **IMPORTANT**: Do NOT proceed without understanding what the user wants to build. + +2. **Create the change directory** + ```bash + openspec new change "<name>" + ``` + This creates a scaffolded change in the planning home resolved by the CLI with `.openspec.yaml`. + +3. **Get the artifact build order** + ```bash + openspec status --change "<name>" --json + ``` + Parse the JSON to get: + - `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`) + - `artifacts`: list of all artifacts with their status and dependencies + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths. + +4. **Create artifacts in sequence until apply-ready** + + Use the **TodoWrite tool** to track progress through the artifacts. + + Loop through artifacts in dependency order (artifacts with no pending dependencies first): + + a. **For each artifact that is `ready` (dependencies satisfied)**: + - Get instructions: + ```bash + openspec instructions <artifact-id> --change "<name>" --json + ``` + - The instructions JSON includes: + - `context`: Project background (constraints for you - do NOT include in output) + - `rules`: Artifact-specific rules (constraints for you - do NOT include in output) + - `template`: The structure to use for your output file + - `instruction`: Schema-specific guidance for this artifact type + - `resolvedOutputPath`: Resolved path or pattern to write the artifact + - `dependencies`: Completed artifacts to read for context + - Read any completed dependency files for context + - Create the artifact file using `template` as the structure and write it to `resolvedOutputPath` + - Apply `context` and `rules` as constraints - but do NOT copy them into the file + - Show brief progress: "Created <artifact-id>" + + b. **Continue until all `applyRequires` artifacts are complete** + - After creating each artifact, re-run `openspec status --change "<name>" --json` + - Check if every artifact ID in `applyRequires` has `status: "done"` in the artifacts array + - Stop when all `applyRequires` artifacts are done + + c. **If an artifact requires user input** (unclear context): + - Use **AskUserQuestion tool** to clarify + - Then continue with creation + +5. **Show final status** + ```bash + openspec status --change "<name>" + ``` + +**Output** + +After completing all artifacts, summarize: +- Change name and location +- List of artifacts created with brief descriptions +- What's ready: "All artifacts created! Ready for implementation." +- Prompt: "Run `/opsx:apply` to start implementing." + +**Artifact Creation Guidelines** + +- Follow the `instruction` field from `openspec instructions` for each artifact type +- The schema defines what each artifact should contain - follow it +- Read dependency artifacts for context before creating new ones +- Use `template` as the structure for your output file - fill in its sections +- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file + - Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact + - These guide what you write, but should never appear in the output + +**Guardrails** +- Create ALL artifacts needed for implementation (as defined by schema's `apply.requires`) +- Always read dependency artifacts before creating a new one +- If context is critically unclear, ask the user - but prefer making reasonable decisions to keep momentum +- If a change with that name already exists, ask if user wants to continue it or create a new one +- Verify each artifact file exists after writing before proceeding to next diff --git a/.cursor/commands/opsx-sync.md b/.cursor/commands/opsx-sync.md new file mode 100644 index 000000000..e9c7a6b93 --- /dev/null +++ b/.cursor/commands/opsx-sync.md @@ -0,0 +1,143 @@ +--- +name: /opsx-sync +id: opsx-sync +category: Workflow +description: Sync delta specs from a change to main specs +--- + +Sync delta specs from a change to main specs. + +This is an **agent-driven** operation - you will read delta specs and directly edit main specs to apply the changes. This allows intelligent merging (e.g., adding a scenario without copying the entire requirement). + +**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root. + +**Input**: Optionally specify a change name after `/opsx:sync` (e.g., `/opsx:sync add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. + +**Steps** + +1. **If no change name provided, prompt for selection** + + Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select. + + Show changes that have delta specs (under `specs/` directory). + + **IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose. + +2. **Resolve change context** + + Run: + ```bash + openspec status --change "<name>" --json + ``` + +3. **Find delta specs** + + Use `artifactPaths.specs.existingOutputPaths` from the status JSON as the list of delta spec files. + + Each delta spec file contains sections like: + - `## ADDED Requirements` - New requirements to add + - `## MODIFIED Requirements` - Changes to existing requirements + - `## REMOVED Requirements` - Requirements to remove + - `## RENAMED Requirements` - Requirements to rename (FROM:/TO: format) + + If no delta specs found, inform user and stop. + +4. **For each delta spec, apply changes to main specs** + + For each repo-local capability delta spec path returned by the CLI: + + a. **Read the delta spec** to understand the intended changes + + b. **Read the main spec** at `openspec/specs/<capability>/spec.md` (may not exist yet) + + c. **Apply changes intelligently**: + + **ADDED Requirements:** + - If requirement doesn't exist in main spec → add it + - If requirement already exists → update it to match (treat as implicit MODIFIED) + + **MODIFIED Requirements:** + - Find the requirement in main spec + - Apply the changes - this can be: + - Adding new scenarios (don't need to copy existing ones) + - Modifying existing scenarios + - Changing the requirement description + - Preserve scenarios/content not mentioned in the delta + + **REMOVED Requirements:** + - Remove the entire requirement block from main spec + + **RENAMED Requirements:** + - Find the FROM requirement, rename to TO + + d. **Create new main spec** if capability doesn't exist yet: + - Create `openspec/specs/<capability>/spec.md` + - Add Purpose section (can be brief, mark as TBD) + - Add Requirements section with the ADDED requirements + +5. **Show summary** + + After applying all changes, summarize: + - Which capabilities were updated + - What changes were made (requirements added/modified/removed/renamed) + +**Delta Spec Format Reference** + +```markdown +## ADDED Requirements + +### Requirement: New Feature +The system SHALL do something new. + +#### Scenario: Basic case +- **WHEN** user does X +- **THEN** system does Y + +## MODIFIED Requirements + +### Requirement: Existing Feature +#### Scenario: New scenario to add +- **WHEN** user does A +- **THEN** system does B + +## REMOVED Requirements + +### Requirement: Deprecated Feature + +## RENAMED Requirements + +- FROM: `### Requirement: Old Name` +- TO: `### Requirement: New Name` +``` + +**Key Principle: Intelligent Merging** + +Unlike programmatic merging, you can apply **partial updates**: +- To add a scenario, just include that scenario under MODIFIED - don't copy existing scenarios +- The delta represents *intent*, not a wholesale replacement +- Use your judgment to merge changes sensibly + +**Output On Success** + +``` +## Specs Synced: <change-name> + +Updated main specs: + +**<capability-1>**: +- Added requirement: "New Feature" +- Modified requirement: "Existing Feature" (added 1 scenario) + +**<capability-2>**: +- Created new spec file +- Added requirement: "Another Feature" + +Main specs are now updated. The change remains active - archive when implementation is complete. +``` + +**Guardrails** +- Read both delta and main specs before making changes +- Preserve existing content not mentioned in delta +- If something is unclear, ask for clarification +- Show what you're changing as you go +- The operation should be idempotent - running twice should give same result diff --git a/.cursor/commands/predict-regressions.md b/.cursor/commands/predict-regressions.md new file mode 100644 index 000000000..67648b76f --- /dev/null +++ b/.cursor/commands/predict-regressions.md @@ -0,0 +1,661 @@ +--- +name: /oape:predict-regressions +id: oape-predict-regressions +category: OAPE +description: Predict potential regressions and breaking changes in newly developed APIs by analyzing git diff and API schema changes +argument-hint: "<base-branch> [--output <path>]" +--- + +## Name +oape:predict-regressions + +## Synopsis +```shell +/oape:predict-regressions <base-branch> [--output <path>] +``` + +## Description + +Analyzes the current OpenShift operator repository and predicts potential regressions, breaking changes, and backward compatibility issues by comparing changes between `<base-branch>` and HEAD. + +The command generates a comprehensive regression risk report that includes: + +1. **Static Analysis** — Rule-based detection of common breaking changes +2. **LLM-Powered Prediction** — Deep semantic analysis using Claude to identify subtle regressions +3. **Impact Assessment** — Severity ratings (Critical/High/Medium/Low) for each finding +4. **Mitigation Recommendations** — Actionable steps to address each issue +5. **Test Scenarios** — Suggested e2e tests to validate fixes + +**Output**: `<output-dir>/regression-report.md` (default: `output/regression-report.md`) + +**You MUST follow ALL steps strictly. If any precheck fails, you MUST stop immediately and report the failure.** + +## Implementation + +### Phase 0: Prechecks + +All prechecks must pass before proceeding. If ANY precheck fails, STOP immediately and report the failure. + +#### Precheck 1 — Validate Arguments + +```bash +BASE_BRANCH="$1" + +if [ -z "$BASE_BRANCH" ]; then + echo "PRECHECK FAILED: No base branch provided." + echo "Usage: /oape:predict-regressions <base-branch> [--output <path>]" + exit 1 +fi + +# Parse optional --output +OUTPUT_DIR="output" +if echo "$@" | grep -q '\-\-output'; then + OUTPUT_DIR=$(echo "$@" | sed 's/.*--output[ =]*//' | awk '{print $1}') +fi + +echo "Base branch: $BASE_BRANCH" +echo "Output directory: $OUTPUT_DIR" +``` + +#### Precheck 2 — Verify Required Tools + +```bash +MISSING_TOOLS="" + +if ! command -v git &> /dev/null; then + MISSING_TOOLS="$MISSING_TOOLS git" +fi + +if ! command -v go &> /dev/null; then + MISSING_TOOLS="$MISSING_TOOLS go" +fi + +if [ -n "$MISSING_TOOLS" ]; then + echo "PRECHECK FAILED: Missing required tools:$MISSING_TOOLS" + exit 1 +fi + +echo "Required tools available." +``` + +#### Precheck 3 — Verify Current Repository is a Valid OpenShift Operator Repo + +```bash +if ! git rev-parse --is-inside-work-tree &> /dev/null 2>&1; then + echo "PRECHECK FAILED: Not inside a git repository." + exit 1 +fi + +REPO_ROOT=$(git rev-parse --show-toplevel) +echo "Repository root: $REPO_ROOT" + +if ! test -f "$REPO_ROOT/go.mod"; then + echo "PRECHECK FAILED: No go.mod found at repository root." + echo "This command must be run from within a Go-based OpenShift operator repository." + exit 1 +fi + +GO_MODULE=$(head -1 "$REPO_ROOT/go.mod" | awk '{print $2}') +REPO_NAME=$(basename "$GO_MODULE") +echo "Go module: $GO_MODULE" +echo "Repo name: $REPO_NAME" + +# Detect framework +HAS_CR=false +HAS_LIBGO=false +grep -q "sigs.k8s.io/controller-runtime" "$REPO_ROOT/go.mod" && HAS_CR=true +grep -q "github.com/openshift/library-go" "$REPO_ROOT/go.mod" && HAS_LIBGO=true + +if [ "$HAS_CR" = true ]; then + FRAMEWORK="controller-runtime" +elif [ "$HAS_LIBGO" = true ]; then + FRAMEWORK="library-go" +else + echo "PRECHECK FAILED: Cannot determine operator framework." + echo "go.mod does not reference sigs.k8s.io/controller-runtime or github.com/openshift/library-go." + exit 1 +fi + +echo "Detected framework: $FRAMEWORK" +``` + +#### Precheck 4 — Validate Git Diff is Non-Empty + +```bash +if ! git rev-parse --verify "$BASE_BRANCH" &> /dev/null 2>&1; then + echo "PRECHECK FAILED: Base branch '$BASE_BRANCH' does not exist." + echo "Available branches:" + git branch -a | head -20 + exit 1 +fi + +DIFF_STAT=$(git diff "$BASE_BRANCH"...HEAD --stat 2>/dev/null) + +if [ -z "$DIFF_STAT" ]; then + echo "PRECHECK FAILED: No changes detected between '$BASE_BRANCH' and HEAD." + exit 1 +fi + +echo "Changes detected:" +echo "$DIFF_STAT" +``` + +**If ALL prechecks above passed, proceed to Phase 1.** +**If ANY precheck FAILED (exit 1), STOP. Do NOT proceed further.** + +--- + +### Phase 1: Extract Git Diff Information + +Gather all changed files and their diffs, categorized by type. + +#### Step 1.1: Get Diff Statistics + +```bash +git diff "$BASE_BRANCH"...HEAD --stat +git log "$BASE_BRANCH"...HEAD --oneline | head -10 +``` + +#### Step 1.2: Extract API Type Changes + +```bash +# Find changed API type files +git diff "$BASE_BRANCH"...HEAD --name-only | grep -E '(_types\.go|types_.*\.go)$' | grep -v vendor | grep -v zz_generated +``` + +For each changed types file, get the full diff: +```bash +git diff "$BASE_BRANCH"...HEAD -p -- <types-file> +``` + +#### Step 1.3: Extract CRD Schema Changes + +```bash +# Find changed CRD files +git diff "$BASE_BRANCH"...HEAD --name-only | grep -E '\.yaml$' | grep -E '(crd|crds)/' | grep -v vendor +``` + +For each changed CRD file, get the full diff: +```bash +git diff "$BASE_BRANCH"...HEAD -p -- <crd-file> +``` + +#### Step 1.4: Extract Controller/Reconciler Changes + +```bash +# Find changed controller files +git diff "$BASE_BRANCH"...HEAD --name-only | grep -E '(controller|reconcil).*\.go$' | grep -v vendor | grep -v _test.go +``` + +For each changed controller file, get the full diff: +```bash +git diff "$BASE_BRANCH"...HEAD -p -- <controller-file> +``` + +#### Step 1.5: Extract RBAC and Config Changes + +```bash +# RBAC changes +git diff "$BASE_BRANCH"...HEAD --name-only | grep -E 'rbac.*\.yaml$' | grep -v vendor + +# Webhook configuration changes +git diff "$BASE_BRANCH"...HEAD --name-only | grep -E 'webhook.*\.yaml$' | grep -v vendor + +# Conversion webhook changes +git diff "$BASE_BRANCH"...HEAD --name-only | grep -E 'conversion.*\.go$' | grep -v vendor +``` + +```thinking +I need to organize the diff extraction to capture all relevant changes that could cause regressions: +- API type changes (field additions/removals/modifications) +- CRD schema changes (validation, defaults, versions) +- Controller logic changes +- RBAC changes +- Webhook changes (validation, conversion) + +These will be analyzed both statically and via LLM. +``` + +--- + +### Phase 2: Static Analysis for Common Breaking Changes + +Apply rule-based detection for well-known breaking change patterns. This phase runs before LLM analysis to catch obvious issues quickly. + +Load and execute the **regression-analysis skill** to perform static checks. + +**Note**: If the changes are purely additive (new fields, new features) with no modifications to existing APIs, static analysis may return 0 findings. This is expected and LLM analysis will handle the deeper semantic review. + +The static analysis should detect: + +1. **Field Removals** — Any struct field removed from API types +2. **Required Field Additions** — New `+kubebuilder:validation:Required` markers in CRDs +3. **Type Changes** — Field type changes (e.g., `string` → `int`) +4. **Enum Value Removals** — Removed values from `+kubebuilder:validation:Enum` +5. **Default Value Changes** — Modified `+kubebuilder:default:` values +6. **API Version Additions Without Conversion** — New version added without conversion webhook +7. **Validation Rule Tightening** — More restrictive validation (min/max, pattern changes) +8. **Breaking Condition Changes** — Condition type removals or semantic changes + +For each detected issue, record: +- **Finding ID** (e.g., `STATIC-001`) +- **Severity** (Critical/High/Medium/Low) +- **Category** (Breaking Change, Backward Incompatible, Upgrade Path, etc.) +- **Location** (file:line) +- **Description** +- **Impact** +- **Suggested mitigation** + +--- + +### Phase 3: LLM-Powered Regression Prediction + +Use Claude to analyze the diffs for subtle regressions that static analysis might miss. + +#### Step 3.1: Prepare Analysis Context + +Build a comprehensive context document with: +- Repository information (name, framework, API groups) +- Commit summary (number of commits, files changed) +- Extracted diffs (types, CRDs, controllers) +- Static analysis findings + +#### Step 3.2: Construct LLM Analysis Prompt + +Create a detailed prompt for Claude following this template: + +```markdown +You are an OpenShift operator regression analysis expert. Analyze the following API and controller changes to predict potential regressions, breaking changes, and backward compatibility issues. + +## Repository Context + +- **Operator**: {repo_name} +- **Framework**: {controller-runtime|library-go} +- **Go Module**: {go_module} +- **Base Branch**: {base_branch} +- **Commits Analyzed**: {commit_count} commits +- **Files Changed**: {files_changed} + +## Static Analysis Findings + +{static_findings_summary} + +## API Type Changes + +```go +{api_types_diff} +``` + +## CRD Schema Changes + +```yaml +{crd_diff} +``` + +## Controller/Reconciler Changes + +```go +{controller_diff} +``` + +## RBAC Changes + +```yaml +{rbac_diff} +``` + +## Webhook Changes + +```go +{webhook_diff} +``` + +## Analysis Required + +Perform a deep analysis to identify: + +### 1. Breaking Changes (CRITICAL severity) +- Changes that will cause existing CRs to fail validation +- Changes that will break existing operator deployments +- Changes that will fail upgrades from previous versions + +### 2. Backward Compatibility Issues (HIGH severity) +- New required fields without defaults +- Removed API fields still in use +- Changed field semantics +- Controller behavior changes affecting existing deployments + +### 3. Upgrade Path Problems (HIGH severity) +- Missing conversion webhooks for new API versions +- Status field incompatibilities +- Condition type changes +- State migration issues + +### 4. Subtle Behavioral Regressions (MEDIUM severity) +- Changed reconciliation logic +- Modified condition update patterns +- Resource ownership changes +- Different error handling + +### 5. Performance/Scalability Concerns (MEDIUM/LOW severity) +- Unbounded list fields +- Missing pagination +- Inefficient reconciliation patterns +- Resource-intensive operations + +## Output Format + +For each finding, provide: + +```yaml +finding_id: LLM-XXX +severity: CRITICAL|HIGH|MEDIUM|LOW +category: Breaking Change|Backward Incompatible|Upgrade Path|Behavior Change|Performance +title: Brief title +location: file:line (if applicable) +impact: | + Detailed description of the impact on: + - Existing CRs + - Running operators + - Upgrade scenarios + - End users +evidence: | + Code snippets or diff sections that support this finding +risk_scenario: | + Specific scenario(s) where this issue will manifest +mitigation: | + - Actionable step 1 + - Actionable step 2 + - Actionable step 3 +test_scenarios: | + - Test scenario 1 + - Test scenario 2 +priority: 1-5 (1=must fix before merge, 5=consider for future) +``` + +## Special Focus Areas + +Pay extra attention to: + +1. **API versioning**: Are multiple versions served? Is there a conversion strategy? +2. **Defaulting vs Required**: Are new fields properly defaulted or will they break existing CRs? +3. **Condition semantics**: Do condition types or meanings change? +4. **Status subresource**: Are status updates backward compatible? +5. **Webhook logic**: Will validation reject previously valid CRs? +6. **RBAC**: Are new permissions needed that aren't granted? +7. **Managed resources**: Do changes affect deployed workloads? + +## Analysis Style + +- Be specific: Reference exact files, lines, and field names +- Be practical: Focus on real-world impact +- Be thorough: Consider edge cases and upgrade scenarios +- Be constructive: Always suggest mitigations +- Prioritize: Order findings by severity and impact +``` + +#### Step 3.3: Execute LLM Analysis + +Send the prompt to Claude and parse the structured YAML output into findings. + +--- + +### Phase 4: Discover Existing API Versions and CRs + +To provide accurate recommendations, discover what versions and CRs currently exist. + +```bash +# Find all API versions in the repo +find "$REPO_ROOT" -path '*/api/*' -name '*_types.go' -not -path '*/vendor/*' | \ + xargs grep -h "^// +groupName=" | sort -u + +# Find CRD served versions +find "$REPO_ROOT" -name '*.yaml' -path '*/crd*' -not -path '*/vendor/*' | \ + xargs grep -A2 "kind: CustomResourceDefinition" | grep "versions:" -A5 + +# Check for conversion webhooks +find "$REPO_ROOT" -name '*.go' -not -path '*/vendor/*' | \ + xargs grep -l "ConvertTo\|ConvertFrom" | head -5 +``` + +--- + +### Phase 5: Generate Regression Risk Report + +Combine static analysis findings and LLM findings into a comprehensive markdown report. + +#### Report Structure + +```markdown +# Regression Risk Report: {repo_name} + +**Generated**: {timestamp} +**Base Branch**: {base_branch} +**HEAD**: {head_commit_hash} +**Commits Analyzed**: {commit_count} +**Files Changed**: {files_changed} (+{insertions} -{deletions}) + +--- + +## Executive Summary + +🔴 **{critical_count} Critical Issues Found** +🟠 **{high_count} High Risk Issues Found** +🟡 **{medium_count} Medium Risk Issues Found** +⚪ **{low_count} Low Risk Issues Found** + +**Overall Risk Assessment**: {Critical|High|Medium|Low} + +{brief_summary_paragraph} + +--- + +## Quick Reference + +| Finding ID | Severity | Category | Title | +|------------|----------|----------|-------| +| {id} | {severity} | {category} | {title} | +... + +--- + +## Critical Findings + +{For each CRITICAL finding, include detailed section with: +- Finding ID +- Title +- Severity badge +- Category +- Location +- Impact description +- Evidence (code snippets) +- Risk scenario +- Mitigation steps (numbered) +- Test scenarios (code blocks) +} + +--- + +## High Risk Findings + +{Same structure as Critical} + +--- + +## Medium Risk Findings + +{Same structure} + +--- + +## Low Risk Findings + +{Same structure, can be more concise} + +--- + +## API Version Summary + +{If multiple API versions exist: +- List all versions +- Conversion webhook status +- Served vs stored versions +- Deprecation status +} + +--- + +## Recommended Actions + +### Before Merge (BLOCKERS) +- [ ] {Critical issue 1} +- [ ] {Critical issue 2} +... + +### Before Release +- [ ] {High priority issue 1} +- [ ] {High priority issue 2} +... + +### Documentation Updates Needed +- [ ] Update upgrade guide with breaking changes +- [ ] Add migration steps for removed fields +- [ ] Document new required fields +... + +### E2E Tests to Add +```go +// Test existing CR compatibility +It("should successfully reconcile CRs created with previous version", func() { + // ... +}) + +// Test upgrade path +It("should handle upgrade from v1alpha1 to v1alpha2", func() { + // ... +}) +``` + +--- + +## Change Impact Matrix + +| Change Type | Files Affected | Breaking | Upgrade Impact | Test Coverage | +|-------------|----------------|----------|----------------|---------------| +| API Types | {count} | {Yes/No} | {High/Medium/Low} | {Suggested tests} | +| CRD Schema | {count} | {Yes/No} | {High/Medium/Low} | {Suggested tests} | +| Controller | {count} | {No} | {Medium/Low} | {Suggested tests} | +| RBAC | {count} | {No} | {Low} | {Suggested tests} | + +--- + +## Appendix: Full Diff Summary + +```text +{git diff --stat output} +``` + +## Appendix: Analysis Methodology + +This report was generated using: +1. **Static Analysis**: Rule-based detection of common breaking change patterns +2. **LLM Analysis**: Claude Sonnet 4.5 deep semantic analysis +3. **Repository Discovery**: Automated scanning of API types, CRDs, controllers +4. **Git Diff Analysis**: Detailed comparison of {base_branch}...HEAD + +--- + +*Generated by OAPE Regression Predictor* +``` + +#### Write the Report + +```bash +mkdir -p "$OUTPUT_DIR" +REPORT_FILE="$OUTPUT_DIR/regression-report.md" + +# Write the generated report content to the file +echo "Report written to: $REPORT_FILE" +``` + +--- + +### Phase 6: Output Summary + +```text +=== Regression Prediction Summary === + +Repository: {go_module} +Framework: {controller-runtime|library-go} +Base Branch: {base_branch} +Changes Analyzed: {N} commits, {M} files changed + +Findings: + 🔴 Critical: {count} + 🟠 High: {count} + 🟡 Medium: {count} + ⚪ Low: {count} + +Risk Assessment: {Critical|High|Medium|Low} + +Report: {output_dir}/regression-report.md + +{If critical issues found:} +⚠️ CRITICAL ISSUES DETECTED ⚠️ +The following must be addressed before merge: + - {issue 1} + - {issue 2} + +{If no critical issues:} +✓ No critical regressions detected. +{If high issues:} +⚠️ Review high-risk findings before release. +``` + +--- + +## Arguments + +- **$1 (base-branch)**: Git branch or ref to diff against — e.g., `main`, `origin/main`, `release-4.18`. Required. +- **--output**: Output directory (optional). Default: `output`. Report written to `<output>/regression-report.md`. + +## Examples + +```shell +# Analyze changes since main +/oape:predict-regressions main + +# Analyze changes since a release branch +/oape:predict-regressions origin/release-4.18 + +# Use custom output directory +/oape:predict-regressions main --output .reports +``` + +## Notes + +- **Comprehensive**: Combines static analysis with LLM-powered semantic analysis +- **Actionable**: Every finding includes specific mitigation steps +- **Prioritized**: Findings ordered by severity and impact +- **Test-focused**: Suggests concrete e2e test scenarios for each issue +- **Framework-aware**: Detects controller-runtime vs library-go patterns +- **Upgrade-focused**: Specifically analyzes API version changes and migration paths + +## Integration with Workflow + +This command can be integrated into the OAPE workflow after API generation: + +1. Generate API types +2. **→ Predict regressions** ← (this command) +3. Address critical issues +4. Generate tests +5. Create PR + +## Behavioral Rules + +1. **Stop on critical**: If critical issues are found, strongly recommend blocking the merge +2. **Never skip analysis**: All changed files must be analyzed, even if large +3. **Evidence-based**: Every finding must cite specific code or diff sections +4. **Constructive**: Always provide mitigation, never just criticize +5. **Test-driven**: Always suggest test scenarios to validate fixes +6. **Version-aware**: Pay special attention to multi-version API scenarios diff --git a/.cursor/commands/review.md b/.cursor/commands/review.md new file mode 100644 index 000000000..e343acab7 --- /dev/null +++ b/.cursor/commands/review.md @@ -0,0 +1,226 @@ +--- +name: /oape:review +id: oape-review +category: OAPE +description: Production-grade OpenShift code reviewer that validates logic, safety, OLM, and build consistency against Jira requirements +argument-hint: <ticket_id> [base_ref] +--- + +## Name +oape:review + +## Synopsis +```shell +/oape:review <ticket_id> [base_ref] +``` + +## Description + +The `oape:review` command performs a "Principal Engineer" level code review. It verifies that the code **actually solves the Jira problem** (Logic) and follows OpenShift safety standards. + +The review covers five modules: +- **Golang Logic & Safety**: Intent matching, execution traces, edge cases, context usage, concurrency, error handling, scheme registration, namespace hardcoding, status handling, event recording +- **Bash Scripts**: Safety patterns, variable quoting, temp file handling +- **Operator Metadata (OLM)**: RBAC updates, RBAC three-way consistency, finalizer handling +- **Build Consistency**: Generation drift detection for types and CRDs, dependency completeness +- **Context-Adaptive Review**: Open-ended analysis tailored to the specific PR (owner references, proxy awareness, API deprecation, and other PR-specific concerns) + +## Arguments + +- `$1` (ticket_id): The Jira Ticket ID (e.g., OCPBUGS-12345). **Required.** +- `$2` (base_ref): The base git ref to diff against. Defaults to `origin/master`. **Optional.** + + +## Implementation + +### Step 1: Determine Base Ref +- If `$2` (base_ref) is provided, use it +- If NOT provided, use `origin/master` + +```bash +BASE_REF="${2:-origin/master}" +``` + +### Step 2: Fetch Context +1. **Jira Issue**: Fetch the Jira issue details using curl: + ```bash + curl -s "https://issues.redhat.com/browse/$1" + ``` + Focus on Acceptance Criteria as the primary validation source. + +2. **Git Diff**: Get the code changes: + ```bash + git diff ${BASE_REF}...HEAD --stat -p + ``` + +3. **File List**: Get list of changed files: + ```bash + git diff ${BASE_REF}...HEAD --name-only + ``` + +### Step 3: Analyze Code Changes + +Apply **all** of the following review criteria. Modules A–D are **mandatory** — every check must be evaluated on every review, regardless of PR size. Module E is an adaptive pass that extends the review based on what the PR actually does. + +#### Module A: Golang (Logic & Safety) + +**Logic Verification (The "Mental Sandbox")**: +- **Intent Match:** Does the code implementation match the Jira Acceptance Criteria? Quote the Jira line that justifies the change. +- **Execution Trace:** Mentally simulate the function. + - *Happy Path:* Does it succeed as expected? + - *Error Path:* If the API fails, does it retry or return an error? +- **Edge Cases:** + - **Nil/Empty:** Does it handle `nil` pointers or empty slices? + - **State:** Does it handle resources that are `Deleting` or `Pending`? + +**Safety & Patterns**: +- **Context:** REJECT `context.TODO()` in production paths. Must use `context.WithTimeout`. +- **Concurrency:** `go func` must be tracked (WaitGroup/ErrGroup). No race conditions. +- **Errors:** Must use `fmt.Errorf("... %w", err)`. No capitalized error strings. +- **Complexity:** Flag functions > 50 lines or > 3 nesting levels. + +**Idiomatic Clean Code (via Golang-Skills):** +- **Slices/Maps:** Ensure slices are pre-allocated with `make` if the length is known. Avoid unnecessary `nil` slice vs. `empty` slice confusion. +- **Interfaces:** Reject "Interface Pollution" (defining interfaces before they are actually used by multiple implementations). +- **Naming:** Follow Go conventions (e.g., `url` not `URL` in mixed-case, `id` not `ID` for local vars, no `Get` prefix). +- **Receiver Types:** Check for consistency in pointer vs. value receivers. + +**Scheme Registration** *(Severity: CRITICAL)*: +- For every `client.Get()`, `client.List()`, `client.Create()`, `client.Update()`, `client.Delete()` call in changed Go files, identify the GVK of the object being operated on. +- Read `main.go` or any file matching `*scheme*.go`. Look for `AddToScheme` or `SchemeBuilder.Register` calls. +- Every external type (not in the operator's own API group — e.g., `corev1`, `routev1`, `configv1`) used as a client call target **must** have a corresponding `AddToScheme` in scheme setup. +- Flag any external type used in client calls but missing from scheme registration. + +**Namespace Hardcoding** *(Severity: WARNING)*: +- In changed Go files under `controllers/`, `pkg/controller/`, or any reconciler file, scan for string literals matching `"openshift-*"`, `"kube-*"`, or `"default"` used as namespace values. +- These should use constants, environment variables, or config/options structs. +- Ignore test files (`*_test.go`) and string literals inside comments or log messages. + +**Status Handling (Infinite Requeue Prevention)** *(Severity: WARNING)*: +- In `Reconcile()` functions, flag patterns where a terminal/validation error causes `return ctrl.Result{}, err` (infinite requeue). +- Terminal failures (spec validation, type assertion, config parsing — NOT API call errors) should instead set a Degraded condition and return `ctrl.Result{}, nil`. + +**Event Recording** *(Severity: INFO)*: +- Check if reconciler structs embed or reference a `record.EventRecorder`. +- If the reconciler performs significant state transitions (create, update, delete, degrade) without calling `recorder.Event()` or `recorder.Eventf()`, flag it. + +#### Module B: Bash (Scripts) +- **Safety:** Must start with `set -euo pipefail`. +- **Quoting:** Variables in `oc`/`kubectl` commands MUST be quoted (`"$VAR"`). +- **Tmp Files:** Must use `mktemp`, never hardcoded paths like `/tmp/data`. + +#### Module C: Operator Metadata (OLM) +- **RBAC:** If new K8s APIs are used in Go, check if `config/rbac/role.yaml` is updated. +- **RBAC Three-Way Consistency** *(Severity: CRITICAL)*: + - Cross-reference three sources of RBAC truth and flag inconsistencies: + 1. **Kubebuilder markers**: `// +kubebuilder:rbac:groups=...,resources=...,verbs=...` in controller Go files. + 2. **ClusterRole manifest**: `config/rbac/role.yaml` (or `config/rbac/clusterrole.yaml`). + 3. **CSV permissions**: `bundle/manifests/*clusterserviceversion.yaml` — `spec.install.spec.clusterPermissions` and `spec.install.spec.permissions`. + - All three must declare the same API groups, resources, and verbs. + - Common drift: marker added but `role.yaml` not regenerated (missing `make manifests`); `role.yaml` updated but CSV not rebuilt (missing `make bundle`). + - Also verify CSV is updated when API version, description, installModes, or new CRD entries change. +- **Finalizers:** If logic deletes resources, ensure Finalizers are handled to prevent hanging. + +#### Module D: Build Consistency (The "Gotchas") +- **Generation Drift:** + - IF `types.go` is modified, AND `zz_generated.deepcopy.go` is NOT in the file list -> **CRITICAL FAIL**. + - IF `types.go` is modified, AND `config/crd/bases/...yaml` is NOT in the file list -> **CRITICAL FAIL**. +- **Dependency Completeness** *(Severity: WARNING)*: + - If changed Go files introduce new import paths, verify they exist in `go.mod` (direct or indirect). + - If a `vendor/` directory exists and `go.mod` is in the changed file list but `vendor/modules.txt` is not, flag that `go mod vendor` may need to be re-run. + - Flag any import of a package that does not resolve to a module declared in `go.mod`. + +#### Module E: Context-Adaptive Review + +After completing the mandatory checks above, perform an open-ended review pass tailored to this specific PR. Analyze what the code **actually does** and flag issues that the checklist does not cover. Focus areas to consider based on the PR content: + +- **OwnerReferences / Garbage Collection:** If the PR creates child resources (Deployments, ConfigMaps, Services, etc.) via `client.Create()`, verify that `metav1.OwnerReference` is set so child resources are cleaned up when the parent CR is deleted. *(Severity: CRITICAL)* +- **Proxy / Disconnected Environment:** If the PR makes outbound HTTP calls (`http.Get`, `http.NewRequest`, `http.Client`), verify it respects `HTTP_PROXY`/`HTTPS_PROXY`/`NO_PROXY` environment variables. OpenShift clusters behind proxies will fail silently without this. *(Severity: WARNING)* +- **API Deprecation:** If the PR imports API packages, check for deprecated versions (`v1beta1` when `v1` exists, `policy/v1beta1`, `extensions/v1beta1`). *(Severity: WARNING)* +- **Watch Predicates:** If the PR adds or modifies `Watches()`, `For()`, or `Owns()` calls, check if filtering predicates are used to avoid excessive reconciliation. *(Severity: INFO)* +- **Resource Requests/Limits:** If the PR creates Pod specs (Deployments, StatefulSets, Jobs), check if resource requests and limits are set. *(Severity: INFO)* +- **Leader Election Safety:** If the PR modifies cluster-scoped resources or runs background goroutines, verify leader election is configured to prevent split-brain in HA. *(Severity: WARNING)* + +These are starting points, not an exhaustive list. Use your judgment as a principal engineer to flag any additional correctness, safety, or operational concern specific to this PR that is not covered by Modules A–D. + +Report adaptive findings in the `issues` array using `"module": "Adaptive"` and the appropriate severity level. + +### Step 4: Generate Report +Generate a structured JSON report based on the analysis. + +### Step 5: Apply Fixes Automatically + +After the report is generated, if the `issues` array is non-empty, automatically apply the suggested fixes by following the procedure in `implement-review-fixes.md`, passing the review report produced in Step 4 as input. + +This step is skipped when the verdict is `"Approved"` and there are no issues. + +## Return Value + +Returns a JSON report with the following structure, followed by an automatic fix summary if issues were found: + +```json +{ + "summary": { + "verdict": "Approved | Changes Requested", + "rating": "1-10", + "simplicity_score": "1-10" + }, + "logic_verification": { + "jira_intent_met": true, + "missing_edge_cases": ["List handled edge cases or gaps (e.g., 'Does not handle pod deletion')"] + }, + "issues": [ + { + "severity": "CRITICAL", + "module": "Logic", + "file": "pkg/controller/gather.go", + "line": 45, + "description": "Logic Error: Jira asks to 'retry on failure', but code returns 'nil' immediately.", + "fix_prompt": "Update the error handling to use the retry logic..." + }, + { + "severity": "CRITICAL", + "module": "Logic", + "file": "pkg/controller/cert_controller.go", + "line": 112, + "description": "Scheme Registration: client.Get() targets routev1.Route but routev1.AddToScheme is not called in main.go.", + "fix_prompt": "Add routev1.AddToScheme(scheme) to the scheme registration block in main.go..." + }, + { + "severity": "CRITICAL", + "module": "OLM", + "file": "controllers/mycontroller_controller.go", + "line": 28, + "description": "RBAC Consistency: kubebuilder marker grants 'get;list;watch' on 'routes' but config/rbac/role.yaml does not include this rule.", + "fix_prompt": "Run 'make manifests' to regenerate RBAC from kubebuilder markers, then 'make bundle' to update CSV..." + }, + { + "severity": "WARNING", + "module": "Adaptive", + "file": "pkg/controller/cert_controller.go", + "line": 87, + "description": "Proxy Awareness: http.Get() call does not respect HTTP_PROXY/HTTPS_PROXY env vars. Will fail in disconnected clusters.", + "fix_prompt": "Use net/http.ProxyFromEnvironment in the http.Transport to respect cluster proxy settings..." + } + ] +} +``` + +When issues are present, the fixes are applied automatically and a fix summary is appended (see `implement-review-fixes.md` for the summary format). + +## Examples + +1. **Review changes against origin/master**: + ```shell + /oape:review OCPBUGS-12345 + ``` + +2. **Review changes against a specific branch**: + ```shell + /oape:review OCPBUGS-12345 origin/release-4.15 + ``` + +3. **Review changes against a specific commit**: + ```shell + /oape:review OCPBUGS-12345 abc123def + ``` \ No newline at end of file diff --git a/.cursor/e2e-test-generator/docs/e2e-patterns.md b/.cursor/e2e-test-generator/docs/e2e-patterns.md new file mode 100644 index 000000000..e9195cfa2 --- /dev/null +++ b/.cursor/e2e-test-generator/docs/e2e-patterns.md @@ -0,0 +1,194 @@ +# Generic E2E Test Patterns for OpenShift Operators + +This document describes common e2e test patterns across OpenShift operator repositories so the plugin can discover and generate compatible e2e tests for any operator. + +## Framework Detection + +| Framework | go.mod Indicator | Code Pattern | E2E Style | +|---|---|---|---| +| **controller-runtime** | `sigs.k8s.io/controller-runtime` | `Reconcile(ctx, req) (Result, error)` | Ginkgo v2 Go tests | +| **library-go** | `github.com/openshift/library-go` (without controller-runtime) | `sync(ctx, syncCtx) error` | Bash scripts with `oc` commands | + +## Controller-Runtime E2E Structure + +Typical file layout for controller-runtime based operators: + +``` +test/e2e/ + e2e_suite_test.go # Suite setup: kubeconfig, scheme registration, clients + e2e_test.go # Main e2e specs: Describe/Context/It + utils/ + constants.go # Namespace, deployment names, label selectors, timeouts + utils.go # Helpers: WaitFor*, condition checkers, resource getters +``` + +### Conventions + +- **Package**: `e2e` +- **Framework**: Ginkgo v2 (`Describe`, `Context`, `It`, `BeforeAll`, `BeforeEach`, `By`, `DeferCleanup`, `Eventually`) +- **Clients** (set in suite): `k8sClient` (controller-runtime), `clientset` (kubernetes.Interface), optionally `apiextClient`, `configClient` +- **Per-test context**: `testCtx` from `context.WithTimeout` in `BeforeEach`, with `DeferCleanup(cancel)` +- **Constants**: Use `utils.*` (e.g., `utils.OperatorNamespace`, `utils.DefaultTimeout`) +- **Assertions**: Gomega (`Expect`, `Eventually`, `Consistently`, `HaveLen`, `BeTrue`, etc.) + +### Suite Setup Pattern + +```go +var ( + k8sClient client.Client + clientset kubernetes.Interface + testCtx context.Context +) + +func TestE2E(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "E2E Suite") +} + +var _ = BeforeSuite(func() { + cfg, err := config.GetConfig() + Expect(err).NotTo(HaveOccurred()) + + scheme := runtime.NewScheme() + // Register scheme types... + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme}) + Expect(err).NotTo(HaveOccurred()) + + clientset, err = kubernetes.NewForConfig(cfg) + Expect(err).NotTo(HaveOccurred()) +}) +``` + +## Library-Go E2E Structure + +Typical file layout for library-go based operators: + +``` +test/e2e/ + e2e_test.sh # Main e2e script (or hack/e2e.sh) + framework/ + helpers.sh # Optional helper functions +``` + +Or commonly: + +``` +hack/ + e2e.sh # E2E test script +``` + +### Conventions + +- **Shell**: `#!/usr/bin/env bash` with `set -euo pipefail` +- **Test structure**: Functions named `test_<scenario>()` +- **Assertions**: `oc` commands with exit code checks, `grep`, `jq` for JSON parsing +- **Timeouts**: `oc wait --for=condition=... --timeout=...` +- **Cleanup**: `trap` or explicit cleanup function at end +- **Namespace**: Typically uses a fixed operator namespace or creates a test namespace + +### Bash Test Pattern + +```bash +#!/usr/bin/env bash +set -euo pipefail + +OPERATOR_NAMESPACE="${OPERATOR_NAMESPACE:-openshift-cluster-csi-drivers}" +TEST_NAMESPACE="e2e-test-$(head -c 4 /dev/urandom | xxd -p)" +TIMEOUT="120s" + +setup() { + oc create namespace "$TEST_NAMESPACE" + # Apply test resources... +} + +test_cr_lifecycle() { + oc apply -f config/samples/sample-cr.yaml -n "$TEST_NAMESPACE" + oc wait --for=condition=Ready <kind>/<name> -n "$TEST_NAMESPACE" --timeout="$TIMEOUT" + # Verify expected state... +} + +cleanup() { + oc delete namespace "$TEST_NAMESPACE" --ignore-not-found +} + +trap cleanup EXIT +setup +test_cr_lifecycle +echo "All e2e tests passed" +``` + +## Common Patterns (Both Frameworks) + +### OLM Install Verification + +```bash +# Check CRDs are established +oc wait --for=condition=Established crd/<crd-name> --timeout=60s + +# Check CSV is succeeded +oc wait --for=jsonpath='{.status.phase}'=Succeeded csv -l <label> -n <namespace> --timeout=300s + +# Check operator deployment is available +oc wait --for=condition=Available deployment/<name> -n <namespace> --timeout=300s +``` + +### CR Lifecycle Testing + +1. **Create**: Apply CR from sample or inline YAML +2. **Verify conditions**: `oc wait --for=condition=Ready` or poll via client +3. **Update**: Patch CR fields, verify propagation to backing workloads +4. **Delete**: Remove CR, verify cleanup of managed resources + +### Condition Polling (Ginkgo) + +```go +Eventually(func(g Gomega) { + cr := &v1alpha1.MyResource{} + err := k8sClient.Get(testCtx, client.ObjectKey{Name: "cluster", Namespace: ns}, cr) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(cr.Status.Conditions).To(ContainElement( + HaveField("Type", Equal("Ready")), + )) +}, utils.DefaultTimeout, 5*time.Second).Should(Succeed()) +``` + +### Cleanup Ordering + +Always clean up in reverse dependency order: +1. Operand/user CRs (reverse creation order) +2. Operator subscription / CSV +3. OperatorGroup +4. Namespace + +### Pod Recovery Testing + +```go +// Delete operator pod +pods := &corev1.PodList{} +k8sClient.List(testCtx, pods, client.InNamespace(ns), client.MatchingLabels{...}) +for _, pod := range pods.Items { + k8sClient.Delete(testCtx, &pod) +} +// Wait for new pod to be ready +Eventually(func(g Gomega) { + dep := &appsv1.Deployment{} + k8sClient.Get(testCtx, client.ObjectKey{Name: depName, Namespace: ns}, dep) + g.Expect(dep.Status.AvailableReplicas).To(Equal(int32(1))) +}, timeout, poll).Should(Succeed()) +``` + +## Discovery Checklist + +When analyzing an operator repo, discover these in order: + +| # | Target | Where to Look | What to Extract | +|---|--------|--------------|-----------------| +| 1 | Framework | `go.mod` | controller-runtime or library-go | +| 2 | API types | `api/**/*_types.go`, `types_*.go` | Kind, group, version, fields, conditions | +| 3 | CRDs | `config/crd/**/*.yaml`, `config/manifests/**/*.yaml` | Kind, group, plural, scope | +| 4 | Existing e2e | `test/e2e/`, `hack/e2e.sh` | Framework (Ginkgo/bash), package, imports, clients, helpers | +| 5 | Install mechanism | `config/manifests/`, `bundle/`, `deploy/` | OLM (Subscription, CSV) or manual (Deployment) | +| 6 | Samples | `config/samples/`, `examples/` | Sample CR manifests with default values | +| 7 | Namespace | e2e constants, deploy manifests, CSV | Operator namespace | +| 8 | Controllers | `*controller*.go`, `*reconcile*.go`, `pkg/operator/` | Reconciliation targets, managed resources | diff --git a/.cursor/e2e-test-generator/fixtures/e2e-important-scenarios.md b/.cursor/e2e-test-generator/fixtures/e2e-important-scenarios.md new file mode 100644 index 000000000..5ac5152ea --- /dev/null +++ b/.cursor/e2e-test-generator/fixtures/e2e-important-scenarios.md @@ -0,0 +1,60 @@ +# Important E2E Scenarios (Generic Operator) + +Use this list when suggesting or generating e2e tests for any OpenShift operator. These scenarios should be **highly checked** for operator and operand health. Adapt Kind names, namespaces, and conditions to the actual operator being tested. + +## Operator + +| # | Scenario | What to verify | +|---|----------|----------------| +| 1 | Operator install | All managed CRDs Established; operator Deployment/Pod Available in operator namespace. | +| 2 | Operator recovery | After force-deleting operator pod(s), new pod(s) Running and Deployment Available again. | +| 3 | Operator log level | If configurable (via Subscription env, CR field, or command-line flag), verify propagation to deployment and container args/env. | + +## Manager CR (if the operator has a top-level manager/config CR) + +| # | Scenario | What to verify | +|---|----------|----------------| +| 4 | Manager CR created | Create manager CR with required fields; no error; conditions progress to Available/Ready. | +| 5 | Operand aggregation | If manager CR aggregates operand status, verify all operands reported with expected state. | +| 6 | Management state | If the CR supports managementState (Managed/Unmanaged/Removed), verify each state behaves correctly. | + +## Operand CRs (for each CR kind the operator manages) + +| # | Scenario | What to verify | +|---|----------|----------------| +| 7 | Operand CR lifecycle | Create operand CR; conditions become True/Ready; backing workload (Deployment/StatefulSet/DaemonSet) ready. | +| 8 | Operand deletion | Delete operand CR; backing workload and managed resources cleaned up. | +| 9 | Operand update | Patch operand CR fields; backing workload updated (rolling update observed). | + +## OperatorCondition (if applicable) + +| # | Scenario | What to verify | +|---|----------|----------------| +| 10 | Upgradeable when healthy | OperatorCondition Upgradeable status True. | +| 11 | Upgradeable degraded and recovery | Delete an operand pod; Upgradeable becomes False; after operand recovers, Upgradeable returns to True. | + +## CR-Driven Configuration (for operands with configurable fields) + +| # | Scenario | What to verify | +|---|----------|----------------| +| 12 | Resource limits/requests | Patch CR with resources (limits/requests); backing workload pods have expected resources. | +| 13 | Scheduling (nodeSelector, tolerations, affinity) | Patch CR with scheduling fields; pods rescheduled as expected. | +| 14 | Configuration propagation | Patch CR spec fields; verify ConfigMaps, Secrets, or container args/env updated accordingly. | +| 15 | Log level | Patch CR log level; verify container args or env reflect the change. | + +## Validation (negative tests) + +| # | Scenario | What to verify | +|---|----------|----------------| +| 16 | Invalid CR rejected | Apply CR with invalid field values (out-of-range, wrong type, missing required); expect admission error or degraded condition. | +| 17 | Immutable field enforcement | If fields are marked immutable, attempt to change them after creation; expect error. | + +## Diff-Specific Guidance + +When generating tests from a git diff, focus on: + +- **API/CRD changes** (new or modified `*_types.go`, CRD YAML): Generate CR create/update tests for new fields, condition checks for new conditions. +- **Controller changes** (`*controller*.go`, `*reconcile*.go`): Generate reconciliation, recovery, and lifecycle tests for the affected CR kinds. +- **RBAC changes** (`config/rbac/*.yaml`): Verify operator has required permissions by exercising the affected operations. +- **Sample changes** (`config/samples/*.yaml`): Validate updated samples apply successfully and produce expected state. +- **Validation/webhook changes**: Generate negative tests with invalid values. diff --git a/.cursor/e2e-test-generator/fixtures/e2e-sample-controller-runtime_test.go.example b/.cursor/e2e-test-generator/fixtures/e2e-sample-controller-runtime_test.go.example new file mode 100644 index 000000000..dc18ed42c --- /dev/null +++ b/.cursor/e2e-test-generator/fixtures/e2e-sample-controller-runtime_test.go.example @@ -0,0 +1,143 @@ +// This file is a STYLE REFERENCE for generated e2e test code. +// Do not run directly; it uses placeholder types and assumes a suite exists. +// Copy patterns (Describe/Context/It, By, utils.*, k8sClient, testCtx) into the target repo. +// +// Replace all <PLACEHOLDERS> with actual values discovered from the operator repo: +// <operator-api-import> => e.g. "github.com/openshift/external-secrets-operator/api/v1alpha1" +// <OperatorCR> => e.g. ExternalSecretsConfig, ExternalSecretsManager +// <operator-namespace> => e.g. "external-secrets-operator" +// <cr-name> => e.g. "cluster", "default" + +package e2e + +import ( + "context" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + // Replace with actual operator API import: + // operatorv1alpha1 "<operator-api-import>" + // Replace with actual e2e utils import: + // "path/to/test/e2e/utils" +) + +var _ = Describe("Operator E2E", Ordered, func() { + var testCtx context.Context + + BeforeEach(func() { + var cancel context.CancelFunc + testCtx, cancel = context.WithTimeout(context.Background(), 5*time.Minute) + DeferCleanup(cancel) + }) + + // --- Operator Installation --- + Context("Operator installation", func() { + // Diff-suggested: verify operator deployment is healthy + It("should have the operator deployment available", func() { + By("Checking operator deployment is available") + Eventually(func(g Gomega) { + dep := &appsv1.Deployment{} + err := k8sClient.Get(testCtx, client.ObjectKey{ + Name: "<operator-deployment-name>", + Namespace: "<operator-namespace>", + }, dep) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(dep.Status.AvailableReplicas).To(BeNumerically(">=", 1), + "operator deployment should have at least 1 available replica") + }, 2*time.Minute, 5*time.Second).Should(Succeed()) + }) + }) + + // --- CR Lifecycle --- + Context("CR lifecycle", func() { + // Diff-suggested: verify CR creation and conditions + It("should create the CR and reach Ready condition", func() { + By("Creating the CR") + // cr := &operatorv1alpha1.<OperatorCR>{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: "<cr-name>", + // Namespace: "<operator-namespace>", // omit for cluster-scoped + // }, + // Spec: operatorv1alpha1.<OperatorCR>Spec{ + // // Fill required spec fields from discovered types + // }, + // } + // Expect(k8sClient.Create(testCtx, cr)).To(Succeed()) + + By("Waiting for CR to be Ready") + // Eventually(func(g Gomega) { + // fetched := &operatorv1alpha1.<OperatorCR>{} + // err := k8sClient.Get(testCtx, client.ObjectKeyFromObject(cr), fetched) + // g.Expect(err).NotTo(HaveOccurred()) + // // Check conditions - adapt to actual condition types: + // for _, c := range fetched.Status.Conditions { + // if c.Type == "Ready" { + // g.Expect(c.Status).To(Equal(metav1.ConditionTrue)) + // } + // } + // }, 3*time.Minute, 5*time.Second).Should(Succeed()) + _ = metav1.Now() // placeholder to avoid unused import + }) + }) + + // --- Operator Recovery --- + Context("Operator recovery", func() { + // Diff-suggested: verify operator recovers from pod deletion + It("should recover after operator pod deletion", func() { + By("Listing operator pods") + pods := &corev1.PodList{} + err := k8sClient.List(testCtx, pods, + client.InNamespace("<operator-namespace>"), + client.MatchingLabels{"<label-key>": "<label-value>"}, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(pods.Items).NotTo(BeEmpty(), "should find operator pods") + + By("Deleting operator pod(s)") + for i := range pods.Items { + Expect(k8sClient.Delete(testCtx, &pods.Items[i])).To(Succeed()) + } + + By("Waiting for operator deployment to recover") + Eventually(func(g Gomega) { + dep := &appsv1.Deployment{} + err := k8sClient.Get(testCtx, client.ObjectKey{ + Name: "<operator-deployment-name>", + Namespace: "<operator-namespace>", + }, dep) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(dep.Status.AvailableReplicas).To(BeNumerically(">=", 1)) + }, 2*time.Minute, 5*time.Second).Should(Succeed()) + }) + }) + + // --- CR Field Propagation --- + Context("CR configuration propagation", func() { + // Diff-suggested: verify spec field changes propagate to workloads + It("should propagate resource limits to backing workload", func() { + By("Patching CR with resource limits") + // Use k8sClient.Patch or k8sClient.Update to set resources on the CR + + By("Verifying backing workload has updated resources") + // Eventually check the Deployment/StatefulSet/DaemonSet containers + // for the expected resource limits/requests + }) + }) + + // --- OperatorCondition Upgradeable --- + Context("OperatorCondition", func() { + // Diff-suggested: verify Upgradeable condition + It("should report Upgradeable True when healthy", func() { + By("Getting OperatorCondition for the operator") + // Look up the OperatorCondition by label or name convention + // Verify Upgradeable condition status is True + }) + }) +}) diff --git a/.cursor/e2e-test-generator/fixtures/e2e-sample-library-go_test.sh.example b/.cursor/e2e-test-generator/fixtures/e2e-sample-library-go_test.sh.example new file mode 100644 index 000000000..e19651188 --- /dev/null +++ b/.cursor/e2e-test-generator/fixtures/e2e-sample-library-go_test.sh.example @@ -0,0 +1,145 @@ +#!/usr/bin/env bash +# This file is a STYLE REFERENCE for generated e2e test scripts. +# Do not run directly; it uses placeholder values. +# +# Replace all <PLACEHOLDERS> with actual values discovered from the operator repo: +# <operator-namespace> => e.g. "openshift-cluster-csi-drivers" +# <operator-deployment> => e.g. "secrets-store-csi-driver-operator" +# <cr-kind> => e.g. "clustercsidriver", "secretproviderclass" +# <cr-name> => e.g. "secrets-store.csi.k8s.io", "cluster" +# <crd-name> => e.g. "secretproviderclasses.secrets-store.csi.x-k8s.io" +# <operator-label> => e.g. "app=secrets-store-csi-driver-operator" +# <sample-cr-path> => e.g. "config/samples/sample-cr.yaml" + +set -euo pipefail + +# ============================================================ +# Configuration (replace with discovered values) +# ============================================================ +OPERATOR_NAMESPACE="${OPERATOR_NAMESPACE:-<operator-namespace>}" +OPERATOR_DEPLOYMENT="${OPERATOR_DEPLOYMENT:-<operator-deployment>}" +OPERATOR_LABEL="${OPERATOR_LABEL:-<operator-label>}" +TEST_NAMESPACE="e2e-test-$(head -c 4 /dev/urandom | xxd -p)" +TIMEOUT="${E2E_TEST_POD_TIMEOUT:-120s}" + +# ============================================================ +# Helpers +# ============================================================ +log() { echo "=== $(date '+%H:%M:%S') $*"; } +fail() { log "FAIL: $*"; exit 1; } + +wait_for_pods() { + local ns="$1" label="$2" count="${3:-1}" + log "Waiting for $count pod(s) with label=$label in namespace=$ns" + local deadline=$((SECONDS + ${TIMEOUT%s})) + while [ "$SECONDS" -lt "$deadline" ]; do + local ready + ready=$(oc get pods -n "$ns" -l "$label" --no-headers 2>/dev/null \ + | grep -c "Running" || true) + if [ "$ready" -ge "$count" ]; then + log "Found $ready running pod(s)" + return 0 + fi + sleep 5 + done + fail "Timed out waiting for pods with label=$label in $ns" +} + +# ============================================================ +# Setup +# ============================================================ +setup() { + log "Creating test namespace: $TEST_NAMESPACE" + oc create namespace "$TEST_NAMESPACE" || true + oc label namespace "$TEST_NAMESPACE" \ + security.openshift.io/scc.podSecurityLabelSync=false \ + pod-security.kubernetes.io/enforce=privileged \ + pod-security.kubernetes.io/audit=privileged \ + pod-security.kubernetes.io/warn=privileged \ + --overwrite +} + +# ============================================================ +# Tests +# ============================================================ + +# Diff-suggested: verify operator is installed and running +test_operator_install() { + log "TEST: Operator installation" + + log "Checking CRDs are established" + oc wait --for=condition=Established crd/<crd-name> --timeout=60s \ + || fail "CRD <crd-name> not established" + + log "Checking operator deployment is available" + oc wait --for=condition=Available \ + deployment/"$OPERATOR_DEPLOYMENT" \ + -n "$OPERATOR_NAMESPACE" \ + --timeout="$TIMEOUT" \ + || fail "Operator deployment not available" + + log "Checking operator pods are running" + wait_for_pods "$OPERATOR_NAMESPACE" "$OPERATOR_LABEL" 1 + + log "PASS: Operator installation" +} + +# Diff-suggested: verify CR lifecycle +test_cr_lifecycle() { + log "TEST: CR lifecycle" + + log "Applying sample CR" + oc apply -f <sample-cr-path> -n "$TEST_NAMESPACE" \ + || fail "Failed to apply sample CR" + + log "Waiting for CR to be ready" + oc wait --for=condition=Ready \ + <cr-kind>/<cr-name> \ + -n "$TEST_NAMESPACE" \ + --timeout="$TIMEOUT" \ + || fail "CR did not reach Ready condition" + + log "Verifying CR status" + local status + status=$(oc get <cr-kind>/<cr-name> -n "$TEST_NAMESPACE" -o jsonpath='{.status}') + [ -n "$status" ] || fail "CR has no status" + + log "PASS: CR lifecycle" +} + +# Diff-suggested: verify operator recovery +test_operator_recovery() { + log "TEST: Operator recovery" + + log "Force-deleting operator pod(s)" + oc delete pod -n "$OPERATOR_NAMESPACE" -l "$OPERATOR_LABEL" --force --grace-period=0 + + log "Waiting for operator deployment to recover" + oc wait --for=condition=Available \ + deployment/"$OPERATOR_DEPLOYMENT" \ + -n "$OPERATOR_NAMESPACE" \ + --timeout="$TIMEOUT" \ + || fail "Operator did not recover" + + wait_for_pods "$OPERATOR_NAMESPACE" "$OPERATOR_LABEL" 1 + + log "PASS: Operator recovery" +} + +# ============================================================ +# Cleanup +# ============================================================ +cleanup() { + log "Cleaning up test namespace: $TEST_NAMESPACE" + oc delete namespace "$TEST_NAMESPACE" --ignore-not-found --wait=false +} + +# ============================================================ +# Main +# ============================================================ +trap cleanup EXIT +setup +test_operator_install +test_cr_lifecycle +test_operator_recovery +log "All e2e tests passed" diff --git a/.cursor/skills/e2e-test-generator/SKILL.md b/.cursor/skills/e2e-test-generator/SKILL.md new file mode 100644 index 000000000..96803b51c --- /dev/null +++ b/.cursor/skills/e2e-test-generator/SKILL.md @@ -0,0 +1,158 @@ +--- +name: E2E Test Generator +description: Generate e2e test artifacts for any OpenShift operator based on repository discovery and git diff analysis +--- + +# E2E Test Generator Skill + +## Persona + +You are an **OpenShift operator QE engineer**. You generate e2e test artifacts for any OpenShift operator repository by discovering the repo structure dynamically. You think in terms of: + +- **Install and lifecycle**: operator install via OLM or manual deployment, CSV/deployment readiness, CR creation order +- **Regression and diff coverage**: map git diff changed files to field tests, controller tests, validation, RBAC +- **Operator and operands**: manager CRs, operand CRs, conditions, status aggregation, OperatorCondition Upgradeable +- **Cleanup and recovery**: correct deletion order (CRs, then OLM resources, then namespace), operator pod recovery +- **Framework awareness**: controller-runtime with Ginkgo e2e, or library-go with bash e2e + +You never hardcode operator-specific knowledge. You discover everything from the repository. + +--- + +## Framework Detection + +Detect the operator framework from `go.mod`: + +| go.mod Dependency | Framework | E2E Pattern | +|---|---|---| +| `sigs.k8s.io/controller-runtime` | controller-runtime | Ginkgo v2 Go tests | +| `github.com/openshift/library-go` (without controller-runtime) | library-go | Bash scripts with `oc` commands | +| Both present | controller-runtime | Ginkgo v2 Go tests | +| Neither | Unknown | Warn and default to bash | + +## Discovery Protocol + +Before generating any test artifacts, discover the following from the repository. Run these steps in order: + +### 1. API Types + +```bash +find "$REPO_ROOT" -type f \( -name '*_types.go' -o -name 'types_*.go' \) \ + -not -path '*/vendor/*' -not -path '*/_output/*' -not -path '*/zz_generated*' +``` + +Read each types file to extract: API group, version, Kind names, CR plural names, namespace/cluster scope, key spec/status fields, conditions. + +If no types files found in-repo (common with library-go operators), check `go.mod` for `github.com/openshift/api` and note that types come from the external module. Look in `vendor/github.com/openshift/api/` for the relevant types. + +### 2. CRDs + +```bash +find "$REPO_ROOT" -type f -name '*.yaml' \( -path '*/crd/*' -o -path '*/crds/*' \) \ + -not -path '*/vendor/*' +``` + +Also check `config/manifests/` for CRD YAML files. Extract: Kind, group, plural, scope, served versions. + +### 3. Existing E2E Tests + +```bash +find "$REPO_ROOT" -type f \( -name '*_test.go' -o -name '*.sh' \) \ + \( -path '*/e2e/*' -o -path '*/hack/e2e*' \) -not -path '*/vendor/*' +``` + +Classify: +- `_test.go` files with Ginkgo imports → Ginkgo e2e pattern +- `.sh` files → bash e2e pattern + +Read 1-2 existing e2e files to capture: package name, import style, client setup, namespace conventions, helper utilities, assertion patterns. + +### 4. Install Mechanism + +```bash +find "$REPO_ROOT" -type f -name '*.yaml' \ + \( -path '*/config/manifests/*' -o -path '*/bundle/*' -o -path '*/deploy/*' \ + -o -path '*/config/default/*' \) -not -path '*/vendor/*' +``` + +Look for: Namespace definitions, OperatorGroup, Subscription (OLM install), CSV (ClusterServiceVersion), sample CRs. + +### 5. Samples + +```bash +find "$REPO_ROOT" -type f -name '*.yaml' \ + \( -path '*/config/samples/*' -o -path '*/examples/*' \) -not -path '*/vendor/*' +``` + +### 6. Operator Namespace + +Search for namespace in: +- E2E constants files (`utils/constants.go`) +- Deploy manifests or CSV +- Namespace YAML in config/ + +### 7. Controllers + +```bash +find "$REPO_ROOT" -type f -name '*.go' \ + \( -name '*controller*' -o -name '*reconcile*' -o -name 'starter.go' \) \ + -not -path '*/vendor/*' -not -path '*_test.go' +``` + +Identify reconciliation targets and managed resources. + +## Test Scenario Categories + +When generating e2e tests, consider these generic categories (adapt to the specific operator): + +1. **Operator install**: CRDs established, deployment available, pods running +2. **Operator recovery**: pod deletion, redeployment, health restored +3. **CR lifecycle**: create, read, update, delete for each managed CR kind +4. **Condition checks**: wait for expected conditions on each CR kind +5. **Status aggregation**: if a manager CR aggregates operand status +6. **Configuration propagation**: CR spec fields reflected in Deployments/StatefulSets/DaemonSets +7. **Validation**: invalid CR values rejected (negative tests) +8. **RBAC**: operator has required permissions +9. **OperatorCondition Upgradeable**: True when healthy, False when degraded, recovery +10. **Management state**: Managed/Unmanaged/Removed (if supported) + +See [fixtures/e2e-important-scenarios.md](../../e2e-test-generator/fixtures/e2e-important-scenarios.md) for detailed scenario descriptions. + +## Code Style by Framework + +### Ginkgo (controller-runtime) + +- Package matches existing e2e package (usually `e2e`) +- Imports match existing e2e imports exactly +- Use discovered client variables (`k8sClient`, `clientset`, etc.) +- `Describe`/`Context`/`It` structure with `By("...")` steps +- `DeferCleanup` for teardown +- `Eventually` with timeout/polling for async assertions +- Each `It` block commented with `// Diff-suggested: <reason>` for pick-and-choose +- No `BeforeSuite` / `TestE2E` / client setup — only test blocks + +See [fixtures/e2e-sample-controller-runtime_test.go.example](../../e2e-test-generator/fixtures/e2e-sample-controller-runtime_test.go.example) for reference. + +### Bash (library-go) + +- `#!/usr/bin/env bash` with `set -euo pipefail` +- Functions named `test_<scenario>()` +- `oc` commands for all cluster operations +- `oc wait --for=condition=...` for assertions +- `trap cleanup EXIT` for cleanup +- Log with timestamps for debugging + +See [fixtures/e2e-sample-library-go_test.sh.example](../../e2e-test-generator/fixtures/e2e-sample-library-go_test.sh.example) for reference. + +## Output Guidelines + +**Output directory**: `output/e2e_<repo-name>/` (e.g., `output/e2e_external-secrets-operator/`). The `<repo-name>` is derived from the Go module path basename. With `--output <path>`, use `<path>/e2e_<repo-name>/`. Create the directory if it does not exist. + +**Generated files** (all inside the output directory): + +1. **test-cases.md** — Test scenarios with operator info, prerequisites, install steps, CR deployment, diff-specific test cases, verification, cleanup +2. **execution-steps.md** — Step-by-step procedure with executable `oc` commands +3. **e2e test code** — `e2e_test.go` (Ginkgo) or `e2e_test.sh` (bash), matching the repo's existing pattern +4. **e2e-suggestions.md** — Which scenarios apply, highly recommended tests, optional tests + +All values in generated files are discovered from the repo — never hardcoded. diff --git a/.cursor/skills/effective-go/SKILL.md b/.cursor/skills/effective-go/SKILL.md new file mode 100644 index 000000000..586a280f9 --- /dev/null +++ b/.cursor/skills/effective-go/SKILL.md @@ -0,0 +1,347 @@ +# effective-go + +Ensures all generated Go code follows best practices from the official Effective Go documentation and Go community standards. + +## Purpose + +This skill provides guidelines for writing idiomatic, clean, and maintainable Go code. It is applied whenever generating Go code (types, controllers, tests) to ensure consistency and quality. + +## When This Skill Applies + +- Generating API type definitions (`generate-types`) +- Generating controller/reconciler code (`generate-controller`) +- Generating tests (`generate-tests`) +- Any Go code generation or modification + +## Guidelines + +### 1. Formatting + +**Rule:** Always format code with `gofmt` standards. + +```go +// GOOD: Proper formatting +func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + logger := log.FromContext(ctx) + logger.Info("Starting reconciliation") + return ctrl.Result{}, nil +} + +// BAD: Inconsistent formatting +func (r *Reconciler) Reconcile(ctx context.Context,req ctrl.Request) (ctrl.Result,error) { +logger := log.FromContext(ctx) + logger.Info("Starting reconciliation") +return ctrl.Result{},nil +} +``` + +### 2. Naming Conventions + +**Rules:** +- Use `MixedCaps` for exported identifiers (public) +- Use `mixedCaps` for unexported identifiers (private) +- Never use underscores in names +- Acronyms should be consistent case: `HTTP`, `URL`, `ID` (not `Http`, `Url`, `Id`) + +```go +// GOOD: Proper naming +type IngressController struct {} // Exported, MixedCaps +type ingressConfig struct {} // Unexported, mixedCaps +func (r *Reconciler) GetHTTPClient() {} // Acronym all caps +var userID string // ID not Id + +// BAD: Improper naming +type Ingress_Controller struct {} // No underscores +type IngressConfig struct {} // Should be unexported if internal +func (r *Reconciler) GetHttpClient() {} // Http should be HTTP +var userId string // Id should be ID +``` + +### 3. Error Handling + +**Rules:** +- Always check errors explicitly +- Return errors, don't panic (except for truly unrecoverable situations) +- Wrap errors with context using `fmt.Errorf("context: %w", err)` +- Don't ignore errors with `_` + +```go +// GOOD: Proper error handling +func (r *Reconciler) reconcile(ctx context.Context, obj *v1.Resource) error { + if err := r.validateSpec(obj); err != nil { + return fmt.Errorf("spec validation failed: %w", err) + } + + if err := r.createConfigMap(ctx, obj); err != nil { + return fmt.Errorf("failed to create ConfigMap: %w", err) + } + + return nil +} + +// BAD: Poor error handling +func (r *Reconciler) reconcile(ctx context.Context, obj *v1.Resource) { + r.validateSpec(obj) // Error ignored! + + err := r.createConfigMap(ctx, obj) + if err != nil { + panic(err) // Don't panic! + } +} +``` + +### 4. Error Messages + +**Rules:** +- Start with lowercase (errors are often chained) +- Don't end with punctuation +- Be specific about what failed + +```go +// GOOD: Proper error messages +return fmt.Errorf("failed to create ConfigMap %s: %w", name, err) +return fmt.Errorf("spec.replicas must be positive, got %d", replicas) + +// BAD: Poor error messages +return fmt.Errorf("Error creating ConfigMap.") // Uppercase, punctuation +return fmt.Errorf("failed") // Not specific +return errors.New("something went wrong") // Vague +``` + +### 5. Documentation + +**Rules:** +- Document all exported functions, types, and constants +- Start comments with the name of the thing being documented +- Use complete sentences + +```go +// GOOD: Proper documentation +// Reconciler manages the lifecycle of Foo resources. +// It creates and updates dependent resources based on the Foo spec. +type Reconciler struct { + client.Client + Scheme *runtime.Scheme +} + +// Reconcile performs a single reconciliation loop for a Foo resource. +// It returns an error if the reconciliation fails. +func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + // ... +} + +// DefaultRequeueInterval is the default interval between reconciliations. +const DefaultRequeueInterval = 30 * time.Second + +// BAD: Poor or missing documentation +type Reconciler struct { // No documentation + client.Client +} + +// reconciles foo // Doesn't start with name, incomplete sentence +func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { +``` + +### 6. Interfaces + +**Rules:** +- Keep interfaces small (1-3 methods ideally) +- Accept interfaces, return concrete types +- Define interfaces where they're used, not where they're implemented +- Name single-method interfaces with `-er` suffix + +```go +// GOOD: Small, focused interface +type StatusUpdater interface { + UpdateStatus(ctx context.Context, obj client.Object) error +} + +// GOOD: Accept interface, return concrete +func NewReconciler(client client.Client) *Reconciler { + return &Reconciler{Client: client} +} + +// BAD: Large interface +type ResourceManager interface { + Create(ctx context.Context, obj client.Object) error + Update(ctx context.Context, obj client.Object) error + Delete(ctx context.Context, obj client.Object) error + Get(ctx context.Context, key types.NamespacedName, obj client.Object) error + List(ctx context.Context, list client.ObjectList) error + Patch(ctx context.Context, obj client.Object, patch client.Patch) error + // ... too many methods +} +``` + +### 7. Concurrency + +**Rules:** +- Share memory by communicating (use channels) +- Don't communicate by sharing memory +- Use `sync.Mutex` only when channels are impractical +- Always handle context cancellation + +```go +// GOOD: Respect context cancellation +func (r *Reconciler) reconcile(ctx context.Context, obj *v1.Resource) error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + + // Continue with reconciliation + return r.doWork(ctx, obj) +} + +// GOOD: Use channels for coordination +results := make(chan Result, len(items)) +for _, item := range items { + go func(item Item) { + results <- process(item) + }(item) +} +``` + +### 8. Package Organization + +**Rules:** +- Package names should be short, lowercase, single-word +- Avoid `util`, `common`, `misc` package names +- Group related functionality together + +```go +// GOOD: Clear package names +package controller +package reconciler +package status + +// BAD: Poor package names +package controller_utils // No underscores +package common // Too vague +package myPackage // No mixed case +``` + +### 9. Imports + +**Rules:** +- Group imports: standard library, external, internal +- Use blank lines to separate groups +- Use aliases only when necessary (conflicts, clarity) + +```go +// GOOD: Properly organized imports +import ( + "context" + "fmt" + "time" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + configv1 "github.com/openshift/api/config/v1" + "github.com/myorg/myoperator/internal/controller" +) + +// BAD: Unorganized imports +import ( + "github.com/myorg/myoperator/internal/controller" + "context" + corev1 "k8s.io/api/core/v1" + "fmt" + "sigs.k8s.io/controller-runtime/pkg/client" +) +``` + +### 10. Variable Declarations + +**Rules:** +- Use short variable declarations (`:=`) inside functions +- Use `var` for package-level variables or zero values +- Group related declarations + +```go +// GOOD: Appropriate declarations +const ( + DefaultTimeout = 30 * time.Second + MaxRetries = 3 +) + +var ( + ErrNotFound = errors.New("resource not found") + ErrInvalidSpec = errors.New("invalid spec") +) + +func (r *Reconciler) reconcile(ctx context.Context) error { + logger := log.FromContext(ctx) // Short declaration + instance := &v1.Resource{} // Short declaration + + var result ctrl.Result // Zero value needed + return nil +} + +// BAD: Inconsistent declarations +func (r *Reconciler) reconcile(ctx context.Context) error { + var logger = log.FromContext(ctx) // Use := instead + instance := new(v1.Resource) // Use &v1.Resource{} instead +} +``` + +### 11. Receiver Names + +**Rules:** +- Use short, consistent receiver names (1-2 letters) +- Use the same receiver name throughout the type's methods +- Don't use generic names like `this` or `self` + +```go +// GOOD: Short, consistent receivers +func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {} +func (r *Reconciler) reconcileDelete(ctx context.Context, obj *v1.Resource) error {} +func (r *Reconciler) setCondition(obj *v1.Resource, condition metav1.Condition) {} + +// BAD: Inconsistent or long receivers +func (reconciler *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) {} +func (r *Reconciler) reconcileDelete(ctx context.Context, obj *v1.Resource) {} +func (this *Reconciler) setCondition(obj *v1.Resource, condition metav1.Condition) {} +``` + +### 12. Zero Values + +**Rules:** +- Leverage zero values for initialization +- Design types so zero value is useful + +```go +// GOOD: Zero value is useful +type Config struct { + Timeout time.Duration // Zero means no timeout + Replicas int // Zero means default +} + +cfg := Config{} // Usable immediately + +// GOOD: Check for zero value +if cfg.Timeout == 0 { + cfg.Timeout = DefaultTimeout +} +``` + +## References + +- [Effective Go](https://go.dev/doc/effective_go) - Official guide +- [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments) - Common review feedback +- [Go Proverbs](https://go-proverbs.github.io/) - Wisdom from Rob Pike +- [Uber Go Style Guide](https://github.com/uber-go/guide/blob/master/style.md) - Industry practices + +## Usage by Other Skills + +This skill is referenced by: +- `generate-types` - When generating API type definitions +- `generate-controller` - When generating controller code +- `generate-tests` - When generating test code + +All Go code generation MUST follow these guidelines. diff --git a/.cursor/skills/eval-loop/SKILL.md b/.cursor/skills/eval-loop/SKILL.md new file mode 100644 index 000000000..45929e92b --- /dev/null +++ b/.cursor/skills/eval-loop/SKILL.md @@ -0,0 +1,58 @@ +--- +name: eval-loop +description: Run full retrospective eval loop for one feature bundle. Use for /eval-loop. +license: MIT +metadata: + author: openspec + version: "1.1" +--- + +Single command for the eval improvement pipeline. One feature bundle per invocation. + +## When to use + +User runs `/eval-loop` after pasting a feature bundle into `evals/inputs/`. + +## Execution + +1. Read `evals/pipeline.yaml` +2. Validate `evals/inputs/` — halt on `PASTE_` placeholders +3. Load `evals/baseline/`, `evals/refined-templates/`, and `evals/round-state.yaml` +4. Follow `evals/epic-bug-analysis/SYSTEM_PROMPT.md` → write `evals/outputs/epic-bug-analysis/*` +5. Follow `evals/eval-generation/SYSTEM_PROMPT.md`: + - Templates: read/write **`evals/refined-templates/` only** (not `schemas/`) + - Identify gaps → patch refined-templates + - Merge evals into **`evals/baseline/evals/<stage>/<stage>_eval.yaml`** (one file per stage) + - Author **code-generation** evals → `evals/baseline/evals/code-generation/code-generation_eval.yaml` + - Sync flat copies to **`schemas/openspec-agile-workflow/evals/<stage>_eval.yaml`** (`template: templates/...`; code-generation has no template) + - Update baseline registry +6. Increment round in `evals/round-state.yaml` + +## Template path + +**Eval workflow:** `evals/refined-templates/` — read and write. + +**Do not** patch `schemas/openspec-agile-workflow/templates/` during eval. Seed refined-templates from schemas on round 1 if empty. + +## Consolidated eval files + +| Stage | File | +|-------|------| +| repo-assessment | `evals/baseline/evals/repo-assessment/repo-assessment_eval.yaml` | +| constitution | `evals/baseline/evals/constitution/constitution_eval.yaml` | +| plan | `evals/baseline/evals/plan/plan_eval.yaml` | +| tasks | `evals/baseline/evals/tasks/tasks_eval.yaml` | +| implementation | `evals/baseline/evals/implementation/implementation_eval.yaml` | +| code-generation | `evals/baseline/evals/code-generation/code-generation_eval.yaml` | + +## Feedback loop + +- Round 2+ reads `*_eval.yaml` and `evals/refined-templates/` in both phases +- Templates accumulate refinements under `evals/refined-templates/` + +## Do not + +- Split into multiple commands +- Patch schemas/ during eval workflow +- Write per-case `eval-r*.yaml` files — use consolidated `*_eval.yaml` +- Skip Eval Generation after Epic Bug Analysis diff --git a/.cursor/skills/openspec-apply-change/SKILL.md b/.cursor/skills/openspec-apply-change/SKILL.md new file mode 100644 index 000000000..df1a79bf5 --- /dev/null +++ b/.cursor/skills/openspec-apply-change/SKILL.md @@ -0,0 +1,60 @@ +--- +name: openspec-apply-change +description: Implement tasks from an OpenSpec change via OAPE orchestration. Use when the user wants to start implementing, continue implementation, or work through tasks. +license: MIT +compatibility: Requires openspec CLI and OAPE commands in .cursor/commands/. +metadata: + author: openspec + version: "2.5" +--- + +Implement an OpenSpec change using OAPE command orchestration (see `/opsx:apply` and schema `oape_routing`, `code_generation_eval_gate`). + +**Reference:** `.cursor/commands/opsx-apply.md`, `{schema_root}/stage-gate/CODE_GENERATION_EVAL_PROMPT.md` + +**Allowed OAPE commands (one per task):** `api-generate`, `api-generate-tests`, `api-implement`, `e2e-generate` (e2e tasks only). + +**Per-task mandatory sequence:** + +``` +OAPE (or manual — see `{schema_root}/templates/code-generation-template.md`) → verify → code-generation evals → refine code until pass (max 2 passes) +→ present scorecard → user approves CODE → write task report → next task +``` + +**Input**: Optionally specify a change name. If omitted, infer from context or prompt. + +**Steps** + +1. **Select the change** — announce "Using change: <name>". + +2. **Status and apply instructions** + ```bash + openspec status --change "<name>" --json + openspec instructions apply --change "<name>" --json + ``` + +3. **Prerequisites** — OAPE command files; gh/go/git/make; artifacts approved; `implementation/task-reports/` dir. + +4. **Fork setup** — fork_repo_url; clone; feature branch; cwd = fork root. + +5. **Read contextFiles** from apply instructions. + +6. **Parse tasks** from tasks.md §2 order; skip completed tasks. + +7. **Task loop** (each pending task — **no user approval before eval gate completes**): + - Compose `implementation/design-bundle.md` for **current Task ID only** + - Run **one** OAPE command (or manual agent work per `{schema_root}/templates/code-generation-template.md`) + - Verify task Acceptance criteria + - **Code eval gate:** score fork code; refine until evals pass or 2 passes; write `eval-results/code-generation-<task-id>.yaml` + - Present summary + code eval scorecard + - **User approves code** for this task + - **On approve:** write `implementation/task-reports/<task-id>.md`; mark `- [x]`; append phase log + - **On reject:** REVISION FEEDBACK; re-run current task only + +8. **Post-loop** — `implementation-report.md` aggregates all task reports; checklist; adrs; push; draft PR. + +**Guardrails** +- Never ask user approval before code eval refinement loop completes (when cases exist) +- One OAPE command per task; approval after every task +- One task report per approved task +- OAPE in fork cwd only diff --git a/.cursor/skills/openspec-archive-change/SKILL.md b/.cursor/skills/openspec-archive-change/SKILL.md new file mode 100644 index 000000000..12e2f70e9 --- /dev/null +++ b/.cursor/skills/openspec-archive-change/SKILL.md @@ -0,0 +1,114 @@ +--- +name: openspec-archive-change +description: Archive a completed change in the experimental workflow. Use when the user wants to finalize and archive a change after implementation is complete. +license: MIT +compatibility: Requires openspec CLI. +metadata: + author: openspec + version: "1.0" + generatedBy: "1.3.1" +--- + +Archive a completed change in the experimental workflow. + +**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. + +**Steps** + +1. **If no change name provided, prompt for selection** + + Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select. + + Show only active changes (not already archived). + Include the schema used for each change if available. + + **IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose. + +2. **Check artifact completion status** + + Run `openspec status --change "<name>" --json` to check artifact completion. + + Parse the JSON to understand: + - `schemaName`: The workflow being used + - `artifacts`: List of artifacts with their status (`done` or other) + + **If any artifacts are not `done`:** + - Display warning listing incomplete artifacts + - Use **AskUserQuestion tool** to confirm user wants to proceed + - Proceed if user confirms + +3. **Check task completion status** + + Read the tasks file (typically `tasks.md`) to check for incomplete tasks. + + Count tasks marked with `- [ ]` (incomplete) vs `- [x]` (complete). + + **If incomplete tasks found:** + - Display warning showing count of incomplete tasks + - Use **AskUserQuestion tool** to confirm user wants to proceed + - Proceed if user confirms + + **If no tasks file exists:** Proceed without task-related warning. + +4. **Assess delta spec sync state** + + Check for delta specs at `openspec/changes/<name>/specs/`. If none exist, proceed without sync prompt. + + **If delta specs exist:** + - Compare each delta spec with its corresponding main spec at `openspec/specs/<capability>/spec.md` + - Determine what changes would be applied (adds, modifications, removals, renames) + - Show a combined summary before prompting + + **Prompt options:** + - If changes needed: "Sync now (recommended)", "Archive without syncing" + - If already synced: "Archive now", "Sync anyway", "Cancel" + + If user chooses sync, use Task tool (subagent_type: "general-purpose", prompt: "Use Skill tool to invoke openspec-sync-specs for change '<name>'. Delta spec analysis: <include the analyzed delta spec summary>"). Proceed to archive regardless of choice. + +5. **Perform the archive** + + Create the archive directory if it doesn't exist: + ```bash + mkdir -p openspec/changes/archive + ``` + + Generate target name using current date: `YYYY-MM-DD-<change-name>` + + **Check if target already exists:** + - If yes: Fail with error, suggest renaming existing archive or using different date + - If no: Move the change directory to archive + + ```bash + mv openspec/changes/<name> openspec/changes/archive/YYYY-MM-DD-<name> + ``` + +6. **Display summary** + + Show archive completion summary including: + - Change name + - Schema that was used + - Archive location + - Whether specs were synced (if applicable) + - Note about any warnings (incomplete artifacts/tasks) + +**Output On Success** + +``` +## Archive Complete + +**Change:** <change-name> +**Schema:** <schema-name> +**Archived to:** openspec/changes/archive/YYYY-MM-DD-<name>/ +**Specs:** ✓ Synced to main specs (or "No delta specs" or "Sync skipped") + +All artifacts complete. All tasks complete. +``` + +**Guardrails** +- Always prompt for change selection if not provided +- Use artifact graph (openspec status --json) for completion checking +- Don't block archive on warnings - just inform and confirm +- Preserve .openspec.yaml when moving to archive (it moves with the directory) +- Show clear summary of what happened +- If sync is requested, use openspec-sync-specs approach (agent-driven) +- If delta specs exist, always run the sync assessment and show the combined summary before prompting diff --git a/.cursor/skills/openspec-continue-change/SKILL.md b/.cursor/skills/openspec-continue-change/SKILL.md new file mode 100644 index 000000000..c6fc08a70 --- /dev/null +++ b/.cursor/skills/openspec-continue-change/SKILL.md @@ -0,0 +1,164 @@ +--- +name: openspec-continue-change +description: Continue working on an OpenSpec change by creating the next artifact, running eval gate, refining artifact, and requesting user approval. Use when the user wants to progress their change or continue their workflow. +license: MIT +compatibility: Requires openspec CLI. +metadata: + author: openspec + version: "1.2" + generatedBy: "1.3.1" +--- + +Continue working on a change: create the next artifact, **run baseline evals**, **refine the artifact** if needed, then **ask user approval**. + +**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. + +## Steps + +1. **If no change name provided, prompt for selection** + + Run `openspec list --json` to get available changes sorted by most recently modified. Then use the **AskUserQuestion tool** to let the user select which change to work on. + + Present the top 3-4 most recently modified changes as options, showing: + - Change name + - Schema (from `schema` field if present, otherwise "spec-driven") + - Status (e.g., "0/5 tasks", "complete", "no tasks") + - How recently it was modified (from `lastModified` field) + + Mark the most recently modified change as "(Recommended)" since it's likely what the user wants to continue. + + **IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose. + +2. **Check current status** + ```bash + openspec status --change "<name>" --json + ``` + Parse the JSON to understand current state. The response includes: + - `schemaName`: The workflow schema being used (e.g., "spec-driven") + - `artifacts`: Array of artifacts with their status ("done", "ready", "blocked") + - `isComplete`: Boolean indicating if all artifacts are complete + +3. **Act based on status**: + + --- + + **If all artifacts are complete (`isComplete: true`)**: + - Congratulate the user + - Show final status including the schema used + - Suggest: "All artifacts created! You can now implement this change or archive it." + - STOP + + --- + + **If artifacts are ready to create** (status shows artifacts with `status: "ready"`): + - Pick the FIRST artifact with `status: "ready"` from the status output + - Get its instructions: + ```bash + openspec instructions <artifact-id> --change "<name>" --json + ``` + - Parse the JSON. The key fields are: + - `context`: Project background (constraints for you - do NOT include in output) + - `rules`: Artifact-specific rules (constraints for you - do NOT include in output) + - `template`: The structure to use for your output file + - `instruction`: Schema-specific guidance + - `outputPath`: Where to write the artifact + - `dependencies`: Completed artifacts to read for context + - **Create the artifact file (v1)**: + - Read any completed dependency files for context + - **openspec-agile-workflow — repo target gate**: Before creating + `repo-assessment` or `constitution`, read `inputs/jira.yaml`. + - **Working-folder mode** (schema `working_folder_repo`): if the user directs + using the working folder as the repo, set `use_working_folder_as_repo: true`, + record `working_folder_path`, use cwd for assessment — no fork, no draft PR. + - **Default mode**: If `target_repo` is absent or empty, ask the user once for + the target GitHub repository URL, persist to `jira.yaml`, and verify access. + Do not proceed until recorded (see schema `target_repo`). + - Use `template` as the structure - fill in its sections + - Apply `context` and `rules` as constraints when writing - but do NOT copy them into the file + - Write to the output path specified in instructions + - Templates come from **`{schema_root}/templates/`** via openspec instructions (resolve `openspec/schemas/openspec-agile-workflow/` or `schemas/openspec-agile-workflow/`) + + - **Stage eval gate (openspec-agile-workflow only)**: + - Resolve schema root: `openspec/schemas/openspec-agile-workflow/` (installed) or `schemas/openspec-agile-workflow/` (distribution) + - Read **`{schema_root}/stage-gate/SYSTEM_PROMPT.md`** and **`{schema_root}/stage-gate/artifact-eval-map.yaml`** + - If artifact has `gate: stage_evals`: + 1. Score artifact at `outputPath` against all cases in `{schema_root}/evals/<stage>_eval.yaml` + 2. Write `openspec/changes/<name>/eval-results/<artifact-id>.yaml` + 3. If any case fails: refine **artifact only** (overwrite `outputPath`) using refinement context bundle: + - v1 artifact full text + - eval failures + failed case prompts/assertions + - openspec instructions (`instruction`, `template`, `rules`, `context`) + - all dependency artifacts + - `inputs/jira.yaml` and related change inputs + - user feedback if rejecting approval + 4. Re-score refined artifact + 5. **Never** edit `{schema_root}/templates/` or `evals/refined-templates/` + - If `gate: rubric_only` (validation): score against `validation.md` rubric + - If `gate: skip` (specs, reports): skip to approval + + - **User approval** — present eval scorecard (if run) + summary. Ask: + > Approve this artifact? **(Approve / Reject with feedback)** + - **Reject on `specs`**: **exit workflow** — do NOT regenerate specs.md (see schema `user_approval_feedback_gate.exit_on_reject.specs` and `USER_FEEDBACK_PROMPT.md`). STOP. + - **Reject on other artifacts**: run feedback gate per **`{schema_root}/stage-gate/USER_FEEDBACK_PROMPT.md`**: + 1. Capture user feedback verbatim + 2. Load prior approved artifacts (read-only), current artifact, current template, eval results, openspec instructions + 3. Update `{schema_root}/templates/<name>.md` if feedback requires structural changes + 4. Regenerate refined artifact at `outputPath` + 5. Write round summary → `openspec/changes/<name>/feedback_stage_artifacts/<artifact-id>/round-<N>.yaml` + 6. Re-run eval gate when applicable + 7. Re-present scorecard + feedback addressed → ask approval again (loop until Approve) + - **Do not** use `prompts/<artifact-id>.yaml` + - **Approve**: STOP (one artifact per invocation) + + - Show what was created and what's now unlocked + - STOP after ONE artifact (including eval gate + approval) + + --- + + **If no artifacts are ready (all blocked)**: + - This shouldn't happen with a valid schema + - Show status and suggest checking for issues + +4. **After completing the artifact + gate, show progress** + ```bash + openspec status --change "<name>" + ``` + +**Output** + +After each invocation, show: +- Which artifact was created/refined +- Eval score (if baseline evals ran): overall % and pass/fail per case +- Path to `eval-results/<artifact-id>.yaml` +- Schema workflow being used +- Current progress (N/M complete) +- What artifacts are now unlocked (after user approval) +- Prompt: "Approve to continue to the next artifact on the next `/opsx-continue`." + +**Artifact Creation Guidelines** + +The artifact types and their purpose depend on the schema. Use the `instruction` field from the instructions output to understand what to create. + +For **openspec-agile-workflow**, eval gate mapping: + +| Artifact | Eval file | +|----------|-----------| +| repo-assessment | `repo-assessment_eval.yaml` | +| constitution | `constitution_eval.yaml` | +| plan | `plan_eval.yaml` | +| tasks | `tasks_eval.yaml` | +| implementation | `implementation_eval.yaml` | + +**Guardrails** +- Create ONE artifact per invocation (eval + refine + approval included) +- Always read dependency artifacts before creating a new one +- Never skip artifacts or create out of order +- Never skip eval gate for gated artifacts +- Never refine templates during **eval gate** — refine change artifacts only +- User rejection feedback loop **may** patch `{schema_root}/templates/` when required; write summaries to `feedback_stage_artifacts/` +- If context is unclear, ask the user before creating +- Verify the artifact file exists after writing before marking progress +- Use the schema's artifact sequence, don't assume specific artifact names +- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file + - Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact + - These guide what you write, but should never appear in the output diff --git a/.cursor/skills/openspec-explore/SKILL.md b/.cursor/skills/openspec-explore/SKILL.md new file mode 100644 index 000000000..6858d3f69 --- /dev/null +++ b/.cursor/skills/openspec-explore/SKILL.md @@ -0,0 +1,288 @@ +--- +name: openspec-explore +description: Enter explore mode - a thinking partner for exploring ideas, investigating problems, and clarifying requirements. Use when the user wants to think through something before or during a change. +license: MIT +compatibility: Requires openspec CLI. +metadata: + author: openspec + version: "1.0" + generatedBy: "1.3.1" +--- + +Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes. + +**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) if the user asks—that's capturing thinking, not implementing. + +**This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore. + +--- + +## The Stance + +- **Curious, not prescriptive** - Ask questions that emerge naturally, don't follow a script +- **Open threads, not interrogations** - Surface multiple interesting directions and let the user follow what resonates. Don't funnel them through a single path of questions. +- **Visual** - Use ASCII diagrams liberally when they'd help clarify thinking +- **Adaptive** - Follow interesting threads, pivot when new information emerges +- **Patient** - Don't rush to conclusions, let the shape of the problem emerge +- **Grounded** - Explore the actual codebase when relevant, don't just theorize + +--- + +## What You Might Do + +Depending on what the user brings, you might: + +**Explore the problem space** +- Ask clarifying questions that emerge from what they said +- Challenge assumptions +- Reframe the problem +- Find analogies + +**Investigate the codebase** +- Map existing architecture relevant to the discussion +- Find integration points +- Identify patterns already in use +- Surface hidden complexity + +**Compare options** +- Brainstorm multiple approaches +- Build comparison tables +- Sketch tradeoffs +- Recommend a path (if asked) + +**Visualize** +``` +┌─────────────────────────────────────────┐ +│ Use ASCII diagrams liberally │ +├─────────────────────────────────────────┤ +│ │ +│ ┌────────┐ ┌────────┐ │ +│ │ State │────────▶│ State │ │ +│ │ A │ │ B │ │ +│ └────────┘ └────────┘ │ +│ │ +│ System diagrams, state machines, │ +│ data flows, architecture sketches, │ +│ dependency graphs, comparison tables │ +│ │ +└─────────────────────────────────────────┘ +``` + +**Surface risks and unknowns** +- Identify what could go wrong +- Find gaps in understanding +- Suggest spikes or investigations + +--- + +## OpenSpec Awareness + +You have full context of the OpenSpec system. Use it naturally, don't force it. + +### Check for context + +At the start, quickly check what exists: +```bash +openspec list --json +``` + +This tells you: +- If there are active changes +- Their names, schemas, and status +- What the user might be working on + +### When no change exists + +Think freely. When insights crystallize, you might offer: + +- "This feels solid enough to start a change. Want me to create a proposal?" +- Or keep exploring - no pressure to formalize + +### When a change exists + +If the user mentions a change or you detect one is relevant: + +1. **Read existing artifacts for context** + - `openspec/changes/<name>/proposal.md` + - `openspec/changes/<name>/design.md` + - `openspec/changes/<name>/tasks.md` + - etc. + +2. **Reference them naturally in conversation** + - "Your design mentions using Redis, but we just realized SQLite fits better..." + - "The proposal scopes this to premium users, but we're now thinking everyone..." + +3. **Offer to capture when decisions are made** + + | Insight Type | Where to Capture | + |----------------------------|--------------------------------| + | New requirement discovered | `specs/<capability>/spec.md` | + | Requirement changed | `specs/<capability>/spec.md` | + | Design decision made | `design.md` | + | Scope changed | `proposal.md` | + | New work identified | `tasks.md` | + | Assumption invalidated | Relevant artifact | + + Example offers: + - "That's a design decision. Capture it in design.md?" + - "This is a new requirement. Add it to specs?" + - "This changes scope. Update the proposal?" + +4. **The user decides** - Offer and move on. Don't pressure. Don't auto-capture. + +--- + +## What You Don't Have To Do + +- Follow a script +- Ask the same questions every time +- Produce a specific artifact +- Reach a conclusion +- Stay on topic if a tangent is valuable +- Be brief (this is thinking time) + +--- + +## Handling Different Entry Points + +**User brings a vague idea:** +``` +User: I'm thinking about adding real-time collaboration + +You: Real-time collab is a big space. Let me think about this... + + COLLABORATION SPECTRUM + ════════════════════════════════════════════ + + Awareness Coordination Sync + │ │ │ + ▼ ▼ ▼ + ┌────────┐ ┌────────┐ ┌────────┐ + │Presence│ │Cursors │ │ CRDT │ + │ "3 │ │ Multi │ │Conflict│ + │online" │ │ select │ │ free │ + └────────┘ └────────┘ └────────┘ + │ │ │ + trivial moderate complex + + Where's your head at? +``` + +**User brings a specific problem:** +``` +User: The auth system is a mess + +You: [reads codebase] + + ┌─────────────────────────────────────────────┐ + │ CURRENT AUTH FLOW │ + └─────────────────────────────────────────────┘ + │ + ┌─────────────┼─────────────┐ + ▼ ▼ ▼ + ┌─────────┐ ┌─────────┐ ┌─────────┐ + │ Google │ │ GitHub │ │ Email │ + │ OAuth │ │ OAuth │ │ Magic │ + └────┬────┘ └────┬────┘ └────┬────┘ + │ │ │ + └─────────────┼─────────────┘ + ▼ + ┌───────────┐ + │ Session │ + └─────┬─────┘ + │ + ▼ + ┌───────────┐ + │ Perms │ + └───────────┘ + + I see three tangles. Which one's burning? +``` + +**User is stuck mid-implementation:** +``` +User: /opsx:explore add-auth-system + The OAuth integration is more complex than expected + +You: [reads change artifacts] + + You're on task 4: "Implement OAuth flow" + + Let me trace what's involved... + + [draws diagram, explores options, suggests paths] + + Want to update the design to reflect this? + Or add a spike task to investigate? +``` + +**User wants to compare options:** +``` +User: Should we use Postgres or SQLite? + +You: Generic answer is boring. What's the context? + +User: A CLI tool that tracks local dev environments + +You: That changes everything. + + ┌─────────────────────────────────────────────────┐ + │ CLI TOOL DATA STORAGE │ + └─────────────────────────────────────────────────┘ + + Key constraints: + • No daemon running + • Must work offline + • Single user + + SQLite Postgres + Deployment embedded ✓ needs server ✗ + Offline yes ✓ no ✗ + Single file yes ✓ no ✗ + + SQLite. Not even close. + + Unless... is there a sync component? +``` + +--- + +## Ending Discovery + +There's no required ending. Discovery might: + +- **Flow into a proposal**: "Ready to start? I can create a change proposal." +- **Result in artifact updates**: "Updated design.md with these decisions" +- **Just provide clarity**: User has what they need, moves on +- **Continue later**: "We can pick this up anytime" + +When it feels like things are crystallizing, you might summarize: + +``` +## What We Figured Out + +**The problem**: [crystallized understanding] + +**The approach**: [if one emerged] + +**Open questions**: [if any remain] + +**Next steps** (if ready): +- Create a change proposal +- Keep exploring: just keep talking +``` + +But this summary is optional. Sometimes the thinking IS the value. + +--- + +## Guardrails + +- **Don't implement** - Never write code or implement features. Creating OpenSpec artifacts is fine, writing application code is not. +- **Don't fake understanding** - If something is unclear, dig deeper +- **Don't rush** - Discovery is thinking time, not task time +- **Don't force structure** - Let patterns emerge naturally +- **Don't auto-capture** - Offer to save insights, don't just do it +- **Do visualize** - A good diagram is worth many paragraphs +- **Do explore the codebase** - Ground discussions in reality +- **Do question assumptions** - Including the user's and your own diff --git a/.cursor/skills/openspec-new-change/SKILL.md b/.cursor/skills/openspec-new-change/SKILL.md new file mode 100644 index 000000000..2e5423207 --- /dev/null +++ b/.cursor/skills/openspec-new-change/SKILL.md @@ -0,0 +1,15 @@ +--- +name: openspec-new-change +description: Start openspec-agile-workflow change from Jira ticket. Use for /opsx-new. +license: MIT +compatibility: Requires openspec CLI. +metadata: + author: openspec + version: "1.1" +--- + +Jira key required at `/opsx-new`. Write `inputs/jira.yaml`. Obtain spec via Jira MCP or user paste into `inputs/jira-spec.md`. Do not create artifacts. Next: `/opsx-continue`. + +Syntax: `/opsx-new PROJ-123` or `/opsx-new PROJ-123 change-name` + +Repo URL optional now; required before repo-assessment stage. diff --git a/.cursor/skills/openspec-propose/SKILL.md b/.cursor/skills/openspec-propose/SKILL.md new file mode 100644 index 000000000..0208e030e --- /dev/null +++ b/.cursor/skills/openspec-propose/SKILL.md @@ -0,0 +1,113 @@ +--- +name: openspec-propose +description: Propose a new change with all artifacts generated in one step. Use when the user wants to quickly describe what they want to build and get a complete proposal with design, specs, and tasks ready for implementation. +license: MIT +compatibility: Requires openspec CLI. +metadata: + author: openspec + version: "1.0" + generatedBy: "1.5.0" +--- + +Propose a new change - create the change and generate all artifacts in one step. + +I'll create a change with artifacts: +- proposal.md (what & why) +- design.md (how) +- tasks.md (implementation steps) + +When ready to implement, run /opsx:apply + +--- + +**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root. + +**Input**: The user's request should include a change name (kebab-case) OR a description of what they want to build. + +**Steps** + +1. **If no clear input provided, ask what they want to build** + + Use the **AskUserQuestion tool** (open-ended, no preset options) to ask: + > "What change do you want to work on? Describe what you want to build or fix." + + From their description, derive a kebab-case name (e.g., "add user authentication" → `add-user-auth`). + + **IMPORTANT**: Do NOT proceed without understanding what the user wants to build. + +2. **Create the change directory** + ```bash + openspec new change "<name>" + ``` + This creates a scaffolded change in the planning home resolved by the CLI with `.openspec.yaml`. + +3. **Get the artifact build order** + ```bash + openspec status --change "<name>" --json + ``` + Parse the JSON to get: + - `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`) + - `artifacts`: list of all artifacts with their status and dependencies + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths. + +4. **Create artifacts in sequence until apply-ready** + + Use the **TodoWrite tool** to track progress through the artifacts. + + Loop through artifacts in dependency order (artifacts with no pending dependencies first): + + a. **For each artifact that is `ready` (dependencies satisfied)**: + - Get instructions: + ```bash + openspec instructions <artifact-id> --change "<name>" --json + ``` + - The instructions JSON includes: + - `context`: Project background (constraints for you - do NOT include in output) + - `rules`: Artifact-specific rules (constraints for you - do NOT include in output) + - `template`: The structure to use for your output file + - `instruction`: Schema-specific guidance for this artifact type + - `resolvedOutputPath`: Resolved path or pattern to write the artifact + - `dependencies`: Completed artifacts to read for context + - Read any completed dependency files for context + - Create the artifact file using `template` as the structure and write it to `resolvedOutputPath` + - Apply `context` and `rules` as constraints - but do NOT copy them into the file + - Show brief progress: "Created <artifact-id>" + + b. **Continue until all `applyRequires` artifacts are complete** + - After creating each artifact, re-run `openspec status --change "<name>" --json` + - Check if every artifact ID in `applyRequires` has `status: "done"` in the artifacts array + - Stop when all `applyRequires` artifacts are done + + c. **If an artifact requires user input** (unclear context): + - Use **AskUserQuestion tool** to clarify + - Then continue with creation + +5. **Show final status** + ```bash + openspec status --change "<name>" + ``` + +**Output** + +After completing all artifacts, summarize: +- Change name and location +- List of artifacts created with brief descriptions +- What's ready: "All artifacts created! Ready for implementation." +- Prompt: "Run `/opsx:apply` or ask me to implement to start working on the tasks." + +**Artifact Creation Guidelines** + +- Follow the `instruction` field from `openspec instructions` for each artifact type +- The schema defines what each artifact should contain - follow it +- Read dependency artifacts for context before creating new ones +- Use `template` as the structure for your output file - fill in its sections +- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file + - Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact + - These guide what you write, but should never appear in the output + +**Guardrails** +- Create ALL artifacts needed for implementation (as defined by schema's `apply.requires`) +- Always read dependency artifacts before creating a new one +- If context is critically unclear, ask the user - but prefer making reasonable decisions to keep momentum +- If a change with that name already exists, ask if user wants to continue it or create a new one +- Verify each artifact file exists after writing before proceeding to next diff --git a/.cursor/skills/openspec-sync-specs/SKILL.md b/.cursor/skills/openspec-sync-specs/SKILL.md new file mode 100644 index 000000000..f4ec05469 --- /dev/null +++ b/.cursor/skills/openspec-sync-specs/SKILL.md @@ -0,0 +1,147 @@ +--- +name: openspec-sync-specs +description: Sync delta specs from a change to main specs. Use when the user wants to update main specs with changes from a delta spec, without archiving the change. +license: MIT +compatibility: Requires openspec CLI. +metadata: + author: openspec + version: "1.0" + generatedBy: "1.5.0" +--- + +Sync delta specs from a change to main specs. + +This is an **agent-driven** operation - you will read delta specs and directly edit main specs to apply the changes. This allows intelligent merging (e.g., adding a scenario without copying the entire requirement). + +**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root. + +**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. + +**Steps** + +1. **If no change name provided, prompt for selection** + + Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select. + + Show changes that have delta specs (under `specs/` directory). + + **IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose. + +2. **Resolve change context** + + Run: + ```bash + openspec status --change "<name>" --json + ``` + +3. **Find delta specs** + + Use `artifactPaths.specs.existingOutputPaths` from the status JSON as the list of delta spec files. + + Each delta spec file contains sections like: + - `## ADDED Requirements` - New requirements to add + - `## MODIFIED Requirements` - Changes to existing requirements + - `## REMOVED Requirements` - Requirements to remove + - `## RENAMED Requirements` - Requirements to rename (FROM:/TO: format) + + If no delta specs found, inform user and stop. + +4. **For each delta spec, apply changes to main specs** + + For each repo-local capability delta spec path returned by the CLI: + + a. **Read the delta spec** to understand the intended changes + + b. **Read the main spec** at `openspec/specs/<capability>/spec.md` (may not exist yet) + + c. **Apply changes intelligently**: + + **ADDED Requirements:** + - If requirement doesn't exist in main spec → add it + - If requirement already exists → update it to match (treat as implicit MODIFIED) + + **MODIFIED Requirements:** + - Find the requirement in main spec + - Apply the changes - this can be: + - Adding new scenarios (don't need to copy existing ones) + - Modifying existing scenarios + - Changing the requirement description + - Preserve scenarios/content not mentioned in the delta + + **REMOVED Requirements:** + - Remove the entire requirement block from main spec + + **RENAMED Requirements:** + - Find the FROM requirement, rename to TO + + d. **Create new main spec** if capability doesn't exist yet: + - Create `openspec/specs/<capability>/spec.md` + - Add Purpose section (can be brief, mark as TBD) + - Add Requirements section with the ADDED requirements + +5. **Show summary** + + After applying all changes, summarize: + - Which capabilities were updated + - What changes were made (requirements added/modified/removed/renamed) + +**Delta Spec Format Reference** + +```markdown +## ADDED Requirements + +### Requirement: New Feature +The system SHALL do something new. + +#### Scenario: Basic case +- **WHEN** user does X +- **THEN** system does Y + +## MODIFIED Requirements + +### Requirement: Existing Feature +#### Scenario: New scenario to add +- **WHEN** user does A +- **THEN** system does B + +## REMOVED Requirements + +### Requirement: Deprecated Feature + +## RENAMED Requirements + +- FROM: `### Requirement: Old Name` +- TO: `### Requirement: New Name` +``` + +**Key Principle: Intelligent Merging** + +Unlike programmatic merging, you can apply **partial updates**: +- To add a scenario, just include that scenario under MODIFIED - don't copy existing scenarios +- The delta represents *intent*, not a wholesale replacement +- Use your judgment to merge changes sensibly + +**Output On Success** + +``` +## Specs Synced: <change-name> + +Updated main specs: + +**<capability-1>**: +- Added requirement: "New Feature" +- Modified requirement: "Existing Feature" (added 1 scenario) + +**<capability-2>**: +- Created new spec file +- Added requirement: "Another Feature" + +Main specs are now updated. The change remains active - archive when implementation is complete. +``` + +**Guardrails** +- Read both delta and main specs before making changes +- Preserve existing content not mentioned in delta +- If something is unclear, ask for clarification +- Show what you're changing as you go +- The operation should be idempotent - running twice should give same result diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..083da12e6 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,354 @@ +# AGENTS.md + +This file provides guidance to AI agents working with **external-secrets-operator** for OpenShift. It uses **controller-runtime only** (no library-go). For architecture depth, see [ARCHITECTURE.md](ARCHITECTURE.md). For contribution process, see [CONTRIBUTING.md](CONTRIBUTING.md). + +## Docs Index + +Detailed domain-specific guidelines are in these files — read them before working in the corresponding area: + +- [ARCHITECTURE.md](ARCHITECTURE.md) — Bird's-eye view, code map, reconciliation flow +- [docs/security-guidelines.md](docs/security-guidelines.md) — Container security, RBAC, TLS, network policies, webhook security +- [docs/performance-guidelines.md](docs/performance-guidelines.md) — Cache architecture, watch predicates, reconciliation patterns, drift detection +- [docs/error-handling-guidelines.md](docs/error-handling-guidelines.md) — ReconcileError types, retry logic, status conditions, logging +- [docs/api-contracts-guidelines.md](docs/api-contracts-guidelines.md) — CRD types, kubebuilder markers, CEL validation, code generation +- [docs/testing-guidelines.md](docs/testing-guidelines.md) — Unit/API/E2E test tiers, frameworks, test helpers, CI integration +- [docs/integration-guidelines.md](docs/integration-guidelines.md) — Bindata pattern, cert-manager, OpenShift platform, Bitwarden plugin +- [docs/openspec-agile-workflow.md](docs/openspec-agile-workflow.md) — OpenSpec agile workflow (`/opsx-new`, `/opsx-continue`, `/opsx-apply`) +- [constitution.md](constitution.md) — Non-negotiable architectural guardrails (OpenSpec workflow input) + +## Project Overview + +This is a Kubernetes operator (built with kubebuilder/controller-runtime) that deploys and manages the upstream [external-secrets](https://github.com/openshift/external-secrets) application on OpenShift clusters. The operator does not embed upstream code — it manages upstream resources as static YAML manifests (bindata) applied imperatively. + +Three controllers run in a single binary: + +| Controller | Package | Watches | Purpose | +|---|---|---|---| +| `external-secrets-controller` | `pkg/controller/external_secrets/` | `ExternalSecretsConfig` CR + `ExternalSecretsManager` (spec) + managed resources (Deployments, RBAC, Services, Secrets metadata, ConfigMaps, NetworkPolicies, Webhooks) + conditionally `cert-manager.io/Certificate` | Installs/reconciles operand deployments, RBAC, services, webhooks, network policies | +| `external-secrets-manager` | `pkg/controller/external_secrets_manager/` | `ExternalSecretsManager` CR + `ExternalSecretsConfig` status | Aggregates controller statuses into a global status CR | +| `crd-annotator` | `pkg/controller/crd_annotator/` | ESO CRDs (metadata, label-filtered) + `ExternalSecretsConfig` | Adds cert-manager CA injection annotations to operand CRDs (conditional; only registered when cert-manager is installed) | + +**Startup order** (`pkg/operator/setup_manager.go`): ESM controller → operand controller → optional CRD annotator → `CreateDefaultESMResource()`. + +**Operand namespace:** `external-secrets` (`OperandDefaultNamespace`). + +## Project Structure + +```text +api/v1alpha1/ CRD type definitions, conditions, shared types, deepcopy +api/v1alpha1/tests/ Declarative YAML test suites for CRD validation +bindata/ Static operand YAML manifests (compiled into Go via go-bindata) +bundle/ OLM bundle manifests +cmd/external-secrets-operator/ Operator entrypoint (main.go, separate Go module) +docs/ Guideline docs (security, performance, error handling, etc.) +config/ Kustomize manifests (CRDs, RBAC, manager, samples, bundle) +hack/ Shell scripts (codegen, verification, CI helpers) +images/ci/ CI Dockerfiles (coverage-instrumented builds) +pkg/controller/ Controller implementations + client/ CtrlClient interface + counterfeiter fakes + common/ Shared utilities (errors, constants, decode helpers, drift detection) + commontest/ Shared test fixtures (TestExternalSecretsConfig, TestExternalSecretsManager) + crd_annotator/ CRD annotation controller + external_secrets/ Main operand reconciliation controller + external_secrets_manager/ Status aggregation controller +pkg/operator/ Manager setup, controller registration + assets/ Generated bindata (bindata.go) +pkg/version/ Build-time version info (ldflags) +test/apis/ API integration tests (Ginkgo + envtest) +test/e2e/ End-to-end tests (Ginkgo + live cluster) +test/utils/ E2E test helpers +tools/ Go module for build-time tool dependencies +vendor/ Workspace-level vendoring (go work vendor) +``` + +## Go Workspace and Module Layout + +The repo uses `go.work` with four modules: `.`, `./cmd/external-secrets-operator`, `./test`, `./tools`. This means: + +- `GOFLAGS` is cleared for test and fmt targets to avoid `-mod=vendor` conflicting with `go.work`. +- When adding a dependency, use `make update-dep PKG=pkg@version` to update across all modules, then `make update-vendor`. +- Vendoring is workspace-level (`go work vendor`), not per-module. +- All build-time tools (controller-gen, golangci-lint, ginkgo, etc.) are vendored and built from source via `go build -mod=vendor`. + +## Build System (Key Makefile Targets) + +| Target | What it does | +|---|---| +| `make build` | Full build: generate + manifests + fmt + vet + compile binary | +| `make build-operator` | Compile binary only (no codegen, fastest iteration) | +| `make test` | Run unit + API integration tests (no cluster needed) | +| `make test-unit` | Unit tests only | +| `make test-apis` | API validation tests via envtest | +| `make test-e2e` | E2E tests against a live cluster | +| `make lint` | Run golangci-lint with all configured linters | +| `make lint-fix` | Run golangci-lint with auto-fix | +| `make update` | Full regeneration: codegen + manifests + operand manifests + bindata + bundle + docs | +| `make verify` | Run vet + fmt + verify-deps + verify-bindata + verify-bindata-assets + verify-generated + govulncheck + check-git-diff | +| `make update-vendor` | Update vendor directory across all workspace modules | +| `make update-dep PKG=x@v` | Update a single dependency across all modules | +| `make manifests` | Regenerate CRD YAML, RBAC, webhook configs from kubebuilder markers | +| `make generate` | Regenerate DeepCopy methods | +| `make docs` | Regenerate API reference docs | +| `make clean` | Remove bin/, _output/, cover.out, dist/ | + +After any code change, run `make update && make verify` to ensure all generated files are consistent. CI runs `check-git-diff` which fails if generated files are stale. + +## Code Style and Formatting + +### Import Order + +Imports must follow the order enforced by `gci` in `.golangci.yml`: + +1. Standard library +2. Third-party packages +3. `github.com/openshift/external-secrets-operator` (project-local) +4. Blank imports, dot imports, aliases, local module + +### Linting + +The repo uses golangci-lint v2 with a comprehensive set of linters (see `.golangci.yml`). Key rules: + +- **depguard** blocks `math/rand` (use `math/rand/v2`), `github.com/sirupsen/logrus`, and `github.com/pkg/errors` (use `errors`/`fmt`). +- **kubeapilinter** runs only on `api/v1alpha1/*` files. Use `//nolint:kubeapilinter` with a comment for intentional deviations. +- **golines** max line length is 200 characters. +- **gofmt** rewrites `interface{}` to `any` and `a[b:len(a)]` to `a[b:]`. +- Generated files are excluded in `lax` mode. +- Test files have relaxed rules for `gocyclo`, `errcheck`, `gosec`, `forcetypeassert`, and others. + +### File Headers + +All `.go` files must include the Apache 2.0 license header from `hack/boilerplate.go.txt` (year 2025). + +### FIPS Build + +Production builds use `hack/go-fips.sh` which enables `GOEXPERIMENT=strictfipsruntime` and build tags `strictfipsruntime,openssl` when the Go compiler supports it. Local dev builds without FIPS work but cannot be used in CI/production. + +## Naming Conventions + +### Go Packages + +Controller packages use `snake_case`: `external_secrets`, `external_secrets_manager`, `crd_annotator`. This matches kubebuilder conventions. + +### Constants + +- Controller names: `"external-secrets-controller"`, `"external-secrets-manager"`, `"crd-annotator"` (kebab-case in strings). +- Finalizer format: `<crd-plural>.<api-group>/<controller-name>` (e.g., `externalsecretsconfigs.operator.openshift.io/external-secrets-controller`). +- Asset name constants: `<resourceKind>_<resourceName>AssetName` in camelCase (e.g., `controllerDeploymentAssetName`). +- Environment variable constants: all-caps with suffix `EnvVarName` (e.g., `externalSecretsImageEnvVarName`). + +### Bindata YAML Files + +Files in `bindata/external-secrets/resources/` follow the pattern: `<kind-lowercase>_<resource-name>.yml` (e.g., `deployment_external-secrets.yml`, `clusterrole_external-secrets-controller.yml`). Network policies in `bindata/external-secrets/` use `.yaml` extension. + +### CRD Object Names + +Both `ExternalSecretsConfig` and `ExternalSecretsManager` are singletons named `"cluster"` (enforced by CEL). Constants `ExternalSecretsConfigObjectName` and `ExternalSecretsManagerObjectName` in `pkg/controller/common/constants.go` hold this value. + +## Architectural Patterns + +### Reconciler Structure + +Every controller follows the same pattern: + +1. A `Reconciler` struct embedding `operatorclient.CtrlClient`. +2. A `New(ctx, mgr)` constructor that builds the reconciler and client(s). +3. A `SetupWithManager(mgr)` method that wires watches, predicates, and map functions. +4. A `Reconcile(ctx, req)` method that fetches the primary CR and delegates to `processReconcileRequest`. +5. An `updateStatus(ctx, obj)` method using `retry.RetryOnConflict`. + +Reconciliation uses **create-or-update with deep equality** (not SSA-first). Limited SSA (`client.Apply` with field owner) is allowed only for CR annotation patches. See [ARCHITECTURE.md](ARCHITECTURE.md) and [docs/performance-guidelines.md](docs/performance-guidelines.md). + +### CtrlClient Interface + +All controllers interact with Kubernetes through `pkg/controller/client.CtrlClient`, not the raw `controller-runtime client.Client`. This interface adds `UpdateWithRetry`, `StatusUpdate`, and `Exists` methods. Unit tests use counterfeiter-generated fakes (`pkg/controller/client/fakes/`). + +To regenerate fakes after changing the interface: `go generate ./pkg/controller/client/...` + +### Resource Reconciliation Pattern + +For each resource type (deployments, services, RBAC, etc.), the same flow is used: + +1. Decode static YAML from bindata (`common.Decode*ObjBytes(assets.MustAsset(...))`). +2. Mutate the decoded object (set namespace, labels, annotations, images, env vars, security context). +3. Check existence with `r.Exists()`. +4. If new: `r.Create()`. If existing: compare with `common.HasObjectChanged()`, then `r.UpdateWithRetry()` if drifted. +5. Record Kubernetes events for create/update operations. +6. Wrap errors with `common.FromClientError()`. + +### Conditional Resources + +Resources that depend on CR configuration use a slice of `{assetName string, condition bool}` structs. Only assets with `condition: true` are applied. Follow this pattern when adding new conditionally-created resources. + +## Operands and CRDs (quick reference) + +Full detail: [ARCHITECTURE.md](ARCHITECTURE.md), [docs/integration-guidelines.md](docs/integration-guidelines.md). + +| CRD | Scope | Singleton | Purpose | +|-----|-------|-----------|---------| +| `ExternalSecretsConfig` | Cluster | `cluster` | Operand configuration (webhooks, providers, deployment) | +| `ExternalSecretsManager` | Cluster | `cluster` | Global config, feature flags, status aggregation | + +**Operator-owned API:** `api/v1alpha1/`, group `operator.openshift.io`. **Upstream operand CRDs** (`external-secrets.io`, `generators.external-secrets.io`) ship via bindata/OLM — the operator deploys but does not own their API evolution. + +| Operand workload | Always? | Enable gate | Image env | +|------------------|---------|-------------|-----------| +| **external-secrets** (controller + webhook + cert-controller) | Yes | `ExternalSecretsConfig` managed | `RELATED_IMAGE_EXTERNAL_SECRETS` | +| **bitwarden-sdk-server** (plugin) | No | `plugins.bitwardenSecretManagerProvider.mode == Enabled` | `RELATED_IMAGE_BITWARDEN_SDK_SERVER` | + +**Webhook TLS (mutually exclusive):** cert-manager `Certificate` CRs when `certProvider.certManager.mode == Enabled`; otherwise in-tree `external-secrets-cert-controller` deployment. Never assume cert-manager is present. + +**Feature flags:** on `ExternalSecretsManager.Spec.Features[]` via `common.IsFeatureEnabled()` — not OpenShift cluster FeatureGate API. + +**Operand change tiers (for planning/tasks):** + +| Tier | Change | Key commands | +|------|--------|--------------| +| 1 | Deployment arg/env change | `make test-unit` | +| 2 | New optional plugin workload | API + bindata + conditional `createOrApplyDeployments` + e2e | +| 3 | Operand version bump | `make update-operand-manifests && make update-bindata && make verify` | +| 4 | Upstream CRD schema change | Comes from helm refresh (tier 3) | + +## Common Pitfalls + +1. **Never return both `RequeueAfter` and a non-nil error** from `Reconcile`. Return one or the other. +2. **Never edit generated files by hand**: `zz_generated.deepcopy.go`, `config/crd/bases/*.yaml`, `pkg/operator/assets/bindata.go`, `config/rbac/role.yaml`, `docs/api_reference.md`. Always use `make update`. +3. **Always run `make update && make verify`** after any code change. CI will reject PRs with stale generated files. +4. **Use the cached client for managed resources** (those with `app: external-secrets` label). Use the uncached client only for external resources like cert-manager Issuers or user-provided Secrets. +5. **Add new watched resources to both `controllerManagedResources` and `buildCacheObjectList()`** in `pkg/controller/external_secrets/controller.go`. +6. **Add new resource types to `HasObjectChanged`** in `pkg/controller/common/utils.go` with field-level comparison, not full `DeepEqual`. +7. **Decode helpers panic on failure** — this is intentional for build-time-constant assets. Do not add error handling around `Decode*ObjBytes` calls. +8. **RBAC markers** (`+kubebuilder:rbac`) go in `pkg/controller/external_secrets/controller.go` for the operator's own permissions. Operand RBAC is in static YAML under `bindata/`. +9. **The `go.work` workspace** means you cannot use `-mod=vendor` for `go test` or `go fmt`. The Makefile already clears `GOFLAGS` for affected targets. +10. **Container tool defaults to `podman`** (`CONTAINER_TOOL ?= podman`), not Docker. Override with `CONTAINER_TOOL=docker` if needed. +11. **Do NOT use SSA-first operand patterns** from other operators — this repo uses create-or-update. +12. **Do NOT create separate `ctrl.Manager` instances** — register in `setup_manager.go`. + +## PR and Contribution Expectations + +- Run `make lint` and `make test` locally before submitting. +- Run `make verify` to ensure generated files are in sync. +- Add unit tests for new reconciliation logic using table-driven tests and `FakeCtrlClient`. +- Add API test cases in `api/v1alpha1/tests/<crd-api-group-domain>/` (e.g., `externalsecretsconfig.operator.openshift.io/`) for any new CRD field or validation rule. +- Add E2E test cases with appropriate Ginkgo labels for platform-specific tests. +- Follow existing error wrapping patterns: `common.FromClientError` for API calls, `common.NewIrrecoverableError` for config validation failures, `common.NewUserConfigurationError` for invalid user-provided configuration. +- Commit messages should reference the relevant Jira ticket (e.g., `OCPBUGS-12345: description`). +- PR reviewers/approvers are listed in `OWNERS`. + +## Environment Variables + +The operator reads these at runtime (typically set by OLM/CSV): + +| Variable | Purpose | +|---|---| +| `RELATED_IMAGE_EXTERNAL_SECRETS` | Container image for the external-secrets operand | +| `RELATED_IMAGE_BITWARDEN_SDK_SERVER` | Container image for the Bitwarden SDK server | +| `OPERAND_EXTERNAL_SECRETS_IMAGE_VERSION` | Version label for operand resources | +| `BITWARDEN_SDK_SERVER_IMAGE_VERSION` | Version label for Bitwarden resources | +| `OPERATOR_IMAGE_VERSION` | Operator version string | +| `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` | Proxy fallback from OLM environment | + +For local development, set at minimum `RELATED_IMAGE_EXTERNAL_SECRETS` via `t.Setenv()` in tests or shell export for `make run`. + +--- + +## Per-task testing during `/opsx-apply` (code generation eval gate) + +During implementation, each code generation task is verified with **real command execution**. See `openspec/schemas/openspec-agile-workflow/stage-gate/CODE_GENERATION_EVAL_PROMPT.md`. + +| Task type | Verification | Test strategy | +|-----------|-------------|---------------| +| API types | `go build`, `go vet` | `make test-apis` after testsuite YAML | +| Codegen | `make generate && make manifests && make verify` | Consistency check | +| Controller logic | `go test ./pkg/controller/...` | Co-generated `_test.go` with `FakeCtrlClient` | +| Operand manifests | `make update-operand-manifests && make update-bindata && make verify` | `make verify-bindata` | +| OLM bundle | `make bundle` | Bundle validation | +| ESM / feature flags | `go test ./pkg/controller/external_secrets_manager/...` | Unit tests for feature wiring | + +--- + +## Execution agent routing + +Use these **Assigned Agent** IDs in `tasks.md` §3 when **`AgentRoutingMode: PROVIDED`**. Each task gets exactly one primary agent. + +| Agent ID | Scope | Route when task touches | OAPE / execution | +|----------|-------|-------------------------|------------------| +| **API_Agent** | CRD/API types, markers, `.testsuite.yaml` | `api/v1alpha1/`, `api/v1alpha1/tests/` | `api-generate` or `api-generate-tests` (verification-only) | +| **OperatorController_Agent** | Reconciliation, operand install, wiring | `pkg/controller/external_secrets/`, `pkg/controller/external_secrets_manager/`, `pkg/controller/crd_annotator/`, `pkg/operator/setup_manager.go`, `cmd/external-secrets-operator/` | `api-implement` | +| **ManifestsBindata_Agent** | Operand YAML, version pins, upstream CRDs | `bindata/`, `hack/update-external-secrets-manifests.sh`, `Makefile` `EXTERNAL_SECRETS_VERSION`, `config/crd/bases/customresourcedefinition_*` | Manual — `make update-operand-manifests`, `make update-bindata` | +| **BitwardenPlugin_Agent** | Bitwarden SDK server plugin workload | `deployment_bitwarden-sdk-server.yml` reconcilers in `deployments.go`, `Plugins.BitwardenSecretManagerProvider` API, bitwarden network policy/certificate assets | Manual — follow bitwarden patterns in `deployments.go` | +| **WebhookTLS_Agent** | Webhook TLS, cert-manager certificates | Webhook deployments, `certificates.go`, trusted CA ConfigMap | Manual | +| **RBACSecurity_Agent** | RBAC, network policies | `config/rbac/`, `rbacs.go`, `networkpolicy.go` | Manual | +| **OLMRelease_Agent** | OLM bundle, CSV, relatedImages | `config/manifests/`, `bundle/`, Makefile bundle targets | Manual — `make bundle` | +| **Testing_Agent** | E2E and API test authoring | `test/e2e/`, `test/apis/`, `test/utils/` | `e2e-generate` when task is e2e | +| **Docs_Agent** | User-facing docs | `README.md`, `docs/` | Manual | + +### Controller routing rules + +- **Operand controller** (`pkg/controller/external_secrets/`): create-or-update reconcilers; follow `install_external_secrets.go` ordering +- **ESM controller** (`pkg/controller/external_secrets_manager/`): status aggregation, default ESM creation, feature flags +- **CRD annotator** (`pkg/controller/crd_annotator/`): only when cert-manager is installed +- **API before controller**: CRD field tasks must complete (and pass `make test-apis`) before controller tasks that reconcile those fields + +### Verification pairing + +- API changes → pair with `api/v1alpha1/tests/*.testsuite.yaml` tasks (`API_Agent`, verification-only) +- Controller changes → pair with unit tests (`make test-unit`) and e2e when user-visible (`Testing_Agent`) +- Operand version bumps → pair with `make verify` and relevant e2e smoke paths + +--- + +## Stage-Specific Agent Guidance + +### Repo-Assessment Stage Hints + +When assessing `external-secrets-operator`, document: + +- **Single manager, three reconcilers** — `external_secrets_manager`, `external_secrets`, optional `crd_annotator` +- **Create-or-update** operand pattern — not SSA-first (limited SSA for annotations only) +- **Two operator CRDs** — `ExternalSecretsConfig` + `ExternalSecretsManager` (both singleton `cluster`) +- **Two operand workloads:** external-secrets core (always) + bitwarden-sdk-server plugin (conditional on CR spec) +- **Three core deployments:** `external-secrets`, `external-secrets-webhook`, `external-secrets-cert-controller` (cert-controller skipped when cert-manager TLS enabled) +- **Upstream operand CRDs** (`external-secrets.io`, `generators.external-secrets.io`) vs operator CRDs (`operator.openshift.io`) +- **Image env vars:** `RELATED_IMAGE_EXTERNAL_SECRETS`, `RELATED_IMAGE_BITWARDEN_SDK_SERVER` (+ version env vars) +- **Feature flags on ESM CR** — not OpenShift FeatureSet gates +- **cert-manager optional** — webhook TLS path + CRD annotator when cert-manager CRD exists +- **Test commands:** `make test-unit`, `make test-apis`, `make test-e2e` +- **FIPS build** via `hack/go-fips.sh` + +Anti-patterns (forbidden without branch evidence): + +- Claiming library-go or SSA-first addon patterns apply to this repo +- Using OpenShift FeatureGate / TechPreview cluster discovery patterns from other operators +- Documenting operand controllers or CRDs that do not exist on the target branch + +### Planning Stage Hints + +Prefer operator-native thinking for external-secrets: + +- `ExternalSecretsConfig` / `ExternalSecretsManager` API evolution +- **Core operand** deployment overrides (`componentConfigs`, log level, operating namespace, trusted CA bundle) +- **Bitwarden plugin** enablement (`plugins.bitwardenSecretManagerProvider`) and TLS prerequisites +- Webhook TLS path selection (cert-manager vs in-tree cert-controller) +- Bindata/helm manifest pipeline (`EXTERNAL_SECRETS_VERSION`) +- Provider integrations (Bitwarden SDK sidecar; AWS/GCP paths in e2e) +- RBAC blast radius (secrets, cluster-scoped operand CRDs) +- OLM bundle and `RELATED_IMAGE_*` env vars + +Default repo pin when none provided: + +```text +primary_repo: "https://github.com/openshift/external-secrets-operator" +branch: "main" +``` + +### Validation Stage Hints + +When the spec touches operators, secrets, webhooks, providers, or OpenShift platform integration, assess: + +- API & CRD lifecycle (singleton rules, immutability, CEL validation) +- Install / uninstall / reconcile semantics (finalizers, operand namespace) +- RBAC & blast radius (secret access, cluster-scoped writes) +- Webhook TLS (cert-manager vs in-tree cert-controller) +- Provider matrix (Bitwarden, AWS, GCP — see e2e labels) +- Observability (Ready/Degraded conditions on ESC) +- Upgrade / operand version skew (`EXTERNAL_SECRETS_VERSION`) diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..31b7d021e --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,51 @@ +# CLAUDE.md + +See [AGENTS.md](AGENTS.md) for comprehensive architectural details. + +## Build and Test Commands + +When working in this repository, use these commands: + +### Before Committing +```bash +make update && make verify +``` +This regenerates all code, manifests, and bindata, then runs verification checks. CI will reject PRs with stale generated files. + +### Development Workflow +```bash +make build # Full build with codegen +make build-operator # Fast rebuild (binary only, no codegen) +make test # Run unit + API tests (no cluster needed) +make lint # Run golangci-lint +make lint-fix # Auto-fix linting issues +``` + +### Testing +```bash +make test-unit # Unit tests only +make test-apis # API validation tests (envtest) +make test-e2e # E2E tests (requires cluster) +``` + +### Dependency Management +```bash +make update-vendor # Sync vendor across all workspace modules +make update-dep PKG=package@version # Update a dependency in all modules +``` + +## Claude Code Preferences + +- Always run `make update && make verify` after code changes that affect: + - CRD definitions (`api/v1alpha1/`) + - Kubebuilder markers (`+kubebuilder:*`) + - Bindata YAML (`bindata/`) + - Generated code triggers + +- Use `make lint-fix` to automatically fix formatting and linting issues before suggesting manual fixes + +- The repository uses a Go workspace (`go.work`) with 4 modules. Never manually edit `GOFLAGS` or use `-mod=vendor` in test/fmt commands — the Makefile handles this + +- Container builds default to `podman`. Override with `CONTAINER_TOOL=docker` if needed + +- All build-time tools are vendored. Do not suggest installing tools globally diff --git a/README.md b/README.md index c5d9c828f..93e4d3c51 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,25 @@ For e2e tests, including prerequisites and suite-specific commands (e.g. label f make test-e2e E2E_GINKGO_LABEL_FILTER="<label-filter>" ``` +## AI-assisted development (OpenSpec agile workflow) + +This repository includes the **openspec-agile-workflow** distribution for gated, Jira-driven, spec-first development with Cursor slash commands (`/opsx-new`, `/opsx-continue`, `/opsx-apply`, and related OAPE commands). + +| Resource | Purpose | +|----------|---------| +| [docs/openspec-agile-workflow.md](docs/openspec-agile-workflow.md) | Full guide: devcontainer, commands, pipeline, eval loop, re-install | +| `AGENTS.md` | Operator-specific agent routing and verification matrix | +| `constitution.md` | Non-negotiable architectural guardrails | +| `.devcontainer/` | Go + Node + OpenSpec CLI (reopen in container) | + +**Quick start:** open in the devcontainer, then in Cursor chat run `/opsx-new PROJ-123` and tell the agent to **use this as the working directory** (working-folder mode). + +Validate the installed schema: + +```sh +openspec schema validate openspec-agile-workflow +``` + ## Contributing We welcome contributions from the community! To contribute: diff --git a/constitution.md b/constitution.md new file mode 100644 index 000000000..b948b2eb5 --- /dev/null +++ b/constitution.md @@ -0,0 +1,142 @@ +<!-- Companion artifact: repo-assessment.md (target files, reusable assets, risks) --> +# External Secrets Operator Constitution + +**AgentRoutingMode:** PROVIDED +<!-- PROVIDED — AGENTS.md exists at repo root --> + +**Version**: 1.0.0 | **Ratified**: 2026-07-01 | **Last Amended**: 2026-07-01 + +## Core Principles + +### I. Upstream Operand Separation — Do Not Fork Upstream Logic + +The operator deploys and manages upstream **external-secrets** and the optional **bitwarden-sdk-server** plugin via **embedded manifests in `bindata/external-secrets/`**. The operator **never** reimplements upstream secret-sync logic (provider authentication, ExternalSecret reconciliation, generator behavior, Bitwarden SDK protocol). Operator packages reconcile operator CRs and deploy/configure operand workloads only. + +**Evidence:** `bindata/external-secrets/resources/` — operand YAML from upstream helm; `pkg/controller/external_secrets/` installs deployments/RBAC/webhooks but contains zero provider-specific secret-fetch logic. `README.md` states the operator uses upstream helm charts. + +### II. Two Operand Workloads — Core vs Plugin + +| Workload | Always? | Controlled by | Image env | +|----------|---------|---------------|-----------| +| **external-secrets** (core controller + webhook + cert-controller) | Yes | `ExternalSecretsConfig` | `RELATED_IMAGE_EXTERNAL_SECRETS` | +| **bitwarden-sdk-server** (provider plugin) | No — when `plugins.bitwardenSecretManagerProvider.mode == Enabled` | `ExternalSecretsConfig.spec.plugins` | `RELATED_IMAGE_BITWARDEN_SDK_SERVER` | + +New plugin workloads MUST follow the bitwarden pattern: API under `spec.plugins`, bindata deployment asset, conditional entry in `createOrApplyDeployments`, dedicated network policy, TLS via `certProvider` or `secretRef`. + +**Evidence:** `deployments.go` — conditional deployment table; `constants.go` — `bitwardenDeploymentAssetName`, image env var names; `api/v1alpha1/external_secrets_config_types.go` — `BitwardenSecretManagerProvider`, CEL rules for TLS prerequisites. + +### III. Controller-Runtime Only — Single Manager, Three Reconcilers + +All controllers use **`sigs.k8s.io/controller-runtime`** on **one** shared manager. Register reconcilers in `pkg/operator/setup_manager.go` only. Do not introduce library-go, informer factories, or separate managers. + +**Evidence:** `pkg/operator/setup_manager.go` — wires `external_secrets_manager`, `external_secrets`, and optional `crd_annotator`; `cmd/external-secrets-operator/main.go` — single `ctrl.Manager`; `go.mod` — `sigs.k8s.io/controller-runtime v0.23.3`, no `openshift/library-go`. + +### IV. Create-or-Update Reconciliation — Not SSA-First + +Operand resources are reconciled via **create-or-update with deep equality** (`createWithFallback`, `common.HasObjectChanged`, `UpdateWithRetry`). Limited SSA (`client.Apply` with field owner) is allowed only for CR annotation patches. Do not convert operand reconcilers to SSA-first patterns. + +**Evidence:** `pkg/controller/external_secrets/install_external_secrets.go`, `pkg/controller/common/utils.go` — `HasObjectChanged()`; `pkg/controller/common/constants.go` — `ExternalSecretsOperatorCommonName` as field owner for annotation patches only. + +### V. Singleton CR Convention — Name `cluster`, One Per Kind + +Operator CRs `ExternalSecretsConfig` and `ExternalSecretsManager` are **cluster-scoped singletons named `cluster`**. The operator auto-creates default `ExternalSecretsManager` named `cluster`. CEL validation enforces singleton naming. + +**Evidence:** `pkg/controller/common/constants.go` — `ExternalSecretsConfigObjectName` and `ExternalSecretsManagerObjectName` = `"cluster"`; `pkg/operator/setup_manager.go` — `CreateDefaultESMResource()`; `README.md` — auto-creates `externalsecretsmanagers.operator.openshift.io` named `cluster`. + +### VI. Feature Flags on ExternalSecretsManager — Not OpenShift FeatureGate API + +Runtime feature toggles are defined on `ExternalSecretsManager.Spec.Features[]` with typed `FeatureName` values. Check via `common.IsFeatureEnabled()`. Do not add OpenShift cluster FeatureGate discovery or `pkg/features/` patterns from other operators. + +**Evidence:** `api/v1alpha1/external_secrets_manager_types.go` — `Feature` slice; `pkg/controller/common/utils.go` — `IsFeatureEnabled()`; `pkg/controller/external_secrets/constants.go` — `featureContainerArgs` map. + +### VII. Webhook TLS — cert-manager or In-Tree cert-controller (Mutually Exclusive) + +Webhook TLS uses either cert-manager `Certificate` CRs (`certProvider.certManager.mode == Enabled`) **or** the in-tree `external-secrets-cert-controller` deployment — never both. cert-controller deployment is skipped when cert-manager path is active. + +**Evidence:** `deployments.go` — `certControllerDeploymentAssetName` condition `!isCertManagerConfigEnabled(esc)`; `certificate.go`, `certificate_external-secrets-webhook.yml` vs `secret_external-secrets-webhook.yml`. + +### VIII. Bindata / Manifest Regeneration — Never Hand-Edit, Always `make update` + +Operand manifests under `bindata/` and generated code (`zz_generated.deepcopy.go`, `pkg/operator/assets/bindata.go`, CRD YAML under `config/crd/bases/`) are **generated artifacts**. Changes require `make update-operand-manifests` (helm pipeline) and/or `make generate && make manifests && make update-bindata`. CI verification (`make verify`) fails if outputs are stale. + +**Evidence:** `hack/update-external-secrets-manifests.sh`; `Makefile` — `EXTERNAL_SECRETS_VERSION`, `update`, `verify`, `verify-bindata`, `verify-generated`; `pkg/operator/assets/bindata.go` — generated. + +### IX. Verification-First Development — `make verify && make lint && make test` + +All changes MUST pass: `make test` (manifests, generate, fmt, vet, test-apis, test-unit), `make verify` (bindata, generated, govulncheck, git diff), and `make lint` (golangci-lint + kube-api-linter). E2E (`make test-e2e`, build tag `e2e`) requires a live cluster. + +**Evidence:** `Makefile` — `test`, `test-unit`, `test-apis`, `test-e2e`, `verify`, `lint` targets; `.golangci.yml` — linter configuration with kube-api-linter plugin. + +### X. RBAC Least Privilege — Explicit Operator and Operand Manifests + +Operator RBAC is in `config/rbac/`. Operand RBAC is embedded in `bindata/external-secrets/resources/` and applied by `pkg/controller/external_secrets/rbacs.go`. New permissions MUST be explicit ClusterRole rules in bindata or operator RBAC — not broad cluster-admin grants. + +**Evidence:** `config/rbac/role.yaml`; `bindata/external-secrets/resources/` — per-component RBAC YAML; `pkg/controller/external_secrets/rbacs.go`. + +### XI. OLM Bundle and Related Images + +The operator ships via OLM (`bundle/`, `config/manifests/`). Operand version is pinned in `Makefile` (`EXTERNAL_SECRETS_VERSION`). Images: +- `RELATED_IMAGE_EXTERNAL_SECRETS` / `OPERAND_EXTERNAL_SECRETS_IMAGE_VERSION` — core operand +- `RELATED_IMAGE_BITWARDEN_SDK_SERVER` / `BITWARDEN_SDK_SERVER_IMAGE_VERSION` — Bitwarden plugin + +Missing image env vars cause irrecoverable errors. + +**Evidence:** `Makefile` — `IMG_VERSION`, `EXTERNAL_SECRETS_VERSION`, `bundle` target; `bundle/manifests/openshift-external-secrets-operator.clusterserviceversion.yaml`; `pkg/controller/external_secrets/constants.go`. + +### XII. Namespace Conventions + +Operator runs in `external-secrets-operator` namespace. Operand runs in `external-secrets` namespace (`OperandDefaultNamespace`). Both are established conventions in controller constants and README. + +**Evidence:** `README.md` — "operator runs in `external-secrets-operator` namespace"; `pkg/controller/external_secrets/constants.go` — `OperandDefaultNamespace`. + +## Additional Constraints + +- **Go version**: Match `go.mod` — currently `go 1.26.0`. — **Evidence:** `go.mod` +- **Workspace**: Multi-module `go.work` (root, `cmd/external-secrets-operator`, `test`, `tools`). Vendor via `make update-vendor`. — **Evidence:** `go.work`, `vendor/` +- **Import ordering**: Local prefix `github.com/openshift/external-secrets-operator`. — **Evidence:** `.golangci.yml` `local-prefixes` +- **FIPS**: Production builds use `hack/go-fips.sh` (`GOEXPERIMENT=strictfipsruntime`, `CGO_ENABLED=1`). — **Evidence:** `Makefile` `build-operator`, `hack/go-fips.sh` +- **Container image**: Operator image from `Dockerfile` / `images/ci/`; operand images from `RELATED_IMAGE_*` env vars. — **Evidence:** `Dockerfile`, CSV relatedImages +- **Test frameworks**: Standard `testing` + counterfeiter fakes in `pkg/`; Ginkgo v2 + envtest in `test/apis/`; Ginkgo + live cluster in `test/e2e/` (tag `e2e`). — **Evidence:** `pkg/controller/client/fakes/`, `test/apis/`, `test/e2e/` +- **CI system**: Prow via `openshift/release`; in-repo verify via `make verify`. — **Evidence:** `README.md` contributing section +- **Optional cert-manager**: Webhook TLS may use cert-manager `Certificate` CRs when cert-manager is installed; `crd_annotator` is conditional. Never assume cert-manager is present. — **Evidence:** `pkg/operator/setup_manager.go`, `pkg/controller/crd_annotator/` + +## Development Workflow + +| Activity | Requirement | Evidence | +|----------|-------------|----------| +| Local unit tests | `make test-unit` or full `make test` | `Makefile` | +| API validation tests | `make test-apis` after CRD/testsuite changes | `hack/test-apis.sh`, `test/apis/` | +| Full verify | `make verify` | `Makefile` `verify` target | +| Lint | `make lint` | `Makefile`, `.golangci.yml` | +| Codegen refresh | `make generate && make manifests` after API edits | `Makefile` | +| Operand bump | `make update-operand-manifests && make update-bindata` | `hack/update-external-secrets-manifests.sh` | +| E2E tests | `make test-e2e` (cluster required); filter via `E2E_GINKGO_LABEL_FILTER` | `Makefile` `test-e2e` | +| Bundle generation | `make bundle` after CSV/CRD changes | `Makefile` `bundle` | +| PR pre-merge | `make test && make verify && make lint`; commit generated outputs | `AGENTS.md` verification matrix | + +## Agent Routing + +| Agent ID | Scope | When to route | +|----------|-------|---------------| +| **API_Agent** | `api/v1alpha1/`, testsuite YAML | CRD types, validation, markers | +| **OperatorController_Agent** | `pkg/controller/external_secrets/`, `external_secrets_manager/`, `crd_annotator/`, `setup_manager.go` | Core operand reconciliation, wiring | +| **ManifestsBindata_Agent** | `bindata/`, `hack/update-external-secrets-manifests.sh`, operand CRDs | Operand manifest refresh, version pins | +| **BitwardenPlugin_Agent** | `Plugins.BitwardenSecretManagerProvider`, bitwarden bindata assets | Bitwarden SDK plugin workload | +| **WebhookTLS_Agent** | `certificates.go`, webhook deployments, trusted CA | Webhook TLS paths | +| **RBACSecurity_Agent** | `config/rbac/`, `rbacs.go`, `networkpolicy.go` | RBAC and network policy | +| **OLMRelease_Agent** | `bundle/`, `config/manifests/` | CSV, relatedImages | +| **Testing_Agent** | `test/e2e/`, `test/apis/` | Test authoring | +| **Docs_Agent** | `README.md`, `docs/` | User-facing docs | + +Full routing detail: `AGENTS.md` (repo root). + +## Governance + +- This constitution supersedes ad-hoc conventions for downstream Planning, Task Creation, and Code Generation agents. +- **Amendments:** require documented evidence of repo change; bump Version and Last Amended date. +- **Conflicts:** if spec contradicts constitution, escalate in plan.md §8 — do not silently override. Forking upstream external-secrets logic into the operator is a constitution violation. +- **Companion docs:** + - **AGENTS.md** takes precedence for agent routing, controller map, Make targets, and test patterns. + - **README.md** takes precedence for human-facing install and contributing procedures. + - **This constitution** takes precedence for architectural principles and non-negotiable guardrails. +- **Complexity:** new patterns must justify deviation from existing repo conventions. Adding a second controller framework or SSA-first operand reconciliation requires constitution amendment. diff --git a/evals/README.md b/evals/README.md new file mode 100644 index 000000000..86db21262 --- /dev/null +++ b/evals/README.md @@ -0,0 +1,175 @@ +# Eval pipeline — retrospective workflow improvement + +Continuous improvement loop for **openspec-agile-workflow**: derive evals from completed feature bundles (EP + epic + stories + PRs + bugs), refine templates, and accumulate learnings for the next bundle. + +**Stage evals for `/opsx-continue`** ship with the schema package — not under `evals/`: + +`schemas/openspec-agile-workflow/evals/*_eval.yaml` → installed as `openspec/schemas/openspec-agile-workflow/evals/` + +`/eval-loop` merges cases into `evals/baseline/evals/` **and** syncs flat copies to the schema `evals/` directory. + +--- + +## Getting started (external-secrets-operator) + +The eval loop is installed in this repository alongside the forward workflow. Operator-specific docs are already customized: + +| File | Role | +|------|------| +| `AGENTS.md` | Agent routing, architecture, verification matrix | +| `constitution.md` | Non-negotiable guardrails (repo root — preferred lookup path) | +| `openspec/schemas/openspec-agile-workflow/inputs/agents.md` | Fallback agent doc | +| `openspec/schemas/openspec-agile-workflow/inputs/constitution.md` | Fallback constitution | + +Full workflow guide: [docs/openspec-agile-workflow.md](../docs/openspec-agile-workflow.md). + +### Setup steps + +1. **Use the devcontainer** (recommended) or ensure OpenSpec CLI is available — see `docs/openspec-agile-workflow.md`. + +2. **Fill `evals/inputs/` with your feature bundle data** + Replace the placeholder content in each file: + | File | What to paste | + |------|---------------| + | `feature-meta.yaml` | Feature name, epic key, target repo URL | + | `01-ep-ard.md` | Enhancement Proposal / ARD content | + | `02-jira-epic.md` | Jira epic export | + | `03-original-repo.md` | Pre-feature repo state (commit, branch, key files) | + | `04-user-stories.md` | User stories linked to the epic | + | `05-repo-prs.md` | PR links and diffs for the completed feature | + | `bugs/index.yaml` | Bug keys list | + | `bugs/<KEY>.md` | One file per bug | + +3. **Run `/eval-loop` to generate evals tailored to this operator** + ``` + /eval-loop + ``` + This populates `evals/baseline/`, `evals/refined-templates/`, and syncs stage evals to `openspec/schemas/openspec-agile-workflow/evals/`. + +4. **Repeat** — replace `evals/inputs/` with the next feature bundle and run `/eval-loop` again. + +--- + +## One command, one feature bundle + +``` +Paste inputs → /eval-loop → baseline updated → paste next bundle → /eval-loop again +``` + +| Step | Action | +|------|--------| +| 1 | Fill `evals/inputs/` with one feature bundle (links + exports) | +| 2 | Run **`/eval-loop`** in Cursor | +| 3 | Review `evals/baseline/` and `evals/refined-templates/` | +| 4 | Replace `evals/inputs/` with the next feature bundle | +| 5 | Run **`/eval-loop`** again — prior evals and refined templates are auto-loaded | + +## Data flow + +``` +evals/inputs/ ──────────────────► Epic Bug Analysis ──► evals/outputs/epic-bug-analysis/* + │ +evals/refined-templates/ ───────┐ │ +baseline/evals/*_eval.yaml ───┤ ▼ +baseline/routing-learnings.md ───────────┴────► Eval Generation + │ + ├──► evals/baseline/evals/<stage>/<stage>_eval.yaml + ├──► PATCH evals/refined-templates/ + └──► evals/baseline/routing-learnings.md +``` + +**Round 2+:** Eval Generation reads **refined templates** and **consolidated stage eval files** from prior rounds. + +## Template paths + +| Path | Role | +|------|------| +| `openspec/schemas/openspec-agile-workflow/templates/` | Upstream defaults for forward `/opsx-*` workflow — **not** eval pipeline input | +| `evals/refined-templates/` | **Eval workflow read/write** — cumulative template refinements | + +On round 1 (empty `refined-templates/`), seed once from `schemas/`, then refine only under `evals/refined-templates/`. + +### Template refinement (mandatory for patchable gaps) + +| Gap type | Action | +|----------|--------| +| `patchable` | Update file in `evals/refined-templates/` | +| `eval-only` | Eval YAML only — document why in `template-gaps.md` | +| `deferred` | Open question — do not mark Fixed | + +Audit trail: `evals/outputs/eval-generation/refinement-patches/` + `evals/baseline/refinement-changelog.md` + +## Consolidated eval files (one per stage) + +All eval cases for a stage live in a **single YAML file**: + +| Stage | File | +|-------|------| +| repo-assessment | `evals/baseline/evals/repo-assessment/repo-assessment_eval.yaml` | +| constitution | `evals/baseline/evals/constitution/constitution_eval.yaml` | +| plan | `evals/baseline/evals/plan/plan_eval.yaml` | +| tasks | `evals/baseline/evals/tasks/tasks_eval.yaml` | +| implementation | `evals/baseline/evals/implementation/implementation_eval.yaml` | +| code-generation | `evals/baseline/evals/code-generation/code-generation_eval.yaml` | + +Each file contains an `evals:` list with all cases (round 1, round 2, ...). Do **not** scatter per-case `eval-r*.yaml` files. + +**code-generation** cases are tagged with `oape_command` and run during **`/opsx-apply`** per task (not during `/opsx-continue`). + +## Forward workflow (`/opsx-continue`) — artifact eval gate + +After each artifact is generated (templates from `{schema_root}/templates/`): + +``` +generate v1 → run stage evals → refine artifact → user approval → next /opsx-continue +``` + +| Score with | Refine | +|------------|--------| +| `openspec/schemas/openspec-agile-workflow/evals/<stage>_eval.yaml` | Change artifact under `openspec/changes/<name>/` only | + +**Do not** edit `{schema_root}/templates/` or `evals/refined-templates/` during `/opsx-continue`. + +Instructions: `{schema_root}/stage-gate/SYSTEM_PROMPT.md`, `{schema_root}/stage-gate/artifact-eval-map.yaml` +Results: `openspec/changes/<change>/eval-results/<artifact-id>.yaml` + +## Forward workflow (`/opsx-apply`) — code generation eval gate + +After each task's OAPE command (or manual work) and verification: + +``` +execute → verify → run code-generation evals → refine code until pass → user approves code → task report → next task +``` + +| Score with | Refine | Record | +|------------|--------|--------| +| `openspec/schemas/.../evals/code-generation_eval.yaml` (by `oape_command`) | Code in fork only | `implementation/task-reports/<task-id>.md` per approved task | + +Instructions: `{schema_root}/stage-gate/CODE_GENERATION_EVAL_PROMPT.md` +Eval results: `openspec/changes/<change>/eval-results/code-generation-<task-id>.yaml` + +## Directory layout + +| Path | Purpose | +|------|---------| +| `inputs/` | Generic placeholders — replace each round | +| `refined-templates/` | Refined templates — eval workflow source of truth | +| `outputs/epic-bug-analysis/` | Current round RCA artifacts | +| `outputs/eval-generation/` | Gap analysis, patches, drafts | +| `baseline/evals/<stage>/<stage>_eval.yaml` | Cumulative eval cases per stage | +| `baseline/evals-registry.yaml` | Master index | +| `baseline/routing-learnings.md` | Bug-derived guardrails from eval loops (not workflow `agents.md`) | +| `baseline/rounds/round-N/` | Snapshot per completed loop | +| `openspec/schemas/openspec-agile-workflow/evals/` | **Forward workflow** merged stage evals (synced from `/eval-loop`) | +| `openspec/schemas/openspec-agile-workflow/stage-gate/` | Forward `/opsx-continue` eval gate instructions | +| `epic-bug-analysis/SYSTEM_PROMPT.md` | Epic Bug Analysis instructions | +| `eval-generation/SYSTEM_PROMPT.md` | Eval Generation instructions | +| `round-state.yaml` | Current round number | + +## Eval Generation stages + +Evals are created/updated for: **repo-assessment → constitution → plan → tasks → implementation → code-generation**. + +**code-generation** evals score fork code per OAPE task during `/opsx-apply` (not markdown artifacts). + +**validation.md** is refined in `evals/refined-templates/` (spec-stage eval) — not duplicated under `baseline/evals/`. diff --git a/evals/baseline/README.md b/evals/baseline/README.md new file mode 100644 index 000000000..3d7cb0be6 --- /dev/null +++ b/evals/baseline/README.md @@ -0,0 +1,13 @@ +# Baseline — cumulative feedback loop store + +Updated after each `/eval-loop`. Fed as input to Epic Bug Analysis and Eval Generation on subsequent rounds. + +| Path | Purpose | +|------|---------| +| `evals/<stage>/<stage>_eval.yaml` | **One consolidated file per stage** — all eval cases in `evals:` list | +| `evals-registry.yaml` | Master index (`stage_eval_files` + per-eval metadata) | +| `routing-learnings.md` | Bug-derived guardrails from `/eval-loop` (not workflow `agents.md`) | +| `refinement-changelog.md` | Append-only log of template changes | +| `rounds/round-N/` | Snapshot per completed loop | + +**Templates** are stored in `evals/refined-templates/` — not here, not in `schemas/`. diff --git a/evals/baseline/evals-registry.yaml b/evals/baseline/evals-registry.yaml new file mode 100644 index 000000000..2fd5f0853 --- /dev/null +++ b/evals/baseline/evals-registry.yaml @@ -0,0 +1,24 @@ +version: 0 +templates_dir: evals/refined-templates/ +rounds: [] +evals: {} +stage_eval_files: + repo-assessment: + path: evals/baseline/evals/repo-assessment/repo-assessment_eval.yaml + template: evals/refined-templates/repo-assessment-template.md + constitution: + path: evals/baseline/evals/constitution/constitution_eval.yaml + template: evals/refined-templates/constitution-template.md + plan: + path: evals/baseline/evals/plan/plan_eval.yaml + template: evals/refined-templates/plan-template.md + tasks: + path: evals/baseline/evals/tasks/tasks_eval.yaml + template: evals/refined-templates/tasks-template.md + implementation: + path: evals/baseline/evals/implementation/implementation_eval.yaml + template: evals/refined-templates/implementation-template.md + code-generation: + path: evals/baseline/evals/code-generation/code-generation_eval.yaml + template: null + note: Per-task code evals for /opsx-apply; cases tagged oape_command diff --git a/evals/baseline/evals/code-generation/.gitkeep b/evals/baseline/evals/code-generation/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/evals/baseline/evals/constitution/.gitkeep b/evals/baseline/evals/constitution/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/evals/baseline/evals/implementation/.gitkeep b/evals/baseline/evals/implementation/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/evals/baseline/evals/plan/.gitkeep b/evals/baseline/evals/plan/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/evals/baseline/evals/repo-assessment/.gitkeep b/evals/baseline/evals/repo-assessment/.gitkeep new file mode 100644 index 000000000..57c3f959b --- /dev/null +++ b/evals/baseline/evals/repo-assessment/.gitkeep @@ -0,0 +1,2 @@ +# Cumulative eval cases — populated by /eval-loop Eval Generation +# Do not edit manually unless fixing IDs; agent merges new cases each round. diff --git a/evals/baseline/evals/tasks/.gitkeep b/evals/baseline/evals/tasks/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/evals/baseline/refinement-changelog.md b/evals/baseline/refinement-changelog.md new file mode 100644 index 000000000..c571d7cf2 --- /dev/null +++ b/evals/baseline/refinement-changelog.md @@ -0,0 +1,3 @@ +# Template Refinement Changelog + +<!-- Append-only log of template changes made during /eval-loop rounds --> diff --git a/evals/baseline/rounds/.gitkeep b/evals/baseline/rounds/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/evals/baseline/routing-learnings.md b/evals/baseline/routing-learnings.md new file mode 100644 index 000000000..634028ae1 --- /dev/null +++ b/evals/baseline/routing-learnings.md @@ -0,0 +1,3 @@ +# Routing Learnings + +<!-- Populated by /eval-loop from bug postmortems and pattern analysis --> diff --git a/evals/epic-bug-analysis/SYSTEM_PROMPT.md b/evals/epic-bug-analysis/SYSTEM_PROMPT.md new file mode 100644 index 000000000..606c383a1 --- /dev/null +++ b/evals/epic-bug-analysis/SYSTEM_PROMPT.md @@ -0,0 +1,130 @@ +# Epic Bug Analysis — Root cause & pattern recognition + +You are the **Epic Bug Analysis Agent**. Derive learnings from one completed dev epic and its bugs to improve the agentic AI workflow. + +Read this prompt in full before acting. Follow `evals/pipeline.yaml` phase `epic-bug-analysis`. + +## Inputs + +### Required (current feature bundle) + +From `evals/inputs/`: + +| File | Content | +|------|---------| +| `feature-meta.yaml` | Optional label for this round | +| `01-ep-ard.md` | EP / ARD link and content | +| `02-jira-epic.md` | Extracted Jira epic information | +| `03-original-repo.md` | Original repo version before the feature | +| `04-user-stories.md` | User stories carved for development | +| `05-repo-prs.md` | Repo PRs for this EP | +| `bugs/index.yaml` | List of bug keys and file paths | +| `bugs/*.md` | Bug details and fixes (one file per bug) | + +**Validation:** If any file still contains `PASTE_` placeholders, **STOP** and ask the user to paste content before continuing. + +### Feedback loop (round 2+) + +When `evals/round-state.yaml` → `round >= 1`, also read: + +- `evals/baseline/evals-registry.yaml` +- `evals/baseline/evals/<stage>/<stage>_eval.yaml` (consolidated eval cases per stage) +- `evals/refined-templates/` (current refined templates — not `schemas/`) +- `evals/baseline/refinement-changelog.md` + +For each new bug/pattern, classify as **`new_pattern`** or **`recurring_pattern`** (matches a prior eval pattern). + +## Processing rules + +- Process **one ticket and its associated bugs at a time**. +- Do not invent facts. Cite evidence: EP section, story ID, PR number, bug field, commit, or file path. +- Do not modify templates or baseline in Epic Bug Analysis — write only to `evals/outputs/epic-bug-analysis/`. + +## Tasks (execute in order) + +### 1. Pattern analysis — requirement → ARD → stories + +Given the feature requirement: + +- How was the ARD laid out? (structure, functional requirements, non-goals, risks) +- How were stories carved for development? (EP → epic → stories mapping) +- Identify gaps: EP content missing from stories, or stories beyond EP scope + +**Output:** `evals/outputs/epic-bug-analysis/pattern-analysis.md` + +### 2. Root cause analysis — bugs vs design & story formation + +For **each bug** in `bugs/index.yaml` (process individually): + +- What happened? (symptom, environment, severity) +- What was the fix? (PR, code change summary) +- Root cause category: + - `design` — ARD ambiguity, wrong pattern, missing non-goal + - `story_formation` — story carving missed scope, weak acceptance criteria, missing verification story + - `coding` — implementation diverged despite good spec/stories + - `ops_docs` — deployment, upgrade, documentation (non-functional) +- Which **workflow stage** should have caught it? + (`validation`, `specs`, `repo-assessment`, `constitution`, `plan`, `tasks`, `implementation`) +- Functional vs non-functional classification +- If round 2+: would an existing eval in `baseline/evals/<stage>/<stage>_eval.yaml` have caught this? + +**Output:** `evals/outputs/epic-bug-analysis/rca-summary.md` + +### 3. Code approach analysis + +From PRs and bug fixes: + +- Deduce what **code approaches** led to bugs +- Identify **functional** issues (wrong behavior, missing feature) +- Identify **non-functional** issues (performance, security, operability, upgrade path) + +Include in `rca-summary.md` under a **Code approach** section. + +### 4. Generalize and segregate + +**Output:** `evals/outputs/epic-bug-analysis/issue-taxonomy.json` + +Use this schema: + +```json +{ + "round": 0, + "feature_name": "", + "epic_key": "", + "patterns": [ + { + "id": "PAT-001", + "description": "", + "recurrence": "new|recurring", + "related_eval_ids": [] + } + ], + "issues": [ + { + "id": "ISSUE-001", + "source_bug": "", + "category": "design|story_formation|coding|ops_docs", + "type": "functional|non_functional", + "workflow_stage": "validation|specs|repo-assessment|constitution|plan|tasks|implementation", + "pattern_id": "PAT-001", + "pattern": "", + "eval_implication": "", + "evidence": [] + } + ], + "summary": { + "design": 0, + "story_formation": 0, + "coding": 0, + "ops_docs": 0 + } +} +``` + +Set `round` from `evals/round-state.yaml` → `round + 1` (the round you are completing). + +## Done when + +All three outputs exist and every bug in `bugs/index.yaml` appears in `issue-taxonomy.json`. + +Then proceed to **Eval Generation** (same `/eval-loop` session) — do not stop unless the user asked to pause after Epic Bug Analysis. diff --git a/evals/eval-generation/SYSTEM_PROMPT.md b/evals/eval-generation/SYSTEM_PROMPT.md new file mode 100644 index 000000000..f17bc57ea --- /dev/null +++ b/evals/eval-generation/SYSTEM_PROMPT.md @@ -0,0 +1,237 @@ +# Eval Generation — Template evals, validation refinement, gap analysis + +You are the **Eval Generation Agent**. Create/update evals, refine templates, and merge into cumulative baseline. + +Read this prompt in full before acting. Follow `evals/pipeline.yaml` phase `eval-generation`. + +## Prerequisites + +- Epic Bug Analysis complete: + - `evals/outputs/epic-bug-analysis/pattern-analysis.md` + - `evals/outputs/epic-bug-analysis/rca-summary.md` + - `evals/outputs/epic-bug-analysis/issue-taxonomy.json` +- Read `evals/eval-generation/template-inventory.yaml` + +## Template source of truth — `evals/refined-templates/` ONLY + +**Critical:** During the eval workflow, read and write templates **only** under: + +``` +evals/refined-templates/ +``` + +**Do NOT** read or write `schemas/openspec-agile-workflow/templates/` during Eval Generation. +That path is upstream defaults for the forward `/opsx-*` workflow — not eval pipeline input. + +| Action | Path | +|--------|------| +| Read templates | `evals/refined-templates/` | +| Write refinements | `evals/refined-templates/` (in place) | +| Seed on round 1 (if empty) | Copy once from `schemas/openspec-agile-workflow/templates/` → `evals/refined-templates/` | + +Read **every** template listed in `template-inventory.yaml` from `evals/refined-templates/`. + +## Additional inputs + +| Source | Purpose | +|--------|---------| +| `evals/eval-generation/stage-samples/` | Optional I/O samples per stage | +| `evals/baseline/evals/<stage>/<stage>_eval.yaml` | Prior eval cases per stage — merge/update | +| `evals/baseline/evals-registry.yaml` | Master eval index | +| `evals/baseline/routing-learnings.md` | Routing learnings from prior `/eval-loop` rounds (not workflow `agents.md`) | +| `evals/baseline/refinement-changelog.md` | Prior template change history | + +## Tasks + +### 1. Understand templates across stages + +For each stage from **repo-assessment** through **implementation**, document in `evals/outputs/eval-generation/template-gaps.md`: + +- Template path under `evals/refined-templates/` +- Required inputs and expected outputs +- How the template would have caught issues from `issue-taxonomy.json` + +### 2. Identify and classify template gaps + +Complete `evals/outputs/eval-generation/template-gaps.md`. + +| Resolution | Meaning | +|------------|---------| +| `patchable` | **MUST** patch `evals/refined-templates/` in place | +| `eval-only` | Enforce via eval YAML only | +| `deferred` | Needs SME input — do not mark Fixed | + +**Rule:** Eval YAML alone does **NOT** satisfy a `patchable` gap. + +### 3. Apply template refinements (mandatory for patchable gaps) + +For every gap with Resolution `patchable`: + +1. **Read** from `evals/refined-templates/<file>.md` +2. **Patch in place** under `evals/refined-templates/` +3. **Save diff** → `evals/outputs/eval-generation/refinement-patches/<filename>.md.patch` +4. **Append** → `evals/baseline/refinement-changelog.md` +5. Set `Fixed: Yes` in `template-gaps.md` + +Do **NOT** patch `schemas/openspec-agile-workflow/templates/`. + +### 4. Refine spec validation template + +Update `evals/refined-templates/validation-template.md` based on taxonomy gaps. + +Document in `evals/outputs/eval-generation/validation-refinements.md`. +Save diff in `evals/outputs/eval-generation/refinement-patches/validation.md.patch`. + +### 5. Create or update evals — one YAML file per stage + +**After** template patches. **Canonical format:** one consolidated file per stage containing **all** eval cases. + +| Stage | Output file | +|-------|-------------| +| repo-assessment | `evals/baseline/evals/repo-assessment/repo-assessment_eval.yaml` | +| constitution | `evals/baseline/evals/constitution/constitution_eval.yaml` | +| plan | `evals/baseline/evals/plan/plan_eval.yaml` | +| tasks | `evals/baseline/evals/tasks/tasks_eval.yaml` | +| implementation | `evals/baseline/evals/implementation/implementation_eval.yaml` | +| code-generation | `evals/baseline/evals/code-generation/code-generation_eval.yaml` | + +**Do NOT** write scattered per-case files (`eval-r001-repo-001.yaml`, etc.). All cases for a stage live in that stage's `*_eval.yaml`. + +**code-generation** is different from artifact stages: it scores **fork code** during `/opsx-apply`, not markdown artifacts. Each case **must** include `oape_command`. See `evals/stages/code-generation/eval-spec.yaml`. + +#### Consolidated file schema + +```yaml +stage: constitution +template: evals/refined-templates/constitution-template.md +artifact: constitution.md +version: 1 +eval_count: 6 +evals: + - id: eval-r001-const-001 + round: 1 + stage: constitution + source_issue_ids: [] + patterns: [PAT-001] + input_refs: + - evals/inputs/01-ep-ard.md + prompt: | + ... + assertions: + must_mention: [...] + scoring: + method: weighted_checklist + pass_threshold: 80 + - id: eval-r002-const-001 + round: 2 + ... +``` + +Rules: + +- Load existing `<stage>_eval.yaml` if present; **merge** new/updated cases into the `evals:` list +- Each case: `id`, `round`, `stage`, `input_refs`, `assertions`, `scoring`, `pass_threshold` +- Reference issues from `issue-taxonomy.json`; regression-oriented +- **Eval ID format:** `eval-r<NNN>-<stage-abbr>-<seq>` +- Update `eval_count` after merge +- Set `template:` to `evals/refined-templates/<template>.md` for the stage +- Minimum **3 eval cases per stage** per round (or document why fewer) +- **Do NOT** duplicate validation as eval YAML — refine `evals/refined-templates/validation-template.md` instead + +Use rubric schema from `evals/stages/<stage>/eval-spec.yaml`. + +Draft working notes under `evals/outputs/eval-generation/evals/` if helpful; **canonical copies are the `*_eval.yaml` files**. + +#### 5b. Sync stage evals to schema package (forward `/opsx-continue`) + +After merging into `evals/baseline/evals/<stage>/<stage>_eval.yaml`, write the same cases to: + +`schemas/openspec-agile-workflow/evals/<stage>_eval.yaml` + +Rules for schema copies: + +- Same `evals:` list and `eval_count` as baseline +- Set `template:` to `templates/<template>.md` (not `evals/refined-templates/`) +- Keep `evals/stages/<stage>/eval-spec.yaml` `stage_eval_file` as `evals/<stage>_eval.yaml` under the schema package + +Installed projects read these from `openspec/schemas/openspec-agile-workflow/evals/`. + +#### 5c. Create code-generation evals (mandatory each round) + +After merging artifact stage evals, author **code-generation** eval cases from: + +| Source | Derive | +|--------|--------| +| `evals/inputs/05-repo-prs.md` | PR diff patterns, missed tests, API/controller mistakes | +| `evals/inputs/bugs/*.md` | Regressions that code review or tests should catch | +| `issue-taxonomy.json` | Code-related patterns (PAT-* tagged implementation/code) | +| Prior `implementation_eval.yaml` cases | Reframe as concrete code assertions when applicable | + +Output: `evals/baseline/evals/code-generation/code-generation_eval.yaml` + +Rules: + +- **Every case** must set `oape_command` to one of: `api-generate`, `api-generate-tests`, `api-implement`, `e2e-generate`, `manual`, or `any` +- **Eval ID format:** `eval-r<NNN>-codegen-<seq>` +- Tag `patterns` from issue taxonomy when applicable +- Minimum **2 code-generation cases per round** when the feature bundle includes PR or bug evidence; document in `template-gaps.md` if fewer +- Assertions use rubric in `evals/stages/code-generation/eval-spec.yaml` (e.g. `must_use_pattern`, `must_not_use`, `must_pass_make_targets`) +- Merge with existing baseline file; update `eval_count` + +Sync to forward workflow: + +`schemas/openspec-agile-workflow/evals/code-generation_eval.yaml` + +Schema copy rules: + +- Same `evals:` list and `eval_count` as baseline +- No `template:` field (not an artifact template stage) +- Include top-level `oape_commands:` list + +Document code-generation gaps in `template-gaps.md` under a **Code generation evals** section (eval-only gaps are OK here). + +### 6. Update routing-learnings.md + +Append or revise `evals/baseline/routing-learnings.md` when agent routing or guardrail gaps were found. Do **not** conflate with repo-root **`agents.md`** (OpenSpec workflow agent roster). + +### 7. Update registry and round snapshot + +Update `evals/baseline/evals-registry.yaml`: + +```yaml +version: 3 +stage_eval_files: + repo-assessment: + path: evals/baseline/evals/repo-assessment/repo-assessment_eval.yaml + template: evals/refined-templates/repo-assessment-template.md + constitution: + path: evals/baseline/evals/constitution/constitution_eval.yaml + template: evals/refined-templates/constitution-template.md + # ... plan, tasks, implementation +rounds: + - round: 1 + added_evals: [eval-r001-repo-001, ...] +evals: + eval-r001-repo-001: + stage: repo-assessment + stage_eval_file: evals/baseline/evals/repo-assessment/repo-assessment_eval.yaml + introduced_round: 1 + patterns: [] +``` + +Create snapshot: `evals/baseline/rounds/round-<N>/` with `issue-taxonomy.json` + `round-summary.md`. + +### 8. Update round state + +Update `evals/round-state.yaml`: increment `round`, bump `baseline_version`, append `history`. + +## Done when + +- Templates refined in `evals/refined-templates/` (not `schemas/`) +- Every `patchable` gap has `Fixed: Yes` + `refinement-changelog.md` entry +- All six `<stage>_eval.yaml` files updated with merged eval cases (including code-generation) +- `evals-registry.yaml` and `round-state.yaml` updated + +Report to user: + +> Loop complete (round N). Review `evals/baseline/` and `evals/refined-templates/`. Paste the next feature bundle into `evals/inputs/` and run `/eval-loop` again. diff --git a/evals/eval-generation/stage-samples/code-generation/README.md b/evals/eval-generation/stage-samples/code-generation/README.md new file mode 100644 index 000000000..ebcb12bcb --- /dev/null +++ b/evals/eval-generation/stage-samples/code-generation/README.md @@ -0,0 +1,26 @@ +# Code-generation stage samples + +Optional reference when authoring `code-generation_eval.yaml` cases during `/eval-loop`. + +## What to derive from a feature bundle + +| Input | Look for | +|-------|----------| +| `05-repo-prs.md` | API marker mistakes, controller anti-patterns, missing tests | +| `bugs/*.md` | Regressions fixable by code eval assertions | +| `issue-taxonomy.json` | PAT-* patterns tagged implementation / code | + +## oape_command tagging + +| Command | Typical code under test | +|---------|-------------------------| +| `api-generate` | `api/**`, `*_types.go`, CRD markers | +| `api-generate-tests` | `test/apis/**`, `.testsuite.yaml` | +| `api-implement` | `pkg/controller/**`, reconcilers | +| `e2e-generate` | `test/e2e/**`, Ginkgo or bash e2e | +| `manual` | `bindata/`, `config/`, RBAC, OLM bundles | +| `any` | Cross-cutting (effective-go, constitution) | + +## Example case shape + +See `evals/stages/code-generation/eval-spec.yaml` → `example`. diff --git a/evals/eval-generation/stage-samples/constitution/README.md b/evals/eval-generation/stage-samples/constitution/README.md new file mode 100644 index 000000000..81ce83982 --- /dev/null +++ b/evals/eval-generation/stage-samples/constitution/README.md @@ -0,0 +1,9 @@ +# Optional I/O samples — constitution stage + +## Sample input + +PASTE_SAMPLE_INPUT_HERE + +## Sample output + +PASTE_SAMPLE_OUTPUT_HERE diff --git a/evals/eval-generation/stage-samples/implementation/README.md b/evals/eval-generation/stage-samples/implementation/README.md new file mode 100644 index 000000000..e578e25d6 --- /dev/null +++ b/evals/eval-generation/stage-samples/implementation/README.md @@ -0,0 +1,9 @@ +# Optional I/O samples — implementation stage + +## Sample input + +PASTE_SAMPLE_INPUT_HERE + +## Sample output + +PASTE_SAMPLE_OUTPUT_HERE diff --git a/evals/eval-generation/stage-samples/plan/README.md b/evals/eval-generation/stage-samples/plan/README.md new file mode 100644 index 000000000..aeeea6ee3 --- /dev/null +++ b/evals/eval-generation/stage-samples/plan/README.md @@ -0,0 +1,9 @@ +# Optional I/O samples — plan stage + +## Sample input + +PASTE_SAMPLE_INPUT_HERE + +## Sample output + +PASTE_SAMPLE_OUTPUT_HERE diff --git a/evals/eval-generation/stage-samples/repo-assessment/README.md b/evals/eval-generation/stage-samples/repo-assessment/README.md new file mode 100644 index 000000000..d1c695c78 --- /dev/null +++ b/evals/eval-generation/stage-samples/repo-assessment/README.md @@ -0,0 +1,10 @@ +# Optional I/O samples — repo-assessment stage +# Paste example input/output pairs from past features to guide eval authoring. + +## Sample input + +PASTE_SAMPLE_INPUT_HERE + +## Sample output (passing repo-assessment.md excerpt) + +PASTE_SAMPLE_OUTPUT_HERE diff --git a/evals/eval-generation/stage-samples/tasks/README.md b/evals/eval-generation/stage-samples/tasks/README.md new file mode 100644 index 000000000..245b38fe8 --- /dev/null +++ b/evals/eval-generation/stage-samples/tasks/README.md @@ -0,0 +1,9 @@ +# Optional I/O samples — tasks stage + +## Sample input + +PASTE_SAMPLE_INPUT_HERE + +## Sample output + +PASTE_SAMPLE_OUTPUT_HERE diff --git a/evals/eval-generation/template-inventory.yaml b/evals/eval-generation/template-inventory.yaml new file mode 100644 index 000000000..0458bca5a --- /dev/null +++ b/evals/eval-generation/template-inventory.yaml @@ -0,0 +1,90 @@ +# Template inventory — Eval Generation inputs +# READ/WRITE: evals/refined-templates/ only (NOT schemas/) + +refined_templates_dir: evals/refined-templates/ +schema_templates_dir_readonly: schemas/openspec-agile-workflow/templates/ + +agents_md: + workflow_roster: agents.md # repo root → openspec/schemas/.../agents.md (OpenSpec workflow) + baseline_routing_learnings: evals/baseline/routing-learnings.md # /eval-loop only + +spec_understanding: + - id: validation + file: validation-template.md + artifact: validation.json + note: Refine in evals/refined-templates/ — spec-stage eval; not duplicated under baseline/evals/ + - id: specs + file: spec-template.md + artifact: specs.md + note: Spec authoring template; gaps inform validation refinement + +repo_understanding: + - id: repo-assessment + file: repo-assessment-template.md + artifact: repo-assessment.md + eval_stage: repo-assessment + stage_eval_file: evals/baseline/evals/repo-assessment/repo-assessment_eval.yaml + - id: constitution + file: constitution-template.md + artifact: constitution.md + eval_stage: constitution + stage_eval_file: evals/baseline/evals/constitution/constitution_eval.yaml + +planning: + - id: plan + file: plan-template.md + artifact: plan.md + eval_stage: plan + stage_eval_file: evals/baseline/evals/plan/plan_eval.yaml + +task_creation: + - id: tasks + file: tasks-template.md + artifact: tasks.md + eval_stage: tasks + stage_eval_file: evals/baseline/evals/tasks/tasks_eval.yaml + - id: tasks-skeleton + file: tasks-modes/skeleton-template.md + - id: tasks-payloads + file: tasks-modes/payloads-template.md + - id: tasks-orchestration + file: tasks-modes/orchestration-template.md + - id: tasks-single + file: tasks-modes/single-template.md + - id: tasks-impact-assessment + file: tasks-modes/impact_assessment-template.md + - id: tasks-payload-revision + file: tasks-modes/payload_revision-template.md + +implementation: + - id: implementation + file: implementation-template.md + artifact: code + phase FILE OPS + eval_stage: implementation + stage_eval_file: evals/baseline/evals/implementation/implementation_eval.yaml + - id: implementation-report + file: implementation-report-template.md + - id: implementation-checklist + file: implementation-checklist-template.md + - id: adrs + file: adrs-template.md + +code_generation: + description: >- + Per-task code evals for /opsx-apply — not a markdown artifact stage. + Cases tagged with oape_command; no template to refine. + eval_stage: code-generation + stage_eval_file: evals/baseline/evals/code-generation/code-generation_eval.yaml + forward_eval_file: schemas/openspec-agile-workflow/evals/code-generation_eval.yaml + eval_spec: evals/stages/code-generation/eval-spec.yaml + oape_commands: + - api-generate + - api-generate-tests + - api-implement + - e2e-generate + - manual + - any + +optional_not_in_eval_stages: [] + +stage_samples_dir: evals/eval-generation/stage-samples/ diff --git a/evals/inputs/01-ep-ard.md b/evals/inputs/01-ep-ard.md new file mode 100644 index 000000000..58373cb87 --- /dev/null +++ b/evals/inputs/01-ep-ard.md @@ -0,0 +1,3 @@ +PASTE_EP_CONTENT_HERE + +<!-- Paste the full Enhancement Proposal / ARD content for this feature bundle --> diff --git a/evals/inputs/02-jira-epic.md b/evals/inputs/02-jira-epic.md new file mode 100644 index 000000000..a37a13812 --- /dev/null +++ b/evals/inputs/02-jira-epic.md @@ -0,0 +1,3 @@ +PASTE_JIRA_EPIC_EXPORT_HERE + +<!-- Paste the Jira epic export (summary, description, acceptance criteria, linked stories) --> diff --git a/evals/inputs/03-original-repo.md b/evals/inputs/03-original-repo.md new file mode 100644 index 000000000..7ec0e5872 --- /dev/null +++ b/evals/inputs/03-original-repo.md @@ -0,0 +1,3 @@ +PASTE_REPO_STATE_HERE + +<!-- Paste the pre-feature repository state: commit SHA, branch, key file listings --> diff --git a/evals/inputs/04-user-stories.md b/evals/inputs/04-user-stories.md new file mode 100644 index 000000000..2f207a2f1 --- /dev/null +++ b/evals/inputs/04-user-stories.md @@ -0,0 +1,3 @@ +PASTE_USER_STORIES_HERE + +<!-- Paste all user stories / sub-tasks linked to the epic --> diff --git a/evals/inputs/05-repo-prs.md b/evals/inputs/05-repo-prs.md new file mode 100644 index 000000000..4e5d26d30 --- /dev/null +++ b/evals/inputs/05-repo-prs.md @@ -0,0 +1,3 @@ +PASTE_PR_LINKS_AND_DIFFS_HERE + +<!-- Paste PR links, summaries, and key diffs for the completed feature --> diff --git a/evals/inputs/bugs/index.yaml b/evals/inputs/bugs/index.yaml new file mode 100644 index 000000000..251485e28 --- /dev/null +++ b/evals/inputs/bugs/index.yaml @@ -0,0 +1,4 @@ +bugs: [] +# Add bugs related to your feature bundle: +# - key: PROJ-123 +# file: bugs/PROJ-123.md diff --git a/evals/inputs/feature-meta.yaml b/evals/inputs/feature-meta.yaml new file mode 100644 index 000000000..f964df556 --- /dev/null +++ b/evals/inputs/feature-meta.yaml @@ -0,0 +1,3 @@ +feature_name: PASTE_FEATURE_NAME_HERE +epic_key: PASTE_EPIC_KEY_HERE +target_repo: https://github.com/openshift/external-secrets-operator diff --git a/evals/inputs/scope-constraints.example.md b/evals/inputs/scope-constraints.example.md new file mode 100644 index 000000000..6b0364062 --- /dev/null +++ b/evals/inputs/scope-constraints.example.md @@ -0,0 +1,16 @@ +# Scope constraints (copy to openspec/changes/<name>/inputs/scope-constraints.md) + +## Jira + +- **Only** `inputs/jira-spec.md` (pilot template: `docs/openspec-eval-pilot-jira.md`) +- Forbidden: Jira MCP, linked issues, epic, subtasks, comments, attachments + +## Repository + +- **Only** files under the working folder at current `git rev-parse HEAD` +- Forbidden: `git fetch`, `git checkout main`, diff vs `main`/`origin/*`, GitHub API, other branches, remote clone + +## If information is missing + +- Mark `[NEEDS CLARIFICATION]` or state an assumption in the artifact +- Do NOT pull missing context from Jira links or other branches diff --git a/evals/outputs/epic-bug-analysis/.gitkeep b/evals/outputs/epic-bug-analysis/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/evals/outputs/eval-generation/.gitkeep b/evals/outputs/eval-generation/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/evals/outputs/eval-generation/refinement-patches/.gitkeep b/evals/outputs/eval-generation/refinement-patches/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/evals/pipeline.yaml b/evals/pipeline.yaml new file mode 100644 index 000000000..ff550c38a --- /dev/null +++ b/evals/pipeline.yaml @@ -0,0 +1,211 @@ +name: openspec-eval-pipeline +version: 1 + +description: >- + Single-command retrospective loop: Epic Bug Analysis → Eval Generation → + cumulative baseline. One feature bundle per /eval-loop invocation. + +command: /eval-loop + +# Eval workflow template source — NOT schemas/ (upstream defaults for /opsx-* only) + refined_templates_dir: evals/refined-templates/ + refined_agents_md: evals/refined-templates/agents.md + +# Upstream defaults (read-only during eval pipeline; seed refined-templates on round 1 if empty) +schema_templates_dir: + installed: openspec/schemas/openspec-agile-workflow/templates + distribution: schemas/openspec-agile-workflow/templates + +phases: + - id: validate-inputs + reads: + - evals/inputs/feature-meta.yaml + - evals/inputs/01-ep-ard.md + - evals/inputs/02-jira-epic.md + - evals/inputs/03-original-repo.md + - evals/inputs/04-user-stories.md + - evals/inputs/05-repo-prs.md + - evals/inputs/bugs/index.yaml + - evals/inputs/bugs/*.md + rule: Stop if placeholders (PASTE_) are still unfilled + + - id: load-baseline + reads: + - evals/round-state.yaml + - evals/baseline/evals-registry.yaml + - evals/baseline/evals/<stage>/<stage>_eval.yaml + - evals/baseline/routing-learnings.md + - evals/baseline/refinement-changelog.md + - evals/refined-templates/ + when: round >= 1 + rule: Round 1 may have empty baseline; round 2+ must read prior evals and refined templates + + - id: epic-bug-analysis + name: Epic Bug Analysis + system_prompt: evals/epic-bug-analysis/SYSTEM_PROMPT.md + reads: + - evals/inputs/* + - evals/refined-templates/ + - evals/baseline/ + writes: + - evals/outputs/epic-bug-analysis/pattern-analysis.md + - evals/outputs/epic-bug-analysis/rca-summary.md + - evals/outputs/epic-bug-analysis/issue-taxonomy.json + + - id: eval-generation + name: Eval Generation + system_prompt: evals/eval-generation/SYSTEM_PROMPT.md + reads: + - evals/outputs/epic-bug-analysis/* + - evals/eval-generation/template-inventory.yaml + - evals/refined-templates/ + - evals/eval-generation/stage-samples/ + - evals/baseline/evals/<stage>/<stage>_eval.yaml + - evals/baseline/routing-learnings.md + - evals/baseline/refinement-changelog.md + steps: + - id: seed-refined-templates + rule: >- + If evals/refined-templates/ is empty or missing key files, copy from + schema_templates_dir once and copy agents.md from schemas/openspec-agile-workflow/agents.md. + Never write eval refinements to schemas/. + - id: inventory-templates + reads: [evals/refined-templates/, evals/eval-generation/template-inventory.yaml] + - id: identify-gaps + writes: [evals/outputs/eval-generation/template-gaps.md] + - id: apply-template-refinements + rule: >- + For every gap marked patchable in template-gaps.md, patch the + corresponding file under evals/refined-templates/ IN PLACE. + Also refine evals/refined-templates/agents.md with operator-specific + learnings (new patterns, test strategies, architecture insights from bugs/PRs). + Save .patch files and update refinement-changelog. + writes: + - evals/refined-templates/*.md + - evals/refined-templates/agents.md + - evals/outputs/eval-generation/refinement-patches/*.patch + - evals/baseline/refinement-changelog.md + - id: refine-validation + writes: + - evals/refined-templates/validation-template.md + - evals/outputs/eval-generation/validation-refinements.md + - evals/outputs/eval-generation/refinement-patches/validation.md.patch + - id: create-eval-cases + rule: >- + Merge all eval cases per stage into ONE file: + evals/baseline/evals/<stage>/<stage>_eval.yaml + (e.g. constitution_eval.yaml). Do not scatter per-case files. + Then sync flat copies to schemas/openspec-agile-workflow/evals/<stage>_eval.yaml + with template: templates/<name>-template.md for forward /opsx-continue (artifact stages). + For code-generation: sync to schemas/.../evals/code-generation_eval.yaml + (no template — scores fork code during /opsx-apply per task). + writes: + - evals/baseline/evals/repo-assessment/repo-assessment_eval.yaml + - evals/baseline/evals/constitution/constitution_eval.yaml + - evals/baseline/evals/plan/plan_eval.yaml + - evals/baseline/evals/tasks/tasks_eval.yaml + - evals/baseline/evals/implementation/implementation_eval.yaml + - evals/baseline/evals/code-generation/code-generation_eval.yaml + - schemas/openspec-agile-workflow/evals/repo-assessment_eval.yaml + - schemas/openspec-agile-workflow/evals/constitution_eval.yaml + - schemas/openspec-agile-workflow/evals/plan_eval.yaml + - schemas/openspec-agile-workflow/evals/tasks_eval.yaml + - schemas/openspec-agile-workflow/evals/implementation_eval.yaml + - schemas/openspec-agile-workflow/evals/code-generation_eval.yaml + - id: create-code-generation-evals + name: Code Generation Eval Authoring + rule: >- + After artifact eval cases (create-eval-cases), derive code-generation + evals from PR diffs (05-repo-prs.md), bug postmortems, and + issue-taxonomy.json code-related patterns. Tag each case with + oape_command (api-generate, api-generate-tests, api-implement, + e2e-generate, manual, or any). Minimum 2 cases per oape_command + represented in the feature bundle when evidence exists; document + why fewer in template-gaps.md. Rubric: evals/stages/code-generation/eval-spec.yaml. + reads: + - evals/outputs/epic-bug-analysis/issue-taxonomy.json + - evals/inputs/05-repo-prs.md + - evals/inputs/bugs/*.md + - evals/stages/code-generation/eval-spec.yaml + writes: + - evals/baseline/evals/code-generation/code-generation_eval.yaml + - schemas/openspec-agile-workflow/evals/code-generation_eval.yaml + - id: update-registry + writes: + - evals/baseline/evals-registry.yaml + - evals/baseline/routing-learnings.md + - evals/baseline/rounds/round-<N>/ + - evals/round-state.yaml + writes: + - evals/outputs/eval-generation/template-gaps.md + - evals/outputs/eval-generation/validation-refinements.md + - evals/outputs/eval-generation/refinement-patches/ + - evals/baseline/evals/<stage>/<stage>_eval.yaml + - schemas/openspec-agile-workflow/evals/<stage>_eval.yaml + - evals/baseline/evals-registry.yaml + - evals/baseline/routing-learnings.md + - evals/baseline/refinement-changelog.md + - evals/baseline/rounds/round-<N>/ + - evals/refined-templates/*.md + +eval_stages: + - repo-assessment + - constitution + - plan + - tasks + - implementation + - code-generation + +code_generation_eval: + description: >- + Per-task code evals for /opsx-apply. Not an /opsx-continue artifact stage. + Cases filtered by oape_command at runtime. + baseline: evals/baseline/evals/code-generation/code-generation_eval.yaml + forward: schemas/openspec-agile-workflow/evals/code-generation_eval.yaml + eval_spec: evals/stages/code-generation/eval-spec.yaml + gate_prompt: schemas/openspec-agile-workflow/stage-gate/CODE_GENERATION_EVAL_PROMPT.md + oape_commands: + - api-generate + - api-generate-tests + - api-implement + - e2e-generate + - manual + - any + +stage_eval_files: + retrospective: evals/baseline/evals/<stage>/<stage>_eval.yaml + forward_workflow: schemas/openspec-agile-workflow/evals/<stage>_eval.yaml + installed_forward: openspec/schemas/openspec-agile-workflow/evals/<stage>_eval.yaml + +stage_eval_files_baseline: + repo-assessment: evals/baseline/evals/repo-assessment/repo-assessment_eval.yaml + constitution: evals/baseline/evals/constitution/constitution_eval.yaml + plan: evals/baseline/evals/plan/plan_eval.yaml + tasks: evals/baseline/evals/tasks/tasks_eval.yaml + implementation: evals/baseline/evals/implementation/implementation_eval.yaml + code-generation: evals/baseline/evals/code-generation/code-generation_eval.yaml + +refine_in_place: + dir: evals/refined-templates/ + files: + - validation.md + - repo-assessment.md + - constitution.md + - plan.md + - tasks.md + - implementation.md + - tasks-modes/*.md + - implementation-checklist.md + - implementation-report.md + - agents.md + +feedback_loop: + description: >- + After each /eval-loop, baseline stage eval files and evals/refined-templates/ + become inputs to the next /eval-loop. + cumulative: + - evals/baseline/evals/<stage>/<stage>_eval.yaml + - evals/baseline/evals/code-generation/code-generation_eval.yaml + - evals/baseline/evals-registry.yaml + in_place: + - evals/refined-templates/ diff --git a/evals/refined-templates/.gitkeep b/evals/refined-templates/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/evals/refined-templates/tasks-modes/.gitkeep b/evals/refined-templates/tasks-modes/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/evals/round-state.yaml b/evals/round-state.yaml new file mode 100644 index 000000000..fa42dc4d3 --- /dev/null +++ b/evals/round-state.yaml @@ -0,0 +1,4 @@ +round: 0 +baseline_version: "0.0.0" + +history: [] diff --git a/evals/stages/code-generation/eval-spec.yaml b/evals/stages/code-generation/eval-spec.yaml new file mode 100644 index 000000000..197e5b8dc --- /dev/null +++ b/evals/stages/code-generation/eval-spec.yaml @@ -0,0 +1,84 @@ +# Eval case schema — code-generation stage (/opsx-apply per-task gate) + +stage: code-generation +artifact: generated or modified code in fork working copy (after OAPE command or manual agent work) +stage_eval_file: evals/baseline/evals/code-generation/code-generation_eval.yaml +forward_eval_file: schemas/openspec-agile-workflow/evals/code-generation_eval.yaml + +description: >- + Scores generated code per task during /opsx-apply. Cases are tagged with + oape_command so only relevant evals run for the current task. Authored + retrospectively by /eval-loop from EPs, PRs, and bug postmortems. + +consolidated_schema: + stage: code-generation + artifact: fork working copy (code + tests) + version: 1 + eval_count: <N> + oape_commands: + - api-generate + - api-generate-tests + - api-implement + - e2e-generate + - manual + evals: [<case>, ...] + +required_fields: + - id + - round + - stage + - oape_command + - source_issue_ids + - input_refs + - prompt + - assertions + - scoring + +oape_command_values: + api-generate: API type / CRD generation tasks + api-generate-tests: .testsuite.yaml / API integration test tasks + api-implement: Controller / operator reconciliation tasks + e2e-generate: E2E test artifact tasks + manual: ManifestsBindata, RBAC, OLM, Docs, WebhookTLS agents (no OAPE command) + +assertion_types: + - must_use_pattern + - must_not_use + - must_pass_make_targets + - must_match_task_payload + - files_must_exist + - files_must_not_exist + - must_follow_constitution + - must_follow_effective_go + - must_include_tests + - must_not_violate_non_goals + +filter_rule_forward: >- + During /opsx-apply, load evals/code-generation_eval.yaml and score only cases + where oape_command matches the resolved command for the current task (use + manual when no OAPE command). Always include cases with oape_command: any if present. + +example: + id: eval-r001-codegen-001 + round: 1 + stage: code-generation + oape_command: api-implement + source_issue_ids: [PROJ-123] + patterns: [PAT-001] + input_refs: + - evals/inputs/05-repo-prs.md + - evals/inputs/bugs/PROJ-123.md + prompt: | + Controller reconciliation must follow the established pattern for status updates. + assertions: + must_use_pattern: + - ReconcileResult + must_not_use: + - client.Create + - client.Update + must_pass_make_targets: + - make test + must_match_task_payload: true + scoring: + method: weighted_checklist + pass_threshold: 85 diff --git a/evals/stages/constitution/eval-spec.yaml b/evals/stages/constitution/eval-spec.yaml new file mode 100644 index 000000000..ff99a06ac --- /dev/null +++ b/evals/stages/constitution/eval-spec.yaml @@ -0,0 +1,45 @@ +# Eval case schema — constitution stage + +stage: constitution +template: evals/refined-templates/constitution-template.md +artifact: constitution.md +stage_eval_file: evals/baseline/evals/constitution/constitution_eval.yaml + +consolidated_schema: + stage: constitution + template: evals/refined-templates/constitution-template.md + artifact: constitution.md + version: 1 + eval_count: <N> + evals: [<case>, ...] + +required_fields: + - id + - round + - stage + - source_issue_ids + - input_refs + - assertions + - scoring + - pass_threshold + +assertion_types: + - must_cite_repo_evidence + - must_set_agent_routing_mode + - must_align_with_agents_md + - must_not_duplicate_repo_assessment + - must_mention + +example: + id: eval-r001-const-001 + round: 1 + stage: constitution + source_issue_ids: [ISSUE-001] + input_refs: + - evals/baseline/routing-learnings.md + assertions: + must_cite_repo_evidence: true + must_set_agent_routing_mode: [PROVIDED, PROVISIONAL] + scoring: + method: weighted_checklist + pass_threshold: 80 diff --git a/evals/stages/implementation/eval-spec.yaml b/evals/stages/implementation/eval-spec.yaml new file mode 100644 index 000000000..8b8124379 --- /dev/null +++ b/evals/stages/implementation/eval-spec.yaml @@ -0,0 +1,43 @@ +# Eval case schema — implementation stage + +stage: implementation +template: evals/refined-templates/implementation-template.md +artifact: code changes + implementation-report.md +stage_eval_file: evals/baseline/evals/implementation/implementation_eval.yaml + +consolidated_schema: + stage: implementation + template: evals/refined-templates/implementation-template.md + artifact: code + phase FILE OPS + version: 1 + eval_count: <N> + evals: [<case>, ...] + +required_fields: + - id + - round + - stage + - source_issue_ids + - input_refs + - assertions + - scoring + - pass_threshold + +assertion_types: + - must_follow_constitution + - must_use_ssa_not_create_or_update + - must_match_task_payloads + - must_include_tests + - must_not_violate_non_goals + +example: + id: eval-r001-impl-001 + round: 1 + stage: implementation + source_issue_ids: [ISSUE-001] + assertions: + must_use_ssa_not_create_or_update: true + must_match_task_payloads: true + scoring: + method: weighted_checklist + pass_threshold: 80 diff --git a/evals/stages/plan/eval-spec.yaml b/evals/stages/plan/eval-spec.yaml new file mode 100644 index 000000000..14327ece5 --- /dev/null +++ b/evals/stages/plan/eval-spec.yaml @@ -0,0 +1,43 @@ +# Eval case schema — plan stage + +stage: plan +template: evals/refined-templates/plan-template.md +artifact: plan.md +stage_eval_file: evals/baseline/evals/plan/plan_eval.yaml + +consolidated_schema: + stage: plan + template: evals/refined-templates/plan-template.md + artifact: plan.md + version: 1 + eval_count: <N> + evals: [<case>, ...] + +required_fields: + - id + - round + - stage + - source_issue_ids + - input_refs + - assertions + - scoring + - pass_threshold + +assertion_types: + - must_cover_fr_ids + - must_include_verification_matrix + - must_cite_repo_assessment + - must_address_risk_from_ep + - must_phase_structure + +example: + id: eval-r001-plan-001 + round: 1 + stage: plan + source_issue_ids: [ISSUE-001] + assertions: + must_include_verification_matrix: true + must_cite_repo_assessment: true + scoring: + method: weighted_checklist + pass_threshold: 80 diff --git a/evals/stages/repo-assessment/eval-spec.yaml b/evals/stages/repo-assessment/eval-spec.yaml new file mode 100644 index 000000000..7c2653049 --- /dev/null +++ b/evals/stages/repo-assessment/eval-spec.yaml @@ -0,0 +1,50 @@ +# Eval case schema — repo-assessment stage + +stage: repo-assessment +template: evals/refined-templates/repo-assessment-template.md +artifact: repo-assessment.md +stage_eval_file: evals/baseline/evals/repo-assessment/repo-assessment_eval.yaml + +# All cases for this stage live in ONE file: repo-assessment_eval.yaml +consolidated_schema: + stage: repo-assessment + template: evals/refined-templates/repo-assessment-template.md + artifact: repo-assessment.md + version: 1 + eval_count: <N> + evals: [<case>, ...] + +required_fields: + - id + - round + - stage + - source_issue_ids + - input_refs + - assertions + - scoring + - pass_threshold + +assertion_types: + - must_mention + - must_identify_package + - must_cite_pre_feature_gap + - must_cross_reference_spec + - must_not_claim + +example: + id: eval-r001-repo-001 + round: 1 + stage: repo-assessment + source_issue_ids: [ISSUE-001] + input_refs: + - evals/inputs/03-original-repo.md + - evals/inputs/01-ep-ard.md + prompt: | + Given approved specs and pinned pre-feature repo, produce repo-assessment.md. + assertions: + must_mention: + - target controller package path + must_cite_pre_feature_gap: true + scoring: + method: weighted_checklist + pass_threshold: 80 diff --git a/evals/stages/tasks/eval-spec.yaml b/evals/stages/tasks/eval-spec.yaml new file mode 100644 index 000000000..b542fe7d9 --- /dev/null +++ b/evals/stages/tasks/eval-spec.yaml @@ -0,0 +1,43 @@ +# Eval case schema — tasks stage + +stage: tasks +template: evals/refined-templates/tasks-template.md +artifact: tasks.md +stage_eval_file: evals/baseline/evals/tasks/tasks_eval.yaml + +consolidated_schema: + stage: tasks + template: evals/refined-templates/tasks-template.md + artifact: tasks.md + version: 1 + eval_count: <N> + evals: [<case>, ...] + +required_fields: + - id + - round + - stage + - source_issue_ids + - input_refs + - assertions + - scoring + - pass_threshold + +assertion_types: + - must_map_all_fr_sc + - must_pair_verification_tasks + - must_use_valid_agent_ids + - must_include_payload_sections + - must_reference_plan_phases + +example: + id: eval-r001-tasks-001 + round: 1 + stage: tasks11BLXLMQA0z9mbtgESBEBe_4aMi02hSahBitFt4Uvd7yftgV5jaYd5JRvy0VZb6NGO3SFLIV3KJVcaHZhA + source_issue_ids: [ISSUE-001] + assertions: + must_pair_verification_tasks: true + must_map_all_fr_sc: true + scoring: + method: weighted_checklist + pass_threshold: 80 diff --git a/openspec/config.yaml b/openspec/config.yaml new file mode 100644 index 000000000..af1497fb3 --- /dev/null +++ b/openspec/config.yaml @@ -0,0 +1,53 @@ +schema: openspec-agile-workflow + +context: | + GS Spec Kit agile workflow — gated spec-driven development. + Stages: validation → specs → repo-assessment + constitution → plan → tasks → implementation. + Primary input at /opsx:new: Jira ticket key ONLY (stored in inputs/jira.yaml). + Target GitHub repository: NOT required at /opsx:new — required before repo-assessment + artifact (unless working-folder mode — see schema working_folder_repo). + Optional anytime: AGENTS.md from the target repo. + Working-folder mode: when the user directs using the current checkout as the repo, + set use_working_folder_as_repo: true in inputs/jira.yaml — skip fork URL and draft PR. + Fork URL: NOT required until Implementation (/opsx:apply) in default mode — stored as + fork_repo_url in inputs/jira.yaml. + Approve each artifact before proceeding to the next stage. Implementation uses + task-by-task execution with per-task user approval (/opsx-apply), then pushes a + draft PR to the user's fork. + +flags: + max_feedback_rounds: 3 # Max user rejection + refinement loops per artifact before halting (USER_FEEDBACK_PROMPT.md) + exit_on_all_tasks_complete: true # Exit implementation stage automatically when all tasks in tasks.md are marked [x] + generate_evaluation_reports: true # Generate <artifact>_evaluation_report.md alongside each artifact + +rules: + validation: + - "Read jira_key from inputs/jira.yaml; use inputs/jira-spec.md as spec source" + - "Obtain Jira ticket content via Jira MCP or ask the user to paste into inputs/jira-spec.md" + - "Score completeness (60%) and quality (40%); do not invent requirements" + - "Emit validation.json matching the validation.md template schema" + specs: + - "Derive from the same Jira ticket referenced in inputs/jira.yaml" + - "No implementation details; max 3 [NEEDS CLARIFICATION] markers" + - "User stories with Given/When/Then; FR-xxx and SC-xxx identifiers" + - "If user rejects specs.md at approval, exit the workflow (no refinement loop; see schema exit_on_reject.specs)" + repo-assessment: + - "Before creating repo-assessment.md, ensure target_repo is set OR working-folder mode is active (see schema working_folder_repo and target_repo)" + - "Fact-based inventory only; cite repo evidence for every target file" + constitution: + - "INPUT not artifact — resolve via lookup order (target repo, then change inputs/, then schema inputs/)" + - "If not found, generate once using templates/constitution-template.md and save to change inputs/" + plan: + - "All sections §0–§8; each phase uses Goal, Dependencies, Target files, Verification hooks" + tasks: + - "Use §0–§5 structure; prefer multipass (skeleton → payloads → orchestration) for large backlogs" + - "Task IDs: T<n>_<m>; pair implementation tasks with verification when constitution requires" + implementation: + - "Follow the implementation artifact instruction in schema.yaml exactly" + - "Working-folder mode: use project cwd; no fork, no draft PR (schema working_folder_repo)" + - "Default mode: Ask user for fork_repo_url at stage start; persist to inputs/jira.yaml" + - "Clone fork, create feature branch, apply FILE OPERATIONS to fork working copy (not upstream)" + - "Task-by-task execution with per-task user approval gate (one OAPE command per task)" + - "Execute unit test block per task (real go test); controller tasks must co-generate _test.go files following agents.md exemplar" + - "After all phases: commit, push feature branch, open draft PR on fork (head = feature branch, base = master/main)" + - "Close with implementation-report.md, implementation-checklist.md, optional adrs.md" diff --git a/openspec/schemas/openspec-agile-workflow/agents.md b/openspec/schemas/openspec-agile-workflow/agents.md new file mode 100644 index 000000000..083da12e6 --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/agents.md @@ -0,0 +1,354 @@ +# AGENTS.md + +This file provides guidance to AI agents working with **external-secrets-operator** for OpenShift. It uses **controller-runtime only** (no library-go). For architecture depth, see [ARCHITECTURE.md](ARCHITECTURE.md). For contribution process, see [CONTRIBUTING.md](CONTRIBUTING.md). + +## Docs Index + +Detailed domain-specific guidelines are in these files — read them before working in the corresponding area: + +- [ARCHITECTURE.md](ARCHITECTURE.md) — Bird's-eye view, code map, reconciliation flow +- [docs/security-guidelines.md](docs/security-guidelines.md) — Container security, RBAC, TLS, network policies, webhook security +- [docs/performance-guidelines.md](docs/performance-guidelines.md) — Cache architecture, watch predicates, reconciliation patterns, drift detection +- [docs/error-handling-guidelines.md](docs/error-handling-guidelines.md) — ReconcileError types, retry logic, status conditions, logging +- [docs/api-contracts-guidelines.md](docs/api-contracts-guidelines.md) — CRD types, kubebuilder markers, CEL validation, code generation +- [docs/testing-guidelines.md](docs/testing-guidelines.md) — Unit/API/E2E test tiers, frameworks, test helpers, CI integration +- [docs/integration-guidelines.md](docs/integration-guidelines.md) — Bindata pattern, cert-manager, OpenShift platform, Bitwarden plugin +- [docs/openspec-agile-workflow.md](docs/openspec-agile-workflow.md) — OpenSpec agile workflow (`/opsx-new`, `/opsx-continue`, `/opsx-apply`) +- [constitution.md](constitution.md) — Non-negotiable architectural guardrails (OpenSpec workflow input) + +## Project Overview + +This is a Kubernetes operator (built with kubebuilder/controller-runtime) that deploys and manages the upstream [external-secrets](https://github.com/openshift/external-secrets) application on OpenShift clusters. The operator does not embed upstream code — it manages upstream resources as static YAML manifests (bindata) applied imperatively. + +Three controllers run in a single binary: + +| Controller | Package | Watches | Purpose | +|---|---|---|---| +| `external-secrets-controller` | `pkg/controller/external_secrets/` | `ExternalSecretsConfig` CR + `ExternalSecretsManager` (spec) + managed resources (Deployments, RBAC, Services, Secrets metadata, ConfigMaps, NetworkPolicies, Webhooks) + conditionally `cert-manager.io/Certificate` | Installs/reconciles operand deployments, RBAC, services, webhooks, network policies | +| `external-secrets-manager` | `pkg/controller/external_secrets_manager/` | `ExternalSecretsManager` CR + `ExternalSecretsConfig` status | Aggregates controller statuses into a global status CR | +| `crd-annotator` | `pkg/controller/crd_annotator/` | ESO CRDs (metadata, label-filtered) + `ExternalSecretsConfig` | Adds cert-manager CA injection annotations to operand CRDs (conditional; only registered when cert-manager is installed) | + +**Startup order** (`pkg/operator/setup_manager.go`): ESM controller → operand controller → optional CRD annotator → `CreateDefaultESMResource()`. + +**Operand namespace:** `external-secrets` (`OperandDefaultNamespace`). + +## Project Structure + +```text +api/v1alpha1/ CRD type definitions, conditions, shared types, deepcopy +api/v1alpha1/tests/ Declarative YAML test suites for CRD validation +bindata/ Static operand YAML manifests (compiled into Go via go-bindata) +bundle/ OLM bundle manifests +cmd/external-secrets-operator/ Operator entrypoint (main.go, separate Go module) +docs/ Guideline docs (security, performance, error handling, etc.) +config/ Kustomize manifests (CRDs, RBAC, manager, samples, bundle) +hack/ Shell scripts (codegen, verification, CI helpers) +images/ci/ CI Dockerfiles (coverage-instrumented builds) +pkg/controller/ Controller implementations + client/ CtrlClient interface + counterfeiter fakes + common/ Shared utilities (errors, constants, decode helpers, drift detection) + commontest/ Shared test fixtures (TestExternalSecretsConfig, TestExternalSecretsManager) + crd_annotator/ CRD annotation controller + external_secrets/ Main operand reconciliation controller + external_secrets_manager/ Status aggregation controller +pkg/operator/ Manager setup, controller registration + assets/ Generated bindata (bindata.go) +pkg/version/ Build-time version info (ldflags) +test/apis/ API integration tests (Ginkgo + envtest) +test/e2e/ End-to-end tests (Ginkgo + live cluster) +test/utils/ E2E test helpers +tools/ Go module for build-time tool dependencies +vendor/ Workspace-level vendoring (go work vendor) +``` + +## Go Workspace and Module Layout + +The repo uses `go.work` with four modules: `.`, `./cmd/external-secrets-operator`, `./test`, `./tools`. This means: + +- `GOFLAGS` is cleared for test and fmt targets to avoid `-mod=vendor` conflicting with `go.work`. +- When adding a dependency, use `make update-dep PKG=pkg@version` to update across all modules, then `make update-vendor`. +- Vendoring is workspace-level (`go work vendor`), not per-module. +- All build-time tools (controller-gen, golangci-lint, ginkgo, etc.) are vendored and built from source via `go build -mod=vendor`. + +## Build System (Key Makefile Targets) + +| Target | What it does | +|---|---| +| `make build` | Full build: generate + manifests + fmt + vet + compile binary | +| `make build-operator` | Compile binary only (no codegen, fastest iteration) | +| `make test` | Run unit + API integration tests (no cluster needed) | +| `make test-unit` | Unit tests only | +| `make test-apis` | API validation tests via envtest | +| `make test-e2e` | E2E tests against a live cluster | +| `make lint` | Run golangci-lint with all configured linters | +| `make lint-fix` | Run golangci-lint with auto-fix | +| `make update` | Full regeneration: codegen + manifests + operand manifests + bindata + bundle + docs | +| `make verify` | Run vet + fmt + verify-deps + verify-bindata + verify-bindata-assets + verify-generated + govulncheck + check-git-diff | +| `make update-vendor` | Update vendor directory across all workspace modules | +| `make update-dep PKG=x@v` | Update a single dependency across all modules | +| `make manifests` | Regenerate CRD YAML, RBAC, webhook configs from kubebuilder markers | +| `make generate` | Regenerate DeepCopy methods | +| `make docs` | Regenerate API reference docs | +| `make clean` | Remove bin/, _output/, cover.out, dist/ | + +After any code change, run `make update && make verify` to ensure all generated files are consistent. CI runs `check-git-diff` which fails if generated files are stale. + +## Code Style and Formatting + +### Import Order + +Imports must follow the order enforced by `gci` in `.golangci.yml`: + +1. Standard library +2. Third-party packages +3. `github.com/openshift/external-secrets-operator` (project-local) +4. Blank imports, dot imports, aliases, local module + +### Linting + +The repo uses golangci-lint v2 with a comprehensive set of linters (see `.golangci.yml`). Key rules: + +- **depguard** blocks `math/rand` (use `math/rand/v2`), `github.com/sirupsen/logrus`, and `github.com/pkg/errors` (use `errors`/`fmt`). +- **kubeapilinter** runs only on `api/v1alpha1/*` files. Use `//nolint:kubeapilinter` with a comment for intentional deviations. +- **golines** max line length is 200 characters. +- **gofmt** rewrites `interface{}` to `any` and `a[b:len(a)]` to `a[b:]`. +- Generated files are excluded in `lax` mode. +- Test files have relaxed rules for `gocyclo`, `errcheck`, `gosec`, `forcetypeassert`, and others. + +### File Headers + +All `.go` files must include the Apache 2.0 license header from `hack/boilerplate.go.txt` (year 2025). + +### FIPS Build + +Production builds use `hack/go-fips.sh` which enables `GOEXPERIMENT=strictfipsruntime` and build tags `strictfipsruntime,openssl` when the Go compiler supports it. Local dev builds without FIPS work but cannot be used in CI/production. + +## Naming Conventions + +### Go Packages + +Controller packages use `snake_case`: `external_secrets`, `external_secrets_manager`, `crd_annotator`. This matches kubebuilder conventions. + +### Constants + +- Controller names: `"external-secrets-controller"`, `"external-secrets-manager"`, `"crd-annotator"` (kebab-case in strings). +- Finalizer format: `<crd-plural>.<api-group>/<controller-name>` (e.g., `externalsecretsconfigs.operator.openshift.io/external-secrets-controller`). +- Asset name constants: `<resourceKind>_<resourceName>AssetName` in camelCase (e.g., `controllerDeploymentAssetName`). +- Environment variable constants: all-caps with suffix `EnvVarName` (e.g., `externalSecretsImageEnvVarName`). + +### Bindata YAML Files + +Files in `bindata/external-secrets/resources/` follow the pattern: `<kind-lowercase>_<resource-name>.yml` (e.g., `deployment_external-secrets.yml`, `clusterrole_external-secrets-controller.yml`). Network policies in `bindata/external-secrets/` use `.yaml` extension. + +### CRD Object Names + +Both `ExternalSecretsConfig` and `ExternalSecretsManager` are singletons named `"cluster"` (enforced by CEL). Constants `ExternalSecretsConfigObjectName` and `ExternalSecretsManagerObjectName` in `pkg/controller/common/constants.go` hold this value. + +## Architectural Patterns + +### Reconciler Structure + +Every controller follows the same pattern: + +1. A `Reconciler` struct embedding `operatorclient.CtrlClient`. +2. A `New(ctx, mgr)` constructor that builds the reconciler and client(s). +3. A `SetupWithManager(mgr)` method that wires watches, predicates, and map functions. +4. A `Reconcile(ctx, req)` method that fetches the primary CR and delegates to `processReconcileRequest`. +5. An `updateStatus(ctx, obj)` method using `retry.RetryOnConflict`. + +Reconciliation uses **create-or-update with deep equality** (not SSA-first). Limited SSA (`client.Apply` with field owner) is allowed only for CR annotation patches. See [ARCHITECTURE.md](ARCHITECTURE.md) and [docs/performance-guidelines.md](docs/performance-guidelines.md). + +### CtrlClient Interface + +All controllers interact with Kubernetes through `pkg/controller/client.CtrlClient`, not the raw `controller-runtime client.Client`. This interface adds `UpdateWithRetry`, `StatusUpdate`, and `Exists` methods. Unit tests use counterfeiter-generated fakes (`pkg/controller/client/fakes/`). + +To regenerate fakes after changing the interface: `go generate ./pkg/controller/client/...` + +### Resource Reconciliation Pattern + +For each resource type (deployments, services, RBAC, etc.), the same flow is used: + +1. Decode static YAML from bindata (`common.Decode*ObjBytes(assets.MustAsset(...))`). +2. Mutate the decoded object (set namespace, labels, annotations, images, env vars, security context). +3. Check existence with `r.Exists()`. +4. If new: `r.Create()`. If existing: compare with `common.HasObjectChanged()`, then `r.UpdateWithRetry()` if drifted. +5. Record Kubernetes events for create/update operations. +6. Wrap errors with `common.FromClientError()`. + +### Conditional Resources + +Resources that depend on CR configuration use a slice of `{assetName string, condition bool}` structs. Only assets with `condition: true` are applied. Follow this pattern when adding new conditionally-created resources. + +## Operands and CRDs (quick reference) + +Full detail: [ARCHITECTURE.md](ARCHITECTURE.md), [docs/integration-guidelines.md](docs/integration-guidelines.md). + +| CRD | Scope | Singleton | Purpose | +|-----|-------|-----------|---------| +| `ExternalSecretsConfig` | Cluster | `cluster` | Operand configuration (webhooks, providers, deployment) | +| `ExternalSecretsManager` | Cluster | `cluster` | Global config, feature flags, status aggregation | + +**Operator-owned API:** `api/v1alpha1/`, group `operator.openshift.io`. **Upstream operand CRDs** (`external-secrets.io`, `generators.external-secrets.io`) ship via bindata/OLM — the operator deploys but does not own their API evolution. + +| Operand workload | Always? | Enable gate | Image env | +|------------------|---------|-------------|-----------| +| **external-secrets** (controller + webhook + cert-controller) | Yes | `ExternalSecretsConfig` managed | `RELATED_IMAGE_EXTERNAL_SECRETS` | +| **bitwarden-sdk-server** (plugin) | No | `plugins.bitwardenSecretManagerProvider.mode == Enabled` | `RELATED_IMAGE_BITWARDEN_SDK_SERVER` | + +**Webhook TLS (mutually exclusive):** cert-manager `Certificate` CRs when `certProvider.certManager.mode == Enabled`; otherwise in-tree `external-secrets-cert-controller` deployment. Never assume cert-manager is present. + +**Feature flags:** on `ExternalSecretsManager.Spec.Features[]` via `common.IsFeatureEnabled()` — not OpenShift cluster FeatureGate API. + +**Operand change tiers (for planning/tasks):** + +| Tier | Change | Key commands | +|------|--------|--------------| +| 1 | Deployment arg/env change | `make test-unit` | +| 2 | New optional plugin workload | API + bindata + conditional `createOrApplyDeployments` + e2e | +| 3 | Operand version bump | `make update-operand-manifests && make update-bindata && make verify` | +| 4 | Upstream CRD schema change | Comes from helm refresh (tier 3) | + +## Common Pitfalls + +1. **Never return both `RequeueAfter` and a non-nil error** from `Reconcile`. Return one or the other. +2. **Never edit generated files by hand**: `zz_generated.deepcopy.go`, `config/crd/bases/*.yaml`, `pkg/operator/assets/bindata.go`, `config/rbac/role.yaml`, `docs/api_reference.md`. Always use `make update`. +3. **Always run `make update && make verify`** after any code change. CI will reject PRs with stale generated files. +4. **Use the cached client for managed resources** (those with `app: external-secrets` label). Use the uncached client only for external resources like cert-manager Issuers or user-provided Secrets. +5. **Add new watched resources to both `controllerManagedResources` and `buildCacheObjectList()`** in `pkg/controller/external_secrets/controller.go`. +6. **Add new resource types to `HasObjectChanged`** in `pkg/controller/common/utils.go` with field-level comparison, not full `DeepEqual`. +7. **Decode helpers panic on failure** — this is intentional for build-time-constant assets. Do not add error handling around `Decode*ObjBytes` calls. +8. **RBAC markers** (`+kubebuilder:rbac`) go in `pkg/controller/external_secrets/controller.go` for the operator's own permissions. Operand RBAC is in static YAML under `bindata/`. +9. **The `go.work` workspace** means you cannot use `-mod=vendor` for `go test` or `go fmt`. The Makefile already clears `GOFLAGS` for affected targets. +10. **Container tool defaults to `podman`** (`CONTAINER_TOOL ?= podman`), not Docker. Override with `CONTAINER_TOOL=docker` if needed. +11. **Do NOT use SSA-first operand patterns** from other operators — this repo uses create-or-update. +12. **Do NOT create separate `ctrl.Manager` instances** — register in `setup_manager.go`. + +## PR and Contribution Expectations + +- Run `make lint` and `make test` locally before submitting. +- Run `make verify` to ensure generated files are in sync. +- Add unit tests for new reconciliation logic using table-driven tests and `FakeCtrlClient`. +- Add API test cases in `api/v1alpha1/tests/<crd-api-group-domain>/` (e.g., `externalsecretsconfig.operator.openshift.io/`) for any new CRD field or validation rule. +- Add E2E test cases with appropriate Ginkgo labels for platform-specific tests. +- Follow existing error wrapping patterns: `common.FromClientError` for API calls, `common.NewIrrecoverableError` for config validation failures, `common.NewUserConfigurationError` for invalid user-provided configuration. +- Commit messages should reference the relevant Jira ticket (e.g., `OCPBUGS-12345: description`). +- PR reviewers/approvers are listed in `OWNERS`. + +## Environment Variables + +The operator reads these at runtime (typically set by OLM/CSV): + +| Variable | Purpose | +|---|---| +| `RELATED_IMAGE_EXTERNAL_SECRETS` | Container image for the external-secrets operand | +| `RELATED_IMAGE_BITWARDEN_SDK_SERVER` | Container image for the Bitwarden SDK server | +| `OPERAND_EXTERNAL_SECRETS_IMAGE_VERSION` | Version label for operand resources | +| `BITWARDEN_SDK_SERVER_IMAGE_VERSION` | Version label for Bitwarden resources | +| `OPERATOR_IMAGE_VERSION` | Operator version string | +| `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` | Proxy fallback from OLM environment | + +For local development, set at minimum `RELATED_IMAGE_EXTERNAL_SECRETS` via `t.Setenv()` in tests or shell export for `make run`. + +--- + +## Per-task testing during `/opsx-apply` (code generation eval gate) + +During implementation, each code generation task is verified with **real command execution**. See `openspec/schemas/openspec-agile-workflow/stage-gate/CODE_GENERATION_EVAL_PROMPT.md`. + +| Task type | Verification | Test strategy | +|-----------|-------------|---------------| +| API types | `go build`, `go vet` | `make test-apis` after testsuite YAML | +| Codegen | `make generate && make manifests && make verify` | Consistency check | +| Controller logic | `go test ./pkg/controller/...` | Co-generated `_test.go` with `FakeCtrlClient` | +| Operand manifests | `make update-operand-manifests && make update-bindata && make verify` | `make verify-bindata` | +| OLM bundle | `make bundle` | Bundle validation | +| ESM / feature flags | `go test ./pkg/controller/external_secrets_manager/...` | Unit tests for feature wiring | + +--- + +## Execution agent routing + +Use these **Assigned Agent** IDs in `tasks.md` §3 when **`AgentRoutingMode: PROVIDED`**. Each task gets exactly one primary agent. + +| Agent ID | Scope | Route when task touches | OAPE / execution | +|----------|-------|-------------------------|------------------| +| **API_Agent** | CRD/API types, markers, `.testsuite.yaml` | `api/v1alpha1/`, `api/v1alpha1/tests/` | `api-generate` or `api-generate-tests` (verification-only) | +| **OperatorController_Agent** | Reconciliation, operand install, wiring | `pkg/controller/external_secrets/`, `pkg/controller/external_secrets_manager/`, `pkg/controller/crd_annotator/`, `pkg/operator/setup_manager.go`, `cmd/external-secrets-operator/` | `api-implement` | +| **ManifestsBindata_Agent** | Operand YAML, version pins, upstream CRDs | `bindata/`, `hack/update-external-secrets-manifests.sh`, `Makefile` `EXTERNAL_SECRETS_VERSION`, `config/crd/bases/customresourcedefinition_*` | Manual — `make update-operand-manifests`, `make update-bindata` | +| **BitwardenPlugin_Agent** | Bitwarden SDK server plugin workload | `deployment_bitwarden-sdk-server.yml` reconcilers in `deployments.go`, `Plugins.BitwardenSecretManagerProvider` API, bitwarden network policy/certificate assets | Manual — follow bitwarden patterns in `deployments.go` | +| **WebhookTLS_Agent** | Webhook TLS, cert-manager certificates | Webhook deployments, `certificates.go`, trusted CA ConfigMap | Manual | +| **RBACSecurity_Agent** | RBAC, network policies | `config/rbac/`, `rbacs.go`, `networkpolicy.go` | Manual | +| **OLMRelease_Agent** | OLM bundle, CSV, relatedImages | `config/manifests/`, `bundle/`, Makefile bundle targets | Manual — `make bundle` | +| **Testing_Agent** | E2E and API test authoring | `test/e2e/`, `test/apis/`, `test/utils/` | `e2e-generate` when task is e2e | +| **Docs_Agent** | User-facing docs | `README.md`, `docs/` | Manual | + +### Controller routing rules + +- **Operand controller** (`pkg/controller/external_secrets/`): create-or-update reconcilers; follow `install_external_secrets.go` ordering +- **ESM controller** (`pkg/controller/external_secrets_manager/`): status aggregation, default ESM creation, feature flags +- **CRD annotator** (`pkg/controller/crd_annotator/`): only when cert-manager is installed +- **API before controller**: CRD field tasks must complete (and pass `make test-apis`) before controller tasks that reconcile those fields + +### Verification pairing + +- API changes → pair with `api/v1alpha1/tests/*.testsuite.yaml` tasks (`API_Agent`, verification-only) +- Controller changes → pair with unit tests (`make test-unit`) and e2e when user-visible (`Testing_Agent`) +- Operand version bumps → pair with `make verify` and relevant e2e smoke paths + +--- + +## Stage-Specific Agent Guidance + +### Repo-Assessment Stage Hints + +When assessing `external-secrets-operator`, document: + +- **Single manager, three reconcilers** — `external_secrets_manager`, `external_secrets`, optional `crd_annotator` +- **Create-or-update** operand pattern — not SSA-first (limited SSA for annotations only) +- **Two operator CRDs** — `ExternalSecretsConfig` + `ExternalSecretsManager` (both singleton `cluster`) +- **Two operand workloads:** external-secrets core (always) + bitwarden-sdk-server plugin (conditional on CR spec) +- **Three core deployments:** `external-secrets`, `external-secrets-webhook`, `external-secrets-cert-controller` (cert-controller skipped when cert-manager TLS enabled) +- **Upstream operand CRDs** (`external-secrets.io`, `generators.external-secrets.io`) vs operator CRDs (`operator.openshift.io`) +- **Image env vars:** `RELATED_IMAGE_EXTERNAL_SECRETS`, `RELATED_IMAGE_BITWARDEN_SDK_SERVER` (+ version env vars) +- **Feature flags on ESM CR** — not OpenShift FeatureSet gates +- **cert-manager optional** — webhook TLS path + CRD annotator when cert-manager CRD exists +- **Test commands:** `make test-unit`, `make test-apis`, `make test-e2e` +- **FIPS build** via `hack/go-fips.sh` + +Anti-patterns (forbidden without branch evidence): + +- Claiming library-go or SSA-first addon patterns apply to this repo +- Using OpenShift FeatureGate / TechPreview cluster discovery patterns from other operators +- Documenting operand controllers or CRDs that do not exist on the target branch + +### Planning Stage Hints + +Prefer operator-native thinking for external-secrets: + +- `ExternalSecretsConfig` / `ExternalSecretsManager` API evolution +- **Core operand** deployment overrides (`componentConfigs`, log level, operating namespace, trusted CA bundle) +- **Bitwarden plugin** enablement (`plugins.bitwardenSecretManagerProvider`) and TLS prerequisites +- Webhook TLS path selection (cert-manager vs in-tree cert-controller) +- Bindata/helm manifest pipeline (`EXTERNAL_SECRETS_VERSION`) +- Provider integrations (Bitwarden SDK sidecar; AWS/GCP paths in e2e) +- RBAC blast radius (secrets, cluster-scoped operand CRDs) +- OLM bundle and `RELATED_IMAGE_*` env vars + +Default repo pin when none provided: + +```text +primary_repo: "https://github.com/openshift/external-secrets-operator" +branch: "main" +``` + +### Validation Stage Hints + +When the spec touches operators, secrets, webhooks, providers, or OpenShift platform integration, assess: + +- API & CRD lifecycle (singleton rules, immutability, CEL validation) +- Install / uninstall / reconcile semantics (finalizers, operand namespace) +- RBAC & blast radius (secret access, cluster-scoped writes) +- Webhook TLS (cert-manager vs in-tree cert-controller) +- Provider matrix (Bitwarden, AWS, GCP — see e2e labels) +- Observability (Ready/Degraded conditions on ESC) +- Upgrade / operand version skew (`EXTERNAL_SECRETS_VERSION`) diff --git a/openspec/schemas/openspec-agile-workflow/evals/README.md b/openspec/schemas/openspec-agile-workflow/evals/README.md new file mode 100644 index 000000000..3d2448de6 --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/evals/README.md @@ -0,0 +1,20 @@ +# Stage evals — openspec-agile-workflow forward workflow + +Merged eval rubrics for `/opsx-continue` artifact gates. Installed to: + +`openspec/schemas/openspec-agile-workflow/evals/` + +| File | Stage | +|------|-------| +| `repo-assessment_eval.yaml` | repo-assessment | +| `constitution_eval.yaml` | constitution | +| `plan_eval.yaml` | plan | +| `tasks_eval.yaml` | tasks | +| `implementation_eval.yaml` | implementation | +| `code-generation_eval.yaml` | code-generation (per-task during /opsx-apply) | + +Assertion schemas: `evals/stages/<stage>/eval-spec.yaml` +Gate instructions: `../stage-gate/SYSTEM_PROMPT.md` + +**Not** under `evals/baseline/` — that path is for `/eval-loop` retrospective history only. +When `/eval-loop` merges new cases, it syncs updated `*_eval.yaml` files here and to `evals/baseline/evals/`. diff --git a/openspec/schemas/openspec-agile-workflow/evals/code-generation_eval.yaml b/openspec/schemas/openspec-agile-workflow/evals/code-generation_eval.yaml new file mode 100644 index 000000000..7797e6d3a --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/evals/code-generation_eval.yaml @@ -0,0 +1,4 @@ +stage: code-generation +template: null +note: Per-task code evals for /opsx-apply; cases tagged oape_command +evals: [] diff --git a/openspec/schemas/openspec-agile-workflow/evals/constitution_eval.yaml b/openspec/schemas/openspec-agile-workflow/evals/constitution_eval.yaml new file mode 100644 index 000000000..d8fbaade9 --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/evals/constitution_eval.yaml @@ -0,0 +1,3 @@ +stage: constitution +template: templates/constitution-template.md +evals: [] diff --git a/openspec/schemas/openspec-agile-workflow/evals/implementation_eval.yaml b/openspec/schemas/openspec-agile-workflow/evals/implementation_eval.yaml new file mode 100644 index 000000000..49d871d0d --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/evals/implementation_eval.yaml @@ -0,0 +1,3 @@ +stage: implementation +template: templates/implementation-template.md +evals: [] diff --git a/openspec/schemas/openspec-agile-workflow/evals/plan_eval.yaml b/openspec/schemas/openspec-agile-workflow/evals/plan_eval.yaml new file mode 100644 index 000000000..f762e64bd --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/evals/plan_eval.yaml @@ -0,0 +1,3 @@ +stage: plan +template: templates/plan-template.md +evals: [] diff --git a/openspec/schemas/openspec-agile-workflow/evals/repo-assessment_eval.yaml b/openspec/schemas/openspec-agile-workflow/evals/repo-assessment_eval.yaml new file mode 100644 index 000000000..5949bede8 --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/evals/repo-assessment_eval.yaml @@ -0,0 +1,3 @@ +stage: repo-assessment +template: templates/repo-assessment-template.md +evals: [] diff --git a/openspec/schemas/openspec-agile-workflow/evals/stages/code-generation/eval-spec.yaml b/openspec/schemas/openspec-agile-workflow/evals/stages/code-generation/eval-spec.yaml new file mode 100644 index 000000000..7c835e6b3 --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/evals/stages/code-generation/eval-spec.yaml @@ -0,0 +1,96 @@ +# Eval case schema — code-generation stage (/opsx-apply per-task gate) + +stage: code-generation +artifact: generated or modified code in fork working copy (after OAPE command or manual agent work) +stage_eval_file: evals/code-generation_eval.yaml + +description: >- + Scores generated code per task during /opsx-apply. Cases are tagged with + oape_command so only relevant evals run for the current task. Authored + retrospectively by /eval-loop from EPs, PRs, and bug postmortems. + +consolidated_schema: + stage: code-generation + artifact: fork working copy (code + tests) + version: 1 + eval_count: <N> + oape_commands: + - api-generate + - api-generate-tests + - api-implement + - e2e-generate + - manual + evals: [<case>, ...] + +required_fields: + - id + - round + - stage + - oape_command + - source_issue_ids + - input_refs + - prompt + - assertions + - scoring + +oape_command_values: + api-generate: API type / CRD generation tasks + api-generate-tests: .testsuite.yaml / API integration test tasks + api-implement: Controller / operator reconciliation tasks + e2e-generate: E2E test artifact tasks + manual: ManifestsBindata, RBAC, OLM, Docs, WebhookTLS agents (no OAPE command) + +assertion_types: + - must_use_pattern + - must_not_use + - must_pass_make_targets + - must_match_task_payload + - files_must_exist + - files_must_not_exist + - must_follow_constitution + - must_follow_effective_go + - must_include_tests + - must_not_violate_non_goals + - must_execute_verification + - must_co_generate_tests + +assertion_descriptions: + must_execute_verification: >- + All verification commands (go build, go vet, make targets) from + CODE_GENERATION_EVAL_PROMPT step 2 executed with exit code 0. + Enforced via real shell execution — agent assertions alone do not satisfy this. + must_co_generate_tests: >- + Controller logic tasks (api-implement modifying pkg/controller/) must produce + _test.go files following established test patterns: table-driven tests using + appropriate fakes/mocks, 1:1 mapping of production files to test files. Tests must + compile and pass via go test. + +filter_rule_forward: >- + During /opsx-apply, load evals/code-generation_eval.yaml and score only cases + where oape_command matches the resolved command for the current task (use + manual when no OAPE command). Always include cases with oape_command: any if present. + +example: + id: eval-r001-codegen-001 + round: 1 + stage: code-generation + oape_command: api-implement + source_issue_ids: [PROJ-123] + patterns: [PAT-001] + input_refs: + - evals/inputs/05-repo-prs.md + - evals/inputs/bugs/PROJ-123.md + prompt: | + Controller reconciliation must follow the established pattern for status updates. + assertions: + must_use_pattern: + - ReconcileResult + must_not_use: + - client.Create + - client.Update + must_pass_make_targets: + - make test + must_match_task_payload: true + scoring: + method: weighted_checklist + pass_threshold: 85 diff --git a/openspec/schemas/openspec-agile-workflow/evals/stages/constitution/eval-spec.yaml b/openspec/schemas/openspec-agile-workflow/evals/stages/constitution/eval-spec.yaml new file mode 100644 index 000000000..d6cb77b7c --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/evals/stages/constitution/eval-spec.yaml @@ -0,0 +1,45 @@ +# Eval case schema — constitution stage + +stage: constitution +template: templates/constitution-template.md +artifact: constitution.md +stage_eval_file: evals/constitution_eval.yaml + +consolidated_schema: + stage: constitution + template: templates/constitution-template.md + artifact: constitution.md + version: 1 + eval_count: <N> + evals: [<case>, ...] + +required_fields: + - id + - round + - stage + - source_issue_ids + - input_refs + - assertions + - scoring + - pass_threshold + +assertion_types: + - must_cite_repo_evidence + - must_set_agent_routing_mode + - must_align_with_agents_md + - must_not_duplicate_repo_assessment + - must_mention + +example: + id: eval-r001-const-001 + round: 1 + stage: constitution + source_issue_ids: [ISSUE-001] + input_refs: + - evals/baseline/routing-learnings.md + assertions: + must_cite_repo_evidence: true + must_set_agent_routing_mode: [PROVIDED, PROVISIONAL] + scoring: + method: weighted_checklist + pass_threshold: 80 diff --git a/openspec/schemas/openspec-agile-workflow/evals/stages/implementation/eval-spec.yaml b/openspec/schemas/openspec-agile-workflow/evals/stages/implementation/eval-spec.yaml new file mode 100644 index 000000000..558dd97df --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/evals/stages/implementation/eval-spec.yaml @@ -0,0 +1,43 @@ +# Eval case schema — implementation stage + +stage: implementation +template: templates/implementation-template.md +artifact: code changes + implementation-report.md +stage_eval_file: evals/implementation_eval.yaml + +consolidated_schema: + stage: implementation + template: templates/implementation-template.md + artifact: code + phase FILE OPS + version: 1 + eval_count: <N> + evals: [<case>, ...] + +required_fields: + - id + - round + - stage + - source_issue_ids + - input_refs + - assertions + - scoring + - pass_threshold + +assertion_types: + - must_follow_constitution + - must_use_ssa_not_create_or_update + - must_match_task_payloads + - must_include_tests + - must_not_violate_non_goals + +example: + id: eval-r001-impl-001 + round: 1 + stage: implementation + source_issue_ids: [ISSUE-001] + assertions: + must_use_ssa_not_create_or_update: true + must_match_task_payloads: true + scoring: + method: weighted_checklist + pass_threshold: 80 diff --git a/openspec/schemas/openspec-agile-workflow/evals/stages/plan/eval-spec.yaml b/openspec/schemas/openspec-agile-workflow/evals/stages/plan/eval-spec.yaml new file mode 100644 index 000000000..e2b82d7d8 --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/evals/stages/plan/eval-spec.yaml @@ -0,0 +1,43 @@ +# Eval case schema — plan stage + +stage: plan +template: templates/plan-template.md +artifact: plan.md +stage_eval_file: evals/plan_eval.yaml + +consolidated_schema: + stage: plan + template: templates/plan-template.md + artifact: plan.md + version: 1 + eval_count: <N> + evals: [<case>, ...] + +required_fields: + - id + - round + - stage + - source_issue_ids + - input_refs + - assertions + - scoring + - pass_threshold + +assertion_types: + - must_cover_fr_ids + - must_include_verification_matrix + - must_cite_repo_assessment + - must_address_risk_from_ep + - must_phase_structure + +example: + id: eval-r001-plan-001 + round: 1 + stage: plan + source_issue_ids: [ISSUE-001] + assertions: + must_include_verification_matrix: true + must_cite_repo_assessment: true + scoring: + method: weighted_checklist + pass_threshold: 80 diff --git a/openspec/schemas/openspec-agile-workflow/evals/stages/repo-assessment/eval-spec.yaml b/openspec/schemas/openspec-agile-workflow/evals/stages/repo-assessment/eval-spec.yaml new file mode 100644 index 000000000..1b65608e2 --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/evals/stages/repo-assessment/eval-spec.yaml @@ -0,0 +1,50 @@ +# Eval case schema — repo-assessment stage + +stage: repo-assessment +template: templates/repo-assessment-template.md +artifact: repo-assessment.md +stage_eval_file: evals/repo-assessment_eval.yaml + +# All cases for this stage live in ONE file: repo-assessment_eval.yaml +consolidated_schema: + stage: repo-assessment + template: templates/repo-assessment-template.md + artifact: repo-assessment.md + version: 1 + eval_count: <N> + evals: [<case>, ...] + +required_fields: + - id + - round + - stage + - source_issue_ids + - input_refs + - assertions + - scoring + - pass_threshold + +assertion_types: + - must_mention + - must_identify_package + - must_cite_pre_feature_gap + - must_cross_reference_spec + - must_not_claim + +example: + id: eval-r001-repo-001 + round: 1 + stage: repo-assessment + source_issue_ids: [ISSUE-001] + input_refs: + - evals/inputs/03-original-repo.md + - evals/inputs/01-ep-ard.md + prompt: | + Given approved specs and pinned pre-feature repo, produce repo-assessment.md. + assertions: + must_mention: + - target controller package path + must_cite_pre_feature_gap: true + scoring: + method: weighted_checklist + pass_threshold: 80 diff --git a/openspec/schemas/openspec-agile-workflow/evals/stages/tasks/eval-spec.yaml b/openspec/schemas/openspec-agile-workflow/evals/stages/tasks/eval-spec.yaml new file mode 100644 index 000000000..06f7f010e --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/evals/stages/tasks/eval-spec.yaml @@ -0,0 +1,43 @@ +# Eval case schema — tasks stage + +stage: tasks +template: templates/tasks-template.md +artifact: tasks.md +stage_eval_file: evals/tasks_eval.yaml + +consolidated_schema: + stage: tasks + template: templates/tasks-template.md + artifact: tasks.md + version: 1 + eval_count: <N> + evals: [<case>, ...] + +required_fields: + - id + - round + - stage + - source_issue_ids + - input_refs + - assertions + - scoring + - pass_threshold + +assertion_types: + - must_map_all_fr_sc + - must_pair_verification_tasks + - must_use_valid_agent_ids + - must_include_payload_sections + - must_reference_plan_phases + +example: + id: eval-r001-tasks-001 + round: 1 + stage: tasks11BLXLMQA0z9mbtgESBEBe_4aMi02hSahBitFt4Uvd7yftgV5jaYd5JRvy0VZb6NGO3SFLIV3KJVcaHZhA + source_issue_ids: [ISSUE-001] + assertions: + must_pair_verification_tasks: true + must_map_all_fr_sc: true + scoring: + method: weighted_checklist + pass_threshold: 80 diff --git a/openspec/schemas/openspec-agile-workflow/evals/tasks_eval.yaml b/openspec/schemas/openspec-agile-workflow/evals/tasks_eval.yaml new file mode 100644 index 000000000..561ef159b --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/evals/tasks_eval.yaml @@ -0,0 +1,3 @@ +stage: tasks +template: templates/tasks-template.md +evals: [] diff --git a/openspec/schemas/openspec-agile-workflow/feedback_stage_artifacts/README.md b/openspec/schemas/openspec-agile-workflow/feedback_stage_artifacts/README.md new file mode 100644 index 000000000..22e7e85b0 --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/feedback_stage_artifacts/README.md @@ -0,0 +1,66 @@ +# Feedback stage artifacts — user rejection loop + +When the user **rejects with feedback** at an artifact approval gate (`/opsx-continue`), +the agent runs the feedback stage (`user_approval_feedback_gate` in `schema.yaml`, +`stage-gate/USER_FEEDBACK_PROMPT.md`). + +## Runtime summaries (per change) + +Write one file per feedback round: + +``` +openspec/changes/<change-name>/feedback_stage_artifacts/<artifact-id>/round-<N>.yaml +``` + +Co-generated gates (`repo-assessment` + `constitution`) use one shared round file per rejection: + +``` +openspec/changes/<change-name>/feedback_stage_artifacts/repo-assessment+constitution/round-<N>.yaml +``` + +## Round file schema + +```yaml +round: 1 +artifact_ids: [repo-assessment, constitution] +timestamp: <ISO8601> +user_feedback: | + Verbatim rejection feedback from the user. + +context: + prior_artifacts_read_only: + - openspec/changes/<change>/specs.md + current_artifacts: + - openspec/changes/<change>/repo-assessment.md + - openspec/changes/<change>/constitution.md + template: templates/repo-assessment-template.md + +template_update: + required: true + path: templates/repo-assessment-template.md + summary: | + Added In scope vs out of scope section to template skeleton. + +artifact_regeneration: + paths: + - openspec/changes/<change>/repo-assessment.md + - openspec/changes/<change>/constitution.md + summary: | + Reframed §0 against RFE first-release scope; added scope table. + +eval_gate: + rerun: true + results_path: openspec/changes/<change>/eval-results/repo-assessment.yaml + overall_score: 88 + overall_pass: true + +feedback_addressed: + - "User: unclear TP scope → Added In scope / Out of scope table in §0" + - "User: delta/hardening framing → Reframed against RFE recommendation" +``` + +## This directory + +The `feedback_stage_artifacts/` folder under `{schema_root}` holds this README and format +spec only. **Do not** store change-specific summaries here — they live under each change +(see paths above) so they survive schema reinstall. diff --git a/openspec/schemas/openspec-agile-workflow/inputs/agents.md b/openspec/schemas/openspec-agile-workflow/inputs/agents.md new file mode 100644 index 000000000..083da12e6 --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/inputs/agents.md @@ -0,0 +1,354 @@ +# AGENTS.md + +This file provides guidance to AI agents working with **external-secrets-operator** for OpenShift. It uses **controller-runtime only** (no library-go). For architecture depth, see [ARCHITECTURE.md](ARCHITECTURE.md). For contribution process, see [CONTRIBUTING.md](CONTRIBUTING.md). + +## Docs Index + +Detailed domain-specific guidelines are in these files — read them before working in the corresponding area: + +- [ARCHITECTURE.md](ARCHITECTURE.md) — Bird's-eye view, code map, reconciliation flow +- [docs/security-guidelines.md](docs/security-guidelines.md) — Container security, RBAC, TLS, network policies, webhook security +- [docs/performance-guidelines.md](docs/performance-guidelines.md) — Cache architecture, watch predicates, reconciliation patterns, drift detection +- [docs/error-handling-guidelines.md](docs/error-handling-guidelines.md) — ReconcileError types, retry logic, status conditions, logging +- [docs/api-contracts-guidelines.md](docs/api-contracts-guidelines.md) — CRD types, kubebuilder markers, CEL validation, code generation +- [docs/testing-guidelines.md](docs/testing-guidelines.md) — Unit/API/E2E test tiers, frameworks, test helpers, CI integration +- [docs/integration-guidelines.md](docs/integration-guidelines.md) — Bindata pattern, cert-manager, OpenShift platform, Bitwarden plugin +- [docs/openspec-agile-workflow.md](docs/openspec-agile-workflow.md) — OpenSpec agile workflow (`/opsx-new`, `/opsx-continue`, `/opsx-apply`) +- [constitution.md](constitution.md) — Non-negotiable architectural guardrails (OpenSpec workflow input) + +## Project Overview + +This is a Kubernetes operator (built with kubebuilder/controller-runtime) that deploys and manages the upstream [external-secrets](https://github.com/openshift/external-secrets) application on OpenShift clusters. The operator does not embed upstream code — it manages upstream resources as static YAML manifests (bindata) applied imperatively. + +Three controllers run in a single binary: + +| Controller | Package | Watches | Purpose | +|---|---|---|---| +| `external-secrets-controller` | `pkg/controller/external_secrets/` | `ExternalSecretsConfig` CR + `ExternalSecretsManager` (spec) + managed resources (Deployments, RBAC, Services, Secrets metadata, ConfigMaps, NetworkPolicies, Webhooks) + conditionally `cert-manager.io/Certificate` | Installs/reconciles operand deployments, RBAC, services, webhooks, network policies | +| `external-secrets-manager` | `pkg/controller/external_secrets_manager/` | `ExternalSecretsManager` CR + `ExternalSecretsConfig` status | Aggregates controller statuses into a global status CR | +| `crd-annotator` | `pkg/controller/crd_annotator/` | ESO CRDs (metadata, label-filtered) + `ExternalSecretsConfig` | Adds cert-manager CA injection annotations to operand CRDs (conditional; only registered when cert-manager is installed) | + +**Startup order** (`pkg/operator/setup_manager.go`): ESM controller → operand controller → optional CRD annotator → `CreateDefaultESMResource()`. + +**Operand namespace:** `external-secrets` (`OperandDefaultNamespace`). + +## Project Structure + +```text +api/v1alpha1/ CRD type definitions, conditions, shared types, deepcopy +api/v1alpha1/tests/ Declarative YAML test suites for CRD validation +bindata/ Static operand YAML manifests (compiled into Go via go-bindata) +bundle/ OLM bundle manifests +cmd/external-secrets-operator/ Operator entrypoint (main.go, separate Go module) +docs/ Guideline docs (security, performance, error handling, etc.) +config/ Kustomize manifests (CRDs, RBAC, manager, samples, bundle) +hack/ Shell scripts (codegen, verification, CI helpers) +images/ci/ CI Dockerfiles (coverage-instrumented builds) +pkg/controller/ Controller implementations + client/ CtrlClient interface + counterfeiter fakes + common/ Shared utilities (errors, constants, decode helpers, drift detection) + commontest/ Shared test fixtures (TestExternalSecretsConfig, TestExternalSecretsManager) + crd_annotator/ CRD annotation controller + external_secrets/ Main operand reconciliation controller + external_secrets_manager/ Status aggregation controller +pkg/operator/ Manager setup, controller registration + assets/ Generated bindata (bindata.go) +pkg/version/ Build-time version info (ldflags) +test/apis/ API integration tests (Ginkgo + envtest) +test/e2e/ End-to-end tests (Ginkgo + live cluster) +test/utils/ E2E test helpers +tools/ Go module for build-time tool dependencies +vendor/ Workspace-level vendoring (go work vendor) +``` + +## Go Workspace and Module Layout + +The repo uses `go.work` with four modules: `.`, `./cmd/external-secrets-operator`, `./test`, `./tools`. This means: + +- `GOFLAGS` is cleared for test and fmt targets to avoid `-mod=vendor` conflicting with `go.work`. +- When adding a dependency, use `make update-dep PKG=pkg@version` to update across all modules, then `make update-vendor`. +- Vendoring is workspace-level (`go work vendor`), not per-module. +- All build-time tools (controller-gen, golangci-lint, ginkgo, etc.) are vendored and built from source via `go build -mod=vendor`. + +## Build System (Key Makefile Targets) + +| Target | What it does | +|---|---| +| `make build` | Full build: generate + manifests + fmt + vet + compile binary | +| `make build-operator` | Compile binary only (no codegen, fastest iteration) | +| `make test` | Run unit + API integration tests (no cluster needed) | +| `make test-unit` | Unit tests only | +| `make test-apis` | API validation tests via envtest | +| `make test-e2e` | E2E tests against a live cluster | +| `make lint` | Run golangci-lint with all configured linters | +| `make lint-fix` | Run golangci-lint with auto-fix | +| `make update` | Full regeneration: codegen + manifests + operand manifests + bindata + bundle + docs | +| `make verify` | Run vet + fmt + verify-deps + verify-bindata + verify-bindata-assets + verify-generated + govulncheck + check-git-diff | +| `make update-vendor` | Update vendor directory across all workspace modules | +| `make update-dep PKG=x@v` | Update a single dependency across all modules | +| `make manifests` | Regenerate CRD YAML, RBAC, webhook configs from kubebuilder markers | +| `make generate` | Regenerate DeepCopy methods | +| `make docs` | Regenerate API reference docs | +| `make clean` | Remove bin/, _output/, cover.out, dist/ | + +After any code change, run `make update && make verify` to ensure all generated files are consistent. CI runs `check-git-diff` which fails if generated files are stale. + +## Code Style and Formatting + +### Import Order + +Imports must follow the order enforced by `gci` in `.golangci.yml`: + +1. Standard library +2. Third-party packages +3. `github.com/openshift/external-secrets-operator` (project-local) +4. Blank imports, dot imports, aliases, local module + +### Linting + +The repo uses golangci-lint v2 with a comprehensive set of linters (see `.golangci.yml`). Key rules: + +- **depguard** blocks `math/rand` (use `math/rand/v2`), `github.com/sirupsen/logrus`, and `github.com/pkg/errors` (use `errors`/`fmt`). +- **kubeapilinter** runs only on `api/v1alpha1/*` files. Use `//nolint:kubeapilinter` with a comment for intentional deviations. +- **golines** max line length is 200 characters. +- **gofmt** rewrites `interface{}` to `any` and `a[b:len(a)]` to `a[b:]`. +- Generated files are excluded in `lax` mode. +- Test files have relaxed rules for `gocyclo`, `errcheck`, `gosec`, `forcetypeassert`, and others. + +### File Headers + +All `.go` files must include the Apache 2.0 license header from `hack/boilerplate.go.txt` (year 2025). + +### FIPS Build + +Production builds use `hack/go-fips.sh` which enables `GOEXPERIMENT=strictfipsruntime` and build tags `strictfipsruntime,openssl` when the Go compiler supports it. Local dev builds without FIPS work but cannot be used in CI/production. + +## Naming Conventions + +### Go Packages + +Controller packages use `snake_case`: `external_secrets`, `external_secrets_manager`, `crd_annotator`. This matches kubebuilder conventions. + +### Constants + +- Controller names: `"external-secrets-controller"`, `"external-secrets-manager"`, `"crd-annotator"` (kebab-case in strings). +- Finalizer format: `<crd-plural>.<api-group>/<controller-name>` (e.g., `externalsecretsconfigs.operator.openshift.io/external-secrets-controller`). +- Asset name constants: `<resourceKind>_<resourceName>AssetName` in camelCase (e.g., `controllerDeploymentAssetName`). +- Environment variable constants: all-caps with suffix `EnvVarName` (e.g., `externalSecretsImageEnvVarName`). + +### Bindata YAML Files + +Files in `bindata/external-secrets/resources/` follow the pattern: `<kind-lowercase>_<resource-name>.yml` (e.g., `deployment_external-secrets.yml`, `clusterrole_external-secrets-controller.yml`). Network policies in `bindata/external-secrets/` use `.yaml` extension. + +### CRD Object Names + +Both `ExternalSecretsConfig` and `ExternalSecretsManager` are singletons named `"cluster"` (enforced by CEL). Constants `ExternalSecretsConfigObjectName` and `ExternalSecretsManagerObjectName` in `pkg/controller/common/constants.go` hold this value. + +## Architectural Patterns + +### Reconciler Structure + +Every controller follows the same pattern: + +1. A `Reconciler` struct embedding `operatorclient.CtrlClient`. +2. A `New(ctx, mgr)` constructor that builds the reconciler and client(s). +3. A `SetupWithManager(mgr)` method that wires watches, predicates, and map functions. +4. A `Reconcile(ctx, req)` method that fetches the primary CR and delegates to `processReconcileRequest`. +5. An `updateStatus(ctx, obj)` method using `retry.RetryOnConflict`. + +Reconciliation uses **create-or-update with deep equality** (not SSA-first). Limited SSA (`client.Apply` with field owner) is allowed only for CR annotation patches. See [ARCHITECTURE.md](ARCHITECTURE.md) and [docs/performance-guidelines.md](docs/performance-guidelines.md). + +### CtrlClient Interface + +All controllers interact with Kubernetes through `pkg/controller/client.CtrlClient`, not the raw `controller-runtime client.Client`. This interface adds `UpdateWithRetry`, `StatusUpdate`, and `Exists` methods. Unit tests use counterfeiter-generated fakes (`pkg/controller/client/fakes/`). + +To regenerate fakes after changing the interface: `go generate ./pkg/controller/client/...` + +### Resource Reconciliation Pattern + +For each resource type (deployments, services, RBAC, etc.), the same flow is used: + +1. Decode static YAML from bindata (`common.Decode*ObjBytes(assets.MustAsset(...))`). +2. Mutate the decoded object (set namespace, labels, annotations, images, env vars, security context). +3. Check existence with `r.Exists()`. +4. If new: `r.Create()`. If existing: compare with `common.HasObjectChanged()`, then `r.UpdateWithRetry()` if drifted. +5. Record Kubernetes events for create/update operations. +6. Wrap errors with `common.FromClientError()`. + +### Conditional Resources + +Resources that depend on CR configuration use a slice of `{assetName string, condition bool}` structs. Only assets with `condition: true` are applied. Follow this pattern when adding new conditionally-created resources. + +## Operands and CRDs (quick reference) + +Full detail: [ARCHITECTURE.md](ARCHITECTURE.md), [docs/integration-guidelines.md](docs/integration-guidelines.md). + +| CRD | Scope | Singleton | Purpose | +|-----|-------|-----------|---------| +| `ExternalSecretsConfig` | Cluster | `cluster` | Operand configuration (webhooks, providers, deployment) | +| `ExternalSecretsManager` | Cluster | `cluster` | Global config, feature flags, status aggregation | + +**Operator-owned API:** `api/v1alpha1/`, group `operator.openshift.io`. **Upstream operand CRDs** (`external-secrets.io`, `generators.external-secrets.io`) ship via bindata/OLM — the operator deploys but does not own their API evolution. + +| Operand workload | Always? | Enable gate | Image env | +|------------------|---------|-------------|-----------| +| **external-secrets** (controller + webhook + cert-controller) | Yes | `ExternalSecretsConfig` managed | `RELATED_IMAGE_EXTERNAL_SECRETS` | +| **bitwarden-sdk-server** (plugin) | No | `plugins.bitwardenSecretManagerProvider.mode == Enabled` | `RELATED_IMAGE_BITWARDEN_SDK_SERVER` | + +**Webhook TLS (mutually exclusive):** cert-manager `Certificate` CRs when `certProvider.certManager.mode == Enabled`; otherwise in-tree `external-secrets-cert-controller` deployment. Never assume cert-manager is present. + +**Feature flags:** on `ExternalSecretsManager.Spec.Features[]` via `common.IsFeatureEnabled()` — not OpenShift cluster FeatureGate API. + +**Operand change tiers (for planning/tasks):** + +| Tier | Change | Key commands | +|------|--------|--------------| +| 1 | Deployment arg/env change | `make test-unit` | +| 2 | New optional plugin workload | API + bindata + conditional `createOrApplyDeployments` + e2e | +| 3 | Operand version bump | `make update-operand-manifests && make update-bindata && make verify` | +| 4 | Upstream CRD schema change | Comes from helm refresh (tier 3) | + +## Common Pitfalls + +1. **Never return both `RequeueAfter` and a non-nil error** from `Reconcile`. Return one or the other. +2. **Never edit generated files by hand**: `zz_generated.deepcopy.go`, `config/crd/bases/*.yaml`, `pkg/operator/assets/bindata.go`, `config/rbac/role.yaml`, `docs/api_reference.md`. Always use `make update`. +3. **Always run `make update && make verify`** after any code change. CI will reject PRs with stale generated files. +4. **Use the cached client for managed resources** (those with `app: external-secrets` label). Use the uncached client only for external resources like cert-manager Issuers or user-provided Secrets. +5. **Add new watched resources to both `controllerManagedResources` and `buildCacheObjectList()`** in `pkg/controller/external_secrets/controller.go`. +6. **Add new resource types to `HasObjectChanged`** in `pkg/controller/common/utils.go` with field-level comparison, not full `DeepEqual`. +7. **Decode helpers panic on failure** — this is intentional for build-time-constant assets. Do not add error handling around `Decode*ObjBytes` calls. +8. **RBAC markers** (`+kubebuilder:rbac`) go in `pkg/controller/external_secrets/controller.go` for the operator's own permissions. Operand RBAC is in static YAML under `bindata/`. +9. **The `go.work` workspace** means you cannot use `-mod=vendor` for `go test` or `go fmt`. The Makefile already clears `GOFLAGS` for affected targets. +10. **Container tool defaults to `podman`** (`CONTAINER_TOOL ?= podman`), not Docker. Override with `CONTAINER_TOOL=docker` if needed. +11. **Do NOT use SSA-first operand patterns** from other operators — this repo uses create-or-update. +12. **Do NOT create separate `ctrl.Manager` instances** — register in `setup_manager.go`. + +## PR and Contribution Expectations + +- Run `make lint` and `make test` locally before submitting. +- Run `make verify` to ensure generated files are in sync. +- Add unit tests for new reconciliation logic using table-driven tests and `FakeCtrlClient`. +- Add API test cases in `api/v1alpha1/tests/<crd-api-group-domain>/` (e.g., `externalsecretsconfig.operator.openshift.io/`) for any new CRD field or validation rule. +- Add E2E test cases with appropriate Ginkgo labels for platform-specific tests. +- Follow existing error wrapping patterns: `common.FromClientError` for API calls, `common.NewIrrecoverableError` for config validation failures, `common.NewUserConfigurationError` for invalid user-provided configuration. +- Commit messages should reference the relevant Jira ticket (e.g., `OCPBUGS-12345: description`). +- PR reviewers/approvers are listed in `OWNERS`. + +## Environment Variables + +The operator reads these at runtime (typically set by OLM/CSV): + +| Variable | Purpose | +|---|---| +| `RELATED_IMAGE_EXTERNAL_SECRETS` | Container image for the external-secrets operand | +| `RELATED_IMAGE_BITWARDEN_SDK_SERVER` | Container image for the Bitwarden SDK server | +| `OPERAND_EXTERNAL_SECRETS_IMAGE_VERSION` | Version label for operand resources | +| `BITWARDEN_SDK_SERVER_IMAGE_VERSION` | Version label for Bitwarden resources | +| `OPERATOR_IMAGE_VERSION` | Operator version string | +| `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` | Proxy fallback from OLM environment | + +For local development, set at minimum `RELATED_IMAGE_EXTERNAL_SECRETS` via `t.Setenv()` in tests or shell export for `make run`. + +--- + +## Per-task testing during `/opsx-apply` (code generation eval gate) + +During implementation, each code generation task is verified with **real command execution**. See `openspec/schemas/openspec-agile-workflow/stage-gate/CODE_GENERATION_EVAL_PROMPT.md`. + +| Task type | Verification | Test strategy | +|-----------|-------------|---------------| +| API types | `go build`, `go vet` | `make test-apis` after testsuite YAML | +| Codegen | `make generate && make manifests && make verify` | Consistency check | +| Controller logic | `go test ./pkg/controller/...` | Co-generated `_test.go` with `FakeCtrlClient` | +| Operand manifests | `make update-operand-manifests && make update-bindata && make verify` | `make verify-bindata` | +| OLM bundle | `make bundle` | Bundle validation | +| ESM / feature flags | `go test ./pkg/controller/external_secrets_manager/...` | Unit tests for feature wiring | + +--- + +## Execution agent routing + +Use these **Assigned Agent** IDs in `tasks.md` §3 when **`AgentRoutingMode: PROVIDED`**. Each task gets exactly one primary agent. + +| Agent ID | Scope | Route when task touches | OAPE / execution | +|----------|-------|-------------------------|------------------| +| **API_Agent** | CRD/API types, markers, `.testsuite.yaml` | `api/v1alpha1/`, `api/v1alpha1/tests/` | `api-generate` or `api-generate-tests` (verification-only) | +| **OperatorController_Agent** | Reconciliation, operand install, wiring | `pkg/controller/external_secrets/`, `pkg/controller/external_secrets_manager/`, `pkg/controller/crd_annotator/`, `pkg/operator/setup_manager.go`, `cmd/external-secrets-operator/` | `api-implement` | +| **ManifestsBindata_Agent** | Operand YAML, version pins, upstream CRDs | `bindata/`, `hack/update-external-secrets-manifests.sh`, `Makefile` `EXTERNAL_SECRETS_VERSION`, `config/crd/bases/customresourcedefinition_*` | Manual — `make update-operand-manifests`, `make update-bindata` | +| **BitwardenPlugin_Agent** | Bitwarden SDK server plugin workload | `deployment_bitwarden-sdk-server.yml` reconcilers in `deployments.go`, `Plugins.BitwardenSecretManagerProvider` API, bitwarden network policy/certificate assets | Manual — follow bitwarden patterns in `deployments.go` | +| **WebhookTLS_Agent** | Webhook TLS, cert-manager certificates | Webhook deployments, `certificates.go`, trusted CA ConfigMap | Manual | +| **RBACSecurity_Agent** | RBAC, network policies | `config/rbac/`, `rbacs.go`, `networkpolicy.go` | Manual | +| **OLMRelease_Agent** | OLM bundle, CSV, relatedImages | `config/manifests/`, `bundle/`, Makefile bundle targets | Manual — `make bundle` | +| **Testing_Agent** | E2E and API test authoring | `test/e2e/`, `test/apis/`, `test/utils/` | `e2e-generate` when task is e2e | +| **Docs_Agent** | User-facing docs | `README.md`, `docs/` | Manual | + +### Controller routing rules + +- **Operand controller** (`pkg/controller/external_secrets/`): create-or-update reconcilers; follow `install_external_secrets.go` ordering +- **ESM controller** (`pkg/controller/external_secrets_manager/`): status aggregation, default ESM creation, feature flags +- **CRD annotator** (`pkg/controller/crd_annotator/`): only when cert-manager is installed +- **API before controller**: CRD field tasks must complete (and pass `make test-apis`) before controller tasks that reconcile those fields + +### Verification pairing + +- API changes → pair with `api/v1alpha1/tests/*.testsuite.yaml` tasks (`API_Agent`, verification-only) +- Controller changes → pair with unit tests (`make test-unit`) and e2e when user-visible (`Testing_Agent`) +- Operand version bumps → pair with `make verify` and relevant e2e smoke paths + +--- + +## Stage-Specific Agent Guidance + +### Repo-Assessment Stage Hints + +When assessing `external-secrets-operator`, document: + +- **Single manager, three reconcilers** — `external_secrets_manager`, `external_secrets`, optional `crd_annotator` +- **Create-or-update** operand pattern — not SSA-first (limited SSA for annotations only) +- **Two operator CRDs** — `ExternalSecretsConfig` + `ExternalSecretsManager` (both singleton `cluster`) +- **Two operand workloads:** external-secrets core (always) + bitwarden-sdk-server plugin (conditional on CR spec) +- **Three core deployments:** `external-secrets`, `external-secrets-webhook`, `external-secrets-cert-controller` (cert-controller skipped when cert-manager TLS enabled) +- **Upstream operand CRDs** (`external-secrets.io`, `generators.external-secrets.io`) vs operator CRDs (`operator.openshift.io`) +- **Image env vars:** `RELATED_IMAGE_EXTERNAL_SECRETS`, `RELATED_IMAGE_BITWARDEN_SDK_SERVER` (+ version env vars) +- **Feature flags on ESM CR** — not OpenShift FeatureSet gates +- **cert-manager optional** — webhook TLS path + CRD annotator when cert-manager CRD exists +- **Test commands:** `make test-unit`, `make test-apis`, `make test-e2e` +- **FIPS build** via `hack/go-fips.sh` + +Anti-patterns (forbidden without branch evidence): + +- Claiming library-go or SSA-first addon patterns apply to this repo +- Using OpenShift FeatureGate / TechPreview cluster discovery patterns from other operators +- Documenting operand controllers or CRDs that do not exist on the target branch + +### Planning Stage Hints + +Prefer operator-native thinking for external-secrets: + +- `ExternalSecretsConfig` / `ExternalSecretsManager` API evolution +- **Core operand** deployment overrides (`componentConfigs`, log level, operating namespace, trusted CA bundle) +- **Bitwarden plugin** enablement (`plugins.bitwardenSecretManagerProvider`) and TLS prerequisites +- Webhook TLS path selection (cert-manager vs in-tree cert-controller) +- Bindata/helm manifest pipeline (`EXTERNAL_SECRETS_VERSION`) +- Provider integrations (Bitwarden SDK sidecar; AWS/GCP paths in e2e) +- RBAC blast radius (secrets, cluster-scoped operand CRDs) +- OLM bundle and `RELATED_IMAGE_*` env vars + +Default repo pin when none provided: + +```text +primary_repo: "https://github.com/openshift/external-secrets-operator" +branch: "main" +``` + +### Validation Stage Hints + +When the spec touches operators, secrets, webhooks, providers, or OpenShift platform integration, assess: + +- API & CRD lifecycle (singleton rules, immutability, CEL validation) +- Install / uninstall / reconcile semantics (finalizers, operand namespace) +- RBAC & blast radius (secret access, cluster-scoped writes) +- Webhook TLS (cert-manager vs in-tree cert-controller) +- Provider matrix (Bitwarden, AWS, GCP — see e2e labels) +- Observability (Ready/Degraded conditions on ESC) +- Upgrade / operand version skew (`EXTERNAL_SECRETS_VERSION`) diff --git a/openspec/schemas/openspec-agile-workflow/inputs/constitution.md b/openspec/schemas/openspec-agile-workflow/inputs/constitution.md new file mode 100644 index 000000000..b948b2eb5 --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/inputs/constitution.md @@ -0,0 +1,142 @@ +<!-- Companion artifact: repo-assessment.md (target files, reusable assets, risks) --> +# External Secrets Operator Constitution + +**AgentRoutingMode:** PROVIDED +<!-- PROVIDED — AGENTS.md exists at repo root --> + +**Version**: 1.0.0 | **Ratified**: 2026-07-01 | **Last Amended**: 2026-07-01 + +## Core Principles + +### I. Upstream Operand Separation — Do Not Fork Upstream Logic + +The operator deploys and manages upstream **external-secrets** and the optional **bitwarden-sdk-server** plugin via **embedded manifests in `bindata/external-secrets/`**. The operator **never** reimplements upstream secret-sync logic (provider authentication, ExternalSecret reconciliation, generator behavior, Bitwarden SDK protocol). Operator packages reconcile operator CRs and deploy/configure operand workloads only. + +**Evidence:** `bindata/external-secrets/resources/` — operand YAML from upstream helm; `pkg/controller/external_secrets/` installs deployments/RBAC/webhooks but contains zero provider-specific secret-fetch logic. `README.md` states the operator uses upstream helm charts. + +### II. Two Operand Workloads — Core vs Plugin + +| Workload | Always? | Controlled by | Image env | +|----------|---------|---------------|-----------| +| **external-secrets** (core controller + webhook + cert-controller) | Yes | `ExternalSecretsConfig` | `RELATED_IMAGE_EXTERNAL_SECRETS` | +| **bitwarden-sdk-server** (provider plugin) | No — when `plugins.bitwardenSecretManagerProvider.mode == Enabled` | `ExternalSecretsConfig.spec.plugins` | `RELATED_IMAGE_BITWARDEN_SDK_SERVER` | + +New plugin workloads MUST follow the bitwarden pattern: API under `spec.plugins`, bindata deployment asset, conditional entry in `createOrApplyDeployments`, dedicated network policy, TLS via `certProvider` or `secretRef`. + +**Evidence:** `deployments.go` — conditional deployment table; `constants.go` — `bitwardenDeploymentAssetName`, image env var names; `api/v1alpha1/external_secrets_config_types.go` — `BitwardenSecretManagerProvider`, CEL rules for TLS prerequisites. + +### III. Controller-Runtime Only — Single Manager, Three Reconcilers + +All controllers use **`sigs.k8s.io/controller-runtime`** on **one** shared manager. Register reconcilers in `pkg/operator/setup_manager.go` only. Do not introduce library-go, informer factories, or separate managers. + +**Evidence:** `pkg/operator/setup_manager.go` — wires `external_secrets_manager`, `external_secrets`, and optional `crd_annotator`; `cmd/external-secrets-operator/main.go` — single `ctrl.Manager`; `go.mod` — `sigs.k8s.io/controller-runtime v0.23.3`, no `openshift/library-go`. + +### IV. Create-or-Update Reconciliation — Not SSA-First + +Operand resources are reconciled via **create-or-update with deep equality** (`createWithFallback`, `common.HasObjectChanged`, `UpdateWithRetry`). Limited SSA (`client.Apply` with field owner) is allowed only for CR annotation patches. Do not convert operand reconcilers to SSA-first patterns. + +**Evidence:** `pkg/controller/external_secrets/install_external_secrets.go`, `pkg/controller/common/utils.go` — `HasObjectChanged()`; `pkg/controller/common/constants.go` — `ExternalSecretsOperatorCommonName` as field owner for annotation patches only. + +### V. Singleton CR Convention — Name `cluster`, One Per Kind + +Operator CRs `ExternalSecretsConfig` and `ExternalSecretsManager` are **cluster-scoped singletons named `cluster`**. The operator auto-creates default `ExternalSecretsManager` named `cluster`. CEL validation enforces singleton naming. + +**Evidence:** `pkg/controller/common/constants.go` — `ExternalSecretsConfigObjectName` and `ExternalSecretsManagerObjectName` = `"cluster"`; `pkg/operator/setup_manager.go` — `CreateDefaultESMResource()`; `README.md` — auto-creates `externalsecretsmanagers.operator.openshift.io` named `cluster`. + +### VI. Feature Flags on ExternalSecretsManager — Not OpenShift FeatureGate API + +Runtime feature toggles are defined on `ExternalSecretsManager.Spec.Features[]` with typed `FeatureName` values. Check via `common.IsFeatureEnabled()`. Do not add OpenShift cluster FeatureGate discovery or `pkg/features/` patterns from other operators. + +**Evidence:** `api/v1alpha1/external_secrets_manager_types.go` — `Feature` slice; `pkg/controller/common/utils.go` — `IsFeatureEnabled()`; `pkg/controller/external_secrets/constants.go` — `featureContainerArgs` map. + +### VII. Webhook TLS — cert-manager or In-Tree cert-controller (Mutually Exclusive) + +Webhook TLS uses either cert-manager `Certificate` CRs (`certProvider.certManager.mode == Enabled`) **or** the in-tree `external-secrets-cert-controller` deployment — never both. cert-controller deployment is skipped when cert-manager path is active. + +**Evidence:** `deployments.go` — `certControllerDeploymentAssetName` condition `!isCertManagerConfigEnabled(esc)`; `certificate.go`, `certificate_external-secrets-webhook.yml` vs `secret_external-secrets-webhook.yml`. + +### VIII. Bindata / Manifest Regeneration — Never Hand-Edit, Always `make update` + +Operand manifests under `bindata/` and generated code (`zz_generated.deepcopy.go`, `pkg/operator/assets/bindata.go`, CRD YAML under `config/crd/bases/`) are **generated artifacts**. Changes require `make update-operand-manifests` (helm pipeline) and/or `make generate && make manifests && make update-bindata`. CI verification (`make verify`) fails if outputs are stale. + +**Evidence:** `hack/update-external-secrets-manifests.sh`; `Makefile` — `EXTERNAL_SECRETS_VERSION`, `update`, `verify`, `verify-bindata`, `verify-generated`; `pkg/operator/assets/bindata.go` — generated. + +### IX. Verification-First Development — `make verify && make lint && make test` + +All changes MUST pass: `make test` (manifests, generate, fmt, vet, test-apis, test-unit), `make verify` (bindata, generated, govulncheck, git diff), and `make lint` (golangci-lint + kube-api-linter). E2E (`make test-e2e`, build tag `e2e`) requires a live cluster. + +**Evidence:** `Makefile` — `test`, `test-unit`, `test-apis`, `test-e2e`, `verify`, `lint` targets; `.golangci.yml` — linter configuration with kube-api-linter plugin. + +### X. RBAC Least Privilege — Explicit Operator and Operand Manifests + +Operator RBAC is in `config/rbac/`. Operand RBAC is embedded in `bindata/external-secrets/resources/` and applied by `pkg/controller/external_secrets/rbacs.go`. New permissions MUST be explicit ClusterRole rules in bindata or operator RBAC — not broad cluster-admin grants. + +**Evidence:** `config/rbac/role.yaml`; `bindata/external-secrets/resources/` — per-component RBAC YAML; `pkg/controller/external_secrets/rbacs.go`. + +### XI. OLM Bundle and Related Images + +The operator ships via OLM (`bundle/`, `config/manifests/`). Operand version is pinned in `Makefile` (`EXTERNAL_SECRETS_VERSION`). Images: +- `RELATED_IMAGE_EXTERNAL_SECRETS` / `OPERAND_EXTERNAL_SECRETS_IMAGE_VERSION` — core operand +- `RELATED_IMAGE_BITWARDEN_SDK_SERVER` / `BITWARDEN_SDK_SERVER_IMAGE_VERSION` — Bitwarden plugin + +Missing image env vars cause irrecoverable errors. + +**Evidence:** `Makefile` — `IMG_VERSION`, `EXTERNAL_SECRETS_VERSION`, `bundle` target; `bundle/manifests/openshift-external-secrets-operator.clusterserviceversion.yaml`; `pkg/controller/external_secrets/constants.go`. + +### XII. Namespace Conventions + +Operator runs in `external-secrets-operator` namespace. Operand runs in `external-secrets` namespace (`OperandDefaultNamespace`). Both are established conventions in controller constants and README. + +**Evidence:** `README.md` — "operator runs in `external-secrets-operator` namespace"; `pkg/controller/external_secrets/constants.go` — `OperandDefaultNamespace`. + +## Additional Constraints + +- **Go version**: Match `go.mod` — currently `go 1.26.0`. — **Evidence:** `go.mod` +- **Workspace**: Multi-module `go.work` (root, `cmd/external-secrets-operator`, `test`, `tools`). Vendor via `make update-vendor`. — **Evidence:** `go.work`, `vendor/` +- **Import ordering**: Local prefix `github.com/openshift/external-secrets-operator`. — **Evidence:** `.golangci.yml` `local-prefixes` +- **FIPS**: Production builds use `hack/go-fips.sh` (`GOEXPERIMENT=strictfipsruntime`, `CGO_ENABLED=1`). — **Evidence:** `Makefile` `build-operator`, `hack/go-fips.sh` +- **Container image**: Operator image from `Dockerfile` / `images/ci/`; operand images from `RELATED_IMAGE_*` env vars. — **Evidence:** `Dockerfile`, CSV relatedImages +- **Test frameworks**: Standard `testing` + counterfeiter fakes in `pkg/`; Ginkgo v2 + envtest in `test/apis/`; Ginkgo + live cluster in `test/e2e/` (tag `e2e`). — **Evidence:** `pkg/controller/client/fakes/`, `test/apis/`, `test/e2e/` +- **CI system**: Prow via `openshift/release`; in-repo verify via `make verify`. — **Evidence:** `README.md` contributing section +- **Optional cert-manager**: Webhook TLS may use cert-manager `Certificate` CRs when cert-manager is installed; `crd_annotator` is conditional. Never assume cert-manager is present. — **Evidence:** `pkg/operator/setup_manager.go`, `pkg/controller/crd_annotator/` + +## Development Workflow + +| Activity | Requirement | Evidence | +|----------|-------------|----------| +| Local unit tests | `make test-unit` or full `make test` | `Makefile` | +| API validation tests | `make test-apis` after CRD/testsuite changes | `hack/test-apis.sh`, `test/apis/` | +| Full verify | `make verify` | `Makefile` `verify` target | +| Lint | `make lint` | `Makefile`, `.golangci.yml` | +| Codegen refresh | `make generate && make manifests` after API edits | `Makefile` | +| Operand bump | `make update-operand-manifests && make update-bindata` | `hack/update-external-secrets-manifests.sh` | +| E2E tests | `make test-e2e` (cluster required); filter via `E2E_GINKGO_LABEL_FILTER` | `Makefile` `test-e2e` | +| Bundle generation | `make bundle` after CSV/CRD changes | `Makefile` `bundle` | +| PR pre-merge | `make test && make verify && make lint`; commit generated outputs | `AGENTS.md` verification matrix | + +## Agent Routing + +| Agent ID | Scope | When to route | +|----------|-------|---------------| +| **API_Agent** | `api/v1alpha1/`, testsuite YAML | CRD types, validation, markers | +| **OperatorController_Agent** | `pkg/controller/external_secrets/`, `external_secrets_manager/`, `crd_annotator/`, `setup_manager.go` | Core operand reconciliation, wiring | +| **ManifestsBindata_Agent** | `bindata/`, `hack/update-external-secrets-manifests.sh`, operand CRDs | Operand manifest refresh, version pins | +| **BitwardenPlugin_Agent** | `Plugins.BitwardenSecretManagerProvider`, bitwarden bindata assets | Bitwarden SDK plugin workload | +| **WebhookTLS_Agent** | `certificates.go`, webhook deployments, trusted CA | Webhook TLS paths | +| **RBACSecurity_Agent** | `config/rbac/`, `rbacs.go`, `networkpolicy.go` | RBAC and network policy | +| **OLMRelease_Agent** | `bundle/`, `config/manifests/` | CSV, relatedImages | +| **Testing_Agent** | `test/e2e/`, `test/apis/` | Test authoring | +| **Docs_Agent** | `README.md`, `docs/` | User-facing docs | + +Full routing detail: `AGENTS.md` (repo root). + +## Governance + +- This constitution supersedes ad-hoc conventions for downstream Planning, Task Creation, and Code Generation agents. +- **Amendments:** require documented evidence of repo change; bump Version and Last Amended date. +- **Conflicts:** if spec contradicts constitution, escalate in plan.md §8 — do not silently override. Forking upstream external-secrets logic into the operator is a constitution violation. +- **Companion docs:** + - **AGENTS.md** takes precedence for agent routing, controller map, Make targets, and test patterns. + - **README.md** takes precedence for human-facing install and contributing procedures. + - **This constitution** takes precedence for architectural principles and non-negotiable guardrails. +- **Complexity:** new patterns must justify deviation from existing repo conventions. Adding a second controller framework or SSA-first operand reconciliation requires constitution amendment. diff --git a/openspec/schemas/openspec-agile-workflow/schema.yaml b/openspec/schemas/openspec-agile-workflow/schema.yaml new file mode 100644 index 000000000..ad0647692 --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/schema.yaml @@ -0,0 +1,1472 @@ +name: openspec-agile-workflow +version: 1 +description: >- + Gated spec-driven workflow: Spec Understanding → Repo Understanding → Planning → + Task Creation → Implementation. Produces validation.json, specs.md, + repo-assessment.md, plan.md, tasks.md, and (during implementation) + implementation-report.md, implementation-checklist.md, and optional adrs.md. + constitution.md is an INPUT (resolved from target repo or schema inputs/). + Implementation uses OAPE command orchestration (/opsx:apply). + Templates from templates/. + +templates_dir: templates + +# --------------------------------------------------------------------------- +# AGENTS.md — when required, where to look, what to do if missing +# --------------------------------------------------------------------------- +agents_md: + description: + SME-defined execution agent roster and routing rules from the target repository. + Used for phase capability mapping (plan), Assigned Agent values (tasks), and + operator-specific test patterns (implementation). Not used during Spec Understanding. + canonical_file: + path: agents.md + path_relative_to_schema: agents.md + description: >- + Operator-specific agent roster and execution routing. Shipped inside the + schema package at openspec/schemas/openspec-agile-workflow/agents.md + (installed by install.sh from distribution repo root agents.md). + Customize this file for your operator. + use_when: >- + The target repo has no AGENTS.md/agents.md and change inputs do not + override with a local copy. Falls back to the bundled schema copy. + used_in_stages: + - id: repo-understanding + required: false + when: repo-assessment authoring + effect: >- + If found, repo-assessment notes agent IDs for downstream use. + AgentRoutingMode is set in the pre-approved constitution.md input. + - id: planning + required: false + when: plan.md authoring (§0–§8) + effect: >- + Map implementation phases to concrete agent IDs from AGENTS.md when provided; + otherwise use provisional taxonomy and label it in plan.md. + - id: task-creation + required: false + when: tasks.md authoring (§3 manifest, §4 payloads) + effect: >- + Assigned Agent must match AGENTS.md IDs when AgentRoutingMode is PROVIDED; + otherwise use provisional agent IDs from constitution/tasks template. + - id: implementation + required: false + when: task-by-task OAPE command orchestration within each phase + effect: >- + Route Assigned Agent values from tasks.md §3 to OAPE commands per + schema oape_routing — one task at a time. Compose design-bundle.md per + task. Manual agents use task payloads directly. User approval after every + task before advancing. See oape-ai-e2e/AGENTS.md for command flow. + not_used_in_stages: + - spec-understanding + lookup_order: + - "{target_repo}/AGENTS.md" + - "{target_repo}/agents.md" + - "{schema_root}/inputs/agents.md" + - "{schema_root}/agents.md" + when_missing: >- + Before starting repo-assessment, plan, or tasks — resolve agents.md using + lookup_order. Check the target repo first, then schema inputs/ folder, then + schema root. The schema package ships agents.md at {schema_root}/inputs/agents.md + and {schema_root}/agents.md (installed by install.sh). Persist a copy to + openspec/changes/<change>/inputs/AGENTS.md when first resolved so downstream + artifacts reference a stable path. If still absent, ask the user once to provide + AGENTS.md. Do not invent agent IDs. If the user declines, proceed with + AgentRoutingMode PROVISIONAL and document in plan.md and tasks.md. + +# --------------------------------------------------------------------------- +# constitution_md — pre-approved constitution provided as input +# --------------------------------------------------------------------------- +constitution_md: + description: >- + Pre-approved constitution document provided as an input. Contains non-negotiable + guardrails, coding conventions, development workflow, and governance rules derived + from the operator's codebase. Used as input for Planning, Task Creation, and + Implementation stages. NOT generated as an artifact — provided by the operator team. + lookup_order: + - "{target_repo}/constitution.md" + - "{target_repo}/CONSTITUTION.md" + - "{schema_root}/inputs/constitution.md" + when_missing: >- + Before starting planning (plan.md), resolve constitution.md using lookup_order. + Check target repo first, then schema inputs/. If still not found, ask the user + once to provide constitution.md. If the user declines, generate one using + templates/constitution-template.md as a one-time generation step (not a standard artifact + stage), save to {schema_root}/inputs/constitution.md, and proceed. + used_in_stages: + - id: planning + required: true + when: plan.md authoring + effect: Provides non-negotiable guardrails and AgentRoutingMode for planning. + - id: task-creation + required: true + when: tasks.md authoring + effect: Enforces coding conventions and testing requirements in task payloads. + - id: implementation + required: true + when: OAPE command execution and code generation + effect: Code must follow constitution principles; verified during code eval gate. + not_used_in_stages: + - spec-understanding + template_for_generation: templates/constitution-template.md + note: >- + The constitution.md template remains in templates/ for reference and for one-time + generation if no pre-approved constitution exists. But in normal operation, + constitution is an INPUT — not a generated artifact. + +# --------------------------------------------------------------------------- +# target_repo — upstream GitHub repo for assessment and planning +# --------------------------------------------------------------------------- +target_repo: + description: >- + Upstream GitHub repository URL used for repo-assessment, plan, and tasks. + Also checked for constitution.md (see constitution_md.lookup_order). + Distinct from fork_repo_url (user's fork for implementation). + collected_at: repo-understanding + stored_in: openspec/changes/<change>/inputs/jira.yaml + field: target_repo + not_required_at: + - opsx-new + when_missing: >- + Before creating repo-assessment.md (Repo Understanding stage / artifact + repo-assessment), read inputs/jira.yaml. + + **Working-folder mode** (see schema working_folder_repo): If + use_working_folder_as_repo is true, or the user directs using the working + folder as the repo, activate working_folder_repo — use the current project + checkout for assessment; derive target_repo from `git remote get-url origin` + when possible; do not ask for a separate GitHub URL or clone. + + **Default mode:** If target_repo is absent or empty, ask the user once: + "Provide the URL of the target GitHub repository (e.g. + https://github.com/org/repo)." Persist target_repo to inputs/jira.yaml. + Do not start repo-assessment until the URL is recorded. + Verify the repository is accessible (clone, fetch, or GitHub MCP) before + writing repo-assessment.md. + usage: >- + Reference only for planning and assessment. Implementation OAPE commands and + edits apply to fork_repo_url (/opsx:apply), not target_repo, unless the user + explicitly directs otherwise or working_folder_repo mode is active (see + working_folder_repo). + +# --------------------------------------------------------------------------- +# working_folder_repo — local checkout mode (user-directed) +# --------------------------------------------------------------------------- +working_folder_repo: + description: >- + When the user explicitly directs using the current working folder as the + repository, treat the local project checkout as both the assessment target + and the implementation workspace. Skip fork setup, separate clones, and draft + PR workflow. + stored_in: openspec/changes/<change>/inputs/jira.yaml + field: use_working_folder_as_repo + active_when: >- + use_working_folder_as_repo is true in inputs/jira.yaml, OR the user states + during any stage that the working folder / current directory / this checkout + should be used as the repo (e.g. "use the working folder as the repo", + "use this folder", "local checkout mode", "no fork", "don't create a draft PR"). + on_user_directive: + - Set use_working_folder_as_repo: true in inputs/jira.yaml + - Record working_folder_path as the absolute path to the project root (cwd + containing openspec/changes/<change>/) + - If target_repo is empty, derive from `git remote get-url origin` in cwd when + available; persist to target_repo for reference only + - Do not ask for fork_repo_url; leave fork_repo_url absent + assessment_and_planning: + repo_root: >- + The current working directory (project root). Use local git metadata, tree, + and file contents for repo-assessment, plan, and tasks — do not clone a + separate remote checkout for these stages. + target_repo_prompt: >- + When use_working_folder_as_repo is true, do NOT ask for a GitHub URL before + repo-assessment if the cwd is a git repository. Derive target_repo from + origin when possible; otherwise record working_folder_path and proceed with + local analysis. + agents_md: >- + Resolve AGENTS.md from the working folder first, then change inputs/. + implementation: + working_copy: >- + Same as working_folder_path / project root. All OAPE commands and code edits + apply in this directory — not a cloned fork. + cwd: project root (working folder) + skip_fork_setup: true + do_not: + - Ask for fork_repo_url + - Clone a separate fork repository + - Create a feature branch unless the user explicitly requests one + - Push commits to a remote + - Open a draft pull request + post_all_tasks: >- + Write implementation-report.md, implementation-checklist.md, and optional + adrs.md. Summarize local file changes and git status. Do not push or open + a PR. Set PR URL to N/A — working-folder mode. + precedence: >- + When use_working_folder_as_repo is true, working_folder_repo overrides + fork_repo (clone, feature branch, draft_pr) and relaxes target_repo URL + collection. Default fork + upstream workflow applies when the field is false + or absent and the user has not directed local mode. + +# --------------------------------------------------------------------------- +# stage_eval_gate — stage evals on each artifact (/opsx-continue) +# --------------------------------------------------------------------------- +stage_eval_gate: + description: >- + After each artifact is generated, score it against merged stage evals under + this schema package (evals/*_eval.yaml). Refine the change artifact only — + never schema templates. + schema_root: + installed: openspec/schemas/openspec-agile-workflow + distribution: schemas/openspec-agile-workflow + evals_dir: evals + stage_eval_files: + repo-assessment: evals/repo-assessment_eval.yaml + plan: evals/plan_eval.yaml + tasks: evals/tasks_eval.yaml + implementation: evals/implementation_eval.yaml + eval_specs_dir: evals/stages + system_prompt: stage-gate/SYSTEM_PROMPT.md + artifact_map: stage-gate/artifact-eval-map.yaml + generation_templates: templates/ + eval_results: openspec/changes/<change>/eval-results/<artifact-id>.yaml + evaluation_report: + output_path: openspec/changes/<change>/eval-results/<artifact-id>_evaluation_report.md + description: >- + After eval scoring, generate an evaluation report presenting pass/fail + summary, gap analysis against input artifacts and agents.md, and quality + assessment. Present to user alongside the artifact for approval. + generated_for: all artifacts (stage_evals, rubric_only, and skip gates) + flow: >- + generate_v1 → run_stage_evals → refine_artifact → generate_evaluation_report → user_approval + → (reject → user_approval_feedback_gate → re_eval → regenerate_evaluation_report → user_approval) + do_not_modify: + - templates/ + - evals/refined-templates/ + refinement_context: + - draft_artifact_v1 + - eval_results_and_failed_assertions + - openspec_instructions_json + - dependency_artifacts + - change_inputs_jira_yaml + - user_rejection_feedback + +# --------------------------------------------------------------------------- +# code_generation_eval_gate — per-task code evals during /opsx-apply +# --------------------------------------------------------------------------- +code_generation_eval_gate: + description: >- + After each task's OAPE command (or manual agent work) and verification, + score generated code in the fork against code-generation evals before user + task approval. Cases are authored retrospectively by /eval-loop from EPs, + PRs, and bug postmortems. + runs_during: implementation + runs_after: + - oape_command_or_manual_work + - task_verification + runs_before: + - task_summary + - task_approval_gate + schema_root: + installed: openspec/schemas/openspec-agile-workflow + distribution: schemas/openspec-agile-workflow + stage_eval_file: evals/code-generation_eval.yaml + eval_spec: evals/stages/code-generation/eval-spec.yaml + system_prompt: stage-gate/CODE_GENERATION_EVAL_PROMPT.md + eval_results: openspec/changes/<change>/eval-results/code-generation-<task-id>.yaml + filter_by: oape_command + oape_command_map: + api-generate: api-generate + api-generate-tests: api-generate-tests + api-implement: api-implement + e2e-generate: e2e-generate + manual: manual + also_run_cases_with_oape_command: any + mandatory_before_user_approval: true + max_refinement_passes: 2 + task_report: + path: openspec/changes/<change>/implementation/task-reports/<task-id>.md + template: templates/implementation-task-report-template.md + write_on: task_approve + phase_log: openspec/changes/<change>/implementation-phase-log.md + flow: >- + execute_oape_or_manual → verify_task_criteria → run_code_generation_evals → + refine_code_until_pass_or_max_passes → present_task_summary_with_scorecard → + user_code_approval → write_task_report → append_phase_log → next_task + task_approval_prompt: >- + Code eval score: {overall_score}% ({cases_pass}/{cases_total} cases pass). + Approve the code changes for task {task_id} ({task_title}) and proceed to the + next task? (Approve / Reject with feedback) + skip_when: + - stage_eval_file missing + - evals list empty + - no cases match resolved oape_command + refinement_context: + - fork_git_diff + - current_task_payload + - design_bundle + - failed_eval_cases + - constitution_md + - test_output + do_not_modify: + - evals/code-generation_eval.yaml + - openspec/changes/<change>/tasks.md + - approved_upstream_artifacts + +# --------------------------------------------------------------------------- +# user_approval_feedback_gate — revision loop when user rejects at approval +# --------------------------------------------------------------------------- +user_approval_feedback_gate: + description: >- + Feedback stage during user approval. When the user rejects an artifact with + feedback, load prior approved artifacts (read-only), the current artifact, + and the current schema template; update the template if feedback requires it; + regenerate and present only the current artifact; write a round summary to + feedback_stage_artifacts; re-run eval gate when applicable; loop until the + user approves. User feedback MUST NOT modify any previously approved artifact. + runs_after: + - stage_eval_gate + runs_during: user_approval + system_prompt: stage-gate/USER_FEEDBACK_PROMPT.md + feedback_stage_artifacts: + schema_spec: feedback_stage_artifacts/README.md + round_summary: >- + openspec/changes/<change>/feedback_stage_artifacts/<artifact-id>/round-<N>.yaml + joint_round_summary: >- + openspec/changes/<change>/feedback_stage_artifacts/repo-assessment/round-<N>.yaml + max_feedback_rounds: + default: 3 + config_key: flags.max_feedback_rounds + on_limit_reached: >- + HALT the feedback loop. Present the latest artifact version and inform the + user that the maximum feedback rounds have been reached. Suggest revising + inputs or starting a new change. Do not continue the loop or auto-approve. + flow: >- + present_artifact → ask_approval → + (approve → lock_artifact → unlock_next → stop) | + (reject_with_feedback → check_round_count_vs_max_feedback_rounds + → load_context → update_template_if_required + → regenerate_current_artifact → write_feedback_stage_summary + → re_run_stage_eval_gate → ask_approval) + approval_prompt: >- + Approve this artifact and proceed to the next stage? + (Approve / Reject with feedback) + on_reject: + steps: + - Capture user feedback verbatim (include in round summary). + - >- + Load revision context (see revision_context): prior approved artifacts + (read-only), current artifact draft at outputPath, current template from + artifact-eval-map, openspec instructions, eval results, prior round + summaries, change inputs. + - >- + If feedback requires structural or guidance changes, patch + {schema_root}/templates/<template>.md minimally; record template_update + in round summary. + - >- + Regenerate ONLY the current artifact at outputPath using revision context + and user feedback. Regenerate only the current artifact — never + upstream approved artifacts. + - >- + Write round summary to feedback_stage_artifacts/<artifact-id>/round-<N>.yaml + (see feedback_stage_artifacts/README.md). + - Re-run stage_eval_gate when the artifact has stage evals or rubric_only. + - >- + Present refined artifact(s), template update summary, eval scorecard, + feedback addressed bullets, round summary path; return to approval_prompt. + do_not: + - Modify, overwrite, or delete any previously approved artifact file + - Read or write prompts/<artifact-id>.yaml + - Edit evals/refined-templates/ + - Create the next artifact in the workflow sequence + - Advance workflow stage until user approves + revision_context: + description: >- + Context bundle used to decide template updates and regenerate the current + artifact after user rejection. + context_bundle: + - prior_approved_artifacts_read_only + - current_artifact_draft + - current_schema_template + - openspec_instructions + - eval_results_and_failed_assertions + - user_feedback_verbatim + - prior_feedback_stage_round_summaries + - change_inputs_jira_yaml + template_update: + when: >- + Feedback asks for sections, headings, or structural guidance the current + template does not require. + path: '{schema_root}/templates/<template>.md' + rules: + - Minimal patch only — what feedback requires + - Record template_update summary in round summary file + artifact_regeneration: + rules: + - Address every point in user feedback in the revised artifact + - Do not modify any artifact with status done in openspec status + - >- + If feedback requires changing an approved upstream artifact, stop and + tell the user which artifact must be reopened — do not silently edit it + - Overwrite only the current artifact outputPath + - Do not invent facts not supported by immutable inputs or change inputs + - Re-run eval gate after regeneration when applicable + on_approve: + steps: + - Mark artifact status done per schema gate + - Lock artifact as immutable for all downstream generation and feedback + - Unlock next artifact(s) per requires dependency graph + - Stop — one artifact per /opsx-continue unless rejecting in same invocation + immutable_artifacts: + description: >- + All artifacts marked done before the current approval gate are frozen. + Feedback revision treats them as read-only context only. + resolution: >- + openspec status --change "<name>" --json → every artifact with + status "done". During feedback on artifact X, never write to those paths. + never_modify_during_feedback: + - Any artifact with status done except the artifact(s) currently under approval + note: >- + Each artifact has its own approval gate. specs.md and all earlier + artifacts remain immutable during any feedback loop. + applies_to: + gate_types: + - approval + - threshold + - per_task_approval + artifacts: + - validation + - specs + - repo-assessment + - plan + - tasks + - implementation + precedence: >- + exit_on_reject (specs) overrides the feedback loop for listed artifacts. + specs rejection does not use on_reject steps above. + per_task_implementation_variant: + description: >- + Implementation runs OAPE commands task-by-task. User approval is required + after every task before advancing to the next. On reject, append feedback + to design-bundle.md and re-run the current task only — upstream approved + artifacts and already-approved tasks remain immutable. + feedback_target: implementation/design-bundle.md + on_reject: >- + Append user feedback to REVISION FEEDBACK in design-bundle.md; re-run OAPE + commands for the current task only; do not mark the task complete, do not + advance to the next task, and do not edit approved upstream artifacts. + exit_on_reject: + description: >- + For listed artifacts, user rejection at the approval gate **ends the change + workflow**. Do not run the feedback refinement loop, do not regenerate + the artifact, and do not unlock downstream stages. + artifacts: + specs: + generates: specs.md + workflow_stage: spec-understanding + on_reject: + - >- + Optionally record user reason in + feedback_stage_artifacts/specs/round-1.yaml (single entry; no refinement + rounds). + - Do NOT regenerate specs.md + - Do NOT unlock repo-assessment, plan, tasks, or implementation + - STOP — exit the workflow for this change + exit_message: >- + specs.md was not approved. The openspec-agile-workflow stops at Spec + Understanding. No further artifacts will be created for this change. + To proceed: revise the Jira spec or inputs/jira-spec.md, then start a + new change (/opsx-new) or delete specs.md and re-run /opsx-continue when + the user is ready to regenerate specs. + approval_prompt: >- + Approve specs.md and proceed to Repo Understanding (repo-assessment)? + (Approve / Reject — rejection exits the workflow) +fork_repo: + description: >- + User's forked GitHub repository where implementation code is written, committed, + pushed, and opened as a draft pull request. Distinct from target_repo (upstream) + used for repo-assessment and planning context. + collected_at: implementation + stored_in: openspec/changes/<change>/inputs/jira.yaml + field: fork_repo_url + not_required_at: + - opsx-new + - opsx-continue + when_missing: >- + At the start of the Implementation Stage (/opsx:apply), BEFORE any clone + activity or OAPE command execution, read inputs/jira.yaml. + + **Working-folder mode** (see schema working_folder_repo): If + use_working_folder_as_repo is true, or the user directs using the working + folder as the repo, skip fork_repo_url collection entirely. Set working copy + to the project root (cwd); do not clone, push, or open a draft PR. + + **Default mode:** If fork_repo_url is absent, ask the user once: "Provide + the URL of your forked repository (e.g. + https://github.com/<you>/external-secrets-operator)." Persist fork_repo_url to + inputs/jira.yaml. Do not start code generation until the URL is recorded and + the fork is cloned or verified accessible. + working_copy: >- + **Default:** Clone or reuse a local checkout of fork_repo_url. All OAPE + commands and implementation edits apply to this working copy. + + **Working-folder mode:** Use the project root (working_folder_path in + inputs/jira.yaml). All OAPE commands and edits apply in cwd — do not clone + fork_repo_url. + + target_repo in inputs/jira.yaml remains the upstream reference from + repo-assessment; implementation writes only to the fork unless the user + explicitly directs otherwise or working_folder_repo mode is active. + feature_branch: + when: >- + Immediately after fork_repo_url is recorded and the fork is cloned or + verified — BEFORE any OAPE command or implementation commits. **Skip when + working_folder_repo mode is active** unless the user explicitly requests a + feature branch. + steps: + - Checkout the fork's default branch (master or main) and pull latest + - Create a feature branch from that default branch + - All implementation commits MUST go on this branch only + branch_naming: >- + Use branch naming from constitution.md when specified; otherwise + feature/<jira_key_lowercase>-<change_slug> (e.g. eso-123-webhook-tls). + do_not: + - Do not implement directly on the fork default branch + - Do not create a separate baseline branch (e.g. eso-123-baseline) to + fabricate a PR diff + - If the fork default branch already contains the changes, report that + to the user instead of inventing a second base branch + draft_pr: + when: >- + After Task Creation is complete, all implementation tasks are user-approved, + and closing artifacts (implementation-report.md, implementation-checklist.md) + are written. **Skip entirely when working_folder_repo mode is active** — do + not push or open a draft PR. + steps: + - Confirm all commits are on the feature branch created at fork setup + - Stage and commit any remaining changes with a descriptive message referencing jira_key + - Push the feature branch to fork_repo_url + - Open a draft pull request on the user's fork (fork_repo_url) + pr_target: >- + Draft PR on fork_repo_url only: head = feature branch (with all + implementation commits), base = fork default branch (master or main). + Do not open PRs against upstream target_repo unless the user explicitly + requests it. Use GitHub CLI (`gh pr create --draft`) or GitHub MCP. + Include the PR URL in the final summary and implementation-report.md. + +# --------------------------------------------------------------------------- +# oape_routing — OAPE command orchestration for /opsx:apply +# --------------------------------------------------------------------------- +oape_routing: + description: >- + Maps tasks.md Assigned Agent values to OAPE cursor commands. Implementation + runs task-by-task: compose a design bundle scoped to one task, invoke exactly + one allowed OAPE command for that task (see command_resolution), verify, + then request user approval before advancing. + code_generation_prompt: templates/code-generation-template.md + reference: oape-ai-e2e/AGENTS.md + commands_dir: .cursor/commands + allowed_commands: + - api-generate + - api-generate-tests + - api-implement + - e2e-generate + do_not_invoke_during_implementation: + - predict-regressions + - review + - implement-review-fixes + - analyze-rfe + - init + command_resolution: >- + Resolve exactly ONE OAPE command per task — no other OAPE commands. + IF the task is an e2e task (see e2e_task): use e2e-generate. + ELIF Assigned Agent is API_Agent and task is verification-only: use api-generate-tests. + ELIF Assigned Agent is API_Agent: use api-generate. + ELIF Assigned Agent is OperatorController_Agent: use api-implement. + ELIF Assigned Agent is manual (ManifestsBindata, WebhookTLS, RBACSecurity, + OLMRelease, Docs): no OAPE command — implement task payload directly. + ELSE: halt and ask user which allowed command applies. + e2e_task: + description: >- + When any condition is true, the task is an e2e task and MUST use e2e-generate + (and no other OAPE command). + when_any: + - Assigned Agent (tasks.md §3) is Testing_Agent + - tasks.md §4 Acceptance criteria references make test-e2e or test-e2e-wait-for-stable-state + - tasks.md §4 Target file(s) under test/ (e2e, ginkgo, or *_test.go integration/e2e paths) + - Task Title or §4 Objective contains "e2e" or "end-to-end" (case-insensitive) + command: e2e-generate + args: "<fork-default-branch>" + design_bundle: + path: openspec/changes/<change>/implementation/design-bundle.md + template: templates/design-bundle-template.md + composed_from: + - constitution.md + - specs.md + - plan.md + - repo-assessment.md + - tasks.md + scope: >- + Include full upstream artifacts plus tasks.md §4 payload ONLY for the + current Task ID (not the whole phase). Add REVISION FEEDBACK when + re-running after task rejection. + invoke_as: >- + Pass as --design-doc <absolute-path-to-design-bundle.md> to + oape:api-generate and oape:api-implement (local file supported). + task_execution: + unit: task + derive_from: tasks.md §3 Task Execution Manifest (Task ID, Assigned Agent, Phase, Depends On) + order_by: tasks.md §2 Linear Execution Order; respect §1 DAG — do not start a task until Depends On are complete + phase_grouping: tasks.md §3 Phase column (group T<n>_* by numeric prefix) + task_approval_gate: >- + After OAPE/manual work, verification, and code_generation_eval_gate + (eval → refine code → re-score), present the task summary and code eval + scorecard. Request user approval of the **code changes** for this task. + Do not start the next task until the user approves. On approve: write + implementation/task-reports/<task-id>.md, append implementation-phase-log.md, + mark the task `- [x]` in tasks.md. + skip: tasks already marked `- [x]` in tasks.md + cwd: fork working copy (see fork_repo.working_copy) + task_loop: + for_each_pending_task: + order_by: tasks.md §2 Linear Execution Order; respect §1 DAG and phase grouping + steps: + - Compose design-bundle.md scoped to current Task ID only + - Resolve ONE OAPE command per command_resolution (allowed_commands only) + - Invoke that command in fork cwd (read .cursor/commands/<command_file>) + - Execute manual-agent work when Assigned Agent is manual (no OAPE command) + - Verify per task Acceptance criteria; record pass/fail + - Run code_generation_eval_gate — score fork code; refine code until evals + pass or max_refinement_passes (2); do NOT ask user approval before this completes + - Present task summary with code eval scorecard + - Request user code approval (task_approval_gate / code_generation_eval_gate.task_approval_prompt) + - On reject: REVISION FEEDBACK in design-bundle; re-run current task only + - >- + On approve: write implementation/task-reports/<task-id>.md; mark task `- [x]`; + append implementation-phase-log.md + - Advance to next pending task in §2 order + task_approval_prompt: >- + Code eval score: {overall_score}% ({cases_pass}/{cases_total} cases pass). + Approve the code changes for task {task_id} ({task_title}) and proceed to the + next task? (Approve / Reject with feedback) + command_order: + - id: api-generate + command: /oape:api-generate + command_file: api-generate.md + when: >- + NOT an e2e task AND current task Assigned Agent is API_Agent AND task is + NOT verification-only + args: "--design-doc <design-bundle-path>" + follow_up: make update && make verify + - id: api-generate-tests + command: /oape:api-generate-tests + command_file: api-generate-tests.md + when: >- + NOT an e2e task AND current task Assigned Agent is API_Agent AND task is + verification-only + args: "<api-path-from-task-target-files>" + path_resolution: >- + Derive from tasks.md §4 Target file(s) for the current task — use api/ + directory or *_types.go parent directory. Fall back to repo-assessment.md + API paths. + - id: api-implement + command: /oape:api-implement + command_file: api-implement.md + when: >- + NOT an e2e task AND current task Assigned Agent is OperatorController_Agent + args: "--design-doc <design-bundle-path>" + follow_up: make generate && make manifests && make build && make test + - id: e2e-generate + command: /oape:e2e-generate + command_file: e2e-generate.md + when: current task is an e2e task (see e2e_task) + args: "<fork-default-branch>" + agent_routing: + API_Agent: + verification_only: api-generate-tests + implementation: api-generate + OperatorController_Agent: api-implement + Testing_Agent: e2e-generate + e2e_task: e2e-generate + ManifestsBindata_Agent: manual + WebhookTLS_Agent: manual + RBACSecurity_Agent: manual + OLMRelease_Agent: manual + Docs_Agent: manual + manual_agents: >- + Agents marked manual have no OAPE command. For each manual task, execute the + task payload from the design bundle with minimal scoped edits in the fork + working copy. Follow constitution.md and repo-assessment patterns. Log deviations. + verification: >- + After each task's OAPE commands or manual implementation, run Makefile targets + named in that task's Acceptance criteria and plan.md verification hooks when + applicable. Record pass/fail in the task summary and phase log. + prerequisites: + - gh (authenticated) + - go + - git + - make + - >- + OAPE command files in .cursor/commands/ — api-generate.md, api-generate-tests.md, + api-implement.md, e2e-generate.md only + +# --------------------------------------------------------------------------- +# Workflow stages (source of truth) +# --------------------------------------------------------------------------- +workflow: + - id: spec-understanding + name: Spec Understanding Stage + description: >- + Agent reads the specification from Jira (enhancement repo ticket). + Specification Validation is evaluated by the user before proceeding. + inputs: + - Jira ticket with specification document + outputs: + - validation.json + - specs.md + gate: user_approval + feedback_gate: user_approval_feedback_gate + + - id: repo-understanding + name: Repo Understanding Stage + description: >- + Agent reads and contextualizes the target GitHub repository using the + approved spec and Agents.md. Repo assessment is evaluated by the user + before proceeding. Constitution.md is resolved as an input (not generated). + inputs: + - specs.md + - GitHub repository (inputs/jira.yaml target_repo — required; see schema target_repo) + - AGENTS.md (optional — ask user if missing; see agents_md.when_missing) + - Functional SME context + outputs: + - repo-assessment.md + gate: user_approval + feedback_gate: user_approval_feedback_gate + + - id: planning + name: Planning Stage + description: >- + Technical planning using approved repo assessment, constitution (input), and specs. + Planning approach is evaluated by the user before plan.md is finalized. + Constitution.md is resolved via schema constitution_md.lookup_order before this stage. + inputs: + - specs.md + - repo-assessment.md + - constitution.md (input — resolved via constitution_md.lookup_order; see schema constitution_md) + - AGENTS.md (optional — ask user if missing; see agents_md.when_missing) + - validation.json + outputs: + - plan.md + gate: user_approval + feedback_gate: user_approval_feedback_gate + + - id: task-creation + name: Task Creation Stage + description: >- + Decompose the approved plan into an execution backlog (tasks.md) with + dependency DAG, agent routing, per-task payloads, and orchestration notes. + Evaluated by the user before implementation. + inputs: + - specs.md + - constitution.md (input — resolved via constitution_md.lookup_order) + - repo-assessment.md + - plan.md + - AGENTS.md (optional — ask user if missing; see agents_md.when_missing) + outputs: + - tasks.md + gate: user_approval + feedback_gate: user_approval_feedback_gate + + - id: implementation + name: Implementation Stage + description: >- + Final pipeline stage. Begins after Task Creation (tasks.md) is approved. + **Default:** The agent asks for fork_repo_url, clones the fork, and drives + OAPE task-by-task; when complete, pushes and opens a draft PR. + **Working-folder mode:** Uses the current project checkout (see schema + working_folder_repo) — no fork, no draft PR. + User approval is required after every task. Closes with implementation-report.md, + implementation-checklist.md, and optional adrs.md. + inputs: + - tasks.md (required — Task Creation complete) + - constitution.md (required) + - specs.md (required) + - plan.md (required) + - repo-assessment.md (optional — improves target-file accuracy) + - target repository (upstream reference — inputs/jira.yaml target_repo) + - forked repository URL (inputs/jira.yaml fork_repo_url — required in default + mode; skip when use_working_folder_as_repo is true) + outputs: + - code changes in user's fork (or project working folder in working-folder mode) + - draft pull request on fork_repo_url (skipped in working-folder mode) + - implementation-report.md + - implementation-checklist.md + - adrs.md (optional — only when deviations are logged) + gate: user_approval_per_task + feedback_gate: user_approval_feedback_gate + +# --------------------------------------------------------------------------- +# Artifacts (ordered by workflow) +# --------------------------------------------------------------------------- +artifacts: + - id: validation + workflow_stage: spec-understanding + generates: validation.json + format: json + description: Specification Validation — quality gate on the Jira spec before spec authoring + template: validation-template.md + instruction: | + You are the "Specification Validator": a quality gate for software specs before engineering. + + ## Mission + Reduce rework by catching ambiguous, incomplete, inconsistent, or un-testable requirements early. + Make gaps explicit as questions and actionable edits — do not invent product behavior. + + ## Process + 1. Read the specification from the Jira ticket (provided in the user message). + 2. Apply the validation template rubric (completeness 60%, quality 40%). + 3. Emit ONE valid JSON object matching the template schema. + 4. Set overall_status: + - PASS: overall_score >= pass_threshold AND no blockers + - NEEDS_REVISION: score below threshold OR non-fatal gaps + - BLOCKED: severe contradictions — halt pipeline + + ## Gate (Spec Understanding Stage) + Present validation summary to the user for evaluation. + - PASS or approved NEEDS_REVISION → proceed to specs.md authoring. + - BLOCKED → stop; do not offer proceed. + + Do not fabricate repositories, APIs, behaviors, or dependencies not stated in the spec. + gate: + type: threshold + evaluated_by: user + pass_status: PASS + blocked_status: BLOCKED + revision_status: NEEDS_REVISION + default_threshold: 80 + requires: [] + + - id: specs + workflow_stage: spec-understanding + generates: specs.md + format: markdown + description: User Approved Specs — feature specification from the validated Jira ticket + template: spec-template.md + instruction: | + Transform the Jira specification into a user-approved specs.md artifact. + + ## Process + 1. Read the Specification Analyst preamble and output template in spec.md — follow both. + 2. Read validation.json if available; address missing_elements as assumptions. + 3. Produce user stories (P1/P2/P3) with Given/When/Then acceptance scenarios. + 4. Derive functional requirements (FR-001…), success criteria (SC-001…), assumptions. + 5. No implementation details (languages, frameworks, file paths). + 6. Maximum 3 [NEEDS CLARIFICATION] markers. + + ## APPROVAL GATE + After writing specs.md: + 1. SUMMARIZE: stories, requirements, success criteria, assumptions. + 2. ASK: see schema user_approval_feedback_gate.exit_on_reject.specs.approval_prompt + 3. ON REJECT: **Exit workflow** (see schema user_approval_feedback_gate.exit_on_reject.specs). + Do NOT run user_approval_feedback_gate. Do NOT regenerate specs.md. + Optionally log rejection reason to feedback/specs.yaml. STOP. + Do not unlock Repo Understanding or any downstream stage. + 4. ON APPROVE: Lock specs.md as immutable; unlock Repo Understanding Stage. + gate: + type: approval + evaluated_by: user + on_reject: exit_workflow + exit_on_reject_ref: user_approval_feedback_gate.exit_on_reject.specs + requires: + - validation + + - id: repo-assessment + workflow_stage: repo-understanding + generates: repo-assessment.md + format: markdown + description: Repo Assessment Report — grounded engineering inventory for planning + template: repo-assessment-template.md + instruction: | + Produce a Repo Assessment Report from the approved spec and repository analysis. + + ## Inputs + - User Approved specs.md + - GitHub repository (tree, key files, git metadata) + - agents.md (INPUT — resolved via lookup: change inputs/ → target repo → schema inputs/; + contains operator architecture, agent routing, and test patterns; read in full) + - Functional SME context when provided + + ## Process + 0. Resolve repo target (REQUIRED — see schema target_repo and + working_folder_repo): read inputs/jira.yaml. + - **Working-folder mode:** If use_working_folder_as_repo is true or the + user directs using the working folder as the repo, analyze the current + project checkout (cwd) — do not clone a separate repository. + - **Default mode:** If target_repo is absent or empty, ask the user once, + persist to jira.yaml, and verify the repository is accessible. Do not + proceed until recorded. + 1. Resolve agents.md: check target repo, then schema inputs/. + Read it in full — it contains operator-specific architecture and routing. + If absent, ask the user once to provide it (or proceed PROVISIONAL if declined). + 2. Read and contextualize the repository; cross-reference with specs.md. + 3. Read the Repository Assessment Agent preamble and output template in + repo-assessment.md — follow both. + 4. Document target files, reusable assets, guardrails, and risks with evidence. + 5. Fact-based inventory only — no governance principles (those go in constitution.md input). + + ## APPROVAL GATE + 0. **Eval gate** (see schema stage_eval_gate): score repo-assessment.md against + evals/repo-assessment_eval.yaml; refine artifact + if needed; write eval-results/repo-assessment.yaml. + 1. SUMMARIZE: target files, reusable assets, guardrails, top risks, eval score. + 2. ASK: Approve repo-assessment.md and proceed to Planning? + 3. ON REJECT: Run user_approval_feedback_gate — update templates if required; + regenerate repo-assessment.md only; write round summary to + feedback_stage_artifacts; specs.md and all earlier artifacts remain + immutable. Re-run eval gate; re-ask approval. + 4. ON APPROVE: Lock artifact as immutable; resolve constitution.md (see schema + constitution_md.lookup_order); unlock Planning Stage. + gate: + type: approval + evaluated_by: user + feedback_gate: user_approval_feedback_gate + requires: + - specs + + # constitution is no longer a generated artifact — it is an INPUT. + # See schema constitution_md for lookup order and resolution. + # The template at templates/constitution-template.md is retained for reference and one-time generation + # if no pre-approved constitution exists (see constitution_md.when_missing). + + - id: plan + workflow_stage: planning + generates: plan.md + format: markdown + description: Technical Implementation Plan — architectural blueprint (sections 0–8) + template: plan-template.md + inputs: + required: + - specs.md + - constitution.md (INPUT — resolved via schema constitution_md.lookup_order) + - repo-assessment.md + optional: + - AGENTS.md + - agents.md + - validation.json + instruction: | + Create plan.md — the Technical Implementation Plan for the feature. + + ## Input precedence (conflicts) + 1. constitution.md (non-negotiable guardrails — resolved as INPUT, not generated) + 2. specs.md (validated feature requirements) + 3. repo-assessment.md (repo facts, target files, reuse mandates) + 4. AGENTS.md (optional — agent routing) + 5. validation.json (optional — Stage 0 gaps) + + ## Process + 0. Resolve constitution.md: check target repo first ({target_repo}/constitution.md), + then change inputs/, then schema inputs/ (see schema constitution_md.lookup_order). + If not found, generate once using templates/constitution-template.md and save to inputs/. + 0b. Resolve AGENTS.md: if not found in target repo or change inputs/, ask the user + once to provide AGENTS.md before planning (see schema agents_md.when_missing). + 1. Read all inputs and plan.md template (§0–§8 output schema). + 2. Produce plan.md with all sections §0 through §8 in full. + 3. §1 MUST include a repo-grounded reality check citing repo-assessment. + 4. Each implementation phase MUST use the phase template (Goal, Dependencies, + Target files, Required capabilities, Verification hooks). + 5. Do NOT create tasks, tickets, or code — planning only. + + ## APPROVAL GATE + After writing plan.md: + 0. **Eval gate** (see schema stage_eval_gate): score plan.md against + evals/plan_eval.yaml; refine artifact if cases fail; + write eval-results/plan.yaml. Do not edit schema templates. + 1. SUMMARIZE: phase count (### Phase headings), top risks, open questions, eval score. + 2. ASK: see schema user_approval_feedback_gate.approval_prompt + 3. ON REJECT: Run user_approval_feedback_gate — update template if required; + regenerate plan.md only; write round summary to feedback_stage_artifacts; + approved upstream artifacts are immutable. Re-run eval gate; re-ask approval. + 4. ON APPROVE: Lock plan.md as immutable; unlock Task Creation Stage. + gate: + type: approval + evaluated_by: user + feedback_gate: user_approval_feedback_gate + requires: + - specs + - repo-assessment + + - id: tasks + workflow_stage: task-creation + generates: tasks.md + format: markdown + description: Execution backlog — dependency DAG, agent routing, payloads, orchestration notes + template: tasks-template.md + template_modes_dir: tasks-modes + inputs: + required: + - specs.md + - constitution.md + - repo-assessment.md + - plan.md + optional: + - AGENTS.md + - agents.md + instruction: | + Create tasks.md — an Execution Backlog for downstream code generation. + + You are the Sub-Task Creation Agent. Follow tasks.md (base template — shared rules) + plus mode-specific instructions in templates/tasks-modes/ when generating + in passes (see stage4_tasks.py multipass modes). + + ## AGENTS.md + If AGENTS.md is not in the target repo or change inputs/, ask the user once to + provide it (see schema agents_md.when_missing). Respect AgentRoutingMode from + constitution.md (PROVIDED vs PROVISIONAL). + + ## Input precedence (conflicts) + 1. constitution.md + 2. specs.md (validated_specs.md in prompts) + 3. plan.md (technical_plan.md) + 4. repo-assessment.md (for file paths) + 5. agents.md (routing) + + ## Required output structure (§0–§5) + - §0 Input coverage checklist — map every FR-xx, SC-xx, and plan phase to Task IDs + - §1 Task Dependency Graph (Mermaid) — stable node IDs T1_1, T1_2, … + - §2 Linear Execution Order — valid topological sort + - §3 Task Execution Manifest — table columns: + Task ID | Task Title | Assigned Agent | Phase | Depends On | Parallel OK | Complexity | Risk + - §4 Task Payloads — ### Task <ID>: <Title> with Objective, Target file(s), + Non-goals, Implementation notes, Acceptance criteria, Downstream handoff + - §5 Orchestration notes — Retry Boundaries, Merge Conflict Hotspots, + Open Questions Requiring SME Before Execution + + Header fields: Feature, AgentRoutingMode (from constitution), ConstitutionVersion. + + ## Agent routing + - If agents.md PROVIDED: Assigned Agent must match agents.md IDs exactly. + - If NOT PROVIDED: use provisional IDs (API_Agent, OperatorController_Agent, + ManifestsBindata_Agent, WebhookTLS_Agent, RBACSecurity_Agent, OLMRelease_Agent, + Testing_Agent, Docs_Agent) and mark AgentRoutingMode PROVISIONAL. + + ## Generation modes (matches test harness stage4_tasks.py) + Prefer multipass for large backlogs: + + **Pass 1 — skeleton** (`tasks-modes/skeleton-template.md`): + Generate §0–§3 only + fenced `tasks_index.json` array. + Required JSON fields per task: id, title, summary, phase, depends_on, + agent, parallel_ok, complexity (1|2|3|5|8), risk (Low|Med|High). + + **Pass 2 — payloads** (`tasks-modes/payloads-template.md`): + Generate §4 subsections for each task batch (by plan phase). + May split batches on truncation; never skip a task ID. + + **Pass 3 — orchestration** (`tasks-modes/orchestration-template.md`): + Generate §5 only from full tasks_index.json. + + **Pass 4 — impact_assessment** (`tasks-modes/impact_assessment-template.md`): + When user rejects a phase in phase-feedback mode, classify feedback as + structural (skeleton change) vs payload-level (revision only). + Respond with JSON: structural, affected_phases, revision_notes, + and skeleton_guidance OR payload_guidance. + + **Pass 5 — payload_revision** (`tasks-modes/payload_revision-template.md`): + Re-generate §4 payloads incorporating user feedback and revision guidance. + + **single mode** (`tasks-modes/single-template.md`): + One response with complete §0–§5 (may truncate if >15 tasks). + + Merge scratch artifacts (skeleton + payloads + orchestration) into final tasks.md. + + ## Hard boundaries + - Do NOT write production code, patches, or diffs. + - Do NOT invent file paths not in repo-assessment.md or plan.md. + - Mark Evidence: PARTIAL when repo-assessment confidence is low. + - Pair implementation tasks with verification tasks when constitution requires. + - Complexity: Fibonacci 1, 2, 3, 5, 8. + + ## APPROVAL GATE + After merged tasks.md: + 0. **Eval gate** (see schema stage_eval_gate): score tasks.md against + evals/tasks_eval.yaml; refine artifact if cases fail; + write eval-results/tasks.yaml. Do not edit schema templates. + 1. SUMMARIZE: total tasks, complexity points, high-risk count, parallelisable count, + agent distribution, manifest preview (first 5 rows), eval score. + 2. Optional per-phase gates when phase_feedback is enabled (review each phase's + manifest rows and payload preview before continuing). + 3. ASK: see schema user_approval_feedback_gate.approval_prompt + 4. ON REJECT: Run user_approval_feedback_gate — update template if required; + regenerate tasks.md only (multipass or phase-level per impact assessment); + write round summary to feedback_stage_artifacts; approved upstream artifacts + remain immutable. Re-run eval gate; re-ask approval. + 5. ON APPROVE: Lock tasks.md as immutable; unlock Implementation Stage. + gate: + type: approval + evaluated_by: user + feedback_gate: user_approval_feedback_gate + generation: + mode: multipass + modes_supported: + - multipass + - single + passes: + - id: skeleton + generates_sections: "0-3" + also_generates: tasks_index.json + mode_template: tasks-modes/skeleton-template.md + - id: payloads + generates_sections: "4" + batched_by: plan_phase + mode_template: tasks-modes/payloads-template.md + - id: orchestration + generates_sections: "5" + mode_template: tasks-modes/orchestration-template.md + - id: impact_assessment + mode_template: tasks-modes/impact_assessment-template.md + trigger: phase_feedback_rejection + - id: payload_revision + mode_template: tasks-modes/payload_revision-template.md + trigger: non_structural_phase_rejection + scratch_dir: output/_stage4 + regenerate_orchestration_only: + cli_flag: --pass3-only + requires: multipass + requires_scratch: true + description: >- + Re-run Pass 3 (§5 orchestration) and merge from existing scratch artifacts + (skeleton.md, tasks_index.json, payloads/) without regenerating §0–§4. + env_overrides: + STAGE4_MODE: overrides stage4_mode (multipass | single) + config_keys: + stage4_mode: multipass + stage4_payload_max_tokens: 4096 + stage4_orchestration_max_tokens: 4096 + stage4_keep_scratch: true + stage4_phase_feedback: true + stage4_impact_max_tokens: 1024 + requires: + - plan + + - id: implementation + workflow_stage: implementation + generates: implementation-phase-log.md + template: implementation-template.md + description: >- + OAPE orchestration — task-by-task execution into the fork using composed + design-bundle.md (tasks.md + upstream artifacts) and /oape:* cursor commands. + User approval after every task before advancing. + outputs: + - code changes in user's fork (or project working folder when use_working_folder_as_repo) + - draft pull request on fork_repo_url (skipped in working-folder mode) + - implementation/design-bundle.md (per task, regenerated) + - implementation/task-reports/<task-id>.md (per approved task) + - implementation-report.md + - implementation-checklist.md + - adrs.md (conditional) + inputs: + required: + - tasks.md + - constitution.md + - specs.md + - plan.md + - forked repository URL (inputs/jira.yaml fork_repo_url — ask user if missing; + **skip when use_working_folder_as_repo is true**) + optional: + - repo-assessment.md + - target repository (inputs/jira.yaml target_repo — upstream reference) + - AGENTS.md + - agents.md + instruction: | + You are the OAPE Implementation Orchestrator. + + ## Mission + Execute approved tasks.md **task-by-task** in §2 Linear Execution Order. For + each task, compose a design bundle scoped to that Task ID, invoke applicable + OAPE cursor commands (see schema oape_routing and oape-ai-e2e/AGENTS.md), + verify, run **code-generation evals**, refine code until evals pass, then + **request user approval of the code** before advancing to the next task. + Work in the user's fork working copy, **or the project working folder when + use_working_folder_as_repo is true** (see schema working_folder_repo). Push + and open draft PR when all tasks are approved and complete — **unless + working-folder mode is active** (no push, no draft PR). + + ## Prerequisites + Confirm in openspec/changes/<change>/: + - tasks.md, constitution.md, specs.md, plan.md (approved) + - OAPE commands in .cursor/commands/ (api-generate.md, api-implement.md, etc.) + - gh, go, git, make available; gh authenticated (gh optional in working-folder mode) + + ## Repo / fork setup (REQUIRED — before any OAPE command) + Read inputs/jira.yaml for use_working_folder_as_repo (see schema working_folder_repo). + + **Working-folder mode** (use_working_folder_as_repo true or user directed): + 1. Set cwd to project root (working folder containing openspec/changes/<change>/). + 2. Record working_folder_path in jira.yaml if not already set. + 3. Do NOT ask for fork_repo_url; do NOT clone a fork; do NOT create a feature + branch unless the user explicitly requests one. + 4. All OAPE commands and code edits apply in cwd. + + **Default mode:** + 1. Read inputs/jira.yaml for fork_repo_url (see schema fork_repo). + 2. If fork_repo_url is absent, ASK once for the fork URL; persist to jira.yaml. + 3. Clone or verify fork checkout; record jira_key for branch naming. + 4. Create feature branch (see fork_repo.feature_branch). All work on this branch. + 5. Set cwd to fork root for every OAPE command. + + ## Task and phase parsing + From tasks.md: + 1. Group tasks by §3 Phase column (T<n>_* by numeric prefix). + 2. Within each phase, order tasks by §2 Linear Execution Order. + 3. Respect §1 DAG Depends On — do not start a task until dependencies complete. + 4. Skip tasks already marked `- [x]`. + 5. Skip phases whose tasks are all complete. + 6. If §4 absent, treat backlog as single "All Tasks" phase. + + ## Design bundle (each task) + 1. Create openspec/changes/<change>/implementation/ if missing. + 2. Write implementation/design-bundle.md using templates/design-bundle-template.md. + 3. Include constitution, specs, plan, repo-assessment excerpts + §4 payload + for ONLY the **current Task ID** (not the whole phase). + 4. Derive API specification and Reconciliation workflow sections for OAPE + from the current task payload when applicable. + 5. On task rejection, add REVISION FEEDBACK and regenerate bundle when + re-running the current task. + + ## Task execution loop + FOR EACH pending task in §2 Linear Execution Order (respect §1 Depends On): + + 1. **Compose** design-bundle.md scoped to this Task ID. + 2. **Resolve command** — exactly ONE from oape_routing.command_resolution / + allowed_commands. IF e2e task → e2e-generate. ELIF API_Agent verification-only + → api-generate-tests. ELIF API_Agent → api-generate. ELIF OperatorController_Agent + → api-implement. Do not invoke predict-regressions, review, or any other OAPE command. + 3. **Announce**: Phase, Task ID, Task Title, command. + 4. **Invoke**: Read .cursor/commands/<command_file> and execute its workflow + in the fork cwd. Pass --design-doc with absolute path to design-bundle.md + for api-generate and api-implement only. + 5. **Follow-up**: Run follow_up make targets when defined (make update, etc.). + 6. **Manual agents**: For ManifestsBindata_Agent, OLMRelease_Agent, + RBACSecurity_Agent, WebhookTLS_Agent, Docs_Agent — read + templates/code-generation-template.md for FILE OPERATIONS format; implement this + task's payload directly in cwd with minimal scoped edits; log deviations. + 7. **Verify**: Run Acceptance criteria for this task; record pass/fail. + 8. **Code eval gate** (see schema code_generation_eval_gate and + stage-gate/CODE_GENERATION_EVAL_PROMPT.md): load evals/code-generation_eval.yaml; + filter by resolved oape_command; score fork working copy; write + eval-results/code-generation-<task-id>.yaml; **refine code in fork until + all applicable cases pass or 2 refinement passes** — do NOT ask user + approval before this loop completes. + 9. **Present** task summary including code eval scorecard and files touched. + 10. **ASK**: code_generation_eval_gate.task_approval_prompt (include eval score). + 11. **ON REJECT**: append feedback to design-bundle.md REVISION FEEDBACK; re-run + **this task only** from step 1. + 12. **ON APPROVE**: + - Write `implementation/task-reports/<task-id>.md` (template: implementation-task-report.md) + - Mark task `- [x]` in tasks.md + - Append `implementation-phase-log.md` (link to task report) + - Advance to next task in §2 order + + Allowed OAPE commands (one per task): + - **e2e task** → `/oape:e2e-generate <fork-default-branch>` + - **API_Agent** (implementation) → `/oape:api-generate --design-doc <bundle>` + - **API_Agent** (verification-only) → `/oape:api-generate-tests <api-path>` + - **OperatorController_Agent** → `/oape:api-implement --design-doc <bundle>` + + api-generate-tests path: derive from current task §4 Target file(s) or repo-assessment. + Base branch for e2e-generate: fork default (main/master). + + ## Task response format (presented after each task, before approval) + ### ## TASK SUMMARY + Task ID, title, phase, Assigned Agent, OAPE commands run, files touched. + + ### ## OAPE COMMANDS EXECUTED + | Command | Args | Outcome | + |---------|------|---------| + + ### ## TEST RESULTS + | Test | Result | Notes | + + ### ## CODE GENERATION EVAL SCORECARD + Overall score, cases pass/fail, refinement rounds, eval-driven fixes applied. + Omit section when no applicable eval cases. + + ### ## APPROVAL + Ask user to approve **code changes** for this task (not just the plan). + + ### ## DEVIATIONS (optional) + `- **Task ID**: <description>` — omit when none. + + ## Error handling + - fork_repo_url missing → halt and ask user (**skip when use_working_folder_as_repo**). + - OAPE precheck failure → report, suggest fix, wait for user. + - Test failure on a task → report; do not advance to next task without user decision. + - Never skip task approval gate. + - Invoke exactly one allowed OAPE command per task; never predict-regressions, + review, or any command outside allowed_commands. + + ## Post-loop (all tasks approved) + 0. **Exit check:** When `exit_on_all_tasks_complete` is true (config.yaml flags, + default true), verify all tasks in tasks.md §3 are marked `- [x]`. If all + complete, proceed to closing artifacts below and then EXIT the implementation + stage. Do not prompt for additional work. + 1. Write implementation-report.md — aggregate all `implementation/task-reports/*.md` + 2. Write implementation-checklist.md + 2. Write adrs.md ONLY if any phase logged DEVIATIONS. + 3. **Default mode:** Push and draft PR (see schema fork_repo.draft_pr). + 4. **Working-folder mode:** Do not push or open draft PR; record local changes + and git status in implementation-report.md (PR URL = N/A). + 5. Final summary: phases, tasks, files, tests, deviations; draft PR URL when applicable. + gate: + type: per_task_approval + evaluated_by: user + feedback_gate: user_approval_feedback_gate + prompt: see schema oape_routing.task_approval_prompt + on_reject: >- + Run user_approval_feedback_gate per_task_implementation_variant: append + user feedback to design-bundle.md REVISION FEEDBACK; re-run OAPE commands + for the current task only; do not mark the task complete, do not advance + to the next task, and do not modify approved upstream artifacts. + on_command_failure: >- + Report OAPE precheck or execution failure; do not advance until resolved + or user directs otherwise. + requires: + - tasks + + - id: implementation-report + workflow_stage: implementation + co_generated_with: + - implementation-checklist + generates: implementation-report.md + template: implementation-report-template.md + format: markdown + description: >- + Post-execution summary written at the end of the Implementation Stage after + all tasks are user-approved. + instruction: | + Co-generate at the end of the Implementation Stage (after all tasks approved). + + ## Sections (required) + - **Summary** — one paragraph overview + - **Per-Task Reports** — table linking each approved task to + `implementation/task-reports/<task-id>.md` with eval score and test status + - **Phases Completed** — table: Phase | Tasks | OAPE Commands | Files Changed | Code Eval | Tests | Deviations + - **All Files Changed** — grouped by phase (from task reports) + - **Code Generation Eval Summary** — aggregate pass/fail across all tasks + - **Test Results Summary** + - **Deviations / ADRs** — if any (otherwise state "None") + - **Draft Pull Request** — fork_repo_url, branch name, and PR URL + + Source: read every file under `implementation/task-reports/` written at task approval. + + Source data from the phase log: phase name, tasks executed in order, touched + files, test outcome, and DEVIATIONS text from each approved phase. Include + draft PR details from the post-loop push step. + gate: + type: informational + evaluated_by: agent + requires: + - tasks + + - id: implementation-checklist + workflow_stage: implementation + co_generated_with: + - implementation-report + generates: implementation-checklist.md + template: implementation-checklist-template.md + format: markdown + description: >- + Traceability checklist mapping every touched file to specs.md requirement IDs. + Co-generated with implementation-report.md at stage close. + instruction: | + Co-generate with implementation-report.md at stage close. + + Produce a markdown table: + + | File | Requirement IDs | Reason | + + Map every file touched during implementation to the FR-*, SC-*, and AC-* + IDs from specs.md that the change satisfies. Include a brief reason column. + gate: + type: informational + evaluated_by: agent + requires: + - tasks + + - id: adrs + workflow_stage: implementation + generates: adrs.md + template: adrs-template.md + format: markdown + description: >- + Architectural Decision Records — deviation log written only when one or more + phases reported DEVIATIONS during implementation. + optional: true + instruction: | + Write adrs.md ONLY when at least one phase logged a ## DEVIATIONS section. + + ## Format + ``` + # Architectural Decision Records (Deviations) + + ## <Phase name> + <deviation bullets from that phase> + + --- + + ## <Next phase name> + ... + ``` + + Each deviation bullet MUST reference the Task ID and rationale. + gate: + type: informational + evaluated_by: agent + requires: + - tasks + +apply: + workflow_stage: implementation + artifact: implementation + requires: + - tasks + - specs + - plan + optional_context: + - repo-assessment + - constitution.md (input — resolved via constitution_md.lookup_order) + - AGENTS.md + fork_repo: >- + {fork_repo_url from inputs/jira.yaml — ask at stage start if missing in default + mode; skip when use_working_folder_as_repo is true — use project cwd instead} + working_folder: >- + {working_folder_path from inputs/jira.yaml when use_working_folder_as_repo is true} + upstream_repo: "{target_repo from inputs/jira.yaml — reference only}" + tracks: tasks.md + oape_routing: true + instruction: | + Execute the Implementation Stage via OAPE orchestration. Follow the + `implementation` artifact instruction and schema `oape_routing` exactly. + + Entry: /opsx:apply on a change where tasks.md and upstream artifacts are approved. + + FIRST STEP: Read inputs/jira.yaml for use_working_folder_as_repo. + - **Working-folder mode:** Set cwd to project root; do not ask for fork_repo_url; + do not clone or open draft PR (see schema working_folder_repo). + - **Default mode:** Ask for fork_repo_url if missing. Clone fork, create feature + branch, set cwd to fork root. + + FOR EACH pending task in §2 Linear Execution Order (respect §1 Depends On): + 1. Compose implementation/design-bundle.md scoped to current Task ID only. + 2. Resolve ONE OAPE command per oape_routing.command_resolution (allowed only: + api-generate, api-generate-tests, api-implement, e2e-generate if e2e task). + 3. Execute manual-agent work for this task when assigned (no OAPE command). + 4. Verify per task Acceptance criteria. + 5. Run code_generation_eval_gate (CODE_GENERATION_EVAL_PROMPT.md): score fork code; + refine until evals pass or max 2 passes — do NOT ask user approval before this. + 6. Present task summary with code eval scorecard; ask user to approve CODE + (code_generation_eval_gate.task_approval_prompt). + 7. On approve: write implementation/task-reports/<task-id>.md; mark task `- [x]`; + append implementation-phase-log.md; next task. + 8. On reject: REVISION FEEDBACK in design bundle; re-run current task only. + + After all tasks: write implementation-report.md aggregating all task-reports/. + per task. Do not use predict-regressions, review, or any other OAPE command. + Do not advance to the next task until the user approves the code for the current task. diff --git a/openspec/schemas/openspec-agile-workflow/stage-gate/CODE_GENERATION_EVAL_PROMPT.md b/openspec/schemas/openspec-agile-workflow/stage-gate/CODE_GENERATION_EVAL_PROMPT.md new file mode 100644 index 000000000..af006d12b --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/stage-gate/CODE_GENERATION_EVAL_PROMPT.md @@ -0,0 +1,350 @@ +# Code Generation Eval Gate — Forward workflow (`/opsx-apply` per task) + +Score **generated or modified code** in the fork working copy after each task's OAPE command (or manual agent work), **execute real verification and test commands**, **refine code until evals pass and tests pass**, then present for **user code approval**. + +Paths below are **relative to the schema root** (`openspec/schemas/openspec-agile-workflow/` when installed). + +## Mandatory per-task sequence + +**Do not skip steps. Do not ask for user approval before completing the eval + test execution loop.** + +``` +1. Execute OAPE command (or manual agent work) in fork cwd +2. Execute verification commands (go build, go vet, make targets — capture real exit codes) +3. Run code-generation evals (filter by oape_command) +4. Execute test block (co-generate _test.go for controller tasks; run go test / make test) +5. IF any eval case OR verification OR test fails → fix code → re-run steps 2–4 (up to 2 refinement passes) +6. Present task summary + code eval scorecard + verification results + test results +7. User approves CODE for this task +8. ON APPROVE → write task report → append phase log → mark task [x] → next task +``` + +| Step | User approval allowed? | +|------|------------------------| +| 1–5 | **No** — complete eval + verification + test loop first | +| 6–7 | **Yes** — present full results, then ask | +| 8 | After explicit user Approve only | + +## Eval source + +| Purpose | Path | +|---------|------| +| **Eval cases** | `evals/code-generation_eval.yaml` | +| **Assertion schema** | `evals/stages/code-generation/eval-spec.yaml` | +| **Do NOT edit** | Eval YAML during forward workflow (read-only; cases added via `/eval-loop`) | + +## Step 1 — Resolve filter + +From the current task, determine `oape_command`: + +| Resolved command | `oape_command` filter | +|------------------|------------------------| +| `/oape:api-generate` | `api-generate` | +| `/oape:api-generate-tests` | `api-generate-tests` | +| `/oape:api-implement` | `api-implement` | +| `/oape:e2e-generate` | `e2e-generate` | +| Manual agent (no OAPE) | `manual` | + +Load `evals/code-generation_eval.yaml`. Score only cases where: + +- `oape_command` equals the resolved command, **or** +- `oape_command` is `any` (applies to all tasks) + +If the file is missing, `evals:` is empty, or no cases match: **skip scoring** but **still execute steps 2 and 4** (verification and test block are mandatory for every task). + +## Step 2 — Execute verification commands (mandatory — real execution, not assertions) + +After OAPE command output, **actually execute** the following commands in the fork/working +directory. Capture real exit codes, stdout, and stderr. Do NOT report "PASSED" for +commands that were not executed. + +### 2a. Minimum verification (every task) + +Always run for any package modified by this task: + +```bash +go build <package-under-change>/... +go vet <package-under-change>/... +``` + +Resolve `<package-under-change>` from the task's Target file(s): +- `api/<group>/<version>/<name>_types.go` → `go build ./api/<group>/<version>/...` +- `pkg/controller/<name>/controller.go` → `go build ./pkg/controller/<name>/...` +- `pkg/operator/starter.go` → `go build ./pkg/operator/...` +- Multiple packages modified → run `go build` and `go vet` for each + +### 2b. Task-specific verification + +Run additional commands from the task's **Acceptance criteria** in `tasks.md §4`. +Refer to `agents.md` "Per-task testing" section for the operator-specific verification +matrix mapping task types to commands. + +| Task type | Additional commands | +|-----------|-------------------| +| Codegen (`make generate`, `make manifests`) | `make generate && make manifests && make verify` | +| Feature gate edits | `go test ./<features-package>/... -run <test-pattern>` | +| Bindata / manifest tasks | `make update-bindata && make verify` | +| OLM bundle tasks | `make bundle && hack/verify-bundle.sh` | +| Hack scripts | `bash -n <script-path>` (syntax check) | + +### 2c. Record results + +Record each command with its real exit code: + +```yaml +verification: + commands: + - cmd: "go build ./pkg/controller/<name>/..." + exit_code: 0 + pass: true + - cmd: "go vet ./pkg/controller/<name>/..." + exit_code: 0 + pass: true + - cmd: "go build ./pkg/operator/..." + exit_code: 2 + pass: false + stderr_summary: "pkg/operator/starter.go:42: undefined: <name>.SetupManager" + overall_pass: false +``` + +If any verification command fails: **do not proceed to evals**. Fix the code first, +then re-run verification. This counts toward the 2-pass refinement budget. + +## Step 3 — Score each applicable eval case + +For each filtered case in `evals:`: + +1. Read case `prompt`, `assertions`, `scoring.pass_threshold` +2. Inspect **fork working copy** — `git diff` for this task, changed files, verification output +3. Evaluate against assertion types in `evals/stages/code-generation/eval-spec.yaml` + +| Assertion | Check | +|-----------|--------| +| `must_use_pattern` | String/pattern appears in relevant source files | +| `must_not_use` | Pattern absent (e.g. deprecated client.Create) | +| `must_pass_make_targets` | Listed make targets **actually executed and passed** (use exit code from step 2) | +| `must_match_task_payload` | Code aligns with tasks.md §4 for current Task ID | +| `files_must_exist` | Paths exist in fork | +| `files_must_not_exist` | Paths absent | +| `must_follow_constitution` | No constitution violations in generated code | +| `must_follow_effective_go` | Follow `.cursor/skills/effective-go/SKILL.md` | +| `must_include_tests` | Task-appropriate tests present (co-generated `_test.go` for controller tasks) | +| `must_not_violate_non_goals` | Non-goals from task/spec not violated | +| `must_execute_verification` | Verification commands from step 2 all passed (exit code 0) | +| `must_co_generate_tests` | Controller tasks produced `_test.go` files following the exemplar pattern defined in `agents.md` | + +**`must_pass_make_targets` is now enforced by real execution.** The agent MUST have +actually run the listed make target in step 2 and it MUST have returned exit code 0. +Do NOT mark this assertion as passed based on code inspection alone. + +Record per case: `pass`, `score` (0–100), `failures[]`. + +Overall task code score: average of applicable case scores. Pass if all cases ≥ their `pass_threshold`. + +## Step 4 — Execute test block (mandatory — real execution) + +After eval scoring, run the **test execution block**. The test strategy depends on +the task type. Tests are **real `go test` executions** — not agent assertions. + +### 4a. Classify the task + +| Task type | Condition | Test strategy | +|-----------|-----------|--------------| +| **Controller logic** | `oape_command` is `api-implement` AND task modifies `pkg/controller/<name>/` | Co-generate `_test.go` files → run `go test` | +| **API types** | `oape_command` is `api-generate` AND task creates/modifies `*_types.go` | Run `go build` + `go vet` (sufficient; CRD tests deferred to codegen task) | +| **API tests** | `oape_command` is `api-generate-tests` | Task itself produces tests → run `go test` on generated test package | +| **Feature gate** | Task modifies `features.go` | Run existing feature gate tests | +| **Codegen / verify** | Task runs `make generate`, `make manifests` | Run `make verify` (checks generated files match) | +| **Bindata / manifests** | `oape_command` is `manual` AND task creates YAML/scripts | Run `make verify` or `bash -n` (no Go tests needed) | +| **OLM bundle** | Task modifies bundle/ or CSV | Run `make bundle && hack/verify-bundle.sh` | +| **E2E** | `oape_command` is `e2e-generate` | Run `go build` on test package (full e2e needs live cluster) | + +### 4b. Controller logic tasks — co-generate `_test.go` + +When the task type is **controller logic** (`api-implement` modifying `pkg/controller/`): + +1. **Co-generate test file(s)** alongside the production code, in the same package directory. + Follow the test exemplar pattern defined in **`agents.md`** (section "Tests" or equivalent). + Each production `.go` file gets a matching `_test.go` file. + +2. **First controller task** must also create: + - Mock/fake client file (e.g. `fakes/` directory) — matching the operator's established mock pattern + - `test_utils.go` — shared test helpers (factory functions for CR instances, expected objects) + +3. **Test structure** — table-driven tests using the operator's mock client: + - Successful reconciliation case + - Exists-check failure case + - Create-when-not-exists case + - Update-when-spec-changed case (semantic equality) + - Error propagation cases + +4. **Subsequent controller tasks** add test cases to existing `_test.go` files or create + new ones matching the file being added. + +5. Test files are **permanent parts of the codebase** — committed alongside production code. + +### 4c. Execute tests + +Run the resolved test command and capture real output: + +```bash +# Controller tasks +go test ./pkg/controller/<name>/... -v -count=1 + +# Feature gate tasks +go test ./<features-package>/... -run <TestPattern> -v + +# API test generation tasks +go test ./api/<group>/<version>/... -v -count=1 + +# Full suite (when task Acceptance criteria specifies) +make test +``` + +### 4d. Record test results + +```yaml +test_execution: + strategy: co_generated_tests # or: existing_tests, build_only, make_verify + commands: + - cmd: "go test ./pkg/controller/<name>/... -v -count=1" + exit_code: 0 + pass: true + summary: "ok <module>/pkg/controller/<name> 1.234s" + tests_run: 12 + tests_passed: 12 + tests_failed: 0 + - cmd: "go build ./pkg/operator/..." + exit_code: 0 + pass: true + overall_pass: true + test_files_generated: + - pkg/controller/<name>/deployments_test.go + - pkg/controller/<name>/fakes/fake_ctrl_client.go +``` + +If any test fails: fix code (and/or test), re-run. Counts toward the 2-pass refinement budget +shared with eval refinement. + +## Step 5 — Refine code (mandatory when anything fails) + +If **any** eval case, verification command, or test execution fails, **do not ask for +user approval yet**. Loop: + +1. Load failed case `prompt` + `assertions`, failed verification output, failed test output +2. Load current task §4 payload and design-bundle.md +3. **Fix code (and test files if co-generated) in fork working copy only** — do not modify approved markdown artifacts +4. Re-run verification commands (step 2) +5. Re-score code-generation evals (step 3) +6. Re-run test block (step 4) +7. Repeat until **all pass** OR **2 refinement passes** exhausted + +If still failing after 2 passes: proceed to step 6 with scorecard showing remaining +failures; user decides at approval gate. + +## Step 6 — Write eval results + +``` +openspec/changes/<change-name>/eval-results/code-generation-<task-id>.yaml +``` + +```yaml +task_id: T3_2 +oape_command: api-implement +stage: code-generation +stage_eval_file: evals/code-generation_eval.yaml +scored_at: <ISO8601> +refinement_rounds: 1 +overall_score: 95 +overall_pass: true +verification: + commands: + - cmd: "go build ./pkg/controller/<name>/..." + exit_code: 0 + pass: true + - cmd: "go vet ./pkg/controller/<name>/..." + exit_code: 0 + pass: true + overall_pass: true +test_execution: + strategy: co_generated_tests + commands: + - cmd: "go test ./pkg/controller/<name>/... -v -count=1" + exit_code: 0 + pass: true + tests_run: 8 + tests_passed: 8 + tests_failed: 0 + overall_pass: true + test_files_generated: + - pkg/controller/<name>/deployments_test.go +cases: + - id: eval-r001-codegen-001 + score: 95 + pass: true + failures: [] + - id: eval-r001-codegen-002 + score: 95 + pass: true + failures: [] +``` + +Update `refinement_rounds` after each code-fix pass. + +## Step 7 — Present task summary (code ready for review) + +Include all three result sections: + +1. **Files touched** — paths changed in fork for this task (including co-generated `_test.go` files) +2. **Verification results** — table of executed commands with real exit codes: + + ``` + ### Verification Results + | Command | Exit Code | Result | + |---------|-----------|--------| + | go build ./pkg/controller/<name>/... | 0 | PASSED | + | go vet ./pkg/controller/<name>/... | 0 | PASSED | + ``` + +3. **Test execution results** — table of test commands with real output: + + ``` + ### Test Execution Results + | Command | Tests | Passed | Failed | Result | + |---------|-------|--------|--------|--------| + | go test ./pkg/controller/<name>/... -v | 8 | 8 | 0 | PASSED | + ``` + +4. **Code eval scorecard** — overall %, cases pass/fail, refinement rounds, eval-driven fixes applied +5. **Remaining gaps** — if any cases/tests still fail after max refinement passes + +## Step 8 — User code approval + +Ask (substitute task_id, task_title, verification/test results): + +> **Code eval score: {overall_score}%** ({N}/{M} cases pass). +> **Verification: {V_pass}/{V_total} commands pass. Tests: {T_pass}/{T_total} pass.** +> Approve the **code changes** for task {task_id} ({task_title}) and proceed to the next task? +> **(Approve / Reject with feedback)** + +- **Approve** → step 9 +- **Reject** → add REVISION FEEDBACK to design-bundle; re-run task from step 1 (including full eval + verification + test gate) + +## Step 9 — On approve (record and advance) + +1. Mark task `- [x]` in tasks.md +2. Write **`implementation/task-reports/<task-id>.md`** using `templates/implementation-task-report-template.md` +3. Append section to **`implementation-phase-log.md`** (link to task report) +4. Advance to next pending task + +## Guardrails + +- **Never** present user approval before running code-generation evals AND verification AND test execution +- **Never** advance to the next task without user Approve +- **Never** report "PASSED" for commands that were not actually executed — capture real exit codes +- **Always** run `go build` + `go vet` for every task that produces or modifies Go source files +- **Always** co-generate `_test.go` files for controller logic tasks (`api-implement` modifying `pkg/controller/`) +- Co-generated test files are **permanent** — committed alongside production code (follow the test exemplar in `agents.md`) +- Score **code in fork cwd** — not markdown under `openspec/changes/` +- Task reports accumulate under `implementation/task-reports/` for final `implementation-report.md` +- Refinement budget: **2 passes total** shared across eval failures, verification failures, and test failures diff --git a/openspec/schemas/openspec-agile-workflow/stage-gate/SYSTEM_PROMPT.md b/openspec/schemas/openspec-agile-workflow/stage-gate/SYSTEM_PROMPT.md new file mode 100644 index 000000000..78f25f217 --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/stage-gate/SYSTEM_PROMPT.md @@ -0,0 +1,227 @@ +# Stage Eval Gate — Forward workflow (`/opsx-continue`) + +Score and refine **change artifacts** using stage evals shipped with the schema package. **Do not** modify schema templates or `evals/refined-templates/`. + +Paths below are **relative to the schema root** (`openspec/schemas/openspec-agile-workflow/` when installed, or `schemas/openspec-agile-workflow/` in this distribution repo). + +Read `stage-gate/artifact-eval-map.yaml` for artifact → eval file mapping. + +## When this runs + +After **openspec-agile-workflow** creates one artifact (step 6 of `/opsx-continue`), **before** user approval: + +``` +Generate v1 → Run evals → Refine artifact (v2+) → Present scorecard → User approval → STOP +``` + +Next `/opsx-continue` unlocks the following artifact only after user approved the current one. + +## Template vs eval sources + +| Purpose | Path | +|---------|------| +| **Generate artifact** | `templates/` (via `openspec instructions --json`) | +| **Score artifact** | `evals/<stage>_eval.yaml` | +| **Assertion schema** | `evals/stages/<stage>/eval-spec.yaml` | +| **Do NOT edit** | `schemas/.../templates/`, `evals/refined-templates/` | + +## Step 1 — Generate artifact (v1) + +Use existing `/opsx-continue` flow: + +1. `openspec instructions <artifact-id> --change "<name>" --json` +2. Read all `dependencies` from the change directory +3. Write artifact to `outputPath` (v1) + +Save a copy reference: you will need v1 text for refinement even after overwriting the file. + +## Step 2 — Run stage evals + +Load mapping from `artifact-eval-map.yaml`: + +| `gate` | Action | +|--------|--------| +| `stage_evals` | Load `stage_eval_file`, score every case in `evals:` list | +| `rubric_only` | Score `validation.json` against `schemas/.../validation.md` rubric (no YAML cases) | +| `skip` | Skip eval scoring; proceed to user approval after generation | + +### Scoring each case (`stage_evals`) + +For each entry in `<stage>_eval.yaml` → `evals:`: + +1. Read case `prompt`, `assertions`, `scoring.pass_threshold` (or per-case threshold) +2. Evaluate the **artifact at outputPath** against each assertion type in `evals/stages/<stage>/eval-spec.yaml` +3. Record per case: `pass`, `score` (0–100), `failures[]` (specific missed assertions with quotes) + +**Assertion checks (agent judgment):** + +- `must_mention`: each string appears in artifact (case-insensitive ok for paths; exact for ports/IDs) +- `must_not_mention` / `must_not_claim`: must be absent +- `must_identify_package`, `must_cite_pre_feature_gap`, `must_cite_repo_evidence`, etc.: apply as rubric intent +- `must_include_verification_matrix`, `must_pair_verification_tasks`, …: structural checks on artifact + +Overall stage score: average of case scores (or weighted if case specifies). Stage passes if all cases ≥ their `pass_threshold`. + +### Write eval results + +``` +openspec/changes/<change-name>/eval-results/<artifact-id>.yaml +``` + +```yaml +artifact_id: plan +artifact_path: openspec/changes/my-feature/plan.md +stage: plan +stage_eval_file: evals/plan_eval.yaml +scored_at: <ISO8601> +overall_score: 72 +overall_pass: false +cases: + - id: eval-r001-plan-001 + score: 100 + pass: true + failures: [] + - id: eval-r002-plan-003 + score: 45 + pass: false + failures: + - "must_mention: tamper — not found in §6 verification matrix" +``` + +## Step 3 — Refine artifact (mandatory if any case fails) + +If **any** case fails, produce a **refined artifact** at the same `outputPath` (v2). One refinement pass minimum; re-score after refinement. If still failing, refine again (max 2 auto passes) or present remaining gaps to user at approval. + +### Refinement context bundle (pass ALL of these) + +Include in the refinement prompt — do not regenerate blind: + +| # | Context | Source | +|---|---------|--------| +| 1 | **Draft artifact v1** | Full text before overwrite | +| 2 | **Eval scorecard** | `eval-results/<artifact-id>.yaml` failures | +| 3 | **Failed case details** | `prompt` + `assertions` from each failed case in `*_eval.yaml` | +| 4 | **Openspec instructions** | `instruction`, `template`, `rules`, `context` from `openspec instructions --json` | +| 5 | **Schema template** | `templates/<template>.md` (structure only) | +| 6 | **Dependency artifacts** | All files listed in instructions `dependencies` / change deps | +| 7 | **Change inputs** | `openspec/changes/<name>/inputs/jira.yaml`, `jira-spec.md` if present | +| 8 | **User feedback** | If user rejected prior approval — include verbatim | + +### Refinement rules + +- **Fix only** failed assertions and obvious contradictions with dependencies +- **Preserve** content that already passes eval cases +- **Do not** edit `schemas/.../templates/` or `evals/refined-templates/` +- **Do not** invent facts not in dependencies / inputs +- Overwrite artifact at `outputPath` with v2 + +Re-run Step 2 on v2. Update eval-results file (append `refinement_round: 2` or replace with latest). + +## Step 4 — Generate evaluation report + +After eval scoring (or rubric check for validation), generate an **evaluation report** and write it alongside the artifact: + +**Output path:** `openspec/changes/<change>/<artifact-id>_evaluation_report.md` + +The evaluation report must contain: + +### Report structure + +```markdown +# Evaluation Report: <artifact-id> + +**Change:** <change-name> +**Artifact:** <artifact-id> (<artifact-path>) +**Evaluated at:** <ISO8601 timestamp> + +## Eval Summary + +| Metric | Value | +|--------|-------| +| Overall score | X% | +| Cases passed | N / M | +| Cases failed | F | +| Refinement applied | Yes/No | + +## Cases Detail + +| Case ID | Score | Pass | Failures | +|---------|-------|------|----------| +| ... | ... | ... | ... | + +## Gap Analysis + +Evaluate the generated artifact against: +1. **Input artifacts** used to produce it (listed dependencies) +2. **agents.md** (operator-specific routing, architecture, test patterns) +3. **Template requirements** (structural completeness) + +For each gap found: +- What is missing or inconsistent +- Which input artifact or agents.md section it should have addressed +- Severity: CRITICAL / MODERATE / MINOR + +## Quality Assessment + +- Completeness: Does the artifact cover all requirements from input artifacts? +- Consistency: Does it align with prior approved artifacts? +- Grounding: Are all claims supported by repo evidence or input data? +- Agent routing: Does it correctly use agents.md when applicable? + +## Recommendations + +- Items to verify during review +- Potential issues for downstream stages +``` + +### When to generate + +| Gate type | Evaluation report | +|-----------|-------------------| +| `stage_evals` | Full report with all cases, gaps, and quality assessment | +| `rubric_only` | Report with rubric scoring and gap analysis (no eval cases) | +| `skip` | Minimal report — gap analysis and quality assessment only (no scoring) | + +Always generate the report — even for `skip` gates. The report serves as a quality checkpoint for the user. + +## Step 5 — User approval gate + +Present to user: + +1. **Artifact**: path + short summary of what was produced/refined +2. **Evaluation report**: path to the `<artifact-id>_evaluation_report.md` +3. **Eval scorecard**: overall % + table of cases (pass/fail) + top failures +4. **Gaps identified**: summary of critical/moderate gaps from the evaluation report +5. **Refinement**: "Refined after eval" yes/no; what was added/fixed +6. **Ask**: + +> Eval score: **{overall_score}%** ({N}/{M} cases pass). +> Evaluation report: `openspec/changes/<change>/<artifact-id>_evaluation_report.md` +> Gaps: {critical_count} critical, {moderate_count} moderate, {minor_count} minor +> Approve this artifact and proceed to the next stage? +> **(Approve / Reject with feedback)** + +- **Approve** → mark artifact done per schema gate; STOP (one artifact per `/opsx-continue`) +- **Reject with feedback** → run **user approval feedback gate** (schema `user_approval_feedback_gate`, read `stage-gate/USER_FEEDBACK_PROMPT.md`): load prior artifacts + current template, update template if feedback requires it, regenerate **current artifact only**, write round summary to `feedback_stage_artifacts/`, re-run eval gate if applicable, regenerate evaluation report, re-present this step; loop until approve; do not modify previously approved artifacts + +Do **not** create the next artifact in the same invocation. + +## Constitution as input (not generated) + +Constitution.md is **not** a generated artifact — it is resolved as an input before planning. +See schema `constitution_md.lookup_order`: +1. `{target_repo}/constitution.md` +2. `{target_repo}/CONSTITUTION.md` +3. `openspec/changes/<change>/inputs/constitution.md` +4. `{schema_root}/inputs/constitution.md` + +If not found, the agent generates one using `templates/constitution-template.md` as a one-time step +(not a recurring artifact stage) and saves it to `openspec/changes/<change>/inputs/constitution.md`. + +## Guardrails + +- Forward workflow (eval gate) refines **artifacts only** — not templates +- User rejection feedback loop **may** patch `{schema_root}/templates/` when feedback requires structural changes; record in `feedback_stage_artifacts/` +- Stage evals are **read-only** during forward workflow (do not add cases mid-change unless user asks) +- `evals/refined-templates/` is for `/eval-loop` only +- One artifact (+ eval gate) per `/opsx-continue` invocation diff --git a/openspec/schemas/openspec-agile-workflow/stage-gate/USER_FEEDBACK_PROMPT.md b/openspec/schemas/openspec-agile-workflow/stage-gate/USER_FEEDBACK_PROMPT.md new file mode 100644 index 000000000..d39d278e4 --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/stage-gate/USER_FEEDBACK_PROMPT.md @@ -0,0 +1,196 @@ +# User Approval Feedback Gate — Forward workflow (`/opsx-continue`) + +When the user **rejects with feedback** at artifact approval, run this feedback stage. +**Do not** modify previously approved artifacts. + +**No prompt snapshots:** Do **not** read or write `prompts/<artifact-id>.yaml`. + +## When this runs + +After **stage eval gate** (if applicable) and **user approval prompt**: + +``` +Present artifact → Ask approval → + Approve → lock artifact → unlock next → STOP + Reject with feedback → FEEDBACK LOOP (below) until user approves +``` + +### Feedback loop (repeat until Approve or max rounds reached) + +``` +1. Capture user feedback +2. Check round count against max_feedback_rounds (config.yaml flags, default 3) + — If round N >= max_feedback_rounds: HALT and inform the user that the + maximum feedback rounds have been reached. Present the latest artifact + and suggest revising inputs or starting a new change. Do NOT continue + the loop. +3. Load context (prior artifacts, current artifact, current template, evals, inputs) +4. Update template for this artifact if feedback requires it +5. Regenerate refined artifact(s) +6. Write round summary → feedback_stage_artifacts/ +7. Re-run eval gate (when applicable) +8. Present scorecard + feedback addressed → Ask approval again +``` + +**Exception — `specs.md`:** Rejection **exits the workflow**. Do **not** run this loop. +See [Specs rejection — exit workflow](#specs-rejection--exit-workflow). + +--- + +## Step 1 — Capture feedback + +Record the user's rejection feedback verbatim. You will include it in the round summary file (Step 5). + +Do **not** edit any artifact file with status `done` in `openspec status --change "<name>" --json`. + +--- + +## Step 2 — Load revision context + +Resolve `{schema_root}` (`openspec/schemas/openspec-agile-workflow/` installed, or `schemas/openspec-agile-workflow/` in distribution). + +Load mapping from `{schema_root}/stage-gate/artifact-eval-map.yaml` for the current artifact(s). + +| # | Context | Source | +|---|---------|--------| +| 1 | **Prior approved artifacts** | Every dependency / `requires` artifact with status `done` — **read-only** | +| 2 | **Current artifact draft** | Full text at `outputPath` (post eval-gate version) | +| 3 | **Current template** | `{schema_root}/templates/<schema_template>.md` from artifact-eval-map | +| 4 | **Openspec instructions** | `openspec instructions <artifact-id> --change "<name>" --json` (`instruction`, `rules`, `context`) | +| 5 | **Eval scorecard** | `openspec/changes/<change>/eval-results/<artifact-id>.yaml` if stage evals ran | +| 6 | **Prior feedback rounds** | `openspec/changes/<change>/feedback_stage_artifacts/<artifact-id>/round-*.yaml` | +| 7 | **Change inputs** | `inputs/jira.yaml`, `jira-spec.md` if present | + +For **joint gates** (`repo-assessment` + `constitution`): load both current artifacts and both templates; revise both in one round. + +--- + +## Step 3 — Update template (if required) + +Based on user feedback, decide whether `{schema_root}/templates/<template>.md` needs a structural or guidance update (e.g. missing section skeleton, unclear mandatory headings). + +| Update template? | When | +|------------------|------| +| **Yes** | Feedback asks for sections/structure the template does not currently require | +| **No** | Feedback is content-only and the template already supports the requested shape | + +When updating: + +1. Patch `{schema_root}/templates/<template>.md` in place +2. Keep changes minimal — only what feedback requires +3. Record `template_update.summary` for the round file (Step 5) + +**Do not** edit `evals/refined-templates/` (eval-loop only). + +--- + +## Step 4 — Regenerate refined artifact(s) + +Using: + +- Prior approved artifacts (read-only context) +- Current artifact draft +- Updated template (Step 3) +- User feedback (verbatim) +- Openspec instructions + +Regenerate **only** the current artifact at `outputPath`. For joint gates, regenerate **both** co-generated files — never upstream approved artifacts. + +Address every feedback point. Preserve content that already passes eval cases and does not conflict with feedback. + +--- + +## Step 5 — Write feedback stage summary + +Append one round file: + +``` +openspec/changes/<change>/feedback_stage_artifacts/<artifact-id>/round-<N>.yaml +``` + +Joint gate: + +``` +openspec/changes/<change>/feedback_stage_artifacts/repo-assessment+constitution/round-<N>.yaml +``` + +Use schema in `{schema_root}/feedback_stage_artifacts/README.md`. Include: + +- `user_feedback` (verbatim) +- `template_update` (required, path, summary) +- `artifact_regeneration` (paths, summary) +- `feedback_addressed` (bullet list mapping feedback → change) + +--- + +## Step 6 — Re-run eval gate (when applicable) + +If `artifact-eval-map.yaml` maps this artifact to `gate: stage_evals` or `gate: rubric_only`: + +- Re-score the refined artifact(s) +- Update `openspec/changes/<change>/eval-results/<artifact-id>.yaml` +- Record scores in the round summary + +--- + +## Step 7 — Re-present approval (loop) + +Present: + +1. **Refined artifact(s)** — path + summary of changes +2. **Template update** — yes/no; what changed in `templates/<name>.md` +3. **Eval scorecard** — if evals ran (updated scores) +4. **Feedback addressed** — bullets mapping feedback → template + artifact changes +5. **Round summary path** — `feedback_stage_artifacts/.../round-<N>.yaml` +6. **Immutable inputs** — confirm no upstream approved artifacts were modified + +Ask: + +> Approve this artifact and proceed to the next stage? +> **(Approve / Reject with feedback)** + +- **Approve** → mark artifact done; lock as immutable; STOP +- **Reject with feedback** → return to **Step 1** with new feedback (increment round) + +--- + +## Guardrails + +- **Never** overwrite files for artifacts already marked `done` (except artifact(s) currently under approval) +- If feedback **requires** changing an approved upstream artifact, **stop** and tell the user which stage must be reopened +- **Do not** use or create `prompts/<artifact-id>.yaml` +- **Do not** edit `evals/refined-templates/` +- **Do not** create the next workflow artifact in the same invocation + +--- + +## Co-generated artifacts (repo-assessment + constitution) + +Single joint approval covers both. On reject: + +- One feedback loop revises **both** artifacts and **both** templates if needed +- One shared round summary under `feedback_stage_artifacts/repo-assessment+constitution/` +- Run eval gate separately for each artifact file +- Treat `specs.md` and all earlier artifacts as immutable + +--- + +## Implementation task approval variant + +Implementation runs OAPE **task-by-task**. User approval is required **after every +task** before advancing to the next. On reject, append feedback to +`implementation/design-bundle.md` **REVISION FEEDBACK** and re-run OAPE commands for +the **current task only** — do not use this artifact feedback loop for per-task code. + +--- + +## Specs rejection — exit workflow + +When the user **rejects** `specs.md` at the approval gate: + +1. **Do NOT** run the feedback loop above +2. Optionally record reason in `feedback_stage_artifacts/specs/round-1.yaml` +3. Present schema `exit_on_reject.specs.exit_message` +4. **STOP** — do not create repo-assessment or downstream artifacts + +The user must revise inputs and start fresh (`/opsx-new`) or delete specs and re-run `/opsx-continue`. diff --git a/openspec/schemas/openspec-agile-workflow/stage-gate/artifact-eval-map.yaml b/openspec/schemas/openspec-agile-workflow/stage-gate/artifact-eval-map.yaml new file mode 100644 index 000000000..e12ed23d2 --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/stage-gate/artifact-eval-map.yaml @@ -0,0 +1,85 @@ +# Maps openspec-agile-workflow artifact IDs → stage eval files (forward /opsx-continue gate) +# +# Generation templates: templates/ (via openspec instructions) +# Eval rubrics: evals/<stage>_eval.yaml +# Do NOT refine templates during forward workflow — refine the change artifact only. + +schema_templates_dir: templates/ + +artifacts: + validation: + stage: validation + generates: validation.json + schema_template: validation.md + stage_eval_file: null + eval_spec: null + gate: rubric_only + note: Score against validation.md rubric; no stage *_eval.yaml + + specs: + stage: specs + generates: specs.md + schema_template: spec.md + stage_eval_file: null + eval_spec: null + gate: skip + note: No stage eval file; user approval only after generation + + repo-assessment: + stage: repo-assessment + generates: repo-assessment.md + schema_template: repo-assessment.md + stage_eval_file: evals/repo-assessment_eval.yaml + eval_spec: evals/stages/repo-assessment/eval-spec.yaml + gate: stage_evals + + # constitution is no longer a generated artifact — it is an INPUT. + # See schema constitution_md for lookup order. Template retained at templates/constitution-template.md + # for reference and fallback generation. + + plan: + stage: plan + generates: plan.md + schema_template: plan.md + stage_eval_file: evals/plan_eval.yaml + eval_spec: evals/stages/plan/eval-spec.yaml + gate: stage_evals + + tasks: + stage: tasks + generates: tasks.md + schema_template: tasks.md + stage_eval_file: evals/tasks_eval.yaml + eval_spec: evals/stages/tasks/eval-spec.yaml + gate: stage_evals + + implementation: + stage: implementation + generates: implementation-phase-log.md + schema_template: implementation.md + stage_eval_file: evals/implementation_eval.yaml + eval_spec: evals/stages/implementation/eval-spec.yaml + gate: stage_evals + note: Score phase log / implementation notes when artifact is implementation-phase-log + + implementation-report: + gate: skip + + implementation-checklist: + gate: skip + + adrs: + gate: skip + +eval_results_dir: openspec/changes/<change-name>/eval-results/ + +# Evaluation reports — generated alongside each artifact after eval scoring +evaluation_reports: + enabled: true + output_dir: openspec/changes/<change-name>/ + naming: "<artifact-id>_evaluation_report.md" + description: >- + After eval scoring (or rubric check), generate a structured evaluation report + that presents: eval pass/fail summary, gap analysis against input artifacts + and agents.md, and quality assessment. Present to user alongside the artifact + for approval. diff --git a/openspec/schemas/openspec-agile-workflow/templates/adrs-template.md b/openspec/schemas/openspec-agile-workflow/templates/adrs-template.md new file mode 100644 index 000000000..b33b1e77f --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/templates/adrs-template.md @@ -0,0 +1,19 @@ +# Architectural Decision Records (Deviations) + +**Change**: [CHANGE_NAME] +**Jira**: [JIRA_KEY] + +Write this file ONLY when at least one implementation phase logged a DEVIATIONS section. +Each entry MUST reference the Task ID and rationale. + +--- + +## [Phase name] + +- **Task [ID]**: [Description of deviation from plan.md or task payload, with rationale] + +--- + +## [Next phase name] + +- **Task [ID]**: [Description and rationale] diff --git a/openspec/schemas/openspec-agile-workflow/templates/code-generation-template.md b/openspec/schemas/openspec-agile-workflow/templates/code-generation-template.md new file mode 100644 index 000000000..4b60c4e35 --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/templates/code-generation-template.md @@ -0,0 +1,131 @@ +Role: You are the Code Generation Agent (Robotic Engineer Role). + +## Mission + +Consume the task payloads provided (via implementation/design-bundle.md) and generate +machine-executable code. You build the system incrementally, focusing on small, reviewable +pieces of code — one task at a time. + +## Inputs (via design-bundle.md) + +The design bundle is composed per task from approved upstream artifacts: + +| # | Source | Role | +|---|--------|------| +| 1 | constitution.md | Non-negotiable coding rules — match existing repo patterns exactly | +| 2 | specs.md | Requirements (FR-*, SC-*, AC-*) — trace acceptance criteria | +| 3 | plan.md | Architectural context, phase goals, verification hooks | +| 4 | repo-assessment.md | Target files, Makefile targets, reusable assets (optional) | +| 5 | tasks.md §4 (current Task ID) | Objective, target files, non-goals, acceptance criteria | +| 6 | REVISION FEEDBACK | User feedback from prior task rejection (when re-running) | + +Input precedence on conflicts: constitution → specs → plan → repo-assessment → task payload. + +## OpenSpec execution mode (/opsx:apply) + +Each task resolves to **exactly one** execution route (see schema `oape_routing.command_resolution`): + +| Route | When | Action | +|-------|------|--------| +| `/oape:api-generate` | API_Agent (implementation) | Read `.cursor/commands/api-generate.md`; execute in cwd; pass `--design-doc` | +| `/oape:api-generate-tests` | API_Agent (verification-only) | Read `.cursor/commands/api-generate-tests.md`; execute in cwd | +| `/oape:api-implement` | OperatorController_Agent | Read `.cursor/commands/api-implement.md`; execute in cwd; pass `--design-doc` | +| `/oape:e2e-generate` | E2E / Testing_Agent | Read `.cursor/commands/e2e-generate.md`; execute in cwd | +| **Manual agent** | ManifestsBindata, WebhookTLS, RBACSecurity, OLMRelease, Docs | Apply FILE OPERATIONS below directly in cwd | + +- **One** command per task — never invoke multiple OAPE commands for the same task. +- Forbidden during implementation: `predict-regressions`, `review`, `implement-review-fixes`, `analyze-rfe`, `init`. +- After code changes: verify acceptance criteria → code-generation eval gate scores the code → refine until evals pass → user approves code. + +## Core rules + +1. **Tool usage (manual tasks):** Express every file mutation using the FILE OPERATIONS format + below. Do NOT output raw code outside of a file operation block. +2. **OAPE tasks:** Follow the resolved OAPE command workflow from `.cursor/commands/`. Do not + mix FILE OPERATIONS with OAPE unless the command workflow explicitly requires patches. +3. **No scope creep:** Do not invent new requirements. If a utility is missing from your task + list, note it in the DEVIATIONS section rather than silently improvising. +4. **Validation:** Ensure your generated code explicitly satisfies the Acceptance Criteria + for the current task. +5. **TDD compliance:** If the task payload says "write test before implementation", produce + the test file operation before the implementation file operation. +6. **Strict constraints:** Follow constitution.md conventions exactly. Match existing patterns + in the repository. Respect per-task Non-goals and forbidden edits. +7. **One task:** Do not implement the next task in the same pass. Each invocation covers one + Task ID only. + +## Required response format (manual-agent tasks) + +You MUST structure your response with these sections in order: + +### TASK SUMMARY + +Brief description of what this task implements: Task ID, title, phase, assigned agent. + +### FILE OPERATIONS + +For each file you create, edit, or delete, use one of these formats: + +#### CREATE: `<relative/path/to/file>` +```<language> +<full file content> +``` + +#### EDIT: `<relative/path/to/file>` +##### FIND +```<language> +<exact existing code to locate> +``` +##### REPLACE +```<language> +<replacement code> +``` + +#### DELETE: `<relative/path/to/file>` + +You may include multiple EDIT blocks for the same file. + +### DEVIATIONS (optional) + +If you encountered blockers preventing strict adherence to plan.md, or had to make decisions +not covered by the task payloads, log each deviation here: + +- **Task ID**: `<deviation description and rationale>` + +If there are no deviations, omit this section entirely. + +## Response format (OAPE tasks) + +For tasks routed to an OAPE command, apply code changes in the repository per that command's +workflow. After execution, present: + +### TASK SUMMARY + +Task ID, title, phase, OAPE command invoked, files touched. + +### DEVIATIONS (optional) + +Same format as manual tasks — log any divergence from plan or task payload. + +## Verification (before eval gate) + +After code changes: +- Run acceptance criteria from the current task (e.g. `make test`, task-specific targets) +- Record pass/fail +- Fix obvious compilation or lint failures before the code-generation eval gate scores + +The eval gate (`stage-gate/CODE_GENERATION_EVAL_PROMPT.md`) runs **after** your work, scores +the code, and may refine it up to 2 passes. You do not run the eval gate yourself — the +orchestrator handles that step. + +## What this prompt does NOT cover + +| Concern | Where it lives | +|---------|----------------| +| Fork/repo setup, feature branch, draft PR | Schema `fork_repo`, `working_folder_repo` | +| Code-generation eval scoring + refinement | `stage-gate/CODE_GENERATION_EVAL_PROMPT.md` | +| User approval prompt | Schema `oape_routing.task_approval_prompt` | +| Task report (post-approval) | `templates/implementation-task-report-template.md` | +| Closing report + checklist | `templates/implementation-report-template.md` | +| Design bundle composition | `templates/design-bundle-template.md` | +| Orchestration (task ordering, DAG) | Schema `oape_routing.task_loop` | diff --git a/openspec/schemas/openspec-agile-workflow/templates/constitution-template.md b/openspec/schemas/openspec-agile-workflow/templates/constitution-template.md new file mode 100644 index 000000000..a5e3af3e8 --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/templates/constitution-template.md @@ -0,0 +1,130 @@ +You are the "Constitution Agent": a repository governance analyst for a spec-driven development pipeline. + +## Mission +Analyse the provided repository and produce constitution.md — a document of core principles, +coding conventions, development workflow, and governance rules derived from the codebase itself. +This artifact is injected into all downstream agents (Planning, Task Creation, Code Generation) +as non-negotiable guardrails. + +## Why this matters +Downstream agents must follow the repo's EXISTING patterns. The constitution prevents agents +from introducing incompatible patterns, ignoring existing conventions, or duplicating logic. + +## Inputs (provided in the user message or change context) +- Repository analysis: directory tree, key file contents, git log, branch, commit + (from target repo, working folder, or agent tools — see schema working_folder_repo). +- Feature specification (specs.md): the "what" being built. +- Optional AGENTS.md / agents.md from the target repo or change inputs/: explicit agent + routing and conventions (see schema agents_md). + +## Task +1) Derive Core Principles from the repo's ACTUAL conventions — each principle must be + observable in the codebase (cite file/pattern evidence). No generic best-practice platitudes. +2) Record Additional Constraints: tech stack requirements, compliance standards, deployment policies. +3) Document Development Workflow: code review requirements, testing gates, CI/CD process as + actually practiced (from .github/workflows, Makefile targets, CONTRIBUTING.md, etc.). +4) If AGENTS.md was found: set AgentRoutingMode: PROVIDED and record agent definitions. + If not found: set AgentRoutingMode: PROVISIONAL with provisional agent IDs. +5) Governance section: how this constitution relates to AGENTS.md/CLAUDE.md/CONTRIBUTING.md. + +## Quality rules +- Every principle must be repo-evidence-backed. Do not invent principles. +- Do not include implementation decisions — those belong in plan.md (Planning Stage). +- Do not include file lists or risk analysis — those belong in repo-assessment.md. + +## Output +Output ONLY the complete constitution.md markdown document. +No preamble, no explanation, no code fences — just the document. +Follow the output template structure exactly. + +--- + +## Output Template + +<!-- Companion artifact: repo-assessment.md (target files, reusable assets, risks) --> +# [PROJECT_NAME] Constitution + +**AgentRoutingMode:** PROVIDED | PROVISIONAL +<!-- PROVIDED when AGENTS.md exists in repo; PROVISIONAL otherwise — downstream tasks MUST match this value --> + +**Version**: [CONSTITUTION_VERSION] | **Ratified**: [RATIFICATION_DATE] | **Last Amended**: [LAST_AMENDED_DATE] + +<!-- + QUALITY TARGET: ≥90% against Stage 2 constitution rubric. + Self-check (all must pass): + - Every principle cites observable repo evidence (file path or pattern), not generic best practices. + - No file inventories, hook tables, or risk analysis — those belong in repo-assessment.md only. + - No implementation sequencing — that belongs in plan.md (Stage 3). + - AgentRoutingMode matches whether AGENTS.md was found and parsed. + - Upstream operand vs Open: separate principles where the repo embeds upstream workloads. + - Addon controllers: note controller-runtime exception if repo uses library-go for core + runtime for addons. +--> + +## Core Principles + +### I. [PRINCIPLE_NAME — e.g., Follow Existing Controller Patterns] +[PRINCIPLE_DESCRIPTION — what to do and why, grounded in repo evidence] + +**Evidence:** `[path/to/file.go]` — [one-line observation from actual code, Makefile, or CI config] + +### II. [PRINCIPLE_NAME — e.g., Upstream Operand Separation] +[PRINCIPLE_DESCRIPTION — operator reconciles CR + deploys embedded manifests; do not fork upstream controller logic in operator packages] + +**Evidence:** `[path/to/bindata/or/controller/]` — [pattern observed] + +### III. [PRINCIPLE_NAME — e.g., Test-First / Verification Gates] +[PRINCIPLE_DESCRIPTION — actual test commands and gates from Makefile, hack/verify-*, CI workflows] + +**Evidence:** `Makefile` / `.github/workflows/` — [target names, e.g., `make test`, `hack/verify-*`] + +### IV. [PRINCIPLE_NAME — e.g., Generated Code Discipline] +[PRINCIPLE_DESCRIPTION — what is generated, how to regenerate, what must not be hand-edited] + +**Evidence:** `[path/to/generated/or/codegen]` — [tooling observed] + +### V. [PRINCIPLE_NAME — e.g., RBAC / Security Posture] +[PRINCIPLE_DESCRIPTION — least privilege, secrets handling, cluster-scoped writes justification] + +**Evidence:** `[path/to/rbac/or/manifests]` — [pattern observed] + +### VI. [PRINCIPLE_NAME — e.g., OLM / Release Constraints] +[PRINCIPLE_DESCRIPTION — CSV ownership, relatedImages, feature gates, TechPreview markers if applicable] + +**Evidence:** `[path/to/bundle/or/features.go]` — [pattern observed] + +<!-- Add more principles only when repo evidence supports them. Prefer 5–8 substantive principles over padding. --> + +## Additional Constraints + +<!-- Tech stack, compliance, deployment policies, naming conventions — all evidence-backed --> + +- **[Constraint category]:** [Rule derived from repo] — **Evidence:** `[path]` +- **[Constraint category]:** [Rule derived from repo] — **Evidence:** `[path]` + +## Development Workflow + +<!-- How work actually flows in this repo: review, CI, local verify, bundle generation --> + +| Activity | Requirement | Evidence | +|----------|-------------|----------| +| Local unit tests | [e.g., `make test`] | `Makefile` | +| Full verify | [e.g., `make verify` or `hack/verify-*`] | `hack/` | +| Codegen refresh | [when required after API changes] | `[path]` | +| PR / review | [from CONTRIBUTING.md or team norm] | `[path]` | + +## Agent Routing + +<!-- Only when AgentRoutingMode is PROVIDED — summarize AGENTS.md agent IDs and when to use each. + When PROVISIONAL: list provisional IDs and state that downstream tasks must use them exactly. --> + +| Agent ID | Scope | When to route | +|----------|-------|---------------| +| [AGENT_ID] | [capability] | [task types] | + +## Governance + +- This constitution supersedes ad-hoc conventions for downstream Planning, Task Creation, and Code Generation agents. +- **Amendments:** require documented evidence of repo change; bump Version and Last Amended date. +- **Conflicts:** if spec contradicts constitution, escalate in plan.md §8 — do not silently override. +- **Companion docs:** AGENTS.md / CLAUDE.md / CONTRIBUTING.md — [which takes precedence for what]. +- **Complexity:** new patterns must justify deviation from existing repo conventions with explicit rationale. diff --git a/openspec/schemas/openspec-agile-workflow/templates/design-bundle-template.md b/openspec/schemas/openspec-agile-workflow/templates/design-bundle-template.md new file mode 100644 index 000000000..62da7b77c --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/templates/design-bundle-template.md @@ -0,0 +1,118 @@ +# Implementation Design Bundle + +**Change:** [CHANGE_NAME] +**Jira:** [JIRA_KEY] +**Phase:** [PHASE_NAME] +**Current Task:** [TASK_ID — e.g. T1_1] +**Task Title:** [TASK_TITLE] + +This bundle replaces an OpenShift Enhancement Proposal (EP) when driving OAPE +commands from `/opsx:apply`. It is composed from approved OpenSpec artifacts +and scoped to the **current task only** (one Task ID per OAPE invocation). + +--- + +## Input precedence (conflicts) + +1. constitution.md (non-negotiable guardrails) +2. specs.md (requirements and acceptance criteria) +3. plan.md (architectural context and verification hooks) +4. repo-assessment.md (target files, Makefile targets, evidence) +5. tasks.md §4 payload for the **current Task ID** (most specific) + +--- + +## Constitution (guardrails) + +<!-- Paste or summarize constitution.md sections relevant to this phase --> + +--- + +## Specifications (requirements) + +<!-- Paste or summarize specs.md: user stories, FR-*, SC-*, AC-* traced by this task --> + +--- + +## Plan (architectural context) + +<!-- Paste or summarize plan.md phase goals, target files, verification hooks for this task --> + +--- + +## Repo assessment (grounding) + +<!-- Paste repo-assessment.md excerpts: target paths, Makefile targets, patterns --> + +--- + +## Task payload (current task) + +<!-- Paste tasks.md §4 subsection for the current Task ID only --> + +--- + +## API specification (derived — for oape:api-generate) + +- **Group:** +- **Version:** +- **Kind:** +- **Scope:** Cluster | Namespaced +- **FeatureGate:** (if applicable) + +### Spec fields + +- `fieldName` (type): description + - Validation: + - Default: + - Immutable: + +### Status fields + +- `conditions`: Standard OpenShift conditions +- `observedGeneration`: int64 + +--- + +## Reconciliation workflow (derived — for oape:api-implement) + +1. Validate spec +2. … + +### Dependent resources + +- ConfigMap: … +- Deployment: … + +### Status conditions + +- Available: … +- Progressing: … +- Degraded: … + +### Events + +- … + +### Cleanup / finalizers + +- … + +--- + +## Verification (this task) + +<!-- From current task Acceptance criteria and plan §6 verification matrix --> + +| Hook | Command / test | Task ID | +|------|----------------|---------| +| Unit | make test | [TASK_ID] | +| Integration | … | … | +| E2E | … | … | + +--- + +## Revision feedback (when re-running after task rejection) + +<!-- User feedback from prior task gate rejection — omit when none --> +<!-- Re-run the current task only; compose bundle for that Task ID --> diff --git a/openspec/schemas/openspec-agile-workflow/templates/implementation-checklist-template.md b/openspec/schemas/openspec-agile-workflow/templates/implementation-checklist-template.md new file mode 100644 index 000000000..9752c130e --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/templates/implementation-checklist-template.md @@ -0,0 +1,40 @@ +# [CHECKLIST TYPE] Checklist: [FEATURE NAME] + +**Purpose**: [Brief description of what this checklist covers] +**Created**: [DATE] +**Feature**: [Link to spec.md or relevant documentation] + +**Note**: This checklist is generated by the `__SPECKIT_COMMAND_CHECKLIST__` command based on feature context and requirements. + +<!-- + ============================================================================ + IMPORTANT: The checklist items below are SAMPLE ITEMS for illustration only. + + The __SPECKIT_COMMAND_CHECKLIST__ command MUST replace these with actual items based on: + - User's specific checklist request + - Feature requirements from spec.md + - Technical context from plan.md + - Implementation details from tasks.md + + DO NOT keep these sample items in the generated checklist file. + ============================================================================ +--> + +## [Category 1] + +- [ ] CHK001 First checklist item with clear action +- [ ] CHK002 Second checklist item +- [ ] CHK003 Third checklist item + +## [Category 2] + +- [ ] CHK004 Another category item +- [ ] CHK005 Item with specific criteria +- [ ] CHK006 Final item in this category + +## Notes + +- Check items off as completed: `[x]` +- Add comments or findings inline +- Link to relevant resources or documentation +- Items are numbered sequentially for easy reference diff --git a/openspec/schemas/openspec-agile-workflow/templates/implementation-report-template.md b/openspec/schemas/openspec-agile-workflow/templates/implementation-report-template.md new file mode 100644 index 000000000..7bb5c1171 --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/templates/implementation-report-template.md @@ -0,0 +1,49 @@ +# Implementation Report + +**Change**: [CHANGE_NAME] +**Jira**: [JIRA_KEY] +**Completed**: [DATE] + +## Summary + +[One paragraph overview of what was implemented, aggregate code eval results, test outcomes, and PR status.] + +## Per-Task Reports + +| Task ID | Title | Phase | OAPE Command | Code Eval | Tests | Report | +|---------|-------|-------|--------------|-----------|-------|--------| +| T1_1 | … | Phase 1 | api-generate | 100% (3/3) | PASSED | [task-reports/T1_1.md](implementation/task-reports/T1_1.md) | + +## Phases Completed + +| Phase | Tasks | OAPE Commands | Files Changed | Code Eval (avg) | Tests | Deviations | +|-------|-------|---------------|---------------|-----------------|-------|------------| +| [Phase 1] | T1_1, T1_2 | api-generate, api-implement | [count] | [N]% | PASSED | None | + +## All Files Changed + +### [Phase 1] + +- `relative/path/to/file` — [task T1_1 — brief purpose] + +## Code Generation Eval Summary + +| Task | Score | Cases pass | Refinement rounds | +|------|-------|------------|-------------------| +| T1_1 | 100% | 3/3 | 0 | + +## Test Results Summary + +[Aggregate pass/fail/skip counts and notable failures.] + +## Deviations / ADRs + +[None — or link to adrs.md and summarize deviation count.] + +## Draft Pull Request + +| Field | Value | +|-------|-------| +| Fork | [fork_repo_url] | +| Branch | [feature branch name] | +| PR URL | [draft PR URL] | diff --git a/openspec/schemas/openspec-agile-workflow/templates/implementation-task-report-template.md b/openspec/schemas/openspec-agile-workflow/templates/implementation-task-report-template.md new file mode 100644 index 000000000..0fada6fa2 --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/templates/implementation-task-report-template.md @@ -0,0 +1,70 @@ +# Task Implementation Report + +**Change**: [CHANGE_NAME] +**Task ID**: [TASK_ID] +**Task Title**: [TASK_TITLE] +**Phase**: [PHASE_NAME] +**Assigned Agent**: [ASSIGNED_AGENT] +**OAPE Command**: [OAPE_COMMAND or manual] +**Approved**: [ISO8601_DATE] +**User approved by**: [user confirmation] + +## Summary + +[2–4 sentences: what this task implemented, outcome of OAPE command, eval gate result, and test status.] + +## OAPE Commands Executed + +| Command | Args | Outcome | +|---------|------|---------| +| /oape:… | … | Success / Failed | + +## Code Changes + +### Files modified or created + +| File | Change | +|------|--------| +| `relative/path` | [brief description] | + +### Git diff summary + +``` +[Key hunks or `git diff --stat` for this task's scope] +``` + +## Verification + +| Check | Result | Notes | +|-------|--------|-------| +| Task acceptance criteria | PASSED / FAILED | | +| make targets | PASSED / FAILED | | + +## Code Generation Eval Gate + +**Eval results**: `eval-results/code-generation-[TASK_ID].yaml` + +| Metric | Value | +|--------|-------| +| Overall score | [N]% | +| Cases pass | [N]/[M] | +| Refinement rounds | [0–2] | + +### Cases + +| Case ID | Score | Pass | Notes | +|---------|-------|------|-------| +| eval-r…-codegen-… | … | yes/no | | + +### Eval-driven code fixes applied + +- [List fixes made to pass eval cases — or "None; all cases passed on first score"] + +## Deviations + +[None — or describe deviation from task payload / constitution and rationale] + +## Links + +- Design bundle: `implementation/design-bundle.md` (snapshot at approval time) +- Phase log entry: `implementation-phase-log.md` diff --git a/openspec/schemas/openspec-agile-workflow/templates/implementation-template.md b/openspec/schemas/openspec-agile-workflow/templates/implementation-template.md new file mode 100644 index 000000000..6565ef859 --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/templates/implementation-template.md @@ -0,0 +1,57 @@ +# Implementation Phase Log + +**Change**: [CHANGE_NAME] +**Jira**: [JIRA_KEY] +**Fork**: [FORK_REPO_URL] +**Branch**: [FEATURE_BRANCH] +**Started**: [DATE] + +Append one section per **approved task** during `/opsx:apply`. Each approved task +also gets a full report at `implementation/task-reports/[TASK_ID].md`. + +--- + +## Task: [TASK_ID] — [TASK_TITLE] + +**Phase**: [PHASE_NAME] +**Status**: Approved +**Agent**: [ASSIGNED_AGENT] +**OAPE Command**: [api-generate | api-generate-tests | api-implement | e2e-generate | manual] +**Task report**: [implementation/task-reports/TASK_ID.md] + +### OAPE Commands Executed + +| Command | Args | Outcome | +|---------|------|---------| +| /oape:… | … | Success | + +### Code Generation Eval + +| Metric | Value | +|--------|-------| +| Overall score | [N]% | +| Cases pass | [N]/[M] | +| Refinement rounds | [0–2] | +| Eval results | eval-results/code-generation-[TASK_ID].yaml | + +### Files Touched + +- `relative/path/to/file` + +### Test Results + +| Test | Result | Notes | +|------|--------|-------| +| make test | PASSED | | + +### Deviations + +- [description — omit section when none] + +--- + +## Phase Log Notes + +- Per-task flow: OAPE → verify → code evals → refine code → **user code approval** → task report. +- Design bundle: `implementation/design-bundle.md` (regenerated per task, scoped to one Task ID). +- On reject: REVISION FEEDBACK in design bundle; re-run the current task only. diff --git a/openspec/schemas/openspec-agile-workflow/templates/plan-template.md b/openspec/schemas/openspec-agile-workflow/templates/plan-template.md new file mode 100644 index 000000000..4b8c2b216 --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/templates/plan-template.md @@ -0,0 +1,256 @@ +You are the Technical Planning Agent. + +## Mission +Produce a single markdown document: `technical_plan.md` (per the required schema below). Your output +is the architectural and sequencing blueprint for implementation. It explains HOW work should proceed +and in what order, without creating assignable tasks. + +## Inputs you will receive (user message) +You MUST treat these as authoritative, in this precedence order: +1) constitution.md (non-negotiable guardrails — resolved as INPUT before planning begins; + lookup: target repo → change inputs/ → schema inputs/; see schema constitution_md) +2) validated_specs.md (the validated feature specification) +3) repo_assessment.md (codebase grounding; file paths and reuse mandates) +4) agents.md (optional) SME-defined capability matrix for downstream execution agents +5) spec_validator_results.json (optional) JSON gate results / known gaps + +**constitution.md is a pre-approved input** You MUST read it +in full before producing the plan. All principles and guardrails in constitution.md are +binding — do not skip or summarize them. + +If inputs conflict: +- constitution.md wins unless it explicitly defers to organizational policy elsewhere. +- Otherwise validated_specs.md wins over repo_assessment.md for product behavior. +- repo_assessment.md wins for repository facts (paths/patterns) over assumptions. + +## Hard boundaries (non-negotiable) +- Do NOT write code, patches, or diffs. +- Do NOT create Jira tickets, checklists with assignees, sprint plans, or granular "tasks". +- Do NOT invent file paths, APIs, ports, feature gates, or behaviors not evidenced by the inputs. +- If repo_assessment.md indicates partial tooling / low confidence, include explicit verification + prerequisites rather than guessing. +- **COMPLETENESS IS MANDATORY (target ≥80–85%):** Output ALL sections §0 through §8 in full. + If length-constrained, shorten phase prose — NEVER stop mid-table or mid-phase. §8 MUST list every + open question row completely or state "None — all decisions resolved in this plan." +- **Repo-grounded reality check is mandatory in §1:** Cross-reference repo_assessment.md §0/§1/§11.1. + If the assessment says a feature is NOT on the pinned branch, plan greenfield work — do NOT frame + phases as "verify existing implementation" or "harden existing controller" without branch evidence. + +## constitution.md usage +- Extract and comply with ALL explicit rules: coding standards, testing requirements, security/RBAC + posture, release/OLM constraints, naming, logging, backwards compatibility, documentation mandates. +- Read **AgentRoutingMode** from constitution.md; mirror it in §0 inputs table. +- If constitution requires something not covered by the spec, add it under Open questions OR as an + explicit planning constraint (do not silently expand product scope). + +## agents.md usage +agents.md is an INPUT resolved via lookup order: change inputs/ → target repo → schema inputs/. +It contains operator-specific agent routing, architecture patterns, and test conventions. +Read it in full before planning. + +- If agents.md is PROVIDED: map each phase to concrete agent IDs/capabilities defined there. +- If agents.md is NOT PROVIDED: use the provisional capability taxonomy below and label it clearly as + provisional in section 0 and in each phase. + +Provisional taxonomy (use only when agents.md missing): +API, OperatorController, ManifestsBindata, WebhookTLS, RBACSecurity, OLMRelease, Testing, Docs. + +## Required output schema (markdown headings must match exactly) +Output EXACTLY ONE markdown document using these headings and order: + +# Technical Implementation Plan +**Feature:** [Feature Name] + +## 0. Inputs acknowledged +## 1. Architectural strategy +## 2. Persistence & state +## 3. Interfaces & contracts (operator-native) +### 3.1 Kubernetes APIs (CRDs/CRs) +### 3.2 Controller/runtime interfaces (internal) +### 3.3 Webhooks / admission (if applicable) +### 3.4 RBAC / security boundaries (if applicable) +### 3.5 Packaging / OLM (if applicable) +## 4. Dependencies & sequencing graph +## 5. Implementation phases (logical sequence; NOT tasks) +### Phase 1: ... +(add as many phases as needed; each phase MUST use the phase template below) +## 6. Verification matrix (maps to spec acceptance) +## 7. Risks, migrations, and operational follow-ups +## 8. Open questions / SME decisions + +### Phase template (repeat for every phase) +Each phase MUST include these bullets: +- **Goal:** +- **Dependencies:** (must wait for …) +- **Target files:** (only from repo_assessment.md or marked UNVERIFIED + discovery step) +- **Required capabilities:** (from agents.md OR provisional taxonomy; mark provisional if needed) +- **Verification hooks:** (unit/integration/e2e/manual; name suites/areas if known from inputs) + +### N/A policy +Any subsection that does not apply MUST be `N/A` with a one-line reason. + +## Project-specific planning content expectations + +If an AGENTS.md file is provided for the target repository and it contains a +**Planning Stage Hints** section, apply its project-specific content expectations +(e.g., operator-native thinking patterns, domain-specific concerns, default repo pins) +in addition to the generic guidance in this template. + +## Output hygiene +- No preamble before the H1 title. +- Use concrete but non-granular sequencing; phases are logical groupings, not day-by-day work. +- End with Open questions if anything required by constitution/spec/repo evidence is missing. +- Verification commands MUST match Makefile targets from repo_assessment (e.g., `make test`, not invented targets). + +## Quality self-check (target ≥80–85%) +Before finalizing, verify: +- [ ] §0 inputs table complete; AgentRoutingMode matches constitution.md +- [ ] §1 includes **Repo-grounded reality check** (greenfield / delta / mix) citing repo_assessment +- [ ] Every spec FR and P1 user story maps to ≥1 phase and ≥1 verification matrix row +- [ ] All phases use the full phase template (Goal, Dependencies, Target files, Capabilities, Verification hooks) +- [ ] Target files come only from repo_assessment.md or are marked UNVERIFIED + discovery step +- [ ] §6 verification matrix has rows for Unit, Integration, E2E, Manual (or N/A with reason) +- [ ] §7 risks derived from repo_assessment §5 and §11.1 UNVERIFIED items +- [ ] §8 complete — every open question has owner + default assumption; no truncated rows +- [ ] No false "already exists" claims contradicted by repo_assessment branch verification + +--- + +## Detailed Section Guide + +### § 0. Inputs acknowledged + +| Input | Status | +|-------|--------| +| Spec source | [TICKET_ID or feature name from validated_specs.md] | +| Repo assessment pin | [REPO_URL], branch [BRANCH], commit [COMMIT_SHA] (tooling_status: FULL\|PARTIAL) | +| `agents.md` | PROVIDED / NOT PROVIDED — if not provided, state provisional taxonomy used | +| `spec_validator_results.json` | PROVIDED / NOT PROVIDED | +| `constitution.md` | PROVIDED / PLACEHOLDER — if placeholder, list provisional guardrails assumed | + +### § 1. Architectural strategy + +Prose section: synthesize HOW the feature integrates into the project's existing components and +patterns. Include a **Repo-grounded reality check** paragraph cross-referencing the +repo_assessment.md Key Finding AND §11.1 branch absences to determine whether this is greenfield, +delta/hardening, or a mix. When repo_assessment states code is absent on the pinned branch, phases +MUST follow the documented exemplar pattern for the project (see AGENTS.md if provided). + +### § 2. Persistence & state + +- **Kubernetes objects:** which objects are source-of-truth vs derived/reconciled, labels/annotations + driving behavior. +- **Operand config/state:** flags/env/args, ConfigMaps/Secrets, bindata generation notes. +- **External/platform-injected state:** CNO-injected CA bundles, platform ConfigMaps (only if spec + requires). +- **N/A:** if purely stateless, state "N/A — no new persistence introduced" with brief reason. + +### § 3. Interfaces & contracts (operator-native) + +Define every interface boundary downstream tasks must implement. Derive from spec FRs and +repo_assessment.md target files. Subsections that do not apply must say `N/A — reason`. + +#### 3.1 Kubernetes APIs (CRDs/CRs) +CRDs, CR versions, immutability rules, validation, conversion assumptions. + +#### 3.2 Controller/runtime interfaces (internal) +Key packages/types to introduce or extend (names only; no code). Reconcile inputs/outputs, +status conditions, metrics/health endpoints if applicable. + +#### 3.3 Webhooks / admission (if applicable) +Validating/mutating webhooks, CABundle injection source, TLS issuance path, failure modes. + +#### 3.4 RBAC / security boundaries (if applicable) +Roles, ClusterRoles, ServiceAccount permissions, blast radius. Secrets and cluster-scoped writes +require explicit justification. + +#### 3.5 Packaging / OLM (if applicable) +OLM/CSV ownership rules, bundle layout, image references, feature gates/TechPreview markers. + +### § 4. Dependencies & sequencing graph + +- **Critical path summary:** ordered list of logical sequencing constraints. +- **Parallelizable workstreams:** streams that can proceed concurrently once prerequisites are met. +- **Explicit blockers / external dependencies:** cross-team or cross-repo dependencies. + +### § 5. Implementation phases (logical sequence; NOT tasks) + +Number phases sequentially. Each phase uses the phase template above. Phases MUST NOT contain +assignee names, ticket IDs, or "do X in PR" task lists. Typical ordering for operator projects +(adapt as needed): API/CRD → codegen/deepcopy → controller/operand logic → manifest refresh → +RBAC/webhook wiring → unit/integration tests → e2e + CI → packaging/bundle + docs. +For other project types, derive phase ordering from repo_assessment.md and AGENTS.md. + +### § 6. Verification matrix (maps to spec acceptance) + +| Category | Coverage | Files / Suites | +|----------|----------|----------------| +| Unit | what unit tests cover | file paths from repo_assessment | +| Integration | what integration tests cover | file paths | +| E2E | what e2e tests cover | file paths | +| Manual / Cluster | manual verification steps | runbook commands | +| N/A | what is not tested and why | - | + +### § 7. Risks, migrations, and operational follow-ups + +Derive from repo_assessment.md § 5 risks, UNVERIFIED items, spec gaps, constitution strains. + +- **Upgrade/migration:** ... +- **Compatibility (OpenShift/MicroShift/Hypershift):** ... +- **Upstream API drift risks:** ... +- **[other risks]:** ... + +### § 8. Open questions / SME decisions + +List decisions the plan cannot make without additional input. For each: state the question, who can +answer it (SME / constitution / agents.md / downstream repo), and what the plan assumes if no +answer arrives before Task Creation. + +If no open questions: "None — all decisions resolved in this plan." + +--- + +## User Message Template + +When invoking the Technical Planning Agent, use this format: + +``` +metadata: + feature_name: "<short name>" + planning_date: "<ISO date>" + repo_pin: + primary_repo: "<repo-url>" + branch: "<branch>" + commit: "<sha|unknown>" + inputs: + constitution: PROVIDED # always PROVIDED in your pipeline + validated_specs: PROVIDED + repo_assessment: PROVIDED + agents_md: PROVIDED | NOT_PROVIDED + spec_validator_json: PROVIDED | NOT_PROVIDED + +constitution.md (INPUT — resolved via lookup order: target repo → change inputs/ → schema inputs/): +<<<PASTE constitution.md — this is a pre-approved input; read ALL principles before planning>>> + +validated_specs.md: +<<<PASTE validated spec>>> + +repo_assessment.md: +<<<PASTE repo assessment report>>> + +agents.md (INPUT — resolved via lookup order: change inputs/ → target repo → schema inputs/): +<<<PASTE agents.md — pre-approved input; read ALL routing rules before planning; OR leave exactly the line: NOT_PROVIDED>>> + +spec_validator_results.json: +<<<PASTE JSON OR leave exactly the line: NOT_PROVIDED>>> + +instructions: +Generate `technical_plan.md` content per the system schema. +- If agents_md is NOT_PROVIDED, use the provisional capability taxonomy and label it in section 0 + and in each phase. +- Every phase must include Target files drawn only from repo_assessment.md unless explicitly marked + UNVERIFIED with a discovery step (prefer requesting a repo assessment rerun over guessing paths). +- Map spec goals to phases and to verification hooks in section 6. +- Apply constitution.md strictly; if it blocks an approach from the spec, document the conflict under + Open questions with options (do not choose silently). +``` diff --git a/openspec/schemas/openspec-agile-workflow/templates/repo-assessment-template.md b/openspec/schemas/openspec-agile-workflow/templates/repo-assessment-template.md new file mode 100644 index 000000000..56e49b52b --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/templates/repo-assessment-template.md @@ -0,0 +1,445 @@ +You are the Repository Assessment Agent (Principal Software Engineer). + +Mission: Produce a grounded, repo-evidenced assessment that serves as the FOUNDATION +for planning and implementing new features. This is NOT a structural inventory — it is +a "how to work in this repo" playbook that a downstream Planning AI Agent can use to +produce accurate, repo-grounded implementation plans for any new feature. + +Every section must answer: "What does a Planning AI Agent need to know about this to +produce a safe, accurate, and complete implementation plan for a new feature?" + +Inputs you will receive in the user message: +- Validated spec text + metadata (repo/branch/commit + optional validator summary) +- Repository analysis (auto-collected by the stage2 Python script before your invocation): + directory tree (5 levels), key file contents (README, go.mod, Makefile, Dockerfile, etc.), + and recent git log (last 20 commits). +- agents.md (INPUT — resolved via lookup order: change inputs/ → target repo → schema inputs/; + see schema agents_md). Contains operator-specific agent routing, architecture patterns, and + test conventions. Read it in full to understand the operator's structure. + +Rules: +1) Only assert file paths and symbols supported by repository evidence from your tools. + If tools/repo access are unavailable, set tooling_status to BLOCKED and avoid fabricated paths. +2) Prefer narrow, high-signal file lists over broad dumps. +3) Read ACTUAL source files to understand behavior — do not guess from file names alone. + If you cannot read a file, say so in §12.1 rather than speculating about its contents. +4) For Go/Kubernetes operators: you MUST read controller reconciliation code, API types, + deployment hooks, image resolution logic, and status condition code. A surface-level + file listing without understanding the reconciliation flow is INSUFFICIENT. +5) For any project: you MUST read the Makefile/build config to document developer workflow, + test commands, and verification steps. A feature developer needs to know how to build, + test, and ship — not just what files to edit. +6) If the spec spans multiple repos and the user provided multiple roots, produce clearly + separated per-repo subsections OR separate reports per user instruction. +7) Use the exact Markdown schema below with ALL required headings. + Sections that do not apply to the repo type should contain a single bullet: + "* Not applicable — [brief reason]." +8) When a specific feature spec is provided, tailor all sections to that feature. + When no feature spec is provided (general assessment), document everything a developer + would need for ANY future feature work in this repo. +9) Do NOT copy a prior assessment verbatim. The template defines structure and depth; + each run must be re-grounded in the target repo/branch/commit. Use prior exemplars + only for formatting patterns, not as a content shortcut. +10) **COMPLETENESS IS MANDATORY (≥90% quality target):** Output MUST reach §12 in full. + If approaching output limits, shorten §4.1 field tables and §4.2 hook rows — NEVER truncate + mid-section. Priority order when space-constrained: §0 → §1 → §11.1 branch honesty → + §5–§7 → §8–§9 → §12 → then expand §4 tables. A complete brief §4 beats an incomplete long §4. +11) **Branch verification before feature claims:** Before stating a feature "exists", "is implemented", + or "needs hardening", verify on the pinned branch/commit. If absent, state explicitly in §0, §1.3, + and §11.1 (e.g., "NOT on branch X — greenfield implementation required"). Never assume main/master + has code that the pinned branch lacks. +12) **No draft/meta prose:** Forbidden phrases include "I will now…", "Let me read…", "This assessment + will cover…". Output reads as finished engineering documentation. + +## Exemplar Reference (format only — not content to copy) + +If an AGENTS.md file is provided for the target repository, check its **Repo-Assessment +Stage Hints** section for project-specific exemplar references, deep-dive requirements, +and quality checklist additions. Apply those hints in addition to the generic guidance here. + +Patterns that good assessments demonstrate and your output MUST match: +- §1 before §2 (architecture before file lists) +- Explicit "dead code / do not edit" traps called out in §1.3 and §2 +- §4.2 as a numbered hook/pipeline table with error behavior column +- §5 entries state WHAT to reuse and WHEN (not just file paths) +- §6 guardrails grouped by category (Structural, API, Build, Deployment, Codegen, Security) +- §7 change-cascade table with real `make` / `hack/verify-*` commands from the Makefile +- §8.2 copy-paste-ready test commands +- §9.4 at least one "How to add..." walkthrough tied to actual files +- §11.1 honest UNVERIFIED list (including branch-specific absences) +- §12 preflight checklist + quick-nav table + +## Repo-Type Hinting + +Detect the project type from build files (go.mod, package.json, Cargo.toml, etc.) and +adapt your analysis depth accordingly. The section structure stays the same for all types. + +### Kubernetes/OpenShift Operator Repos +When the repo is a Kubernetes operator (detected via controller-runtime, operator-sdk, +kubebuilder, library-go, OLM bundle, CRDs), you MUST cover: +- Dual/multiple controller framework architectures (library-go vs controller-runtime) +- Complete reconciliation flow: triggers → sync/reconcile → hooks → apply → status +- Image resolution mechanism (RELATED_IMAGE env vars, CSV relatedImages, OLM injection) +- Arg/env/resource override patterns with validation allowlists +- Status condition systems (OpenShift OperatorStatus vs custom conditions) +- Error classification (irrecoverable vs retryable and their effects) +- Feature gate runtime behavior (not just definitions — how gates are checked at runtime) +- OLM lifecycle: CSV replaces/skipRange, installModes, relatedImages, channels +- OpenShift-specific: SCC/pod security, proxy/trusted-CA propagation, CCO/credentials, + Route integration, ClusterOperator status, FIPS build, console integration, feature sets +- Bindata/manifest embedding pipeline (upstream → hack script → bindata → go-bindata → binary) +- Generated code inventory (clientgen, informers, listers, deepcopy, applyconfigurations) + +### Project-Specific Deep-Dive (from AGENTS.md) + +When an AGENTS.md file is provided for the target repository and it contains a +**Repo-Assessment Stage Hints** section, apply ALL the project-specific deep-dive +requirements defined there IN ADDITION to the generic repo-type hints above. + +AGENTS.md deep-dive sections typically cover project-specific: +- Branch verification requirements and anti-patterns +- Architecture details (controller patterns, bootstrap sequences, dead-code traps) +- Reconciliation flow and hook ordering +- Configuration surface (CR spec fields, runtime flags, validation allowlists) +- Image resolution mechanisms +- Status condition systems and error classification +- Feature gate behavior +- Platform integration details (cloud credentials, proxy/CA, FIPS, routes, console) +- Testing structure and coverage gaps + +If no AGENTS.md is provided, rely only on the generic repo-type hints above and +document any gaps in §11.1. + +### Web Application Repos +When the repo is a web application, adapt the same sections to cover: +- Routing architecture, middleware chain, auth patterns +- State management, data fetching patterns +- Component hierarchy and shared utilities +- Build pipeline, bundling, environment config +- API contract and schema validation + +### Library / SDK Repos +- Public API surface, versioning, backwards compatibility constraints +- Consumer patterns and integration points + +--- + +## Output Schema + +The agent MUST output exactly this top-level structure. Do NOT skip sections. +Do NOT reorder sections. Sections that are not applicable should say so explicitly. + +--- + +# Repository Assessment Report +**Feature:** [Feature Name or "General Repository Assessment — <project name>"] + +## 0. Inputs & Tooling +- `repo`, `branch`, `commit` +- `tooling_status`: OK | BLOCKED +- If BLOCKED: one paragraph explaining missing access and what human must provide +- Spec status and summary + +## 1. Architecture Overview +*High-level architectural map so the Planning Agent understands the system before proposing any changes.* + +### 1.1 Project Type & Tech Stack +* Language, framework versions, key dependencies with versions +* Build system (Make, Gradle, npm, etc.) + +### 1.2 Component Map +* Top-level packages/modules and their responsibilities (one-line each) +* Which are hand-written vs generated (mark generated code explicitly) +* Dependency flow between components + +### 1.3 Framework & Pattern Architecture +* What frameworks/patterns drive the codebase (e.g., library-go factory controllers, + controller-runtime reconcilers, Express middleware, React hooks) +* If MULTIPLE frameworks coexist, explain which parts use which and why +* Entry point(s) and bootstrap sequence + +### 1.4 Runtime Data/Control Flow +* How a user action (CR change, API call, UI event) flows through the system end-to-end +* For operators: reconcile trigger → sync/hook chain → resource apply → status update +* For web apps: request → middleware → handler → response +* For libraries: public entry points → internal flow + +## 2. Target Files (Modification & Creation) +*Files the Planner will actively need to change or create for the specified feature.* +*When no specific feature is provided, list key file categories with typical modification targets.* + +Group by logical category (API types, controllers, manifests, config, tests, etc.). +Include granular paths where helpful (e.g. specific bindata YAMLs, per-deployment controller files). +Call out dead-code traps explicitly (e.g. RBAC-only reconcilers that must NOT receive logic). +For each file: +* `path/to/file`: Brief reason why it needs modification. (confidence: high|medium|low) +* `path/to/new_file`: (New) Brief reason for creation. + +## 3. Reference Context (Read-Only) +*Files the Planner must read to understand existing interfaces, patterns, and constraints.* +*Organize by purpose, not just directory.* + +### 3.1 Entry Points & Wiring +* Main entry point, controller/handler registration, manager bootstrap + +### 3.2 API / Interface Patterns +* Type definitions, interface contracts, schema definitions +* Condition/status helpers, metadata utilities + +### 3.3 Build, CI & Tooling +* Build system config, CI pipeline files, linter config +* Dockerfiles, image build pipeline + +### 3.4 Manifest / Config Generation Pipelines +* Hack scripts, code generators, kustomize overlays, Helm charts +* Upstream sync mechanisms + +### 3.5 Test Patterns & Fixtures +* Existing test files that show patterns to follow +* Test data directories, fixture files, sample CRs + +## 4. Configuration Surface & Runtime Behavior +*What is configurable today, and how the runtime processes configuration.* +*This is the baseline all new features build on — the Planning Agent must know what exists +to avoid proposing redundant or conflicting changes.* + +### 4.1 Current Configuration Surface +* For operators: complete list of CR spec fields with types, defaults, and constraints + (one table per CR type). Include what overrides are supported (args, env, resources, + scheduling, labels, replicas) and what validation/allowlists apply. +* For web apps: environment variables, config files, feature flags +* For libraries: public API options, configuration objects + +### 4.2 Reconciliation / Processing Flow (Detailed) +* For operators: step-by-step sync/reconcile sequence with hook ordering + (e.g., "1. withOperandImageOverrideHook → 2. withLogLevel → ... → 13. withCloudCredentials") +* For web apps: request processing pipeline, middleware ordering +* For libraries: initialization and processing stages +* Include what happens on error at each stage (retry, abort, degrade) + +### 4.3 Image / Dependency Resolution +* For operators: how operand images are resolved (RELATED_IMAGE env vars, defaults, + OLM injection path). Document the complete image map. +* For web apps: how external services/APIs are configured +* For libraries: how optional dependencies are resolved + +### 4.4 Status / Health Reporting +* For operators: what conditions exist on each CR, how they're set, what triggers transitions. + Document BOTH status systems if multiple exist (e.g., OpenShift OperatorStatus vs custom conditions). +* Error classification system (irrecoverable vs retryable and their effects on status/retry) +* For web apps: health check endpoints, monitoring hooks +* For libraries: error reporting patterns + +### 4.5 Feature Gate / Feature Flag Mechanism +* How feature gates/flags are defined, registered, and checked at runtime +* What gates exist today, their default state, and graduation criteria +* For OpenShift: interaction with cluster FeatureSet (TechPreview, Custom, etc.) + +## 5. Reusable Assets (Anti-Duplication) +*Existing functions, components, or utilities the Planner MUST use instead of reimplementing.* +*For each asset, explain WHAT it does and WHEN to use it — not just that it exists.* + +* `path/to/asset`: Use `FunctionName()` for [specific purpose] instead of reimplementing. + Evidence: [how you verified this exists and what it does]. + +Include library dependencies that provide reusable patterns: +* `dependency@version` — use for [specific capability]. Do not reimplement. + +## 6. Architectural Guardrails +*Rules the Planner MUST follow based on current repository patterns.* +*Each guardrail must cite evidence (file, pattern, or convention observed).* + +Organize guardrails by category: +- **Structural**: component boundaries, naming conventions, module organization +- **API / Schema**: versioning rules, backwards compatibility, field constraints +- **Build / Tooling**: compiler version, linter rules, required build flags (e.g., FIPS) +- **Deployment / Packaging**: how artifacts are built, bundled, and shipped +- **Code Generation**: what is generated, how to regenerate, verification steps +- **Security**: authentication patterns, authorization model, crypto constraints + +## 7. Change Cascade Checklist +*When you change X, you MUST also change Y. This is the most critical section for the Planning Agent — +it prevents plans that miss cascading steps and fail CI.* +*Format as a table mapping trigger → required cascading changes → verification command.* + +| When you change... | You must also... | Verify with... | +|---|---|---| +| Example: API type fields in `api/` | Regenerate deepcopy, update CRD bases, regenerate bundle, update swagger | `make generate && make manifests && make bundle && make verify` | + +Include ALL cascades discovered in the codebase (type changes, RBAC, images, bindata, etc.). + +## 8. Test & CI Reference +*How to test changes and what CI will enforce — the Planning Agent must include test tasks in every plan.* + +### 8.1 Test Structure +* Test directory layout (unit, integration, e2e locations) +* Frameworks used per test tier (testing, testify, ginkgo, jest, pytest, etc.) +* Key test helper files and shared fixtures + +### 8.2 How to Run Tests Locally +* Exact commands for each test tier (copy-paste ready) +* Required environment variables or prerequisites +* Expected runtimes + +### 8.3 CI Pipeline +* What jobs run on PR (required vs optional) +* What each job checks (verify, unit, e2e, lint, FIPS scan, etc.) +* Platform/label filters for e2e (e.g., Ginkgo label expressions) +* Where CI config lives (in-repo or external, e.g., openshift/release) + +### 8.4 Test Coverage Gaps +* Which packages/areas have tests vs which lack them +* Areas where e2e is the only coverage (no unit tests) +* This helps the developer know where to add tests for new features + +## 9. Developer Workflow +*Practical workflow reference so the Planning Agent can include correct build/verify/generate +steps in the implementation plan.* + +### 9.1 Key Commands Reference +* Complete list of important build/test/verify/generate targets (table format) +* Which commands to run before pushing (the "preflight checklist") + +### 9.2 Version Variables +* All version variables that control the build (operand versions, tool versions, Go version) +* Where they're defined and when they need updating + +### 9.3 Local Development Setup +* How to run the project locally for development +* Required tools and their versions +* Environment variables needed + +### 9.4 Common Development Scenarios +* "How to add a new API field" — step-by-step +* "How to add a new controller/handler" — step-by-step +* "How to add a new operand/component" — step-by-step (if applicable) +* These walkthroughs should follow the ACTUAL patterns observed in the codebase, + citing specific files and commits as examples. + +## 10. Platform & Environment Integration +*Platform-specific concerns that constrain or enable features.* +*The Planning Agent must account for these in every plan — ignoring them causes runtime failures.* +*Skip sub-sections that don't apply to the project type.* + +### 10.1 Security Context & Permissions +* For OpenShift: SCC constraints, pod security context patterns +* For K8s: PodSecurityStandards, RBAC model +* For web apps: authentication/authorization model, CORS, CSP + +### 10.2 Proxy & Network Configuration +* How proxy settings propagate (e.g., OLM → operator → operands) +* Trusted CA bundle injection mechanism +* Network policy patterns + +### 10.3 Cloud Provider Integration +* Credential provisioning (CCO, CredentialsRequest, workload identity, etc.) +* Which cloud providers are supported, how credentials reach components +* What's NOT supported (explicitly document gaps) + +### 10.4 Build & Compliance Constraints +* FIPS compliance: build flags, crypto library, verification +* Multi-arch support: build matrix, Dockerfile patterns +* Disconnected/air-gapped support: image pinning, relatedImages + +### 10.5 Console / UI Integration +* Console plugins, YAML samples, quickstarts +* CLI tooling, dashboards + +### 10.6 Packaging & Lifecycle +* For OLM operators: CSV structure, upgrade path (replaces/skipRange), channels, + installModes, scorecard tests +* For web apps: deployment strategy, rollback mechanism +* For libraries: publishing, versioning, changelog + +## 11. Risks & Downstream Impacts +*Warnings for the Planner regarding potential breakages and high-risk areas.* +*Each risk should include: what can break, why, and how to mitigate.* + +* **Risk Name:** Description of what can go wrong. Impact: [scope]. Mitigation: [action]. + +### 11.1 Assessment Limitations / UNVERIFIED Items +*Bulleted list of anything that could not be confirmed from repo evidence.* +*For each item, state what would need to be verified and how.* + +* "file/path not opened — verify [specific concern] by reading [specific file/function]." + +## 12. Quick Reference Card +*A condensed cheat sheet the Planning Agent references when constructing implementation task sequences.* + +### Preflight Checklist (run before every PR) +``` +1. make <verify-command> +2. make <test-command> +3. make <lint-command> +4. <any other required checks> +``` + +### Key File Quick-Nav +| I want to... | Look at... | +|---|---| +| Add a new API field | `path/to/types.go` | +| Add a new controller | `path/to/controller_registration` | +| Add a new manifest | `path/to/bindata/` + `path/to/static_resource_controller` | +| Change RBAC | `path/to/rbac/` (NOT the generated bundle) | +| Add a test | `path/to/test/pattern_to_follow` | + +--- + +## Quality Checklist (self-check before output — target ≥90%) + +Before finalizing your assessment, verify ALL items pass: +- [ ] **COMPLETENESS:** Document reaches §12 with no truncated tables, lists, or mid-sentence cuts +- [ ] **§0 branch pin:** Repo URL, branch, commit, tooling_status, and spec status recorded +- [ ] **Feature tailoring:** When spec provided, §1–§4 reference the feature's CR/operand/controller path +- [ ] **Branch honesty:** §11.1 lists branch-specific absences (code NOT present on pinned branch) +- [ ] Section 1 comes before §2 and explains architecture without requiring source reads +- [ ] §1.3 calls out dead-code / RBAC-placeholder traps; §2 repeats critical "do not edit" warnings +- [ ] Section 4.1 lists configurable fields in tables (abbreviate if needed — do not omit section) +- [ ] Section 4.2 is a numbered hook/pipeline table with error behavior — from reading code, not file names +- [ ] Sections 5–6 present (Reusable Assets + Guardrails by category) — mandatory, not optional +- [ ] Section 7 has a concrete change cascade table with real verification commands from Makefile/hack/ +- [ ] Section 8.2 has copy-paste-ready test commands (use actual Makefile target names) +- [ ] Section 9.4 has at least one "How to add..." walkthrough based on observed patterns +- [ ] Section 11.1 honestly lists UNVERIFIED items AND branch-specific absences +- [ ] Section 12 has preflight checklist + quick-nav table +- [ ] No file path is asserted without evidence (tool output or provided repo analysis) +- [ ] No draft/meta sentences ("I will now read...") — output reads as finished work +- [ ] The assessment answers "how to work in this repo" not just "what files exist" +- [ ] Greenfield vs delta/hardening conclusion is explicit when spec feature is absent on branch +- [ ] If AGENTS.md provided: all project-specific quality checklist items from its + Repo-Assessment Stage Hints section are satisfied + +--- + +## User Message Template + +When invoking the Repository Assessment Agent, use this format: + +``` +metadata: + spec_filename: spec.md + validated_spec_status: PASS | NEEDS_REVISION | UNKNOWN + spec_validator_results: optional-path-or-inline-json + repos: + - name: primary + root: <path-or-clone-url> + branch: <branch> + commit: <sha|unknown> + # - name: secondary + # root: ... + # branch: ... + # commit: ... + +output: + format: markdown # markdown | markdown+manifest_json + multi_repo_layout: per_repo_files # per_repo_files | single_file_sections + +validated_spec: +<<<PASTE VALIDATED SPEC TEXT>>> + +task: +Generate the Repository Assessment Report using the system schema. +Every non-obvious path bullet must include a one-line evidence pointer +(e.g., "found controller registration in cmd/..." or "matches existing feature X in pkg/..."). +If evidence is weak, mark confidence explicitly in the bullet text: (confidence: medium). +``` diff --git a/openspec/schemas/openspec-agile-workflow/templates/spec-template.md b/openspec/schemas/openspec-agile-workflow/templates/spec-template.md new file mode 100644 index 000000000..110ff645a --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/templates/spec-template.md @@ -0,0 +1,201 @@ +You are the "Specification Analyst": a requirements transformation agent for a spec-driven development pipeline. + +## Mission +Transform a raw Jira ticket (plus optional validation context from Stage 0) into a clean, +technology-agnostic feature specification (specs.md) that downstream Planning and +Code-Generation agents can reason about without needing the original ticket. + +## Why this matters +The spec MUST NOT contain implementation details (languages, frameworks, APIs, file paths). +It must only express user value and business requirements. Planning agents fail when specs mix +"what" with "how"—keep them strictly separated. + +## Inputs (provided in the user message or change inputs) +- Jira ticket content: summary, description, linked issues, subtasks, comments. +- `openspec/changes/<change>/inputs/jira-spec.md` when present. +- Optional Stage 0 validation context from `validation.json`: missing_elements, quality_issues, + non_blockers. When present, address each item explicitly in the generated spec. + +## Task +1) Extract user stories from the ticket. Assign priorities (P1 critical, P2 important, P3 nice-to-have). + Each story must be independently testable with acceptance scenarios in Given/When/Then format. +2) Derive functional requirements (FR-001, FR-002, ...) from ticket description and acceptance criteria. + Every requirement must be testable and unambiguous. No implementation details. +3) Identify data/domain entities if the feature involves data (Key Entities section). +4) Define measurable, technology-agnostic success criteria (SC-001, SC-002, ...). +5) Document all assumptions — reasonable defaults for anything the ticket does not specify. + If Stage 0 flagged missing_elements, add an explicit Assumption for each. + +## Quality rules +- No implementation details (no language names, no framework names, no file paths, no API endpoints). +- Focused on user value and business needs, not technical approach. +- Requirements are testable: each FR must map to at least one Given/When/Then scenario. +- Success criteria are measurable: quantified outcomes, not adjectives. +- Maximum 3 [NEEDS CLARIFICATION] markers — only for decisions that significantly impact scope. + All other gaps must be resolved with a stated assumption. +- All mandatory sections completed (see output template below). + +## Output +Output ONLY the complete specs.md markdown document. +No preamble, no explanation, no code fences — just the document. +Follow the output template structure exactly. + +--- + +## Output Template + +# Feature Specification: [FEATURE NAME] + +**Feature Branch**: `[###-feature-name]` + +**Created**: [DATE] + +**Status**: Draft + +**Input**: User description: "$ARGUMENTS" + +<!-- + QUALITY TARGET: ≥95% against the Stage 1 rubric before output is final. + Self-check (all must pass): + - Every FR maps to ≥1 Given/When/Then scenario; every P1 story has ≥2 scenarios. + - Zero implementation leakage (no languages, frameworks, file paths, API groups, version pins). + - Success criteria are user-observable outcomes — NOT CI gates, release processes, or internal milestones. + - Edge cases state concrete outcomes (not open questions); resolve singleton/scope ambiguities in FR text. + - At most 3 [NEEDS CLARIFICATION] markers total; all other gaps become numbered Assumptions (A-001…). + - Assumptions section is complete — one bullet per unresolved ticket gap or Stage 0 missing_element. +--> + +## User Scenarios & Testing *(mandatory)* + +<!-- + IMPORTANT: User stories should be PRIORITIZED as user journeys ordered by importance. + Each user story/journey must be INDEPENDENTLY TESTABLE - meaning if you implement just ONE of them, + you should still have a viable MVP (Minimum Viable Product) that delivers value. + + Assign priorities (P1, P2, P3, etc.) to each story, where P1 is the most critical. + Think of each story as a standalone slice of functionality that can be: + - Developed independently + - Tested independently + - Deployed independently + - Demonstrated to users independently +--> + +### User Story 1 - [Brief Title] (Priority: P1) + +[Describe this user journey in plain language] + +**Why this priority**: [Explain the value and why it has this priority level] + +**Independent Test**: [Describe how this can be tested independently - e.g., "Can be fully tested by [specific action] and delivers [specific value]"] + +**Acceptance Scenarios**: + +1. **Given** [initial state], **When** [action], **Then** [expected outcome] +2. **Given** [initial state], **When** [action], **Then** [expected outcome] + +--- + +### User Story 2 - [Brief Title] (Priority: P2) + +[Describe this user journey in plain language] + +**Why this priority**: [Explain the value and why it has this priority level] + +**Independent Test**: [Describe how this can be tested independently] + +**Acceptance Scenarios**: + +1. **Given** [initial state], **When** [action], **Then** [expected outcome] + +--- + +### User Story 3 - [Brief Title] (Priority: P3) + +[Describe this user journey in plain language] + +**Why this priority**: [Explain the value and why it has this priority level] + +**Independent Test**: [Describe how this can be tested independently] + +**Acceptance Scenarios**: + +1. **Given** [initial state], **When** [action], **Then** [expected outcome] + +--- + +[Add more user stories as needed, each with an assigned priority] + +### Edge Cases + +<!-- + ACTION REQUIRED: Each edge case MUST state a concrete user-visible or operator-visible outcome. + Format: "**When** [condition], **then** [observable outcome]." — NOT "What happens when…?" + Cover: invalid input, missing prerequisites, concurrent/conflicting config, disable/teardown, + upgrade from prior release state, and platform constraints mentioned in the ticket. +--> + +- **When** [boundary condition], **then** [observable outcome — error message, degraded state, or safe default]. +- **When** [error scenario], **then** [observable outcome — retry, block, or partial success with status]. + +## Requirements *(mandatory)* + +<!-- + ACTION REQUIRED: The content in this section represents placeholders. + Fill them out with the right functional requirements. +--> + +### Functional Requirements + +<!-- + RULES: + - Use "System MUST" / "Operator MUST" / "Administrator MUST" — technology-agnostic verbs only. + - Resolve scope ambiguities here (e.g., singleton vs namespaced, required vs optional fields). + - Do NOT name CRD kinds, API groups, Go packages, Helm charts, or upstream version numbers. + - Prefer definitive requirements; use [NEEDS CLARIFICATION: …] only when scope truly forks (max 3 total). +--> + +- **FR-001**: System MUST [specific capability, e.g., "allow cluster administrators to enable the feature via the operator API"] +- **FR-002**: System MUST [specific capability, e.g., "reject invalid configuration with a clear validation error"] +- **FR-003**: Users MUST be able to [key interaction, e.g., "disable the feature without removing unrelated operands"] +- **FR-004**: System MUST [data requirement, e.g., "preserve existing trust bundles when the feature is disabled"] +- **FR-005**: System MUST [behavior, e.g., "surface health/degraded status when prerequisites are unmet"] + +### Key Entities *(include if feature involves data)* + +- **[Entity 1]**: [What it represents, key attributes without implementation] +- **[Entity 2]**: [What it represents, relationships to other entities] + +## Success Criteria *(mandatory)* + +<!-- + ACTION REQUIRED: Define measurable success criteria. + These must be technology-agnostic and measurable. +--> + +### Measurable Outcomes + +<!-- + RULES: + - Each SC must be verifiable by a user, admin, or cluster observer WITHOUT reading source code. + - GOOD: time-to-complete, error-rate, availability, observable status conditions, documented runbook steps. + - BAD: "unit tests pass", "CSV updated", "code merged", "e2e green" — those belong in Plan/Tasks, not here. + - Map each SC to at least one FR and one acceptance scenario where practical. +--> + +- **SC-001**: [User-observable metric, e.g., "Administrator can enable the feature and see Ready status within 5 minutes on a standard cluster"] +- **SC-002**: [Reliability metric, e.g., "Invalid configuration is rejected before any workload is created"] +- **SC-003**: [Operational metric, e.g., "Disabling the feature removes managed resources without affecting unrelated operator functions"] +- **SC-004**: [Upgrade metric, e.g., "Existing clusters upgrade without manual intervention when prerequisites are met"] + +## Assumptions + +<!-- + ACTION REQUIRED: Number every assumption A-001, A-002, … + Each assumption resolves a ticket gap that is NOT marked [NEEDS CLARIFICATION]. + Include release/scope boundaries (Tech Preview vs GA), platform targets, and dependency on existing operator APIs. +--> + +- **A-001**: [Assumption about target users or personas, e.g., "Cluster administrators manage this feature via the operator API"] +- **A-002**: [Scope boundary, e.g., "Hypershift-specific behavior is out of scope unless the ticket explicitly requires it"] +- **A-003**: [Environment assumption, e.g., "Target OpenShift version supports the operator's existing feature-gate mechanism"] +- **A-004**: [Dependency assumption, e.g., "Core platform components are already installed and healthy before this feature is enabled"] diff --git a/openspec/schemas/openspec-agile-workflow/templates/tasks-modes/impact_assessment-template.md b/openspec/schemas/openspec-agile-workflow/templates/tasks-modes/impact_assessment-template.md new file mode 100644 index 000000000..ce879e605 --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/templates/tasks-modes/impact_assessment-template.md @@ -0,0 +1,43 @@ +## Mode: Impact Assessment (`pass_mode: impact_assessment`) + +You will receive: +- User feedback on a specific phase's payloads +- The full `tasks_index.json` (all tasks across all phases) +- The name of the phase being reviewed +- A list of phases already approved by the user + +Determine whether the user's feedback requires **structural changes** (adding, removing, splitting, +or re-routing tasks; changing dependencies between tasks) or only **payload-level revisions** +(updating implementation notes, acceptance criteria, target files, or other content within +existing task payloads without changing the task structure). + +Respond with ONLY a JSON object (no markdown fencing, no explanation outside the JSON): + +```json +{ + "structural": false, + "affected_phases": ["Phase 3: Phase Name"], + "revision_notes": "Brief description of what changed and why.", + "payload_guidance": "Specific guidance for re-generating the phase's payloads." +} +``` + +When `structural` is `true`, include `skeleton_guidance` instead of `payload_guidance`: + +```json +{ + "structural": true, + "affected_phases": ["Phase 3: Phase Name", "Phase 4: Phase Name"], + "revision_notes": "Brief description of structural changes needed.", + "skeleton_guidance": "Specific guidance for regenerating the skeleton: which tasks to add/remove/split, which dependency edges change." +} +``` + +Rules: +- `structural: true` when feedback requires adding new tasks, removing tasks, splitting a task + into multiple, merging tasks, changing task IDs, or altering dependency edges in the DAG. +- `structural: false` when feedback only changes content within existing task payloads (objectives, + implementation notes, acceptance criteria, target files, non-goals). +- `affected_phases` must list ALL phases whose tasks are affected — including downstream phases + whose dependencies or payloads reference the changed tasks. +- When in doubt, prefer `structural: true` (conservative — triggers full re-evaluation). diff --git a/openspec/schemas/openspec-agile-workflow/templates/tasks-modes/orchestration-template.md b/openspec/schemas/openspec-agile-workflow/templates/tasks-modes/orchestration-template.md new file mode 100644 index 000000000..9755b4b2c --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/templates/tasks-modes/orchestration-template.md @@ -0,0 +1,23 @@ +## Mode: Orchestration (`pass_mode: orchestration`) + +You will receive: +- The `tasks_index.json` (full list) +- Brief context from constitution.md + +Generate ONLY the §5 content, using this EXACT `##` heading: + +## 5. Orchestration notes (non-code) + +#### Retry Boundaries +- [RETRY_GUIDANCE] + +#### Merge Conflict Hotspots +- [HOTSPOT_FILES_AND_MITIGATION] + +#### Open Questions Requiring SME Before Execution +- [OPEN_QUESTION]: blocks [TASK_IDS] + +### Quality self-check +- [ ] `## 5.` heading present +- [ ] All 3 subsections (Retry Boundaries, Merge Conflict Hotspots, Open Questions) present +- [ ] Document ends cleanly after the last subsection diff --git a/openspec/schemas/openspec-agile-workflow/templates/tasks-modes/payload_revision-template.md b/openspec/schemas/openspec-agile-workflow/templates/tasks-modes/payload_revision-template.md new file mode 100644 index 000000000..10a816c8c --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/templates/tasks-modes/payload_revision-template.md @@ -0,0 +1,25 @@ +## Mode: Payload Revision (`pass_mode: payload_revision`) + +You will receive: +- The `tasks_index.json` entries for the tasks being revised +- User feedback and revision guidance from the impact assessment +- All accumulated feedback history for context +- Relevant excerpts from plan.md, specs.md, repo-assessment.md, constitution.md + +Generate ONLY `### Task <ID>: <Title>` subsections for the listed task IDs, incorporating +the user's feedback. Do not emit §0–§3 or §5. Address every point in the feedback directly. + +### § 4. Task Specifications — payload format + +### Task <ID>: <Title> +- **Objective:** ... +- **Target file(s):** ... (from repo_assessment/plan only) +- **Non-goals / forbidden edits:** ... (pull from constitution + plan guardrails) +- **Implementation notes:** ... (non-code; constraints, patterns to follow) +- **Acceptance criteria:** ... (must trace to validated_specs.md; include tests to run/areas) +- **Downstream handoff:** expected artifacts for codegen agent (files touched, contracts frozen) + +### Quality self-check +- [ ] Every Task ID in the provided list has a matching payload subsection +- [ ] Every point in the user's feedback is addressed +- [ ] No truncated mid-task payloads; last payload ends cleanly diff --git a/openspec/schemas/openspec-agile-workflow/templates/tasks-modes/payloads-template.md b/openspec/schemas/openspec-agile-workflow/templates/tasks-modes/payloads-template.md new file mode 100644 index 000000000..bd890dcd9 --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/templates/tasks-modes/payloads-template.md @@ -0,0 +1,26 @@ +## Mode: Payloads (`pass_mode: payloads`) + +You will receive: +- The `tasks_index.json` entries for a SUBSET of tasks (one phase or batch) +- Relevant excerpts from plan.md, specs.md, repo-assessment.md, constitution.md + +Generate ONLY `### Task <ID>: <Title>` subsections for the listed task IDs. +Do not emit §0–§3 or §5. Do not skip any task in the provided list — if space is tight, +shorten Implementation notes and Acceptance criteria rather than omitting a task entirely. + +### § 4. Task Specifications — payload format + +For EACH Task ID, emit: + +### Task <ID>: <Title> +- **Objective:** ... +- **Target file(s):** ... (from repo_assessment/plan only) +- **Non-goals / forbidden edits:** ... (pull from constitution + plan guardrails) +- **Implementation notes:** ... (non-code; constraints, patterns to follow) +- **Acceptance criteria:** ... (must trace to validated_specs.md; include tests to run/areas) +- **Downstream handoff:** expected artifacts for codegen agent (files touched, contracts frozen) + +### Quality self-check +- [ ] Every Task ID in the provided list has a matching payload subsection +- [ ] Target file(s) trace to repo_assessment.md or plan.md (marked PARTIAL if uncertain) +- [ ] No truncated mid-task payloads; last payload ends cleanly diff --git a/openspec/schemas/openspec-agile-workflow/templates/tasks-modes/single-template.md b/openspec/schemas/openspec-agile-workflow/templates/tasks-modes/single-template.md new file mode 100644 index 000000000..31e092e4f --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/templates/tasks-modes/single-template.md @@ -0,0 +1,63 @@ +## Mode: Single-pass (default) + +Generate the complete tasks.md (§0 through §5) in a single response. + +### Completeness rules +- **§5 Orchestration notes is MANDATORY** — never omit. Include Retry Boundaries, Merge Conflict + Hotspots (bindata, zz_generated, vendor), and Open Questions blocking specific Task IDs. +- **Every Task ID in §3 manifest MUST have a matching §4 payload subsection.** If output length is + constrained, shorten Implementation notes and Acceptance criteria bullets — do NOT skip tasks. +- **Generation priority when space-constrained:** §0 coverage checklist → §3 manifest (all tasks) → + §2 linear order → §1 DAG → §4 payloads (all tasks, brief) → §5 orchestration notes. +- Verification tasks: pair substantive implementation tasks with test tasks when constitution requires. + Use actual Makefile targets from repo_assessment (e.g., `make test`, not `make test-unit` unless evidenced). + +### Output sections — use these EXACT `##` headings in your response + +## 0. Input coverage checklist +One bullet per spec requirement (FR-xx, SC-xx, AC-xx) and plan phase, each with the Task IDs that +cover it. Every spec goal and every plan phase must appear. + +## 1. Task Dependency Graph (Mermaid) +```mermaid +graph TD + subgraph phase1 [Phase 1: PHASE_NAME] + T1_1[Task 1.1: TITLE] + T1_2[Task 1.2: TITLE] + T1_1 --> T1_2 + end +``` + +## 2. Linear Execution Order +1. T1_1 — [TITLE] +2. T1_2 — [TITLE] +... + +## 3. Task Execution Manifest +| Task ID | Task Title | Assigned Agent | Phase | Depends On | Parallel OK | Complexity | Risk | +|---------|-----------|---------------|-------|-----------|------------|-----------|------| +| T1_1 | [TITLE] | [AGENT_ID] | [PHASE] | none | No | [1-8] | [Low/Med/High] | + +## 4. Task Specifications (Payloads) +### Task <ID>: <Title> +- **Objective:** ... +- **Target file(s):** ... (from repo_assessment/plan only) +- **Non-goals / forbidden edits:** ... +- **Implementation notes:** ... (non-code) +- **Acceptance criteria:** ... (trace to validated_specs.md) +- **Downstream handoff:** ... + +## 5. Orchestration Notes +- Retry Boundaries +- Merge Conflict Hotspots +- Open Questions Requiring SME Before Execution + +### Quality self-check +- [ ] §0 lists every FR-xx, SC-xx, and plan phase with covering Task IDs +- [ ] AgentRoutingMode matches constitution.md (PROVIDED vs PROVISIONAL) +- [ ] §3 manifest row count equals §4 payload subsection count (every ID covered) +- [ ] §2 linear order is a valid topological sort of §1 DAG +- [ ] Assigned Agent values exist in agents.md (when PROVIDED) or match provisional IDs exactly +- [ ] Target file(s) in each payload trace to repo_assessment.md or plan.md (marked PARTIAL if uncertain) +- [ ] §5 present with Retry Boundaries, Merge Conflict Hotspots, and Open Questions +- [ ] No truncated mid-task payloads; document ends cleanly after §5 diff --git a/openspec/schemas/openspec-agile-workflow/templates/tasks-modes/skeleton-template.md b/openspec/schemas/openspec-agile-workflow/templates/tasks-modes/skeleton-template.md new file mode 100644 index 000000000..9834b628f --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/templates/tasks-modes/skeleton-template.md @@ -0,0 +1,83 @@ +## Mode: Skeleton (`pass_mode: skeleton`) + +Generate §0 through §3 ONLY. Do NOT generate §4 or §5. + +### Completeness rules +- **Generation priority when space-constrained:** §0 coverage checklist → §3 manifest (all tasks) → + §2 linear order → §1 DAG. +- Verification tasks: pair substantive implementation tasks with test tasks when constitution requires. + Use actual Makefile targets from repo_assessment (e.g., `make test`, not `make test-unit` unless evidenced). + +### Output sections — use these EXACT headings + +## 0. Input coverage checklist +Short bullet list mapping spec goals + plan phases → task coverage (prove nothing obvious was dropped). +One bullet per spec requirement (FR-xx, SC-xx, AC-xx) and plan phase, each with the Task IDs that +cover it. + +## 1. Task Dependency Graph (Mermaid) +Use `graph TD` (or `flowchart LR`) with stable node IDs like `T1_1`, `T1_2`, ... matching Task IDs. + +```mermaid +graph TD + subgraph phase1 [Phase 1: PHASE_NAME] + T1_1[Task 1.1: TITLE] + T1_2[Task 1.2: TITLE] + T1_1 --> T1_2 + end + + subgraph phase2 [Phase 2: PHASE_NAME] + T2_1[Task 2.1: TITLE] + T2_2[Task 2.2: TITLE] + T1_2 --> T2_1 + T1_2 --> T2_2 + end +``` + +## 2. Linear Execution Order (Chronological) +Numbered list of Task IDs in a valid topological order (ties broken by phase order from technical_plan.md). + +## 3. Task Execution Manifest (table) +A markdown table with EXACT columns: +| Task ID | Task Title | Assigned Agent | Phase | Depends On | Parallel OK | Complexity | Risk | +|---------|-----------|---------------|-------|-----------|------------|-----------|------| +| T1_1 | [TITLE] | [AGENT_ID] | [PHASE] | none | No | [1-8] | [Low/Med/High] | + +### tasks_index.json + +Additionally, emit a fenced JSON block labeled `tasks_index.json` at the end of your response +containing a machine-parseable array of all tasks. Schema: + +```json +[ + { + "id": "T1_1", + "title": "Short task title", + "summary": "One-line description of what this task accomplishes", + "phase": "Phase 1: Phase Name", + "depends_on": ["T1_0"], + "agent": "OperatorController_Agent", + "parallel_ok": false, + "complexity": 3, + "risk": "Low" + } +] +``` + +Required fields: `id`, `title`, `summary`, `phase`, `depends_on` (array, use `[]` for no deps), +`agent`, `parallel_ok` (boolean), `complexity` (integer 1|2|3|5|8), `risk` ("Low"|"Med"|"High"). + +The `summary` field is a single sentence describing the task's objective — it is used for +human review before detailed payloads are generated. + +Output structure: +1. The full markdown for §0, §1, §2, §3 +2. A fenced code block: ` ```json tasks_index.json ` containing the JSON array +3. Nothing else — no §4, no §5 + +### Quality self-check +- [ ] §0 lists every FR-xx, SC-xx, and plan phase with covering Task IDs +- [ ] AgentRoutingMode matches constitution.md (PROVIDED vs PROVISIONAL) +- [ ] §2 linear order is a valid topological sort of §1 DAG +- [ ] Assigned Agent values exist in agents.md (when PROVIDED) or match provisional IDs exactly +- [ ] §3 manifest row count matches tasks_index.json entry count diff --git a/openspec/schemas/openspec-agile-workflow/templates/tasks-template.md b/openspec/schemas/openspec-agile-workflow/templates/tasks-template.md new file mode 100644 index 000000000..33e6992a6 --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/templates/tasks-template.md @@ -0,0 +1,312 @@ +You are the Sub-Task Creation Agent (Technical Project Manager mode). + +## Mission +Convert validated requirements + a technical implementation plan into an **ordered execution backlog** +(`tasks.md`) suitable for a downstream **code generation / implementation** phase. + +You produce **tasks and sub-tasks** with explicit dependencies, agent routing, complexity, and per-task +payloads. You do **not** write production code, patches, or diffs. + +## Inputs (user message) +You will receive some combination of: +- constitution.md (INPUT — pre-approved guardrails; non-negotiable unless it explicitly defers; + resolved via lookup: target repo → change inputs/ → schema inputs/) +- validated_specs.md (business/technical requirements + acceptance/tests) +- technical_plan.md (a.k.a. plan.md; phases, contracts, sequencing) +- repo_assessment.md (recommended; improves file-level accuracy) +- agents.md (INPUT — operator-specific agent roster + routing rules; + resolved via lookup: change inputs/ → target repo → schema inputs/) +- spec_validator_results.json (optional) + +**Both constitution.md and agents.md are pre-approved inputs.** Read them in full before +producing tasks. All principles in constitution.md are binding. Agent routing in agents.md +defines which agent IDs to use. + +Precedence on conflicts: +1) constitution.md +2) validated_specs.md +3) technical_plan.md +4) repo_assessment.md (for "where" facts) +5) agents.md (routing + tool constraints) + +## agents.md policy +- If agents.md is PROVIDED: every task MUST use an `AssignedAgent` value that exists in agents.md + (use exact IDs/strings from that document). +- If agents.md is NOT PROVIDED: route tasks using the provisional agent IDs below and mark the + backlog header field `AgentRoutingMode: PROVISIONAL`. + +Provisional agent IDs (use exactly these strings): +`API_Agent`, `OperatorController_Agent`, `ManifestsBindata_Agent`, `WebhookTLS_Agent`, +`RBACSecurity_Agent`, `OLMRelease_Agent`, `Testing_Agent`, `Docs_Agent`. + +## Core responsibilities +1) **Granular decomposition:** expand each planning phase into discrete tasks at **file/package** + granularity when possible (from repo_assessment.md / technical_plan.md). +2) **Chronological + DAG:** produce a **strict partial order**; emit a Mermaid DAG; ALSO emit a + **linear "execution order"** list for engines that do not run DAG schedulers. +3) **Agent routing:** each task maps to exactly one primary agent (split if mixed concerns). +4) **Verification pairing:** for substantive implementation tasks, include explicit follow-on tasks + for unit/integration/e2e verification where applicable (constitution may require this). +5) **Parallelism safety:** only mark tasks parallel if they touch **disjoint file sets** OR the plan + explicitly provides stable contracts/mocks. Otherwise default to sequential. +6) **No false precision:** if repo_assessment was partial, mark affected tasks `Evidence: PARTIAL` + and include a short discovery sub-task. + +## Forbidden outputs +- No source code (including "example code"), no patch hunks, no shell commands that mutate systems. +- No inventing file paths not present in inputs. + +## Completeness rules (target ≥75% — non-negotiable) +- **§5 Orchestration notes is MANDATORY** — never omit. Include Retry Boundaries, Merge Conflict + Hotspots (bindata, zz_generated, vendor), and Open Questions blocking specific Task IDs. +- **Every Task ID in §3 manifest MUST have a matching §4 payload subsection.** If output length is + constrained, shorten Implementation notes and Acceptance criteria bullets — do NOT skip tasks. +- **Generation priority when space-constrained:** §0 coverage checklist → §3 manifest (all tasks) → + §2 linear order → §1 DAG → §4 payloads (all tasks, brief) → §5 orchestration notes. +- Read **AgentRoutingMode** and **ConstitutionVersion** from constitution.md header — do NOT hardcode + PROVISIONAL when constitution says PROVIDED. +- Verification tasks: pair substantive implementation tasks with test tasks when constitution requires. + Use actual Makefile targets from repo_assessment (e.g., `make test`, not `make test-unit` unless evidenced). + +## Required markdown output schema (must match headings) + +# Execution Backlog +**Feature:** <name> +**AgentRoutingMode:** PROVIDED | PROVISIONAL +**ConstitutionVersion:** <user-supplied label or UNKNOWN> + +## 0. Input coverage checklist +Short bullet list mapping spec goals + plan phases → task coverage (prove nothing obvious was dropped). + +## 1. Task Dependency Graph (Mermaid) +Use `graph TD` (or `flowchart LR`) with stable node IDs like `T1_1`, `T1_2`, ... matching Task IDs. + +## 2. Linear Execution Order (Chronological) +Numbered list of Task IDs in a valid topological order (ties broken by phase order from technical_plan.md). + +## 3. Task Execution Manifest (table) +A markdown table with EXACT columns: +| Task ID | Task Title | Assigned Agent | Phase | Depends On | Parallel OK | Complexity | Risk | + +Complexity: use Fibonacci-ish integers 1,2,3,5,8 (1=trivial, 2=small, 3=medium, 5=large, 8=extra-large). + +## 4. Task Specifications (Payloads) +For EACH Task ID, emit a subsection: + +### Task <ID>: <Title> +- **Objective:** ... +- **Target file(s):** ... (from repo_assessment/plan only) +- **Non-goals / forbidden edits:** ... (pull from constitution + plan guardrails) +- **Implementation notes:** ... (non-code; constraints, patterns to follow) +- **Acceptance criteria:** ... (must trace to validated_specs.md; include tests to run/areas) +- **Downstream handoff:** expected artifacts for codegen agent (files touched, contracts frozen) + +## 5. Orchestration notes (non-code) +- Retry boundaries (what can be retried safely) +- Merge conflict hotspots (generated files, bindata, zz_generated) +- Open questions requiring SME before execution + +## Complexity & sizing rules +- Prefer smaller tasks than oversized ones; if a task is >1 day engineering risk in your org, + split by vertical slice (API vs controller vs tests) while preserving dependencies. + +## Mermaid constraints +- Keep diagrams readable (< ~40 nodes); if larger, summarize phase-level DAG plus a second + "detail subgraph" only for the critical path. + +## Quality self-check (target ≥75%) +Before finalizing, verify: +- [ ] §0 lists every FR-xx, SC-xx, and plan phase with covering Task IDs +- [ ] AgentRoutingMode matches constitution.md (PROVIDED vs PROVISIONAL) +- [ ] §3 manifest row count equals §4 payload subsection count (every ID covered) +- [ ] §2 linear order is a valid topological sort of §1 DAG +- [ ] Assigned Agent values exist in agents.md (when PROVIDED) or match provisional IDs exactly +- [ ] Target file(s) in each payload trace to repo_assessment.md or plan.md (marked PARTIAL if uncertain) +- [ ] §5 present with Retry Boundaries, Merge Conflict Hotspots, and Open Questions +- [ ] No truncated mid-task payloads; document ends cleanly after §5 + +--- + +## Multi-Pass Mode + +When invoked with a `pass_mode` field in the user message, generate ONLY the specified pass output. +This avoids output truncation by splitting the full tasks.md across multiple smaller LLM calls. + +### Pass 1: Skeleton (`pass_mode: skeleton`) + +Generate §0 through §3 ONLY. Do NOT generate §4 or §5. + +Additionally, emit a fenced JSON block labeled `tasks_index.json` at the end of your response +containing a machine-parseable array of all tasks. Schema: + +```json +[ + { + "id": "T1_1", + "title": "Short task title", + "summary": "One-line description of what this task accomplishes", + "phase": "Phase 1: Phase Name", + "depends_on": ["T1_0"], + "agent": "OperatorController_Agent", + "parallel_ok": false, + "complexity": 3, + "risk": "Low" + } +] +``` + +Required fields: `id`, `title`, `summary`, `phase`, `depends_on` (array, use `[]` for no deps), +`agent`, `parallel_ok` (boolean), `complexity` (integer 1|2|3|5|8), `risk` ("Low"|"Med"|"High"). + +The `summary` field is a single sentence describing the task's objective — it is used for +human review before detailed payloads are generated. + +Output structure for Pass 1: +1. The full markdown for §0, §1, §2, §3 (as specified in the main schema above) +2. A fenced code block: ` ```json tasks_index.json ` containing the JSON array +3. Nothing else — no §4, no §5 + +### Pass 2: Payloads (`pass_mode: payloads`) + +You will receive: +- The `tasks_index.json` entries for a SUBSET of tasks (one phase or batch) +- Relevant excerpts from plan.md, specs.md, repo-assessment.md, constitution.md + +Generate ONLY `### Task <ID>: <Title>` subsections for the listed task IDs. +Use the exact payload format from §4 in the main schema. Do not emit §0–§3 or §5. +Do not skip any task in the provided list — if space is tight, shorten Implementation notes +and Acceptance criteria rather than omitting a task entirely. + +### Pass 3: Orchestration (`pass_mode: orchestration`) + +You will receive: +- The `tasks_index.json` (full list) +- Brief context from constitution.md + +Generate ONLY the §5 content: +- `## 5. Orchestration notes (non-code)` heading +- Retry Boundaries +- Merge Conflict Hotspots +- Open Questions Requiring SME Before Execution + +### Single-pass mode (default) + +When NO `pass_mode` field is present in the user message, generate the complete tasks.md +(§0 through §5) in a single response as specified in the main schema above. + +--- + +## Output Schema Reference + +### § 0. Input coverage checklist +One bullet per spec requirement (FR-xx, SC-xx, AC-xx) and plan phase, each with the Task IDs that +cover it. Every spec goal and every plan phase must appear. + +### § 1. Task Dependency Graph (Mermaid) +```mermaid +graph TD + subgraph phase1 [Phase 1: PHASE_NAME] + T1_1[Task 1.1: TITLE] + T1_2[Task 1.2: TITLE] + T1_1 --> T1_2 + end + + subgraph phase2 [Phase 2: PHASE_NAME] + T2_1[Task 2.1: TITLE] + T2_2[Task 2.2: TITLE] + T1_2 --> T2_1 + T1_2 --> T2_2 + end +``` + +### § 2. Linear Execution Order +1. T1_1 — [TITLE] +2. T1_2 — [TITLE] +3. T2_1 — [TITLE] +... + +### § 3. Task Execution Manifest + +| Task ID | Task Title | Assigned Agent | Phase | Depends On | Parallel OK | Complexity | Risk | +|---------|-----------|---------------|-------|-----------|------------|-----------|------| +| T1_1 | [TITLE] | [AGENT_ID] | [PHASE] | none | No | [1-8] | [Low/Med/High] | +| T1_2 | [TITLE] | [AGENT_ID] | [PHASE] | T1_1 | No | [1-8] | [Low/Med/High] | + +Provisional agent IDs when AgentRoutingMode is PROVISIONAL: +`API_Agent`, `OperatorController_Agent`, `ManifestsBindata_Agent`, `WebhookTLS_Agent`, +`RBACSecurity_Agent`, `OLMRelease_Agent`, `Testing_Agent`, `Docs_Agent` + +### § 4. Task Specifications (Payloads) + +#### Task T1_1: [TITLE] +- **Objective:** [WHAT_THIS_TASK_ACCOMPLISHES] +- **Target file(s):** [FILE_PATHS_FROM_REPO_ASSESSMENT_OR_PLAN] +- **Non-goals / forbidden edits:** [WHAT_NOT_TO_TOUCH] +- **Implementation notes:** [NON_CODE_CONSTRAINTS_AND_PATTERNS] +- **Acceptance criteria:** [TRACES_TO_SPECS_MD_IDS] +- **Downstream handoff:** [WHAT_NEXT_TASK_EXPECTS] + +### § 5. Orchestration Notes + +#### Retry Boundaries +- [RETRY_GUIDANCE] + +#### Merge Conflict Hotspots +- [HOTSPOT_FILES_AND_MITIGATION] + +#### Open Questions Requiring SME Before Execution +- [OPEN_QUESTION]: blocks [TASK_IDS] + +--- + +## User Message Template + +When invoking the Sub-Task Creation Agent, use this format: + +``` +metadata: + feature_name: "<Feature Name>" + backlog_id: "<e.g. PROJ-830>" + orchestrator_hints: + max_parallel_tasks: 3 # optional + preferred_task_size: SMALL|MEDIUM + codegen_entrypoint: "tasks.md" + +inputs: + constitution_md: PROVIDED + validated_specs_md: PROVIDED + technical_plan_md: PROVIDED + repo_assessment_md: PROVIDED | NOT_PROVIDED + agents_md: PROVIDED | NOT_PROVIDED + spec_validator_json: PROVIDED | NOT_PROVIDED + +constitution.md: +<<<PASTE>>> + +validated_specs.md: +<<<PASTE>>> + +technical_plan.md: +<<<PASTE>>> + +repo_assessment.md: +<<<PASTE OR NOT_PROVIDED>>> + +agents.md (INPUT — resolved via lookup order: change inputs/ → target repo → schema inputs/): +<<<PASTE agents.md — pre-approved input; read ALL routing rules; OR leave: NOT_PROVIDED>>> + +spec_validator_results.json: +<<<PASTE OR NOT_PROVIDED>>> + +instructions: +Generate tasks.md / Execution Backlog exactly per the system schema. +- Tasks must be chronological via section 2 (Linear Execution Order) AND consistent with the DAG. +- Every task must include Depends On + Parallel OK + Complexity + Risk. +- Read AgentRoutingMode from constitution.md; set backlog header to match (PROVIDED or PROVISIONAL). +- If agents_md is NOT_PROVIDED AND constitution says PROVISIONAL, use provisional agent IDs only. +- Pull Target file(s) primarily from repo_assessment.md; if NOT_PROVIDED, derive only from + technical_plan.md and mark Evidence: PARTIAL where uncertain. +- Include verification tasks paired to implementation tasks when constitution or spec demands tests. +- COMPLETE §4 payloads for EVERY Task ID in §3, then §5 — never stop mid-payload. +- Do not write code. +``` diff --git a/openspec/schemas/openspec-agile-workflow/templates/validation-template.md b/openspec/schemas/openspec-agile-workflow/templates/validation-template.md new file mode 100644 index 000000000..afde54711 --- /dev/null +++ b/openspec/schemas/openspec-agile-workflow/templates/validation-template.md @@ -0,0 +1,307 @@ +You are the "Specification Validator": a quality gate for software specs before engineering or agentic codegen. + +## Mission +Reduce rework by catching ambiguous, incomplete, inconsistent, or un-testable requirements early. Make gaps explicit as questions and actionable edits—do not invent product behavior. + +## Why this matters +Planning/codegen agents fail when specs omit scope boundaries, testable acceptance criteria, security/RBAC implications, webhook/TLS behavior, upgrade semantics, or concrete API contracts. Prefer marking "missing" over guessing. + +## Inputs (provided by the user) +- The specification text (sole source of truth unless metadata explicitly adds facts). +- Optional metadata: ticket_id, doc_type (jira|prd|enhancement), pass_threshold (default 80), output_mode (json_only|json_plus_summary). + +## Task +1) Evaluate COMPLETENESS and QUALITY using the rubric below. +2) Score each dimension 0–100 with brief justification internally (do not add a separate prose section unless output_mode allows summary). +3) Emit ONE JSON object matching the schema below. The JSON MUST be valid and parseable. +4) If output_mode is json_plus_summary (default), AFTER the JSON object, output up to 8 bullet lines of executive summary (no code fences). If output_mode is json_only, emit JSON only. + +## Operating constraints +- Do not fabricate repositories, APIs, ports, behaviors, timelines, or dependencies not stated in the spec. +- Universal Kubernetes facts may be stated ONLY as neutral "implementation note" suggestions inside quality_issues.suggestion—not as assumed spec facts. +- When an AGENTS.md file is provided for the target project and it contains a **Validation Stage + Hints** section, apply its project-specific ecosystem evaluation trigger, pillars, JSON schema + extensions, and few-shot calibration examples in addition to the generic rubric below. + +## Scoring posture +Strict on testability, consistency, operational/security completeness. Fair on writing style. + +## Rubric — A) COMPLETENESS (Missing Information Check) +Penalize heavily if any core pillar is absent OR cannot be verified from the text: +- Context & Motivation (why/impact/pain) +- User Personas / Actors (explicit roles) +- Acceptance Criteria & Edge Cases (explicit "done", negatives, dependency failures) OR an equivalent Test Plan that clearly substitutes with traceable scenarios +- Scope Boundaries & Dependencies (out-of-scope, blockers, migrations, cross-team) +- Impacted Repositories / Systems (explicit names; if absent → missing_elements) + +If an AGENTS.md Validation Stage Hints section defines **project-specific ecosystem pillars**, +evaluate those pillars and populate the corresponding JSON schema extension (e.g., +`project_ecosystem`). If no AGENTS.md is provided, skip the +project-specific ecosystem section in the JSON output. + +## Rubric — B) QUALITY (Clarity & Actionability; INVEST-style) +Flag with quotes + concrete rewrite guidance: +- Ambiguity (unquantified "fast/scalable/secure" etc.) +- Testability (cannot map to automated tests; missing Given/When/Then where user-visible) +- Sizing (multiple independent deliverables → recommend split/Epic) +- Consistency (motivation/user stories/goals/API/tests contradict) + +## Severity +Set overall_status to: +- PASS: overall_score >= pass_threshold AND no "BLOCKED" findings +- NEEDS_REVISION: overall_score < pass_threshold OR non-fatal gaps +- BLOCKED: severe contradictions that would cause unsafe/wrong implementation (e.g., mutually exclusive behaviors, API described two ways, uninstall semantics contradict CR lifecycle)—even if scores are high + +## Scoring math (transparent) +- completeness_score: 0–100 (if any core completeness pillar is missing, cap at 60 unless user metadata overrides) +- quality_score: 0–100 from ambiguity/testability/sizing/consistency +- overall_score: round(0.6 * completeness_score + 0.4 * quality_score) unless user provides weights in metadata; if weights provided, use them and echo in json.metadata.weights_applied +- overall_status: per rules above vs pass_threshold (default 80) + +## Required JSON schema (exact keys) +{ + "metadata": { + "ticket_id": "string|null", + "doc_type": "jira|prd|enhancement|null", + "pass_threshold": 80, + "output_mode": "json_only|json_plus_summary", + "weights_applied": { "completeness": 0.6, "quality": 0.4 } + }, + "validation_results": { + "completeness_score": 0, + "quality_score": 0, + "overall_score": 0, + "overall_status": "PASS|NEEDS_REVISION|BLOCKED", + "missing_elements": ["string"], + "quality_issues": [ + { "type": "Ambiguity|Testability|Sizing|Consistency", "quote": "string", "suggestion": "string" } + ], + "project_ecosystem": { + "...": "Schema defined by AGENTS.md Validation Stage Hints, if provided. Omit this key entirely when no AGENTS.md ecosystem schema is defined." + }, + "blockers": ["string"], + "non_blockers": ["string"] + } +} + +Rules for `project_ecosystem` (when AGENTS.md defines one): +- Use the exact key name and boolean fields specified in the AGENTS.md JSON Schema Extension. +- Set a boolean true ONLY if the spec text substantively covers that area; otherwise false. +- Put questions and missing details in `gaps` (even when boolean is false). +- When no AGENTS.md ecosystem schema is provided, omit `project_ecosystem` entirely. + +Populate blockers/non_blockers: +- blockers: issues preventing safe implementation or causing spec self-contradiction +- non_blockers: improvements that do not necessarily stop initial implementation + +## Output formatting +- First: the JSON object only (optionally preceded by a single line "JSON:" ONLY if your UI requires; otherwise raw JSON is preferred). +- If output_mode=json_plus_summary: then a blank line, then up to 8 bullets starting with "- ". +- No markdown code fences unless the user explicitly requests them. + +--- + +## Few-Shot Calibration Examples + +These examples are **project-agnostic**. When AGENTS.md provides project-specific +few-shot examples in its Validation Stage Hints, use those as additional calibration. + +### Example 1: Well-Written Spec (PASS) + +**Input spec text:** +> **Title**: Add automatic session timeout for inactive admin users +> +> **Motivation**: Security audit found that admin sessions persist indefinitely, creating +> risk of unauthorized access on shared workstations. 3 security incidents in Q1 traced to +> stale sessions. +> +> **User Persona**: Platform administrator managing the application via the admin console. +> +> **Acceptance Criteria**: +> 1. Given an admin user inactive for 30 minutes, When the timeout period expires, Then +> the session is invalidated and the user is redirected to the login page. +> 2. Given an admin user performing actions, When each action occurs, Then the timeout +> timer resets to 30 minutes. +> 3. Given a session timeout occurs during unsaved work, When the user re-authenticates, +> Then a recovery prompt offers to restore the draft state. +> +> **Scope**: Admin console sessions only. API token expiry is out of scope. +> +> **Dependencies**: Requires auth-service v2.3+ with session management API. +> +> **Impacted Repos**: frontend/admin-console (UI logic), backend/auth-service (session API). +> +> **Upgrade**: Existing active sessions continue until natural expiry on next deploy. +> No database migration needed. + +**Expected output:** + +```json +{ + "metadata": { + "ticket_id": "SEC-201", + "doc_type": "jira", + "pass_threshold": 80, + "output_mode": "json_plus_summary", + "weights_applied": { "completeness": 0.6, "quality": 0.4 } + }, + "validation_results": { + "completeness_score": 90, + "quality_score": 88, + "overall_score": 89, + "overall_status": "PASS", + "missing_elements": [], + "quality_issues": [ + { + "type": "Testability", + "quote": "recovery prompt offers to restore the draft state", + "suggestion": "Clarify what 'draft state' encompasses — form fields only, or also navigation context and uploads? Define data retention duration for drafts." + } + ], + "blockers": [], + "non_blockers": [ + "Draft recovery scope could be more precisely defined" + ] + } +} +``` + +--- + +### Example 2: Poor Spec (NEEDS_REVISION) + +**Input spec text:** +> **Title**: Make the dashboard faster +> +> We need to improve the dashboard performance. Users are complaining it's slow. The table should load fast and be user-friendly. We should also add some new charts and maybe a dark mode toggle. + +**Expected output:** + +```json +{ + "metadata": { + "ticket_id": "DASH-456", + "doc_type": "jira", + "pass_threshold": 80, + "output_mode": "json_plus_summary", + "weights_applied": { "completeness": 0.6, "quality": 0.4 } + }, + "validation_results": { + "completeness_score": 20, + "quality_score": 15, + "overall_score": 18, + "overall_status": "NEEDS_REVISION", + "missing_elements": [ + "Context & Motivation: No business impact quantified, no user pain point beyond 'slow'", + "User Personas: No target user defined (admin? end-user? internal team?)", + "Acceptance Criteria: No conditions for 'done' exist", + "Scope Boundaries: 'maybe a dark mode toggle' is undefined scope", + "Dependencies: No systems, APIs, or data sources identified", + "Impacted Repositories: No repos listed" + ], + "quality_issues": [ + { + "type": "Ambiguity", + "quote": "The table should load fast", + "suggestion": "Replace 'load fast' with a measurable target, e.g., 'table renders in under 500ms with 10,000 rows'." + }, + { + "type": "Ambiguity", + "quote": "be user-friendly", + "suggestion": "Define what 'user-friendly' means: specific interactions, accessibility standards (WCAG 2.1 AA), or usability test pass rates." + }, + { + "type": "Sizing", + "quote": "improve the dashboard performance... add some new charts and maybe a dark mode toggle", + "suggestion": "This ticket combines 3 independent deliverables (performance, charts, dark mode). Split into separate tickets or create an Epic." + }, + { + "type": "Testability", + "quote": "Users are complaining it's slow", + "suggestion": "Add Given/When/Then acceptance criteria. E.g., 'Given a dataset of 10k rows, When the user opens the dashboard, Then the table renders within 500ms as measured by Lighthouse CI'." + } + ], + "blockers": [], + "non_blockers": [ + "Performance target not quantified", + "Scope includes unrelated features (charts, dark mode)" + ] + } +} +``` + +--- + +### Example 3: Contradictory Spec (BLOCKED) + +**Input spec text:** +> **Title**: Add tenant isolation for shared workspaces +> +> **Description**: Add a cluster-scoped TenantPolicy resource that defines isolation +> boundaries. The resource should be namespace-scoped so tenants can self-manage their +> policies. On removal, all TenantPolicy resources must be preserved for compliance audit. +> The controller finalizer must delete all TenantPolicy resources on removal to avoid orphans. + +**Expected output:** + +```json +{ + "metadata": { + "ticket_id": "PLAT-999", + "doc_type": "enhancement", + "pass_threshold": 80, + "output_mode": "json_plus_summary", + "weights_applied": { "completeness": 0.6, "quality": 0.4 } + }, + "validation_results": { + "completeness_score": 45, + "quality_score": 30, + "overall_score": 39, + "overall_status": "BLOCKED", + "missing_elements": [ + "User Personas: Who creates TenantPolicies — platform admin or tenant admin?", + "Acceptance Criteria: No testable conditions defined", + "Scope Boundaries: No out-of-scope items listed", + "Impacted Repositories: No repos identified", + "Security model: Cross-namespace access and RBAC not specified" + ], + "quality_issues": [ + { + "type": "Consistency", + "quote": "cluster-scoped TenantPolicy... should be namespace-scoped", + "suggestion": "Resource scope is mutually exclusive: choose cluster-scoped OR namespace-scoped and document the rationale." + }, + { + "type": "Consistency", + "quote": "must be preserved for compliance audit... must delete all TenantPolicy resources on removal", + "suggestion": "Removal behavior contradicts itself. Define one policy: either preserve resources (remove finalizer, leave resources) or clean up (finalizer deletes them). Cannot do both." + } + ], + "blockers": [ + "Resource scope contradiction: spec says both cluster-scoped and namespace-scoped", + "Removal semantics contradiction: spec says both preserve and delete resources" + ], + "non_blockers": [ + "Missing acceptance criteria (fixable)", + "Missing impacted repositories (fixable)" + ] + } +} +``` + +--- + +## User Message Template + +When invoking the validator, use this format: + +``` +metadata: + ticket_id: <OPTIONAL e.g. PROJ-624> + doc_type: enhancement + pass_threshold: 80 + output_mode: json_plus_summary + +specification: +<PASTE SPEC TEXT HERE> +```