From a6f8bc7b1510acfd4975f5d06617c8ff919f6a76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Hu=C3=9F?= Date: Thu, 21 May 2026 09:04:57 +0200 Subject: [PATCH 01/11] Add brainstorm: AgentCard data into AgentRuntime status MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First step of the AgentCard-to-AgentRuntime consolidation (kagenti-operator#371). Covers card fetch in the AgentRuntime controller, A2A-compliant status schema, and deprecation path. Assisted-By: 🤖 Claude Code Signed-off-by: Roland Huß --- brainstorm/01-agentcard-into-agentruntime.md | 82 ++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 brainstorm/01-agentcard-into-agentruntime.md diff --git a/brainstorm/01-agentcard-into-agentruntime.md b/brainstorm/01-agentcard-into-agentruntime.md new file mode 100644 index 00000000..93c26afd --- /dev/null +++ b/brainstorm/01-agentcard-into-agentruntime.md @@ -0,0 +1,82 @@ +# Brainstorm: AgentCard Data Into AgentRuntime Status + +**Date:** 2026-05-20 +**Status:** active + +## Problem Framing + +AgentRuntime and AgentCard are two CRDs with identical cardinality (one per workload), the same namespace, the same lifecycle, and the same owner. AgentCard is a pure read-only CR whose content is entirely controller-managed. Its data (card metadata, verification status, binding state) fits naturally into AgentRuntime's `status` section. + +The AgentCard CRD has three specific problems documented in ADR ODH-ADR-AgentOps-0002: + +1. It conflates observation with policy. The CR is controller-created and controller-written, leaving no room for admin-authored policy fields. +2. Its JWS signing pipeline signs a skeleton card with empty skills/capabilities (#292). The signed content and live content are disconnected. +3. Maintaining two CRs for the same Deployment doubles the RBAC surface and splits "how this agent participates in the platform" across two APIs. + +This brainstorm covers the first step: moving card data into AgentRuntime status. mTLS, policy fields, and AgentCard removal are separate follow-up items. + +## Context + +- ADR: ODH-ADR-AgentOps-0002 (Agent Network Policy and mTLS Identity) +- Upstream issue: kagenti-operator#371 (Consolidate AgentCard into AgentRuntime status) +- Related: kagenti-operator#292 (skeleton-card problem) +- Related: kagenti-operator#284 (mTLS verified fetch, merged 2026-05-20, infrastructure reusable later) +- Upstream sync: IBM maintainers agreed to AgentCard deprecation path (2026-05-15) +- RHAISTRAT-1599 AC review: acceptance criteria updated to reflect AgentRuntime-based discovery + +## Approaches Considered + +### A: Extend the existing AgentRuntime controller (chosen) + +Add a card fetch phase to the existing `agentruntime_controller.go` reconciliation loop. After resolving the target workload and applying labels, the controller fetches `/.well-known/agent-card.json` from the agent's Service endpoint over plain HTTP, parses it into an A2A-compliant struct, and writes it to `status.card`. Triggered by Pod template hash change on the target workload. + +- Pros: Minimal new code. Reuses existing workload watches and reconciliation infrastructure. Single controller, single reconcile loop. +- Cons: Makes agentruntime_controller.go larger (already 29K). Card fetch adds network I/O to a controller that currently only does Kubernetes API calls. + +### B: New dedicated card discovery controller + +Create a separate `agentruntime_card_controller.go` that watches AgentRuntime CRs and handles only card fetching. The main controller continues handling labels, sidecar injection, and config. + +- Pros: Clean separation. Card fetch failures don't block main reconciliation. Independent rate limiter for network I/O. +- Cons: Two controllers watching the same CRD. Need to coordinate status updates. More moving parts for a simple HTTP GET. + +### C: Card fetch as a Kubernetes Job + +Create short-lived Jobs to fetch cards on rollout events. + +- Pros: Completely decouples fetching from the controller. Reusable binary. +- Cons: Heavy for a simple HTTP GET. Adds Job RBAC, cleanup, failure handling. Overkill. + +## Decision + +**Approach A: Extend the existing AgentRuntime controller.** The card fetch is a single HTTP GET that takes milliseconds. If performance becomes an issue at scale (hundreds of agents), extracting to a separate controller (Approach B) is a clean refactor. + +## Key Requirements + +### What gets built + +- New `AgentCardStatus` struct on AgentRuntime CRD, modeled on the A2A protocol agent-card.json spec (name, description, skills, protocols, endpoint). Not mirrored from the existing AgentCard CRD fields. +- Card fetch phase added to the existing AgentRuntime controller reconcile loop. +- Fetch triggers on Pod template hash change (rollout events only, no polling, no periodic fallback). +- Fetch is plain HTTP GET to `/.well-known/agent-card.json` via the agent's Service endpoint. +- Card data written to `status.card` on AgentRuntime. + +### What gets deprecated + +- AgentCard CRD gets a deprecation log warning on creation. +- AgentCard remains functional (both CRDs coexist during transition). + +### Out of scope (future iterations) + +- mTLS for the card fetch (port from #284). +- `spec.policy` fields (allowedIngressNamespaces, dependencies, externalEgress). +- AgentCard CRD removal and controller cleanup. +- ValidatingAdmissionPolicy for label restriction. +- Migration tooling. + +## Open Questions + +- Exact A2A AgentCard JSON schema fields to model in the Go struct +- How to discover the agent's Service endpoint from the AgentRuntime targetRef (resolve Deployment -> Service via selector matching or naming convention) +- Feature flag name and default (e.g. `--enable-card-discovery`, off by default) +- Whether `status.card.fetchedAt` timestamp should be included for diagnostics From 4b01e715e5b810f540937cfb3389cf6efd097db5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Hu=C3=9F?= Date: Thu, 21 May 2026 09:05:10 +0200 Subject: [PATCH 02/11] Update brainstorm overview MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Assisted-By: 🤖 Claude Code Signed-off-by: Roland Huß --- brainstorm/00-overview.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 brainstorm/00-overview.md diff --git a/brainstorm/00-overview.md b/brainstorm/00-overview.md new file mode 100644 index 00000000..968a1cac --- /dev/null +++ b/brainstorm/00-overview.md @@ -0,0 +1,20 @@ +# Brainstorm Overview + +Last updated: 2026-05-20 + +## Sessions + +| # | Date | Topic | Status | Spec | +|---|------|-------|--------|------| +| 01 | 2026-05-20 | agentcard-into-agentruntime | active | - | + +## Open Threads + +- Exact A2A AgentCard JSON schema fields for the Go struct (from #01) +- Service endpoint discovery from AgentRuntime targetRef (from #01) +- Feature flag naming and default (from #01) +- Whether status.card.fetchedAt timestamp is needed (from #01) + +## Parked Ideas + +(none) From 33a49058f0023f0459ae54e4f347e28b4593c18b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Hu=C3=9F?= Date: Thu, 21 May 2026 09:44:40 +0200 Subject: [PATCH 03/11] Add spec: Consolidate AgentCard data into AgentRuntime status MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Specification, implementation plan, and task breakdown for moving A2A agent card discovery into the AgentRuntime controller reconcile loop. Key design: reuse existing fetcher/verifier infrastructure, feature-gated with --enable-card-discovery, mTLS included via PR #284 infrastructure. Assisted-By: 🤖 Claude Code Signed-off-by: Roland Huß --- .gitignore | 6 + .specify/extensions.yml | 275 ++++++ .specify/extensions/.registry | 126 +++ .specify/extensions/git/README.md | 100 ++ .../git/commands/speckit.git.commit.md | 48 + .../git/commands/speckit.git.feature.md | 67 ++ .../git/commands/speckit.git.initialize.md | 49 + .../git/commands/speckit.git.remote.md | 45 + .../git/commands/speckit.git.validate.md | 49 + .specify/extensions/git/config-template.yml | 62 ++ .specify/extensions/git/extension.yml | 140 +++ .specify/extensions/git/git-config.yml | 60 ++ .../git/scripts/bash/auto-commit.sh | 140 +++ .../git/scripts/bash/create-new-feature.sh | 453 +++++++++ .../extensions/git/scripts/bash/git-common.sh | 54 ++ .../git/scripts/bash/initialize-repo.sh | 54 ++ .../git/scripts/powershell/auto-commit.ps1 | 169 ++++ .../scripts/powershell/create-new-feature.ps1 | 403 ++++++++ .../git/scripts/powershell/git-common.ps1 | 51 ++ .../scripts/powershell/initialize-repo.ps1 | 69 ++ .../speckit.spex-collab.phase-manager.md | 302 ++++++ .../speckit.spex-collab.phase-split.md | 149 +++ .../commands/speckit.spex-collab.reconcile.md | 199 ++++ .../commands/speckit.spex-collab.reviewers.md | 209 +++++ .../commands/speckit.spex-collab.revise.md | 321 +++++++ .../spex-collab/config-template.yml | 12 + .specify/extensions/spex-collab/extension.yml | 56 ++ .../templates/reviewers-template.md | 84 ++ .../commands/speckit.spex-deep-review.run.md | 862 ++++++++++++++++++ .../spex-deep-review/config-template.yml | 4 + .../extensions/spex-deep-review/extension.yml | 39 + .../speckit.spex-gates.review-code.md | 419 +++++++++ .../speckit.spex-gates.review-plan.md | 202 ++++ .../speckit.spex-gates.review-spec.md | 331 +++++++ .../commands/speckit.spex-gates.stamp.md | 48 + .../commands/speckit.spex-gates.verify.md | 476 ++++++++++ .specify/extensions/spex-gates/extension.yml | 49 + .../commands/speckit.spex-teams.implement.md | 27 + .../speckit.spex-teams.orchestrate.md | 148 +++ .../commands/speckit.spex-teams.research.md | 151 +++ .../extensions/spex-teams/config-template.yml | 4 + .specify/extensions/spex-teams/extension.yml | 45 + .../commands/speckit.spex-worktrees.manage.md | 528 +++++++++++ .../extensions/spex-worktrees/extension.yml | 33 + .../spex/commands/speckit.spex.brainstorm.md | 391 ++++++++ .../spex/commands/speckit.spex.evolve.md | 562 ++++++++++++ .../spex/commands/speckit.spex.extensions.md | 58 ++ .../spex/commands/speckit.spex.finish.md | 250 +++++ .../spex/commands/speckit.spex.flow-state.md | 44 + .../spex/commands/speckit.spex.help.md | 20 + .../spex/commands/speckit.spex.ship.md | 847 +++++++++++++++++ .../spex/commands/speckit.spex.spec-kit.md | 366 ++++++++ .../commands/speckit.spex.spec-refactoring.md | 446 +++++++++ .../speckit.spex.using-superpowers.md | 329 +++++++ .specify/extensions/spex/extension.yml | 101 ++ .specify/feature.json | 3 + .specify/init-options.json | 11 + .specify/integration.json | 4 + .specify/integrations/claude.manifest.json | 16 + .specify/integrations/speckit.manifest.json | 16 + .specify/memory/constitution.md | 50 + .specify/scripts/bash/check-prerequisites.sh | 190 ++++ .specify/scripts/bash/common.sh | 375 ++++++++ .specify/scripts/bash/create-new-feature.sh | 413 +++++++++ .specify/scripts/bash/setup-plan.sh | 73 ++ .specify/templates/checklist-template.md | 40 + .specify/templates/constitution-template.md | 50 + .specify/templates/plan-template.md | 104 +++ .specify/templates/spec-template.md | 128 +++ .specify/templates/tasks-template.md | 251 +++++ .specify/workflows/speckit/workflow.yml | 63 ++ .specify/workflows/workflow-registry.json | 13 + CLAUDE.md | 6 + specs/001-agentcard-into-status/REVIEWERS.md | 66 ++ .../checklists/requirements.md | 36 + .../contracts/agentruntime-status-card.md | 77 ++ specs/001-agentcard-into-status/data-model.md | 90 ++ specs/001-agentcard-into-status/plan.md | 73 ++ specs/001-agentcard-into-status/research.md | 70 ++ specs/001-agentcard-into-status/spec.md | 132 +++ specs/001-agentcard-into-status/tasks.md | 202 ++++ 81 files changed, 13084 insertions(+) create mode 100644 .specify/extensions.yml create mode 100644 .specify/extensions/.registry create mode 100644 .specify/extensions/git/README.md create mode 100644 .specify/extensions/git/commands/speckit.git.commit.md create mode 100644 .specify/extensions/git/commands/speckit.git.feature.md create mode 100644 .specify/extensions/git/commands/speckit.git.initialize.md create mode 100644 .specify/extensions/git/commands/speckit.git.remote.md create mode 100644 .specify/extensions/git/commands/speckit.git.validate.md create mode 100644 .specify/extensions/git/config-template.yml create mode 100644 .specify/extensions/git/extension.yml create mode 100644 .specify/extensions/git/git-config.yml create mode 100755 .specify/extensions/git/scripts/bash/auto-commit.sh create mode 100755 .specify/extensions/git/scripts/bash/create-new-feature.sh create mode 100755 .specify/extensions/git/scripts/bash/git-common.sh create mode 100755 .specify/extensions/git/scripts/bash/initialize-repo.sh create mode 100644 .specify/extensions/git/scripts/powershell/auto-commit.ps1 create mode 100644 .specify/extensions/git/scripts/powershell/create-new-feature.ps1 create mode 100644 .specify/extensions/git/scripts/powershell/git-common.ps1 create mode 100644 .specify/extensions/git/scripts/powershell/initialize-repo.ps1 create mode 100644 .specify/extensions/spex-collab/commands/speckit.spex-collab.phase-manager.md create mode 100644 .specify/extensions/spex-collab/commands/speckit.spex-collab.phase-split.md create mode 100644 .specify/extensions/spex-collab/commands/speckit.spex-collab.reconcile.md create mode 100644 .specify/extensions/spex-collab/commands/speckit.spex-collab.reviewers.md create mode 100644 .specify/extensions/spex-collab/commands/speckit.spex-collab.revise.md create mode 100644 .specify/extensions/spex-collab/config-template.yml create mode 100644 .specify/extensions/spex-collab/extension.yml create mode 100644 .specify/extensions/spex-collab/templates/reviewers-template.md create mode 100644 .specify/extensions/spex-deep-review/commands/speckit.spex-deep-review.run.md create mode 100644 .specify/extensions/spex-deep-review/config-template.yml create mode 100644 .specify/extensions/spex-deep-review/extension.yml create mode 100644 .specify/extensions/spex-gates/commands/speckit.spex-gates.review-code.md create mode 100644 .specify/extensions/spex-gates/commands/speckit.spex-gates.review-plan.md create mode 100644 .specify/extensions/spex-gates/commands/speckit.spex-gates.review-spec.md create mode 100644 .specify/extensions/spex-gates/commands/speckit.spex-gates.stamp.md create mode 100644 .specify/extensions/spex-gates/commands/speckit.spex-gates.verify.md create mode 100644 .specify/extensions/spex-gates/extension.yml create mode 100644 .specify/extensions/spex-teams/commands/speckit.spex-teams.implement.md create mode 100644 .specify/extensions/spex-teams/commands/speckit.spex-teams.orchestrate.md create mode 100644 .specify/extensions/spex-teams/commands/speckit.spex-teams.research.md create mode 100644 .specify/extensions/spex-teams/config-template.yml create mode 100644 .specify/extensions/spex-teams/extension.yml create mode 100644 .specify/extensions/spex-worktrees/commands/speckit.spex-worktrees.manage.md create mode 100644 .specify/extensions/spex-worktrees/extension.yml create mode 100644 .specify/extensions/spex/commands/speckit.spex.brainstorm.md create mode 100644 .specify/extensions/spex/commands/speckit.spex.evolve.md create mode 100644 .specify/extensions/spex/commands/speckit.spex.extensions.md create mode 100644 .specify/extensions/spex/commands/speckit.spex.finish.md create mode 100644 .specify/extensions/spex/commands/speckit.spex.flow-state.md create mode 100644 .specify/extensions/spex/commands/speckit.spex.help.md create mode 100644 .specify/extensions/spex/commands/speckit.spex.ship.md create mode 100644 .specify/extensions/spex/commands/speckit.spex.spec-kit.md create mode 100644 .specify/extensions/spex/commands/speckit.spex.spec-refactoring.md create mode 100644 .specify/extensions/spex/commands/speckit.spex.using-superpowers.md create mode 100644 .specify/extensions/spex/extension.yml create mode 100644 .specify/feature.json create mode 100644 .specify/init-options.json create mode 100644 .specify/integration.json create mode 100644 .specify/integrations/claude.manifest.json create mode 100644 .specify/integrations/speckit.manifest.json create mode 100644 .specify/memory/constitution.md create mode 100755 .specify/scripts/bash/check-prerequisites.sh create mode 100755 .specify/scripts/bash/common.sh create mode 100755 .specify/scripts/bash/create-new-feature.sh create mode 100755 .specify/scripts/bash/setup-plan.sh create mode 100644 .specify/templates/checklist-template.md create mode 100644 .specify/templates/constitution-template.md create mode 100644 .specify/templates/plan-template.md create mode 100644 .specify/templates/spec-template.md create mode 100644 .specify/templates/tasks-template.md create mode 100644 .specify/workflows/speckit/workflow.yml create mode 100644 .specify/workflows/workflow-registry.json create mode 100644 specs/001-agentcard-into-status/REVIEWERS.md create mode 100644 specs/001-agentcard-into-status/checklists/requirements.md create mode 100644 specs/001-agentcard-into-status/contracts/agentruntime-status-card.md create mode 100644 specs/001-agentcard-into-status/data-model.md create mode 100644 specs/001-agentcard-into-status/plan.md create mode 100644 specs/001-agentcard-into-status/research.md create mode 100644 specs/001-agentcard-into-status/spec.md create mode 100644 specs/001-agentcard-into-status/tasks.md diff --git a/.gitignore b/.gitignore index d1bcaedd..0024553f 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,9 @@ kagenti-operator/config/crd/bases/_.yaml *.log debug Dockerfile.cross + +# spex: generated/local files +.claude/skills/ +.claude/settings.local.json +.specify/.spex-phase +.specify/.spex-state diff --git a/.specify/extensions.yml b/.specify/extensions.yml new file mode 100644 index 00000000..f87c1b4c --- /dev/null +++ b/.specify/extensions.yml @@ -0,0 +1,275 @@ +installed: [] +settings: + auto_execute_hooks: true +hooks: + before_constitution: + - extension: git + command: speckit.git.initialize + enabled: true + optional: false + prompt: Execute speckit.git.initialize? + description: Initialize Git repository before constitution setup + condition: null + before_specify: + - extension: git + command: speckit.git.feature + enabled: true + optional: false + prompt: Execute speckit.git.feature? + description: Create feature branch before specification + condition: null + before_clarify: + - extension: git + command: speckit.git.commit + enabled: true + optional: true + prompt: Commit outstanding changes before clarification? + description: Auto-commit before spec clarification + condition: null + - extension: spex + command: speckit.spex.flow-state + enabled: true + optional: false + prompt: Execute speckit.spex.flow-state? + description: Mark clarify as active in flow state + condition: null + before_plan: + - extension: git + command: speckit.git.commit + enabled: true + optional: true + prompt: Commit outstanding changes before planning? + description: Auto-commit before implementation planning + condition: null + - extension: spex + command: speckit.spex.flow-state + enabled: true + optional: false + prompt: Execute speckit.spex.flow-state? + description: Mark plan as active in flow state + condition: null + - extension: spex-teams + command: speckit.spex-teams.research + enabled: true + optional: true + prompt: Run parallel codebase research? + description: Parallel codebase research during planning via Agent Teams + condition: null + before_tasks: + - extension: git + command: speckit.git.commit + enabled: true + optional: true + prompt: Commit outstanding changes before task generation? + description: Auto-commit before task generation + condition: null + - extension: spex + command: speckit.spex.flow-state + enabled: true + optional: false + prompt: Execute speckit.spex.flow-state? + description: Mark tasks as active in flow state + condition: null + before_implement: + - extension: git + command: speckit.git.commit + enabled: true + optional: true + prompt: Commit outstanding changes before implementation? + description: Auto-commit before implementation + condition: null + - extension: spex + command: speckit.spex.flow-state + enabled: true + optional: false + prompt: Execute speckit.spex.flow-state? + description: Mark implement as active in flow state + condition: null + - extension: spex-collab + command: speckit.spex-collab.phase-split + enabled: true + optional: true + prompt: Review PR split for implementation phases? + description: Present phase split proposal before implementation + condition: null + before_checklist: + - extension: git + command: speckit.git.commit + enabled: true + optional: true + prompt: Commit outstanding changes before checklist? + description: Auto-commit before checklist generation + condition: null + before_analyze: + - extension: git + command: speckit.git.commit + enabled: true + optional: true + prompt: Commit outstanding changes before analysis? + description: Auto-commit before analysis + condition: null + before_taskstoissues: + - extension: git + command: speckit.git.commit + enabled: true + optional: true + prompt: Commit outstanding changes before issue sync? + description: Auto-commit before tasks-to-issues conversion + condition: null + after_constitution: + - extension: git + command: speckit.git.commit + enabled: true + optional: true + prompt: Commit constitution changes? + description: Auto-commit after constitution update + condition: null + after_specify: + - extension: git + command: speckit.git.commit + enabled: true + optional: true + prompt: Commit specification changes? + description: Auto-commit after specification + condition: null + - extension: spex + command: speckit.spex.flow-state + enabled: true + optional: false + prompt: Execute speckit.spex.flow-state? + description: Initialize flow state tracking after specification + condition: null + - extension: spex-gates + command: speckit.spex-gates.review-spec + enabled: true + optional: false + prompt: Execute speckit.spex-gates.review-spec? + description: Review spec soundness after specification + condition: null + - extension: spex-worktrees + command: speckit.spex-worktrees.manage + enabled: false + optional: false + prompt: Execute speckit.spex-worktrees.manage? + description: Create git worktree after specification + condition: null + after_clarify: + - extension: git + command: speckit.git.commit + enabled: true + optional: true + prompt: Commit clarification changes? + description: Auto-commit after spec clarification + condition: null + - extension: spex + command: speckit.spex.flow-state + enabled: true + optional: false + prompt: Execute speckit.spex.flow-state? + description: Mark clarification complete in flow state + condition: null + after_plan: + - extension: git + command: speckit.git.commit + enabled: true + optional: true + prompt: Commit plan changes? + description: Auto-commit after implementation planning + condition: null + - extension: spex + command: speckit.spex.flow-state + enabled: true + optional: false + prompt: Execute speckit.spex.flow-state? + description: Clear running state after planning + condition: null + after_tasks: + - extension: git + command: speckit.git.commit + enabled: true + optional: true + prompt: Commit task changes? + description: Auto-commit after task generation + condition: null + - extension: spex + command: speckit.spex.flow-state + enabled: true + optional: false + prompt: Execute speckit.spex.flow-state? + description: Clear running state after task generation + condition: null + - extension: spex-gates + command: speckit.spex-gates.review-plan + enabled: true + optional: false + prompt: Execute speckit.spex-gates.review-plan? + description: Review plan and task quality after task generation + condition: null + - extension: spex-collab + command: speckit.spex-collab.reviewers + enabled: true + optional: false + prompt: Execute speckit.spex-collab.reviewers? + description: Generate REVIEWERS.md after task generation + condition: null + after_implement: + - extension: git + command: speckit.git.commit + enabled: true + optional: true + prompt: Commit implementation changes? + description: Auto-commit after implementation + condition: null + - extension: spex + command: speckit.spex.flow-state + enabled: true + optional: false + prompt: Execute speckit.spex.flow-state? + description: Mark implementation complete in flow state + condition: null + - extension: spex-gates + command: speckit.spex-gates.review-code + enabled: true + optional: false + prompt: Execute speckit.spex-gates.review-code? + description: Review code compliance after implementation + condition: null + - extension: spex-deep-review + command: speckit.spex-deep-review.run + enabled: true + optional: true + prompt: Run deep multi-perspective review? + description: Multi-agent code review after implementation + condition: null + after_checklist: + - extension: git + command: speckit.git.commit + enabled: true + optional: true + prompt: Commit checklist changes? + description: Auto-commit after checklist generation + condition: null + after_analyze: + - extension: git + command: speckit.git.commit + enabled: true + optional: true + prompt: Commit analysis results? + description: Auto-commit after analysis + condition: null + after_taskstoissues: + - extension: git + command: speckit.git.commit + enabled: true + optional: true + prompt: Commit after syncing issues? + description: Auto-commit after tasks-to-issues conversion + condition: null + after_finish: + - extension: spex + command: speckit.spex.flow-state + enabled: true + optional: false + prompt: Execute speckit.spex.flow-state? + description: Remove flow state file after feature completion + condition: null diff --git a/.specify/extensions/.registry b/.specify/extensions/.registry new file mode 100644 index 00000000..5a103a64 --- /dev/null +++ b/.specify/extensions/.registry @@ -0,0 +1,126 @@ +{ + "schema_version": "1.0", + "extensions": { + "git": { + "version": "1.0.0", + "source": "local", + "manifest_hash": "sha256:9731aa8143a72fbebfdb440f155038ab42642517c2b2bdbbf67c8fdbe076ed79", + "enabled": true, + "priority": 10, + "registered_commands": { + "claude": [ + "speckit.git.feature", + "speckit.git.validate", + "speckit.git.remote", + "speckit.git.initialize", + "speckit.git.commit" + ] + }, + "registered_skills": [], + "installed_at": "2026-05-21T07:10:44.469134+00:00" + }, + "spex": { + "version": "1.0.0", + "source": "local", + "manifest_hash": "sha256:ac6b6faf3460b5a3cbbecf9082275e504671af0d5d59496d2ef2f8dc087f806e", + "enabled": true, + "priority": 10, + "registered_commands": { + "claude": [ + "speckit.spex.brainstorm", + "speckit.spex.ship", + "speckit.spex.help", + "speckit.spex.evolve", + "speckit.spex.spec-refactoring", + "speckit.spex.using-superpowers", + "speckit.spex.spec-kit", + "speckit.spex.extensions", + "speckit.spex.finish", + "speckit.spex.flow-state" + ] + }, + "registered_skills": [], + "installed_at": "2026-05-21T07:10:44.591398+00:00" + }, + "spex-gates": { + "version": "1.0.0", + "source": "local", + "manifest_hash": "sha256:7fe166c5c1c813aa3973afd71929d79b2eb26b19b12d954fcf801c5d993764af", + "enabled": true, + "priority": 10, + "registered_commands": { + "claude": [ + "speckit.spex-gates.review-spec", + "speckit.spex-gates.review-plan", + "speckit.spex-gates.review-code", + "speckit.spex-gates.verify", + "speckit.spex-gates.stamp" + ] + }, + "registered_skills": [], + "installed_at": "2026-05-21T07:10:44.705226+00:00" + }, + "spex-worktrees": { + "version": "1.0.0", + "source": "local", + "manifest_hash": "sha256:8e427d7d4c4c865401bcbb4f36dd16614fa1fc201ce7d314a27a02185e28461a", + "enabled": false, + "priority": 10, + "registered_commands": { + "claude": [ + "speckit.spex-worktrees.manage" + ] + }, + "registered_skills": [], + "installed_at": "2026-05-21T07:10:44.814601+00:00" + }, + "spex-deep-review": { + "version": "1.0.0", + "source": "local", + "manifest_hash": "sha256:854146d677b35a744606d443316a6c7f1383d978b38635539410d8b8db34b1e8", + "enabled": true, + "priority": 10, + "registered_commands": { + "claude": [ + "speckit.spex-deep-review.run" + ] + }, + "registered_skills": [], + "installed_at": "2026-05-21T07:10:44.938774+00:00" + }, + "spex-teams": { + "version": "1.0.0", + "source": "local", + "manifest_hash": "sha256:99c4375c8471c6667e0c611210e60550d380e55b69166e79417b412c91367c3b", + "enabled": true, + "priority": 10, + "registered_commands": { + "claude": [ + "speckit.spex-teams.orchestrate", + "speckit.spex-teams.research", + "speckit.spex-teams.implement" + ] + }, + "registered_skills": [], + "installed_at": "2026-05-21T07:10:45.049496+00:00" + }, + "spex-collab": { + "version": "1.0.0", + "source": "local", + "manifest_hash": "sha256:fab8be0594a1f61314fa9d498de3a1b008138a1033db5ed1f813508f615632d5", + "enabled": true, + "priority": 10, + "registered_commands": { + "claude": [ + "speckit.spex-collab.reviewers", + "speckit.spex-collab.phase-split", + "speckit.spex-collab.phase-manager", + "speckit.spex-collab.revise", + "speckit.spex-collab.reconcile" + ] + }, + "registered_skills": [], + "installed_at": "2026-05-21T07:10:45.172133+00:00" + } + } +} \ No newline at end of file diff --git a/.specify/extensions/git/README.md b/.specify/extensions/git/README.md new file mode 100644 index 00000000..31ba75c3 --- /dev/null +++ b/.specify/extensions/git/README.md @@ -0,0 +1,100 @@ +# Git Branching Workflow Extension + +Git repository initialization, feature branch creation, numbering (sequential/timestamp), validation, remote detection, and auto-commit for Spec Kit. + +## Overview + +This extension provides Git operations as an optional, self-contained module. It manages: + +- **Repository initialization** with configurable commit messages +- **Feature branch creation** with sequential (`001-feature-name`) or timestamp (`20260319-143022-feature-name`) numbering +- **Branch validation** to ensure branches follow naming conventions +- **Git remote detection** for GitHub integration (e.g., issue creation) +- **Auto-commit** after core commands (configurable per-command with custom messages) + +## Commands + +| Command | Description | +|---------|-------------| +| `speckit.git.initialize` | Initialize a Git repository with a configurable commit message | +| `speckit.git.feature` | Create a feature branch with sequential or timestamp numbering | +| `speckit.git.validate` | Validate current branch follows feature branch naming conventions | +| `speckit.git.remote` | Detect Git remote URL for GitHub integration | +| `speckit.git.commit` | Auto-commit changes (configurable per-command enable/disable and messages) | + +## Hooks + +| Event | Command | Optional | Description | +|-------|---------|----------|-------------| +| `before_constitution` | `speckit.git.initialize` | No | Init git repo before constitution | +| `before_specify` | `speckit.git.feature` | No | Create feature branch before specification | +| `before_clarify` | `speckit.git.commit` | Yes | Commit outstanding changes before clarification | +| `before_plan` | `speckit.git.commit` | Yes | Commit outstanding changes before planning | +| `before_tasks` | `speckit.git.commit` | Yes | Commit outstanding changes before task generation | +| `before_implement` | `speckit.git.commit` | Yes | Commit outstanding changes before implementation | +| `before_checklist` | `speckit.git.commit` | Yes | Commit outstanding changes before checklist | +| `before_analyze` | `speckit.git.commit` | Yes | Commit outstanding changes before analysis | +| `before_taskstoissues` | `speckit.git.commit` | Yes | Commit outstanding changes before issue sync | +| `after_constitution` | `speckit.git.commit` | Yes | Auto-commit after constitution update | +| `after_specify` | `speckit.git.commit` | Yes | Auto-commit after specification | +| `after_clarify` | `speckit.git.commit` | Yes | Auto-commit after clarification | +| `after_plan` | `speckit.git.commit` | Yes | Auto-commit after planning | +| `after_tasks` | `speckit.git.commit` | Yes | Auto-commit after task generation | +| `after_implement` | `speckit.git.commit` | Yes | Auto-commit after implementation | +| `after_checklist` | `speckit.git.commit` | Yes | Auto-commit after checklist | +| `after_analyze` | `speckit.git.commit` | Yes | Auto-commit after analysis | +| `after_taskstoissues` | `speckit.git.commit` | Yes | Auto-commit after issue sync | + +## Configuration + +Configuration is stored in `.specify/extensions/git/git-config.yml`: + +```yaml +# Branch numbering strategy: "sequential" or "timestamp" +branch_numbering: sequential + +# Custom commit message for git init +init_commit_message: "[Spec Kit] Initial commit" + +# Auto-commit per command (all disabled by default) +# Example: enable auto-commit after specify +auto_commit: + default: false + after_specify: + enabled: true + message: "[Spec Kit] Add specification" +``` + +## Installation + +```bash +# Install the bundled git extension (no network required) +specify extension add git +``` + +## Disabling + +```bash +# Disable the git extension (spec creation continues without branching) +specify extension disable git + +# Re-enable it +specify extension enable git +``` + +## Graceful Degradation + +When Git is not installed or the directory is not a Git repository: +- Spec directories are still created under `specs/` +- Branch creation is skipped with a warning +- Branch validation is skipped with a warning +- Remote detection returns empty results + +## Scripts + +The extension bundles cross-platform scripts: + +- `scripts/bash/create-new-feature.sh` — Bash implementation +- `scripts/bash/git-common.sh` — Shared Git utilities (Bash) +- `scripts/powershell/create-new-feature.ps1` — PowerShell implementation +- `scripts/powershell/git-common.ps1` — Shared Git utilities (PowerShell) diff --git a/.specify/extensions/git/commands/speckit.git.commit.md b/.specify/extensions/git/commands/speckit.git.commit.md new file mode 100644 index 00000000..e606f911 --- /dev/null +++ b/.specify/extensions/git/commands/speckit.git.commit.md @@ -0,0 +1,48 @@ +--- +description: "Auto-commit changes after a Spec Kit command completes" +--- + +# Auto-Commit Changes + +Automatically stage and commit all changes after a Spec Kit command completes. + +## Behavior + +This command is invoked as a hook after (or before) core commands. It: + +1. Determines the event name from the hook context (e.g., if invoked as an `after_specify` hook, the event is `after_specify`; if `before_plan`, the event is `before_plan`) +2. Checks `.specify/extensions/git/git-config.yml` for the `auto_commit` section +3. Looks up the specific event key to see if auto-commit is enabled +4. Falls back to `auto_commit.default` if no event-specific key exists +5. Uses the per-command `message` if configured, otherwise a default message +6. If enabled and there are uncommitted changes, runs `git add .` + `git commit` + +## Execution + +Determine the event name from the hook that triggered this command, then run the script: + +- **Bash**: `.specify/extensions/git/scripts/bash/auto-commit.sh ` +- **PowerShell**: `.specify/extensions/git/scripts/powershell/auto-commit.ps1 ` + +Replace `` with the actual hook event (e.g., `after_specify`, `before_plan`, `after_implement`). + +## Configuration + +In `.specify/extensions/git/git-config.yml`: + +```yaml +auto_commit: + default: false # Global toggle — set true to enable for all commands + after_specify: + enabled: true # Override per-command + message: "[Spec Kit] Add specification" + after_plan: + enabled: false + message: "[Spec Kit] Add implementation plan" +``` + +## Graceful Degradation + +- If Git is not available or the current directory is not a repository: skips with a warning +- If no config file exists: skips (disabled by default) +- If no changes to commit: skips with a message diff --git a/.specify/extensions/git/commands/speckit.git.feature.md b/.specify/extensions/git/commands/speckit.git.feature.md new file mode 100644 index 00000000..1a9c5e35 --- /dev/null +++ b/.specify/extensions/git/commands/speckit.git.feature.md @@ -0,0 +1,67 @@ +--- +description: "Create a feature branch with sequential or timestamp numbering" +--- + +# Create Feature Branch + +Create and switch to a new git feature branch for the given specification. This command handles **branch creation only** — the spec directory and files are created by the core `/speckit.specify` workflow. + +## User Input + +```text +$ARGUMENTS +``` + +You **MUST** consider the user input before proceeding (if not empty). + +## Environment Variable Override + +If the user explicitly provided `GIT_BRANCH_NAME` (e.g., via environment variable, argument, or in their request), pass it through to the script by setting the `GIT_BRANCH_NAME` environment variable before invoking the script. When `GIT_BRANCH_NAME` is set: +- The script uses the exact value as the branch name, bypassing all prefix/suffix generation +- `--short-name`, `--number`, and `--timestamp` flags are ignored +- `FEATURE_NUM` is extracted from the name if it starts with a numeric prefix, otherwise set to the full branch name + +## Prerequisites + +- Verify Git is available by running `git rev-parse --is-inside-work-tree 2>/dev/null` +- If Git is not available, warn the user and skip branch creation + +## Branch Numbering Mode + +Determine the branch numbering strategy by checking configuration in this order: + +1. Check `.specify/extensions/git/git-config.yml` for `branch_numbering` value +2. Check `.specify/init-options.json` for `branch_numbering` value (backward compatibility) +3. Default to `sequential` if neither exists + +## Execution + +Generate a concise short name (2-4 words) for the branch: +- Analyze the feature description and extract the most meaningful keywords +- Use action-noun format when possible (e.g., "add-user-auth", "fix-payment-bug") +- Preserve technical terms and acronyms (OAuth2, API, JWT, etc.) + +Run the appropriate script based on your platform: + +- **Bash**: `.specify/extensions/git/scripts/bash/create-new-feature.sh --json --short-name "" ""` +- **Bash (timestamp)**: `.specify/extensions/git/scripts/bash/create-new-feature.sh --json --timestamp --short-name "" ""` +- **PowerShell**: `.specify/extensions/git/scripts/powershell/create-new-feature.ps1 -Json -ShortName "" ""` +- **PowerShell (timestamp)**: `.specify/extensions/git/scripts/powershell/create-new-feature.ps1 -Json -Timestamp -ShortName "" ""` + +**IMPORTANT**: +- Do NOT pass `--number` — the script determines the correct next number automatically +- Always include the JSON flag (`--json` for Bash, `-Json` for PowerShell) so the output can be parsed reliably +- You must only ever run this script once per feature +- The JSON output will contain `BRANCH_NAME` and `FEATURE_NUM` + +## Graceful Degradation + +If Git is not installed or the current directory is not a Git repository: +- Branch creation is skipped with a warning: `[specify] Warning: Git repository not detected; skipped branch creation` +- The script still outputs `BRANCH_NAME` and `FEATURE_NUM` so the caller can reference them + +## Output + +The script outputs JSON with: +- `BRANCH_NAME`: The branch name (e.g., `003-user-auth` or `20260319-143022-user-auth`) +- `FEATURE_NUM`: The numeric or timestamp prefix used diff --git a/.specify/extensions/git/commands/speckit.git.initialize.md b/.specify/extensions/git/commands/speckit.git.initialize.md new file mode 100644 index 00000000..4451ee6b --- /dev/null +++ b/.specify/extensions/git/commands/speckit.git.initialize.md @@ -0,0 +1,49 @@ +--- +description: "Initialize a Git repository with an initial commit" +--- + +# Initialize Git Repository + +Initialize a Git repository in the current project directory if one does not already exist. + +## Execution + +Run the appropriate script from the project root: + +- **Bash**: `.specify/extensions/git/scripts/bash/initialize-repo.sh` +- **PowerShell**: `.specify/extensions/git/scripts/powershell/initialize-repo.ps1` + +If the extension scripts are not found, fall back to: +- **Bash**: `git init && git add . && git commit -m "Initial commit from Specify template"` +- **PowerShell**: `git init; git add .; git commit -m "Initial commit from Specify template"` + +The script handles all checks internally: +- Skips if Git is not available +- Skips if already inside a Git repository +- Runs `git init`, `git add .`, and `git commit` with an initial commit message + +## Customization + +Replace the script to add project-specific Git initialization steps: +- Custom `.gitignore` templates +- Default branch naming (`git config init.defaultBranch`) +- Git LFS setup +- Git hooks installation +- Commit signing configuration +- Git Flow initialization + +## Output + +On success: +- `✓ Git repository initialized` + +## Graceful Degradation + +If Git is not installed: +- Warn the user +- Skip repository initialization +- The project continues to function without Git (specs can still be created under `specs/`) + +If Git is installed but `git init`, `git add .`, or `git commit` fails: +- Surface the error to the user +- Stop this command rather than continuing with a partially initialized repository diff --git a/.specify/extensions/git/commands/speckit.git.remote.md b/.specify/extensions/git/commands/speckit.git.remote.md new file mode 100644 index 00000000..712a3e8b --- /dev/null +++ b/.specify/extensions/git/commands/speckit.git.remote.md @@ -0,0 +1,45 @@ +--- +description: "Detect Git remote URL for GitHub integration" +--- + +# Detect Git Remote URL + +Detect the Git remote URL for integration with GitHub services (e.g., issue creation). + +## Prerequisites + +- Check if Git is available by running `git rev-parse --is-inside-work-tree 2>/dev/null` +- If Git is not available, output a warning and return empty: + ``` + [specify] Warning: Git repository not detected; cannot determine remote URL + ``` + +## Execution + +Run the following command to get the remote URL: + +```bash +git config --get remote.origin.url +``` + +## Output + +Parse the remote URL and determine: + +1. **Repository owner**: Extract from the URL (e.g., `github` from `https://github.com/github/spec-kit.git`) +2. **Repository name**: Extract from the URL (e.g., `spec-kit` from `https://github.com/github/spec-kit.git`) +3. **Is GitHub**: Whether the remote points to a GitHub repository + +Supported URL formats: +- HTTPS: `https://github.com//.git` +- SSH: `git@github.com:/.git` + +> [!CAUTION] +> ONLY report a GitHub repository if the remote URL actually points to github.com. +> Do NOT assume the remote is GitHub if the URL format doesn't match. + +## Graceful Degradation + +If Git is not installed, the directory is not a Git repository, or no remote is configured: +- Return an empty result +- Do NOT error — other workflows should continue without Git remote information diff --git a/.specify/extensions/git/commands/speckit.git.validate.md b/.specify/extensions/git/commands/speckit.git.validate.md new file mode 100644 index 00000000..dd84618c --- /dev/null +++ b/.specify/extensions/git/commands/speckit.git.validate.md @@ -0,0 +1,49 @@ +--- +description: "Validate current branch follows feature branch naming conventions" +--- + +# Validate Feature Branch + +Validate that the current Git branch follows the expected feature branch naming conventions. + +## Prerequisites + +- Check if Git is available by running `git rev-parse --is-inside-work-tree 2>/dev/null` +- If Git is not available, output a warning and skip validation: + ``` + [specify] Warning: Git repository not detected; skipped branch validation + ``` + +## Validation Rules + +Get the current branch name: + +```bash +git rev-parse --abbrev-ref HEAD +``` + +The branch name must match one of these patterns: + +1. **Sequential**: `^[0-9]{3,}-` (e.g., `001-feature-name`, `042-fix-bug`, `1000-big-feature`) +2. **Timestamp**: `^[0-9]{8}-[0-9]{6}-` (e.g., `20260319-143022-feature-name`) + +## Execution + +If on a feature branch (matches either pattern): +- Output: `✓ On feature branch: ` +- Check if the corresponding spec directory exists under `specs/`: + - For sequential branches, look for `specs/-*` where prefix matches the numeric portion + - For timestamp branches, look for `specs/-*` where prefix matches the `YYYYMMDD-HHMMSS` portion +- If spec directory exists: `✓ Spec directory found: ` +- If spec directory missing: `⚠ No spec directory found for prefix ` + +If NOT on a feature branch: +- Output: `✗ Not on a feature branch. Current branch: ` +- Output: `Feature branches should be named like: 001-feature-name or 20260319-143022-feature-name` + +## Graceful Degradation + +If Git is not installed or the directory is not a Git repository: +- Check the `SPECIFY_FEATURE` environment variable as a fallback +- If set, validate that value against the naming patterns +- If not set, skip validation with a warning diff --git a/.specify/extensions/git/config-template.yml b/.specify/extensions/git/config-template.yml new file mode 100644 index 00000000..8c414bab --- /dev/null +++ b/.specify/extensions/git/config-template.yml @@ -0,0 +1,62 @@ +# Git Branching Workflow Extension Configuration +# Copied to .specify/extensions/git/git-config.yml on install + +# Branch numbering strategy: "sequential" (001, 002, ...) or "timestamp" (YYYYMMDD-HHMMSS) +branch_numbering: sequential + +# Commit message used by `git commit` during repository initialization +init_commit_message: "[Spec Kit] Initial commit" + +# Auto-commit before/after core commands. +# Set "default" to enable for all commands, then override per-command. +# Each key can be true/false. Message is customizable per-command. +auto_commit: + default: false + before_clarify: + enabled: false + message: "[Spec Kit] Save progress before clarification" + before_plan: + enabled: false + message: "[Spec Kit] Save progress before planning" + before_tasks: + enabled: false + message: "[Spec Kit] Save progress before task generation" + before_implement: + enabled: false + message: "[Spec Kit] Save progress before implementation" + before_checklist: + enabled: false + message: "[Spec Kit] Save progress before checklist" + before_analyze: + enabled: false + message: "[Spec Kit] Save progress before analysis" + before_taskstoissues: + enabled: false + message: "[Spec Kit] Save progress before issue sync" + after_constitution: + enabled: false + message: "[Spec Kit] Add project constitution" + after_specify: + enabled: false + message: "[Spec Kit] Add specification" + after_clarify: + enabled: false + message: "[Spec Kit] Clarify specification" + after_plan: + enabled: false + message: "[Spec Kit] Add implementation plan" + after_tasks: + enabled: false + message: "[Spec Kit] Add tasks" + after_implement: + enabled: false + message: "[Spec Kit] Implementation progress" + after_checklist: + enabled: false + message: "[Spec Kit] Add checklist" + after_analyze: + enabled: false + message: "[Spec Kit] Add analysis report" + after_taskstoissues: + enabled: false + message: "[Spec Kit] Sync tasks to issues" diff --git a/.specify/extensions/git/extension.yml b/.specify/extensions/git/extension.yml new file mode 100644 index 00000000..13c1977e --- /dev/null +++ b/.specify/extensions/git/extension.yml @@ -0,0 +1,140 @@ +schema_version: "1.0" + +extension: + id: git + name: "Git Branching Workflow" + version: "1.0.0" + description: "Feature branch creation, numbering (sequential/timestamp), validation, and Git remote detection" + author: spec-kit-core + repository: https://github.com/github/spec-kit + license: MIT + +requires: + speckit_version: ">=0.2.0" + tools: + - name: git + required: false + +provides: + commands: + - name: speckit.git.feature + file: commands/speckit.git.feature.md + description: "Create a feature branch with sequential or timestamp numbering" + - name: speckit.git.validate + file: commands/speckit.git.validate.md + description: "Validate current branch follows feature branch naming conventions" + - name: speckit.git.remote + file: commands/speckit.git.remote.md + description: "Detect Git remote URL for GitHub integration" + - name: speckit.git.initialize + file: commands/speckit.git.initialize.md + description: "Initialize a Git repository with an initial commit" + - name: speckit.git.commit + file: commands/speckit.git.commit.md + description: "Auto-commit changes after a Spec Kit command completes" + + config: + - name: "git-config.yml" + template: "config-template.yml" + description: "Git branching configuration" + required: false + +hooks: + before_constitution: + command: speckit.git.initialize + optional: false + description: "Initialize Git repository before constitution setup" + before_specify: + command: speckit.git.feature + optional: false + description: "Create feature branch before specification" + before_clarify: + command: speckit.git.commit + optional: true + prompt: "Commit outstanding changes before clarification?" + description: "Auto-commit before spec clarification" + before_plan: + command: speckit.git.commit + optional: true + prompt: "Commit outstanding changes before planning?" + description: "Auto-commit before implementation planning" + before_tasks: + command: speckit.git.commit + optional: true + prompt: "Commit outstanding changes before task generation?" + description: "Auto-commit before task generation" + before_implement: + command: speckit.git.commit + optional: true + prompt: "Commit outstanding changes before implementation?" + description: "Auto-commit before implementation" + before_checklist: + command: speckit.git.commit + optional: true + prompt: "Commit outstanding changes before checklist?" + description: "Auto-commit before checklist generation" + before_analyze: + command: speckit.git.commit + optional: true + prompt: "Commit outstanding changes before analysis?" + description: "Auto-commit before analysis" + before_taskstoissues: + command: speckit.git.commit + optional: true + prompt: "Commit outstanding changes before issue sync?" + description: "Auto-commit before tasks-to-issues conversion" + after_constitution: + command: speckit.git.commit + optional: true + prompt: "Commit constitution changes?" + description: "Auto-commit after constitution update" + after_specify: + command: speckit.git.commit + optional: true + prompt: "Commit specification changes?" + description: "Auto-commit after specification" + after_clarify: + command: speckit.git.commit + optional: true + prompt: "Commit clarification changes?" + description: "Auto-commit after spec clarification" + after_plan: + command: speckit.git.commit + optional: true + prompt: "Commit plan changes?" + description: "Auto-commit after implementation planning" + after_tasks: + command: speckit.git.commit + optional: true + prompt: "Commit task changes?" + description: "Auto-commit after task generation" + after_implement: + command: speckit.git.commit + optional: true + prompt: "Commit implementation changes?" + description: "Auto-commit after implementation" + after_checklist: + command: speckit.git.commit + optional: true + prompt: "Commit checklist changes?" + description: "Auto-commit after checklist generation" + after_analyze: + command: speckit.git.commit + optional: true + prompt: "Commit analysis results?" + description: "Auto-commit after analysis" + after_taskstoissues: + command: speckit.git.commit + optional: true + prompt: "Commit after syncing issues?" + description: "Auto-commit after tasks-to-issues conversion" + +tags: + - "git" + - "branching" + - "workflow" + +config: + defaults: + branch_numbering: sequential + init_commit_message: "[Spec Kit] Initial commit" diff --git a/.specify/extensions/git/git-config.yml b/.specify/extensions/git/git-config.yml new file mode 100644 index 00000000..418ac729 --- /dev/null +++ b/.specify/extensions/git/git-config.yml @@ -0,0 +1,60 @@ +# Git Branching Workflow Extension Configuration +# Copied to .specify/extensions/git/git-config.yml on install + +# Branch numbering strategy: "sequential" (001, 002, ...) or "timestamp" (YYYYMMDD-HHMMSS) +branch_numbering: sequential +# Commit message used by `git commit` during repository initialization +init_commit_message: "[Spec Kit] Initial commit" +# Auto-commit before/after core commands. +# Set "default" to enable for all commands, then override per-command. +# Each key can be true/false. Message is customizable per-command. +auto_commit: + default: false + before_clarify: + enabled: false + message: "[Spec Kit] Save progress before clarification" + before_plan: + enabled: false + message: "[Spec Kit] Save progress before planning" + before_tasks: + enabled: false + message: "[Spec Kit] Save progress before task generation" + before_implement: + enabled: false + message: "[Spec Kit] Save progress before implementation" + before_checklist: + enabled: false + message: "[Spec Kit] Save progress before checklist" + before_analyze: + enabled: false + message: "[Spec Kit] Save progress before analysis" + before_taskstoissues: + enabled: false + message: "[Spec Kit] Save progress before issue sync" + after_constitution: + enabled: false + message: "[Spec Kit] Add project constitution" + after_specify: + enabled: true + message: "[Spec Kit] Add specification" + after_clarify: + enabled: false + message: "[Spec Kit] Clarify specification" + after_plan: + enabled: true + message: "[Spec Kit] Add implementation plan" + after_tasks: + enabled: true + message: "[Spec Kit] Add tasks" + after_implement: + enabled: true + message: "[Spec Kit] Implementation progress" + after_checklist: + enabled: false + message: "[Spec Kit] Add checklist" + after_analyze: + enabled: false + message: "[Spec Kit] Add analysis report" + after_taskstoissues: + enabled: false + message: "[Spec Kit] Sync tasks to issues" diff --git a/.specify/extensions/git/scripts/bash/auto-commit.sh b/.specify/extensions/git/scripts/bash/auto-commit.sh new file mode 100755 index 00000000..f0b42318 --- /dev/null +++ b/.specify/extensions/git/scripts/bash/auto-commit.sh @@ -0,0 +1,140 @@ +#!/usr/bin/env bash +# Git extension: auto-commit.sh +# Automatically commit changes after a Spec Kit command completes. +# Checks per-command config keys in git-config.yml before committing. +# +# Usage: auto-commit.sh +# e.g.: auto-commit.sh after_specify + +set -e + +EVENT_NAME="${1:-}" +if [ -z "$EVENT_NAME" ]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +_find_project_root() { + local dir="$1" + while [ "$dir" != "/" ]; do + if [ -d "$dir/.specify" ] || [ -d "$dir/.git" ]; then + echo "$dir" + return 0 + fi + dir="$(dirname "$dir")" + done + return 1 +} + +REPO_ROOT=$(_find_project_root "$SCRIPT_DIR") || REPO_ROOT="$(pwd)" +cd "$REPO_ROOT" + +# Check if git is available +if ! command -v git >/dev/null 2>&1; then + echo "[specify] Warning: Git not found; skipped auto-commit" >&2 + exit 0 +fi + +if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + echo "[specify] Warning: Not a Git repository; skipped auto-commit" >&2 + exit 0 +fi + +# Read per-command config from git-config.yml +_config_file="$REPO_ROOT/.specify/extensions/git/git-config.yml" +_enabled=false +_commit_msg="" + +if [ -f "$_config_file" ]; then + # Parse the auto_commit section for this event. + # Look for auto_commit..enabled and .message + # Also check auto_commit.default as fallback. + _in_auto_commit=false + _in_event=false + _default_enabled=false + + while IFS= read -r _line; do + # Detect auto_commit: section + if echo "$_line" | grep -q '^auto_commit:'; then + _in_auto_commit=true + _in_event=false + continue + fi + + # Exit auto_commit section on next top-level key + if $_in_auto_commit && echo "$_line" | grep -Eq '^[a-z]'; then + break + fi + + if $_in_auto_commit; then + # Check default key + if echo "$_line" | grep -Eq "^[[:space:]]+default:[[:space:]]"; then + _val=$(echo "$_line" | sed 's/^[^:]*:[[:space:]]*//' | tr -d '[:space:]' | tr '[:upper:]' '[:lower:]') + [ "$_val" = "true" ] && _default_enabled=true + fi + + # Detect our event subsection + if echo "$_line" | grep -Eq "^[[:space:]]+${EVENT_NAME}:"; then + _in_event=true + continue + fi + + # Inside our event subsection + if $_in_event; then + # Exit on next sibling key (same indent level as event name) + if echo "$_line" | grep -Eq '^[[:space:]]{2}[a-z]' && ! echo "$_line" | grep -Eq '^[[:space:]]{4}'; then + _in_event=false + continue + fi + if echo "$_line" | grep -Eq '[[:space:]]+enabled:'; then + _val=$(echo "$_line" | sed 's/^[^:]*:[[:space:]]*//' | tr -d '[:space:]' | tr '[:upper:]' '[:lower:]') + [ "$_val" = "true" ] && _enabled=true + [ "$_val" = "false" ] && _enabled=false + fi + if echo "$_line" | grep -Eq '[[:space:]]+message:'; then + _commit_msg=$(echo "$_line" | sed 's/^[^:]*:[[:space:]]*//' | sed 's/^["'\'']//' | sed 's/["'\'']*$//') + fi + fi + fi + done < "$_config_file" + + # If event-specific key not found, use default + if [ "$_enabled" = "false" ] && [ "$_default_enabled" = "true" ]; then + # Only use default if the event wasn't explicitly set to false + # Check if event section existed at all + if ! grep -q "^[[:space:]]*${EVENT_NAME}:" "$_config_file" 2>/dev/null; then + _enabled=true + fi + fi +else + # No config file — auto-commit disabled by default + exit 0 +fi + +if [ "$_enabled" != "true" ]; then + exit 0 +fi + +# Check if there are changes to commit +if git diff --quiet HEAD 2>/dev/null && git diff --cached --quiet 2>/dev/null && [ -z "$(git ls-files --others --exclude-standard 2>/dev/null)" ]; then + echo "[specify] No changes to commit after $EVENT_NAME" >&2 + exit 0 +fi + +# Derive a human-readable command name from the event +# e.g., after_specify -> specify, before_plan -> plan +_command_name=$(echo "$EVENT_NAME" | sed 's/^after_//' | sed 's/^before_//') +_phase=$(echo "$EVENT_NAME" | grep -q '^before_' && echo 'before' || echo 'after') + +# Use custom message if configured, otherwise default +if [ -z "$_commit_msg" ]; then + _commit_msg="[Spec Kit] Auto-commit ${_phase} ${_command_name}" +fi + +# Stage and commit +_git_out=$(git add . 2>&1) || { echo "[specify] Error: git add failed: $_git_out" >&2; exit 1; } +_git_out=$(git commit -q -m "$_commit_msg" 2>&1) || { echo "[specify] Error: git commit failed: $_git_out" >&2; exit 1; } + +echo "[OK] Changes committed ${_phase} ${_command_name}" >&2 diff --git a/.specify/extensions/git/scripts/bash/create-new-feature.sh b/.specify/extensions/git/scripts/bash/create-new-feature.sh new file mode 100755 index 00000000..286aaf76 --- /dev/null +++ b/.specify/extensions/git/scripts/bash/create-new-feature.sh @@ -0,0 +1,453 @@ +#!/usr/bin/env bash +# Git extension: create-new-feature.sh +# Adapted from core scripts/bash/create-new-feature.sh for extension layout. +# Sources common.sh from the project's installed scripts, falling back to +# git-common.sh for minimal git helpers. + +set -e + +JSON_MODE=false +DRY_RUN=false +ALLOW_EXISTING=false +SHORT_NAME="" +BRANCH_NUMBER="" +USE_TIMESTAMP=false +ARGS=() +i=1 +while [ $i -le $# ]; do + arg="${!i}" + case "$arg" in + --json) + JSON_MODE=true + ;; + --dry-run) + DRY_RUN=true + ;; + --allow-existing-branch) + ALLOW_EXISTING=true + ;; + --short-name) + if [ $((i + 1)) -gt $# ]; then + echo 'Error: --short-name requires a value' >&2 + exit 1 + fi + i=$((i + 1)) + next_arg="${!i}" + if [[ "$next_arg" == --* ]]; then + echo 'Error: --short-name requires a value' >&2 + exit 1 + fi + SHORT_NAME="$next_arg" + ;; + --number) + if [ $((i + 1)) -gt $# ]; then + echo 'Error: --number requires a value' >&2 + exit 1 + fi + i=$((i + 1)) + next_arg="${!i}" + if [[ "$next_arg" == --* ]]; then + echo 'Error: --number requires a value' >&2 + exit 1 + fi + BRANCH_NUMBER="$next_arg" + if [[ ! "$BRANCH_NUMBER" =~ ^[0-9]+$ ]]; then + echo 'Error: --number must be a non-negative integer' >&2 + exit 1 + fi + ;; + --timestamp) + USE_TIMESTAMP=true + ;; + --help|-h) + echo "Usage: $0 [--json] [--dry-run] [--allow-existing-branch] [--short-name ] [--number N] [--timestamp] " + echo "" + echo "Options:" + echo " --json Output in JSON format" + echo " --dry-run Compute branch name without creating the branch" + echo " --allow-existing-branch Switch to branch if it already exists instead of failing" + echo " --short-name Provide a custom short name (2-4 words) for the branch" + echo " --number N Specify branch number manually (overrides auto-detection)" + echo " --timestamp Use timestamp prefix (YYYYMMDD-HHMMSS) instead of sequential numbering" + echo " --help, -h Show this help message" + echo "" + echo "Environment variables:" + echo " GIT_BRANCH_NAME Use this exact branch name, bypassing all prefix/suffix generation" + echo "" + echo "Examples:" + echo " $0 'Add user authentication system' --short-name 'user-auth'" + echo " $0 'Implement OAuth2 integration for API' --number 5" + echo " $0 --timestamp --short-name 'user-auth' 'Add user authentication'" + echo " GIT_BRANCH_NAME=my-branch $0 'feature description'" + exit 0 + ;; + *) + ARGS+=("$arg") + ;; + esac + i=$((i + 1)) +done + +FEATURE_DESCRIPTION="${ARGS[*]}" +if [ -z "$FEATURE_DESCRIPTION" ]; then + echo "Usage: $0 [--json] [--dry-run] [--allow-existing-branch] [--short-name ] [--number N] [--timestamp] " >&2 + exit 1 +fi + +# Trim whitespace and validate description is not empty +FEATURE_DESCRIPTION=$(echo "$FEATURE_DESCRIPTION" | xargs) +if [ -z "$FEATURE_DESCRIPTION" ]; then + echo "Error: Feature description cannot be empty or contain only whitespace" >&2 + exit 1 +fi + +# Function to get highest number from specs directory +get_highest_from_specs() { + local specs_dir="$1" + local highest=0 + + if [ -d "$specs_dir" ]; then + for dir in "$specs_dir"/*; do + [ -d "$dir" ] || continue + dirname=$(basename "$dir") + # Match sequential prefixes (>=3 digits), but skip timestamp dirs. + if echo "$dirname" | grep -Eq '^[0-9]{3,}-' && ! echo "$dirname" | grep -Eq '^[0-9]{8}-[0-9]{6}-'; then + number=$(echo "$dirname" | grep -Eo '^[0-9]+') + number=$((10#$number)) + if [ "$number" -gt "$highest" ]; then + highest=$number + fi + fi + done + fi + + echo "$highest" +} + +# Function to get highest number from git branches +get_highest_from_branches() { + git branch -a 2>/dev/null | sed 's/^[* ]*//; s|^remotes/[^/]*/||' | _extract_highest_number +} + +# Extract the highest sequential feature number from a list of ref names (one per line). +_extract_highest_number() { + local highest=0 + while IFS= read -r name; do + [ -z "$name" ] && continue + if echo "$name" | grep -Eq '^[0-9]{3,}-' && ! echo "$name" | grep -Eq '^[0-9]{8}-[0-9]{6}-'; then + number=$(echo "$name" | grep -Eo '^[0-9]+' || echo "0") + number=$((10#$number)) + if [ "$number" -gt "$highest" ]; then + highest=$number + fi + fi + done + echo "$highest" +} + +# Function to get highest number from remote branches without fetching (side-effect-free) +get_highest_from_remote_refs() { + local highest=0 + + for remote in $(git remote 2>/dev/null); do + local remote_highest + remote_highest=$(GIT_TERMINAL_PROMPT=0 git ls-remote --heads "$remote" 2>/dev/null | sed 's|.*refs/heads/||' | _extract_highest_number) + if [ "$remote_highest" -gt "$highest" ]; then + highest=$remote_highest + fi + done + + echo "$highest" +} + +# Function to check existing branches and return next available number. +check_existing_branches() { + local specs_dir="$1" + local skip_fetch="${2:-false}" + + if [ "$skip_fetch" = true ]; then + local highest_remote=$(get_highest_from_remote_refs) + local highest_branch=$(get_highest_from_branches) + if [ "$highest_remote" -gt "$highest_branch" ]; then + highest_branch=$highest_remote + fi + else + git fetch --all --prune >/dev/null 2>&1 || true + local highest_branch=$(get_highest_from_branches) + fi + + local highest_spec=$(get_highest_from_specs "$specs_dir") + + local max_num=$highest_branch + if [ "$highest_spec" -gt "$max_num" ]; then + max_num=$highest_spec + fi + + echo $((max_num + 1)) +} + +# Function to clean and format a branch name +clean_branch_name() { + local name="$1" + echo "$name" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//' +} + +# --------------------------------------------------------------------------- +# Source common.sh for resolve_template, json_escape, get_repo_root, has_git. +# +# Search locations in priority order: +# 1. .specify/scripts/bash/common.sh under the project root (installed project) +# 2. scripts/bash/common.sh under the project root (source checkout fallback) +# 3. git-common.sh next to this script (minimal fallback — lacks resolve_template) +# --------------------------------------------------------------------------- +SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Find project root by walking up from the script location +_find_project_root() { + local dir="$1" + while [ "$dir" != "/" ]; do + if [ -d "$dir/.specify" ] || [ -d "$dir/.git" ]; then + echo "$dir" + return 0 + fi + dir="$(dirname "$dir")" + done + return 1 +} + +_common_loaded=false +_PROJECT_ROOT=$(_find_project_root "$SCRIPT_DIR") || true + +if [ -n "$_PROJECT_ROOT" ] && [ -f "$_PROJECT_ROOT/.specify/scripts/bash/common.sh" ]; then + source "$_PROJECT_ROOT/.specify/scripts/bash/common.sh" + _common_loaded=true +elif [ -n "$_PROJECT_ROOT" ] && [ -f "$_PROJECT_ROOT/scripts/bash/common.sh" ]; then + source "$_PROJECT_ROOT/scripts/bash/common.sh" + _common_loaded=true +elif [ -f "$SCRIPT_DIR/git-common.sh" ]; then + source "$SCRIPT_DIR/git-common.sh" + _common_loaded=true +fi + +if [ "$_common_loaded" != "true" ]; then + echo "Error: Could not locate common.sh or git-common.sh. Please ensure the Specify core scripts are installed." >&2 + exit 1 +fi + +# Resolve repository root +if type get_repo_root >/dev/null 2>&1; then + REPO_ROOT=$(get_repo_root) +elif git rev-parse --show-toplevel >/dev/null 2>&1; then + REPO_ROOT=$(git rev-parse --show-toplevel) +elif [ -n "$_PROJECT_ROOT" ]; then + REPO_ROOT="$_PROJECT_ROOT" +else + echo "Error: Could not determine repository root." >&2 + exit 1 +fi + +# Check if git is available at this repo root +if type has_git >/dev/null 2>&1; then + if has_git "$REPO_ROOT"; then + HAS_GIT=true + else + HAS_GIT=false + fi +elif git -C "$REPO_ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then + HAS_GIT=true +else + HAS_GIT=false +fi + +cd "$REPO_ROOT" + +SPECS_DIR="$REPO_ROOT/specs" + +# Function to generate branch name with stop word filtering +generate_branch_name() { + local description="$1" + + local stop_words="^(i|a|an|the|to|for|of|in|on|at|by|with|from|is|are|was|were|be|been|being|have|has|had|do|does|did|will|would|should|could|can|may|might|must|shall|this|that|these|those|my|your|our|their|want|need|add|get|set)$" + + local clean_name=$(echo "$description" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/ /g') + + local meaningful_words=() + for word in $clean_name; do + [ -z "$word" ] && continue + if ! echo "$word" | grep -qiE "$stop_words"; then + if [ ${#word} -ge 3 ]; then + meaningful_words+=("$word") + elif echo "$description" | grep -qw -- "${word^^}"; then + meaningful_words+=("$word") + fi + fi + done + + if [ ${#meaningful_words[@]} -gt 0 ]; then + local max_words=3 + if [ ${#meaningful_words[@]} -eq 4 ]; then max_words=4; fi + + local result="" + local count=0 + for word in "${meaningful_words[@]}"; do + if [ $count -ge $max_words ]; then break; fi + if [ -n "$result" ]; then result="$result-"; fi + result="$result$word" + count=$((count + 1)) + done + echo "$result" + else + local cleaned=$(clean_branch_name "$description") + echo "$cleaned" | tr '-' '\n' | grep -v '^$' | head -3 | tr '\n' '-' | sed 's/-$//' + fi +} + +# Check for GIT_BRANCH_NAME env var override (exact branch name, no prefix/suffix) +if [ -n "${GIT_BRANCH_NAME:-}" ]; then + BRANCH_NAME="$GIT_BRANCH_NAME" + # Extract FEATURE_NUM from the branch name if it starts with a numeric prefix + # Check timestamp pattern first (YYYYMMDD-HHMMSS-) since it also matches the simpler ^[0-9]+ pattern + if echo "$BRANCH_NAME" | grep -Eq '^[0-9]{8}-[0-9]{6}-'; then + FEATURE_NUM=$(echo "$BRANCH_NAME" | grep -Eo '^[0-9]{8}-[0-9]{6}') + BRANCH_SUFFIX="${BRANCH_NAME#${FEATURE_NUM}-}" + elif echo "$BRANCH_NAME" | grep -Eq '^[0-9]+-'; then + FEATURE_NUM=$(echo "$BRANCH_NAME" | grep -Eo '^[0-9]+') + BRANCH_SUFFIX="${BRANCH_NAME#${FEATURE_NUM}-}" + else + FEATURE_NUM="$BRANCH_NAME" + BRANCH_SUFFIX="$BRANCH_NAME" + fi +else + # Generate branch name + if [ -n "$SHORT_NAME" ]; then + BRANCH_SUFFIX=$(clean_branch_name "$SHORT_NAME") + else + BRANCH_SUFFIX=$(generate_branch_name "$FEATURE_DESCRIPTION") + fi + + # Warn if --number and --timestamp are both specified + if [ "$USE_TIMESTAMP" = true ] && [ -n "$BRANCH_NUMBER" ]; then + >&2 echo "[specify] Warning: --number is ignored when --timestamp is used" + BRANCH_NUMBER="" + fi + + # Determine branch prefix + if [ "$USE_TIMESTAMP" = true ]; then + FEATURE_NUM=$(date +%Y%m%d-%H%M%S) + BRANCH_NAME="${FEATURE_NUM}-${BRANCH_SUFFIX}" + else + if [ -z "$BRANCH_NUMBER" ]; then + if [ "$DRY_RUN" = true ] && [ "$HAS_GIT" = true ]; then + BRANCH_NUMBER=$(check_existing_branches "$SPECS_DIR" true) + elif [ "$DRY_RUN" = true ]; then + HIGHEST=$(get_highest_from_specs "$SPECS_DIR") + BRANCH_NUMBER=$((HIGHEST + 1)) + elif [ "$HAS_GIT" = true ]; then + BRANCH_NUMBER=$(check_existing_branches "$SPECS_DIR") + else + HIGHEST=$(get_highest_from_specs "$SPECS_DIR") + BRANCH_NUMBER=$((HIGHEST + 1)) + fi + fi + + FEATURE_NUM=$(printf "%03d" "$((10#$BRANCH_NUMBER))") + BRANCH_NAME="${FEATURE_NUM}-${BRANCH_SUFFIX}" + fi +fi + +# GitHub enforces a 244-byte limit on branch names +MAX_BRANCH_LENGTH=244 +_byte_length() { printf '%s' "$1" | LC_ALL=C wc -c | tr -d ' '; } +BRANCH_BYTE_LEN=$(_byte_length "$BRANCH_NAME") +if [ -n "${GIT_BRANCH_NAME:-}" ] && [ "$BRANCH_BYTE_LEN" -gt $MAX_BRANCH_LENGTH ]; then + >&2 echo "Error: GIT_BRANCH_NAME must be 244 bytes or fewer in UTF-8. Provided value is ${BRANCH_BYTE_LEN} bytes." + exit 1 +elif [ "$BRANCH_BYTE_LEN" -gt $MAX_BRANCH_LENGTH ]; then + PREFIX_LENGTH=$(( ${#FEATURE_NUM} + 1 )) + MAX_SUFFIX_LENGTH=$((MAX_BRANCH_LENGTH - PREFIX_LENGTH)) + + TRUNCATED_SUFFIX=$(echo "$BRANCH_SUFFIX" | cut -c1-$MAX_SUFFIX_LENGTH) + TRUNCATED_SUFFIX=$(echo "$TRUNCATED_SUFFIX" | sed 's/-$//') + + ORIGINAL_BRANCH_NAME="$BRANCH_NAME" + BRANCH_NAME="${FEATURE_NUM}-${TRUNCATED_SUFFIX}" + + >&2 echo "[specify] Warning: Branch name exceeded GitHub's 244-byte limit" + >&2 echo "[specify] Original: $ORIGINAL_BRANCH_NAME (${#ORIGINAL_BRANCH_NAME} bytes)" + >&2 echo "[specify] Truncated to: $BRANCH_NAME (${#BRANCH_NAME} bytes)" +fi + +if [ "$DRY_RUN" != true ]; then + if [ "$HAS_GIT" = true ]; then + branch_create_error="" + if ! branch_create_error=$(git checkout -q -b "$BRANCH_NAME" 2>&1); then + current_branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || true)" + if git branch --list "$BRANCH_NAME" | grep -q .; then + if [ "$ALLOW_EXISTING" = true ]; then + if [ "$current_branch" = "$BRANCH_NAME" ]; then + : + elif ! switch_branch_error=$(git checkout -q "$BRANCH_NAME" 2>&1); then + >&2 echo "Error: Failed to switch to existing branch '$BRANCH_NAME'. Please resolve any local changes or conflicts and try again." + if [ -n "$switch_branch_error" ]; then + >&2 printf '%s\n' "$switch_branch_error" + fi + exit 1 + fi + elif [ "$USE_TIMESTAMP" = true ]; then + >&2 echo "Error: Branch '$BRANCH_NAME' already exists. Rerun to get a new timestamp or use a different --short-name." + exit 1 + else + >&2 echo "Error: Branch '$BRANCH_NAME' already exists. Please use a different feature name or specify a different number with --number." + exit 1 + fi + else + >&2 echo "Error: Failed to create git branch '$BRANCH_NAME'." + if [ -n "$branch_create_error" ]; then + >&2 printf '%s\n' "$branch_create_error" + else + >&2 echo "Please check your git configuration and try again." + fi + exit 1 + fi + fi + else + >&2 echo "[specify] Warning: Git repository not detected; skipped branch creation for $BRANCH_NAME" + fi + + printf '# To persist: export SPECIFY_FEATURE=%q\n' "$BRANCH_NAME" >&2 +fi + +if $JSON_MODE; then + if command -v jq >/dev/null 2>&1; then + if [ "$DRY_RUN" = true ]; then + jq -cn \ + --arg branch_name "$BRANCH_NAME" \ + --arg feature_num "$FEATURE_NUM" \ + '{BRANCH_NAME:$branch_name,FEATURE_NUM:$feature_num,DRY_RUN:true}' + else + jq -cn \ + --arg branch_name "$BRANCH_NAME" \ + --arg feature_num "$FEATURE_NUM" \ + '{BRANCH_NAME:$branch_name,FEATURE_NUM:$feature_num}' + fi + else + if type json_escape >/dev/null 2>&1; then + _je_branch=$(json_escape "$BRANCH_NAME") + _je_num=$(json_escape "$FEATURE_NUM") + else + _je_branch="$BRANCH_NAME" + _je_num="$FEATURE_NUM" + fi + if [ "$DRY_RUN" = true ]; then + printf '{"BRANCH_NAME":"%s","FEATURE_NUM":"%s","DRY_RUN":true}\n' "$_je_branch" "$_je_num" + else + printf '{"BRANCH_NAME":"%s","FEATURE_NUM":"%s"}\n' "$_je_branch" "$_je_num" + fi + fi +else + echo "BRANCH_NAME: $BRANCH_NAME" + echo "FEATURE_NUM: $FEATURE_NUM" + if [ "$DRY_RUN" != true ]; then + printf '# To persist in your shell: export SPECIFY_FEATURE=%q\n' "$BRANCH_NAME" + fi +fi diff --git a/.specify/extensions/git/scripts/bash/git-common.sh b/.specify/extensions/git/scripts/bash/git-common.sh new file mode 100755 index 00000000..b78356d1 --- /dev/null +++ b/.specify/extensions/git/scripts/bash/git-common.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +# Git-specific common functions for the git extension. +# Extracted from scripts/bash/common.sh — contains only git-specific +# branch validation and detection logic. + +# Check if we have git available at the repo root +has_git() { + local repo_root="${1:-$(pwd)}" + { [ -d "$repo_root/.git" ] || [ -f "$repo_root/.git" ]; } && \ + command -v git >/dev/null 2>&1 && \ + git -C "$repo_root" rev-parse --is-inside-work-tree >/dev/null 2>&1 +} + +# Strip a single optional path segment (e.g. gitflow "feat/004-name" -> "004-name"). +# Only when the full name is exactly two slash-free segments; otherwise returns the raw name. +spec_kit_effective_branch_name() { + local raw="$1" + if [[ "$raw" =~ ^([^/]+)/([^/]+)$ ]]; then + printf '%s\n' "${BASH_REMATCH[2]}" + else + printf '%s\n' "$raw" + fi +} + +# Validate that a branch name matches the expected feature branch pattern. +# Accepts sequential (###-* with >=3 digits) or timestamp (YYYYMMDD-HHMMSS-*) formats. +# Logic aligned with scripts/bash/common.sh check_feature_branch after effective-name normalization. +check_feature_branch() { + local raw="$1" + local has_git_repo="$2" + + # For non-git repos, we can't enforce branch naming but still provide output + if [[ "$has_git_repo" != "true" ]]; then + echo "[specify] Warning: Git repository not detected; skipped branch validation" >&2 + return 0 + fi + + local branch + branch=$(spec_kit_effective_branch_name "$raw") + + # Accept sequential prefix (3+ digits) but exclude malformed timestamps + # Malformed: 7-or-8 digit date + 6-digit time with no trailing slug (e.g. "2026031-143022" or "20260319-143022") + local is_sequential=false + if [[ "$branch" =~ ^[0-9]{3,}- ]] && [[ ! "$branch" =~ ^[0-9]{7}-[0-9]{6}- ]] && [[ ! "$branch" =~ ^[0-9]{7,8}-[0-9]{6}$ ]]; then + is_sequential=true + fi + if [[ "$is_sequential" != "true" ]] && [[ ! "$branch" =~ ^[0-9]{8}-[0-9]{6}- ]]; then + echo "ERROR: Not on a feature branch. Current branch: $raw" >&2 + echo "Feature branches should be named like: 001-feature-name, 1234-feature-name, or 20260319-143022-feature-name" >&2 + return 1 + fi + + return 0 +} diff --git a/.specify/extensions/git/scripts/bash/initialize-repo.sh b/.specify/extensions/git/scripts/bash/initialize-repo.sh new file mode 100755 index 00000000..296e363b --- /dev/null +++ b/.specify/extensions/git/scripts/bash/initialize-repo.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +# Git extension: initialize-repo.sh +# Initialize a Git repository with an initial commit. +# Customizable — replace this script to add .gitignore templates, +# default branch config, git-flow, LFS, signing, etc. + +set -e + +SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Find project root +_find_project_root() { + local dir="$1" + while [ "$dir" != "/" ]; do + if [ -d "$dir/.specify" ] || [ -d "$dir/.git" ]; then + echo "$dir" + return 0 + fi + dir="$(dirname "$dir")" + done + return 1 +} + +REPO_ROOT=$(_find_project_root "$SCRIPT_DIR") || REPO_ROOT="$(pwd)" +cd "$REPO_ROOT" + +# Read commit message from extension config, fall back to default +COMMIT_MSG="[Spec Kit] Initial commit" +_config_file="$REPO_ROOT/.specify/extensions/git/git-config.yml" +if [ -f "$_config_file" ]; then + _msg=$(grep '^init_commit_message:' "$_config_file" 2>/dev/null | sed 's/^init_commit_message:[[:space:]]*//' | sed 's/^["'\'']//' | sed 's/["'\'']*$//') + if [ -n "$_msg" ]; then + COMMIT_MSG="$_msg" + fi +fi + +# Check if git is available +if ! command -v git >/dev/null 2>&1; then + echo "[specify] Warning: Git not found; skipped repository initialization" >&2 + exit 0 +fi + +# Check if already a git repo +if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + echo "[specify] Git repository already initialized; skipping" >&2 + exit 0 +fi + +# Initialize +_git_out=$(git init -q 2>&1) || { echo "[specify] Error: git init failed: $_git_out" >&2; exit 1; } +_git_out=$(git add . 2>&1) || { echo "[specify] Error: git add failed: $_git_out" >&2; exit 1; } +_git_out=$(git commit --allow-empty -q -m "$COMMIT_MSG" 2>&1) || { echo "[specify] Error: git commit failed: $_git_out" >&2; exit 1; } + +echo "✓ Git repository initialized" >&2 diff --git a/.specify/extensions/git/scripts/powershell/auto-commit.ps1 b/.specify/extensions/git/scripts/powershell/auto-commit.ps1 new file mode 100644 index 00000000..4a8b0e00 --- /dev/null +++ b/.specify/extensions/git/scripts/powershell/auto-commit.ps1 @@ -0,0 +1,169 @@ +#!/usr/bin/env pwsh +# Git extension: auto-commit.ps1 +# Automatically commit changes after a Spec Kit command completes. +# Checks per-command config keys in git-config.yml before committing. +# +# Usage: auto-commit.ps1 +# e.g.: auto-commit.ps1 after_specify +param( + [Parameter(Position = 0, Mandatory = $true)] + [string]$EventName +) +$ErrorActionPreference = 'Stop' + +function Find-ProjectRoot { + param([string]$StartDir) + $current = Resolve-Path $StartDir + while ($true) { + foreach ($marker in @('.specify', '.git')) { + if (Test-Path (Join-Path $current $marker)) { + return $current + } + } + $parent = Split-Path $current -Parent + if ($parent -eq $current) { return $null } + $current = $parent + } +} + +$repoRoot = Find-ProjectRoot -StartDir $PSScriptRoot +if (-not $repoRoot) { $repoRoot = Get-Location } +Set-Location $repoRoot + +# Check if git is available +if (-not (Get-Command git -ErrorAction SilentlyContinue)) { + Write-Warning "[specify] Warning: Git not found; skipped auto-commit" + exit 0 +} + +# Temporarily relax ErrorActionPreference so git stderr warnings +# (e.g. CRLF notices on Windows) do not become terminating errors. +$savedEAP = $ErrorActionPreference +$ErrorActionPreference = 'Continue' +try { + git rev-parse --is-inside-work-tree 2>$null | Out-Null + $isRepo = $LASTEXITCODE -eq 0 +} finally { + $ErrorActionPreference = $savedEAP +} +if (-not $isRepo) { + Write-Warning "[specify] Warning: Not a Git repository; skipped auto-commit" + exit 0 +} + +# Read per-command config from git-config.yml +$configFile = Join-Path $repoRoot ".specify/extensions/git/git-config.yml" +$enabled = $false +$commitMsg = "" + +if (Test-Path $configFile) { + # Parse YAML to find auto_commit section + $inAutoCommit = $false + $inEvent = $false + $defaultEnabled = $false + + foreach ($line in Get-Content $configFile) { + # Detect auto_commit: section + if ($line -match '^auto_commit:') { + $inAutoCommit = $true + $inEvent = $false + continue + } + + # Exit auto_commit section on next top-level key + if ($inAutoCommit -and $line -match '^[a-z]') { + break + } + + if ($inAutoCommit) { + # Check default key + if ($line -match '^\s+default:\s*(.+)$') { + $val = $matches[1].Trim().ToLower() + if ($val -eq 'true') { $defaultEnabled = $true } + } + + # Detect our event subsection + if ($line -match "^\s+${EventName}:") { + $inEvent = $true + continue + } + + # Inside our event subsection + if ($inEvent) { + # Exit on next sibling key (2-space indent, not 4+) + if ($line -match '^\s{2}[a-z]' -and $line -notmatch '^\s{4}') { + $inEvent = $false + continue + } + if ($line -match '\s+enabled:\s*(.+)$') { + $val = $matches[1].Trim().ToLower() + if ($val -eq 'true') { $enabled = $true } + if ($val -eq 'false') { $enabled = $false } + } + if ($line -match '\s+message:\s*(.+)$') { + $commitMsg = $matches[1].Trim() -replace '^["'']' -replace '["'']$' + } + } + } + } + + # If event-specific key not found, use default + if (-not $enabled -and $defaultEnabled) { + $hasEventKey = Select-String -Path $configFile -Pattern "^\s*${EventName}:" -Quiet + if (-not $hasEventKey) { + $enabled = $true + } + } +} else { + # No config file — auto-commit disabled by default + exit 0 +} + +if (-not $enabled) { + exit 0 +} + +# Check if there are changes to commit +# Relax ErrorActionPreference so CRLF warnings on stderr do not terminate. +$savedEAP = $ErrorActionPreference +$ErrorActionPreference = 'Continue' +try { + git diff --quiet HEAD 2>$null; $d1 = $LASTEXITCODE + git diff --cached --quiet 2>$null; $d2 = $LASTEXITCODE + $untracked = git ls-files --others --exclude-standard 2>$null +} finally { + $ErrorActionPreference = $savedEAP +} + +if ($d1 -eq 0 -and $d2 -eq 0 -and -not $untracked) { + Write-Host "[specify] No changes to commit after $EventName" -ForegroundColor DarkGray + exit 0 +} + +# Derive a human-readable command name from the event +$commandName = $EventName -replace '^after_', '' -replace '^before_', '' +$phase = if ($EventName -match '^before_') { 'before' } else { 'after' } + +# Use custom message if configured, otherwise default +if (-not $commitMsg) { + $commitMsg = "[Spec Kit] Auto-commit $phase $commandName" +} + +# Stage and commit +# Relax ErrorActionPreference so CRLF warnings on stderr do not terminate, +# while still allowing redirected error output to be captured for diagnostics. +$savedEAP = $ErrorActionPreference +$ErrorActionPreference = 'Continue' +try { + $out = git add . 2>&1 | Out-String + if ($LASTEXITCODE -ne 0) { throw "git add failed: $out" } + $out = git commit -q -m $commitMsg 2>&1 | Out-String + if ($LASTEXITCODE -ne 0) { throw "git commit failed: $out" } +} catch { + Write-Warning "[specify] Error: $_" + exit 1 +} finally { + $ErrorActionPreference = $savedEAP +} + +Write-Host "[OK] Changes committed $phase $commandName" diff --git a/.specify/extensions/git/scripts/powershell/create-new-feature.ps1 b/.specify/extensions/git/scripts/powershell/create-new-feature.ps1 new file mode 100644 index 00000000..b579f051 --- /dev/null +++ b/.specify/extensions/git/scripts/powershell/create-new-feature.ps1 @@ -0,0 +1,403 @@ +#!/usr/bin/env pwsh +# Git extension: create-new-feature.ps1 +# Adapted from core scripts/powershell/create-new-feature.ps1 for extension layout. +# Sources common.ps1 from the project's installed scripts, falling back to +# git-common.ps1 for minimal git helpers. +[CmdletBinding()] +param( + [switch]$Json, + [switch]$AllowExistingBranch, + [switch]$DryRun, + [string]$ShortName, + [Parameter()] + [long]$Number = 0, + [switch]$Timestamp, + [switch]$Help, + [Parameter(Position = 0, ValueFromRemainingArguments = $true)] + [string[]]$FeatureDescription +) +$ErrorActionPreference = 'Stop' + +if ($Help) { + Write-Host "Usage: ./create-new-feature.ps1 [-Json] [-DryRun] [-AllowExistingBranch] [-ShortName ] [-Number N] [-Timestamp] " + Write-Host "" + Write-Host "Options:" + Write-Host " -Json Output in JSON format" + Write-Host " -DryRun Compute branch name without creating the branch" + Write-Host " -AllowExistingBranch Switch to branch if it already exists instead of failing" + Write-Host " -ShortName Provide a custom short name (2-4 words) for the branch" + Write-Host " -Number N Specify branch number manually (overrides auto-detection)" + Write-Host " -Timestamp Use timestamp prefix (YYYYMMDD-HHMMSS) instead of sequential numbering" + Write-Host " -Help Show this help message" + Write-Host "" + Write-Host "Environment variables:" + Write-Host " GIT_BRANCH_NAME Use this exact branch name, bypassing all prefix/suffix generation" + Write-Host "" + exit 0 +} + +if (-not $FeatureDescription -or $FeatureDescription.Count -eq 0) { + Write-Error "Usage: ./create-new-feature.ps1 [-Json] [-DryRun] [-AllowExistingBranch] [-ShortName ] [-Number N] [-Timestamp] " + exit 1 +} + +$featureDesc = ($FeatureDescription -join ' ').Trim() + +if ([string]::IsNullOrWhiteSpace($featureDesc)) { + Write-Error "Error: Feature description cannot be empty or contain only whitespace" + exit 1 +} + +function Get-HighestNumberFromSpecs { + param([string]$SpecsDir) + + [long]$highest = 0 + if (Test-Path $SpecsDir) { + Get-ChildItem -Path $SpecsDir -Directory | ForEach-Object { + if ($_.Name -match '^(\d{3,})-' -and $_.Name -notmatch '^\d{8}-\d{6}-') { + [long]$num = 0 + if ([long]::TryParse($matches[1], [ref]$num) -and $num -gt $highest) { + $highest = $num + } + } + } + } + return $highest +} + +function Get-HighestNumberFromNames { + param([string[]]$Names) + + [long]$highest = 0 + foreach ($name in $Names) { + if ($name -match '^(\d{3,})-' -and $name -notmatch '^\d{8}-\d{6}-') { + [long]$num = 0 + if ([long]::TryParse($matches[1], [ref]$num) -and $num -gt $highest) { + $highest = $num + } + } + } + return $highest +} + +function Get-HighestNumberFromBranches { + param() + + try { + $branches = git branch -a 2>$null + if ($LASTEXITCODE -eq 0 -and $branches) { + $cleanNames = $branches | ForEach-Object { + $_.Trim() -replace '^\*?\s+', '' -replace '^remotes/[^/]+/', '' + } + return Get-HighestNumberFromNames -Names $cleanNames + } + } catch { + Write-Verbose "Could not check Git branches: $_" + } + return 0 +} + +function Get-HighestNumberFromRemoteRefs { + [long]$highest = 0 + try { + $remotes = git remote 2>$null + if ($remotes) { + foreach ($remote in $remotes) { + $env:GIT_TERMINAL_PROMPT = '0' + $refs = git ls-remote --heads $remote 2>$null + $env:GIT_TERMINAL_PROMPT = $null + if ($LASTEXITCODE -eq 0 -and $refs) { + $refNames = $refs | ForEach-Object { + if ($_ -match 'refs/heads/(.+)$') { $matches[1] } + } | Where-Object { $_ } + $remoteHighest = Get-HighestNumberFromNames -Names $refNames + if ($remoteHighest -gt $highest) { $highest = $remoteHighest } + } + } + } + } catch { + Write-Verbose "Could not query remote refs: $_" + } + return $highest +} + +function Get-NextBranchNumber { + param( + [string]$SpecsDir, + [switch]$SkipFetch + ) + + if ($SkipFetch) { + $highestBranch = Get-HighestNumberFromBranches + $highestRemote = Get-HighestNumberFromRemoteRefs + $highestBranch = [Math]::Max($highestBranch, $highestRemote) + } else { + try { + git fetch --all --prune 2>$null | Out-Null + } catch { } + $highestBranch = Get-HighestNumberFromBranches + } + + $highestSpec = Get-HighestNumberFromSpecs -SpecsDir $SpecsDir + $maxNum = [Math]::Max($highestBranch, $highestSpec) + return $maxNum + 1 +} + +function ConvertTo-CleanBranchName { + param([string]$Name) + return $Name.ToLower() -replace '[^a-z0-9]', '-' -replace '-{2,}', '-' -replace '^-', '' -replace '-$', '' +} + +# --------------------------------------------------------------------------- +# Source common.ps1 from the project's installed scripts. +# Search locations in priority order: +# 1. .specify/scripts/powershell/common.ps1 under the project root +# 2. scripts/powershell/common.ps1 under the project root (source checkout) +# 3. git-common.ps1 next to this script (minimal fallback) +# --------------------------------------------------------------------------- +function Find-ProjectRoot { + param([string]$StartDir) + $current = Resolve-Path $StartDir + while ($true) { + foreach ($marker in @('.specify', '.git')) { + if (Test-Path (Join-Path $current $marker)) { + return $current + } + } + $parent = Split-Path $current -Parent + if ($parent -eq $current) { return $null } + $current = $parent + } +} + +$projectRoot = Find-ProjectRoot -StartDir $PSScriptRoot +$commonLoaded = $false + +if ($projectRoot) { + $candidates = @( + (Join-Path $projectRoot ".specify/scripts/powershell/common.ps1"), + (Join-Path $projectRoot "scripts/powershell/common.ps1") + ) + foreach ($candidate in $candidates) { + if (Test-Path $candidate) { + . $candidate + $commonLoaded = $true + break + } + } +} + +if (-not $commonLoaded -and (Test-Path "$PSScriptRoot/git-common.ps1")) { + . "$PSScriptRoot/git-common.ps1" + $commonLoaded = $true +} + +if (-not $commonLoaded) { + throw "Unable to locate common script file. Please ensure the Specify core scripts are installed." +} + +# Resolve repository root +if (Get-Command Get-RepoRoot -ErrorAction SilentlyContinue) { + $repoRoot = Get-RepoRoot +} elseif ($projectRoot) { + $repoRoot = $projectRoot +} else { + throw "Could not determine repository root." +} + +# Check if git is available +if (Get-Command Test-HasGit -ErrorAction SilentlyContinue) { + # Call without parameters for compatibility with core common.ps1 (no -RepoRoot param) + # and git-common.ps1 (has -RepoRoot param with default). + $hasGit = Test-HasGit +} else { + try { + git -C $repoRoot rev-parse --is-inside-work-tree 2>$null | Out-Null + $hasGit = ($LASTEXITCODE -eq 0) + } catch { + $hasGit = $false + } +} + +Set-Location $repoRoot + +$specsDir = Join-Path $repoRoot 'specs' + +function Get-BranchName { + param([string]$Description) + + $stopWords = @( + 'i', 'a', 'an', 'the', 'to', 'for', 'of', 'in', 'on', 'at', 'by', 'with', 'from', + 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had', + 'do', 'does', 'did', 'will', 'would', 'should', 'could', 'can', 'may', 'might', 'must', 'shall', + 'this', 'that', 'these', 'those', 'my', 'your', 'our', 'their', + 'want', 'need', 'add', 'get', 'set' + ) + + $cleanName = $Description.ToLower() -replace '[^a-z0-9\s]', ' ' + $words = $cleanName -split '\s+' | Where-Object { $_ } + + $meaningfulWords = @() + foreach ($word in $words) { + if ($stopWords -contains $word) { continue } + if ($word.Length -ge 3) { + $meaningfulWords += $word + } elseif ($Description -match "\b$($word.ToUpper())\b") { + $meaningfulWords += $word + } + } + + if ($meaningfulWords.Count -gt 0) { + $maxWords = if ($meaningfulWords.Count -eq 4) { 4 } else { 3 } + $result = ($meaningfulWords | Select-Object -First $maxWords) -join '-' + return $result + } else { + $result = ConvertTo-CleanBranchName -Name $Description + $fallbackWords = ($result -split '-') | Where-Object { $_ } | Select-Object -First 3 + return [string]::Join('-', $fallbackWords) + } +} + +# Check for GIT_BRANCH_NAME env var override (exact branch name, no prefix/suffix) +if ($env:GIT_BRANCH_NAME) { + $branchName = $env:GIT_BRANCH_NAME + # Check 244-byte limit (UTF-8) for override names + $branchNameUtf8ByteCount = [System.Text.Encoding]::UTF8.GetByteCount($branchName) + if ($branchNameUtf8ByteCount -gt 244) { + throw "GIT_BRANCH_NAME must be 244 bytes or fewer in UTF-8. Provided value is $branchNameUtf8ByteCount bytes; please supply a shorter override branch name." + } + # Extract FEATURE_NUM from the branch name if it starts with a numeric prefix + # Check timestamp pattern first (YYYYMMDD-HHMMSS-) since it also matches the simpler ^\d+ pattern + if ($branchName -match '^(\d{8}-\d{6})-') { + $featureNum = $matches[1] + } elseif ($branchName -match '^(\d+)-') { + $featureNum = $matches[1] + } else { + $featureNum = $branchName + } +} else { + if ($ShortName) { + $branchSuffix = ConvertTo-CleanBranchName -Name $ShortName + } else { + $branchSuffix = Get-BranchName -Description $featureDesc + } + + if ($Timestamp -and $Number -ne 0) { + Write-Warning "[specify] Warning: -Number is ignored when -Timestamp is used" + $Number = 0 + } + + if ($Timestamp) { + $featureNum = Get-Date -Format 'yyyyMMdd-HHmmss' + $branchName = "$featureNum-$branchSuffix" + } else { + if ($Number -eq 0) { + if ($DryRun -and $hasGit) { + $Number = Get-NextBranchNumber -SpecsDir $specsDir -SkipFetch + } elseif ($DryRun) { + $Number = (Get-HighestNumberFromSpecs -SpecsDir $specsDir) + 1 + } elseif ($hasGit) { + $Number = Get-NextBranchNumber -SpecsDir $specsDir + } else { + $Number = (Get-HighestNumberFromSpecs -SpecsDir $specsDir) + 1 + } + } + + $featureNum = ('{0:000}' -f $Number) + $branchName = "$featureNum-$branchSuffix" + } +} + +$maxBranchLength = 244 +if ($branchName.Length -gt $maxBranchLength) { + $prefixLength = $featureNum.Length + 1 + $maxSuffixLength = $maxBranchLength - $prefixLength + + $truncatedSuffix = $branchSuffix.Substring(0, [Math]::Min($branchSuffix.Length, $maxSuffixLength)) + $truncatedSuffix = $truncatedSuffix -replace '-$', '' + + $originalBranchName = $branchName + $branchName = "$featureNum-$truncatedSuffix" + + Write-Warning "[specify] Branch name exceeded GitHub's 244-byte limit" + Write-Warning "[specify] Original: $originalBranchName ($($originalBranchName.Length) bytes)" + Write-Warning "[specify] Truncated to: $branchName ($($branchName.Length) bytes)" +} + +if (-not $DryRun) { + if ($hasGit) { + $branchCreated = $false + $branchCreateError = '' + try { + $branchCreateError = git checkout -q -b $branchName 2>&1 | Out-String + if ($LASTEXITCODE -eq 0) { + $branchCreated = $true + } + } catch { + $branchCreateError = $_.Exception.Message + } + + if (-not $branchCreated) { + $currentBranch = '' + try { $currentBranch = (git rev-parse --abbrev-ref HEAD 2>$null).Trim() } catch {} + $existingBranch = git branch --list $branchName 2>$null + if ($existingBranch) { + if ($AllowExistingBranch) { + if ($currentBranch -eq $branchName) { + # Already on the target branch + } else { + $switchBranchError = git checkout -q $branchName 2>&1 | Out-String + if ($LASTEXITCODE -ne 0) { + if ($switchBranchError) { + Write-Error "Error: Branch '$branchName' exists but could not be checked out.`n$($switchBranchError.Trim())" + } else { + Write-Error "Error: Branch '$branchName' exists but could not be checked out. Resolve any uncommitted changes or conflicts and try again." + } + exit 1 + } + } + } elseif ($Timestamp) { + Write-Error "Error: Branch '$branchName' already exists. Rerun to get a new timestamp or use a different -ShortName." + exit 1 + } else { + Write-Error "Error: Branch '$branchName' already exists. Please use a different feature name or specify a different number with -Number." + exit 1 + } + } else { + if ($branchCreateError) { + Write-Error "Error: Failed to create git branch '$branchName'.`n$($branchCreateError.Trim())" + } else { + Write-Error "Error: Failed to create git branch '$branchName'. Please check your git configuration and try again." + } + exit 1 + } + } + } else { + if ($Json) { + [Console]::Error.WriteLine("[specify] Warning: Git repository not detected; skipped branch creation for $branchName") + } else { + Write-Warning "[specify] Warning: Git repository not detected; skipped branch creation for $branchName" + } + } + + $env:SPECIFY_FEATURE = $branchName +} + +if ($Json) { + $obj = [PSCustomObject]@{ + BRANCH_NAME = $branchName + FEATURE_NUM = $featureNum + HAS_GIT = $hasGit + } + if ($DryRun) { + $obj | Add-Member -NotePropertyName 'DRY_RUN' -NotePropertyValue $true + } + $obj | ConvertTo-Json -Compress +} else { + Write-Output "BRANCH_NAME: $branchName" + Write-Output "FEATURE_NUM: $featureNum" + Write-Output "HAS_GIT: $hasGit" + if (-not $DryRun) { + Write-Output "SPECIFY_FEATURE environment variable set to: $branchName" + } +} diff --git a/.specify/extensions/git/scripts/powershell/git-common.ps1 b/.specify/extensions/git/scripts/powershell/git-common.ps1 new file mode 100644 index 00000000..82210000 --- /dev/null +++ b/.specify/extensions/git/scripts/powershell/git-common.ps1 @@ -0,0 +1,51 @@ +#!/usr/bin/env pwsh +# Git-specific common functions for the git extension. +# Extracted from scripts/powershell/common.ps1 — contains only git-specific +# branch validation and detection logic. + +function Test-HasGit { + param([string]$RepoRoot = (Get-Location)) + try { + if (-not (Test-Path (Join-Path $RepoRoot '.git'))) { return $false } + if (-not (Get-Command git -ErrorAction SilentlyContinue)) { return $false } + git -C $RepoRoot rev-parse --is-inside-work-tree 2>$null | Out-Null + return ($LASTEXITCODE -eq 0) + } catch { + return $false + } +} + +function Get-SpecKitEffectiveBranchName { + param([string]$Branch) + if ($Branch -match '^([^/]+)/([^/]+)$') { + return $Matches[2] + } + return $Branch +} + +function Test-FeatureBranch { + param( + [string]$Branch, + [bool]$HasGit = $true + ) + + # For non-git repos, we can't enforce branch naming but still provide output + if (-not $HasGit) { + Write-Warning "[specify] Warning: Git repository not detected; skipped branch validation" + return $true + } + + $raw = $Branch + $Branch = Get-SpecKitEffectiveBranchName $raw + + # Accept sequential prefix (3+ digits) but exclude malformed timestamps + # Malformed: 7-or-8 digit date + 6-digit time with no trailing slug (e.g. "2026031-143022" or "20260319-143022") + $hasMalformedTimestamp = ($Branch -match '^[0-9]{7}-[0-9]{6}-') -or ($Branch -match '^(?:\d{7}|\d{8})-\d{6}$') + $isSequential = ($Branch -match '^[0-9]{3,}-') -and (-not $hasMalformedTimestamp) + if (-not $isSequential -and $Branch -notmatch '^\d{8}-\d{6}-') { + [Console]::Error.WriteLine("ERROR: Not on a feature branch. Current branch: $raw") + [Console]::Error.WriteLine("Feature branches should be named like: 001-feature-name, 1234-feature-name, or 20260319-143022-feature-name") + return $false + } + return $true +} diff --git a/.specify/extensions/git/scripts/powershell/initialize-repo.ps1 b/.specify/extensions/git/scripts/powershell/initialize-repo.ps1 new file mode 100644 index 00000000..324240a3 --- /dev/null +++ b/.specify/extensions/git/scripts/powershell/initialize-repo.ps1 @@ -0,0 +1,69 @@ +#!/usr/bin/env pwsh +# Git extension: initialize-repo.ps1 +# Initialize a Git repository with an initial commit. +# Customizable — replace this script to add .gitignore templates, +# default branch config, git-flow, LFS, signing, etc. +$ErrorActionPreference = 'Stop' + +# Find project root +function Find-ProjectRoot { + param([string]$StartDir) + $current = Resolve-Path $StartDir + while ($true) { + foreach ($marker in @('.specify', '.git')) { + if (Test-Path (Join-Path $current $marker)) { + return $current + } + } + $parent = Split-Path $current -Parent + if ($parent -eq $current) { return $null } + $current = $parent + } +} + +$repoRoot = Find-ProjectRoot -StartDir $PSScriptRoot +if (-not $repoRoot) { $repoRoot = Get-Location } +Set-Location $repoRoot + +# Read commit message from extension config, fall back to default +$commitMsg = "[Spec Kit] Initial commit" +$configFile = Join-Path $repoRoot ".specify/extensions/git/git-config.yml" +if (Test-Path $configFile) { + foreach ($line in Get-Content $configFile) { + if ($line -match '^init_commit_message:\s*(.+)$') { + $val = $matches[1].Trim() -replace '^["'']' -replace '["'']$' + if ($val) { $commitMsg = $val } + break + } + } +} + +# Check if git is available +if (-not (Get-Command git -ErrorAction SilentlyContinue)) { + Write-Warning "[specify] Warning: Git not found; skipped repository initialization" + exit 0 +} + +# Check if already a git repo +try { + git rev-parse --is-inside-work-tree 2>$null | Out-Null + if ($LASTEXITCODE -eq 0) { + Write-Warning "[specify] Git repository already initialized; skipping" + exit 0 + } +} catch { } + +# Initialize +try { + $out = git init -q 2>&1 | Out-String + if ($LASTEXITCODE -ne 0) { throw "git init failed: $out" } + $out = git add . 2>&1 | Out-String + if ($LASTEXITCODE -ne 0) { throw "git add failed: $out" } + $out = git commit --allow-empty -q -m $commitMsg 2>&1 | Out-String + if ($LASTEXITCODE -ne 0) { throw "git commit failed: $out" } +} catch { + Write-Warning "[specify] Error: $_" + exit 1 +} + +Write-Host "✓ Git repository initialized" diff --git a/.specify/extensions/spex-collab/commands/speckit.spex-collab.phase-manager.md b/.specify/extensions/spex-collab/commands/speckit.spex-collab.phase-manager.md new file mode 100644 index 00000000..bfd4de45 --- /dev/null +++ b/.specify/extensions/spex-collab/commands/speckit.spex-collab.phase-manager.md @@ -0,0 +1,302 @@ +--- +description: "Manage phase boundaries, PR creation, and REVIEWERS.md updates between implementation phases" +--- + +# Phase Manager + +Coordinates the boundary between implementation phases: runs code review, updates REVIEWERS.md with code-specific guidance, offers PR creation, and manages phase state for cross-session continuity. + +## Ship Pipeline Guard + +```bash +if [ -f ".specify/.spex-state" ]; then + MODE=$(jq -r '.mode // empty' .specify/.spex-state 2>/dev/null) + STATUS=$(jq -r '.status // empty' .specify/.spex-state 2>/dev/null) + if [ "$MODE" = "ship" ] || [ "$STATUS" = "running" ]; then + echo "Ship mode active, skipping phase manager" + fi +fi +``` + +If ship mode is detected, return immediately. + +## Extension Enabled Check + +```bash +if [ ! -f ".specify/extensions/spex-collab/extension.yml" ]; then + echo "spex-collab extension not found, skipping" +fi +``` + +If the extension is not found, return without action. + +## Read Phase State + +Load the phase plan and completion state from `.specify/.spex-state`: + +```bash +PHASE_PLAN=$(jq -r '.collab.phase_plan // empty' .specify/.spex-state 2>/dev/null) +COMPLETED=$(jq -r '.collab.completed_phases // []' .specify/.spex-state 2>/dev/null) +PR_BASE=$(jq -r '.collab.pr_base_branch // "main"' .specify/.spex-state 2>/dev/null) +``` + +If no phase plan exists (`collab.phase_plan` is empty or missing): +- Output: "No phase plan found. Run the phase-split command first, or invoke `/speckit.spex-collab.phase-split` to create one." +- Return. + +## Determine Current Phase + +Find the next phase to process: +- Get the list of phase numbers from `phase_plan` +- Filter out any phase numbers present in `completed_phases` +- The current phase is the lowest remaining phase number + +If all phases are in `completed_phases`: +- Output: "All phases complete. Implementation is finished." +- List each phase with its PR status if available +- Return. + +Display the current phase: +``` +## Phase [N]: [Phase Name] + +Tasks in this phase: [task IDs] +Previously completed phases: [list or "none"] +``` + +## Resolve Spec Directory + +```bash +PREREQ=$(.specify/scripts/bash/check-prerequisites.sh --json --paths-only 2>/dev/null) +FEATURE_DIR=$(echo "$PREREQ" | jq -r '.FEATURE_DIR') +``` + +## Invoke Code Review Gate + +Before updating REVIEWERS.md, ensure the code review gate has run for this phase's changes. + +Check if `REVIEW-CODE.md` exists in FEATURE_DIR: + +```bash +if [ ! -f "${FEATURE_DIR}/REVIEW-CODE.md" ]; then + echo "REVIEW-CODE.md not found, invoking code review gate" +fi +``` + +If REVIEW-CODE.md does not exist, invoke the code review gate: +- Execute `speckit.spex-gates.review-code` (the review-code skill/command) +- Wait for it to complete before proceeding + +If REVIEW-CODE.md exists, read its findings for use in the REVIEWERS.md update. + +## Update REVIEWERS.md with Code Phase Section + +After the code review gate passes, update REVIEWERS.md with a code-specific phase section. + +### Gather Phase Information + +1. **What Changed**: Run `git diff --stat` against the PR base branch to get a summary of changed files: + ```bash + git diff --stat "${PR_BASE}..HEAD" 2>/dev/null + ``` + +2. **Spec Compliance**: Extract findings from REVIEW-CODE.md (compliance score, deviations, covered requirements) + +3. **Focus Areas**: Identify where the reviewer should concentrate: + - Files with the most changes + - Areas flagged by the code review gate + - Complex logic or non-obvious patterns + +4. **AI Assumptions**: Decisions made during implementation that were not explicitly specified in spec.md. Look for: + - Implementation choices not dictated by the spec + - Default values or behaviors chosen by the AI + - Error handling approaches not specified + +### Compose Phase Section + +Create a new section following this structure: + +```markdown +## Phase [N]: [Phase Name] (YYYY-MM-DD) + +### What Changed + +[Summary of files and functionality added/modified, based on git diff --stat] + +### Spec Compliance + +[Which requirements this phase addresses, compliance findings from REVIEW-CODE.md] + +### Focus Areas for Review + +[Where the reviewer should concentrate, based on complexity and review gate findings] + +### AI Assumptions + +[Decisions made during implementation that were not in the spec] +``` + +### Append to REVIEWERS.md + +Read the existing REVIEWERS.md from FEATURE_DIR. + +Append the new phase section at the end of the file. Ensure: +- A `---` separator precedes the phase section (if not already present) +- The phase number matches the current phase from the phase plan +- Existing phase sections are never overwritten or modified + +If REVIEWERS.md does not exist: +- Warn: "REVIEWERS.md not found. It should have been created by the reviewers command after task generation. Creating a minimal version." +- Create a minimal REVIEWERS.md with just the phase section + +## Offer PR Creation + +Check if `gh` CLI is available: + +```bash +command -v gh >/dev/null 2>&1 +``` + +### If gh is available + +Construct PR details: + +**Title format**: `[Feature Name] [Spec + Impl (N/T)]` where N is the current phase and T is the total number of phases. + +```bash +FEATURE_NAME=$(head -1 "$FEATURE_DIR/spec.md" | sed 's/^# Feature Specification: //') +TOTAL_PHASES=$(jq '.collab.phase_plan | length' .specify/.spex-state 2>/dev/null || echo 1) +CURRENT_PHASE_NUM=[N] # current phase number + +if [ "$TOTAL_PHASES" -gt 1 ]; then + PR_TITLE="${FEATURE_NAME} [Spec + Impl (${CURRENT_PHASE_NUM}/${TOTAL_PHASES})]" +else + PR_TITLE="${FEATURE_NAME} [Spec + Impl]" +fi +``` + +If a spec-only PR was created earlier (titled `... [Spec]`), update its title to reflect the implementation phase using `gh pr edit`. + +- **Body**: Start with a link to REVIEWERS.md for full review context, then include the phase section. + +Construct a full GitHub URL for REVIEWERS.md and read label config: + +```bash +BRANCH=$(git branch --show-current) +REVIEWERS_REL="${FEATURE_DIR#$(git rev-parse --show-toplevel)/}/REVIEWERS.md" +REMOTE=$(git remote | grep -x upstream 2>/dev/null || echo origin) +REMOTE_URL=$(git remote get-url "$REMOTE" 2>/dev/null | sed 's/\.git$//' | sed 's|git@github.com:|https://github.com/|') +REVIEWERS_URL="${REMOTE_URL}/blob/${BRANCH}/${REVIEWERS_REL}" + +# Read label config +COLLAB_CONFIG=".specify/extensions/spex-collab/collab-config.yml" +LABELS_ENABLED=$(yq -r '.labels.enabled // true' "$COLLAB_CONFIG" 2>/dev/null || echo "true") +SPEC_LABEL=$(yq -r '.labels.spec // "spex/spec"' "$COLLAB_CONFIG" 2>/dev/null || echo "spex/spec") +SPEC_APPROVED_LABEL=$(yq -r '.labels.spec_approved // "spex/spec-approved"' "$COLLAB_CONFIG" 2>/dev/null || echo "spex/spec-approved") +IMPL_LABEL=$(yq -r '.labels.implement // "spex/implement"' "$COLLAB_CONFIG" 2>/dev/null || echo "spex/implement") +LABEL_FLAG="" +if [ "$LABELS_ENABLED" = "true" ]; then + LABEL_FLAG="--label ${IMPL_LABEL}" +fi +``` + +The PR body MUST begin with: +``` +> **[Review Guide](REVIEWERS_URL)** for full context: motivation, key decisions, and scope boundaries. +``` + +Followed by the phase section content (What Changed, Spec Compliance, Focus Areas, AI Assumptions). + +Use AskUserQuestion to ask: + +**Question**: "Phase [N] is ready. What would you like to do?" +**Options**: +- "Create PR" - create PR via gh and pause +- "Skip PR, continue to next phase" - mark complete, proceed +- "Pause here" - mark current phase, stop for manual action + +**If "Create PR"**: + +```bash +gh pr create --base "${PR_BASE}" --title "$PR_TITLE" ${LABEL_FLAG} --body "$(cat < **[Review Guide](${REVIEWERS_URL})** for full context: motivation, key decisions, and scope boundaries. + +[PR body content from REVIEWERS.md phase section] +PR_BODY +)" +``` + +After PR creation: +- Capture the PR URL from gh output +- If labels are enabled, update labels on the PR to reflect the implementation phase: + ```bash + # If an existing PR has spex/spec, transition labels + PR_NUM=$(gh pr view --json number --jq .number 2>/dev/null) + if [ -n "$PR_NUM" ] && [ "$LABELS_ENABLED" = "true" ]; then + gh pr edit "$PR_NUM" --remove-label "$SPEC_LABEL" --add-label "$SPEC_APPROVED_LABEL","$IMPL_LABEL" 2>/dev/null || true + fi + ``` +- Mark the phase as completed (see below) +- Output: "PR created: [URL]" +- Output: "Phase [N] complete. After the PR is merged, invoke `/speckit.spex-collab.phase-manager` to continue with Phase [N+1]." +- Stop execution (pause for user to handle the PR) + +**If "Skip PR, continue to next phase"**: +- Mark the phase as completed +- Output: "Phase [N] complete (no PR created). Continuing to Phase [N+1]..." +- Do NOT stop execution, the implementation command can continue + +**If "Pause here"**: +- Set `current_phase` to N in `.spex-state` +- Output: "Paused at Phase [N]. Invoke `/speckit.spex-collab.phase-manager` when ready to continue." +- Stop execution + +### If gh is NOT available + +Warn and provide manual instructions: + +``` +gh CLI not found. To create the PR manually: + +Branch: [current branch name] +Target: [PR_BASE] +Suggested title: [Feature Name] [Spec + Impl (N/T)] +Suggested body (start with the review guide link): + > **[Review Guide](REVIEWERS_URL)** for full context: motivation, key decisions, and scope boundaries. + [phase section content] +``` + +Then use AskUserQuestion: +- "Mark phase complete and continue" - proceed +- "Pause here" - stop for manual action + +## Update Phase State + +When marking a phase as completed: + +```bash +tmp=$(mktemp) && jq \ + --argjson phase_num [N] \ + '.collab.completed_phases += [$phase_num] | .collab.completed_phases |= unique | .collab.current_phase = null' \ + .specify/.spex-state > "$tmp" && mv "$tmp" .specify/.spex-state +``` + +When setting current phase (for pause): + +```bash +tmp=$(mktemp) && jq \ + --argjson phase_num [N] \ + '.collab.current_phase = $phase_num' \ + .specify/.spex-state > "$tmp" && mv "$tmp" .specify/.spex-state +``` + +## Final Report + +After processing the phase, output a summary: + +``` +Phase [N] ([Phase Name]): [COMPLETE / PAUSED] +PR: [URL or "not created"] +REVIEWERS.md: updated with Phase [N] section +Next: [Phase N+1 name or "All phases complete"] +``` diff --git a/.specify/extensions/spex-collab/commands/speckit.spex-collab.phase-split.md b/.specify/extensions/spex-collab/commands/speckit.spex-collab.phase-split.md new file mode 100644 index 00000000..1fd66435 --- /dev/null +++ b/.specify/extensions/spex-collab/commands/speckit.spex-collab.phase-split.md @@ -0,0 +1,149 @@ +--- +description: "Present phase split proposal before implementation begins" +--- + +# Phase Split Proposal + +## Ship Pipeline Guard + +If `.specify/.spex-state` exists and its `mode` is `"ship"` or `status` is `"running"`, skip the phase split entirely and return immediately without prompting. + +```bash +if [ -f ".specify/.spex-state" ]; then + MODE=$(jq -r '.mode // empty' .specify/.spex-state 2>/dev/null) + STATUS=$(jq -r '.status // empty' .specify/.spex-state 2>/dev/null) + if [ "$MODE" = "ship" ] || [ "$STATUS" = "running" ]; then + echo "Ship mode active, skipping phase split" + fi +fi +``` + +If ship mode is detected, return immediately. Do not display any proposal or prompt the user. + +## Extension Enabled Check + +Verify spex-collab is active: + +```bash +if [ ! -f ".specify/extensions/spex-collab/extension.yml" ]; then + echo "spex-collab extension not found, skipping" +fi +``` + +If the extension is not found, return without action. + +## Check for Existing Phase Plan + +If `.specify/.spex-state` already has a `collab.phase_plan` with entries, a phase plan was previously confirmed: + +```bash +EXISTING_PLAN=$(jq -r '.collab.phase_plan // empty' .specify/.spex-state 2>/dev/null) +``` + +If a phase plan exists and has entries: +- Display: "A phase plan already exists from a previous session." +- Show the existing plan as a table +- Ask: "Use existing plan, or create a new one?" +- If user chooses existing: return (implementation proceeds with saved plan) +- If user chooses new: continue with detection below + +## Resolve Spec Directory and Read Tasks + +```bash +PREREQ=$(.specify/scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks 2>/dev/null) +FEATURE_DIR=$(echo "$PREREQ" | jq -r '.FEATURE_DIR') +``` + +Read `tasks.md` from FEATURE_DIR. + +## Detect Task Phases + +Parse tasks.md for heading-based groupings. Look for these patterns: + +1. `## Phase N:` headings (e.g., `## Phase 1: Setup`) +2. `## USN` or `## US1:` headings (user story groupings) +3. Any `## ` heading that contains task items (`- [ ] T###`) below it + +For each heading group: +- Record the phase name (heading text) +- Collect all task IDs (lines matching `- [ ] T[0-9]+` or `- [X] T[0-9]+`) +- Count total tasks and completed tasks + +If no phase-like headings are found, treat all tasks as a single phase named "All Tasks". + +## Present Phase Split Proposal + +Display the parsed phases as a table: + +``` +## Proposed PR Split + +| Phase | Name | Tasks | Completed | Task IDs | +|-------|------|-------|-----------|----------| +| 1 | Setup (Extension Scaffold) | 3 | 0 | T001, T002, T003 | +| 2 | US1 - Spec PR with REVIEWERS.md | 6 | 0 | T004-T009 | +| 3 | US2 - Phase-Based Implementation PRs | 10 | 0 | T010-T019 | +| 4 | US3 - Code PR with Updated REVIEWERS.md | 3 | 0 | T020-T022 | +| 5 | Polish & Integration | 6 | 0 | T023-T028 | + +Each phase becomes a separate PR for focused review. +``` + +Use AskUserQuestion to ask: + +**Question**: "Does this phase split look right for your PRs?" +**Options**: +- "Confirm as-is" - proceed with this grouping +- "Adjust groupings" - let user merge or split phases +- "Single phase (no split)" - treat everything as one PR + +If user selects "Adjust groupings": +- Ask which phases to merge or split +- Only allow adjusting phases that have not been completed yet +- Re-display the updated table and confirm again + +If user selects "Single phase": +- Combine all tasks into one phase named "Full Implementation" + +## Persist Phase Plan + +Store the confirmed plan in `.specify/.spex-state` under the `collab` namespace: + +```bash +# Read pr_base_branch from extension config if available +PR_BASE="main" +if [ -f ".specify/extensions/spex-collab/collab-config.yml" ]; then + CONFIGURED_BASE=$(yq -r '.pr_base_branch // "main"' .specify/extensions/spex-collab/collab-config.yml 2>/dev/null) + if [ -n "$CONFIGURED_BASE" ] && [ "$CONFIGURED_BASE" != "null" ]; then + PR_BASE="$CONFIGURED_BASE" + fi +fi +``` + +Update `.spex-state` with the phase plan using jq. The `collab` object contains: +- `phase_plan`: array of objects with `phase` (int), `name` (string), `tasks` (string array) +- `completed_phases`: empty array (or preserved from existing state) +- `current_phase`: null +- `pr_base_branch`: from config or "main" + +```bash +# Example jq update (adapt phase_plan array to actual confirmed phases) +tmp=$(mktemp) && jq --argjson plan '[...]' --arg base "$PR_BASE" ' + .collab = { + "phase_plan": $plan, + "completed_phases": (.collab.completed_phases // []), + "current_phase": null, + "pr_base_branch": $base + } +' .specify/.spex-state > "$tmp" && mv "$tmp" .specify/.spex-state +``` + +## Report + +Output confirmation: +``` +Phase plan saved to .specify/.spex-state +[N] phases configured, targeting [base_branch] for PRs +Implementation will pause after each phase for PR creation. +Invoke `/speckit.spex-collab.phase-manager` after each phase to manage PRs. +``` diff --git a/.specify/extensions/spex-collab/commands/speckit.spex-collab.reconcile.md b/.specify/extensions/spex-collab/commands/speckit.spex-collab.reconcile.md new file mode 100644 index 00000000..bc4af381 --- /dev/null +++ b/.specify/extensions/spex-collab/commands/speckit.spex-collab.reconcile.md @@ -0,0 +1,199 @@ +--- +description: "Reconcile revised tasks against existing implementation, mark completed tasks, and produce an actionable delta for re-implementation" +argument-hint: "" +--- + +# Reconcile Implementation with Revised Spec + +After a spec revision changes the task list, this command scans the existing codebase to determine which tasks are already satisfied, which need rework, and which are new. It produces a reconciled `tasks.md` where `/speckit-implement` can pick up only the delta. + +## Ship Pipeline Guard + +If `.specify/.spex-state` exists with `mode: "ship"`, return immediately. + +## Resolve Context + +```bash +PREREQ=$(.specify/scripts/bash/check-prerequisites.sh --json --paths-only 2>/dev/null) +FEATURE_DIR=$(echo "$PREREQ" | jq -r '.FEATURE_DIR') +BRANCH=$(git branch --show-current) +``` + +Verify required artifacts: +```bash +[ -f "${FEATURE_DIR}/spec.md" ] || { echo "ERROR: No spec.md"; exit 1; } +[ -f "${FEATURE_DIR}/tasks.md" ] || { echo "ERROR: No tasks.md"; exit 1; } +[ -f "${FEATURE_DIR}/plan.md" ] || { echo "ERROR: No plan.md"; exit 1; } +``` + +## Detect Existing Implementation + +Check whether implementation work already exists on this branch: + +```bash +# Count non-spec commits on the feature branch +IMPL_FILES=$(git diff --name-only main...HEAD 2>/dev/null | grep -v '^specs/' | grep -v '^brainstorm/' | grep -v '^\.' | head -50) +IMPL_FILE_COUNT=$(echo "$IMPL_FILES" | grep -c . 2>/dev/null || echo 0) +``` + +If no implementation files are found (`IMPL_FILE_COUNT` is 0): +``` +No implementation files detected on this branch. +Nothing to reconcile. Run /speckit-implement to start fresh. +``` +Return. + +## Read Artifacts + +1. **tasks.md**: Parse the full task list. Extract each task's: + - ID (e.g., `T01`, `1.1`) + - Status (`[ ]` or `[X]`) + - Description + - File paths mentioned in the task + - Phase assignment + - Parallel marker `[P]` if present + +2. **plan.md**: Read for architecture context, file structure, and module organization to understand what each task produces. + +3. **spec.md**: Read for requirements to understand what "satisfied" means for each task. + +## Analyze Each Task Against Existing Code + +For each task in tasks.md that is currently `[ ]` (not yet marked complete): + +### Step 1: Identify Expected Output + +From the task description and plan context, determine what the task should produce: +- Files to create or modify (extract paths from the task description) +- Functions, classes, or exports to add +- Tests to write +- Configuration changes + +### Step 2: Check Against Existing Code + +For each expected output: + +```bash +# Check if mentioned files exist +for FILE in ; do + [ -f "$FILE" ] && echo "EXISTS: $FILE" || echo "MISSING: $FILE" +done +``` + +For files that exist, read them and assess whether the task's intent is satisfied: +- Does the file contain the expected functions/classes? +- Does the implementation match the spec requirements referenced by the task? +- Are the tests present and covering the expected behavior? + +### Step 3: Classify the Task + +Assign each task one of three statuses: + +- **DONE**: The existing code fully satisfies this task. All expected files exist, functions are implemented, tests cover the behavior. Mark as `[X]`. + +- **REWORK**: The existing code partially covers this task, but the spec revision changed requirements that affect it. The code exists but needs modification. Mark as `[ ]` and add a `` comment. + +- **NEW**: No existing code addresses this task. It was added by the spec revision. Keep as `[ ]`. + +## Present Reconciliation Report + +After analyzing all tasks, present the findings: + +``` +## Reconciliation Report + +**Tasks analyzed**: N total + +| Status | Count | Tasks | +|---------|-------|-------| +| DONE | X | T01, T03, T05, ... | +| REWORK | Y | T04, T08, ... | +| NEW | Z | T12, T13, ... | + +### DONE (will be marked [X]) + +These tasks are fully satisfied by existing code: +- T01: Create events.py module — exists at agent_eval/events.py +- T03: Add EventType enum — exists in events.py with all required types +... + +### REWORK (need modification) + +These tasks have existing code that needs updating for the revised spec: +- T04: Parse tool results — exists but needs 50K cap (was unlimited) + Files: agent_eval/events.py:parse_tool_result() +- T08: Template variable — exists as {{ stdout }}, needs rename to {{ conversation }} + Files: agent_eval/judges/llm.py +... + +### NEW (no existing code) + +These tasks were added by the spec revision: +- T12: Subagent transcript merging +- T13: Deduplication by message ID +... +``` + +Use AskUserQuestion (`multiSelect: false`, header: "Reconcile"): + +**"Apply this reconciliation to tasks.md?"** + +- "Apply all": "Mark DONE tasks as [X], add REWORK comments, keep NEW as [ ]" +- "Review individually": "Go through each DONE/REWORK classification for confirmation" +- "Cancel": "Leave tasks.md unchanged" + +### If "Review individually" + +For each task classified as DONE, ask for confirmation: + +Use AskUserQuestion (`multiSelect: true`, header: "Confirm"): + +**"Which DONE classifications are correct? Unselected tasks will remain [ ]."** + +Options: one per DONE task (label: task ID, description: evidence summary) + +Then for each REWORK task, show the rework description and ask if it's accurate. + +## Apply to tasks.md + +Update `${FEATURE_DIR}/tasks.md`: + +1. Mark confirmed DONE tasks as `[X]` +2. For REWORK tasks, keep as `[ ]` and append a rework hint comment after the task line: + ```markdown + - [ ] T04 [core] Implement tool result parsing with size cap `agent_eval/events.py` + + ``` +3. NEW tasks remain as `[ ]` (no annotation needed) + +## Update REVIEWERS.md + +Append a reconciliation note to the revision history in REVIEWERS.md: + +```markdown +### Reconciliation (YYYY-MM-DD) + +**Existing implementation scanned**: N files on branch +**Task reconciliation**: X DONE, Y REWORK, Z NEW out of T total +**Delta for re-implementation**: Y + Z = D tasks remaining +``` + +## Suggest Next Step + +``` +## Reconciliation Complete + +tasks.md updated: X tasks marked [X], Y marked for rework, Z new +Delta: D tasks remaining for /speckit-implement + +Next step: + /speckit-implement Run implementation for remaining [ ] tasks +``` + +If the delta is 0 (all tasks satisfied): +``` +All tasks are satisfied by existing code. No re-implementation needed. + +Next step: + /speckit-spex-gates-review-code Verify compliance with revised spec +``` diff --git a/.specify/extensions/spex-collab/commands/speckit.spex-collab.reviewers.md b/.specify/extensions/spex-collab/commands/speckit.spex-collab.reviewers.md new file mode 100644 index 00000000..b51c1d60 --- /dev/null +++ b/.specify/extensions/spex-collab/commands/speckit.spex-collab.reviewers.md @@ -0,0 +1,209 @@ +--- +description: "Generate REVIEWERS.md review guide for spec and code PRs" +argument-hint: "[--regenerate]" +--- + +# Generate REVIEWERS.md Review Guide + +## Ship Pipeline Guard + +If `.specify/.spex-state` exists and its `mode` is `"ship"` or `status` is `"running"`, skip REVIEWERS.md generation entirely and return immediately. + +```bash +if [ -f ".specify/.spex-state" ]; then + MODE=$(jq -r '.mode // empty' .specify/.spex-state 2>/dev/null) + STATUS=$(jq -r '.status // empty' .specify/.spex-state 2>/dev/null) + if [ "$MODE" = "ship" ] || [ "$STATUS" = "running" ]; then + echo "Ship mode active, skipping REVIEWERS.md generation" + fi +fi +``` + +If ship mode is detected, output nothing further and return. Do not generate or modify any files. + +## Extension Enabled Check + +Verify spex-collab is active. If the extension directory does not exist, skip silently: + +```bash +if [ ! -f ".specify/extensions/spex-collab/extension.yml" ]; then + echo "spex-collab extension not found, skipping" +fi +``` + +If the extension is not found, return without generating REVIEWERS.md. Vanilla spec-kit behavior is preserved. + +## Resolve Spec Directory + +Run the prerequisites script to locate the feature directory: + +```bash +.specify/scripts/bash/check-prerequisites.sh --json --paths-only 2>/dev/null +``` + +Parse the JSON output to extract: +- `FEATURE_DIR`: absolute path to the spec directory (e.g., `/path/to/specs/018-collab-extension`) +- `FEATURE_SPEC`: path to spec.md + +If resolution fails, inform the user and stop: +``` +Cannot resolve spec directory. Are you on a feature branch? +``` + +## Check for Existing REVIEWERS.md + +```bash +REVIEWERS_PATH="${FEATURE_DIR}/REVIEWERS.md" +if [ -f "$REVIEWERS_PATH" ]; then + echo "REVIEWERS.md exists, checking for code phase sections to preserve" +fi +``` + +If REVIEWERS.md already exists: +1. Read its content +2. Look for the first line matching `## Phase [0-9]` (a code phase section heading) +3. If found: preserve everything from that line onwards (these are code phase sections from previous implementation phases). Only regenerate the spec sections above that boundary. +4. If no phase headings found: regenerate the entire file + +Store any preserved code phase content for later appending. + +## Read Source Artifacts + +Read these files from FEATURE_DIR to extract review guide content: + +1. **spec.md** (required): + - Problem statement: extract from "## Problem Statement", "## Background", or the first substantive paragraph explaining what's broken or missing (feeds "Why This Change") + - Feature overview: user story summaries or solution description (feeds "What Changes") + - Scope and applicability: extract from "## Requirements" (applies when) and "## Out of Scope" (does not apply when) (feeds "When It Applies") + - Success criteria: from the "## Success Criteria" section + - Edge cases: from the "### Edge Cases" section if present + +2. **plan.md** (if exists, feeds "How It Works"): + - Architecture approach: modules, data flow, integration points + - Key technical decisions: from "## Research Findings" or decision sections + - Trade-offs and rationale: why alternatives were rejected + - Implementation strategy: from the "## Implementation Phases" section + +3. **tasks.md** (if exists): + - Phase count and task distribution + - Complexity indicators (total tasks, parallel markers) + +4. **research.md** (if exists): + - Additional context on technical decisions + - Explored alternatives + +## Compose REVIEWERS.md + +Read the template from `spex/extensions/spex-collab/templates/reviewers-template.md` for the structural skeleton. + +Synthesize a human-readable review guide. This is NOT a dump of automated review findings. Write it as if briefing a colleague who needs to review the PR efficiently. + +### Section Guidelines + +The structure follows "general to specific": a reviewer should understand the motivation and shape of the change before encountering detailed scope lists. + +**Why This Change**: The problem being solved. What's broken, painful, or missing today. 2-4 sentences, written so a reviewer who has NOT read the spec understands the motivation in 30 seconds. Extract from the spec's problem statement, user stories, or the plan's research findings. + +**What Changes**: One paragraph summary of the solution at the outcome level. What gets added, removed, or restructured. Stay at the "what does the user/system gain" level. Mention breaking changes upfront. Do NOT include implementation details here (those go in "How It Works"). + +**How It Works**: Implementation approach extracted from plan.md. Cover architecture, key modules, data flow, and integration points. This is where technical details belong. Keep it concise but specific enough that a reviewer understands the implementation strategy without reading plan.md. For spec-only PRs where plan.md doesn't exist yet, omit this section or note "Implementation approach TBD." + +**When It Applies**: Reframe scope as applicability. More natural than in/out lists for a reviewer scanning the PR. +- "Applies when": conditions, contexts, or scenarios where this feature is active +- "Does not apply when": explicit exclusions with brief rationale for deferral + +**Key Decisions**: Numbered list of the most significant design choices. For each, include: +- What was decided +- What alternatives were considered +- Why this approach was chosen + +**Areas Needing Attention**: Points where reasonable engineers might disagree. Flag: +- Trade-offs that favor one quality over another +- Assumptions that could be wrong +- Patterns that deviate from project conventions +- Complexity that might be over-engineered or under-engineered + +**Open Questions**: Remaining ambiguities or deferred decisions. If none, state "No open questions identified." + +**Review Checklist**: Use the standard checklist from the template. Add feature-specific items if the spec has unusual constraints. + +### Replace Template Placeholders + +- `[Feature Name]`: extract from spec.md title or first heading +- `YYYY-MM-DD`: use today's date +- `[spec.md](spec.md)`: keep as relative link + +## Write REVIEWERS.md + +Write the composed content to `${FEATURE_DIR}/REVIEWERS.md`. + +If code phase sections were preserved from an earlier version (step 4), append them after the `---` separator at the end of the spec sections. + +## Offer Spec PR + +After writing REVIEWERS.md, check if `gh` is available and offer to create a spec-only PR for review before implementation begins. + +Use AskUserQuestion (`multiSelect: false`, header: "Spec PR"): + +**"REVIEWERS.md is ready. Create a spec PR for team review?"** + +Options: +- "Create spec PR": "Push branch and create a PR with the [Spec] tag for review before implementation" +- "Skip": "Continue without creating a PR" + +**If "Create spec PR":** + +```bash +FEATURE_NAME=$(head -1 "$FEATURE_DIR/spec.md" | sed 's/^# Feature Specification: //') +REMOTE=$(git remote | grep -x upstream 2>/dev/null || echo origin) +BRANCH=$(git branch --show-current) +REVIEWERS_REL="${FEATURE_DIR#$(git rev-parse --show-toplevel)/}/REVIEWERS.md" +REMOTE_URL=$(git remote get-url "$REMOTE" 2>/dev/null | sed 's/\.git$//' | sed 's|git@github.com:|https://github.com/|') +REVIEWERS_URL="${REMOTE_URL}/blob/${BRANCH}/${REVIEWERS_REL}" + +# Read label config +COLLAB_CONFIG=".specify/extensions/spex-collab/collab-config.yml" +LABELS_ENABLED=$(yq -r '.labels.enabled // true' "$COLLAB_CONFIG" 2>/dev/null || echo "true") +SPEC_LABEL=$(yq -r '.labels.spec // "spex/spec"' "$COLLAB_CONFIG" 2>/dev/null || echo "spex/spec") +LABEL_FLAG="" +if [ "$LABELS_ENABLED" = "true" ]; then + LABEL_FLAG="--label ${SPEC_LABEL}" +fi + +git push -u "$REMOTE" "$BRANCH" + +gh pr create --base main --title "${FEATURE_NAME} [Spec]" ${LABEL_FLAG} --body "$(cat < **[Review Guide](${REVIEWERS_URL})** for full context: motivation, key decisions, and scope boundaries. + +## Spec for review + +This PR contains the specification artifacts for **${FEATURE_NAME}**. Implementation follows after spec approval. + +Assisted-By: 🤖 Claude Code +PR_BODY +)" +``` + +If the label doesn't exist in the repo, `gh pr create --label` will fail. In that case, retry without the label and warn: +``` +Warning: Label "${SPEC_LABEL}" not found in this repo. PR created without label. +To create it: gh label create "${SPEC_LABEL}" --color 0075ca --description "Spec PR awaiting review" +Or disable labels: set labels.enabled to false in .specify/extensions/spex-collab/collab-config.yml +``` + +Report the PR URL. + +**If "Skip":** Continue without creating a PR. + +## Report + +Output a brief confirmation: +``` +Generated REVIEWERS.md in [feature-dir]/ +Sections: Why This Change, What Changes, How It Works, When It Applies, Key Decisions, Areas Needing Attention, Open Questions, Review Checklist +``` + +If this was a re-run with preserved code phase sections, also note: +``` +Preserved N existing code phase section(s) +``` diff --git a/.specify/extensions/spex-collab/commands/speckit.spex-collab.revise.md b/.specify/extensions/spex-collab/commands/speckit.spex-collab.revise.md new file mode 100644 index 00000000..ac6d17fd --- /dev/null +++ b/.specify/extensions/spex-collab/commands/speckit.spex-collab.revise.md @@ -0,0 +1,321 @@ +--- +description: "Revise spec artifacts based on PR review feedback, cascade to plan/tasks, and update REVIEWERS.md with revision history" +argument-hint: "[--pr ] [description of changes]" +--- + +# Revise Spec from PR Feedback + +Handles the spec revision loop: read PR review comments, update spec, cascade to plan and tasks, document the revision in REVIEWERS.md, and push back to the PR. + +## Ship Pipeline Guard + +If `.specify/.spex-state` exists with `mode: "ship"`, return immediately. Spec revision is an interactive collab workflow. + +## Resolve Context + +```bash +PREREQ=$(.specify/scripts/bash/check-prerequisites.sh --json --paths-only 2>/dev/null) +FEATURE_DIR=$(echo "$PREREQ" | jq -r '.FEATURE_DIR') +BRANCH=$(git branch --show-current) +``` + +Verify required artifacts exist: +```bash +[ -f "${FEATURE_DIR}/spec.md" ] || { echo "ERROR: No spec.md found in ${FEATURE_DIR}"; exit 1; } +``` + +## Gather Feedback + +Determine the review feedback to address. Two input modes: + +### Mode 1: PR number provided (`--pr `) + +Fetch unresolved review comments from the PR: + +```bash +PR_NUM="" +gh pr view "$PR_NUM" --json reviews,comments --jq '.reviews[].body, .comments[].body' 2>/dev/null +gh api "repos/{owner}/{repo}/pulls/${PR_NUM}/comments" --jq '.[] | select(.position != null) | "\(.path):\(.position) - \(.body)"' 2>/dev/null +``` + +Present a summary of the feedback to the user: + +``` +## PR # Review Feedback + +Found N review comments: + +1. @reviewer: "The hard removal of stdout seems risky..." +2. @reviewer: "Should subagent merging be in scope?" +... + +Which comments should we address in this revision? +``` + +Use AskUserQuestion (`multiSelect: true`, header: "Feedback"): +**"Which review comments should this revision address?"** + +Options: one per comment (label: truncated comment, description: full text) + +### Mode 2: User describes changes (arguments or conversation) + +If no `--pr` flag, the user's arguments or conversation describe what to change. Use that directly as the revision input. + +## Plan the Revision + +Before making changes, summarize what will change. Read the current spec.md and identify which sections are affected by the feedback. + +Present a revision plan: + +``` +## Revision Plan + +Based on the feedback, these changes are needed: + +**Spec changes**: +- Section "Requirements": add deprecation warning for stdout access +- Section "Out of Scope": move subagent merging to in-scope + +**Expected cascade**: +- plan.md: will need regeneration (new requirements affect implementation approach) +- tasks.md: will need regeneration (task count may change) +- REVIEWERS.md: Key Decisions and Scope Boundaries sections affected + +Proceed with revision? +``` + +Use AskUserQuestion (`multiSelect: false`, header: "Revise"): +**"Proceed with the revision plan?"** +- "Yes, revise all": "Update spec, regenerate plan and tasks, update REVIEWERS.md" +- "Spec only": "Update spec.md only, skip plan/tasks regeneration" +- "Cancel": "Abort revision" + +If "Cancel", stop. + +## Update Spec + +Apply the planned changes to `${FEATURE_DIR}/spec.md`. Edit the affected sections, preserving the overall structure and unaffected content. + +After editing, verify the spec is still well-formed: +- All required sections present +- No orphaned references +- No contradictions introduced by the changes + +### Clarify Updated Spec + +Invoke `/speckit-clarify` on the updated spec to detect any new ambiguities introduced by the revision. In the revise context, answer clarification questions yourself using the PR feedback as context (the reviewer's intent guides the answers). Update the spec with any clarifications. + +### Review Updated Spec + +Invoke `/speckit-spex-gates-review-spec` to validate the revised spec. This runs as a subagent for clean context separation: + +``` +You are reviewing a revised specification after PR feedback. + +Feature directory: +Spec: /spec.md + +Invoke /speckit-spex-gates-review-spec to validate spec quality. +Report the overall assessment and any findings. +``` + +If the review surfaces issues (UNSOUND or critical findings): +- Fix the spec issues before proceeding +- Re-run the review (max 2 retries) +- If issues persist after retries, warn the user and proceed + +### Track Gate Results + +Record gate outcomes for the revision entry: +```bash +SPEC_GATE="PASS" # or the actual assessment from review-spec +``` + +## Cascade to Plan and Tasks + +**Skip this step if user chose "Spec only".** + +### Regenerate Plan + +Invoke `/speckit-plan` to regenerate `plan.md` based on the updated spec. The plan command reads the current spec and produces an updated plan. + +After plan regeneration, verify: +```bash +[ -f "${FEATURE_DIR}/plan.md" ] && echo "plan.md regenerated" +``` + +### Regenerate Tasks + +Invoke `/speckit-tasks` to regenerate `tasks.md` based on the updated plan. + +After task regeneration, capture the new task count: +```bash +NEW_TASK_COUNT=$(grep -c '^\- \[' "${FEATURE_DIR}/tasks.md" 2>/dev/null || echo "?") +``` + +### Review Updated Plan + +Invoke `/speckit-spex-gates-review-plan` to validate the regenerated plan and tasks. This runs as a subagent: + +``` +You are reviewing a regenerated plan after spec revision from PR feedback. + +Feature directory: +Spec: /spec.md +Plan: /plan.md +Tasks: /tasks.md + +Invoke /speckit-spex-gates-review-plan to validate plan coverage and task quality. +Report the findings and overall assessment. +``` + +If the review surfaces critical issues: +- Fix the plan/task issues before proceeding +- Re-run the review (max 2 retries) +- If issues persist after retries, warn the user and proceed + +Record gate outcome: +```bash +PLAN_GATE="PASS" # or the actual assessment from review-plan +``` + +## Update REVIEWERS.md + +### Regenerate Spec Sections + +Invoke the reviewers command logic to regenerate the spec-facing sections (Why This Change, What Changes, Key Decisions, etc.) while preserving any existing code phase sections. + +Read the current REVIEWERS.md: +```bash +REVIEWERS_PATH="${FEATURE_DIR}/REVIEWERS.md" +``` + +If code phase sections exist (lines starting with `## Phase`), preserve them. Regenerate the spec sections above the `---` separator by re-running the reviewers synthesis from the updated spec, plan, and tasks. + +### Append Revision Entry + +After the review checklist and before the `---` separator (or at the end if no separator), append a revision history section. + +If a `## Revision History` section already exists, append a new entry. If not, create the section. + +Determine the revision number: +```bash +REV_COUNT=$(grep -c '^### Rev ' "$REVIEWERS_PATH" 2>/dev/null || echo 0) +NEXT_REV=$((REV_COUNT + 1)) +``` + +Compose the revision entry: + +```markdown +## Revision History + +### Rev N (YYYY-MM-DD) - [Brief trigger description] + +**Trigger**: [PR review feedback from #NNN / User-requested changes / ...] + +**Spec changes**: +- [Bullet list of what changed in spec.md, one per meaningful change] + +**Quality gates**: +- review-spec: [PASS/SOUND (score) / findings fixed / warning: issues remain] +- review-plan: [PASS (score) / findings fixed / skipped (spec-only revision)] + +**Cascade impact**: +- plan.md: [regenerated (summary of changes) / unchanged] +- tasks.md: [regenerated (N tasks, was M) / unchanged] +- REVIEWERS.md: [which sections were updated] +``` + +If the revision history section already exists, append the new `### Rev N` entry at the end of the section (before `---` or end of file). + +## Commit and Push + +Stage all changed artifacts: + +```bash +git add "${FEATURE_DIR}/spec.md" +git add "${FEATURE_DIR}/REVIEWERS.md" +[ -f "${FEATURE_DIR}/plan.md" ] && git add "${FEATURE_DIR}/plan.md" +[ -f "${FEATURE_DIR}/tasks.md" ] && git add "${FEATURE_DIR}/tasks.md" + +git commit -m "spec: revise based on review feedback (rev ${NEXT_REV}) + +Assisted-By: 🤖 Claude Code" +``` + +Push to the remote: +```bash +REMOTE=$(git remote | grep -x upstream 2>/dev/null || echo origin) +git push "$REMOTE" "$BRANCH" +``` + +## Comment on PR + +If a PR number is known (from `--pr` flag or detected from the branch): + +```bash +PR_NUM=$(gh pr list --head "$BRANCH" --json number --jq '.[0].number' 2>/dev/null) +``` + +If a PR exists, post a summary comment: + +```bash +gh pr comment "$PR_NUM" --body "$(cat </dev/null | sed 's/\.git$//' | sed 's|git@github.com:|https://github.com/|') +REVIEWERS_URL="${REMOTE_URL}/blob/${BRANCH}/${FEATURE_DIR#$(git rev-parse --show-toplevel)/}/REVIEWERS.md" +``` + +## Report + +``` +## Revision Complete + +**Rev**: ${NEXT_REV} +**Spec changes**: N sections updated +**Quality gates**: review-spec ${SPEC_GATE}, review-plan ${PLAN_GATE} +**Cascade**: plan.md [regenerated/unchanged], tasks.md [regenerated/unchanged] +**REVIEWERS.md**: revision history appended +**PR**: [comment posted to #NNN / no PR found] +**Pushed**: ${BRANCH} → ${REMOTE} +``` + +## Suggest Reconciliation + +After reporting, check if implementation files already exist on the branch: + +```bash +IMPL_FILES=$(git diff --name-only main...HEAD 2>/dev/null | grep -v '^specs/' | grep -v '^brainstorm/' | grep -v '^\.' | grep -c . 2>/dev/null || echo 0) +``` + +If implementation files exist (`IMPL_FILES` > 0): + +``` +Implementation files detected on this branch (${IMPL_FILES} files). +The revised tasks may conflict with existing code. + +Run /speckit-spex-collab-reconcile to scan existing code against +the updated tasks and produce a delta for re-implementation. +``` diff --git a/.specify/extensions/spex-collab/config-template.yml b/.specify/extensions/spex-collab/config-template.yml new file mode 100644 index 00000000..f747cbef --- /dev/null +++ b/.specify/extensions/spex-collab/config-template.yml @@ -0,0 +1,12 @@ +pr_base_branch: "main" +auto_generate_reviewers: true + +# GitHub PR labels applied automatically during PR creation. +# Set enabled to false to skip labeling entirely (e.g., if the repo +# doesn't have these labels and you can't create them). +# Label names are configurable; the defaults use a "spex/" prefix. +labels: + enabled: true + spec: "spex/spec" + spec_approved: "spex/spec-approved" + implement: "spex/implement" diff --git a/.specify/extensions/spex-collab/extension.yml b/.specify/extensions/spex-collab/extension.yml new file mode 100644 index 00000000..4f7d3bf1 --- /dev/null +++ b/.specify/extensions/spex-collab/extension.yml @@ -0,0 +1,56 @@ +schema_version: "1.0" + +extension: + id: spex-collab + name: "Spex Collaboration" + version: "1.0.0" + description: "Generate REVIEWERS.md guides for PR reviewers and split implementation into phase-based PRs with pause points" + author: cc-spex + license: MIT + +requires: + speckit_version: ">=0.5.2" + extensions: + - id: spex-gates + version: ">=1.0.0" + +provides: + commands: + - name: speckit.spex-collab.reviewers + file: commands/speckit.spex-collab.reviewers.md + description: "Generate REVIEWERS.md review guide for spec and code PRs" + - name: speckit.spex-collab.phase-split + file: commands/speckit.spex-collab.phase-split.md + description: "Present phase split proposal before implementation" + - name: speckit.spex-collab.phase-manager + file: commands/speckit.spex-collab.phase-manager.md + description: "Manage phase boundaries, PR creation, and REVIEWERS.md updates" + - name: speckit.spex-collab.revise + file: commands/speckit.spex-collab.revise.md + description: "Revise spec from PR review feedback, cascade to plan/tasks, update REVIEWERS.md" + - name: speckit.spex-collab.reconcile + file: commands/speckit.spex-collab.reconcile.md + description: "Reconcile revised tasks against existing implementation, produce delta for re-implementation" + + config: + - name: "collab-config.yml" + template: "config-template.yml" + description: "Collaboration extension configuration" + required: false + +hooks: + after_tasks: + command: speckit.spex-collab.reviewers + optional: false + description: "Generate REVIEWERS.md after task generation" + before_implement: + command: speckit.spex-collab.phase-split + optional: true + prompt: "Review PR split for implementation phases?" + description: "Present phase split proposal before implementation" + +tags: + - "spex" + - "collaboration" + - "review" + - "pr" diff --git a/.specify/extensions/spex-collab/templates/reviewers-template.md b/.specify/extensions/spex-collab/templates/reviewers-template.md new file mode 100644 index 00000000..a0ede26e --- /dev/null +++ b/.specify/extensions/spex-collab/templates/reviewers-template.md @@ -0,0 +1,84 @@ +# Review Guide: [Feature Name] + +**Generated**: YYYY-MM-DD | **Spec**: [spec.md](spec.md) + +## Why This Change + +[The problem being solved: what's broken, painful, or missing today. +Written so a reviewer who has NOT read the spec understands the +motivation in 30 seconds.] + +## What Changes + +[One paragraph summary of the solution at the outcome level: what +gets added, removed, or restructured. Stay at the "what does the +user/system gain" level. Mention breaking changes upfront if any. +Do NOT describe implementation details here.] + +## How It Works + +[Implementation approach from plan.md: architecture, key modules, +data flow, integration points. This is where technical details +belong. Keep it concise but specific enough that a reviewer +understands the implementation strategy without reading plan.md.] + +## When It Applies + +[Reframe scope as applicability. More natural than in/out lists +for a reviewer scanning the PR.] + +**Applies when**: +- [conditions, contexts, or scenarios where this feature is active] + +**Does not apply when**: +- [explicit exclusions with brief rationale for deferral] + +## Key Decisions + +1. [Numbered list of the most significant design choices. For each: + what was decided, what alternatives were considered, why this + approach was chosen.] + +## Areas Needing Attention + +[Points where reasonable engineers might disagree. Flag trade-offs, +assumptions that could be wrong, patterns that deviate from project +conventions, complexity concerns.] + +## Open Questions + +[Remaining ambiguities or deferred decisions. If none, state +"No open questions identified."] + +## Review Checklist + +- [ ] Key decisions are justified +- [ ] Breaking changes are documented with migration guidance +- [ ] Scope matches the stated boundaries +- [ ] Success criteria are achievable +- [ ] No unstated assumptions + +--- + + + + + diff --git a/.specify/extensions/spex-deep-review/commands/speckit.spex-deep-review.run.md b/.specify/extensions/spex-deep-review/commands/speckit.spex-deep-review.run.md new file mode 100644 index 00000000..2dbd9d3d --- /dev/null +++ b/.specify/extensions/spex-deep-review/commands/speckit.spex-deep-review.run.md @@ -0,0 +1,862 @@ +--- +name: speckit.spex-deep-review.review +description: Multi-perspective code review with autonomous fix loop - dispatches 5 specialized review agents, merges findings, auto-fixes Critical/Important issues +--- + +# Deep Review: Multi-Perspective Code Review + +## Overview + +This command orchestrates a multi-perspective code review using five specialized review agents. Each agent analyzes code from a distinct angle (correctness, architecture, security, production readiness, test quality). Findings are merged, deduplicated, and classified by severity. Critical and Important findings trigger an autonomous fix loop (up to 3 rounds). Results are documented in `review-findings.md`. + +**This command is invoked by `speckit-spex-gates-review-code` when the deep-review extension is enabled, or via the `after_implement` hook.** + +## Prerequisites + +The caller (review-code or ship) may provide these values. When not provided, the deep-review command resolves them itself: + +1. **Stage 1 result**: spec compliance score (or null if no spec) +2. **Invocation context**: `superpowers` or `manual` +3. **Hint text**: optional focus area from user (or null) +4. **External tool settings**: `{coderabbit: true/false, copilot: true/false}` (see resolution below) +5. **Spec path**: path to spec.md (or null, see Spec Resolution below) +6. **Feature directory**: path to the spec directory for artifact output + +### Spec Resolution + +If the caller does not provide a spec path, attempt branch-based resolution: + +```bash +.specify/scripts/bash/check-prerequisites.sh --json --paths-only 2>/dev/null +``` + +If this succeeds (outputs JSON with `FEATURE_SPEC`), use the resolved spec path and feature directory. If this fails (not on a feature branch, no matching spec directory), proceed without a spec (spec compliance checks will be skipped). + +### External Tool Settings Resolution + +If external tool settings are provided by the caller, use them directly. If not (e.g., when invoked directly by `speckit-spex-ship` or manually), resolve from config: + +```bash +# Read config defaults from deep-review extension config (all default to true if key is missing) +DEEP_REVIEW_CONFIG=".specify/extensions/spex-deep-review/deep-review-config.yml" +DEFAULT_CODERABBIT=$(yq -r '.external_tools.coderabbit // true' "$DEEP_REVIEW_CONFIG" 2>/dev/null) +DEFAULT_COPILOT=$(yq -r '.external_tools.copilot // true' "$DEEP_REVIEW_CONFIG" 2>/dev/null) +``` + +``` +Resolution: + coderabbit = DEFAULT_CODERABBIT + copilot = DEFAULT_COPILOT +``` + +This ensures CodeRabbit and Copilot are enabled by default regardless of how deep-review is invoked. + +## Orchestration Flow + +### Step 1: Determine Changed Files + +Identify the files to review: + +```bash +# Get the main branch name +MAIN_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@' || echo "main") + +# Files changed between main branch and HEAD +git diff --name-only "${MAIN_BRANCH}...HEAD" 2>/dev/null + +# Uncommitted changes (staged + unstaged) +git diff --name-only HEAD 2>/dev/null +git diff --name-only --cached 2>/dev/null +``` + +Combine all results into a deduplicated list. Filter to only source code files (exclude binary, images, lock files). **Exclude files under `specs/` and `brainstorm/`** as these are spec artifacts, not implementation code. + +**For re-review rounds (fix loop):** narrow scope to only files modified by the most recent fix round: +```bash +# Files changed since last staging +git diff --name-only --cached 2>/dev/null +``` + +### Step 2: Detect External Tools + +Check for external review CLIs, respecting the external tool settings from the caller: + +```bash +# CodeRabbit (skip only if explicitly disabled in config) +which coderabbit >/dev/null 2>&1 && echo "CODERABBIT_AVAILABLE=true" + +# GitHub Copilot CLI (skip if copilot setting is false) +which copilot >/dev/null 2>&1 && echo "COPILOT_AVAILABLE=true" +``` + +**External tool resolution:** +1. Use the external tool settings from Prerequisites (either caller-provided or self-resolved from config) +2. **CodeRabbit is enabled by default.** Only skip if the config explicitly sets `coderabbit: false` +3. If `copilot` is `false`, skip Copilot detection entirely +4. If a tool is enabled in settings but not installed, proceed silently without it +5. **When CodeRabbit is available and enabled, it MUST be invoked.** Do not skip it for performance or convenience reasons. CodeRabbit provides external validation that complements the internal review agents. + +### Step 3: Dispatch Review Agents + +**Check for teams extension:** + +Read `.specify/extensions/.registry` and check if `spex-teams` extension is enabled (query: `.extensions["spex-teams"].enabled`). + +**Sequential mode** (teams NOT enabled): +- Dispatch each agent one at a time using the Agent tool +- Each agent gets a fresh, isolated context (no session history) +- Report progress after each agent completes: + ``` + Agent 1/5: Correctness... done, N findings + Agent 2/5: Architecture & Idioms... done, N findings + ... + ``` + +**Parallel mode** (teams IS enabled): +- Dispatch all 5 agents in a single message using multiple Agent tool calls +- Each agent runs in isolated context +- Report progress as each agent completes: + ``` + Agent completed: Security... 2 findings + Agent completed: Test Quality... 0 findings + ... + ``` + +**For each agent dispatch**, use the Agent tool with: +- `subagent_type: "general-purpose"` +- The full agent prompt (from the Agent Prompts section below) +- Include the list of changed files and their contents +- **Include the spec text** (spec.md content, if available). Agents need the spec to check code behavior against requirements. Without it, they can only find code-level issues, not spec compliance gaps. +- Include the hint text (if provided) as additional review focus + +### Step 4: Dispatch External Tools (if available) + +**CodeRabbit** (if available): + +**IMPORTANT: CodeRabbit MUST be run when the CLI is installed and the config allows it. Do NOT skip it. CodeRabbit findings are high-value external validation and MUST be included in the fix loop alongside internal agent findings.** + +First, build the file list excluding spec artifacts: +```bash +# Get changed files, excluding specs/ and brainstorm/ directories +REVIEW_FILES=$(git diff --name-only "${MAIN_BRANCH}...HEAD" 2>/dev/null | grep -v -E '^(specs/|brainstorm/)' | sort -u) +``` + +Then invoke CodeRabbit with the explicit file list: +```bash +# Initial review (Stage 2): review changed source files only +coderabbit review --agent --no-color --files $REVIEW_FILES 2>&1 + +# Fix loop re-review rounds: review only the files that were modified by fixes +coderabbit review --agent --type uncommitted --no-color 2>&1 +``` + +The `--agent` flag produces structured, detailed findings with rationale (preferred over `--prompt-only` which only shows prompts). The `--files` flag ensures spec artifacts under `specs/` and `brainstorm/` are never reviewed. + +Parse output: +1. Check for "Review completed" (no issues found) +2. Split on `=============` delimiters +3. For each block: extract file, line, severity keyword, description, and **rationale/explanation** +4. Map severity: critical -> Critical, major -> Important, minor -> Minor +5. Set category = "external", source_agent = "coderabbit", confidence = 75 +6. **Preserve the full rationale** from CodeRabbit output for inclusion in review-findings.md +7. **All CodeRabbit findings with severity Critical or Important MUST enter the fix loop** (Step 7). They are treated identically to internal agent findings for gate and fix purposes. + +**Copilot CLI** (if available): +```bash +copilot -s -p "Review the following git diff for bugs, security issues, and code quality problems. Output ONLY a structured list of findings. For each finding use this exact format: + +### FINDING +- Severity: Critical|Important|Minor +- File: +- Line: +- Description: + +End each finding with --- + +$(git diff HEAD)" 2>&1 +``` +Parse output: +1. Split on "### FINDING" markers +2. For each block: extract Severity, File, Line, Description fields +3. **Discard findings for files under `specs/`** (spec artifacts are not code to review) +4. Set category = "external", source_agent = "copilot", confidence = 75 + +**Error handling for external tools:** +If a tool times out, crashes, or returns an error: +- Log the failure (tool name, error reason) for inclusion in review-findings.md +- Continue with findings from internal agents and any other working tools +- Do NOT block the review + +### Step 5: Merge and Deduplicate Findings + +1. Collect all findings from internal agents and external tools +2. Normalize to common schema: + ``` + { + id: "FINDING-N", + severity: Critical|Important|Minor, + confidence: 0-100, + file: "relative/path", + line_start: N, + line_end: N, + category: correctness|architecture|security|production-readiness|test-quality|external, + description: "what is wrong", + rationale: "why it matters", + fix: "how to fix it", + source_agent: "agent-name", + also_reported_by: [], + external_rationale: "full rationale from external tool (CodeRabbit/Copilot), or null", + resolution: "pending", + round_found: N + } + ``` +3. Sort by file path, then line number +4. Deduplicate: for each pair of findings where: + - Same file path AND + - Overlapping line ranges (A.line_start <= B.line_end AND B.line_start <= A.line_end) AND + - Same category + Then: keep the finding with the longer description, add the other's source_agent to `also_reported_by`, use the higher severity and confidence +5. Assign sequential IDs to merged findings + +### Step 6: Gate Check + +Count findings by severity: +- **Critical count**: findings with severity = Critical +- **Important count**: findings with severity = Important +- **Minor count**: findings with severity = Minor + +**Gate logic:** +- If Critical + Important = 0: **GATE PASS** +- If Critical + Important > 0: proceed to fix loop (or fail if max rounds reached) + +### Step 7: Autonomous Fix Loop + +**Maximum 3 rounds.** + +For each round: +1. Report: `Fix round N/3: N Critical + N Important findings to address` +2. Collect all Critical and Important findings, sorted by file +3. For each file with findings, sort findings by line number **descending** (reverse order to prevent line shifts) +4. Apply each fix: + - Read the file + - Apply the fix suggestion at the specified location + - The main conversation agent performs fixes (not review agents) +5. Stage all changes: `git add ` +6. Report: `Fix round N/3: applied N fixes, re-reviewing...` +7. Re-dispatch review agents on **only the modified files** (narrowed scope) +8. Merge new findings with existing Minor findings +9. Gate check: + - If Critical + Important = 0: **GATE PASS**, exit loop + - If round < 3: continue to next round + - If round = 3: **GATE FAIL**, exit loop + +**No user approval needed.** Fixes are applied autonomously. The user reviews all accumulated changes after the loop completes via `git diff`. + +### Step 7b: Post-Fix Spec Compliance Check + +**This step is MANDATORY when the fix loop removed code (deleted lines, removed functions, or deleted files).** Code removal is the operation most likely to silently drop a spec requirement. Edits and additions don't carry the same risk. + +After the fix loop completes (regardless of PASS or FAIL), check if any fix round removed code: + +```bash +# Check if any fix round deleted lines +REMOVED_LINES=$(git diff --stat HEAD~1 2>/dev/null | grep -oE '[0-9]+ deletion' | head -1) +``` + +If code was removed AND a spec is available: + +1. Read the spec's functional requirements (all FR-NNN entries or requirement bullet points) +2. For each functional requirement, verify that at least one code path still implements it: + - Search for key terms, function names, or class names associated with the requirement + - Check that the implementation file still exists + - Verify the function/method body is not empty or a stub +3. Build a coverage check: + +``` +Post-fix spec coverage: + FR-001: parse JSONL events → agent_eval/events.py:parse_events() ✓ + FR-002: event type discriminator → agent_eval/events.py:EventType ✓ + ... + FR-009: tool result from user msgs → MISSING (removed in fix round 1) ✗ +``` + +4. If any FR is MISSING or STUB: + - Add a new Critical finding for each: `"Spec requirement FR-NNN dropped during fix loop: [requirement text]"` + - If the fix loop has remaining rounds (< 3), run another fix round to re-implement the dropped requirements + - If max rounds reached, report the dropped requirements as Critical findings in the gate outcome + +5. Update the gate outcome: + - If dropped requirements were re-implemented successfully: maintain GATE PASS + - If dropped requirements remain: **GATE FAIL** (spec coverage gaps override code quality PASS) + +If no code was removed, or no spec is available, skip this step. + +### Step 8: Write review-findings.md + +Write `specs//review-findings.md` (overwrite if exists): + +```markdown +# Deep Review Findings + +**Date:** YYYY-MM-DD +**Branch:** branch-name +**Rounds:** N +**Gate Outcome:** PASS|FAIL +**Invocation:** superpowers|manual + +## Summary + +| Severity | Found | Fixed | Remaining | +|----------|-------|-------|-----------| +| Critical | N | N | N | +| Important | N | N | N | +| Minor | N | - | N | +| **Total** | **N** | **N** | **N** | + +**Agents completed:** 5/5 (+ N external tools) +**Agents failed:** [list if any] + +## Findings + +### FINDING-1 +- **Severity:** Critical +- **Confidence:** 85 +- **File:** path/to/file.go:142-148 +- **Category:** correctness +- **Source:** correctness-agent (also reported by: coderabbit) +- **Round found:** 1 +- **Resolution:** fixed (round 1) + +**What is wrong:** +[Describe the issue clearly. What specific code pattern, logic error, or +vulnerability was found? Include the relevant code snippet if it helps +understanding.] + +**Why this matters:** +[Explain the impact. What could go wrong if this is not fixed? Is it a +runtime error, data corruption risk, security exposure, or maintenance +burden? Be specific about the failure scenario.] + +**How it was resolved:** +[If fixed: explain what was changed and why this fix is correct. +If remaining: explain what needs to happen to resolve it.] + +[If CodeRabbit or Copilot reported this finding, include their analysis:] + +**External tool analysis (CodeRabbit):** +> [Preserve the full rationale from CodeRabbit's output. This gives +> reviewers the external AI's perspective, which may differ from or +> complement the internal agent's analysis.] + +### FINDING-2 +[Same structure. Every finding gets the full treatment.] + +... + +## Post-Fix Spec Coverage + +[If Step 7b ran, include the coverage check results:] + +| Requirement | Implementation | Status | +|-------------|---------------|--------| +| FR-001: ... | file.py:func() | ✓ | +| FR-009: ... | MISSING (removed in fix round 1) | ✗ | + +[If all FRs covered: "All spec requirements verified after fix loop."] +[If any dropped: "N spec requirements dropped during fix loop and flagged as Critical findings."] + +## Remaining Findings + +[If gate failed, list unresolved findings here with the same detailed +format. Explain why they could not be auto-fixed and what human action +is needed.] +``` + +### Step 9: Report Gate Outcome with Agent Summary + +After writing `review-findings.md`, output a tabular console summary showing what each agent found, what was fixed, and the gate outcome. This is the primary output the user sees. + +**Always output this summary to the console:** + +``` +Deep review completed. + +Gate: PASS|FAIL (after fix round N) + +Review Agents: + +| Agent | Found | Fixed | Remaining | Status | +|-------------------------|-------|-------|-----------|-----------| +| Correctness | N | N | N | completed | +| Architecture & Idioms | N | N | N | completed | +| Security | N | N | N | completed | +| Production Readiness | N | N | N | completed | +| Test Quality | N | N | N | completed | +| CodeRabbit (external) | N | N | N | completed/skipped/failed | +| Copilot (external) | N | N | N | completed/skipped/failed | +|-------------------------|-------|-------|-----------|-----------| +| Total | N | N | N | | + +Key fixes applied: + 1. [Brief description of fix] (agent-name) + 2. [Brief description of fix] (agent-name) + ... + +Remaining findings (N Important): + - [Finding summary] (agent-name, file:line) + ... + +Post-fix spec coverage: N/N requirements verified [✓ all covered | ✗ N dropped] + +Details: review-findings.md +``` + +**Constraints:** +- Always include the agent table, even if some agents found nothing (show 0) +- Include external tools in the table even if skipped (show "skipped" with reason in Status) +- "Key fixes applied" lists up to 10 most significant fixes, grouped by theme +- "Remaining findings" lists only Critical and Important severity items +- If gate PASSED with zero remaining: omit the "Remaining findings" section +- If CodeRabbit was skipped: show reason (e.g., "skipped (CLI not installed)" or "skipped (disabled in config)") + +--- + +## Finding Output Schema + +Each review agent MUST return findings in this exact format: + +```markdown +## Findings + +### FINDING-1 +- **Severity**: Critical|Important|Minor +- **Confidence**: 0-100 +- **File**: relative/path/to/file.ext +- **Lines**: start-end +- **Category**: [agent's category] +- **Description**: [what is wrong] +- **Rationale**: [why it matters, with evidence from the code] +- **Fix**: [concrete fix suggestion with code if applicable] + +### FINDING-2 +... + +## Self-Verification +- [ ] Each finding has file:line evidence from actual code I read +- [ ] No findings invented for code that is actually clean +- [ ] No duplicate findings (same issue reported twice) +- [ ] Confidence scores reflect my actual certainty, not padding +- [ ] If I found zero issues, I re-read the code a second time to confirm +- [ ] Every finding includes a concrete, implementable fix +- [ ] If a spec was provided, I checked my findings against specific FR/NFR requirements +- [ ] I checked boundary/last-iteration behavior in all loops and retry logic +``` + +If an agent finds no issues after careful review, it MUST return: + +```markdown +## Findings + +No issues found. Code was reviewed twice to confirm. + +## Self-Verification +- [x] Re-read code a second time after initial zero-finding pass +- [x] Confirmed no issues in my focus area +``` + +--- + +## Agent Prompts + +### Common Preamble (included in every agent prompt) + +The following instructions are prepended to every agent's prompt: + +``` +IMPORTANT INSTRUCTIONS - READ BEFORE REVIEWING: + +1. ANTI-SYCOPHANCY: Do NOT start with praise. Do NOT say "Great implementation!", + "Nice work!", or any positive affirmation. Start directly with your findings. + Zero findings is a red flag - if you find nothing, re-read the entire codebase + a second time before confirming zero findings. + +2. DISTRUST: Do NOT trust the implementer's report, comments, or commit messages. + Verify EVERYTHING by reading the actual code. Comments may be wrong. Variable + names may be misleading. Test names may not match what they test. + + ISOLATION: Do NOT read git log, commit messages, brainstorm documents, or + plan.md/tasks.md. These reveal implementation intent and bias your review. + Review the CODE and the SPEC only. Judge what was built, not what was intended. + +3. DO NOT trust test results as proof of correctness. Read the actual assertions. + A passing test with weak assertions proves nothing. Verify what is actually + being tested, not what the test name claims. + +4. FAILURE MODES - You MUST NOT: + - Inflate nits to fill a quota. If the code is clean in your area, say so. + - Invent issues that don't exist. Every finding must cite specific code. + - Repeat the same finding in different words. + - Report issues outside your designated scope (see your role gate below). + +5. CONFIDENCE SCORING: Rate every finding 0-100. + - Only report findings with confidence >= 70 + - EXCEPTION: Critical findings may be reported at confidence >= 50 + - Be honest about uncertainty. 60% confidence on a real issue is better + than 95% confidence on a manufactured one. + +6. EVERY FINDING MUST INCLUDE: + - File path and line number(s) + - What is wrong (specific, not vague) + - Why it matters (impact if not fixed) + - How to fix it (concrete suggestion, not "consider improving") + +7. LANGUAGE AWARENESS: Adapt your checklist based on the programming languages + detected in the changed files. For mixed-language changes, apply language-specific + checks for each language present. + +8. OUTPUT FORMAT: Use the Finding Output Schema exactly as specified. Do not + deviate from the format. Your output will be parsed programmatically. + +9. SPEC AWARENESS: If a spec (spec.md) is provided, cross-check the code + against specific requirements. For each functional requirement (FR-NNN), + verify the code implements exactly what the spec says, not more, not less. + Flag mismatches as findings. Common spec compliance gaps: + - Code handles a broader or narrower set of cases than the spec defines + - Metrics or observability the spec requires but code doesn't expose + - Error codes or status codes that differ from the spec + - Behavioral differences on edge cases (last iteration, empty input, etc.) +``` + +### Agent 1: Correctness + +``` +You are the CORRECTNESS REVIEW AGENT. + +YOUR ROLE: You ARE responsible for finding bugs, logic errors, and correctness issues. +YOUR SCOPE: Mutation safety, shared references, logic errors, resource cleanup, +error path correctness, off-by-one errors, null/nil handling, type confusion. + +YOU ARE NOT RESPONSIBLE FOR: Code style, naming conventions, documentation quality, +performance optimization, test coverage, security vulnerabilities, or architecture +decisions. Those belong to other agents. Stay in your lane. + +CHECKLIST - Check each item against the code: + +For all languages: +- [ ] Shared mutable state: Are references copied before mutation? Are slices/arrays + cloned before passing to goroutines/threads/async functions? +- [ ] Error paths: Do all error returns clean up resources (close files, release locks, + cancel contexts)? Are errors propagated correctly (not silently swallowed)? +- [ ] Logic errors: Are conditions correct (not inverted)? Are loops bounded? + Are edge cases handled (empty input, single element, max values)? +- [ ] Null/nil safety: Can any dereference panic? Are optional values checked + before use? Are map lookups verified? +- [ ] Resource lifecycle: Are all opened resources (files, connections, channels) + properly closed? In the right order? In defer/finally blocks? +- [ ] Concurrency: Are shared variables protected? Can race conditions occur? + Are channels properly drained on cancellation? +- [ ] Last-iteration behavior: In loops with retries, attempts, or pagination, + does the final iteration behave correctly? Common bugs: sleeping after the + last attempt, returning a generic error instead of the original, off-by-one + in attempt counting, unnecessary work on the last pass. +- [ ] Boundary correctness: Does the code match the spec's exact boundaries? + If the spec says "retry on 502/503/504", does the code retry exactly + those, not all 5xx? If the spec says "max 3 attempts", is it 3 not 4? + +For Go specifically: +- [ ] Slice append in loops: Does `append` modify a shared backing array? +- [ ] Goroutine variable capture: Are loop variables captured by value, not reference? +- [ ] Context cancellation: Is context.Cancel() called in defer? +- [ ] Error wrapping: Are errors wrapped with %w for proper unwrapping? + +For Python specifically: +- [ ] Mutable default arguments: Are lists/dicts used as default parameters? +- [ ] Iterator exhaustion: Are generators consumed only once when multiple reads needed? +- [ ] Exception handling: Are bare `except:` clauses catching too broadly? + +For JavaScript/TypeScript specifically: +- [ ] Async/await: Are promises properly awaited? Can unhandled rejections occur? +- [ ] Closure variable capture: Are `var` variables captured in closures inside loops? +- [ ] Type narrowing: After type guards, is the narrowed type used correctly? + +For Bash specifically: +- [ ] Unquoted variables: Can word splitting cause unexpected behavior? +- [ ] Exit codes: Are command failures checked? Is `set -e` or explicit checks used? +- [ ] Subshell variable scope: Are variables set in subshells expected in parent? +``` + +### Agent 2: Architecture & Idioms + +``` +You are the ARCHITECTURE & IDIOMS REVIEW AGENT. + +YOUR ROLE: You ARE responsible for finding structural issues, code smells, and +maintainability problems. +YOUR SCOPE: Dead code, unnecessary complexity, duplication that will diverge, +misleading naming, comment accuracy, abstraction problems, YAGNI violations. + +YOU ARE NOT RESPONSIBLE FOR: Bug detection, security analysis, performance profiling, +test coverage, or production operations concerns. Those belong to other agents. + +CHECKLIST - Check each item against the code: + +- [ ] Dead code: Are there functions, methods, variables, imports, or branches + that are never called/used? Are there commented-out code blocks that should + be deleted (git history preserves them)? +- [ ] Unnecessary complexity: Are there abstractions with only one implementation? + Interfaces with one implementor? Factories that construct one type? + Wrapper functions that add no value? +- [ ] Duplication: Is there copy-pasted code that will diverge as the codebase + evolves? (Three similar lines are fine; three similar 20-line blocks are not.) + Note: intentional duplication for clarity is acceptable. Flag only duplication + that will become a maintenance burden. +- [ ] Misleading naming: Do function/variable names accurately describe what they + do? Are there names that suggest one behavior but implement another? + Are boolean variables named as questions (isReady, hasPermission)? +- [ ] Comment accuracy: Do comments match the code they describe? Are there + TODO/FIXME comments that should be addressed or tracked? Are there comments + that explain "what" (redundant) instead of "why" (valuable)? +- [ ] Abstraction level: Are functions doing work at mixed abstraction levels + (high-level orchestration mixed with low-level byte manipulation)? +- [ ] YAGNI: Is there code written for hypothetical future requirements that + are not in the current spec? Speculative generality? +- [ ] Convention adherence: Does the new code follow the patterns established + in the existing codebase? Or does it introduce a new pattern where one + already exists? +- [ ] State machine completeness: For any state machine (circuit breaker, + retry state, connection lifecycle, etc.), are ALL transitions covered? + Check both success and failure paths. Every transition should be + observable (logged, metriced, or tested). Missing transitions on the + success path are a common blind spot. +- [ ] Observability completeness: If the spec requires specific metrics, + counters, or log entries, verify they are actually exposed. Check that + every metric the spec names has a corresponding implementation, not + just the ones on the error path. +``` + +### Agent 3: Security + +``` +You are the SECURITY REVIEW AGENT. + +YOUR ROLE: You ARE responsible for finding security vulnerabilities and unsafe patterns. +YOUR SCOPE: Input validation, injection risks, secret handling, authentication/ +authorization patterns, RBAC scope, CRD/CEL validation gaps, cryptographic misuse. + +YOU ARE NOT RESPONSIBLE FOR: Code correctness, architecture decisions, performance, +test quality, or code style. Those belong to other agents. + +CHECKLIST - Check each item against the code: + +- [ ] Input validation: Is all external input (user input, API parameters, file + content, environment variables) validated before use? Are validation rules + applied at the boundary, not deep in business logic? +- [ ] Injection: Can any user-controlled string reach SQL queries, shell commands, + template rendering, or HTML output without sanitization? Check for string + concatenation in queries/commands. +- [ ] Secret handling: Are secrets (API keys, passwords, tokens) hardcoded in + source? Are they logged? Exposed in error messages? Stored in plaintext? + Committed to version control? +- [ ] Authentication: Are auth checks present on all protected endpoints/operations? + Can any operation bypass auth by manipulating parameters or headers? +- [ ] Authorization: After auth, are permission checks correct? Can a user access + resources belonging to another user? Are RBAC roles properly scoped? +- [ ] Path traversal: Can user input manipulate file paths to access files outside + intended directories? Are relative paths (../) blocked? +- [ ] Deserialization: Is untrusted data deserialized without validation? Can + deserialization trigger code execution? +- [ ] Rate limiting: Are endpoints that accept user input protected against abuse? + Login endpoints? API endpoints? File upload endpoints? + +For Kubernetes/Operator code specifically: +- [ ] CRD validation: Are all user-provided fields in Custom Resources validated + via CEL expressions or webhook validation? Can a malicious CR crash the + operator or escalate privileges? +- [ ] RBAC scope: Are operator permissions minimal? Does the operator request + cluster-wide permissions when namespace-scoped would suffice? +- [ ] Webhook security: Are admission webhooks configured with proper failure + policies? Can webhook bypass allow invalid resources? + +For web applications specifically: +- [ ] XSS: Is user content HTML-escaped before rendering? Are CSP headers set? +- [ ] CSRF: Are state-changing operations protected with CSRF tokens? +- [ ] CORS: Are allowed origins properly restricted? +``` + +### Agent 4: Production Readiness + +``` +You are the PRODUCTION READINESS REVIEW AGENT. + +YOUR ROLE: You ARE responsible for finding issues that would cause problems in +production environments. +YOUR SCOPE: Performance implications, resource leaks, concurrency issues, memory +patterns, operator patterns, observability gaps, graceful shutdown. + +YOU ARE NOT RESPONSIBLE FOR: Functional correctness, security vulnerabilities, +code style, test coverage, or architecture decisions. Those belong to other agents. + +CHECKLIST - Check each item against the code: + +- [ ] Resource leaks: Are goroutines/threads properly terminated on shutdown? + Are channels closed? Are database connections returned to pools? + Are HTTP response bodies closed after reading? +- [ ] Unbounded growth: Are there maps, slices, channels, or queues that can + grow without limit? Is there backpressure or eviction? Can a burst of + input exhaust memory? +- [ ] Concurrency safety: Are critical sections (mutex-protected regions) + kept small? Can deadlocks occur from lock ordering? Are there + time-of-check-time-of-use (TOCTOU) races? +- [ ] Error amplification: Can a single failure cascade into widespread outage? + Are circuit breakers or retry limits in place? Is there exponential backoff + for retries? +- [ ] Graceful shutdown: Does the service handle SIGTERM properly? Are in-flight + requests completed before exit? Are background workers stopped cleanly? +- [ ] Observability: Are critical operations logged with structured context? + Are errors logged with enough detail to diagnose without reproducing? + Are metrics exposed for key operations (latency, error rate, queue depth)? + +For Go specifically: +- [ ] Goroutine leaks: Can goroutines outlive their parent context? Are they + always cancelled/terminated? Use `runtime.NumGoroutine()` awareness. +- [ ] Channel patterns: Are unbuffered channels used where buffered would prevent + blocking? Are channels properly drained on context cancellation? +- [ ] Slice retention: Are large slices retained in memory because a small sub-slice + still references the backing array? Use `copy()` to release. +- [ ] sync.Pool misuse: Are pooled objects properly reset before returning to pool? + Can pool objects leak state between uses? + +For Kubernetes Operator code specifically: +- [ ] Reconciler concurrency: Is MaxConcurrentReconciles set appropriately? + Can concurrent reconciles conflict on the same resource? +- [ ] Work queue depth: Can the work queue grow unbounded under load? + Is rate limiting configured? +- [ ] Status update storms: Can status updates trigger re-reconciliation loops? + Are status updates debounced or conditional? +- [ ] Finalizer safety: Are finalizers removed only after cleanup is verified? + Can a stuck finalizer block resource deletion indefinitely? +``` + +### Agent 5: Test Quality + +``` +You are the TEST QUALITY REVIEW AGENT. + +YOUR ROLE: You ARE responsible for evaluating test effectiveness and finding +testing gaps. +YOUR SCOPE: Coverage gaps, weak assertions, tests passing for wrong reasons, +missing edge case tests, missing regression tests, test isolation. + +YOU ARE NOT RESPONSIBLE FOR: Code correctness, security, performance, architecture, +or code style. Those belong to other agents. + +IMPORTANT: Do NOT trust test names. Read the actual assertions. A test named +"TestUserCreation" that only checks the HTTP status code is not testing user creation. + +CHECKLIST - Check each item against the code: + +- [ ] Coverage gaps: Are there code paths with no test coverage? Focus on: + error paths, edge cases, boundary conditions, and branching logic. + Look for functions/methods that have no corresponding test. +- [ ] Weak assertions: Do tests actually verify the expected behavior? + Watch for: checking only status codes (not response bodies), checking + only that "no error occurred" (not what the result contains), checking + only array length (not array contents). +- [ ] Wrong-reason passes: Can any test pass even if the code is broken? + Tests that mock too aggressively, tests that check implementation details + rather than behavior, tests that verify the test setup rather than the + code under test. +- [ ] Empty test stubs: Are there test functions with no assertions, only + setup code, or just `t.Skip()`/`t.Pending()`? These give false coverage + and hide untested code paths. An empty test is worse than no test. +- [ ] Missing edge cases: Based on the implementation, what edge cases should + be tested? Empty input, nil/null values, maximum values, concurrent + access, timeout scenarios, malformed input. +- [ ] Missing regression tests: If the code fixes a specific bug or addresses + a specific requirement, is there a test that would catch a regression? +- [ ] Test isolation: Do tests depend on each other's execution order? + Do tests share mutable state? Can running tests in parallel cause + flaky failures? +- [ ] Test naming: Do test names describe the scenario being tested, or are + they generic (Test1, Test2, TestHandler)? +- [ ] Fixture management: Are test fixtures (setup/teardown) clean? Can + leftover state from a failed test affect subsequent tests? +``` + +--- + +## Hint Injection + +When the user provides hint text via `/speckit-spex-gates-review-code `, append this section to each agent's prompt: + +``` +## Additional Review Focus (User Hint) + +The user has requested special attention to: "" + +Apply this focus IN ADDITION TO your standard checklist. Do not replace your +standard checks. Instead, weight findings related to this area more heavily +and look for issues you might otherwise consider borderline. +``` + +--- + +## Progress Reporting + +Throughout the review, output progress updates to keep the user informed: + +``` +Stage 1: Spec compliance... [score]% [PASS|FAIL] +Stage 2: Multi-perspective review (N changed files) + Agent 1/5: Correctness... done, N findings + Agent 2/5: Architecture & Idioms... done, N findings + Agent 3/5: Security... done, N findings + Agent 4/5: Production Readiness... done, N findings + Agent 5/5: Test Quality... done, N findings + [CodeRabbit... done, N findings] (if available) + [Copilot... done, N findings] (if available) + +Merging findings: N total, N after dedup (N Critical, N Important, N Minor) +[Fix round 1/3: addressing N Critical + N Important findings...] +[Fix round 1/3: applied N fixes, re-reviewing...] +Gate: PASS|FAIL +``` + +In parallel mode (teams extension), agents complete in non-deterministic order. Report each as it finishes. + +--- + +## Gate Behavior + +The gate outcome depends on the invocation context: + +**Superpowers context** (triggered as quality gate from `/speckit-implement`): +- **PASS**: Allow proceeding to `speckit-spex-finish` +- **FAIL**: Block completion. The user must resolve remaining findings before the implementation can proceed. + +**Manual context** (user runs `/speckit-spex-gates-review-code` directly): +- **PASS** or **FAIL**: Advisory only. Report findings and let the user decide. Do NOT block further commands. + +The invocation context is determined by the caller. When invoked from the superpowers quality gate in `speckit-implement`, the context is `superpowers`. When invoked directly, the context is `manual`. + +## Update Flow State + +**MANDATORY: Update flow state.** This MUST run after deep review completes (regardless of gate outcome). Deep review completing means the code review phase is done, even if findings remain. Use the flow state script: + +```bash +FLOW_STATE="$(find ~/.claude -name 'spex-flow-state.sh' 2>/dev/null | head -1)" && [ -x "$FLOW_STATE" ] && "$FLOW_STATE" gate review-code && "$FLOW_STATE" implemented +``` + +This ensures the status line shows `R ✓` after deep review finishes, since review-code delegates to deep review and its own final state update may not execute. + +## Next Steps (tell the user) + +After deep review passes, tell the user: + +``` +Deep review complete. To close out this feature: + 1. /clear (free context for final gate) + 2. /speckit-spex-finish (verify + merge/PR, all-in-one) +``` + +This prompt is mandatory on every PASS exit. The user needs to know how to finalize. diff --git a/.specify/extensions/spex-deep-review/config-template.yml b/.specify/extensions/spex-deep-review/config-template.yml new file mode 100644 index 00000000..1f2a96cc --- /dev/null +++ b/.specify/extensions/spex-deep-review/config-template.yml @@ -0,0 +1,4 @@ +# Deep Review Extension Configuration +external_tools: + coderabbit: true + copilot: false diff --git a/.specify/extensions/spex-deep-review/extension.yml b/.specify/extensions/spex-deep-review/extension.yml new file mode 100644 index 00000000..21bc66b5 --- /dev/null +++ b/.specify/extensions/spex-deep-review/extension.yml @@ -0,0 +1,39 @@ +schema_version: "1.0" + +extension: + id: spex-deep-review + name: "Spex Deep Review" + version: "1.0.0" + description: "Multi-perspective code review with 5 specialized agents and autonomous fix loop" + author: cc-spex + license: MIT + +requires: + speckit_version: ">=0.5.2" + extensions: + - id: spex-gates + version: ">=1.0.0" + +provides: + commands: + - name: speckit.spex-deep-review.run + file: commands/speckit.spex-deep-review.run.md + description: "Multi-perspective code review with autonomous fix loop" + + config: + - name: "deep-review-config.yml" + template: "config-template.yml" + description: "Deep review extension configuration" + required: false + +hooks: + after_implement: + command: speckit.spex-deep-review.run + optional: true + prompt: "Run deep multi-perspective review?" + description: "Multi-agent code review after implementation" + +tags: + - "spex" + - "review" + - "agents" diff --git a/.specify/extensions/spex-gates/commands/speckit.spex-gates.review-code.md b/.specify/extensions/spex-gates/commands/speckit.spex-gates.review-code.md new file mode 100644 index 00000000..af8de16d --- /dev/null +++ b/.specify/extensions/spex-gates/commands/speckit.spex-gates.review-code.md @@ -0,0 +1,419 @@ +--- +description: "Review code against spec compliance with deviation tracking and evolution triggers" +--- + +# Code Review Against Specification + +## Ship Pipeline Guard + +If `.specify/.spex-state` exists and its `status` is `running`, this command is part of an autonomous pipeline. Check the `ask` field: +- If `ask` is `"smart"` or `"never"`: suppress all user prompts (do NOT use AskUserQuestion), complete the review autonomously, and return immediately so the pipeline can advance. +- If `ask` is `"always"`: prompt the user as normal. + +```bash +if [ -f ".specify/.spex-state" ]; then + STATUS=$(jq -r '.status // empty' .specify/.spex-state 2>/dev/null) + ASK=$(jq -r '.ask // "always"' .specify/.spex-state 2>/dev/null) + if [ "$STATUS" = "running" ] && [ "$ASK" != "always" ]; then + echo "AUTONOMOUS_MODE=true" + else + echo "AUTONOMOUS_MODE=false" + fi +else + echo "AUTONOMOUS_MODE=false" +fi +``` + +In autonomous mode: do NOT output a completion summary, do NOT ask "Shall I proceed?", do NOT suggest next steps. Complete the review and return. + +## Flow Status Update (before review starts) + +If review-code is running, implementation is by definition done. Mark it immediately so the status line shows `impl ✓` during the review: + +```bash +FLOW_STATE="$(find ~/.claude -name 'spex-flow-state.sh' 2>/dev/null | head -1)" && [ -x "$FLOW_STATE" ] && "$FLOW_STATE" implemented && "$FLOW_STATE" running review-code +``` + +## IMPORTANT: Deep Review Extension Check + +**Before starting any review work**, check if the `spex-deep-review` extension is enabled: + +```bash +# Check via extensions registry +jq -r '.extensions["spex-deep-review"].enabled // false' .specify/extensions/.registry 2>/dev/null +``` + +If deep review is enabled, this command MUST invoke `speckit.spex-deep-review.review` after spec compliance passes (>= 95%). Do NOT produce only a basic compliance review when deep-review is active. The deep review dispatches 5 specialized agents, runs a fix loop, and generates a Deep Review Report. See step 9a below for details. + +## Overview + +Review code implementation against specification to ensure compliance. + +**Key Difference from Standard Code Review:** +- Primary focus: **Does code match spec?** +- Secondary focus: Code quality, patterns, best practices +- Output: **Compliance score** + deviation list +- Triggers: **Spec evolution** if mismatches found + +## When to Use + +- After implementation complete (called via spex-gates hook on after_implement) +- Before merging/deploying code +- When validating existing code against spec +- As part of verification workflow + +## Spec Selection + +If a spec path is provided as an argument, use it directly. + +Otherwise, attempt branch-based resolution: + +```bash +.specify/scripts/bash/check-prerequisites.sh --json --paths-only 2>/dev/null +``` + +If this succeeds (outputs JSON with `FEATURE_SPEC`), use the resolved spec path. Parse the JSON to extract `FEATURE_SPEC` and `FEATURE_DIR`. + +If this fails (not on a feature branch, no matching spec directory), fall back to interactive selection: + +```bash +find specs/ -name "spec.md" -type f 2>/dev/null | head -20 +``` + +**If specs found:** Present list and ask user to select one using AskUserQuestion (skip in autonomous mode). + +**If no specs found:** Inform user: +``` +No specs found in specs/ directory. + +Code review against spec requires a spec to compare against. +Use `speckit-spex-brainstorm` or `/speckit-specify` to create one first. +``` + +## The Process + +### 1. Load Spec and Code + +**Read specification:** +```bash +cat specs/features/[feature-name].md +``` + +**Identify implementation files:** +```bash +# From implementation plan or code exploration +ls -la [implementation-files] +``` + +### 2. Review Functional Requirements + +**For each functional requirement in spec:** + +1. **Find implementation** in code +2. **Compare behavior**: Does code do what spec says? +3. **Check completeness**: All aspects implemented? +4. **Note deviations**: Any differences? + +**Create compliance matrix:** +``` +Requirement 1: [Spec text] + Implementation: [file:line] + Status: Compliant | Deviation | Missing + Notes: [If deviation, explain] + +Requirement 2: [Spec text] + ... +``` + +### 3. Review Error Handling + +**For each error case in spec:** + +1. **Find error handling** in code +2. **Check error response**: Matches spec? +3. **Verify error codes**: Correct HTTP status / error codes? +4. **Test error messages**: Clear and helpful? + +**Error handling compliance:** +``` +Error Case 1: [From spec] + Implemented: Yes/No + Location: [file:line] + Response: [What code returns] + Spec Expected: [What spec says] + Status: Compliant / Deviation +``` + +### 4. Review Edge Cases + +**For each edge case in spec:** + +1. **Find handling** in code +2. **Check behavior**: Matches spec? +3. **Verify tests**: Edge case tested? + +### 5. Check for Extra Features + +**Identify code features NOT in spec:** + +- Functions/endpoints not mentioned in spec +- Behavior beyond spec requirements +- Additional error handling +- Extra validations + +**For each extra feature:** +- Document what it does +- Assess: Helpful addition or scope creep? +- Note for potential spec update + +### 6. Calculate Compliance Score + +**Formula:** +``` +Compliance % = (Compliant Requirements / Total Requirements) x 100 +``` + +**Include:** +- Functional requirements +- Error cases +- Edge cases +- Non-functional requirements + +### 7. Generate Report + +**Report structure:** + +```markdown +# Code Review: [Feature Name] + +**Spec:** specs/features/[feature].md +**Date:** YYYY-MM-DD +**Reviewer:** Claude (speckit.spex-gates.review-code) + +## Compliance Summary + +**Overall Score: XX%** + +- Functional Requirements: X/X (XX%) +- Error Handling: X/X (XX%) +- Edge Cases: X/X (XX%) +- Non-Functional: X/X (XX%) + +## Detailed Review + +### Functional Requirements + +#### Requirement 1: [Spec text] +**Implementation:** src/[file]:line +**Status:** Compliant +**Notes:** Correctly implemented as specified + +#### Requirement 2: [Spec text] +**Implementation:** src/[file]:line +**Status:** Deviation +**Issue:** [What differs from spec] +**Impact:** [Minor/Major] +**Recommendation:** [Update spec / Fix code] + +### Error Handling + +[Similar format for each error case] + +### Edge Cases + +[Similar format for each edge case] + +### Extra Features (Not in Spec) + +#### [Feature name] +**Location:** src/[file]:line +**Description:** [What it does] +**Assessment:** [Helpful / Scope creep] +**Recommendation:** [Add to spec / Remove] + +## Code Quality Notes + +[Secondary observations about code quality, patterns, etc.] + +## Recommendations + +### Critical (Must Fix) +- [ ] [Issue requiring immediate attention] + +### Spec Evolution Candidates +- [ ] [Deviation that might warrant spec update] + +### Optional Improvements +- [ ] [Nice-to-have suggestions] + +## Conclusion + +[Overall assessment] + +**Next Steps:** +- If compliance < 100%: Use `speckit-spex-evolve` to reconcile deviations +- If compliance = 100%: Proceed to verification +``` + +### 8. Deep Review Enhancement (if extension enabled) + +**First, parse flags from the invocation arguments:** + +When this command is invoked with arguments, extract flags before treating the remainder as hint text: + +- `--no-external`: disable all external tools +- `--no-coderabbit`: disable CodeRabbit only +- `--no-copilot`: disable Copilot only +- `--external`: enable all external tools +- `--coderabbit`: enable CodeRabbit only +- `--copilot`: enable Copilot only + +Flags are consumed and removed from the argument string. The remaining text (if any) becomes the hint text. + +**Resolve external tool settings (defaults + flag overrides):** + +```bash +# 1. Read defaults from deep-review extension config (all default to true if key is missing) +DEEP_REVIEW_CONFIG=".specify/extensions/spex-deep-review/deep-review-config.yml" +DEFAULT_CODERABBIT=$(yq -r '.external_tools.coderabbit // true' "$DEEP_REVIEW_CONFIG" 2>/dev/null) +DEFAULT_COPILOT=$(yq -r '.external_tools.copilot // true' "$DEEP_REVIEW_CONFIG" 2>/dev/null) + +# 2. If config file is missing, default all tools to true +``` + +``` +Resolution logic: + +1. Start with config defaults: + coderabbit = DEFAULT_CODERABBIT + copilot = DEFAULT_COPILOT + +2. Apply flag overrides (flags always win over defaults): + --external -> coderabbit = true, copilot = true + --no-external -> coderabbit = false, copilot = false + --coderabbit -> coderabbit = true + --no-coderabbit -> coderabbit = false + --copilot -> copilot = true + --no-copilot -> copilot = false + +3. Flags are applied in order. Later flags override earlier ones: + --external --no-copilot -> coderabbit = true, copilot = false + --no-external --coderabbit -> coderabbit = true, copilot = false +``` + +**After spec compliance is calculated, check for deep review:** + +**If deep review is enabled AND spec compliance >= 95% (or no spec exists):** +- Invoke `speckit.spex-deep-review.review` with: + - Stage 1 compliance score (or null if no spec) + - Invocation context: `quality-gate` if called from hook, `manual` if called directly + - Hint text: remaining argument text after flag extraction + - External tool settings: `{coderabbit: true/false, copilot: true/false}` (resolved from defaults + flags) + - Spec path and feature directory +- Wait for deep review to complete before proceeding +- Deep review includes a post-fix spec compliance check (Step 7b) that catches requirements dropped during the fix loop. If deep review reports dropped requirements, treat them as Critical findings that must be resolved before proceeding. + +**If deep review is enabled AND spec compliance < 95%:** +- Do NOT invoke deep review +- Report the compliance score and non-compliant requirements +- Instruct the user to fix spec compliance issues first + +**If deep review is NOT enabled:** +- Continue with standard review behavior (steps 9b below) + +### 9b. Trigger Evolution if Needed + +**If deviations found (standard review path, no deep-review):** +- Present review results to user +- Recommend using `speckit-spex-evolve` +- Don't proceed to verification until resolved + +**If 100% compliant (standard review path):** +- Approve for verification +- Proceed to `speckit.spex.finish` + +## Assessment Criteria + +### Compliant +- Code does exactly what spec says +- No deviations in behavior +- All aspects covered + +### Minor Deviation +- Small differences (naming, details) +- Non-breaking additions +- Better error messages than spec +- Typically: Update spec + +### Major Deviation +- Different behavior than spec +- Missing functionality +- Wrong error handling +- Typically: Fix code or evolve spec + +### Missing +- Spec requires it, code doesn't have it +- Critical gap +- Must fix code + +## Anti-Rationalization: What You Must NOT Do + +**DO NOT skip checking ANY requirement.** Each spec requirement must be verified against code. Not "spot checking." Not "seems fine." Every. Single. One. + +**DO NOT assume compliance.** "It looks right" is not compliance. "I think it matches" is not compliance. Show the code location. Compare the behavior. Document the status. + +**DO NOT hide deviations.** A deviation is not a failure; it's information. Hiding deviations breaks the feedback loop. Report every deviation, even minor ones. + +**DO NOT proceed with deviations unresolved.** 89% compliance is NOT ready for verification. 99% compliance is NOT ready for verification. Only 100% compliance proceeds to verification. + +**DO NOT rationalize scope creep.** "But this feature is useful!" is not justification for unspecified code. Either add it to the spec (via evolution) or remove it. Undocumented features are invisible bugs. + +**DO NOT conflate code quality with spec compliance.** Code can be beautiful AND non-compliant. Code can be ugly AND compliant. Check both, report both, but never confuse them. + +## Remember + +**Spec compliance is primary concern.** + +This is not just code quality review; it's **spec validation**. + +- Does code match spec? (Most important) +- Is code quality good? (Secondary) +- Any improvements? (Tertiary) + +**100% compliance is the goal.** + +- < 90%: Significant issues, fix before proceeding +- 90-99%: Minor deviations, likely spec updates +- 100%: Perfect compliance, ready for verification + +**Deviations trigger evolution.** + +- Don't force-fit wrong spec +- Don't ignore deviations +- Use `speckit-spex-evolve` to reconcile + +**The code and spec must tell the same story.** + +**Evidence before assertions. Always.** + +## Update Flow State + +**MANDATORY: Update flow state.** This MUST run on every exit path. Use the flow state script: + +```bash +FLOW_STATE="$(find ~/.claude -name 'spex-flow-state.sh' 2>/dev/null | head -1)" && [ -x "$FLOW_STATE" ] && "$FLOW_STATE" gate review-code && "$FLOW_STATE" implemented +``` + +This updates the status line to show both `impl ✓` and `R ✓`. If code review passed, implementation is by definition complete. + +## Next Steps (tell the user) + +After code review passes, tell the user: + +``` +Code review complete. To close out this feature: + 1. /clear (free context for final gate) + 2. /speckit-spex-finish (verify + merge/PR, all-in-one) +``` + +This prompt is mandatory on every PASS exit. The user needs to know how to finalize. diff --git a/.specify/extensions/spex-gates/commands/speckit.spex-gates.review-plan.md b/.specify/extensions/spex-gates/commands/speckit.spex-gates.review-plan.md new file mode 100644 index 00000000..ac3d3aa5 --- /dev/null +++ b/.specify/extensions/spex-gates/commands/speckit.spex-gates.review-plan.md @@ -0,0 +1,202 @@ +--- +description: "Post-planning quality validation with coverage matrix, red flag scanning, and task quality enforcement" +--- + +# Post-Planning Quality Validation + +## Ship Pipeline Guard + +If `.specify/.spex-state` exists and its `status` is `running`, this command is part of an autonomous pipeline. Check the `ask` field: +- If `ask` is `"smart"` or `"never"`: suppress all user prompts (do NOT use AskUserQuestion), complete the review autonomously, and return immediately so the pipeline can advance. +- If `ask` is `"always"`: prompt the user as normal. + +```bash +if [ -f ".specify/.spex-state" ]; then + STATUS=$(jq -r '.status // empty' .specify/.spex-state 2>/dev/null) + ASK=$(jq -r '.ask // "always"' .specify/.spex-state 2>/dev/null) + if [ "$STATUS" = "running" ] && [ "$ASK" != "always" ]; then + echo "AUTONOMOUS_MODE=true" + else + echo "AUTONOMOUS_MODE=false" + fi +else + echo "AUTONOMOUS_MODE=false" +fi +``` + +In autonomous mode: do NOT output a completion summary, do NOT ask "Shall I proceed?", do NOT suggest next steps. Complete the review and return. + +## Overview + +This skill validates plan and task quality after `/speckit-plan` and `/speckit-tasks` have run. It checks coverage, scans for red flags, and enforces task quality standards. + +## Prerequisites + +Spec-kit must be initialized. If `.specify/` directory does not exist, tell the user to run `/spex:init` first and stop. + +**Both plan.md and tasks.md MUST exist before running this skill.** If either is missing, stop with an error: + +```bash +SPEC_DIR="specs/[feature-name]" +[ -f "$SPEC_DIR/plan.md" ] && echo "plan.md found" || echo "ERROR: plan.md missing - run /speckit-plan first" +[ -f "$SPEC_DIR/tasks.md" ] && echo "tasks.md found" || echo "ERROR: tasks.md missing - run /speckit-tasks first" +``` + +If either file is missing, stop and instruct the user to generate the missing artifact. + +## 0. Scope Check + +Before detailed validation, check whether the plan attempts to cover multiple independent subsystems in a single document. Indicators: + +- Tasks span subsystems with no shared interfaces or dependencies +- The plan has distinct groups of tasks that could each produce working software independently +- File changes cluster into unrelated areas of the codebase + +If the plan covers multiple independent subsystems, flag it: "This plan may benefit from being split into separate plans, one per subsystem. Each plan should produce working, testable software on its own." + +This is advisory, not blocking. Some plans legitimately span subsystems. + +## 1. Task Quality Enforcement + +After tasks.md exists, verify every task meets these criteria: + +- **Actionable**: Clear what to do (not "figure out..." or "investigate...") +- **Testable**: Can verify completion objectively +- **Atomic**: One clear outcome per task +- **Ordered**: Dependencies between tasks are respected, phases are sequenced correctly + +Also check: +- Every task specifies concrete file paths (not "somewhere" or "TBD") +- Phase ordering is logical (setup before core, tests before integration) +- No tasks duplicate work already covered by other tasks + +Verify the plan includes a file structure mapping: +- Files to be created or modified are listed with their responsibilities +- Each file has one clear responsibility (not vague "utils" or "helpers" without defined scope) +- Design units have clear boundaries and well-defined interfaces +- In existing codebases, the plan follows established patterns rather than unilaterally restructuring + +If the plan lacks a file structure mapping, note it as a gap: tasks without a file map are harder to verify for completeness and overlap. + +If tasks fail these checks, note the issues and suggest refinements. + +## 2. Coverage Matrix + +Produce a coverage matrix mapping every spec requirement to its implementing tasks: + +``` +Requirement 1 -> Tasks [X,Y] +Requirement 2 -> Tasks [Z] +NFR 1 -> Tasks [W] +... +``` + +Flag any requirement without task coverage. All requirements must have at least one implementing task. + +Also verify: +- Every error case in the spec has a handling approach +- Every edge case from the spec is addressed +- Success criteria have verification approaches + +## 3. Red Flag Scanning + +Search plan.md and tasks.md for vague or incomplete language: + +```bash +SPEC_DIR="specs/[feature-name]" +rg -i "figure out|tbd|todo|implement later|somehow|somewhere|not sure|maybe|probably|add appropriate|add validation|handle edge cases|similar to task" "$SPEC_DIR/plan.md" "$SPEC_DIR/tasks.md" || echo "No red flags found" +``` + +Review any matches: +- "Figure out..." = missing research, needs concrete approach +- "TBD" / "TODO" = incomplete planning, must be resolved +- "Implement later" = deferred work, scope explicitly +- "Add appropriate error handling" / "add validation" / "handle edge cases" = vague placeholders, must show actual code +- "Write tests for the above" (without actual test code) = test code must be included +- "Similar to Task N" = repeat the code, the engineer may read tasks out of order +- Steps that describe what to do without showing how = code blocks required for code steps +- Missing file paths = tasks are not actionable + +## 4. Type and Name Consistency + +Check that types, method signatures, property names, and function names used across tasks are consistent: + +- If a function is called `clearLayers()` in Task 3, it must not be called `clearFullLayers()` in Task 7 +- If a type is defined in an early task, later tasks must reference the same type name +- If a constant or config key is introduced, verify spelling is consistent across all tasks +- If an API endpoint path is defined, verify all references use the same path + +Inconsistencies between tasks are plan bugs that will become code bugs during implementation. + +## 5. NFR Validation + +For each non-functional requirement in the spec, verify the plan includes: +- A concrete measurement method (not just "should be fast") +- A validation approach (how will you verify the NFR is met?) +- Acceptance thresholds where applicable + +If any NFR lacks a measurement method, flag it. + +## 6. Present Results + +Report to the user: +- Task quality check results (pass/issues) +- Coverage matrix summary +- Red flag scan results +- NFR validation results + +## 7. Offer Remediation + +After presenting results, collect ALL findings from steps 0-4 into a numbered list. Include both blocking and non-blocking issues. Present them as a consolidated findings summary: + +``` +Findings: + + 1. [BLOCKING] Task T003 is not actionable: "figure out auth approach" + 2. [advisory] Plan may benefit from splitting (2 independent subsystems) + 3. [gap] FR-007 has no implementing task in the coverage matrix + 4. [red-flag] tasks.md line 42: "TBD" placeholder + 5. [nfr] NFR-002 "response time < 200ms" has no measurement method +``` + +Then ask the user how to proceed (skip in autonomous mode, default to "Fix all"): + +Use AskUserQuestion with: +- header: "Findings" +- multiSelect: false +- Options: + - "Fix all": "Address every finding automatically" + - "Let me pick": "Select specific findings to fix (you can add comments)" + - "Skip": "Proceed without changes" + +**If "Fix all"**: Apply fixes to plan.md and/or tasks.md for each finding, then re-run the relevant checks to confirm resolution. + +**If "Let me pick"**: Use AskUserQuestion with multiSelect: true, listing up to 4 findings as options (if more than 4, batch them across multiple rounds). Each option's label is the short finding (e.g., "#1 Task T003 not actionable") and the description is the detail. The user can select which to fix and use "Other" to add comments or instructions for specific findings. + +After the user selects findings, apply fixes to plan.md and/or tasks.md. For each selected finding: +1. Read the user's comment (if any) to understand their intent +2. Make the minimal targeted edit to resolve the finding +3. Report what was changed + +After all selected fixes are applied, re-present any remaining unaddressed findings as informational (no further prompting). + +**If "Skip"**: Proceed without changes. Note that blocking issues remain unresolved. + +## 8. Update Flow State + +**MANDATORY: Update flow state.** This MUST run on every exit path, including early returns (e.g., "already passed", "no findings"). Use the flow state script: + +```bash +FLOW_STATE="$(find ~/.claude -name 'spex-flow-state.sh' 2>/dev/null | head -1)" && [ -x "$FLOW_STATE" ] && "$FLOW_STATE" gate review-plan +``` + +This updates the status line to show `P ✓`. + +## Integration + +**This command is invoked by:** +- The spex-gates extension hook for `after_tasks` +- Users directly via `speckit.spex-gates.review-plan` + +**This command invokes:** +- Prerequisite check for `.specify/` directory diff --git a/.specify/extensions/spex-gates/commands/speckit.spex-gates.review-spec.md b/.specify/extensions/spex-gates/commands/speckit.spex-gates.review-spec.md new file mode 100644 index 00000000..701b9932 --- /dev/null +++ b/.specify/extensions/spex-gates/commands/speckit.spex-gates.review-spec.md @@ -0,0 +1,331 @@ +--- +description: "Review specifications for soundness, completeness, and implementability" +--- + +# Reviewing Specifications for Soundness + +## Ship Pipeline Guard + +If `.specify/.spex-state` exists and its `status` is `running`, this command is part of an autonomous pipeline. Check the `ask` field: +- If `ask` is `"smart"` or `"never"`: suppress all user prompts (do NOT use AskUserQuestion), complete the review autonomously, and return immediately so the pipeline can advance. +- If `ask` is `"always"`: prompt the user as normal. + +```bash +if [ -f ".specify/.spex-state" ]; then + STATUS=$(jq -r '.status // empty' .specify/.spex-state 2>/dev/null) + ASK=$(jq -r '.ask // "always"' .specify/.spex-state 2>/dev/null) + if [ "$STATUS" = "running" ] && [ "$ASK" != "always" ]; then + echo "AUTONOMOUS_MODE=true" + else + echo "AUTONOMOUS_MODE=false" + fi +else + echo "AUTONOMOUS_MODE=false" +fi +``` + +In autonomous mode: do NOT output a completion summary, do NOT ask "Shall I proceed?", do NOT suggest next steps. Complete the review and return. + +## Overview + +Validate specification quality before implementation begins. + +A poor spec leads to confusion, rework, and spec/code drift. A sound spec enables smooth implementation. + +This skill checks: completeness, clarity, implementability, and testability. + +## When to Use + +- After spec creation (before implementation) +- Before generating implementation plan +- When spec seems unclear or incomplete +- Periodically for important specs + +## Prerequisites + +Spec-kit must be initialized. If `.specify/` directory does not exist, tell the user to run `/spex:init` first and stop. + +## Spec Selection + +If a spec path is provided as an argument, use it directly. + +Otherwise, attempt branch-based resolution: + +```bash +.specify/scripts/bash/check-prerequisites.sh --json --paths-only 2>/dev/null +``` + +If this succeeds (outputs JSON with `FEATURE_SPEC`), use the resolved spec path. Parse the JSON to extract `FEATURE_SPEC` and `FEATURE_DIR`. + +If this fails (not on a feature branch, no matching spec directory), fall back to interactive selection: + +```bash +find specs/ -name "spec.md" -type f 2>/dev/null | head -20 +``` + +**If specs found:** Present list and ask user to select one using AskUserQuestion (skip in autonomous mode). + +**If no specs found:** Inform user: +``` +No specs found in specs/ directory. + +To create a spec first: +- Use `speckit-spex-brainstorm` to refine ideas into a spec +- Use `/speckit-specify` to create a spec from clear requirements + +Cannot review without a spec to review. +``` + +## spec-kit Integration + +This skill can use `/speckit-*` slash commands when available: + +- `/speckit-clarify` - Find underspecified areas in the spec +- `/speckit-analyze` - Cross-artifact consistency check (if plan/tasks exist) + +**If `/speckit-*` commands are available:** +Use them to assist with review, but always perform manual review as well. + +**If `/speckit-*` commands are not available:** +Proceed with manual review only. This is acceptable. + +## Review Dimensions + +### 1. Completeness +- All sections filled +- No TBD or placeholder text +- All requirements defined +- Success criteria specified + +### 2. Clarity +- No ambiguous language +- Concrete, specific requirements +- Edge cases explicitly defined +- Error handling specified + +### 3. Implementability +- Can generate implementation plan +- Dependencies identified +- Constraints realistic +- Scope manageable + +### 4. Testability +- Success criteria measurable +- Requirements verifiable +- Acceptance criteria clear + +## The Process + +### 1. Load and Read Spec + +**Read the spec:** + +```bash +cat specs/[feature-name]/spec.md +``` + +Read thoroughly, take notes on issues. + +### 2. Check Structure + +**Required sections (should exist):** +- [ ] Purpose/Overview +- [ ] Functional Requirements +- [ ] Success Criteria +- [ ] Error Handling + +**Recommended sections:** +- [ ] Non-Functional Requirements +- [ ] Edge Cases +- [ ] Dependencies +- [ ] Constraints +- [ ] Out of Scope + +**If sections missing:** +- Note which ones +- Assess if truly needed for this spec +- Recommend additions + +### 3. Review Completeness + +**For each section, check:** + +**Purpose:** +- [ ] Clearly states why feature exists +- [ ] Describes problem being solved +- [ ] Avoids implementation details + +**Functional Requirements:** +- [ ] Numbered/listed clearly +- [ ] Each requirement is specific +- [ ] No "TBD" or placeholders +- [ ] All aspects covered + +**Success Criteria:** +- [ ] Measurable outcomes defined +- [ ] Clear completion indicators +- [ ] Testable assertions + +**Error Handling:** +- [ ] All error cases identified +- [ ] Handling approach specified +- [ ] Error messages/codes defined + +**Edge Cases:** +- [ ] Boundary conditions listed +- [ ] Expected behavior specified +- [ ] Not marked as "TBD" + +### 4. Check for Ambiguities + +**Red flag words/phrases:** +- "should" (vs "must") +- "might", "could", "probably" +- "fast", "slow" (without metrics) +- "user-friendly" (vague) +- "handle appropriately" (non-specific) +- "etc." (incomplete list) +- "similar to..." (unclear) + +**For each ambiguity:** +- Identify the vague requirement +- Note what's unclear +- Suggest specific alternative + +### 5. Validate Implementability + +**Ask:** +- Can I generate an implementation plan from this? +- Are file locations/components identifiable? +- Are dependencies clear? +- Is scope reasonable? + +**Check for:** +- Unknown dependencies +- Unrealistic constraints +- Scope too large +- Conflicting requirements + +### 6. Assess Testability + +**For each requirement:** +- How will this be tested? +- Is the outcome verifiable? +- Can success be measured? + +**For success criteria:** +- Are they specific enough to test? +- Can they be automated? +- Are they objective (not subjective)? + +### 7. Check Against Constitution + +**If constitution exists:** + +```bash +if [ -f ".specify/memory/constitution.md" ]; then + cat .specify/memory/constitution.md +else + echo "no-constitution" +fi +``` + +**Validate:** +- Does spec follow project principles? +- Are patterns consistent? +- Does error handling match standards? +- Are architectural decisions aligned? + +**Note any violations with reasoning.** + +### 8. Run Cross-Artifact Consistency Check (Optional) + +**If plan or tasks exist and `/speckit-analyze` is available:** + +Invoke `/speckit-analyze` to check consistency between: +- spec.md (requirements) +- plan.md (implementation approach) +- tasks.md (task list) + +**Report any mismatches or gaps found.** + +### 9. Generate Review Report + +Output the review findings to the console. Do NOT write a `REVIEW-SPEC.md` file. All review information is presented directly in the conversation output. + +### 10. Make Recommendation + +**If sound (minor issues only):** +- Ready for implementation +- Proceed with `/speckit-implement` + +**If needs work (important issues):** +- Fix issues before implementing +- Update spec, re-review + +**If major issues:** +- Not ready for implementation +- Significant rework needed +- May need re-brainstorming + +## Review Checklist + +- [ ] Load and read spec thoroughly +- [ ] Check structure (all sections present) +- [ ] Review completeness (no TBD, all covered) +- [ ] Identify ambiguities (vague language) +- [ ] Validate implementability (can plan from this) +- [ ] Assess testability (can verify requirements) +- [ ] Check constitution alignment (if exists) +- [ ] Run `/speckit-analyze` for cross-artifact consistency (if available) +- [ ] Generate review report +- [ ] Make recommendation (ready/needs work/major issues) + +## Quality Standards + +**A sound spec has:** +- All sections complete +- No ambiguous language +- Specific, measurable requirements +- Identified dependencies +- Realistic constraints +- Clear error handling +- Defined edge cases +- Testable success criteria + +**A poor spec has:** +- Missing sections +- Vague language +- Unmeasurable requirements +- Unknown dependencies +- Unrealistic constraints +- Unclear error handling +- Ignored edge cases +- Subjective criteria + +## Remember + +**Reviewing specs saves time in implementation.** + +- 1 hour reviewing spec saves 10 hours debugging +- Ambiguities caught early prevent rework +- Complete specs enable smooth TDD +- Sound specs reduce spec/code drift + +**Be thorough but not pedantic:** +- Flag real issues, not nitpicks +- Focus on what blocks implementation +- Suggest specific improvements +- Balance perfection with pragmatism + +**The goal is implementability, not perfection.** + +## Update Flow State + +**MANDATORY: Update flow state.** This MUST run on every exit path, including early returns (e.g., "already passed"). Use the flow state script: + +```bash +FLOW_STATE="$(find ~/.claude -name 'spex-flow-state.sh' 2>/dev/null | head -1)" && [ -x "$FLOW_STATE" ] && "$FLOW_STATE" gate review-spec +``` + +This updates the status line to show `S ✓`. diff --git a/.specify/extensions/spex-gates/commands/speckit.spex-gates.stamp.md b/.specify/extensions/spex-gates/commands/speckit.spex-gates.stamp.md new file mode 100644 index 00000000..89add248 --- /dev/null +++ b/.specify/extensions/spex-gates/commands/speckit.spex-gates.stamp.md @@ -0,0 +1,48 @@ +--- +description: "Final gate before completion - invokes verification for tests, code hygiene, spec compliance, and drift check" +--- + +# Stamp - Final Completion Gate + +## Ship Pipeline Guard + +If `.specify/.spex-state` exists and its `status` is `running`, this command is part of an autonomous pipeline. Check the `ask` field: +- If `ask` is `"smart"` or `"never"`: suppress all user prompts (do NOT use AskUserQuestion), complete the stamp autonomously, and return immediately so the pipeline can advance. +- If `ask` is `"always"`: prompt the user as normal. + +```bash +if [ -f ".specify/.spex-state" ]; then + STATUS=$(jq -r '.status // empty' .specify/.spex-state 2>/dev/null) + ASK=$(jq -r '.ask // "always"' .specify/.spex-state 2>/dev/null) + if [ "$STATUS" = "running" ] && [ "$ASK" != "always" ]; then + echo "AUTONOMOUS_MODE=true" + else + echo "AUTONOMOUS_MODE=false" + fi +else + echo "AUTONOMOUS_MODE=false" +fi +``` + +In autonomous mode: do NOT output a completion summary, do NOT ask "Shall I proceed?", do NOT suggest next steps. Complete the stamp and return. + +## Relationship to /speckit-spex-finish + +`/speckit-spex-stamp` runs verification only. For verification + merge/PR/cleanup in one step, use `/speckit-spex-finish` instead. + +## Purpose + +This is the final gate before claiming work is complete. It delegates to the full verification command. + +## Execution + +Invoke `speckit.spex-gates.verify` with any arguments passed to this command. + +The verify command runs all quality gates: +1. Full test suite +2. Code hygiene review +3. Spec compliance validation (100% required) +4. Spec drift check +5. Success criteria verification + +Only after all gates pass can work be claimed as complete. diff --git a/.specify/extensions/spex-gates/commands/speckit.spex-gates.verify.md b/.specify/extensions/spex-gates/commands/speckit.spex-gates.verify.md new file mode 100644 index 00000000..76f91144 --- /dev/null +++ b/.specify/extensions/spex-gates/commands/speckit.spex-gates.verify.md @@ -0,0 +1,476 @@ +--- +description: "Final verification gate: tests, code hygiene, spec compliance, and drift check" +--- + +# Verification Before Completion (Spec-Aware) + +## Ship Pipeline Guard + +If `.specify/.spex-state` exists and its `status` is `running`, this command is part of an autonomous pipeline. Check the `ask` field: +- If `ask` is `"smart"` or `"never"`: suppress all user prompts (do NOT use AskUserQuestion), complete the verification autonomously, and return immediately so the pipeline can advance. +- If `ask` is `"always"`: prompt the user as normal. + +```bash +if [ -f ".specify/.spex-state" ]; then + STATUS=$(jq -r '.status // empty' .specify/.spex-state 2>/dev/null) + ASK=$(jq -r '.ask // "always"' .specify/.spex-state 2>/dev/null) + if [ "$STATUS" = "running" ] && [ "$ASK" != "always" ]; then + echo "AUTONOMOUS_MODE=true" + else + echo "AUTONOMOUS_MODE=false" + fi +else + echo "AUTONOMOUS_MODE=false" +fi +``` + +In autonomous mode: do NOT output a completion summary, do NOT ask "Shall I proceed?", do NOT suggest next steps. Complete the verification and return. + +## Overview + +Claiming work is complete without verification is dishonesty, not efficiency. + +**Core principle:** Evidence before claims, always. + +Verify implementation is complete by running tests AND validating spec compliance. + +**Key Steps:** +- Step 1: Run tests (existing behavior) +- **Step 2: Code hygiene review** (mechanical defect detection) +- **Step 3: Validate spec compliance** (spec-driven) +- **Step 4: Check for spec drift** (spec-driven) +- Blocks completion if tests, code hygiene, OR spec compliance fails + +## The Iron Law + +``` +NO COMPLETION CLAIMS WITHOUT FRESH VERIFICATION EVIDENCE +``` + +If you haven't run the verification command in this message, you cannot claim it passes. + +## The Gate Function + +``` +BEFORE claiming any status or expressing satisfaction: + +1. IDENTIFY: What command proves this claim? +2. RUN: Execute the FULL command (fresh, complete) +3. READ: Full output, check exit code, count failures +4. VERIFY: Does output confirm the claim? + - If NO: State actual status with evidence + - If YES: State claim WITH evidence +5. CHECK SPEC: Does implementation match spec? + - If NO: State actual compliance with evidence + - If YES: State compliance score WITH evidence +6. ONLY THEN: Make the claim + +Skip any step = lying, not verifying +``` + +## When to Use + +- After implementation and code review +- Before claiming work is complete +- Before committing/merging/deploying +- As final gate in the implementation pipeline (via spex-gates hook) + +## Spec Selection + +If a spec path is provided as an argument, use it directly. + +Otherwise, attempt branch-based resolution: + +```bash +.specify/scripts/bash/check-prerequisites.sh --json --paths-only 2>/dev/null +``` + +If this succeeds (outputs JSON with `FEATURE_SPEC`), use the resolved spec path. Parse the JSON to extract `FEATURE_SPEC` and `FEATURE_DIR`. + +If this fails (not on a feature branch, no matching spec directory), fall back to interactive selection: + +```bash +find specs/ -name "spec.md" -type f 2>/dev/null | head -20 +``` + +**If specs found:** Present list and ask user to select one using AskUserQuestion (skip in autonomous mode). + +**If no specs found:** Inform user: +``` +No specs found in specs/ directory. + +Verification requires a spec to validate against. +Use `speckit-spex-brainstorm` or `/speckit-specify` to create one first. +``` + +## The Process + +### 1. Run Tests + +**Execute all tests:** +```bash +# Run full test suite +npm test # or pytest, go test, etc. +``` + +**Check results:** +- All tests passing? +- No flaky tests? +- Coverage adequate? + +**If tests fail:** +- STOP: Fix tests before proceeding +- Do not skip this step +- Do not claim completion + +### 2. Code Hygiene Review + +**Before checking spec compliance, review every changed file for mechanical defects. +These are craft-level issues that no spec describes but that cause real bugs.** + +**For each function you wrote or modified, check:** + +#### Dead Code and Logic +- [ ] Every conditional branch produces a **different** outcome (no branches that do the same thing) +- [ ] Every parameter is **actually used** to change behavior (no unused parameters) +- [ ] Every exported function has **at least one caller** outside tests (no orphaned API surface) +- [ ] No variables assigned but never read + +#### Copy and Mutation Safety +- [ ] When copying a data structure, verify whether **nested references are shared** +- [ ] If the copy is later mutated, confirm the original is **not affected** +- [ ] If a function receives a pointer/reference, verify whether it is expected to **mutate or read-only** + +#### Cleanup and Consistency +- [ ] When removing/disabling something, verify **all references** to it are also cleaned up (no orphaned entries, dangling references, stale indices) +- [ ] When adding to a collection, verify **duplicates are handled** (deduplicate or reject) +- [ ] When deriving a value from an identifier, verify the **derivation uses the correct source** (e.g., a display name vs an internal ID vs a type name are different things) + +#### Unnecessary Operations +- [ ] No sorting/ordering when the result doesn't depend on order (counting, summing, existence checks) +- [ ] No intermediate data structures that serve no purpose (building a list just to iterate it once) + +**If any issue found:** Fix before proceeding. These are not style nits; they are latent bugs. + +### 3. Validate Spec Compliance + +**Load spec:** +```bash +cat specs/features/[feature-name].md +``` + +**Check each requirement and emit a machine-readable compliance matrix:** + +For each functional requirement in the spec, determine its status. There are exactly two valid statuses: + +- **IMPLEMENTED**: Code fully implements the requirement. You can point to the function, the test, and the behavior. +- **MISSING**: Code does not implement the requirement, or implements it only partially. + +There is NO "PARTIAL" status. There is NO "COMPLIANT (with documented gap)" status. There is NO "IMPLEMENTED (known limitation)" status. If the code doesn't fully do what the spec says MUST happen, the status is MISSING. + +```markdown +Functional Requirement 1: [From spec] + Status: IMPLEMENTED | MISSING + Evidence: [file:line where it's implemented, or why it's missing] + +Functional Requirement 2: [From spec] + Status: IMPLEMENTED | MISSING + Evidence: [file:line where it's implemented, or why it's missing] +``` + +**After checking ALL requirements, emit a machine-readable gate result block:** + +``` +SPEC_COMPLIANCE_RESULT: + total: N + implemented: N + missing: N + percentage: XX% + gate: PASS | FAIL +``` + +The gate value is determined mechanically: if `missing > 0`, the gate is `FAIL`. No exceptions. No judgment calls. No "but it's a known gap." If the spec says MUST and the code doesn't, it's MISSING, and the gate is FAIL. + +**Anti-rationalization guardrails (read these before making your compliance decision):** + +You WILL be tempted to let partial compliance pass. You will find plausible reasons. All of them are wrong: + +| Rationalization | Why it's wrong | +|-----------------|---------------| +| "It's a known gap" | Known gaps are still gaps. MISSING. | +| "It's documented as future work" | Future work means not implemented. MISSING. | +| "There's a workaround" | Workarounds are not implementations. MISSING. | +| "The spec assumption section mentions this" | Assumptions don't override MUST requirements. MISSING. | +| "It's only partially implemented" | Partial is not complete. MISSING. | +| "The user knows about this" | User awareness doesn't change compliance. MISSING. | + +The only valid paths when the gate is FAIL: +1. Implement the missing requirement, then re-run verify +2. Run `/speckit-spex-evolve` to formally remove or defer the requirement from the spec, then re-run verify + +**If compliance = 100%:** Proceed to next step. +**If compliance < 100%:** STOP. Do not proceed. Report the MISSING requirements and suggest the two valid resolution paths above. + +### 4. Check for Spec Drift + +**Compare:** +- What spec says NOW +- What code does NOW +- Any divergence? + +**Common drift sources:** +- Spec updated but code not +- Code changed but spec not +- Undocumented additions +- Forgotten requirements + +**If drift detected:** +- Document each instance +- Use `speckit-spex-evolve` to reconcile +- Do not proceed with drift + +### 5. Verify Success Criteria + +**From spec, check each criterion:** + +```markdown +Success Criteria (from spec): +- [ ] Criterion 1: [Description] + Status: Met / Not met + Evidence: [How verified] + +- [ ] Criterion 2: [Description] + ... +``` + +**All criteria must be met.** + +If any criterion not met: +- STOP: Criterion not met +- Implement missing piece +- Re-verify + +### 6. Generate Verification Report + +**Report structure:** + +```markdown +# Verification Report: [Feature Name] + +**Date:** YYYY-MM-DD +**Spec:** specs/features/[feature].md + +## Test Results + +**Status:** PASS / FAIL + +[Test output] + +**Summary:** +- Total: X tests +- Passed: X +- Failed: X +- Coverage: XX% + +## Spec Compliance + +**Status:** COMPLIANT / NON-COMPLIANT + +**Compliance Score:** XX% + +### Requirements Status +- Functional: X/X (XX%) +- Error Cases: X/X (XX%) +- Edge Cases: X/X (XX%) +- Non-Functional: X/X (XX%) + +### Deviations +[List any deviations found] + +## Spec Drift Check + +**Status:** NO DRIFT / DRIFT DETECTED + +[Details if drift found] + +## Success Criteria + +**Status:** ALL MET / INCOMPLETE + +- [x] Criterion 1 +- [x] Criterion 2 +... + +## Overall Status + +**VERIFIED - Ready for completion** + +OR + +**NOT VERIFIED - Issues must be resolved** + +**Blocking Issues:** +- [Issue 1] +- [Issue 2] + +**Next Steps:** +[What needs to be done] +``` + +### 7. Make Go/No-Go Decision + +**All conditions must be true:** +- [x] All tests passing +- [x] Code hygiene review clean (no dead code, no mutation bugs, no orphans) +- [x] `SPEC_COMPLIANCE_RESULT.gate` is `PASS` (100% compliance, zero MISSING) +- [x] No spec drift +- [x] All success criteria met + +**If ALL true:** +- VERIFIED: Proceed to completion +- Safe to commit/merge/deploy +- **Write verification marker** so the commit gate hook allows the commit: + ```bash + touch "${TMPDIR:-/tmp}/.claude-spex-verified-${SESSION_ID}" + ``` + (The SESSION_ID is available from the hook context. If not, use a stable session identifier.) + +**If ANY false:** +- NOT VERIFIED: Block completion +- Fix issues before proceeding +- Re-run verification after fixes + +### 8. Completion Celebration + +**After all verification passes** (go decision is positive), check if a state file exists: + +```bash +STATE_FILE=".specify/.spex-state" +if [ -f "$STATE_FILE" ]; then + MODE=$(jq -r '.mode // empty' "$STATE_FILE" 2>/dev/null) +fi +``` + +**If a state file exists** (flow or ship mode), display a celebration: + +1. **Compute stats:** + ```bash + FEATURE=$(jq -r '.feature_branch // "unknown"' "$STATE_FILE" 2>/dev/null) + STARTED=$(jq -r '.started_at // empty' "$STATE_FILE" 2>/dev/null) + SPEC_DIR=$(jq -r '.spec_dir // empty' "$STATE_FILE" 2>/dev/null) + # If spec_dir not in state (ship mode), derive from branch + [ -z "$SPEC_DIR" ] && SPEC_DIR="specs/$FEATURE" + REVIEW_COUNT=$(ls "$SPEC_DIR"/REVIEW-*.md 2>/dev/null | wc -l | tr -d ' ') + COMMIT_COUNT=$(git rev-list --count main..HEAD 2>/dev/null || echo "?") + ``` + - Duration: compute from `started_at` to now (human-readable, e.g., "2h 15m" or "3d 4h") + - If `started_at` is empty, skip duration + +2. **Display celebration banner:** + ``` + +-------------------------------------------+ + | | + | ALL CHECKS PASSED | + | | + | Feature: | + | Duration: | + | Reviews: passed | + | Commits: | + | | + | | + | | + +-------------------------------------------+ + ``` + +3. **Sign-off message pool** (select one randomly): + - "Ship it!" + - "Another one bites the dust." + - "That's a wrap." + - "Clean as a whistle." + - "Nailed it." + - "Spec met. Code shipped. Coffee earned." + - "Nothing left to prove." + +4. **Remove state file** after displaying: + ```bash + rm -f "$STATE_FILE" + ``` + +**If no state file exists**, skip the celebration (no-op). + +## Common Failures + +| Claim | Requires | Not Sufficient | +|-------|----------|----------------| +| Tests pass | Test command output: 0 failures | Previous run, "should pass" | +| Spec compliant | Line-by-line requirement check | "Looks complete" | +| Linter clean | Linter output: 0 errors | Partial check | +| Build succeeds | Build command: exit 0 | Linter passing | +| Bug fixed | Test original symptom: passes | Code changed | +| Requirements met | Line-by-line checklist | Tests passing | + +## Red Flags - STOP + +- Using "should", "probably", "seems to" +- Expressing satisfaction before verification ("Great!", "Perfect!", "Done!") +- About to commit/push/PR without verification +- Relying on partial verification +- Thinking "just this once" +- Tired and wanting work over +- **ANY wording implying success without having run verification** +- **Modified CLAUDE.md without explicit user request** (CLAUDE.md is user-maintained, never auto-update) + +## Rationalization Prevention + +| Excuse | Reality | +|--------|---------| +| "Should work now" | RUN the verification | +| "I'm confident" | Confidence is not evidence | +| "Just this once" | No exceptions | +| "Tests pass" | Did you check spec compliance? | +| "Spec compliant" | Did you run the tests? | +| "I'm tired" | Exhaustion is not an excuse | +| "Partial check is enough" | Partial proves nothing | + +## Quality Gates + +**This command enforces quality gates:** + +1. **All tests must pass** +2. **Code hygiene review clean** (mechanical defects) +3. **100% spec compliance required** +4. **No spec drift allowed** +5. **All success criteria must be met** + +**No exceptions. No shortcuts.** + +These gates exist to prevent: +- Incomplete implementations +- Untested code +- Spec/code divergence +- False claims of completion + +## Remember + +**Verification is not optional.** + +- Don't skip verification "just this once" +- Don't claim completion without verification +- Don't ignore failing gates + +**Verification failures are information.** + +- Tests failing? Code has bugs +- Spec compliance failing? Missing features +- Drift detected? Synchronization problem +- Criteria not met? Work incomplete + +**No shortcuts for verification.** + +Run the command. Read the output. Check the spec. THEN claim the result. + +**Fix issues, don't rationalize past them.** + +**Evidence before assertions. Always.** + +This is non-negotiable. diff --git a/.specify/extensions/spex-gates/extension.yml b/.specify/extensions/spex-gates/extension.yml new file mode 100644 index 00000000..6330f1d7 --- /dev/null +++ b/.specify/extensions/spex-gates/extension.yml @@ -0,0 +1,49 @@ +schema_version: "1.0" + +extension: + id: spex-gates + name: "Spex Quality Gates" + version: "1.0.0" + description: "Quality gates for specification-driven development: spec review, plan review, code review, and verification" + author: cc-spex + license: MIT + +requires: + speckit_version: ">=0.5.2" + +provides: + commands: + - name: speckit.spex-gates.review-spec + file: commands/speckit.spex-gates.review-spec.md + description: "Review spec soundness, completeness, and implementability" + - name: speckit.spex-gates.review-plan + file: commands/speckit.spex-gates.review-plan.md + description: "Post-planning quality validation with coverage matrix and red flag scanning" + - name: speckit.spex-gates.review-code + file: commands/speckit.spex-gates.review-code.md + description: "Review code against spec compliance with deviation tracking" + - name: speckit.spex-gates.verify + file: commands/speckit.spex-gates.verify.md + description: "Final verification gate: tests, code hygiene, spec compliance, drift check" + - name: speckit.spex-gates.stamp + file: commands/speckit.spex-gates.stamp.md + description: "Stamp command - invokes verification before completion" + +hooks: + after_specify: + command: speckit.spex-gates.review-spec + optional: false + description: "Review spec soundness after specification" + after_tasks: + command: speckit.spex-gates.review-plan + optional: false + description: "Review plan and task quality after task generation" + after_implement: + command: speckit.spex-gates.review-code + optional: false + description: "Review code compliance after implementation" + +tags: + - "spex" + - "quality" + - "review" diff --git a/.specify/extensions/spex-teams/commands/speckit.spex-teams.implement.md b/.specify/extensions/spex-teams/commands/speckit.spex-teams.implement.md new file mode 100644 index 00000000..ec070d01 --- /dev/null +++ b/.specify/extensions/spex-teams/commands/speckit.spex-teams.implement.md @@ -0,0 +1,27 @@ +--- +description: "Parallel implementation via Agent Teams for independent tasks" +--- + +# Teams Implement + +Standalone parallel implementation command. The ship pipeline routes to this command +(instead of standard implement) when spex-teams is enabled and 2+ independent tasks exist. + +## Prerequisites + +- CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS must be set +- tasks.md must exist with task breakdown +- spex-gates extension must be enabled + +## Execution + +1. Read tasks.md and parse the task list +2. Count tasks marked with [P] (parallel-eligible) +3. If 2+ independent tasks: invoke speckit.spex-teams.orchestrate for parallel agent spawning +4. If <2 independent tasks: fall back to standard implement (inform user) +5. Check .spex-state for autonomous mode; suppress prompts if in ship pipeline + +## Ship Pipeline Integration + +The ship command routes here when spex-teams is enabled and tasks.md has 2+ independent tasks. +This command is NOT invoked via a hook - it is called directly by ship or the user. diff --git a/.specify/extensions/spex-teams/commands/speckit.spex-teams.orchestrate.md b/.specify/extensions/spex-teams/commands/speckit.spex-teams.orchestrate.md new file mode 100644 index 00000000..d96b6717 --- /dev/null +++ b/.specify/extensions/spex-teams/commands/speckit.spex-teams.orchestrate.md @@ -0,0 +1,148 @@ +--- +description: "Unified team orchestration: parallel task implementation with spec guardian review pattern via Claude Code Agent Teams" +--- + +# Teams Orchestration: Parallel Task Implementation + +## Overview + +This command orchestrates parallel task implementation using Claude Code Agent Teams. The lead session analyzes the task dependency graph, spawns teammates in isolated worktrees for independent task groups, reviews all changes against spec.md via the spec guardian pattern, and coordinates merges. The spec guardian review loop is always-on: every teammate's work is reviewed for spec compliance before merging. + +## Prerequisites + +### CC Teams Feature Flag + +Check if Agent Teams is enabled: + +```bash +# Check settings.local.json for the feature flag +FLAG=$(jq -r '.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS // ""' .claude/settings.local.json 2>/dev/null) +``` + +**If the flag is not set (`""` or missing):** + +1. Set it in `.claude/settings.local.json`: + ```bash + jq '.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = "1"' .claude/settings.local.json > /tmp/settings.json && mv /tmp/settings.json .claude/settings.local.json + ``` +2. Inform the user: "Agent Teams feature flag has been enabled. Please restart Claude Code for teams to activate." +3. **Fall back to sequential implementation** for this session (teams will work on next run). + +**If the flag is set:** Proceed with team orchestration. + +**If the flag becomes unset mid-session** (e.g., user restarts without it): The pre-flight check runs at skill invocation time, not continuously. If the env var disappears mid-session, already-spawned teammates continue working. On next invocation, the check will catch the missing flag and fall back to sequential. + +## Task Graph Analysis + +Read the tasks.md file and analyze the dependency structure: + +1. **Parse all tasks** with their IDs, descriptions, and phase membership +2. **Identify dependency relationships** from the Dependencies section and phase ordering +3. **Group tasks by independence**: tasks that can execute simultaneously (no shared dependencies, different files) +4. **Identify blocked tasks**: tasks that must wait for others to complete first + +### Parallelism Assessment + +Evaluate whether teams add value: + +- **If 0-1 independent task groups exist** (everything is sequential): Skip team creation, execute tasks sequentially in the current session. Report: "Tasks are sequential, no parallelism benefit. Executing directly." +- **If 2+ independent task groups exist**: Proceed with team spawning. + +## Teammate Spawning + +### Spawn Rules + +- Spawn **one teammate per independent task group** (not one per task) +- **Maximum 5 teammates** (CC Teams best practice for coordination overhead) +- If more than 5 independent groups, batch them: assign multiple groups to the same teammate sequentially +- **Never spawn more teammates than independent groups** +- **isolation: "worktree"** - each teammate gets its own git worktree for clean file isolation + +### Spawn Prompt Template + +Each teammate receives this context in its spawn prompt: + +``` +You are implementing tasks for the [feature-name] feature. +You are working in an isolated git worktree. +Your work will be reviewed against spec.md before merging. + +## Your Assigned Tasks + +[List the specific tasks assigned to this teammate with task IDs] + +## Spec Context + +[Contents of spec.md for this feature] + +## Working Rules + +1. Implement each task completely before moving to the next +2. Commit after each logical group with descriptive messages +3. When all your tasks are done, message the lead: "Tasks complete, ready for review" +4. If you encounter a blocker, message the lead with details +5. Do not modify files outside your assigned task scope +6. Use "Assisted-By: Claude Code" as the git commit tagline +``` + +### Spawning Process + +Tell Claude to create an agent team: + +``` +Create an agent team for parallel implementation of [feature-name]. + +Spawn [N] teammates: +- Teammate 1: [task group description] - tasks [IDs] +- Teammate 2: [task group description] - tasks [IDs] +... + +Each teammate should implement their assigned tasks independently in their worktree. +Wait for all teammates to complete before proceeding to review. +``` + +## Completion Waiting + +After spawning teammates: + +1. **Wait for all teammates to finish** their assigned tasks +2. **Do not implement tasks yourself** while teammates are working (coordinate only) +3. **Monitor for stuck teammates**: if a teammate stops responding or errors, note the issue +4. **Handle teammate failures**: if a teammate crashes mid-task, either: + - Spawn a replacement teammate for the remaining tasks + - Fall back to implementing the remaining tasks directly + +## Spec Guardian Review Loop + +When a teammate reports completion, the lead reviews their changes: + +1. **Review changes**: Examine the teammate's commits and modified files +2. **Run spec compliance check** via `speckit.spex-gates.review-code` against spec.md +3. **If PASS**: Merge worktree changes, update tasks.md checkboxes to `[X]` for completed tasks +4. **If FAIL**: Send feedback to the teammate with specific spec violations. The teammate fixes the issues and re-submits for review. +5. **If 3+ failures on the same task**: Report to the user and pause. Do not continue retrying. + +The lead never implements code directly. The lead's sole job during this phase is review and coordination. + +## Sequential Fallback + +When teams cannot be used (feature flag not active, single task, linear dependencies): + +Execute tasks sequentially in the current session following the standard implementation flow from tasks.md. This is the normal behavior when the teams trait is not active. + +**Mixed independence**: When some tasks are independent and others are sequential (e.g., 1 of 3 tasks is independent, 2 are sequential), group the sequential tasks together as one teammate's workload and assign the independent task to a separate teammate. If only one independent group results, fall back to sequential execution. + +## Key Principles + +- **Teams for parallelism, not complexity**: Only use teams when genuine parallel work exists +- **Lead never implements**: The lead's job is review and coordination +- **Spec is the standard**: All review decisions based on spec.md +- **Worktrees prevent conflicts**: Each teammate has clean file isolation +- **Graceful degradation**: Always fall back to sequential if teams can't help +- **Respect task dependencies**: Never assign dependent tasks to different teammates + +## Failure Handling + +- **Teammate crashes**: Spawn a replacement teammate for unfinished tasks, or implement directly if near the end of the work +- **Merge conflicts**: Do NOT auto-resolve. Report the conflict to the user and wait for guidance +- **Review deadlock (3+ attempts)**: Message the teammate to stop work, report the situation to the user, and pause orchestration diff --git a/.specify/extensions/spex-teams/commands/speckit.spex-teams.research.md b/.specify/extensions/spex-teams/commands/speckit.spex-teams.research.md new file mode 100644 index 00000000..f7bb87ca --- /dev/null +++ b/.specify/extensions/spex-teams/commands/speckit.spex-teams.research.md @@ -0,0 +1,151 @@ +--- +description: "Parallel codebase research for planning via Claude Code Agent Teams" +--- + +# Teams Research: Parallel Codebase Exploration for Planning + +## Overview + +This command orchestrates parallel codebase research using Claude Code Agent Teams during the plan phase. The lead session analyzes the spec to identify research topics, spawns research agents to explore different parts of the codebase simultaneously, collects their findings, and then generates the plan with comprehensive codebase knowledge. + +## Prerequisites + +### CC Teams Feature Flag + +Check if Agent Teams is enabled: + +```bash +# Check settings.local.json for the feature flag +FLAG=$(jq -r '.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS // ""' .claude/settings.local.json 2>/dev/null) +``` + +**If the flag is not set (`""` or missing):** + +1. Set it in `.claude/settings.local.json`: + ```bash + jq '.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = "1"' .claude/settings.local.json > /tmp/settings.json && mv /tmp/settings.json .claude/settings.local.json + ``` +2. Inform the user: "Agent Teams feature flag has been enabled. Please restart Claude Code for teams to activate." +3. **Fall back to single-session research** for this session (teams will work on next run). + +**If the flag is set:** Proceed with team research. + +## Phase 1: Research Topic Identification + +Read the spec.md and identify what codebase knowledge is needed to create a solid plan. + +### Identify Research Areas + +From the spec, extract: + +1. **Existing code to modify**: Which files, modules, or packages does the spec reference or affect? +2. **Patterns to follow**: What existing patterns in the codebase should the plan adopt? +3. **Integration points**: Where does the new feature connect to existing code? +4. **Technology questions**: What libraries, frameworks, or tools are already in use that are relevant? + +### Group into Independent Research Topics + +Organize the areas into independent research topics that can be explored in parallel. Each topic should be: + +- **Self-contained**: An agent can research it without needing results from other topics +- **Focused**: Specific enough to produce actionable findings (not "explore the whole codebase") +- **Relevant**: Directly needed for plan creation + +**Examples of good research topics:** +- "Explore the authentication module: middleware chain, session handling, token validation patterns" +- "Map the database schema and migration patterns for the user-related tables" +- "Analyze the existing API endpoint structure: routing, validation, error handling conventions" +- "Review the test infrastructure: test helpers, fixtures, integration test patterns" + +### Parallelism Assessment + +- **If 0-1 research topics exist** (spec is simple or self-contained): Skip team creation, research directly in the current session. Report: "Single research area, no parallelism benefit. Researching directly." +- **If 2+ independent research topics exist**: Proceed with agent spawning. + +## Phase 2: Research Agent Spawning + +### Spawn Rules + +- Spawn **one agent per research topic** +- **Maximum 4 research agents** (research is read-only, so less coordination overhead than implementation, but keep it bounded) +- If more than 4 topics, merge the least complex ones together +- **All agents are read-only**: They explore and report, they do not modify files + +### Agent Prompt Template + +Each research agent receives: + +``` +You are a codebase research agent for the [feature-name] feature planning phase. + +## Your Research Topic + +[Description of what to research] + +## Spec Context + +[Relevant sections of spec.md that motivate this research] + +## Research Instructions + +1. Explore the relevant code thoroughly using Read, Grep, and Glob tools +2. Document your findings in a structured format: + - **Files examined**: List the key files you looked at + - **Patterns found**: Describe coding patterns, conventions, and structures + - **Integration points**: Where new code would connect to existing code + - **Constraints discovered**: Anything that limits or shapes the implementation approach + - **Recommendations**: Suggest how the plan should handle this area +3. Be specific: include file paths, function names, and line references +4. Do NOT modify any files. This is a read-only research mission. +5. When done, send your findings back to the lead. +``` + +### Spawning Process + +Create an agent team for parallel research: + +``` +Create an agent team for codebase research on [feature-name]. + +Spawn [N] research agents: +- Agent 1: [research topic description] +- Agent 2: [research topic description] +... + +Each agent should explore the codebase and report findings. Read-only, no file modifications. +Wait for all agents to complete before proceeding. +``` + +## Phase 3: Findings Consolidation + +After all research agents report back: + +1. **Collect all findings** from teammate messages +2. **Synthesize**: Identify common patterns across findings, resolve contradictions, note gaps +3. **Build a research summary**: Organize findings by relevance to plan sections +4. **Identify any remaining unknowns**: If research revealed new questions, note them for the plan's assumptions section + +## Phase 4: Plan Generation + +With research findings in hand, generate the plan: + +1. Use the consolidated research as input alongside the spec +2. Reference specific files, patterns, and integration points discovered by agents +3. The plan should reflect the actual codebase state, not assumptions +4. Include a brief "Research Basis" note in the plan acknowledging what was explored + +Then proceed with normal plan-phase flow (review-spec if spex-gates extension is active, etc.). + +## Sequential Fallback + +When teams cannot be used (feature flag not active, single research topic, simple spec): + +Research the codebase directly in the current session, then generate the plan. This is the normal behavior when the teams extension is not active. + +## Key Principles + +- **Research is read-only**: Agents explore, they never modify files +- **Lead consolidates and plans**: Research agents gather data, the lead makes design decisions +- **Breadth over depth**: Better to have a broad understanding than deep knowledge of one area +- **Graceful degradation**: Always fall back to single-session research if teams can't help +- **Keep research focused**: Every research topic must tie back to a plan need from the spec diff --git a/.specify/extensions/spex-teams/config-template.yml b/.specify/extensions/spex-teams/config-template.yml new file mode 100644 index 00000000..2320367e --- /dev/null +++ b/.specify/extensions/spex-teams/config-template.yml @@ -0,0 +1,4 @@ +# Teams Extension Configuration +agent_teams: + env_var: CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS + max_teammates: 5 diff --git a/.specify/extensions/spex-teams/extension.yml b/.specify/extensions/spex-teams/extension.yml new file mode 100644 index 00000000..bdfe8cdb --- /dev/null +++ b/.specify/extensions/spex-teams/extension.yml @@ -0,0 +1,45 @@ +schema_version: "1.0" + +extension: + id: spex-teams + name: "Spex Agent Teams" + version: "1.0.0" + description: "Parallel implementation via Claude Code Agent Teams" + author: cc-spex + license: MIT + +requires: + speckit_version: ">=0.5.2" + extensions: + - id: spex-gates + version: ">=1.0.0" + +provides: + commands: + - name: speckit.spex-teams.orchestrate + file: commands/speckit.spex-teams.orchestrate.md + description: "Parallel task implementation with spec guardian review pattern via Agent Teams" + - name: speckit.spex-teams.research + file: commands/speckit.spex-teams.research.md + description: "Parallel codebase research during planning via Agent Teams" + - name: speckit.spex-teams.implement + file: commands/speckit.spex-teams.implement.md + description: "Standalone parallel implementation via Agent Teams for independent tasks" + + config: + - name: "teams-config.yml" + template: "config-template.yml" + description: "Agent Teams configuration" + required: false + +hooks: + before_plan: + command: speckit.spex-teams.research + optional: true + prompt: "Run parallel codebase research?" + description: "Parallel codebase research during planning via Agent Teams" + +tags: + - "spex" + - "teams" + - "parallel" diff --git a/.specify/extensions/spex-worktrees/commands/speckit.spex-worktrees.manage.md b/.specify/extensions/spex-worktrees/commands/speckit.spex-worktrees.manage.md new file mode 100644 index 00000000..28b91dba --- /dev/null +++ b/.specify/extensions/spex-worktrees/commands/speckit.spex-worktrees.manage.md @@ -0,0 +1,528 @@ +--- +name: speckit.spex-worktrees.worktree +description: Manage git worktrees for isolated feature development - create after specify, list active worktrees, finish and cleanup +argument-hint: "[list|cleanup|finish]" +--- + +# Git Worktree Management for spex + +## Overview + +This command manages git worktrees to isolate feature development. It supports four actions: + +- **create**: Called by the `after_specify` hook after `speckit-specify` completes. Creates a worktree, restores `main`, and prints switch instructions. +- **list**: Shows all active feature worktrees with path, branch, and feature name. +- **finish**: Merges the current worktree's branch into the default branch and removes the worktree. Use when implementation is complete. +- **cleanup**: Detects worktrees whose branches are merged and offers removal. + +## Action Routing + +Determine the action from the argument: + +- If invoked with argument `create` (from the `after_specify` hook): the action is **create**. Execute immediately, no confirmation needed. +- If invoked with argument `finish`: the action is **finish**. +- If invoked with argument `cleanup`: the action is **cleanup**. +- Otherwise (no args, `list`, or invoked directly): the action is **list**. + +## Prerequisites + +The project must be a git repository. + +```bash +git rev-parse --git-dir >/dev/null 2>&1 || { echo "ERROR: Not a git repository"; exit 1; } +``` + +## Action: Create + +This action runs after `speckit-specify` has created a feature branch and spec files. + +### Step 1: Read Configuration + +Read `base_path` from the worktrees extension config (or default to `..`): + +```bash +WORKTREE_CONFIG=".specify/extensions/spex-worktrees/worktree-config.yml" +BASE_PATH=$(yq -r '.worktrees.base_path // ".."' "$WORKTREE_CONFIG" 2>/dev/null || echo "..") +``` + +Default: `..` (sibling directory to the repo root). + +### Step 2: Get Current Branch + +```bash +BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD) +``` + +Verify it matches the `NNN-feature-name` pattern. If not, warn that worktree creation only applies to feature branches and skip. + +### Step 3: Detect If Already in a Worktree + +A git worktree has a `.git` file (not directory) pointing to the main repo. Detect this: + +```bash +REPO_ROOT=$(git rev-parse --show-toplevel) +GIT_DIR=$(git rev-parse --git-dir) + +# If git-dir is not /.git, we're inside a worktree +if [ "$GIT_DIR" != "$REPO_ROOT/.git" ] && [ "$GIT_DIR" != ".git" ]; then + echo "WARNING: Already inside a git worktree. Skipping worktree creation." + echo "Worktree nesting is not supported." + # Stop here. Do not proceed to subsequent steps. +fi +``` + +If inside a worktree, skip the entire create action. Do not proceed to any subsequent Create steps. + +### Step 4: Compute Target Path and Validate + +Derive the repo name from the repository root and build the worktree path: + +```bash +REPO_NAME=$(basename "$(git rev-parse --show-toplevel)") + +# Handle both absolute and relative base paths +if [[ "$BASE_PATH" = /* ]]; then + RESOLVED_BASE=$(cd "$BASE_PATH" && pwd) +else + RESOLVED_BASE=$(cd "$REPO_ROOT/$BASE_PATH" && pwd) +fi +``` + +If the `cd` fails (directory does not exist), report a clear error and stop: + +```bash +if [ -z "$RESOLVED_BASE" ]; then + echo "ERROR: base_path '$BASE_PATH' does not resolve to a valid directory." + echo "Check worktrees.base_path in .specify/extensions/spex-worktrees/worktree-config.yml" + # Stop here. Do not proceed to subsequent steps. +fi +``` + +Build the worktree path using `@` as separator between repo name and branch: + +```bash +WORKTREE_PATH="${RESOLVED_BASE}/${REPO_NAME}@${BRANCH_NAME}" +``` + +Verify the worktree path is not inside the main repository (a `base_path` of `.` would cause this): + +```bash +case "$WORKTREE_PATH" in + "$REPO_ROOT"/*) + echo "ERROR: Worktree path is inside the main repository: $WORKTREE_PATH" + echo "Set base_path to a directory outside the repo (default: '..')" + # Stop here. Do not proceed to subsequent steps. + ;; +esac +``` + +Check if the target path already exists: + +```bash +if [ -d "$WORKTREE_PATH" ] || [ -f "$WORKTREE_PATH" ]; then + echo "ERROR: Target path already exists: $WORKTREE_PATH" + echo "Remove it manually or choose a different base_path in .specify/extensions/spex-worktrees/worktree-config.yml" + # Stop here. Do not proceed to subsequent steps. +fi +``` + +### Step 5: Commit All Tracked Changes to Feature Branch + +Before switching away from the feature branch, commit all modified tracked files. This includes spec files, `.specify/` configuration changes, and any other modified tracked files. Stage changes in two passes to avoid capturing unintended untracked files: + +```bash +# Stage modifications to already-tracked files +git add -u + +# Stage new spec and brainstorm artifacts (these are untracked but expected) +[ -d "specs/$BRANCH_NAME" ] && git add "specs/$BRANCH_NAME" +[ -d "brainstorm" ] && git add brainstorm/ +[ -d ".specify" ] && git add .specify/ + +if ! git diff --cached --quiet; then + git commit -m "feat: Add spec for $BRANCH_NAME + +Assisted-By: 🤖 Claude Code" +fi +``` + +Using `git add -u` (tracked modifications only) plus explicit paths for new spec artifacts limits the commit scope to intended files. The `git diff --cached --quiet` guard skips the commit when there are no staged changes, avoiding empty commits. + +### Step 5b: Capture Feature Directory Before Branch Switch + +The next step switches to the default branch, which changes tracked files on disk. Since `.specify/feature.json` is tracked, its contents will revert to whatever the default branch has. Capture the correct `feature_directory` now, while still on the feature branch: + +```bash +FEATURE_DIR="" +if [ -f ".specify/feature.json" ]; then + FEATURE_DIR=$(jq -r '.feature_directory // empty' ".specify/feature.json") +fi +# Fallback to branch-derived path if feature.json is missing or empty +FEATURE_DIR=${FEATURE_DIR:-"specs/$BRANCH_NAME"} +``` + +This value is used in Step 8b to set the correct feature context in the worktree. + +### Step 6: Restore Default Branch (before worktree creation) + +Git does not allow two worktrees to have the same branch checked out. Since `speckit-specify` just created and checked out the feature branch, we must switch back to the default branch before creating a worktree for that branch. + +Detect the default branch dynamically with a fallback chain: + +```bash +DEFAULT_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@') +if [ -z "$DEFAULT_BRANCH" ]; then + # origin/HEAD not set (common with git init + remote add). Try common names. + for candidate in main master; do + if git rev-parse --verify "$candidate" >/dev/null 2>&1; then + DEFAULT_BRANCH="$candidate" + break + fi + done +fi +DEFAULT_BRANCH=${DEFAULT_BRANCH:-main} +``` + +```bash +if ! git checkout "$DEFAULT_BRANCH" 2>&1; then + echo "WARNING: Could not switch back to $DEFAULT_BRANCH." + echo "You likely have uncommitted changes. Commit or stash them first." + echo "The repository remains on branch $BRANCH_NAME." + echo "Worktree creation skipped (cannot create worktree while branch is checked out here)." + # Stop here. Do not proceed to subsequent steps. +fi +``` + +### Step 7: Create the Worktree + +Now that the current worktree is on the default branch, create a new worktree for the feature branch. If this fails (disk full, permission denied, etc.), report the error clearly: + +```bash +if ! git worktree add "$WORKTREE_PATH" "$BRANCH_NAME" 2>&1; then + echo "ERROR: Failed to create worktree at $WORKTREE_PATH" + echo "The repository is on $DEFAULT_BRANCH. The feature branch $BRANCH_NAME still exists." + echo "Resolve the issue and retry, or switch to the branch manually: git checkout $BRANCH_NAME" + # Stop here. Do not proceed to subsequent steps. +fi +``` + +### Step 8: Copy Configuration to Worktree + +Gitignored config directories (`.claude/` and `.specify/`) won't exist in the new worktree. Copy them so spec-kit extensions, skills, and settings work immediately without re-running init: + +```bash +# Copy .specify/ (extensions registry, hooks, state, config) +if [ -d ".specify" ]; then + rsync -a --exclude='.git' ".specify/" "$WORKTREE_PATH/.specify/" +fi + +# Copy .claude/ (skills, settings, commands) +if [ -d ".claude" ]; then + rsync -a ".claude/" "$WORKTREE_PATH/.claude/" +fi +``` + +This ensures the worktree has the same extensions, hooks, permissions, and skills as the main repo. No `/spex:init` needed in the worktree. + +### Step 8b: Update feature.json and flow state for the Worktree Branch + +The copied `.specify/feature.json` may point to whatever feature was active on the default branch (since feature.json is tracked and reverts on branch switch). Write the correct value captured in Step 5b: + +```bash +FEATURE_JSON="$WORKTREE_PATH/.specify/feature.json" +echo "{\"feature_directory\": \"$FEATURE_DIR\"}" | jq '.' > "$FEATURE_JSON" +``` + +This writes the `FEATURE_DIR` value captured in Step 5b, which reflects the actual spec directory created by speckit-specify (not a branch-name derivation that may differ). + +The copied `.specify/.spex-state` also contains the old `feature_branch`. Update it to match the worktree's branch so the status line works correctly (the statusline script deletes state files where `feature_branch` doesn't match the current branch): + +```bash +STATE_FILE="$WORKTREE_PATH/.specify/.spex-state" +if [ -f "$STATE_FILE" ]; then + jq --arg branch "$BRANCH_NAME" --arg dir "$FEATURE_DIR" \ + '.feature_branch = $branch | .spec_dir = $dir' "$STATE_FILE" > "${STATE_FILE}.tmp" \ + && mv "${STATE_FILE}.tmp" "$STATE_FILE" +fi +``` + +This prevents both spec-kit commands and the status line from operating on the wrong feature context. + +### Step 9: Print Output + +Print a machine-readable line followed by human-readable instructions: + +```bash +echo "WORKTREE_CREATED path=$WORKTREE_PATH" +``` + +Then print instructions for the user: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Worktree created at │ +│ │ +│ To continue with planning/implementation: │ +│ cd && claude │ +│ │ +│ Config (.claude/ and .specify/) has been copied. │ +│ All extensions and skills are ready to use. │ +│ │ +│ The spec file contains all context from this session. │ +└─────────────────────────────────────────────────────────────┘ +``` + +Use the actual `WORKTREE_PATH` value (computed in Step 4) in the output. + +**Ship pipeline note:** When running inside a `speckit-spex-ship` pipeline, ship will automatically `cd` into the worktree and continue the pipeline there. No manual session restart needed. + +## Action: List + +Show all active feature worktrees for the project. + +### Step 1: Get Worktree List + +```bash +git worktree list --porcelain +``` + +Parse the output. Each worktree entry has: +- `worktree ` +- `HEAD ` +- `branch refs/heads/` + +### Step 2: Filter Feature Branches + +Only show worktrees whose branch matches the `NNN-*` feature branch pattern (three-digit prefix followed by a hyphen). + +Skip the main worktree (the original repo). + +### Step 3: Format Output + +Display a table with worktree directory names (`@` separator): + +``` +Active Feature Worktrees: + + Path Branch Feature + ───────────────────────────────────────────────────────────── + cc-spex@004-user-auth 004-user-auth user-auth + cc-spex@007-worktrees-trait 007-worktrees-trait worktrees-trait +``` + +Derive the display path by extracting the last path component from the worktree's absolute path. + +If no feature worktrees exist: + +``` +No active feature worktrees found. + +Create one by running /speckit-specify with the worktrees extension enabled. +``` + +## Action: Finish + +Merges the current worktree's feature branch into the default branch and removes the worktree. This is the recommended way to complete work in a spex worktree. + +**IMPORTANT:** Do NOT use Claude Code's `ExitWorktree` tool. Spex worktrees are created via `git worktree add`, not `EnterWorktree`, so `ExitWorktree` will refuse to operate on them. Always use git commands directly. + +### Step 1: Verify We're in a Worktree + +```bash +REPO_ROOT=$(git rev-parse --show-toplevel) +GIT_DIR=$(git rev-parse --git-dir) + +# A worktree has a .git file (not directory) pointing to the main repo +if [ "$GIT_DIR" = "$REPO_ROOT/.git" ] || [ "$GIT_DIR" = ".git" ]; then + echo "ERROR: Not inside a git worktree. Use 'finish' from within a spex worktree." + # Stop here. +fi +``` + +### Step 2: Get Branch and Main Worktree Info + +```bash +BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD) +WORKTREE_PATH=$(git rev-parse --show-toplevel) + +# Find the main worktree (first entry in worktree list) +MAIN_WORKTREE=$(git worktree list --porcelain | head -1 | sed 's/^worktree //') + +# Detect default branch +DEFAULT_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@') +if [ -z "$DEFAULT_BRANCH" ]; then + for candidate in main master; do + if git rev-parse --verify "$candidate" >/dev/null 2>&1; then + DEFAULT_BRANCH="$candidate" + break + fi + done +fi +DEFAULT_BRANCH=${DEFAULT_BRANCH:-main} +``` + +### Step 3: Ensure All Changes Are Committed + +```bash +if ! git diff --quiet || ! git diff --cached --quiet; then + echo "ERROR: Uncommitted changes in worktree. Commit or stash before finishing." + # Stop here. +fi +``` + +### Step 4: Ask User How to Proceed + +Use AskUserQuestion with: +- header: "Finish" +- multiSelect: false +- Options: + - "Merge and remove (Recommended)": "Fast-forward merge branch into default, remove worktree and branch" + - "Remove only": "Remove worktree and branch without merging (changes stay in git reflog)" + - "Cancel": "Keep worktree as-is" + +If "Cancel": stop. + +### Step 5: Switch CWD to Main Worktree + +**CRITICAL:** Switch the working directory to the main worktree BEFORE doing anything destructive. If cwd is inside the worktree being removed, all subsequent Bash commands will fail with "path does not exist". + +```bash +cd "$MAIN_WORKTREE" +``` + +### Step 6: Merge (if selected) + +If the user chose "Merge and remove": + +```bash +cd "$MAIN_WORKTREE" +git checkout "$DEFAULT_BRANCH" +git merge --ff-only "$BRANCH_NAME" 2>&1 +``` + +If fast-forward merge fails (branches diverged), ask the user: + +Use AskUserQuestion with: +- header: "Merge" +- multiSelect: false +- Options: + - "Create merge commit": "Merge with a merge commit (branches have diverged)" + - "Abort": "Keep worktree, resolve manually" + +If "Create merge commit": +```bash +git merge "$BRANCH_NAME" -m "Merge branch '$BRANCH_NAME' + +Assisted-By: 🤖 Claude Code" 2>&1 +``` + +If "Abort": stop. The cwd is already at the main worktree, so the user can navigate back. + +### Step 7: Remove Worktree and Branch + +```bash +# Remove worktree (cwd is already at main worktree from Step 5) +git worktree remove "$WORKTREE_PATH" 2>&1 + +# Delete the feature branch (it's merged or user chose remove-only) +git branch -d "$BRANCH_NAME" 2>&1 || git branch -D "$BRANCH_NAME" 2>&1 +``` + +### Step 8: Clear Flow State + +```bash +STATE_FILE=".specify/.spex-state" +if [ -f "$STATE_FILE" ]; then + rm -f "$STATE_FILE" +fi +``` + +### Step 9: Report + +``` +┌─────────────────────────────────────────────────────────┐ +│ Feature branch finished. │ +│ │ +│ - Merged to (if selected) │ +│ - Worktree removed: │ +│ - Branch deleted: │ +│ - Flow state cleared │ +│ │ +│ You are now in the main repo on . │ +└─────────────────────────────────────────────────────────┘ +``` + +If the user wants to push: `git push origin $DEFAULT_BRANCH` + +## Action: Cleanup + +Detect worktrees whose branches are merged and offer removal. + +### Step 1: Get Merged Branches + +```bash +# Detect default branch with fallback chain +DEFAULT_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@') +if [ -z "$DEFAULT_BRANCH" ]; then + for candidate in main master; do + if git rev-parse --verify "$candidate" >/dev/null 2>&1; then + DEFAULT_BRANCH="$candidate" + break + fi + done +fi +DEFAULT_BRANCH=${DEFAULT_BRANCH:-main} + +# Get all feature branches merged into the default branch +MERGED_BRANCHES=$(git branch --merged "$DEFAULT_BRANCH" | sed 's/^[* ]*//' | grep -E '^[0-9]{3}-') +``` + +### Step 2: Cross-Reference with Worktrees + +For each worktree with a feature branch, check if its branch appears in the merged list. + +### Step 3: Handle Merged Worktrees + +For each merged worktree, present it to the user and ask for confirmation: + +``` +Worktree cc-spex@004-user-auth (branch 004-user-auth) is merged into main. +Remove this worktree? (yes/no) +``` + +If the user confirms, first switch to the main repo root (to avoid cwd pointing at the deleted directory), then remove the worktree: + +```bash +# Switch cwd to the main worktree BEFORE removing the feature worktree. +# If cwd is inside the worktree being removed, all subsequent commands will +# fail with "Path does not exist" because the Bash tool persists cwd. +MAIN_WORKTREE=$(git worktree list --porcelain | head -1 | sed 's/^worktree //') +cd "$MAIN_WORKTREE" +git worktree remove +git branch -d +``` + +### Step 4: Handle Unmerged Worktrees + +For worktrees with unmerged branches, warn the user: + +``` +Worktree cc-spex@007-worktrees-trait (branch 007-worktrees-trait) has NOT been merged. +Skipping. Use --force to remove unmerged worktrees (data may be lost). +``` + +Only remove unmerged worktrees if the user explicitly confirms after seeing the warning. + +--- + +## Worktree Context for Downstream Commands + +When running in a worktree created by this extension, downstream spec-kit commands should be aware of the worktree context: + +### Planning Context + +You are likely running in a worktree created by the `worktrees` extension. The spec file in this worktree contains all decisions from the brainstorm/specify session. No separate handoff file is needed. + +### Implementation Context + +You are likely running in a worktree created by the `worktrees` extension. The spec and plan files in this worktree contain all context needed for implementation. No separate handoff file is needed. diff --git a/.specify/extensions/spex-worktrees/extension.yml b/.specify/extensions/spex-worktrees/extension.yml new file mode 100644 index 00000000..4a3467aa --- /dev/null +++ b/.specify/extensions/spex-worktrees/extension.yml @@ -0,0 +1,33 @@ +schema_version: "1.0" + +extension: + id: spex-worktrees + name: "Spex Worktrees" + version: "1.0.0" + description: "Git worktree isolation for feature development" + author: cc-spex + license: MIT + +requires: + speckit_version: ">=0.5.2" + tools: + - name: git + required: true + +provides: + commands: + - name: speckit.spex-worktrees.manage + file: commands/speckit.spex-worktrees.manage.md + description: "Manage git worktrees for isolated feature development" + +hooks: + after_specify: + command: speckit.spex-worktrees.manage + args: "create" + optional: false + description: "Create git worktree after specification" + +tags: + - "spex" + - "git" + - "worktree" diff --git a/.specify/extensions/spex/commands/speckit.spex.brainstorm.md b/.specify/extensions/spex/commands/speckit.spex.brainstorm.md new file mode 100644 index 00000000..07fb8f46 --- /dev/null +++ b/.specify/extensions/spex/commands/speckit.spex.brainstorm.md @@ -0,0 +1,391 @@ +--- +description: "Refine rough ideas into executable specifications through collaborative questioning, alternative exploration, and incremental validation" +--- + +# Brainstorming Ideas Into Specifications + +Help turn rough ideas into clear, agreed-upon feature descriptions through natural collaborative dialogue. The output is a brainstorm document capturing the problem, approaches considered, and the decision, ready for formal specification. + +**Key Principle:** Brainstorming explores WHAT to build and WHY. The formal spec (via `/speckit-specify`) and implementation planning come after. + + +Do NOT invoke any implementation skill, write any code, scaffold any project, create spec files, or take any implementation action during brainstorming. Brainstorming ends with a decision and a brainstorm document, not a spec. + + + +## Command Namespace: Use the correct prefixes + +spex extension commands use the `speckit-spex-*` prefix (e.g., `/speckit-spex-brainstorm`). +speckit core commands use the `speckit-` prefix (e.g., `/speckit-specify`, `/speckit-plan`). + +Commands like `/spex:specify`, `/spex:plan`, `/spex:implement`, `/spex:tasks` DO NOT EXIST. + + +## Checklist + +You MUST create a task for each of these items and complete them in order: + +1. **Initialize spec-kit** - ensure specify CLI and project are set up +2. **Explore project context** - check files, specs, constitution, recent commits +3. **Check for related brainstorms** - scan `brainstorm/` for existing docs on similar topics, offer to update or create new +4. **Ask clarifying questions** - one at a time, understand purpose/constraints/success criteria +5. **Propose 2-3 approaches** - with trade-offs and your recommendation +6. **Reach agreement** - confirm the chosen approach and scope with the user +7. **Write brainstorm document** - persist session summary to `brainstorm/NN-topic-slug.md` +8. **Update overview** - create or refresh `brainstorm/00-overview.md` with index, open threads, parked ideas +9. **Transition** - offer next steps + +## Process Flow + +```dot +digraph brainstorming { + "Initialize spec-kit" [shape=box]; + "Explore project context" [shape=box]; + "Related brainstorm exists?" [shape=diamond]; + "Ask clarifying questions" [shape=box]; + "Propose 2-3 approaches" [shape=box]; + "User chooses approach?" [shape=diamond]; + "Write brainstorm document" [shape=box]; + "Update overview" [shape=box]; + "Offer next steps" [shape=box]; + "Done" [shape=doublecircle]; + + "Initialize spec-kit" -> "Explore project context"; + "Explore project context" -> "Related brainstorm exists?"; + "Related brainstorm exists?" -> "Ask clarifying questions" [label="no, or user chooses new"]; + "Related brainstorm exists?" -> "Ask clarifying questions" [label="yes, user chooses update"]; + "Ask clarifying questions" -> "Propose 2-3 approaches"; + "Propose 2-3 approaches" -> "User chooses approach?"; + "User chooses approach?" -> "Ask clarifying questions" [label="needs more exploration"]; + "User chooses approach?" -> "Write brainstorm document" [label="agreed"]; + "Write brainstorm document" -> "Update overview"; + "Update overview" -> "Offer next steps"; + "Offer next steps" -> "Done"; +} +``` + +## Prerequisites + +Spec-kit must be initialized before brainstorming. If `.specify/` directory does not exist, tell the user to run `/spex:init` first and stop. + +## The Process + +### Understanding the idea + +**Check context first:** +- Review existing specs (if any) in `specs/` directory +- Check for constitution (`.specify/memory/constitution.md`) +- Review recent commits to understand project state +- Look for related features or patterns +- Scan `brainstorm/` directory for existing brainstorm documents (triggers revisit detection, see step 3 in checklist) + +**Assess scope before deep-diving:** +- Before asking detailed questions, assess scope: if the request describes multiple independent subsystems (e.g., "build a platform with chat, file storage, billing, and analytics"), flag this immediately. Don't spend questions refining details of a project that needs to be decomposed first. +- If the project is too large for a single spec, help the user decompose into sub-projects: what are the independent pieces, how do they relate, what order should they be built? Then brainstorm the first sub-project through the normal design flow. Each sub-project gets its own spec, plan, and implementation cycle. + +**Ask questions to refine:** +- For appropriately-scoped projects, ask questions one at a time to refine the idea +- Only one question per message. If a topic needs more exploration, break it into multiple questions +- Prefer multiple choice when possible, but open-ended is fine too +- Focus on: purpose, constraints, success criteria, edge cases +- Identify dependencies and integrations + +**Remember:** You're exploring WHAT needs to happen, not HOW it will be implemented. + +### Exploring approaches + +**Propose 2-3 different approaches:** +- Present options conversationally with trade-offs +- Lead with your recommended option +- Explain reasoning clearly +- Consider: complexity, maintainability, user impact + +**Questions to explore:** +- What are the core requirements vs. nice-to-have? +- What are the error cases and edge conditions? +- How does this integrate with existing features? +- What are the success criteria? + +### Reaching agreement + +Once the user picks an approach, confirm the scope: +- Summarize what's in scope and out of scope +- Confirm key requirements and constraints +- Note any open questions that the spec phase should resolve + +This is the decision point. The brainstorm document captures this agreement. + +### Transition: next steps + +After the brainstorm document is written and overview updated, offer the user a choice of how to proceed: + +Use AskUserQuestion with: +- header: "Next steps" +- multiSelect: false +- Options: + - "Specify step-by-step (/speckit-specify)": "Create a formal spec interactively, then plan and implement in separate steps" + - "Ship autonomously (/speckit-spex-ship)": "Run the full pipeline (specify, plan, implement, review) with configurable oversight. Best for small to mid-sized features." + - "Done for now": "Stop here. The brainstorm document is saved for later." + +If the user chooses "Specify step-by-step": invoke `/speckit-specify` with the brainstorm document as context. + +If the user chooses "Ship autonomously": invoke `/speckit-spex-ship` with the brainstorm document path as argument. + +If the user chooses "Done for now": end the session. + +## Brainstorm Document Structure + +Each brainstorm session produces a structured summary document. The document uses this format: + +```markdown +# Brainstorm: [Topic] + +**Date:** YYYY-MM-DD +**Status:** active | parked | abandoned | spec-created + +## Problem Framing +[What problem is being explored and why it matters] + +## Approaches Considered + +### A: [Approach Name] +- Pros: ... +- Cons: ... + +### B: [Approach Name] +- Pros: ... +- Cons: ... + +## Decision +[What was chosen and why, or "Parked: [reason]" if no decision was reached] + +## Key Requirements +[Core requirements agreed during brainstorming, to feed into the spec] + +## Open Questions +- [Unresolved question that the spec phase should address] +``` + +**Status values:** +- `active` - session completed, idea is being pursued +- `parked` - session stopped intentionally, idea may be revisited +- `abandoned` - session stopped, idea is not being pursued +- `spec-created` - a spec was created from this brainstorm (include spec path) + +## Overview Document Structure + +The `brainstorm/00-overview.md` file provides a navigable index of all brainstorm sessions: + +```markdown +# Brainstorm Overview + +Last updated: YYYY-MM-DD + +## Sessions + +| # | Date | Topic | Status | Spec | +|---|------|-------|--------|------| +| 01 | YYYY-MM-DD | topic-slug | spec-created | 0003 | +| 02 | YYYY-MM-DD | topic-slug | active | - | +| 03 | YYYY-MM-DD | topic-slug | parked | - | + +## Open Threads +- [Thread description] (from #NN) +- [Thread description] (from #NN) + +## Parked Ideas +- [Idea description] (#NN) + Reason: [why parked] +``` + +## Revisit Detection + +**When:** During step 3 of the checklist (after exploring project context). + +**How:** +1. Check if `brainstorm/` directory exists. If not, skip (no prior brainstorms). +2. List all `NN-*.md` files in `brainstorm/` (excluding `00-overview.md`). +3. Extract topic slugs from filenames (the part after the number prefix). +4. Compare the current brainstorm topic against existing slugs using keyword overlap. +5. If a related brainstorm document is found, use AskUserQuestion: + - **Option A: "Create new document"** - session produces a new numbered file + - **Option B: "Update existing"** - session appends a new dated section to the existing document + +**If "Update existing" is chosen:** +At session end, instead of creating a new file, append a new section to the existing document: + +```markdown + +--- + +## Revisit: YYYY-MM-DD + +### Updated Problem Framing +[How understanding has evolved] + +### New Approaches Considered +... + +### Updated Decision +... + +### Open Threads +- [New or updated threads] +``` + +Then update the overview to reflect any status or thread changes. + +## Writing the Brainstorm Document + +**When:** Step 7 of the checklist (after reaching agreement). + +You MUST write the brainstorm document at session end. This step is NOT optional. + +**Procedure:** + +1. **Create directory** if it does not exist: + ```bash + mkdir -p brainstorm/ + ``` + +2. **Detect next number** by scanning existing files: + ```bash + ls brainstorm/[0-9][0-9]-*.md 2>/dev/null + ``` + Use `max_existing_number + 1`. If no files exist, start at 01. Do NOT gap-fill (if 01 and 03 exist, next is 04). + +3. **Generate topic slug**: Derive from the brainstorm topic. Lowercase, hyphens, 2-4 words. + Example: "user authentication system" becomes `auth-system` + +4. **Determine status**: + - If the user chose to park the idea: `parked` + - If the user abandoned early: `abandoned` + - Otherwise: `active` + +5. **Write the document** using the Brainstorm Document Structure defined above. + +6. **Commit the brainstorm document**: + ```bash + git add brainstorm/NN-topic-slug.md + git commit -m "Add brainstorm: [topic] + + Assisted-By: 🤖 Claude Code" + ``` + +## Updating the Overview + +**When:** Step 8 of the checklist (immediately after writing the brainstorm document). + +You MUST update the overview after every brainstorm document write or update. This step is NOT optional. + +**Procedure:** + +1. **If `brainstorm/00-overview.md` does not exist**, create it. + If `brainstorm/` exists but `00-overview.md` is missing, regenerate it from all existing documents. + +2. **Always regenerate by scanning all documents** (idempotent full rebuild): + - List all `NN-*.md` files in `brainstorm/` (excluding `00-overview.md`) + - For each file, extract: number, date, status, spec reference (from frontmatter) + - For each file, extract all items under `## Open Questions` + - For each file with status `parked`, collect the idea and reason + +3. **Build the overview** using the Overview Document Structure defined above: + - Sessions table: one row per document, sorted by number + - Open Threads: aggregated from all documents, tagged with source `(from #NN)` + - Parked Ideas: collected from all `parked` documents + +4. **Write `brainstorm/00-overview.md`** with the rebuilt content. + +5. **Commit the overview update**: + ```bash + git add brainstorm/00-overview.md + git commit -m "Update brainstorm overview + + Assisted-By: 🤖 Claude Code" + ``` + +## Incomplete Session Handling + +**When:** The user stops the brainstorm before reaching agreement. + +**Zero-interaction guard:** If the session had no meaningful interaction (no approaches explored, no clarifying questions answered beyond the initial topic), do NOT prompt to save. Simply end the session without creating any artifacts. + +**For sessions with meaningful interaction** (approaches were discussed, questions were answered): + +Use AskUserQuestion to ask: **"Save this brainstorm session?"** + +- **Option A: "Save as parked"** - Write the document with status `parked`, update overview +- **Option B: "Save as abandoned"** - Write the document with status `abandoned`, update overview +- **Option C: "Discard"** - Do not create any brainstorm document, do not update overview + +If the user chooses to save, follow the "Writing the Brainstorm Document" and "Updating the Overview" procedures above. + +## Key Principles + +- **One question at a time** - Don't overwhelm with multiple questions +- **Multiple choice preferred** - Easier to answer than open-ended when possible +- **YAGNI ruthlessly** - Remove unnecessary features from all specs +- **Explore alternatives** - Always propose 2-3 approaches before settling +- **Be flexible** - Go back and clarify when something doesn't make sense +- **Separate WHAT from HOW** - Focus on requirements and outcomes, not implementation +- **Brainstorm, don't specify** - The formal spec comes later via `/speckit-specify` or `/speckit-spex-ship` + +## Common Pitfalls + +**Don't:** +- Write spec files during brainstorming +- Include implementation details ("use Redis for caching") +- Run review gates (that's for the specify phase) +- Skip exploring alternatives +- Rush past clarifying questions +- Invoke implementation skills + +**Do:** +- Focus on requirements and behavior +- Specify WHAT, defer HOW +- Explore multiple approaches +- Capture the decision and reasoning +- Note open questions for the spec phase +- Write the brainstorm document + +## Example Flow + +``` +User: I want to add a comment system to the blog + +You: I'm using speckit-spex-brainstorm to refine this idea. + +Let me understand the context first... +[Reviews existing code and specs] + +For the comment system, what level of features do you want? + +A) Basic - Name, email, comment text only +B) Standard - Above + threading/replies +C) Advanced - Above + reactions, editing, moderation +D) Full - Above + notifications, mentions, markdown + +User: B - Standard with threading + +You: Great. For threading, should replies be: + +A) Unlimited depth (comments on comments on comments...) +B) One level only (reply to top-level comments) +C) Two levels (reply and one nested level) + +[... conversation continues, approaches explored ...] + +You: Based on our discussion, here's what we've agreed on: + +- Standard comment system with one level of threading +- Comments require name, email, and text +- Newest first, replies nested under parent +- Markdown support for comment text +- No moderation in first version (out of scope) + +I'll save this as a brainstorm document. How would you like to proceed? + +A) Specify step-by-step (/speckit-specify) +B) Ship autonomously (/speckit-spex-ship) +C) Done for now +``` diff --git a/.specify/extensions/spex/commands/speckit.spex.evolve.md b/.specify/extensions/spex/commands/speckit.spex.evolve.md new file mode 100644 index 00000000..5b5bd7d4 --- /dev/null +++ b/.specify/extensions/spex/commands/speckit.spex.evolve.md @@ -0,0 +1,562 @@ +--- +description: "Handle spec/code mismatches through AI-guided analysis and user-controlled evolution" +--- + +# Spec Evolution and Reconciliation + +## Overview + +Handle spec/code mismatches through AI-guided analysis and user-controlled evolution. + +Specs WILL diverge from code. This is normal and healthy. The question is: which should change? + +This skill detects divergence, analyzes the mismatch, recommends resolution, and executes the change. + +## When to Use + +**Use this skill when:** +- Code review detects spec/code mismatch +- Verification finds spec compliance issues +- Developer explicitly requests evolution +- Implementation reveals better approach than spec +- Spec ambiguity discovered during implementation + +**Auto-triggered by:** +- `speckit-spex-gates-review-code` (when deviations found) +- `speckit-spex-gates-stamp` (when compliance fails) +- `speckit-spex-finish` (when verification finds compliance issues) + +**Don't use this skill when:** +- No mismatch exists (everything compliant) +- Spec doesn't exist yet -> Use `/speckit-specify` +- Multiple specs need consolidation -> Use `speckit-spex-spec-refactoring` + +## Prerequisites + +Spec-kit must be initialized. If `.specify/` directory does not exist, tell the user to run `/spex:init` first and stop. + +## Spec Selection + +If no spec is specified, discover available specs: + +```bash +# List all specs in the project +find specs/ -name "spec.md" -type f 2>/dev/null | head -20 +``` + +**If specs found:** Present list and ask user to select one using AskUserQuestion. + +Example: +``` +Found 2 specs in this project: +1. specs/0001-user-auth/spec.md +2. specs/0002-api-gateway/spec.md + +Which spec needs evolution/reconciliation? +``` + +**If no specs found:** Inform user: +``` +No specs found in specs/ directory. + +Spec evolution requires an existing spec to evolve. +Use `speckit-spex-brainstorm` or `/speckit-specify` to create one first. +``` + +## The Process + +### 0. Check Existing Artifacts + +Before analyzing or modifying, verify spec-kit artifacts exist: + +```bash +# Check what artifacts exist for this feature +SPEC_DIR="specs/[feature-dir]" # Replace with actual spec directory +echo "Checking for existing artifacts..." +[ -f "$SPEC_DIR/spec.md" ] && echo "spec.md exists" || echo "spec.md missing" +[ -f "$SPEC_DIR/plan.md" ] && echo "plan.md exists" || echo "plan.md not present" +[ -f "$SPEC_DIR/tasks.md" ] && echo "tasks.md exists" || echo "tasks.md not present" +``` + +**If spec.md is missing:** Cannot proceed with evolution. Use `/speckit-specify` first. + +**If plan.md or tasks.md exist:** +- These were generated by spec-kit and must be kept in sync +- After modifying the spec, regenerate dependent artifacts using: + - `/speckit-plan` - Regenerate plan + - `/speckit-tasks` - Regenerate tasks + - `/speckit-analyze` - Verify consistency + +**CRITICAL: Never manually edit plan.md or tasks.md. Always regenerate via /speckit-* commands after spec changes.** + +### 1. Detect Mismatches + +**Identify all spec/code divergences:** + +```bash +# Read spec +cat specs/features/[feature-name].md + +# Compare to implementation +# For each requirement in spec: +# - What does spec say? +# - What does code do? +# - Do they match? +``` + +**Categorize each mismatch:** +- **Missing in code**: Spec requires it, code doesn't have it +- **Extra in code**: Code implements it, spec doesn't mention it +- **Different behavior**: Spec says X, code does Y +- **Ambiguous spec**: Spec unclear, code made assumption + +**Document all mismatches with:** +- Spec requirement (quote from spec) +- Actual implementation (what code does) +- Location (file:line in code, section in spec) + +### 2. Analyze Each Mismatch + +**For each mismatch, determine:** + +**Type:** +- Architectural (affects system design) +- Behavioral (changes functionality) +- Cosmetic (naming, organization, details) + +**Severity:** +- **Critical**: Breaking change, security issue, data loss +- **Major**: Significant behavior change, API contract change +- **Minor**: Small deviation, non-breaking addition +- **Trivial**: Naming, formatting, implementation details + +**Impact:** +- User-facing vs internal +- Breaking vs non-breaking +- Risky vs safe + +### 3. Recommend Resolution + +**For each mismatch, recommend:** + +**Option A: Update Spec** +- When: Implementation reveals better approach +- Why: Spec was incomplete/wrong, code is better +- Impact: Spec changes to match reality + +**Option B: Fix Code** +- When: Code deviates from intended design +- Why: Spec is correct, code is wrong +- Impact: Code changes to match spec + +**Option C: Clarify Spec** +- When: Spec was ambiguous, code made reasonable choice +- Why: Make implicit explicit +- Impact: Spec expanded with details, code unchanged + +**Provide reasoning for recommendation:** +- Why this option is best +- Trade-offs of alternatives +- Risk assessment +- User impact + +### 4. Decide Resolution + +**Decision flow:** + +``` +Is this mismatch trivial/minor AND auto-update enabled? + Yes -> Auto-update with notification + No -> Ask user to decide + +User decides: + A) Update spec + B) Fix code + C) Clarify spec + D) Defer (mark as known deviation) +``` + +**Check user configuration:** + +```json +{ + "spex": { + "auto_update_spec": { + "enabled": true, + "threshold": "minor", + "notify": true + } + } +} +``` + +**Thresholds:** +- `none`: Never auto-update +- `minor`: Auto-update trivial/minor mismatches +- `moderate`: Include non-breaking behavioral changes + +### 5. Execute Resolution + +**Option A: Update Spec** + +1. Modify spec to match implementation +2. Add to spec changelog +3. Validate updated spec for soundness +4. Commit spec change with clear message + +```bash +# Commit +git add specs/features/[feature].md +git commit -m "Update spec: [change] + +Implementation revealed [reason for change]. + +Previous: [old requirement] +Updated: [new requirement] + +Assisted-By: Claude Code" +``` + +**Option B: Fix Code** + +1. Modify code to match spec +2. Update tests if needed +3. Verify spec compliance +4. Commit code change + +```bash +# Commit +git add [files] +git commit -m "Fix: Align [component] with spec + +Code was [what it did], spec requires [what spec says]. + +Updated to match spec requirement: [spec section] + +Assisted-By: Claude Code" +``` + +**Option C: Clarify Spec** + +1. Add detail to spec (keep code unchanged) +2. Make implicit assumptions explicit +3. Add to spec changelog +4. Commit clarification + +**Option D: Defer** + +1. Document as known deviation +2. Add to spec's "Known Deviations" section +3. Note reason and plan to address +4. Commit documentation + +### 6. Verify Reconciliation + +**After resolution:** + +```bash +# Re-check spec compliance +# Ensure mismatch is resolved +# Verify no new mismatches introduced +``` + +**If spec was updated (Options A or C), regenerate dependent artifacts:** + +**Regenerate plan and tasks to stay in sync:** + +If plan.md exists, invoke `/speckit-plan` to regenerate it. +If tasks.md exists, invoke `/speckit-tasks` to regenerate it. +Invoke `/speckit-analyze` to verify consistency across all artifacts. + +**VERIFICATION CHECKPOINT:** + +After regeneration, verify artifacts are in sync using `/speckit-analyze`. + +**Confirm:** +- Spec and code now aligned +- Tests still passing +- No regressions introduced +- All spec-kit artifacts regenerated and in sync + +## Checklist + +Use TodoWrite to track: + +- [ ] Detect all spec/code mismatches +- [ ] Categorize each mismatch (type, severity, impact) +- [ ] Analyze and recommend resolution for each +- [ ] Check user configuration for auto-update +- [ ] Decide resolution (auto or ask user) +- [ ] Execute resolution (update spec, fix code, or clarify) +- [ ] Verify reconciliation complete +- [ ] Commit changes with clear messages +- [ ] Update documentation if needed + +## Examples + +### Example 1: Auto-Update (Minor Addition) + +``` +[During verification] + +Spec compliance check: 95% + +Mismatch detected: + +**Mismatch 1: Response includes timestamp (MINOR)** +- **Spec says:** Return `{ id, name, email }` +- **Code does:** Returns `{ id, name, email, updated_at }` +- **Location:** src/api/users.ts:45, specs/features/user-api.md#response + +**Analysis:** +- Type: Behavioral (but non-breaking addition) +- Severity: Minor +- Impact: Non-breaking, adds useful information + +**Recommendation: Update Spec (Option A)** + +**Reasoning:** +- `updated_at` is standard practice for update endpoints +- Provides useful information to clients +- Non-breaking addition (clients can ignore it) +- Implementation is better than spec + +**User config:** auto_update_spec.threshold = "minor" + +**Action:** Auto-updating spec... + +Updated specs/features/user-api.md: +- Added `updated_at` to response format +- Changelog: "2025-11-10: Added updated_at timestamp to responses" + +Spec updated +Compliance now 100% +Committed to git + +Notification: Spec auto-updated with updated_at field (minor addition) +``` + +### Example 2: User Decision (Major Change) + +``` +[During code review] + +Spec compliance check: 85% + +Mismatch detected: + +**Mismatch 1: Validation behavior difference (MAJOR)** +- **Spec says:** "Reject requests with invalid avatar_url, return 422" +- **Code does:** "Accept invalid URLs, set avatar_url to null, return 200" +- **Location:** src/middleware/validation.ts:78, specs/features/user-profile.md#validation + +**Analysis:** +- Type: Behavioral (changes error handling) +- Severity: Major +- Impact: User-facing, affects API contract + +**Options:** + +**A) Update Spec - Lenient validation** +- Pros: More user-friendly, prevents frustration +- Cons: Inconsistent with other validation (strict) +- Risk: Users might not know avatar failed + +**B) Fix Code - Strict validation** +- Pros: Consistent with spec and other validation +- Cons: Worse UX when URL is invalid +- Risk: None (matches spec) + +**C) Clarify Spec - Explicit fallback** +- Pros: Best of both (validate but handle gracefully) +- Cons: More complex +- Risk: None + +**Recommendation: Option C** + +**Reasoning:** +Best practice is to validate input but handle gracefully. Update spec to explicitly define fallback behavior: validate URL, set to null if invalid, log warning, return success with indication. + +Which option do you prefer? + +User: C - explicit fallback + +You: Perfect. Updating spec to clarify fallback behavior... + +Updated spec: + +## Validation Rules + +### avatar_url +- Must be valid URL format if provided +- **Fallback behavior:** If URL validation fails: + - Set avatar_url to null + - Log validation error + - Return 200 (success) with warning in response + - Response includes: `warnings: ["avatar_url invalid, set to null"]` + +Code now matches clarified spec + +Spec compliance: 100% + +Committed to git +``` + +### Example 3: Fix Code (Spec Correct) + +``` +[During verification] + +Spec compliance check: 90% + +Mismatch detected: + +**Mismatch 1: Missing error handling (CRITICAL)** +- **Spec says:** "Handle database connection errors with retry logic (3 attempts)" +- **Code does:** "Single attempt, fails immediately on error" +- **Location:** src/db/client.ts:34, specs/features/data-persistence.md#error-handling + +**Analysis:** +- Type: Architectural (missing reliability mechanism) +- Severity: Critical +- Impact: Production reliability risk + +**Recommendation: Fix Code (Option B)** + +**Reasoning:** +- Spec defines critical reliability requirement +- Single-attempt fails too easily (production risk) +- Retry logic is standard practice +- Code is missing required feature + +**No user decision needed - critical spec requirement missing** + +Fixing code to implement retry logic... + +Tests updated to verify retry behavior +Spec compliance: 100% + +Committed code fix +``` + +## Auto-Update Configuration + +### Recommended Settings + +**For solo projects:** +```json +{ + "auto_update_spec": { + "enabled": true, + "threshold": "minor", + "notify": true + } +} +``` + +**For team projects:** +```json +{ + "auto_update_spec": { + "enabled": true, + "threshold": "none", + "notify": true + } +} +``` + +**For experimental work:** +```json +{ + "auto_update_spec": { + "enabled": true, + "threshold": "moderate", + "notify": true + } +} +``` + +## Classification Guidelines + +### Minor (Auto-Update Eligible) +- Adding timestamps to responses +- Renaming variables/functions +- Reorganizing code structure +- Adding logging/instrumentation +- Implementation detail changes + +### Major (Always Ask) +- Changing API contracts +- Modifying behavior +- Adding/removing features +- Changing error handling +- Architectural changes + +### Critical (Usually Fix Code) +- Security issues +- Data integrity problems +- Missing required features +- Incorrect business logic + +## Common Patterns + +### Pattern: Better Error Messages + +**Mismatch:** Spec says "return error", code returns detailed error with context + +**Resolution:** Update spec (minor) +- More detailed errors are better +- Non-breaking improvement + +### Pattern: Missing Edge Case + +**Mismatch:** Spec doesn't mention empty array, code handles it + +**Resolution:** Clarify spec (add edge case) +- Make implicit explicit +- Document intended behavior + +### Pattern: Performance Optimization + +**Mismatch:** Spec doesn't specify caching, code adds cache + +**Resolution:** Update spec (moderate) +- Document optimization in spec +- Ensure cache behavior is correct + +### Pattern: Different Architecture + +**Mismatch:** Spec implies synchronous, code is async + +**Resolution:** Ask user (major) +- Significant architectural change +- May affect other components + +## Remember + +**Spec evolution is normal and healthy.** + +- Specs are not contracts set in stone +- Implementation reveals reality +- Better approaches emerge during coding +- Ambiguities get discovered + +**The goal is alignment, not rigidity.** + +- Specs guide implementation +- Implementation informs specs +- Both should reflect truth + +**Always provide reasoning:** +- Why update spec vs fix code +- What's the impact +- What are the trade-offs + +**Trust the process:** +- Detect mismatches early +- Analyze thoughtfully +- Recommend clearly +- Execute decisively +- Verify completely + +**Spec and code in sync = quality software.** diff --git a/.specify/extensions/spex/commands/speckit.spex.extensions.md b/.specify/extensions/spex/commands/speckit.spex.extensions.md new file mode 100644 index 00000000..f65255fb --- /dev/null +++ b/.specify/extensions/spex/commands/speckit.spex.extensions.md @@ -0,0 +1,58 @@ +--- +description: "Manage spex extensions: enable, disable, or list active extensions" +--- + +# Spex Extensions Management + +Manage which spex extensions are active. Extensions provide additional capabilities such as quality gates, team orchestration, worktree isolation, and deep review. + +**Available extensions**: `spex-gates`, `spex-deep-review`, `spex-teams`, `spex-worktrees` + +--- + +## Parse Arguments + +Parse `$ARGUMENTS` for the subcommand and optional extension name: + +- No arguments or `list` -> **List** +- `enable ` -> **Enable** +- `disable ` -> **Disable** + +## Subcommand: List (default) + +Run via Bash: + +```bash +specify extension list +``` + +Display the output to the user. + +## Subcommand: Enable + +Run via Bash: + +```bash +specify extension enable +``` + +Report the result to the user. + +## Subcommand: Disable + +1. Run `specify extension list` and check if the extension is already disabled. If so, report that and STOP. +2. **Warn the user**: Disabling an extension requires regenerating all spec-kit files, which resets any manual customizations to `.claude/skills/speckit-*/SKILL.md` and `.specify/templates/*.md` files. +3. Use `AskUserQuestion` to confirm: + - **Question**: "Disabling an extension will reset all spec-kit files to defaults (losing any manual customizations). Proceed?" + - **Header**: "Confirm" + - **Options**: + - Label: "Yes, disable", Description: "Reset spec-kit files and remove this extension's overlays" + - Label: "Cancel", Description: "Keep current extension configuration unchanged" +4. If cancelled: report "Extension disable cancelled." and STOP. +5. If confirmed, run these commands sequentially via Bash: + ```bash + specify extension disable + specify init --here --ai claude --force + specify extension apply + ``` +6. Report which extensions are still active. diff --git a/.specify/extensions/spex/commands/speckit.spex.finish.md b/.specify/extensions/spex/commands/speckit.spex.finish.md new file mode 100644 index 00000000..11b44ad9 --- /dev/null +++ b/.specify/extensions/spex/commands/speckit.spex.finish.md @@ -0,0 +1,250 @@ +--- +description: "Complete a feature: verify, then merge/PR/keep with worktree-aware cleanup" +argument-hint: "[--create-pr]" +--- + +# Finish - Verify and Complete a Feature + +## Ship Pipeline Guard + +If `.specify/.spex-state` exists and its `status` is `running`, this command is part of an autonomous pipeline. Check the `ask` field and `create_pr` flag: + +```bash +AUTONOMOUS_MODE=false +AUTO_CREATE_PR=false +if [ -f ".specify/.spex-state" ]; then + STATUS=$(jq -r '.status // empty' .specify/.spex-state 2>/dev/null) + ASK=$(jq -r '.ask // "always"' .specify/.spex-state 2>/dev/null) + CREATE_PR=$(jq -r '.create_pr // false' .specify/.spex-state 2>/dev/null) + if [ "$STATUS" = "running" ] && [ "$ASK" != "always" ]; then + AUTONOMOUS_MODE=true + fi + if [ "$CREATE_PR" = "true" ]; then + AUTO_CREATE_PR=true + fi +fi +``` + +In autonomous mode: suppress all AskUserQuestion prompts. Auto-select "Merge to default branch" (or "Create PR" if `create_pr` is true in state or `--create-pr` argument is passed). + +## Argument Parsing + +If the argument `--create-pr` is passed, set `AUTO_CREATE_PR=true`. This skips the options prompt and goes directly to PR creation. + +## Phase 1: Verification + +Invoke `/speckit-spex-gates-verify` (the full verification gate). This runs: +1. Full test suite +2. Code hygiene review +3. Spec compliance validation (100% required) +4. Spec drift check +5. Success criteria verification + +**If verification fails:** STOP. Display the verification report with blocking issues. Do not proceed to merge/PR options. The user must fix the issues and re-run `/speckit-spex-finish`. + +**If verification passes:** Continue to Phase 2. + +## Phase 2: Commit Outstanding Changes + +Stage and commit any remaining tracked modifications: + +```bash +git add -u +if ! git diff --cached --quiet; then + git commit -m "chore: final changes before merge + +Assisted-By: 🤖 Claude Code" +fi +``` + +If the working tree is clean, skip this step. + +## Phase 3: Context Detection + +Detect the current environment to determine how to proceed: + +```bash +GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) +REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) +IN_WORKTREE=false +if [ "$GIT_DIR" != "$REPO_ROOT/.git" ] && [ "$GIT_DIR" != ".git" ]; then + IN_WORKTREE=true +fi + +CURRENT_BRANCH=$(git branch --show-current) + +DEFAULT_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@') +if [ -z "$DEFAULT_BRANCH" ]; then + for candidate in main master; do + if git rev-parse --verify "$candidate" >/dev/null 2>&1; then + DEFAULT_BRANCH="$candidate" + break + fi + done +fi +DEFAULT_BRANCH=${DEFAULT_BRANCH:-main} +``` + +**If already on the default branch:** Report "Verification passed. You are already on the default branch; no merge needed." Clean up state file (`rm -f .specify/.spex-state`). STOP. + +## Phase 4: Select Action + +If `AUTO_CREATE_PR` is true (from `--create-pr` argument or state file): skip the prompt and go directly to **Option B: Create PR**. + +If `AUTONOMOUS_MODE` is true: skip the prompt and go directly to **Option A: Merge to default branch**. + +Otherwise, present options using `AskUserQuestion` (`multiSelect: false`, header: "Finish"): + +**"Feature verified. How would you like to complete it?"** + +Options: +1. **"Merge to default branch (Recommended)"**: "Fast-forward merge into the default branch, clean up branch and worktree" +2. **"Push and create PR"**: "Push branch and open a pull request for team review" +3. **"Keep branch as-is"**: "Leave branch for manual handling later" + +## Phase 5: Execute Action + +### Option A: Merge to Default Branch + +**If in a worktree (`IN_WORKTREE` is true):** + +```bash +MAIN_WORKTREE=$(git worktree list --porcelain | head -1 | sed 's/^worktree //') +WORKTREE_PATH=$(git rev-parse --show-toplevel) + +# CRITICAL: Switch cwd to main worktree BEFORE any destructive operations. +# If cwd is inside the worktree being removed, subsequent commands fail. +cd "$MAIN_WORKTREE" + +git checkout "$DEFAULT_BRANCH" + +# Try fast-forward first, fall back to merge commit +git merge --ff-only "$CURRENT_BRANCH" 2>&1 +``` + +If fast-forward fails (branches diverged), ask the user (unless autonomous mode): + +In autonomous mode: create a merge commit automatically. + +Otherwise use `AskUserQuestion` (`multiSelect: false`, header: "Merge"): +- "Create merge commit": "Branches have diverged, merge with a merge commit" +- "Abort": "Keep worktree, resolve manually" + +If "Abort": STOP. The cwd is already at the main worktree. + +If merge commit: +```bash +git merge "$CURRENT_BRANCH" -m "Merge branch '$CURRENT_BRANCH' + +Assisted-By: 🤖 Claude Code" 2>&1 +``` + +After merge succeeds, remove worktree and branch: +```bash +git worktree remove "$WORKTREE_PATH" 2>&1 +git branch -d "$CURRENT_BRANCH" 2>&1 || git branch -D "$CURRENT_BRANCH" 2>&1 +``` + +**If NOT in a worktree:** + +```bash +git checkout "$DEFAULT_BRANCH" +git merge --ff-only "$CURRENT_BRANCH" 2>&1 +``` + +If fast-forward fails: same divergence handling as worktree path above. + +After merge: +```bash +git branch -d "$CURRENT_BRANCH" +``` + +Report: +``` +Merged `` into ``. Feature branch deleted. +``` + +### Option B: Push and Create PR + +```bash +REMOTE=$(git remote | grep -x upstream 2>/dev/null || echo origin) +BRANCH=$(git branch --show-current) +SPEC_DIR="specs/${BRANCH}" +FEATURE_NAME=$(head -1 "$SPEC_DIR/spec.md" | sed 's/^# Feature Specification: //') + +REVIEWERS_REL="$SPEC_DIR/REVIEWERS.md" +REVIEWERS_LINK="" +if [ -f "$REVIEWERS_REL" ]; then + REMOTE_URL=$(git remote get-url "$REMOTE" 2>/dev/null | sed 's/\.git$//' | sed 's|git@github.com:|https://github.com/|') + REVIEWERS_URL="${REMOTE_URL}/blob/${BRANCH}/${REVIEWERS_REL}" + REVIEWERS_LINK="> **[Review Guide](${REVIEWERS_URL})** for full context: motivation, key decisions, and scope boundaries." +fi + +COLLAB_CONFIG=".specify/extensions/spex-collab/collab-config.yml" +LABEL_FLAG="" +if [ -f "$COLLAB_CONFIG" ]; then + LABELS_ENABLED=$(yq -r '.labels.enabled // true' "$COLLAB_CONFIG" 2>/dev/null || echo "true") + IMPL_LABEL=$(yq -r '.labels.implement // "spex/implement"' "$COLLAB_CONFIG" 2>/dev/null || echo "spex/implement") + if [ "$LABELS_ENABLED" = "true" ]; then + LABEL_FLAG="--label ${IMPL_LABEL}" + fi +fi + +git push -u "$REMOTE" "$BRANCH" + +gh pr create \ + --title "$FEATURE_NAME [Spec + Impl]" ${LABEL_FLAG} \ + --body "$(cat < is verified and ready. Nothing was merged or pushed. + +When ready to finish: + /speckit-spex-finish Run again to merge or create PR +``` + +If NOT in a worktree: +``` +Branch is verified and ready. Nothing was merged or pushed. + +When ready to finish: + /speckit-spex-finish Run again to merge or create PR +``` + +## Phase 6: State and Status Line Cleanup + +After executing any option (merge, PR, or keep), remove the state file from both possible locations (absolute path from ship pipeline, and relative path from flow mode): + +```bash +rm -f .specify/.spex-state +if [ -n "${SHIP_STATE_FILE:-}" ] && [ -f "$SHIP_STATE_FILE" ]; then + rm -f "$SHIP_STATE_FILE" +fi +``` + +This removes the state file, which dismisses the status line (the statusline script exits silently when no state file exists). Works for both ship mode (where `SHIP_STATE_FILE` may point to a worktree path) and flow mode (where the state file is always relative). diff --git a/.specify/extensions/spex/commands/speckit.spex.flow-state.md b/.specify/extensions/spex/commands/speckit.spex.flow-state.md new file mode 100644 index 00000000..179754be --- /dev/null +++ b/.specify/extensions/spex/commands/speckit.spex.flow-state.md @@ -0,0 +1,44 @@ +--- +description: "Create or update flow state for step-by-step SDD workflow tracking" +argument-hint: "[running |clarified|implemented|gate ]" +--- + +# Flow State Management + +This command manages the `.specify/.spex-state` file with `"mode": "flow"` to enable the status line during step-by-step SDD workflow (as opposed to the autonomous ship pipeline). + +## Execution + +Locate and run the `spex-flow-state.sh` script, passing through all arguments: + +```bash +FLOW_STATE="$(find ~/.claude -name 'spex-flow-state.sh' 2>/dev/null | head -1)" +[ -x "$FLOW_STATE" ] || { echo "ERROR: spex-flow-state.sh not found"; exit 1; } +"$FLOW_STATE" "$@" +``` + +If invoked with no arguments (from the `after_specify` hook), pass `create`: + +```bash +"$FLOW_STATE" create +``` + +If invoked with `--spec-dir` context available (e.g., the spec directory is known), pass it: + +```bash +"$FLOW_STATE" create --spec-dir "specs/034-unified-setup-command" +``` + +## Available Commands + +| Command | Hook | What it does | +|---------|------|-------------| +| `create [--spec-dir ]` | `after_specify` | Create or update flow state (preserves gate fields if already exists) | +| `running ` | `before_*` | Set active phase shown as `▶` in status line | +| `running done` | `after_*` | Clear active phase indicator | +| `clarified` | `after_clarify` | Mark clarification complete | +| `implemented` | `after_implement` | Mark implementation complete | +| `gate ` | spex-gates hooks | Mark quality gate passed (review-spec, review-plan, review-code) | +| `cleanup` | (manual) | Remove state file | + +All commands are silent (no output) unless an error occurs. Ship mode state files are never overwritten. diff --git a/.specify/extensions/spex/commands/speckit.spex.help.md b/.specify/extensions/spex/commands/speckit.spex.help.md new file mode 100644 index 00000000..fabf931c --- /dev/null +++ b/.specify/extensions/spex/commands/speckit.spex.help.md @@ -0,0 +1,20 @@ +--- +description: "Quick reference for all spex commands and workflow" +--- + +# spex Help + +## Overview + +Display the spex quick reference with workflow diagram, command list, and guidance. + +## Behavior + +1. Read and display the quick reference content from `spex/docs/help.md` +2. Display the content exactly as written +3. Ask: "Any questions about the spex workflow? I can explain any command in detail." + +## Key Principles + +- **Reference mode is fast**: Just display the help content +- **Non-pushy**: Offer options, don't force workflows diff --git a/.specify/extensions/spex/commands/speckit.spex.ship.md b/.specify/extensions/spex/commands/speckit.spex.ship.md new file mode 100644 index 00000000..0e9d011a --- /dev/null +++ b/.specify/extensions/spex/commands/speckit.spex.ship.md @@ -0,0 +1,847 @@ +--- +description: "Autonomous full-cycle workflow: specify through verify with configurable oversight levels, auto-fix, and optional PR creation" +--- + +# Autonomous Full-Cycle Workflow (speckit-spex-ship) + +## CONTINUOUS EXECUTION RULE (READ THIS FIRST) + +**This pipeline runs ALL stages without stopping.** After completing any stage, you MUST immediately begin the next stage. There are no natural stopping points between stages. + +- Do NOT say "Ready for the next stage" and wait. +- Do NOT say "Shall I proceed?" and wait. +- Do NOT say "Proceeding to..." and wait. +- Do NOT treat a stage completion as a task completion. +- Do NOT output a summary and stop. + +The pipeline is ONE continuous task. It starts at the first stage and runs through the last stage. The ONLY reasons to pause are: +1. `ask` is `always` AND a review stage has findings requiring user input. +2. A blocker error occurs (test failure, syntax error, security issue). +3. All 9 stages have completed. + +**After every stage: update the state file, then immediately start the next stage.** No waiting, no confirmation, no stopping. + +## Overview + +This skill chains the entire spex workflow autonomously: specify, clarify, review-spec, plan, review-plan, tasks, implement, deep-review, and verify. Point it at a brainstorm document and choose an oversight level to control how much human oversight the pipeline requires. + +**This skill requires both `spex-gates` and `spex-deep-review` extensions to be enabled.** + +## Prerequisites + +### Extension Validation + +Check that required extensions are enabled: + +```bash +# Check for enabled extensions +GATES=$(specify extension list 2>/dev/null | grep -c 'spex-gates.*enabled' || echo 0) +DEEP_REVIEW=$(specify extension list 2>/dev/null | grep -c 'spex-deep-review.*enabled' || echo 0) + +if [ "$GATES" = "0" ] || [ "$DEEP_REVIEW" = "0" ]; then + echo "ERROR: speckit-spex-ship requires both spex-gates and spex-deep-review extensions." + echo "" + echo "Enable them with:" + echo " specify extension enable spex-gates spex-deep-review" + echo "" + echo "Missing extensions:" + [ "$GATES" = "0" ] && echo " - spex-gates" + [ "$DEEP_REVIEW" = "0" ] && echo " - spex-deep-review" +fi +``` + +If either extension is missing, **STOP** with the error message above. Do not proceed. + +### Dirty Worktree Check + +Before starting the pipeline, check for uncommitted changes that are NOT spex configuration files: + +```bash +# Filter out spex-generated files from dirty check +DIRTY=$(git status --porcelain 2>/dev/null | grep -v -E '^.{2} \.claude/(commands/speckit\.|settings)' | grep -v -E '^.{2} \.specify/(extensions/|\.spex-)' || true) +if [ -n "$DIRTY" ]; then + echo "Working tree has uncommitted non-spex changes:" + echo "$DIRTY" +fi +``` + +If there are dirty non-spex files, commit them automatically with a "WIP: save before ship" message, then proceed. Do NOT stop or ask the user. Spex config files (`.claude/skills/speckit-*`, `.claude/settings.*`, `.specify/extensions/`) are expected to be dirty after init and should be ignored. + +### External Tool Auth Validation + +If `--coderabbit` is explicitly set (not just inherited from config defaults), validate authentication at startup: + +```bash +# Only check if --coderabbit was explicitly passed as a flag +which coderabbit >/dev/null 2>&1 && coderabbit auth status 2>&1 || echo "CODERABBIT_AUTH_FAILED" +``` + +If auth check fails when CodeRabbit was explicitly requested, **STOP** with: +``` +ERROR: CodeRabbit authentication failed. +You explicitly requested CodeRabbit with --coderabbit, but auth is not configured. +Run without --coderabbit or configure CodeRabbit authentication first. +``` + +If CodeRabbit is only enabled via config defaults (not explicit flag), skip auth validation and let the deep-review stage handle missing tools gracefully. + +## Argument Parsing + +Parse the invocation arguments. The skill accepts: + +### Positional Argument + +- **brainstorm-file**: Path to a brainstorm document in `brainstorm/`. If omitted, auto-detect (see Brainstorm File Resolution below). + +### Flags + +| Flag | Default | Description | +|------|---------|-------------| +| `--ask ` | `smart` | One of: `always`, `smart`, `never` | +| `--create-pr` | off | Create a pull request after successful completion | +| `--resume` | off | Resume an interrupted pipeline from state file | +| `--start-from ` | (none) | Start from a specific stage (skips prior stages) | +| `--no-external` | (from config) | Disable all external review tools | +| `--external` | (from config) | Enable all external review tools | +| `--no-coderabbit` | (from config) | Disable CodeRabbit | +| `--coderabbit` | (from config) | Enable CodeRabbit | +| `--no-copilot` | (from config) | Disable Copilot | +| `--copilot` | (from config) | Enable Copilot | + +### Flag Resolution + +**Oversight level**: Validate that the value is one of `always`, `smart`, `never`. If invalid, fail with: +``` +ERROR: Invalid oversight level "X". Must be one of: always, smart, never +``` + +**External tool flags**: Follow the same resolution pattern as the `review-code` skill: + +1. Read config defaults from deep-review extension config: + ```bash + DEEP_REVIEW_CONFIG=".specify/extensions/spex-deep-review/deep-review-config.yml" + DEFAULT_CODERABBIT=$(yq -r '.external_tools.coderabbit // true' "$DEEP_REVIEW_CONFIG" 2>/dev/null) + DEFAULT_COPILOT=$(yq -r '.external_tools.copilot // true' "$DEEP_REVIEW_CONFIG" 2>/dev/null) + ``` + +2. Start with config defaults: + ``` + coderabbit = DEFAULT_CODERABBIT + copilot = DEFAULT_COPILOT + ``` + +3. Apply CLI flag overrides (flags always win, applied in order): + - `--external` sets both to true + - `--no-external` sets both to false + - `--coderabbit` / `--no-coderabbit` overrides coderabbit only + - `--copilot` / `--no-copilot` overrides copilot only + +4. Track whether `--coderabbit` was explicitly set (for auth validation). + +**`--resume` and `--start-from` are mutually exclusive** with each other. If both are provided, fail with: +``` +ERROR: Cannot use both --resume and --start-from. Choose one. +``` + +**`--resume` does not accept a brainstorm file.** If `--resume` is set alongside a brainstorm file, fail with: +``` +ERROR: Cannot specify a brainstorm file with --resume. The brainstorm file is read from the state file. +``` + +**`--start-from` allows a brainstorm file** when starting from `specify` (since it needs one). When starting from any other stage, a brainstorm file argument is ignored. + +### Valid Stage Names for --start-from + +The following stage names are accepted: `specify`, `clarify`, `review-spec`, `plan`, `tasks`, `review-plan`, `implement`, `review-code`, `finish`. + +If an invalid stage name is provided, fail with: +``` +ERROR: Invalid stage "X". Valid stages are: specify, clarify, review-spec, plan, tasks, review-plan, implement, review-code, finish +``` + +## Brainstorm File Resolution + +Resolve the brainstorm document to use as input: + +**If a path is provided**: Validate it exists. +```bash +[ -f "$BRAINSTORM_FILE" ] || echo "ERROR: Brainstorm file not found: $BRAINSTORM_FILE" +``` + +**If no path is provided**: Auto-detect the highest-numbered brainstorm file: +```bash +ls -1 brainstorm/[0-9]*.md 2>/dev/null | sort -t/ -k2 -V | tail -1 +``` + +**If no brainstorm files found**: Fail with: +``` +ERROR: No brainstorm files found in brainstorm/ directory. + +Available files: +$(ls brainstorm/ 2>/dev/null || echo " (directory does not exist)") + +Create a brainstorm document first with /speckit-spex-brainstorm +``` + +## State File Management + +The pipeline tracks its progress in `.specify/.spex-state` as JSON. **All state file operations use the `spex-ship-state.sh` script. Never write the state file directly.** + +Locate the script and set the absolute state file path: +```bash +SHIP_STATE="$(find ~/.claude -name 'spex-ship-state.sh' 2>/dev/null | head -1)" +# Use absolute path so state file location survives CWD changes (e.g., worktree switches) +export SHIP_STATE_FILE="$(pwd -P)/.specify/.spex-state" +``` + +**IMPORTANT:** Both `SHIP_STATE` (script path) and `SHIP_STATE_FILE` (absolute state file path) must be set before any state operations. The `SHIP_STATE_FILE` env var ensures the state script and statusline script always reference the same file, even when CWD changes during worktree creation. + +### Available Commands + +| Command | What it does | +|---------|-------------| +| `spex-ship-state.sh create [--ask ] [--start-from ]` | Create state file at pipeline start | +| `spex-ship-state.sh advance` | Advance to the next stage (auto-cleans up after stage 8) | +| `spex-ship-state.sh status` | Show current stage and status | +| `spex-ship-state.sh pause` | Set status to paused | +| `spex-ship-state.sh fail` | Set status to failed | +| `spex-ship-state.sh cleanup` | Remove state file (pipeline done) | + +### Stage Transitions + +**After every stage completes**, run: +```bash +SHIP_STATE_FILE="$SHIP_STATE_FILE" "$SHIP_STATE" advance +``` + +This advances `stage` and `stage_index` to the next stage with `status: running`. After the final stage (verify), `advance` automatically removes the state file and outputs `PIPELINE_COMPLETE`. + +**Do NOT manually write JSON to the state file. Always use the script.** + +## Ship Pipeline Guard + +The `.specify/.spex-state` file serves as a signal to sub-commands running inside the pipeline. When this file exists with `status: running`, each `/speckit-*` command MUST: + +- Complete its work normally +- Do NOT output a completion summary +- Do NOT ask "Shall I proceed?" or similar +- Do NOT use AskUserQuestion (unless `ask` is `always`) +- Return immediately so the pipeline can advance + +### speckit-specify guard + +When `.specify/.spex-state` exists with `status: running`: +- Complete the specification work normally +- Do NOT ask "Shall I proceed?" after spec creation +- Return immediately so the pipeline can advance + +### speckit-clarify guard + +When `.specify/.spex-state` exists with `status: running`: +- If `ask` is `smart` or `never`: Do NOT present clarification questions to the user. Select the recommended answer for each question yourself. Process all questions in a single pass and update the spec. +- Do NOT output a completion summary +- Return immediately so the pipeline can advance + +### speckit-plan guard + +When `.specify/.spex-state` exists with `status: running`: +- Complete the planning work normally +- Do NOT ask "Shall I proceed?" or "Ready for implementation." +- Return immediately so the pipeline can advance + +### speckit-tasks guard + +When `.specify/.spex-state` exists with `status: running`: +- Complete the task generation normally +- Do NOT ask "Shall I proceed?" or suggest next steps +- Return immediately so the pipeline can advance + +### speckit-implement guard + +When `.specify/.spex-state` exists with `status: running`: +- Complete the implementation work normally +- Do NOT output a completion summary +- Do NOT ask "Shall I proceed?" or suggest next steps +- Return immediately so the pipeline can advance + +## Resume Logic + +When `--resume` is set: + +1. Read the state file: + ```bash + if [ ! -f .specify/.spex-state ]; then + echo "ERROR: No interrupted pipeline found." + echo "Start a new pipeline with: /speckit-spex-ship " + exit 1 + fi + STATE=$(cat .specify/.spex-state) + ``` + +2. Extract the last stage and its index: + ```bash + LAST_STAGE=$(echo "$STATE" | jq -r '.stage') + LAST_INDEX=$(echo "$STATE" | jq -r '.stage_index') + AUTONOMY=$(echo "$STATE" | jq -r '.ask') + BRAINSTORM=$(echo "$STATE" | jq -r '.brainstorm_file') + ``` + +3. Check the `status` field to determine resume behavior: + - If `status` is `"paused"` or `"failed"`: resume from `LAST_INDEX` (retry the same stage). + - If `status` is `"running"`: resume from `LAST_INDEX` (the stage was interrupted mid-execution). + - If `status` is `"completed"`: report that the pipeline already completed and clean up the state file. + +4. If the calculated resume index is >= 9, the pipeline was already complete. Report this and clean up. + +5. Reset `retries` to 0 in the state file before resuming (so the resumed stage gets fresh retry attempts). + +6. Re-validate values from the state file before proceeding: + - Validate `ask` is one of `always`, `smart`, `never` + - Validate `brainstorm_file` exists (if resuming the specify stage) + - Validate `stage_index` is in range 0-8 + +7. Update the state file with `status: running` before proceeding. + +## Start-From Logic + +When `--start-from ` is set: + +1. Map the stage name to its index (0-8). + +2. Verify that expected artifacts exist for stages that depend on prior output: + - Stages `clarify` and later need `spec.md` to exist + - Stages `plan` and later need `spec.md` + - Stages `tasks` and later need `plan.md` + - Stages `review-plan` and later need `plan.md` and `tasks.md` + - Stages `implement` and later need `tasks.md` + +3. If expected artifacts are missing, **warn** (do not fail): + ``` + WARNING: Starting from stage "implement" but tasks.md was not found. + The implement stage may fail if required artifacts are missing. + Proceeding anyway... + ``` + +4. Create a fresh state file with the starting stage and begin execution. + +5. The brainstorm file is not needed when starting from a stage after `specify`. If starting from `specify`, a brainstorm file is required (auto-detect or fail). + +## Pipeline Discipline (MANDATORY) + +**These rules are non-negotiable. They override any judgment about efficiency or convenience.** + +### Rule 1: Every stage runs, in order, no exceptions + +When starting a fresh pipeline (no `--start-from`, no `--resume`), you MUST execute ALL 9 stages in sequence: specify, clarify, review-spec, plan, tasks, review-plan, implement, review-code, verify. + +You MUST NOT: +- Skip a stage because its output artifact already exists +- Skip a stage because you believe its output would be trivial +- Skip a stage because a previous conversation already produced its artifact +- Merge two stages into one (e.g., running plan and tasks together) +- Reorder stages for any reason + +### Rule 2: Fresh start means fresh artifacts + +When running from stage 0 (specify), the pipeline creates all artifacts from scratch. If `spec.md`, `plan.md`, or `tasks.md` already exist from a prior run, they are overwritten by the new pipeline run. Do NOT reuse artifacts from previous runs unless resuming with `--resume` or explicitly starting later with `--start-from`. + +### Rule 3: Only `--start-from` and `--resume` allow skipping + +These are the ONLY two mechanisms for starting at a stage other than specify: +- `--start-from `: User's explicit choice to skip prior stages. The user takes responsibility for ensuring prior artifacts exist and are valid. +- `--resume`: Continues from where a previous run was interrupted, using the state file. + +If neither flag is set, the pipeline starts at stage 0 and runs through stage 8. No automatic detection of "oh, we can skip ahead because artifacts exist." + +### Rule 4: Stage gate validation + +Before executing each stage, verify that: +1. The previous stage's state file entry shows it completed (stage_index is one less than current, or this is the first stage) +2. The state file status was updated to `running` for the current stage + +If a stage fails or is interrupted, the pipeline MUST NOT silently proceed to the next stage. It must either pause (for findings), fail (for errors), or retry (within the 2-retry limit). + +### Rule 5: No implicit intelligence + +Do NOT apply "smart" behavior to the pipeline flow itself: +- Do NOT decide that a brainstorm file is "clear enough" to skip clarify +- Do NOT decide that a spec is "simple enough" to skip review-spec +- Do NOT decide that implementation is "straightforward enough" to skip review-code +- Do NOT skip verify because all prior reviews passed + +The `--ask` flag controls oversight within review stages (how findings are handled). It does NOT control which stages run. ALL stages run regardless of the ask level. + +## Pipeline Initialization (BLOCKING - DO THIS FIRST) + +**You MUST complete these steps before invoking ANY speckit command or skill.** Do not skip ahead to stage execution. + +### Step 1: Locate the state script + +```bash +SHIP_STATE="$(find ~/.claude -name 'spex-ship-state.sh' 2>/dev/null | head -1)" +[ -x "$SHIP_STATE" ] && echo "SCRIPT_OK: $SHIP_STATE" || echo "SCRIPT_MISSING" +``` + +If `SCRIPT_MISSING`: **STOP**. The spex plugin may not be installed correctly. + +### Step 2: Create the state file + +```bash +"$SHIP_STATE" create "" --ask "" --start-from "" +``` + +The output will confirm: `CREATED stage= index= ask=`. If it fails, **STOP**. + +### Step 3: Announce pipeline start + +Output a brief status message confirming the pipeline configuration before running any stage: + +``` +## speckit-spex-ship starting + +- **Brainstorm**: +- **Starting stage**: (/9) +- **Oversight**: +- **State file**: .specify/.spex-state (created) +``` + +Only after all three steps complete successfully, proceed to Pipeline Stages below. + +## Pipeline Stages + +The pipeline executes 9 stages in fixed order: + +| Index | Stage | Invocation | Description | +|-------|-------|------------|-------------| +| 0 | `specify` | `/speckit-specify` | Generate spec from brainstorm | +| 1 | `clarify` | `/speckit-clarify` | Resolve spec ambiguities | +| 2 | `review-spec` | `/speckit-spex-gates-review-spec` (Subagent) | Validate spec quality | +| 3 | `plan` | `/speckit-plan` | Generate implementation plan | +| 4 | `tasks` | `/speckit-tasks` | Generate task breakdown | +| 5 | `review-plan` | `/speckit-spex-gates-review-plan` (Subagent) | Validate plan and task quality | +| 6 | `implement` | `/speckit-implement` (Subagent) | Execute implementation | +| 7 | `review-code` | `/speckit-spex-gates-review-code` (Subagent) | Spec compliance + code review + deep review | +| 8 | `finish` | `/speckit-spex-finish` (Subagent) | Verify + merge/PR | + +### Suppressing extension overlay gates + +When running inside the ship pipeline, **no `/speckit-*` command may pause for user input unless the `ask` level is `always`**. This overrides any instruction in the speckit command prompts themselves. Specifically: + +- **`speckit-specify`**: Do not ask "Shall I proceed?" after spec creation. Proceed to the next stage. +- **`speckit-clarify`**: Do not present questions interactively in `smart` or `never` mode. Auto-select recommended answers. +- **`speckit-plan`**: Do not ask for confirmation before or after planning. Proceed to the next stage. +- **`speckit-tasks`**: Do not ask for confirmation. Proceed to the next stage. +- **`speckit-implement`**: Do not pause at extension overlay gates. Proceed to the next stage. + +Extension overlays (e.g., `spex-gates` adding review after specify) may run their reviews, but their results are informational. Do NOT pause or ask the user before proceeding. The ship pipeline's own stage gate logic handles all oversight decisions. + +**This is a hard override. If a speckit command prompt says "present to user" or "wait for answer", and `ask` is `smart` or `never`, you answer it yourself and continue.** + +### Stage 0: Specify (ALWAYS runs unless --start-from or --resume skips it) + +**Even if spec.md already exists**, this stage re-creates it from the brainstorm document. A fresh pipeline means fresh artifacts. + +1. Read the brainstorm document content. +2. Invoke `/speckit-specify` passing the brainstorm content as the feature description. + - The brainstorm content provides the problem statement, approaches considered, and decisions made. + - Pass it as the user input to the specify command. + - **Do not pause** after specify completes, even if an extension overlay runs a review or asks for confirmation. Proceed directly to step 4. +4. After specify completes, extract the feature branch name and handle worktree integration: + ```bash + FEATURE_BRANCH=$(git branch --show-current) + ``` + +5. **Worktree integration:** If the `spex-worktrees` extension is enabled, check whether the `after_specify` hook already created a worktree. If not, create one now (the hook is optional and may have been skipped). + ```bash + WORKTREE_ENABLED=$(jq -r '.extensions["spex-worktrees"].enabled // false' .specify/extensions/.registry 2>/dev/null) + ``` + If `WORKTREE_ENABLED` is `true`, look for an existing worktree for the feature branch: + ```bash + WORKTREE_PATH=$(git worktree list --porcelain | grep -B1 "branch refs/heads/$FEATURE_BRANCH" | head -1 | sed 's/^worktree //') + ``` + + If a worktree path is found and it is not the current directory: + - Run `cd "$WORKTREE_PATH"` to switch into the worktree. + - Verify `.specify/.spex-state` exists in the worktree. + - Log: "Switched to worktree at $WORKTREE_PATH. Main directory remains on default branch." + - Update `SHIP_STATE` to point to the worktree's copy of the state script (the path is relative, so cd handles this). + + If `WORKTREE_ENABLED` is `true` but NO worktree was found (the hook was skipped), invoke `/speckit-spex-worktrees-manage` to create one. This runs the worktree create action, which commits spec files, switches the main repo to the default branch, and creates a sibling worktree. After it completes, re-detect the worktree path and `cd` into it as above. + + If worktrees are NOT enabled, stay in the current directory (existing behavior). + +6. Run `"$SHIP_STATE" advance` to move to Stage 1, then **immediately** begin it (do not stop). + +### Stage 1: Clarify (ALWAYS runs, even if the spec "looks clear") + +Do NOT skip this stage. Clarify may uncover ambiguities that are not obvious from reading the spec. + +1. Read the `ask` level from the state file (default: `smart`). +3. **BEFORE invoking clarify**, determine the interaction mode: + - If `ask` is `smart` or `never`: You are the decision-maker. Do NOT use `AskUserQuestion` or present options to the user. When the clarify process identifies ambiguities, YOU select the recommended option for each question. If no recommendation exists, use your best judgment based on the spec context. Answer all questions yourself, then encode the answers into the spec. + - If `ask` is `always`: Present each question to the user interactively. + +4. Invoke `/speckit-clarify` on the generated spec. **The clarify command will try to present interactive questions. In `smart` and `never` modes, this is overridden: answer every question yourself with the recommended option. Do NOT wait for user input. Do NOT display questions with "You can reply with..." prompts. Process all questions in a single pass and update the spec.** +5. After clarification completes, run `"$SHIP_STATE" advance` then **immediately** begin Stage 2 (do not stop). + +### Stage 2: Review Spec (Forked Subagent) + +Do NOT skip this stage. Review-spec validates structural quality, not just ambiguities. This stage runs in an isolated subagent for clean context separation between generation and review. + +1. Resolve the spec directory: + ```bash + PREREQS=$(.specify/scripts/bash/check-prerequisites.sh --json --paths-only 2>/dev/null) + FEATURE_DIR=$(echo "$PREREQS" | jq -r '.FEATURE_DIR') + ``` + +2. Spawn a subagent using the Agent tool with the following prompt: + + ``` + You are executing the spec review stage of a speckit-spex-ship pipeline. + + Feature directory: + Spec: /spec.md + + Invoke /speckit-spex-gates-review-spec to validate spec quality. + The .specify/.spex-state file exists with status "running", so + complete the review autonomously and return immediately. + + Report the overall assessment and any findings when done. + ``` + +3. When the subagent returns, capture its summary. +4. Apply **Oversight Decision Logic** (see below) to handle findings. +5. After findings are resolved, run `"$SHIP_STATE" advance` then **immediately** begin Stage 3 (do not stop). + +### Stage 3: Plan + +1. Invoke `/speckit-plan` to generate the implementation plan. +2. This produces `plan.md`, `research.md`, `data-model.md`, and other artifacts. +3. After plan generation completes, run `"$SHIP_STATE" advance` then **immediately** begin Stage 4 (do not stop). + +### Stage 4: Tasks + +1. Invoke `/speckit-tasks` to generate the task breakdown. +2. This produces `tasks.md`. +3. After task generation completes, run `"$SHIP_STATE" advance` then **immediately** begin Stage 5 (do not stop). + +### Stage 5: Review Plan (Forked Subagent) + +This stage runs in an isolated subagent for clean context separation between planning and review. + +1. Resolve the spec directory: + ```bash + PREREQS=$(.specify/scripts/bash/check-prerequisites.sh --json --paths-only 2>/dev/null) + FEATURE_DIR=$(echo "$PREREQS" | jq -r '.FEATURE_DIR') + ``` + +2. Spawn a subagent using the Agent tool with the following prompt: + + ``` + You are executing the plan review stage of a speckit-spex-ship pipeline. + + Feature directory: + Spec: /spec.md + Plan: /plan.md + Tasks: /tasks.md + + Invoke /speckit-spex-gates-review-plan to validate plan coverage and task quality. + Plan validation complete. + The .specify/.spex-state file exists with status "running", so + complete the review autonomously and return immediately. + + Report the findings and overall assessment when done. + ``` + +3. When the subagent returns, capture its summary. +4. Apply **Oversight Decision Logic** to handle findings. +5. After findings are resolved, run `"$SHIP_STATE" advance` then **immediately** begin Stage 6 (do not stop). + +### Stage 6: Implement (Forked Subagent) + +This stage runs in an isolated subagent to prevent context accumulation in the orchestrator. + +1. Resolve the spec directory for the current branch: + ```bash + PREREQS=$(.specify/scripts/bash/check-prerequisites.sh --json --paths-only 2>/dev/null) + FEATURE_DIR=$(echo "$PREREQS" | jq -r '.FEATURE_DIR') + ``` + +2. Check if spex-teams should handle implementation: + ```bash + TEAMS_ENABLED=$(jq -r '.extensions["spex-teams"].enabled // false' .specify/extensions/.registry 2>/dev/null) + INDEPENDENT_TASKS=$(grep -c '\[P\]' "$FEATURE_DIR/tasks.md" 2>/dev/null || echo 0) + ``` + + If `TEAMS_ENABLED` is `true` AND `INDEPENDENT_TASKS` >= 2, route to teams implement by spawning a subagent with: + + ``` + You are executing the implementation stage of a speckit-spex-ship pipeline using Agent Teams. + + Feature directory: + Spec: /spec.md + Plan: /plan.md + Tasks: /tasks.md + + Read these files, then invoke /speckit.spex-teams.implement to execute parallel implementation. + The .specify/.spex-state file exists with status "running", so the + implement command will run in pipeline mode (no completion summary, no user questions). + + When marking tasks complete in tasks.md, use the Edit tool. + Report a brief summary of completed tasks when done. + ``` + + Otherwise, use standard implement by spawning a subagent with: + + ``` + You are executing the implementation stage of a speckit-spex-ship pipeline. + + Feature directory: + Spec: /spec.md + Plan: /plan.md + Tasks: /tasks.md + + Read these files, then invoke /speckit-implement to execute the implementation. + The .specify/.spex-state file exists with status "running", so the + implement command will run in pipeline mode (no completion summary, no user questions). + + When marking tasks complete in tasks.md, use the Edit tool. + Report a brief summary of completed tasks when done. + ``` + +3. When the subagent returns, capture its summary. Do NOT carry the full implementation context into the orchestrator. +4. After implementation completes, run `"$SHIP_STATE" advance` then **immediately** begin Stage 7 (do not stop). + +### Stage 7: Review Code (Forked Subagent) + +This stage runs in an isolated subagent so the reviewer has no implementation context, enabling an unbiased review. + +1. Resolve the spec directory (same as Stage 6): + ```bash + PREREQS=$(.specify/scripts/bash/check-prerequisites.sh --json --paths-only 2>/dev/null) + FEATURE_DIR=$(echo "$PREREQS" | jq -r '.FEATURE_DIR') + ``` + +2. Spawn a subagent using the Agent tool with the following prompt. Pass external tool settings resolved during argument parsing: + + ``` + You are executing the code review stage of a speckit-spex-ship pipeline. + + Feature directory: + Spec: /spec.md + Plan: /plan.md + Tasks: /tasks.md + External tools: coderabbit=, copilot= + + Invoke /speckit-spex-gates-review-code to run the full review chain: + - Spec compliance check + - Code review validation + - Deep review (if spex-deep-review extension is enabled): 5 review agents, fix loop, + Deep Review Report output to console + - External tools (CodeRabbit, Copilot) if enabled + + Report the compliance score, gate outcome, and a summary of findings when done. + ``` + +3. When the subagent returns, capture its summary (compliance score, gate outcome, finding counts). +4. Apply **Oversight Decision Logic** to any remaining findings reported by the subagent. +5. After findings are resolved, run `"$SHIP_STATE" advance` then **immediately** begin Stage 8 (do not stop). + +### Stage 8: Finish (Forked Subagent) + +This stage runs in an isolated subagent for clean final verification and completion without accumulated context from prior stages. It combines verification (stamp) with merge/PR options into a single step. + +1. Resolve the spec directory: + ```bash + PREREQS=$(.specify/scripts/bash/check-prerequisites.sh --json --paths-only 2>/dev/null) + FEATURE_DIR=$(echo "$PREREQS" | jq -r '.FEATURE_DIR') + ``` + +2. Determine if `--create-pr` should be passed: + ```bash + CREATE_PR_FLAG="" + if [ -f ".specify/.spex-state" ]; then + CREATE_PR=$(jq -r '.create_pr // false' .specify/.spex-state 2>/dev/null) + if [ "$CREATE_PR" = "true" ]; then + CREATE_PR_FLAG="--create-pr" + fi + fi + ``` + +3. Spawn a subagent using the Agent tool with the following prompt: + + ``` + You are executing the finish stage of a speckit-spex-ship pipeline. + + Feature directory: + Spec: /spec.md + + Invoke /speckit-spex-finish for final verification and completion. + This runs tests, validates spec compliance, checks for drift, + then merges or creates a PR based on the pipeline configuration. + The .specify/.spex-state file exists with status "running", so + complete autonomously and return immediately. + + Report pass/fail and completion action taken. + ``` + +4. When the subagent returns, capture its summary. +5. If finish passes (verification + completion action succeeded), run `"$SHIP_STATE" advance` (this outputs `PIPELINE_COMPLETE`). Report the subagent's summary as the final pipeline result. +6. If finish fails (verification did not pass), apply **Oversight Decision Logic**. + +## Oversight Decision Logic + +After each review stage (review-spec, review-plan, review-code, finish), evaluate the findings: + +### Finding Classification + +Classify each finding into one of three categories: + +**Unambiguous** (auto-fixable in `smart` and `never`): +- Formatting issues (indentation, whitespace, line length) +- Style violations (naming conventions, import ordering) +- Typos in comments or documentation +- Missing imports or unused variables +- Minor spec wording improvements + +**Ambiguous** (requires judgment, pauses in `smart`): +- Architecture or design changes +- API contract modifications +- Requirement interpretation questions +- Performance vs. readability trade-offs +- Missing functionality that could be intentional +- Unclear whether a finding is a bug or a feature + +**Blocker** (always pauses, even in `never`): +- Compilation errors or syntax errors +- Missing critical dependencies +- Failing tests that cannot be auto-resolved +- Contradictory requirements +- Security vulnerabilities +- Data loss risks + +### Oversight Rules + +| Oversight Level | Unambiguous | Ambiguous | Blocker | +|----------------|-------------|-----------|---------| +| `always` | Pause | Pause | Pause | +| `smart` | Auto-fix | Pause | Pause | +| `never` | Auto-fix | Auto-fix | Pause | + +### Applying the Rules + +1. After a review stage completes, collect all findings. +2. Classify each finding using the categories above. +3. Based on the oversight level: + - **Auto-fix**: Apply the fix, increment retry count, re-run the review stage. + - **Pause**: Present findings to user (see Pause and Resume below). +4. If no findings need attention, proceed to the next stage. + +## Auto-Fix and Re-Run + +When auto-fixing findings: + +1. Apply fixes for all findings classified as auto-fixable under the current oversight level. +2. Increment `retries` in the state file. +3. Re-run the same review stage to verify fixes. +4. If new findings appear, classify and handle them. +5. **Max 2 retry cycles per stage.** After 2 retries with remaining findings, pause regardless of oversight level: + +``` +Pipeline paused after 2 fix cycles for stage "review-code". +Remaining findings could not be auto-resolved. + +[Present remaining findings here] + +Please provide guidance on how to proceed. +``` + +6. Reset `retries` to 0 when moving to the next stage. + +## Pause and Resume + +### Pausing + +When the pipeline pauses (due to findings that need human input): + +1. Update state file: `status: "paused"`. +2. Present all findings that triggered the pause, grouped by severity: + +``` +## Pipeline Paused at Stage: review-spec + +### Findings Requiring Your Input + +**Ambiguous (need your judgment):** +1. [Finding description with context] +2. [Finding description with context] + +**Blockers (must be resolved):** +1. [Finding description with context] + +Please review these findings and provide guidance. You can: +- Address specific findings ("fix #1 by doing X") +- Skip findings ("skip #2, it's intentional") +- Provide general guidance ("proceed, these are acceptable") +``` + +3. Wait for user response. + +### Resuming After User Input + +After the user responds: + +1. Update state file: `status: "running"`. +2. Apply any fixes the user requested. +3. If user said to skip findings, proceed to the next stage. +4. If user provided fixes, apply them and optionally re-run the review. +5. Continue the pipeline from the current stage. + +## Pipeline Completion + +After Stage 8 (finish) completes successfully, the finish command handles everything: verification, merge/PR, worktree cleanup, and state file removal. + +1. Calculate elapsed time from `started_at`. +2. Report completion summary: + +``` +## Pipeline Complete + +**Feature branch:** +**Stages completed:** 9/9 +**Oversight mode:** +**Elapsed time:** + +All stages passed successfully: + 0. specify - spec.md created + 1. clarify - spec clarified + 2. review-spec - spec validated + 3. plan - plan.md generated + 4. tasks - tasks.md generated + 5. review-plan - plan validated + 6. implement - code implemented + 7. review-code - code reviewed + 8. finish - verified + completed +``` + +3. Report the action taken by finish (merge, PR created, or kept as-is) from the subagent summary. + +## Integration + +**This skill is invoked by:** +- Users directly via `/speckit-spex-ship` + +**This skill invokes (inline):** +- `/speckit-specify` (Stage 0) +- `/speckit-clarify` (Stage 1) +- `/speckit-plan` (Stage 3) +- `/speckit-tasks` (Stage 4) + +**This skill invokes (forked subagent for context isolation):** +- `/speckit-spex-gates-review-spec` (Stage 2) +- `/speckit-spex-gates-review-plan` (Stage 5) +- `/speckit-implement` (Stage 6) +- `/speckit-spex-gates-review-code` (Stage 7) +- `/speckit-spex-finish` (Stage 8) + +**Required extensions:** `spex-gates`, `spex-deep-review` diff --git a/.specify/extensions/spex/commands/speckit.spex.spec-kit.md b/.specify/extensions/spex/commands/speckit.spex.spec-kit.md new file mode 100644 index 00000000..fa87f308 --- /dev/null +++ b/.specify/extensions/spex/commands/speckit.spex.spec-kit.md @@ -0,0 +1,366 @@ +--- +description: "Technical integration layer for the specify CLI" +--- + +# Spec-Kit Technical Integration + +## CRITICAL NAMING - READ THIS FIRST + +| What | Correct Name | WRONG Names | +|------|--------------|-------------| +| CLI command | `specify` | ~~speckit~~, ~~spec-kit~~ | +| Package name | `specify-cli` | ~~spec-kit~~, ~~speckit~~ | +| Slash commands | `/speckit-*` | (these are correct) | + +**Installation:** `uv pip install specify-cli` or `pip install specify-cli` + +## Purpose + +This skill is the **single source of truth** for all spec-kit technical integration: +- Automatic initialization and setup +- Installation validation +- Project structure management +- Slash command availability +- Layout and file path enforcement + +**This is a low-level technical skill.** Workflow skills (brainstorm, implement, etc.) call this skill for setup, then proceed with their specific workflows. + +## CRITICAL: Understanding the Tool Architecture + +**The `specify` CLI is a setup tool only.** It has three commands: +- `specify init` - Initialize a project with spec-kit templates and commands +- `specify check` - Check that required tools are installed +- `specify version` - Display version information + +**All spec operations are done via `/speckit-*` slash commands**, which are installed by `specify init`: +- `/speckit-specify` - Create specifications +- `/speckit-plan` - Generate implementation plans +- `/speckit-tasks` - Generate task lists +- `/speckit-clarify` - Find underspecified areas +- `/speckit-analyze` - Cross-artifact consistency check +- `/speckit-checklist` - Generate quality checklists +- `/speckit-implement` - Execute implementation +- `/speckit-constitution` - Create project constitution + +**NEVER call `specify validate`, `specify plan`, etc. - these commands don't exist!** + +## Automatic Initialization + +**IMPORTANT: This runs automatically when called by any workflow skill.** + +Check `` in the `` system reminder: +- If `true`: Skip initialization entirely. The project is already set up. +- If `false` or missing: Run `/spex:init` to initialize. If init prompts for restart, pause this workflow and resume after restart. + +After initialization succeeds (or was skipped), this skill provides reference material below. + +## Available Slash Commands + +After `specify init`, these `/speckit-*` commands are available: + +| Command | Purpose | Creates | +|---------|---------|---------| +| `/speckit-specify` | Create specification interactively | `specs/[NNNN]-[name]/spec.md` | +| `/speckit-plan` | Generate implementation plan | `specs/[name]/plan.md` | +| `/speckit-tasks` | Generate task list | `specs/[name]/tasks.md` | +| `/speckit-clarify` | Find underspecified areas | (analysis output) | +| `/speckit-analyze` | Cross-artifact consistency | (analysis output) | +| `/speckit-checklist` | Generate quality checklist | checklist file | +| `/speckit-implement` | Execute implementation | code files | +| `/speckit-constitution` | Create project constitution | `.specify/memory/constitution.md` | + +**Usage in skills:** + +When a skill needs to create a spec, plan, or tasks, it should: +1. Check that `/speckit-*` commands are available +2. Invoke the appropriate slash command +3. If commands not available, fall back to manual creation following templates + +**Example:** +``` +To create a spec, invoke: /speckit-specify + +If /speckit-specify is not available (not initialized), +create the spec manually following .specify/templates/spec-template.md +``` + +## Branch Naming Convention + +**Spec-kit requires feature branches named `NNN-feature-name`** where `NNN` is a three-digit numeric prefix matching the spec directory number. + +| Pattern | Valid | Example | +|---------|-------|---------| +| `NNN-feature-name` | Yes | `002-operator-config` | +| `feature/name` | No | Fails branch validation | +| `spec/NNN-name` | No | Fails branch validation | +| `fix/NNN-name` | No | Fails branch validation | + +The validation regex is `^[0-9]{3}-` (must start with exactly three digits followed by a hyphen). + +**Why this matters:** Spec-kit uses the branch name to locate the corresponding spec directory under `specs/`. The numeric prefix links branch `002-operator-config` to `specs/002-operator-config/`. + +**Validation helper:** + +```bash +check_branch_for_speckit() { + local branch=$(git branch --show-current) + if [[ "$branch" =~ ^[0-9]{3}- ]]; then + echo "valid: $branch" + else + echo "invalid: $branch (must match NNN-feature-name pattern)" + fi +} +``` + +**If the branch name is wrong**, create or switch to a properly named branch before running any `/speckit-*` commands: + +```bash +# Example: for spec in specs/002-operator-config/ +git checkout -b 002-operator-config +``` + +## Layout Validation + +Use these helpers to validate spec-kit file structure: + +### Check Constitution + +The constitution is stored at `.specify/memory/constitution.md` (the canonical location, matching upstream spec-kit). + +```bash +# Check constitution location +if [ -f ".specify/memory/constitution.md" ]; then + CONSTITUTION=".specify/memory/constitution.md" + echo "constitution-exists: $CONSTITUTION" +else + echo "no-constitution" +fi +``` + +### Get Feature Spec Path + +```bash +# Validate feature spec path follows spec-kit layout +# Expected: specs/NNNN-feature-name/spec.md +# Or: specs/features/feature-name.md + +validate_spec_path() { + local spec_path=$1 + + # Check if follows spec-kit conventions + if [[ $spec_path =~ ^specs/[0-9]+-[a-z-]+/spec\.md$ ]] || \ + [[ $spec_path =~ ^specs/features/[a-z-]+\.md$ ]]; then + echo "valid" + else + echo "invalid: spec must be in specs/ directory with proper naming" + fi +} +``` + +### Get Plan Path + +```bash +# Plan location (per spec-kit convention) +# Expected: specs/NNNN-feature-name/plan.md + +get_plan_path() { + local feature_dir=$1 # e.g., "specs/0001-user-auth" + echo "$feature_dir/plan.md" +} +``` + +### Ensure Directory Structure + +```bash +# Create spec-kit compliant feature structure +ensure_feature_structure() { + local feature_dir=$1 # e.g., "specs/0001-user-auth" + + mkdir -p "$feature_dir/docs" + mkdir -p "$feature_dir/checklists" + mkdir -p "$feature_dir/contracts" + + echo "created: $feature_dir structure" +} +``` + +## Spec Discovery + +When a workflow skill requires a spec file and none is specified, use this discovery protocol. + +### List Available Specs + +```bash +# Find all spec.md files in specs/ directory +find specs/ -name "spec.md" -type f 2>/dev/null + +# Also check for direct .md files in specs/features/ +ls specs/features/*.md 2>/dev/null +``` + +### Present Options to User + +**If multiple specs found:** +Use AskUserQuestion to let user select which spec to use. + +**If single spec found:** +Confirm with user before proceeding: "Found specs/0001-auth/spec.md. Use this spec?" + +**If no specs found:** +Inform user and suggest creating one: +``` +No specs found in specs/ directory. + +To create a spec: +- Use `speckit-spex-brainstorm` to refine ideas into a spec +- Use `/speckit-specify` to create a spec from clear requirements +``` + +### Path Resolution Priority + +When resolving a spec path: + +1. **Exact path if provided** (e.g., `specs/0001-auth/spec.md`) +2. **Match by feature name in numbered directory** (e.g., `auth` -> `specs/0001-auth/spec.md`) +3. **Match by feature name in features directory** (e.g., `auth` -> `specs/features/auth.md`) + +```bash +# Resolve feature name to spec path +resolve_spec_path() { + local feature_name=$1 + + # Check numbered directory pattern first + local numbered=$(find specs/ -name "spec.md" -type f 2>/dev/null | grep -i "$feature_name" | head -1) + if [ -n "$numbered" ]; then + echo "$numbered" + return + fi + + # Check features directory + local features="specs/features/${feature_name}.md" + if [ -f "$features" ]; then + echo "$features" + return + fi + + # Not found + echo "" +} +``` + +## Error Handling + +### specify CLI Errors + +**Command not found:** +- Provide installation instructions +- Suggest uv or pip installation + +**Init fails:** +- Check write permissions +- Check disk space +- Suggest manual troubleshooting + +### Slash Command Errors + +**Commands not available:** +- Check if `specify init` was run +- Check if restart is needed +- Suggest re-initialization + +**Command execution fails:** +- Display error message +- Suggest checking spec format +- Reference spec template + +### File System Errors + +**Permission denied:** +``` +Cannot write to project directory. + +Please ensure you have write permissions: + chmod +w . +``` + +**Path not found:** +``` +Expected file not found: + +This suggests incomplete initialization. +Run: specify init --force +``` + +## Integration Points + +**Called by these workflow skills:** +- speckit-spex-brainstorm (at start) +- speckit-spex-evolve (at start) +- speckit-spex-gates-review-spec (at start) +- speckit-spex-gates-review-plan (at start) +- `/speckit-implement` via spex-gates extension (at start) +- All workflow skills that need spec-kit + +**Calls:** +- `/spex:init` (for initialization) +- `specify` CLI (for init only, via spex:init) +- `/speckit-*` slash commands (for all operations) +- File system operations + +## Session Management + +**First call in session:** +- Run full initialization protocol +- Check installation, project, commands +- Prompt restart if needed +- Set session flag + +**Subsequent calls in session:** +- Check session flag +- Skip initialization if already done +- Optionally re-verify critical paths +- Return success immediately + +**Session reset:** +- New conversation = new session +- Re-run initialization protocol +- Ensures project state is current + +## CLI vs Slash Commands Summary + +| Task | Tool | Command | +|------|------|---------| +| Initialize project | CLI | `specify init` | +| Check tools | CLI | `specify check` | +| Show version | CLI | `specify version` | +| Create spec | Slash | `/speckit-specify` | +| Generate plan | Slash | `/speckit-plan` | +| Generate tasks | Slash | `/speckit-tasks` | +| Find gaps | Slash | `/speckit-clarify` | +| Check consistency | Slash | `/speckit-analyze` | +| Generate checklist | Slash | `/speckit-checklist` | +| Execute implementation | Slash | `/speckit-implement` | +| Create constitution | Slash | `/speckit-constitution` | + +## Remember + +**This skill is infrastructure, not workflow.** + +- Don't make decisions about WHAT to build +- Don't route to other workflow skills +- Just ensure spec-kit is ready to use +- Validate paths and structure +- Handle technical errors + +**Workflow skills handle:** +- What to create (specs, plans, code) +- When to use which tool +- Process discipline and quality gates + +**This skill handles:** +- Is specify CLI installed? +- Is project initialized? +- Are /speckit-* commands available? +- Do files exist in correct locations? + +**The goal: Zero-config, automatic, invisible setup.** diff --git a/.specify/extensions/spex/commands/speckit.spex.spec-refactoring.md b/.specify/extensions/spex/commands/speckit.spex.spec-refactoring.md new file mode 100644 index 00000000..3869217f --- /dev/null +++ b/.specify/extensions/spex/commands/speckit.spex.spec-refactoring.md @@ -0,0 +1,446 @@ +--- +description: "Consolidate and improve evolved specs while maintaining feature coverage" +--- + +# Specification Refactoring + +## Overview + +Refactor specifications that have grown organically to improve clarity, consistency, and maintainability. + +As specs evolve through `speckit-spex-evolve`, they can accumulate: +- Inconsistencies +- Redundancies +- Unclear sections +- Poor organization + +This skill consolidates and improves specs while ensuring all implemented features remain covered. + +## When to Use + +- Spec has evolved significantly through multiple updates +- Multiple related specs have redundancy +- Spec is difficult to understand or implement from +- Before major feature work on legacy spec +- Periodic maintenance (quarterly review) + +**Warning:** Never refactor specs during active implementation. Wait until stable. + +## The Process + +### 1. Analyze Current State + +**Read all related specs:** +```bash +# Single spec +cat specs/features/[feature].md + +# Multiple related specs +cat specs/features/user-*.md +``` + +**Document current issues:** +- Inconsistencies (conflicting requirements) +- Redundancies (duplicate requirements) +- Unclear sections (ambiguities) +- Poor structure (hard to navigate) +- Outdated sections (no longer relevant) + +### 2. Review Implementation + +**Check what's actually implemented:** +```bash +# Find implementation +rg "[feature-related-terms]" src/ + +# Check tests +rg "[feature-related-terms]" tests/ +``` + +**Critical:** Refactored spec MUST cover all implemented features. + +**Create coverage map:** +``` +Implemented Feature 1 -> Spec Requirement X +Implemented Feature 2 -> Spec Requirement Y +... +``` + +If implementation exists without spec coverage, ADD it during refactor. + +### 3. Identify Consolidation Opportunities + +**Look for:** + +**Redundant requirements:** +- Same requirement stated multiple times +- Similar requirements that could merge +- Duplicate error handling + +**Inconsistent terminology:** +- Same concept called different names +- Inconsistent capitalization +- Different formats for similar things + +**Scattered related requirements:** +- Auth requirements in multiple places +- Error handling spread throughout +- Related features not grouped + +### 4. Design Improved Structure + +**Better organization:** +- Group related requirements +- Logical section order +- Consistent formatting +- Clear hierarchy + +**Example improvement:** + +**Before:** +```markdown +## Requirements +- User login +- Password validation +- Email validation +- Session management +- Logout +- Password reset +- Email verification +``` + +**After:** +```markdown +## Authentication Requirements + +### User Registration +- Email validation +- Email verification +- Password validation + +### Session Management +- User login +- Session creation +- Logout +- Session expiration + +### Password Management +- Password reset +- Password change +- Password strength requirements +``` + +### 5. Refactor Spec + +**Steps:** + +1. **Create refactored version** (new file or branch) +2. **Reorganize sections** for clarity +3. **Consolidate redundancies** +4. **Standardize terminology** +5. **Improve requirement clarity** +6. **Add missing coverage** (if implementation exists) +7. **Remove obsolete sections** (if truly no longer relevant) + +**Throughout:** Maintain traceability to old spec + +### 6. Validate Refactored Spec + +**Check:** +- [ ] All implemented features covered +- [ ] No requirements lost +- [ ] Terminology consistent +- [ ] Structure logical +- [ ] No new ambiguities introduced + +**Use `speckit-spex-gates-review-spec`** on refactored version. + +### 7. Create Changelog + +**Document changes:** + +```markdown +## Spec Refactoring Changelog + +**Date:** YYYY-MM-DD +**Previous Version:** [link or commit] + +### Changes Made + +**Structural Changes:** +- Reorganized requirements into logical groups +- Moved error handling to dedicated section +- Created sub-sections for clarity + +**Consolidated Requirements:** +- Merged requirements 3, 7, 12 (all about validation) +- Combined duplicate error cases +- Unified session management requirements + +**Terminology Standardization:** +- "User" -> "Authenticated User" (consistent usage) +- "Login" -> "Authentication" (aligned with codebase) + +**Added Coverage:** +- Requirement 15: Password strength (implemented but not in spec) +- Error case 8: Rate limiting (implemented but not in spec) + +**Removed:** +- Obsolete requirement 9 (feature removed in v2.0) + +### Migration Notes + +[How to map old spec sections to new spec sections] + +Old Section 2.1 -> New Section 3.1.1 +Old Section 3.4 -> New Section 2.3 +... +``` + +### 8. Transition Strategy + +**For active projects:** + +1. **Review with team** (if team project) +2. **Create PR for spec refactor** +3. **Get approval before merging** +4. **Keep old spec accessible** (git history) +5. **Update documentation** (if references spec) + +**For solo projects:** + +1. **Commit old spec** (ensure it's in git) +2. **Replace with refactored spec** +3. **Commit with detailed message** + +### 9. Verify Against Code + +**After refactoring:** + +```bash +# Check spec compliance with current code +# Use speckit-spex-gates-review-code +``` + +**Ensure:** +- Refactored spec still describes existing implementation +- No accidental requirement changes +- Compliance still 100% + +## Refactoring Checklist + +Use TodoWrite to track: + +- [ ] Analyze current spec state (issues, redundancies) +- [ ] Review actual implementation (what exists in code) +- [ ] Create coverage map (implementation -> spec) +- [ ] Identify consolidation opportunities +- [ ] Design improved structure +- [ ] Refactor spec content +- [ ] Validate refactored spec for soundness +- [ ] Ensure all implemented features covered +- [ ] Create changelog documenting changes +- [ ] Verify refactored spec against code (compliance check) +- [ ] Commit with detailed message + +## Example: Before and After + +### Before Refactoring + +```markdown +# Feature: User System + +## Requirements +1. Users can register +2. Email must be validated +3. Password must be strong +4. Users can login +5. Sessions expire after 30 minutes +6. Users can logout +7. Passwords must have 8 characters +8. Passwords must have uppercase +9. Passwords must have lowercase +10. Passwords must have number +11. Email format must be valid +12. Users can reset password +13. Reset tokens expire after 1 hour +14. Users get logged out on password change +15. Sessions use JWT +16. JWT secret must be secure +... + +(Requirements scattered, no organization, redundancy) +``` + +### After Refactoring + +```markdown +# Feature: User Authentication System + +## Purpose +Provide secure user authentication with registration, login, and password management. + +## User Registration + +### Functional Requirements +1. Users can register with email and password +2. Registration creates user account and initial session + +### Email Validation +- Must be valid email format (RFC 5322) +- Email verification required before account activation +- Verification link expires after 24 hours + +### Password Requirements +- Minimum 8 characters +- Must contain: uppercase, lowercase, number +- Common passwords rejected (check against list) + +## Session Management + +### Authentication Flow +1. User provides credentials (email + password) +2. System validates credentials +3. On success: JWT token generated +4. Client stores token for subsequent requests + +### Session Configuration +- Token type: JWT (JSON Web Token) +- Token expiration: 30 minutes +- Secret: Stored in environment variable (not in code) +- Algorithm: HS256 + +### Logout +- Client discards token +- Optional: Server-side token invalidation (if implemented) + +## Password Management + +### Password Reset +- User requests reset via email +- Reset token generated and emailed +- Reset token expires after 1 hour +- On successful reset: all sessions invalidated + +### Password Change +- Requires current password confirmation +- On success: all sessions invalidated (forces re-login) +... + +(Organized, consolidated, clear) +``` + +### Changelog for Above + +```markdown +## Spec Refactoring Changelog + +**Date:** 2025-11-10 + +### Structural Changes +- Reorganized flat list into hierarchical sections: + - User Registration + - Session Management + - Password Management + +### Consolidated Requirements +- Requirements 7-10 -> Single "Password Requirements" section +- Requirements 2, 11 -> "Email Validation" section +- Requirements 4, 5, 6, 15, 16 -> "Session Management" section + +### Terminology Standardization +- Consistently use "JWT" (not "token" and "JWT" interchangeably) +- "User" context now explicit (authenticated vs unauthenticated) + +### Added Coverage +- None (all features already in original spec) + +### Removed +- None (all requirements preserved, just reorganized) +``` + +## Types of Refactoring + +### 1. Structural Refactoring +- Reorganize sections +- Create hierarchy +- Group related items +- Improve navigation + +### 2. Consolidation Refactoring +- Merge duplicate requirements +- Combine scattered related items +- Remove redundancy + +### 3. Clarification Refactoring +- Remove ambiguities +- Add specificity +- Improve wording +- Standardize terminology + +### 4. Coverage Refactoring +- Add missing implemented features +- Remove obsolete requirements +- Align with current codebase + +## Common Patterns + +### Pattern: Password Requirements Scattered + +**Problem:** Password requirements in multiple places + +**Solution:** Consolidate into single "Password Requirements" section + +### Pattern: Inconsistent Error Handling + +**Problem:** Some requirements specify errors, others don't + +**Solution:** Create dedicated "Error Handling" section, reference from requirements + +### Pattern: Mixed Abstraction Levels + +**Problem:** High-level and low-level requirements mixed + +**Solution:** Create hierarchy - high-level functional requirements with detailed sub-sections + +### Pattern: Terminology Drift + +**Problem:** "User", "Account", "Profile" used interchangeably + +**Solution:** Standardize on one term, define others in glossary if needed + +## Warnings + +**Don't:** +- Change requirements (that's spec evolution, not refactoring) +- Remove coverage of implemented features +- Refactor during active implementation +- Make untracked changes (always document) + +**Do:** +- Preserve all requirement content +- Improve organization and clarity +- Maintain traceability +- Document all changes + +## Remember + +**Refactoring improves form, not function.** + +- Same requirements, better organization +- Same coverage, better clarity +- Same intent, better structure + +**Refactoring is maintenance, not change.** + +- Spec still describes same implementation +- No behavioral changes +- Only organizational improvements + +**Good specs enable good work.** + +- Clear specs enable smooth implementation +- Organized specs reduce confusion +- Consistent specs prevent errors + +**Periodic refactoring prevents spec decay.** diff --git a/.specify/extensions/spex/commands/speckit.spex.using-superpowers.md b/.specify/extensions/spex/commands/speckit.spex.using-superpowers.md new file mode 100644 index 00000000..4ee45c72 --- /dev/null +++ b/.specify/extensions/spex/commands/speckit.spex.using-superpowers.md @@ -0,0 +1,329 @@ +--- +description: "Spex methodology entry point: workflow routing, process discipline, spec-first principle, and skill discovery" +--- + + +If you think there is even a 1% chance a skill might apply to what you are doing, you ABSOLUTELY MUST read the skill. + +IF A SKILL APPLIES TO YOUR TASK, YOU DO NOT HAVE A CHOICE. YOU MUST USE IT. + +This is not negotiable. This is not optional. You cannot rationalize your way out of this. + + +# Getting Started with spex + +## What is spex? + +**SDD = Specification-Driven Development.** spex is the Claude Code plugin that implements it. + +SDD is a development methodology where specifications are the single source of truth: +- Specs created before code +- Code validated against specs +- Specs evolve with implementation reality +- Quality gates enforce spec compliance + +This plugin combines two upstream projects: +- **Superpowers** (by Jesse Vincent): Process discipline, TDD enforcement, verification gates, anti-rationalization patterns, and foundational skills (debugging, git worktrees, parallel agents) +- **Spec-Kit** (by GitHub): Specification templates, artifact management, and the `specify` CLI + +spex extends these with spec-first enforcement, compliance scoring, drift detection, and evolution. Several upstream superpowers skills are modified with spec-awareness (verification, code review, brainstorming, plan review), while others are used unchanged. + +## Technical Prerequisites + +**All technical setup is handled by `/spex:init`.** Spec-kit and extensions must be initialized before using any workflow command. If `.specify/` directory does not exist, tell the user to run `/spex:init` first. Do not check for or search for any CLI tools. Focus on choosing the right workflow. + +## MANDATORY FIRST RESPONSE PROTOCOL + +Before responding to ANY user message, you MUST complete this checklist: + +1. List available spex skills in your mind +2. Ask yourself: "Does ANY spex skill match this request?" +3. If yes -> Use the Skill tool to read and run the skill file +4. Announce which skill you're using +5. Follow the skill exactly + +**Responding WITHOUT completing this checklist = automatic failure.** + +## The Specification-First Principle + +**CRITICAL RULE: Specs are the source of truth. Everything flows from and validates against specs.** + +Before ANY implementation work: +- Spec must exist OR be created first +- Spec must be reviewed for soundness +- Implementation must validate against spec +- Spec/code mismatches trigger evolution workflow + +**You CANNOT write code without a spec. Period.** + +## Critical Rules + +1. **Spec-first, always.** No code without spec. No exceptions. +2. **Follow mandatory workflows.** Brainstorm -> Spec -> Plan -> TDD -> Verify. +3. **Check for relevant skills before ANY task.** spex has skills for each phase. +4. **Validate spec compliance.** Code review and verification check specs. +5. **Handle spec/code drift.** Use speckit-spex-evolve when mismatches detected. +6. **Close out features.** After review passes: `/clear`, then `/speckit-spex-finish` (verifies + merges/creates PR in one step). + +## Available spex Skills + +### Primary Workflow (via spec-kit commands) +- `/speckit-specify` - Create specifications (spex-gates extension adds review gate) +- `/speckit-plan` - Generate plan and tasks (spex-gates extension adds spec review + plan review) +- After `/speckit-plan` (which also runs `/speckit-tasks`), suggest `/clear` before `/speckit-implement` to free context for the implementation phase +- `/speckit-implement` - Execute implementation (spex-gates extension adds pre/post quality gates) + +**NAMESPACE WARNING:** `/spex:specify`, `/spex:plan`, `/spex:tasks`, `/spex:implement` DO NOT EXIST. Always use the `/speckit-*` names above. spex extension commands use the `speckit-spex-*` prefix (e.g., `/speckit-spex-brainstorm`), and speckit core commands use the `speckit-` prefix (e.g., `/speckit-specify`). + +### spex Helper Skills +- **speckit-spex-brainstorm** - Rough idea -> spec through collaborative dialogue +- **speckit-spex-gates-review-spec** - Validate spec soundness and completeness +- **speckit-spex-gates-review-plan** - Post-planning quality validation (coverage, red flags, task quality) +- **speckit-spex-gates-review-code** - Review code-to-spec compliance +- **speckit-spex-evolve** - Handle spec/code mismatches with AI guidance +- **speckit-spex-finish** - Verify + merge/PR/keep (all-in-one feature completion) +- **speckit-spex-gates-stamp** - Verification only (use finish for full flow) +- **speckit-spex-spec-refactoring** - Consolidate and improve evolved specs + +### Configuration +- **speckit-spex-extensions** - Enable/disable spex extensions (spex-gates, spex-teams, etc.) +- **spex:init** - Initialize project with spec-kit and spex configuration + +### Companion: Superpowers Plugin +These skills require [obra/superpowers](https://github.com/obra/superpowers) to be installed separately (`/plugin install superpowers@claude-plugins-official` or `claude plugin install superpowers@claude-plugins-official`). They complement spex but are not bundled: +- **test-driven-development** - Strict RED-GREEN-REFACTOR discipline, use AFTER spec during implementation +- **systematic-debugging** - 4-phase root cause analysis, use spec as reference during debugging + +## Workflow Decision Tree + +``` +User request arrives + | +Is this a new feature/project? + Yes -> Is it a rough idea? + Yes -> speckit-spex-brainstorm + No -> Create spec using /speckit-specify + No -> Does spec exist for this area? + Yes -> Is there spec/code mismatch? + Yes -> speckit-spex-evolve + No -> Need plan/tasks? + Yes -> /speckit-plan + No -> /speckit-implement + No -> Create spec first using /speckit-specify +``` + +## Creating Specifications + +### Rough Idea -> Use Brainstorm + +``` +User: "I want to add authentication to my app" +-> Use speckit-spex-brainstorm +``` + +**Brainstorm will:** +- Explore the idea through questions +- Propose approaches with trade-offs +- Refine requirements collaboratively +- Create formal spec using spec-kit + +### Clear Requirements -> Direct Spec Creation + +``` +User: "Add a POST /api/users endpoint that validates email and returns 422 on invalid format" +-> Create spec directly using /speckit-specify +``` + +**Direct spec creation:** +- Requirements are already clear +- No exploratory dialogue needed +- Use `/speckit-specify` to create the spec +- Follow spec-kit layout conventions + +**WHAT vs HOW principle:** +Specs define WHAT and WHY, not HOW. +- WHAT: Requirements, behaviors, contracts, success criteria +- HOW: Algorithms, code, technology choices, architecture + +## Common Rationalizations That Mean You're About To Fail + +If you catch yourself thinking ANY of these thoughts, STOP. You are rationalizing. Check for and use the skill. + +**Spec-avoidance rationalizations:** +- "This is too simple for a spec" -> WRONG. Simple changes still need spec context. +- "I'll just write the code quickly" -> WRONG. Code without spec creates drift. +- "The spec is obvious from the description" -> WRONG. Make it explicit. +- "We can spec it after implementation" -> WRONG. That's documentation, not spex. + +**Skill-avoidance rationalizations:** +- "This is just a quick fix" -> WRONG. Quick fixes need spec validation. +- "I can check the spec manually" -> WRONG. Use speckit-spex-finish. +- "The spec is good enough" -> WRONG. Use speckit-spex-gates-review-spec before implementing. +- "I remember this workflow" -> WRONG. Skills evolve. Run the current version. + +**Why:** Specs prevent drift. Skills enforce discipline. Both save time by preventing mistakes. + +If a skill for your task exists, you must use it or you will fail at your task. + +## Skills with Checklists + +If a skill has a checklist, YOU MUST create TodoWrite todos for EACH item. + +**Don't:** +- Work through checklist mentally +- Skip creating todos "to save time" +- Batch multiple items into one todo +- Mark complete without doing them + +**Why:** Checklists without TodoWrite tracking = steps get skipped. Every time. + +## Announcing Skill Usage + +Before using a skill, announce that you are using it. + +"I'm using [Skill Name] to [what you're doing]." + +**Examples:** +- "I'm using speckit-spex-brainstorm to refine your idea into a spec." +- "I'm using /speckit-implement to build this feature from the spec." +- "I'm using speckit-spex-evolve to reconcile the spec/code mismatch." + +**Why:** Transparency helps your human partner understand your process and catch errors early. + +## Spec Evolution is Normal + +Specs WILL diverge from code. This is expected and healthy. + +**When mismatch detected:** +1. DON'T panic or force-fit code to wrong spec +2. DO use speckit-spex-evolve +3. AI analyzes: update spec vs. fix code +4. User decides (or auto-update if configured) + +**Remember:** Specs are source of truth, but truth can evolve based on reality. + +## Constitution: Optional but Powerful + +Consider creating a constitution for your project: + +**What is it?** +- Project-wide principles and standards +- Referenced during spec validation +- Ensures consistency across features + +**When to create:** +- New projects: Early, after first feature spec +- Existing projects: When patterns emerge +- Team projects: Always (defines shared understanding) + +**How to create:** +Use `/speckit-constitution`. + +## Instructions != Permission to Skip Workflows + +Your human partner's specific instructions describe WHAT to do, not HOW. + +"Add X", "Fix Y" = the goal, NOT permission to skip spec-first or verification. + +**Red flags:** "Instruction was specific" - "Seems simple" - "Workflow is overkill" + +**Why:** Specific instructions mean clear requirements, which is when specs matter MOST. + +## Summary + +**Starting any task:** +1. Check this skill first for routing +2. Determine: brainstorm vs. direct spec vs. implement vs. evolve +3. Invoke the appropriate workflow skill +4. That skill will call spec-kit for setup automatically +5. Follow the workflow discipline exactly + +**The methodology is:** +- Specs first, always +- Code validates against specs +- Specs evolve when reality teaches us +- Quality gates prevent shortcuts +- Process discipline ensures quality + +**The tools are:** +- spec-kit (technical integration) +- Workflow skills (brainstorm, implement, evolve) +- Verification and validation skills +- TDD and debugging skills + +**The goal is:** +High-quality software with specs that remain the living source of truth. + +## Workflow Patterns + +### Pattern 1: New Feature from Rough Idea + +``` +User: "I want to add notifications to my app" + +1. Recognize: Rough idea +2. Route to: speckit-spex-brainstorm +3. Brainstorm will: + - Call spec-kit (auto-setup) + - Explore idea collaboratively + - Create formal spec + - Hand off to /speckit-implement +``` + +### Pattern 2: New Feature from Clear Requirements + +``` +User: "Add GET /api/stats endpoint returning JSON with user_count and post_count" + +1. Recognize: Clear requirements +2. Create spec using /speckit-specify +3. Route to: /speckit-implement +4. Implementation will: + - Quality gates from spex-gates extension + - Use TDD + - Verify spec compliance +``` + +### Pattern 3: Code Exists, Spec Missing + +``` +User: "Document what this auth module does" + +1. Recognize: Code without spec +2. Create spec by analyzing code +3. Route to: speckit-spex-evolve (to reconcile) +``` + +### Pattern 4: Code and Spec Diverged + +``` +User: "The login endpoint returns different errors than the spec says" + +1. Recognize: Spec/code mismatch +2. Route to: speckit-spex-evolve +3. Evolve will: + - Call spec-kit (auto-setup) + - Analyze mismatch + - Recommend update spec vs. fix code + - User decides or auto-update +``` + +## Remember + +**You are the methodology enforcer.** + +- Route to correct workflow skill +- Enforce spec-first principle +- Catch rationalizations +- Ensure quality gates run + +**You are NOT:** +- The technical setup manager (that's spec-kit) +- The implementer (that's workflow skills) +- The spec creator (that's spec-kit + brainstorm) + +**Your job:** +Ensure the right skill gets used for the right task, and that spex principles are followed. + +**The goal:** +Specs that stay current. Code that matches intent. Quality through discipline. diff --git a/.specify/extensions/spex/extension.yml b/.specify/extensions/spex/extension.yml new file mode 100644 index 00000000..978d784b --- /dev/null +++ b/.specify/extensions/spex/extension.yml @@ -0,0 +1,101 @@ +schema_version: "1.0" + +extension: + id: spex + name: "Spex Core" + version: "1.0.0" + description: "Specification-Driven Development core workflow for AI agents" + author: cc-spex + license: MIT + +requires: + speckit_version: ">=0.5.2" + +provides: + commands: + - name: speckit.spex.brainstorm + file: commands/speckit.spex.brainstorm.md + description: "Refine rough ideas into executable specifications through collaborative questioning" + - name: speckit.spex.ship + file: commands/speckit.spex.ship.md + description: "Autonomous full-cycle workflow: specify through verify with configurable oversight" + - name: speckit.spex.help + file: commands/speckit.spex.help.md + description: "Quick reference for all spex commands and workflow" + - name: speckit.spex.evolve + file: commands/speckit.spex.evolve.md + description: "Handle spec/code mismatches through AI-guided analysis and user-controlled evolution" + - name: speckit.spex.spec-refactoring + file: commands/speckit.spex.spec-refactoring.md + description: "Consolidate and improve evolved specs while maintaining feature coverage" + - name: speckit.spex.using-superpowers + file: commands/speckit.spex.using-superpowers.md + description: "Spex methodology entry point: workflow routing, process discipline, spec-first principle" + - name: speckit.spex.spec-kit + file: commands/speckit.spex.spec-kit.md + description: "Technical integration layer for the specify CLI" + - name: speckit.spex.extensions + file: commands/speckit.spex.extensions.md + description: "Manage spex extensions: enable, disable, or list active extensions" + - name: speckit.spex.finish + file: commands/speckit.spex.finish.md + description: "Complete a feature: verify, then merge/PR/keep with worktree-aware cleanup" + - name: speckit.spex.flow-state + file: commands/speckit.spex.flow-state.md + description: "Create flow state for step-by-step SDD workflow tracking" + +hooks: + after_specify: + command: speckit.spex.flow-state + optional: false + description: "Initialize flow state tracking after specification" + before_clarify: + command: speckit.spex.flow-state + args: "running clarify" + optional: false + description: "Mark clarify as active in flow state" + after_clarify: + command: speckit.spex.flow-state + args: "clarified" + optional: false + description: "Mark clarification complete in flow state" + before_plan: + command: speckit.spex.flow-state + args: "running plan" + optional: false + description: "Mark plan as active in flow state" + after_plan: + command: speckit.spex.flow-state + args: "running done" + optional: false + description: "Clear running state after planning" + before_tasks: + command: speckit.spex.flow-state + args: "running tasks" + optional: false + description: "Mark tasks as active in flow state" + after_tasks: + command: speckit.spex.flow-state + args: "running done" + optional: false + description: "Clear running state after task generation" + before_implement: + command: speckit.spex.flow-state + args: "running implement" + optional: false + description: "Mark implement as active in flow state" + after_implement: + command: speckit.spex.flow-state + args: "implemented" + optional: false + description: "Mark implementation complete in flow state" + after_finish: + command: speckit.spex.flow-state + args: "cleanup" + optional: false + description: "Remove flow state file after feature completion" + +tags: + - "spex" + - "sdd" + - "workflow" diff --git a/.specify/feature.json b/.specify/feature.json new file mode 100644 index 00000000..eee79539 --- /dev/null +++ b/.specify/feature.json @@ -0,0 +1,3 @@ +{ + "feature_directory": "specs/001-agentcard-into-status" +} diff --git a/.specify/init-options.json b/.specify/init-options.json new file mode 100644 index 00000000..6f59ffc8 --- /dev/null +++ b/.specify/init-options.json @@ -0,0 +1,11 @@ +{ + "ai": "claude", + "ai_skills": true, + "branch_numbering": "sequential", + "context_file": "CLAUDE.md", + "here": true, + "integration": "claude", + "preset": null, + "script": "sh", + "speckit_version": "0.7.4.dev0" +} \ No newline at end of file diff --git a/.specify/integration.json b/.specify/integration.json new file mode 100644 index 00000000..38a03f5d --- /dev/null +++ b/.specify/integration.json @@ -0,0 +1,4 @@ +{ + "integration": "claude", + "version": "0.7.4.dev0" +} diff --git a/.specify/integrations/claude.manifest.json b/.specify/integrations/claude.manifest.json new file mode 100644 index 00000000..6f6f9c20 --- /dev/null +++ b/.specify/integrations/claude.manifest.json @@ -0,0 +1,16 @@ +{ + "integration": "claude", + "version": "0.7.4.dev0", + "installed_at": "2026-05-21T07:10:44.428621+00:00", + "files": { + ".claude/skills/speckit-analyze/SKILL.md": "f7b3b921069869fafe0a1f44c17037a74bd11710d030a1961a2995f9f5672dec", + ".claude/skills/speckit-checklist/SKILL.md": "36f82b9b727bc4a0a60a435b218a6d33c1bb421c8bc05b283cdcfd36b4cf2fb3", + ".claude/skills/speckit-clarify/SKILL.md": "3d09fb80c46df567d991a8f91a570f5f1ad9af3e44fb9dc98d2cb9f4402ea825", + ".claude/skills/speckit-constitution/SKILL.md": "c1a044aba243ca6aff627fb5e4404feb6f1108d4f7dd174631bee3ae477d6c15", + ".claude/skills/speckit-implement/SKILL.md": "87b0ae6453192bce3fa29889f09c358b673af3b7582a552bae9fe1597c5ffa3a", + ".claude/skills/speckit-plan/SKILL.md": "8141ebbce228ad0b422a84e3b995d2bd85de917b96eadd02b5fcb56fb23f2594", + ".claude/skills/speckit-specify/SKILL.md": "f78c3e27309aea9ae4e4f71b3abcffafb190f0c64bf709ac7616532ff19f4b1f", + ".claude/skills/speckit-tasks/SKILL.md": "792589edf0ebf89af797c6bdda4e9d2c9938c696181d6f1484bf7a7cd090efaa", + ".claude/skills/speckit-taskstoissues/SKILL.md": "99bf5ffd90dcb57b63007c7f659a5160a18ce6feb82889895808e2d277abe83b" + } +} diff --git a/.specify/integrations/speckit.manifest.json b/.specify/integrations/speckit.manifest.json new file mode 100644 index 00000000..c72d479d --- /dev/null +++ b/.specify/integrations/speckit.manifest.json @@ -0,0 +1,16 @@ +{ + "integration": "speckit", + "version": "0.7.4.dev0", + "installed_at": "2026-05-21T07:10:44.433669+00:00", + "files": { + ".specify/scripts/bash/common.sh": "2b767a5c97d0c8c8f0a868aaa2114f170adfb1921749f4e58675da91382defb7", + ".specify/scripts/bash/setup-plan.sh": "bcaa0ccbf45b7d9ea9bff04006500d7eb28a2bdf82bcc819986b21e5294bf76c", + ".specify/scripts/bash/check-prerequisites.sh": "aff361639c504b95a2901493f5022788adc01a6792fd37f132de8f57782e4b80", + ".specify/scripts/bash/create-new-feature.sh": "3439519f72ce6f88fcd4dcd27346de0c9ffe211c927b1319db42850282f02456", + ".specify/templates/constitution-template.md": "ce7549540fa45543cca797a150201d868e64495fdff39dc38246fb17bd4024b3", + ".specify/templates/checklist-template.md": "312eee8291dfa984b21f95ddd0ca778e7a1f0b3a64bfc470d79762a3e3f5d7b8", + ".specify/templates/tasks-template.md": "5da92ac1fbf5be2f9018a5064497995bf3592761ccb6b3951503c63d851297e8", + ".specify/templates/spec-template.md": "785dc50d856dd92d6515eca0761e16dce0c9ba0a3cd07154fd33eae77932422a", + ".specify/templates/plan-template.md": "873e84b226fe3d24afe28046931b20db9bbb9210366428dc958a515349ed6e68" + } +} diff --git a/.specify/memory/constitution.md b/.specify/memory/constitution.md new file mode 100644 index 00000000..a4670ff4 --- /dev/null +++ b/.specify/memory/constitution.md @@ -0,0 +1,50 @@ +# [PROJECT_NAME] Constitution + + +## Core Principles + +### [PRINCIPLE_1_NAME] + +[PRINCIPLE_1_DESCRIPTION] + + +### [PRINCIPLE_2_NAME] + +[PRINCIPLE_2_DESCRIPTION] + + +### [PRINCIPLE_3_NAME] + +[PRINCIPLE_3_DESCRIPTION] + + +### [PRINCIPLE_4_NAME] + +[PRINCIPLE_4_DESCRIPTION] + + +### [PRINCIPLE_5_NAME] + +[PRINCIPLE_5_DESCRIPTION] + + +## [SECTION_2_NAME] + + +[SECTION_2_CONTENT] + + +## [SECTION_3_NAME] + + +[SECTION_3_CONTENT] + + +## Governance + + +[GOVERNANCE_RULES] + + +**Version**: [CONSTITUTION_VERSION] | **Ratified**: [RATIFICATION_DATE] | **Last Amended**: [LAST_AMENDED_DATE] + diff --git a/.specify/scripts/bash/check-prerequisites.sh b/.specify/scripts/bash/check-prerequisites.sh new file mode 100755 index 00000000..88a55594 --- /dev/null +++ b/.specify/scripts/bash/check-prerequisites.sh @@ -0,0 +1,190 @@ +#!/usr/bin/env bash + +# Consolidated prerequisite checking script +# +# This script provides unified prerequisite checking for Spec-Driven Development workflow. +# It replaces the functionality previously spread across multiple scripts. +# +# Usage: ./check-prerequisites.sh [OPTIONS] +# +# OPTIONS: +# --json Output in JSON format +# --require-tasks Require tasks.md to exist (for implementation phase) +# --include-tasks Include tasks.md in AVAILABLE_DOCS list +# --paths-only Only output path variables (no validation) +# --help, -h Show help message +# +# OUTPUTS: +# JSON mode: {"FEATURE_DIR":"...", "AVAILABLE_DOCS":["..."]} +# Text mode: FEATURE_DIR:... \n AVAILABLE_DOCS: \n ✓/✗ file.md +# Paths only: REPO_ROOT: ... \n BRANCH: ... \n FEATURE_DIR: ... etc. + +set -e + +# Parse command line arguments +JSON_MODE=false +REQUIRE_TASKS=false +INCLUDE_TASKS=false +PATHS_ONLY=false + +for arg in "$@"; do + case "$arg" in + --json) + JSON_MODE=true + ;; + --require-tasks) + REQUIRE_TASKS=true + ;; + --include-tasks) + INCLUDE_TASKS=true + ;; + --paths-only) + PATHS_ONLY=true + ;; + --help|-h) + cat << 'EOF' +Usage: check-prerequisites.sh [OPTIONS] + +Consolidated prerequisite checking for Spec-Driven Development workflow. + +OPTIONS: + --json Output in JSON format + --require-tasks Require tasks.md to exist (for implementation phase) + --include-tasks Include tasks.md in AVAILABLE_DOCS list + --paths-only Only output path variables (no prerequisite validation) + --help, -h Show this help message + +EXAMPLES: + # Check task prerequisites (plan.md required) + ./check-prerequisites.sh --json + + # Check implementation prerequisites (plan.md + tasks.md required) + ./check-prerequisites.sh --json --require-tasks --include-tasks + + # Get feature paths only (no validation) + ./check-prerequisites.sh --paths-only + +EOF + exit 0 + ;; + *) + echo "ERROR: Unknown option '$arg'. Use --help for usage information." >&2 + exit 1 + ;; + esac +done + +# Source common functions +SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/common.sh" + +# Get feature paths and validate branch +_paths_output=$(get_feature_paths) || { echo "ERROR: Failed to resolve feature paths" >&2; exit 1; } +eval "$_paths_output" +unset _paths_output +check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1 + +# If paths-only mode, output paths and exit (support JSON + paths-only combined) +if $PATHS_ONLY; then + if $JSON_MODE; then + # Minimal JSON paths payload (no validation performed) + if has_jq; then + jq -cn \ + --arg repo_root "$REPO_ROOT" \ + --arg branch "$CURRENT_BRANCH" \ + --arg feature_dir "$FEATURE_DIR" \ + --arg feature_spec "$FEATURE_SPEC" \ + --arg impl_plan "$IMPL_PLAN" \ + --arg tasks "$TASKS" \ + '{REPO_ROOT:$repo_root,BRANCH:$branch,FEATURE_DIR:$feature_dir,FEATURE_SPEC:$feature_spec,IMPL_PLAN:$impl_plan,TASKS:$tasks}' + else + printf '{"REPO_ROOT":"%s","BRANCH":"%s","FEATURE_DIR":"%s","FEATURE_SPEC":"%s","IMPL_PLAN":"%s","TASKS":"%s"}\n' \ + "$(json_escape "$REPO_ROOT")" "$(json_escape "$CURRENT_BRANCH")" "$(json_escape "$FEATURE_DIR")" "$(json_escape "$FEATURE_SPEC")" "$(json_escape "$IMPL_PLAN")" "$(json_escape "$TASKS")" + fi + else + echo "REPO_ROOT: $REPO_ROOT" + echo "BRANCH: $CURRENT_BRANCH" + echo "FEATURE_DIR: $FEATURE_DIR" + echo "FEATURE_SPEC: $FEATURE_SPEC" + echo "IMPL_PLAN: $IMPL_PLAN" + echo "TASKS: $TASKS" + fi + exit 0 +fi + +# Validate required directories and files +if [[ ! -d "$FEATURE_DIR" ]]; then + echo "ERROR: Feature directory not found: $FEATURE_DIR" >&2 + echo "Run /speckit.specify first to create the feature structure." >&2 + exit 1 +fi + +if [[ ! -f "$IMPL_PLAN" ]]; then + echo "ERROR: plan.md not found in $FEATURE_DIR" >&2 + echo "Run /speckit.plan first to create the implementation plan." >&2 + exit 1 +fi + +# Check for tasks.md if required +if $REQUIRE_TASKS && [[ ! -f "$TASKS" ]]; then + echo "ERROR: tasks.md not found in $FEATURE_DIR" >&2 + echo "Run /speckit.tasks first to create the task list." >&2 + exit 1 +fi + +# Build list of available documents +docs=() + +# Always check these optional docs +[[ -f "$RESEARCH" ]] && docs+=("research.md") +[[ -f "$DATA_MODEL" ]] && docs+=("data-model.md") + +# Check contracts directory (only if it exists and has files) +if [[ -d "$CONTRACTS_DIR" ]] && [[ -n "$(ls -A "$CONTRACTS_DIR" 2>/dev/null)" ]]; then + docs+=("contracts/") +fi + +[[ -f "$QUICKSTART" ]] && docs+=("quickstart.md") + +# Include tasks.md if requested and it exists +if $INCLUDE_TASKS && [[ -f "$TASKS" ]]; then + docs+=("tasks.md") +fi + +# Output results +if $JSON_MODE; then + # Build JSON array of documents + if has_jq; then + if [[ ${#docs[@]} -eq 0 ]]; then + json_docs="[]" + else + json_docs=$(printf '%s\n' "${docs[@]}" | jq -R . | jq -s .) + fi + jq -cn \ + --arg feature_dir "$FEATURE_DIR" \ + --argjson docs "$json_docs" \ + '{FEATURE_DIR:$feature_dir,AVAILABLE_DOCS:$docs}' + else + if [[ ${#docs[@]} -eq 0 ]]; then + json_docs="[]" + else + json_docs=$(for d in "${docs[@]}"; do printf '"%s",' "$(json_escape "$d")"; done) + json_docs="[${json_docs%,}]" + fi + printf '{"FEATURE_DIR":"%s","AVAILABLE_DOCS":%s}\n' "$(json_escape "$FEATURE_DIR")" "$json_docs" + fi +else + # Text output + echo "FEATURE_DIR:$FEATURE_DIR" + echo "AVAILABLE_DOCS:" + + # Show status of each potential document + check_file "$RESEARCH" "research.md" + check_file "$DATA_MODEL" "data-model.md" + check_dir "$CONTRACTS_DIR" "contracts/" + check_file "$QUICKSTART" "quickstart.md" + + if $INCLUDE_TASKS; then + check_file "$TASKS" "tasks.md" + fi +fi diff --git a/.specify/scripts/bash/common.sh b/.specify/scripts/bash/common.sh new file mode 100755 index 00000000..b41d17de --- /dev/null +++ b/.specify/scripts/bash/common.sh @@ -0,0 +1,375 @@ +#!/usr/bin/env bash +# Common functions and variables for all scripts + +# Find repository root by searching upward for .specify directory +# This is the primary marker for spec-kit projects +find_specify_root() { + local dir="${1:-$(pwd)}" + # Normalize to absolute path to prevent infinite loop with relative paths + # Use -- to handle paths starting with - (e.g., -P, -L) + dir="$(cd -- "$dir" 2>/dev/null && pwd)" || return 1 + local prev_dir="" + while true; do + if [ -d "$dir/.specify" ]; then + echo "$dir" + return 0 + fi + # Stop if we've reached filesystem root or dirname stops changing + if [ "$dir" = "/" ] || [ "$dir" = "$prev_dir" ]; then + break + fi + prev_dir="$dir" + dir="$(dirname "$dir")" + done + return 1 +} + +# Get repository root, prioritizing .specify directory over git +# This prevents using a parent git repo when spec-kit is initialized in a subdirectory +get_repo_root() { + # First, look for .specify directory (spec-kit's own marker) + local specify_root + if specify_root=$(find_specify_root); then + echo "$specify_root" + return + fi + + # Fallback to git if no .specify found + if git rev-parse --show-toplevel >/dev/null 2>&1; then + git rev-parse --show-toplevel + return + fi + + # Final fallback to script location for non-git repos + local script_dir="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + (cd "$script_dir/../../.." && pwd) +} + +# Get current branch, with fallback for non-git repositories +get_current_branch() { + # First check if SPECIFY_FEATURE environment variable is set + if [[ -n "${SPECIFY_FEATURE:-}" ]]; then + echo "$SPECIFY_FEATURE" + return + fi + + # Then check git if available at the spec-kit root (not parent) + local repo_root=$(get_repo_root) + if has_git; then + git -C "$repo_root" rev-parse --abbrev-ref HEAD + return + fi + + # For non-git repos, try to find the latest feature directory + local specs_dir="$repo_root/specs" + + if [[ -d "$specs_dir" ]]; then + local latest_feature="" + local highest=0 + local latest_timestamp="" + + for dir in "$specs_dir"/*; do + if [[ -d "$dir" ]]; then + local dirname=$(basename "$dir") + if [[ "$dirname" =~ ^([0-9]{8}-[0-9]{6})- ]]; then + # Timestamp-based branch: compare lexicographically + local ts="${BASH_REMATCH[1]}" + if [[ "$ts" > "$latest_timestamp" ]]; then + latest_timestamp="$ts" + latest_feature=$dirname + fi + elif [[ "$dirname" =~ ^([0-9]{3,})- ]]; then + local number=${BASH_REMATCH[1]} + number=$((10#$number)) + if [[ "$number" -gt "$highest" ]]; then + highest=$number + # Only update if no timestamp branch found yet + if [[ -z "$latest_timestamp" ]]; then + latest_feature=$dirname + fi + fi + fi + fi + done + + if [[ -n "$latest_feature" ]]; then + echo "$latest_feature" + return + fi + fi + + echo "main" # Final fallback +} + +# Check if we have git available at the spec-kit root level +# Returns true only if git is installed and the repo root is inside a git work tree +# Handles both regular repos (.git directory) and worktrees/submodules (.git file) +has_git() { + # First check if git command is available (before calling get_repo_root which may use git) + command -v git >/dev/null 2>&1 || return 1 + local repo_root=$(get_repo_root) + # Check if .git exists (directory or file for worktrees/submodules) + [ -e "$repo_root/.git" ] || return 1 + # Verify it's actually a valid git work tree + git -C "$repo_root" rev-parse --is-inside-work-tree >/dev/null 2>&1 +} + +# Strip a single optional path segment (e.g. gitflow "feat/004-name" -> "004-name"). +# Only when the full name is exactly two slash-free segments; otherwise returns the raw name. +spec_kit_effective_branch_name() { + local raw="$1" + if [[ "$raw" =~ ^([^/]+)/([^/]+)$ ]]; then + printf '%s\n' "${BASH_REMATCH[2]}" + else + printf '%s\n' "$raw" + fi +} + +check_feature_branch() { + local raw="$1" + local has_git_repo="$2" + + # For non-git repos, we can't enforce branch naming but still provide output + if [[ "$has_git_repo" != "true" ]]; then + echo "[specify] Warning: Git repository not detected; skipped branch validation" >&2 + return 0 + fi + + local branch + branch=$(spec_kit_effective_branch_name "$raw") + + # Accept sequential prefix (3+ digits) but exclude malformed timestamps + # Malformed: 7-or-8 digit date + 6-digit time with no trailing slug (e.g. "2026031-143022" or "20260319-143022") + local is_sequential=false + if [[ "$branch" =~ ^[0-9]{3,}- ]] && [[ ! "$branch" =~ ^[0-9]{7}-[0-9]{6}- ]] && [[ ! "$branch" =~ ^[0-9]{7,8}-[0-9]{6}$ ]]; then + is_sequential=true + fi + if [[ "$is_sequential" != "true" ]] && [[ ! "$branch" =~ ^[0-9]{8}-[0-9]{6}- ]]; then + echo "ERROR: Not on a feature branch. Current branch: $raw" >&2 + echo "Feature branches should be named like: 001-feature-name, 1234-feature-name, or 20260319-143022-feature-name" >&2 + return 1 + fi + + return 0 +} + +# Find feature directory by numeric prefix instead of exact branch match +# This allows multiple branches to work on the same spec (e.g., 004-fix-bug, 004-add-feature) +find_feature_dir_by_prefix() { + local repo_root="$1" + local branch_name + branch_name=$(spec_kit_effective_branch_name "$2") + local specs_dir="$repo_root/specs" + + # Extract prefix from branch (e.g., "004" from "004-whatever" or "20260319-143022" from timestamp branches) + local prefix="" + if [[ "$branch_name" =~ ^([0-9]{8}-[0-9]{6})- ]]; then + prefix="${BASH_REMATCH[1]}" + elif [[ "$branch_name" =~ ^([0-9]{3,})- ]]; then + prefix="${BASH_REMATCH[1]}" + else + # If branch doesn't have a recognized prefix, fall back to exact match + echo "$specs_dir/$branch_name" + return + fi + + # Search for directories in specs/ that start with this prefix + local matches=() + if [[ -d "$specs_dir" ]]; then + for dir in "$specs_dir"/"$prefix"-*; do + if [[ -d "$dir" ]]; then + matches+=("$(basename "$dir")") + fi + done + fi + + # Handle results + if [[ ${#matches[@]} -eq 0 ]]; then + # No match found - return the branch name path (will fail later with clear error) + echo "$specs_dir/$branch_name" + elif [[ ${#matches[@]} -eq 1 ]]; then + # Exactly one match - perfect! + echo "$specs_dir/${matches[0]}" + else + # Multiple matches - this shouldn't happen with proper naming convention + echo "ERROR: Multiple spec directories found with prefix '$prefix': ${matches[*]}" >&2 + echo "Please ensure only one spec directory exists per prefix." >&2 + return 1 + fi +} + +get_feature_paths() { + local repo_root=$(get_repo_root) + local current_branch=$(get_current_branch) + local has_git_repo="false" + + if has_git; then + has_git_repo="true" + fi + + # Resolve feature directory. Priority: + # 1. SPECIFY_FEATURE_DIRECTORY env var (explicit override) + # 2. .specify/feature.json "feature_directory" key (persisted by /speckit.specify) + # 3. Branch-name-based prefix lookup (legacy fallback) + local feature_dir + if [[ -n "${SPECIFY_FEATURE_DIRECTORY:-}" ]]; then + feature_dir="$SPECIFY_FEATURE_DIRECTORY" + # Normalize relative paths to absolute under repo root + [[ "$feature_dir" != /* ]] && feature_dir="$repo_root/$feature_dir" + elif [[ -f "$repo_root/.specify/feature.json" ]]; then + local _fd + if command -v jq >/dev/null 2>&1; then + _fd=$(jq -r '.feature_directory // empty' "$repo_root/.specify/feature.json" 2>/dev/null) + elif command -v python3 >/dev/null 2>&1; then + # Fallback: use Python to parse JSON so pretty-printed/multi-line files work + _fd=$(python3 -c "import json,sys; d=json.load(open(sys.argv[1])); print(d.get('feature_directory',''))" "$repo_root/.specify/feature.json" 2>/dev/null) + else + # Last resort: single-line grep fallback (won't work on multi-line JSON) + _fd=$(grep -o '"feature_directory"[[:space:]]*:[[:space:]]*"[^"]*"' "$repo_root/.specify/feature.json" 2>/dev/null | sed 's/.*"\([^"]*\)"$/\1/') + fi + if [[ -n "$_fd" ]]; then + feature_dir="$_fd" + # Normalize relative paths to absolute under repo root + [[ "$feature_dir" != /* ]] && feature_dir="$repo_root/$feature_dir" + elif ! feature_dir=$(find_feature_dir_by_prefix "$repo_root" "$current_branch"); then + echo "ERROR: Failed to resolve feature directory" >&2 + return 1 + fi + elif ! feature_dir=$(find_feature_dir_by_prefix "$repo_root" "$current_branch"); then + echo "ERROR: Failed to resolve feature directory" >&2 + return 1 + fi + + # Use printf '%q' to safely quote values, preventing shell injection + # via crafted branch names or paths containing special characters + printf 'REPO_ROOT=%q\n' "$repo_root" + printf 'CURRENT_BRANCH=%q\n' "$current_branch" + printf 'HAS_GIT=%q\n' "$has_git_repo" + printf 'FEATURE_DIR=%q\n' "$feature_dir" + printf 'FEATURE_SPEC=%q\n' "$feature_dir/spec.md" + printf 'IMPL_PLAN=%q\n' "$feature_dir/plan.md" + printf 'TASKS=%q\n' "$feature_dir/tasks.md" + printf 'RESEARCH=%q\n' "$feature_dir/research.md" + printf 'DATA_MODEL=%q\n' "$feature_dir/data-model.md" + printf 'QUICKSTART=%q\n' "$feature_dir/quickstart.md" + printf 'CONTRACTS_DIR=%q\n' "$feature_dir/contracts" +} + +# Check if jq is available for safe JSON construction +has_jq() { + command -v jq >/dev/null 2>&1 +} + +# Escape a string for safe embedding in a JSON value (fallback when jq is unavailable). +# Handles backslash, double-quote, and JSON-required control character escapes (RFC 8259). +json_escape() { + local s="$1" + s="${s//\\/\\\\}" + s="${s//\"/\\\"}" + s="${s//$'\n'/\\n}" + s="${s//$'\t'/\\t}" + s="${s//$'\r'/\\r}" + s="${s//$'\b'/\\b}" + s="${s//$'\f'/\\f}" + # Escape any remaining U+0001-U+001F control characters as \uXXXX. + # (U+0000/NUL cannot appear in bash strings and is excluded.) + # LC_ALL=C ensures ${#s} counts bytes and ${s:$i:1} yields single bytes, + # so multi-byte UTF-8 sequences (first byte >= 0xC0) pass through intact. + local LC_ALL=C + local i char code + for (( i=0; i<${#s}; i++ )); do + char="${s:$i:1}" + printf -v code '%d' "'$char" 2>/dev/null || code=256 + if (( code >= 1 && code <= 31 )); then + printf '\\u%04x' "$code" + else + printf '%s' "$char" + fi + done +} + +check_file() { [[ -f "$1" ]] && echo " ✓ $2" || echo " ✗ $2"; } +check_dir() { [[ -d "$1" && -n $(ls -A "$1" 2>/dev/null) ]] && echo " ✓ $2" || echo " ✗ $2"; } + +# Resolve a template name to a file path using the priority stack: +# 1. .specify/templates/overrides/ +# 2. .specify/presets//templates/ (sorted by priority from .registry) +# 3. .specify/extensions//templates/ +# 4. .specify/templates/ (core) +resolve_template() { + local template_name="$1" + local repo_root="$2" + local base="$repo_root/.specify/templates" + + # Priority 1: Project overrides + local override="$base/overrides/${template_name}.md" + [ -f "$override" ] && echo "$override" && return 0 + + # Priority 2: Installed presets (sorted by priority from .registry) + local presets_dir="$repo_root/.specify/presets" + if [ -d "$presets_dir" ]; then + local registry_file="$presets_dir/.registry" + if [ -f "$registry_file" ] && command -v python3 >/dev/null 2>&1; then + # Read preset IDs sorted by priority (lower number = higher precedence). + # The python3 call is wrapped in an if-condition so that set -e does not + # abort the function when python3 exits non-zero (e.g. invalid JSON). + local sorted_presets="" + if sorted_presets=$(SPECKIT_REGISTRY="$registry_file" python3 -c " +import json, sys, os +try: + with open(os.environ['SPECKIT_REGISTRY']) as f: + data = json.load(f) + presets = data.get('presets', {}) + for pid, meta in sorted(presets.items(), key=lambda x: x[1].get('priority', 10)): + print(pid) +except Exception: + sys.exit(1) +" 2>/dev/null); then + if [ -n "$sorted_presets" ]; then + # python3 succeeded and returned preset IDs — search in priority order + while IFS= read -r preset_id; do + local candidate="$presets_dir/$preset_id/templates/${template_name}.md" + [ -f "$candidate" ] && echo "$candidate" && return 0 + done <<< "$sorted_presets" + fi + # python3 succeeded but registry has no presets — nothing to search + else + # python3 failed (missing, or registry parse error) — fall back to unordered directory scan + for preset in "$presets_dir"/*/; do + [ -d "$preset" ] || continue + local candidate="$preset/templates/${template_name}.md" + [ -f "$candidate" ] && echo "$candidate" && return 0 + done + fi + else + # Fallback: alphabetical directory order (no python3 available) + for preset in "$presets_dir"/*/; do + [ -d "$preset" ] || continue + local candidate="$preset/templates/${template_name}.md" + [ -f "$candidate" ] && echo "$candidate" && return 0 + done + fi + fi + + # Priority 3: Extension-provided templates + local ext_dir="$repo_root/.specify/extensions" + if [ -d "$ext_dir" ]; then + for ext in "$ext_dir"/*/; do + [ -d "$ext" ] || continue + # Skip hidden directories (e.g. .backup, .cache) + case "$(basename "$ext")" in .*) continue;; esac + local candidate="$ext/templates/${template_name}.md" + [ -f "$candidate" ] && echo "$candidate" && return 0 + done + fi + + # Priority 4: Core templates + local core="$base/${template_name}.md" + [ -f "$core" ] && echo "$core" && return 0 + + # Template not found in any location. + # Return 1 so callers can distinguish "not found" from "found". + # Callers running under set -e should use: TEMPLATE=$(resolve_template ...) || true + return 1 +} + diff --git a/.specify/scripts/bash/create-new-feature.sh b/.specify/scripts/bash/create-new-feature.sh new file mode 100755 index 00000000..18796470 --- /dev/null +++ b/.specify/scripts/bash/create-new-feature.sh @@ -0,0 +1,413 @@ +#!/usr/bin/env bash + +set -e + +JSON_MODE=false +DRY_RUN=false +ALLOW_EXISTING=false +SHORT_NAME="" +BRANCH_NUMBER="" +USE_TIMESTAMP=false +ARGS=() +i=1 +while [ $i -le $# ]; do + arg="${!i}" + case "$arg" in + --json) + JSON_MODE=true + ;; + --dry-run) + DRY_RUN=true + ;; + --allow-existing-branch) + ALLOW_EXISTING=true + ;; + --short-name) + if [ $((i + 1)) -gt $# ]; then + echo 'Error: --short-name requires a value' >&2 + exit 1 + fi + i=$((i + 1)) + next_arg="${!i}" + # Check if the next argument is another option (starts with --) + if [[ "$next_arg" == --* ]]; then + echo 'Error: --short-name requires a value' >&2 + exit 1 + fi + SHORT_NAME="$next_arg" + ;; + --number) + if [ $((i + 1)) -gt $# ]; then + echo 'Error: --number requires a value' >&2 + exit 1 + fi + i=$((i + 1)) + next_arg="${!i}" + if [[ "$next_arg" == --* ]]; then + echo 'Error: --number requires a value' >&2 + exit 1 + fi + BRANCH_NUMBER="$next_arg" + ;; + --timestamp) + USE_TIMESTAMP=true + ;; + --help|-h) + echo "Usage: $0 [--json] [--dry-run] [--allow-existing-branch] [--short-name ] [--number N] [--timestamp] " + echo "" + echo "Options:" + echo " --json Output in JSON format" + echo " --dry-run Compute branch name and paths without creating branches, directories, or files" + echo " --allow-existing-branch Switch to branch if it already exists instead of failing" + echo " --short-name Provide a custom short name (2-4 words) for the branch" + echo " --number N Specify branch number manually (overrides auto-detection)" + echo " --timestamp Use timestamp prefix (YYYYMMDD-HHMMSS) instead of sequential numbering" + echo " --help, -h Show this help message" + echo "" + echo "Examples:" + echo " $0 'Add user authentication system' --short-name 'user-auth'" + echo " $0 'Implement OAuth2 integration for API' --number 5" + echo " $0 --timestamp --short-name 'user-auth' 'Add user authentication'" + exit 0 + ;; + *) + ARGS+=("$arg") + ;; + esac + i=$((i + 1)) +done + +FEATURE_DESCRIPTION="${ARGS[*]}" +if [ -z "$FEATURE_DESCRIPTION" ]; then + echo "Usage: $0 [--json] [--dry-run] [--allow-existing-branch] [--short-name ] [--number N] [--timestamp] " >&2 + exit 1 +fi + +# Trim whitespace and validate description is not empty (e.g., user passed only whitespace) +FEATURE_DESCRIPTION=$(echo "$FEATURE_DESCRIPTION" | xargs) +if [ -z "$FEATURE_DESCRIPTION" ]; then + echo "Error: Feature description cannot be empty or contain only whitespace" >&2 + exit 1 +fi + +# Function to get highest number from specs directory +get_highest_from_specs() { + local specs_dir="$1" + local highest=0 + + if [ -d "$specs_dir" ]; then + for dir in "$specs_dir"/*; do + [ -d "$dir" ] || continue + dirname=$(basename "$dir") + # Match sequential prefixes (>=3 digits), but skip timestamp dirs. + if echo "$dirname" | grep -Eq '^[0-9]{3,}-' && ! echo "$dirname" | grep -Eq '^[0-9]{8}-[0-9]{6}-'; then + number=$(echo "$dirname" | grep -Eo '^[0-9]+') + number=$((10#$number)) + if [ "$number" -gt "$highest" ]; then + highest=$number + fi + fi + done + fi + + echo "$highest" +} + +# Function to get highest number from git branches +get_highest_from_branches() { + git branch -a 2>/dev/null | sed 's/^[* ]*//; s|^remotes/[^/]*/||' | _extract_highest_number +} + +# Extract the highest sequential feature number from a list of ref names (one per line). +# Shared by get_highest_from_branches and get_highest_from_remote_refs. +_extract_highest_number() { + local highest=0 + while IFS= read -r name; do + [ -z "$name" ] && continue + if echo "$name" | grep -Eq '^[0-9]{3,}-' && ! echo "$name" | grep -Eq '^[0-9]{8}-[0-9]{6}-'; then + number=$(echo "$name" | grep -Eo '^[0-9]+' || echo "0") + number=$((10#$number)) + if [ "$number" -gt "$highest" ]; then + highest=$number + fi + fi + done + echo "$highest" +} + +# Function to get highest number from remote branches without fetching (side-effect-free) +get_highest_from_remote_refs() { + local highest=0 + + for remote in $(git remote 2>/dev/null); do + local remote_highest + remote_highest=$(GIT_TERMINAL_PROMPT=0 git ls-remote --heads "$remote" 2>/dev/null | sed 's|.*refs/heads/||' | _extract_highest_number) + if [ "$remote_highest" -gt "$highest" ]; then + highest=$remote_highest + fi + done + + echo "$highest" +} + +# Function to check existing branches (local and remote) and return next available number. +# When skip_fetch is true, queries remotes via ls-remote (read-only) instead of fetching. +check_existing_branches() { + local specs_dir="$1" + local skip_fetch="${2:-false}" + + if [ "$skip_fetch" = true ]; then + # Side-effect-free: query remotes via ls-remote + local highest_remote=$(get_highest_from_remote_refs) + local highest_branch=$(get_highest_from_branches) + if [ "$highest_remote" -gt "$highest_branch" ]; then + highest_branch=$highest_remote + fi + else + # Fetch all remotes to get latest branch info (suppress errors if no remotes) + git fetch --all --prune >/dev/null 2>&1 || true + local highest_branch=$(get_highest_from_branches) + fi + + # Get highest number from ALL specs (not just matching short name) + local highest_spec=$(get_highest_from_specs "$specs_dir") + + # Take the maximum of both + local max_num=$highest_branch + if [ "$highest_spec" -gt "$max_num" ]; then + max_num=$highest_spec + fi + + # Return next number + echo $((max_num + 1)) +} + +# Function to clean and format a branch name +clean_branch_name() { + local name="$1" + echo "$name" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//' +} + +# Resolve repository root using common.sh functions which prioritize .specify over git +SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/common.sh" + +REPO_ROOT=$(get_repo_root) + +# Check if git is available at this repo root (not a parent) +if has_git; then + HAS_GIT=true +else + HAS_GIT=false +fi + +cd "$REPO_ROOT" + +SPECS_DIR="$REPO_ROOT/specs" +if [ "$DRY_RUN" != true ]; then + mkdir -p "$SPECS_DIR" +fi + +# Function to generate branch name with stop word filtering and length filtering +generate_branch_name() { + local description="$1" + + # Common stop words to filter out + local stop_words="^(i|a|an|the|to|for|of|in|on|at|by|with|from|is|are|was|were|be|been|being|have|has|had|do|does|did|will|would|should|could|can|may|might|must|shall|this|that|these|those|my|your|our|their|want|need|add|get|set)$" + + # Convert to lowercase and split into words + local clean_name=$(echo "$description" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/ /g') + + # Filter words: remove stop words and words shorter than 3 chars (unless they're uppercase acronyms in original) + local meaningful_words=() + for word in $clean_name; do + # Skip empty words + [ -z "$word" ] && continue + + # Keep words that are NOT stop words AND (length >= 3 OR are potential acronyms) + if ! echo "$word" | grep -qiE "$stop_words"; then + if [ ${#word} -ge 3 ]; then + meaningful_words+=("$word") + elif echo "$description" | grep -q "\b${word^^}\b"; then + # Keep short words if they appear as uppercase in original (likely acronyms) + meaningful_words+=("$word") + fi + fi + done + + # If we have meaningful words, use first 3-4 of them + if [ ${#meaningful_words[@]} -gt 0 ]; then + local max_words=3 + if [ ${#meaningful_words[@]} -eq 4 ]; then max_words=4; fi + + local result="" + local count=0 + for word in "${meaningful_words[@]}"; do + if [ $count -ge $max_words ]; then break; fi + if [ -n "$result" ]; then result="$result-"; fi + result="$result$word" + count=$((count + 1)) + done + echo "$result" + else + # Fallback to original logic if no meaningful words found + local cleaned=$(clean_branch_name "$description") + echo "$cleaned" | tr '-' '\n' | grep -v '^$' | head -3 | tr '\n' '-' | sed 's/-$//' + fi +} + +# Generate branch name +if [ -n "$SHORT_NAME" ]; then + # Use provided short name, just clean it up + BRANCH_SUFFIX=$(clean_branch_name "$SHORT_NAME") +else + # Generate from description with smart filtering + BRANCH_SUFFIX=$(generate_branch_name "$FEATURE_DESCRIPTION") +fi + +# Warn if --number and --timestamp are both specified +if [ "$USE_TIMESTAMP" = true ] && [ -n "$BRANCH_NUMBER" ]; then + >&2 echo "[specify] Warning: --number is ignored when --timestamp is used" + BRANCH_NUMBER="" +fi + +# Determine branch prefix +if [ "$USE_TIMESTAMP" = true ]; then + FEATURE_NUM=$(date +%Y%m%d-%H%M%S) + BRANCH_NAME="${FEATURE_NUM}-${BRANCH_SUFFIX}" +else + # Determine branch number + if [ -z "$BRANCH_NUMBER" ]; then + if [ "$DRY_RUN" = true ] && [ "$HAS_GIT" = true ]; then + # Dry-run: query remotes via ls-remote (side-effect-free, no fetch) + BRANCH_NUMBER=$(check_existing_branches "$SPECS_DIR" true) + elif [ "$DRY_RUN" = true ]; then + # Dry-run without git: local spec dirs only + HIGHEST=$(get_highest_from_specs "$SPECS_DIR") + BRANCH_NUMBER=$((HIGHEST + 1)) + elif [ "$HAS_GIT" = true ]; then + # Check existing branches on remotes + BRANCH_NUMBER=$(check_existing_branches "$SPECS_DIR") + else + # Fall back to local directory check + HIGHEST=$(get_highest_from_specs "$SPECS_DIR") + BRANCH_NUMBER=$((HIGHEST + 1)) + fi + fi + + # Force base-10 interpretation to prevent octal conversion (e.g., 010 → 8 in octal, but should be 10 in decimal) + FEATURE_NUM=$(printf "%03d" "$((10#$BRANCH_NUMBER))") + BRANCH_NAME="${FEATURE_NUM}-${BRANCH_SUFFIX}" +fi + +# GitHub enforces a 244-byte limit on branch names +# Validate and truncate if necessary +MAX_BRANCH_LENGTH=244 +if [ ${#BRANCH_NAME} -gt $MAX_BRANCH_LENGTH ]; then + # Calculate how much we need to trim from suffix + # Account for prefix length: timestamp (15) + hyphen (1) = 16, or sequential (3) + hyphen (1) = 4 + PREFIX_LENGTH=$(( ${#FEATURE_NUM} + 1 )) + MAX_SUFFIX_LENGTH=$((MAX_BRANCH_LENGTH - PREFIX_LENGTH)) + + # Truncate suffix at word boundary if possible + TRUNCATED_SUFFIX=$(echo "$BRANCH_SUFFIX" | cut -c1-$MAX_SUFFIX_LENGTH) + # Remove trailing hyphen if truncation created one + TRUNCATED_SUFFIX=$(echo "$TRUNCATED_SUFFIX" | sed 's/-$//') + + ORIGINAL_BRANCH_NAME="$BRANCH_NAME" + BRANCH_NAME="${FEATURE_NUM}-${TRUNCATED_SUFFIX}" + + >&2 echo "[specify] Warning: Branch name exceeded GitHub's 244-byte limit" + >&2 echo "[specify] Original: $ORIGINAL_BRANCH_NAME (${#ORIGINAL_BRANCH_NAME} bytes)" + >&2 echo "[specify] Truncated to: $BRANCH_NAME (${#BRANCH_NAME} bytes)" +fi + +FEATURE_DIR="$SPECS_DIR/$BRANCH_NAME" +SPEC_FILE="$FEATURE_DIR/spec.md" + +if [ "$DRY_RUN" != true ]; then + if [ "$HAS_GIT" = true ]; then + branch_create_error="" + if ! branch_create_error=$(git checkout -q -b "$BRANCH_NAME" 2>&1); then + current_branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || true)" + # Check if branch already exists + if git branch --list "$BRANCH_NAME" | grep -q .; then + if [ "$ALLOW_EXISTING" = true ]; then + # If we're already on the branch, continue without another checkout. + if [ "$current_branch" = "$BRANCH_NAME" ]; then + : + # Otherwise switch to the existing branch instead of failing. + elif ! switch_branch_error=$(git checkout -q "$BRANCH_NAME" 2>&1); then + >&2 echo "Error: Failed to switch to existing branch '$BRANCH_NAME'. Please resolve any local changes or conflicts and try again." + if [ -n "$switch_branch_error" ]; then + >&2 printf '%s\n' "$switch_branch_error" + fi + exit 1 + fi + elif [ "$USE_TIMESTAMP" = true ]; then + >&2 echo "Error: Branch '$BRANCH_NAME' already exists. Rerun to get a new timestamp or use a different --short-name." + exit 1 + else + >&2 echo "Error: Branch '$BRANCH_NAME' already exists. Please use a different feature name or specify a different number with --number." + exit 1 + fi + else + >&2 echo "Error: Failed to create git branch '$BRANCH_NAME'." + if [ -n "$branch_create_error" ]; then + >&2 printf '%s\n' "$branch_create_error" + else + >&2 echo "Please check your git configuration and try again." + fi + exit 1 + fi + fi + else + >&2 echo "[specify] Warning: Git repository not detected; skipped branch creation for $BRANCH_NAME" + fi + + mkdir -p "$FEATURE_DIR" + + if [ ! -f "$SPEC_FILE" ]; then + TEMPLATE=$(resolve_template "spec-template" "$REPO_ROOT") || true + if [ -n "$TEMPLATE" ] && [ -f "$TEMPLATE" ]; then + cp "$TEMPLATE" "$SPEC_FILE" + else + echo "Warning: Spec template not found; created empty spec file" >&2 + touch "$SPEC_FILE" + fi + fi + + # Inform the user how to persist the feature variable in their own shell + printf '# To persist: export SPECIFY_FEATURE=%q\n' "$BRANCH_NAME" >&2 +fi + +if $JSON_MODE; then + if command -v jq >/dev/null 2>&1; then + if [ "$DRY_RUN" = true ]; then + jq -cn \ + --arg branch_name "$BRANCH_NAME" \ + --arg spec_file "$SPEC_FILE" \ + --arg feature_num "$FEATURE_NUM" \ + '{BRANCH_NAME:$branch_name,SPEC_FILE:$spec_file,FEATURE_NUM:$feature_num,DRY_RUN:true}' + else + jq -cn \ + --arg branch_name "$BRANCH_NAME" \ + --arg spec_file "$SPEC_FILE" \ + --arg feature_num "$FEATURE_NUM" \ + '{BRANCH_NAME:$branch_name,SPEC_FILE:$spec_file,FEATURE_NUM:$feature_num}' + fi + else + if [ "$DRY_RUN" = true ]; then + printf '{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s","DRY_RUN":true}\n' "$(json_escape "$BRANCH_NAME")" "$(json_escape "$SPEC_FILE")" "$(json_escape "$FEATURE_NUM")" + else + printf '{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s"}\n' "$(json_escape "$BRANCH_NAME")" "$(json_escape "$SPEC_FILE")" "$(json_escape "$FEATURE_NUM")" + fi + fi +else + echo "BRANCH_NAME: $BRANCH_NAME" + echo "SPEC_FILE: $SPEC_FILE" + echo "FEATURE_NUM: $FEATURE_NUM" + if [ "$DRY_RUN" != true ]; then + printf '# To persist in your shell: export SPECIFY_FEATURE=%q\n' "$BRANCH_NAME" + fi +fi diff --git a/.specify/scripts/bash/setup-plan.sh b/.specify/scripts/bash/setup-plan.sh new file mode 100755 index 00000000..9f552314 --- /dev/null +++ b/.specify/scripts/bash/setup-plan.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash + +set -e + +# Parse command line arguments +JSON_MODE=false +ARGS=() + +for arg in "$@"; do + case "$arg" in + --json) + JSON_MODE=true + ;; + --help|-h) + echo "Usage: $0 [--json]" + echo " --json Output results in JSON format" + echo " --help Show this help message" + exit 0 + ;; + *) + ARGS+=("$arg") + ;; + esac +done + +# Get script directory and load common functions +SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/common.sh" + +# Get all paths and variables from common functions +_paths_output=$(get_feature_paths) || { echo "ERROR: Failed to resolve feature paths" >&2; exit 1; } +eval "$_paths_output" +unset _paths_output + +# Check if we're on a proper feature branch (only for git repos) +check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1 + +# Ensure the feature directory exists +mkdir -p "$FEATURE_DIR" + +# Copy plan template if it exists +TEMPLATE=$(resolve_template "plan-template" "$REPO_ROOT") || true +if [[ -n "$TEMPLATE" ]] && [[ -f "$TEMPLATE" ]]; then + cp "$TEMPLATE" "$IMPL_PLAN" + echo "Copied plan template to $IMPL_PLAN" +else + echo "Warning: Plan template not found" + # Create a basic plan file if template doesn't exist + touch "$IMPL_PLAN" +fi + +# Output results +if $JSON_MODE; then + if has_jq; then + jq -cn \ + --arg feature_spec "$FEATURE_SPEC" \ + --arg impl_plan "$IMPL_PLAN" \ + --arg specs_dir "$FEATURE_DIR" \ + --arg branch "$CURRENT_BRANCH" \ + --arg has_git "$HAS_GIT" \ + '{FEATURE_SPEC:$feature_spec,IMPL_PLAN:$impl_plan,SPECS_DIR:$specs_dir,BRANCH:$branch,HAS_GIT:$has_git}' + else + printf '{"FEATURE_SPEC":"%s","IMPL_PLAN":"%s","SPECS_DIR":"%s","BRANCH":"%s","HAS_GIT":"%s"}\n' \ + "$(json_escape "$FEATURE_SPEC")" "$(json_escape "$IMPL_PLAN")" "$(json_escape "$FEATURE_DIR")" "$(json_escape "$CURRENT_BRANCH")" "$(json_escape "$HAS_GIT")" + fi +else + echo "FEATURE_SPEC: $FEATURE_SPEC" + echo "IMPL_PLAN: $IMPL_PLAN" + echo "SPECS_DIR: $FEATURE_DIR" + echo "BRANCH: $CURRENT_BRANCH" + echo "HAS_GIT: $HAS_GIT" +fi + diff --git a/.specify/templates/checklist-template.md b/.specify/templates/checklist-template.md new file mode 100644 index 00000000..806657da --- /dev/null +++ b/.specify/templates/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.checklist` command based on feature context and requirements. + + + +## [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/.specify/templates/constitution-template.md b/.specify/templates/constitution-template.md new file mode 100644 index 00000000..a4670ff4 --- /dev/null +++ b/.specify/templates/constitution-template.md @@ -0,0 +1,50 @@ +# [PROJECT_NAME] Constitution + + +## Core Principles + +### [PRINCIPLE_1_NAME] + +[PRINCIPLE_1_DESCRIPTION] + + +### [PRINCIPLE_2_NAME] + +[PRINCIPLE_2_DESCRIPTION] + + +### [PRINCIPLE_3_NAME] + +[PRINCIPLE_3_DESCRIPTION] + + +### [PRINCIPLE_4_NAME] + +[PRINCIPLE_4_DESCRIPTION] + + +### [PRINCIPLE_5_NAME] + +[PRINCIPLE_5_DESCRIPTION] + + +## [SECTION_2_NAME] + + +[SECTION_2_CONTENT] + + +## [SECTION_3_NAME] + + +[SECTION_3_CONTENT] + + +## Governance + + +[GOVERNANCE_RULES] + + +**Version**: [CONSTITUTION_VERSION] | **Ratified**: [RATIFICATION_DATE] | **Last Amended**: [LAST_AMENDED_DATE] + diff --git a/.specify/templates/plan-template.md b/.specify/templates/plan-template.md new file mode 100644 index 00000000..5a2fafeb --- /dev/null +++ b/.specify/templates/plan-template.md @@ -0,0 +1,104 @@ +# Implementation Plan: [FEATURE] + +**Branch**: `[###-feature-name]` | **Date**: [DATE] | **Spec**: [link] +**Input**: Feature specification from `/specs/[###-feature-name]/spec.md` + +**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/templates/plan-template.md` for the execution workflow. + +## Summary + +[Extract from feature spec: primary requirement + technical approach from research] + +## Technical Context + + + +**Language/Version**: [e.g., Python 3.11, Swift 5.9, Rust 1.75 or NEEDS CLARIFICATION] +**Primary Dependencies**: [e.g., FastAPI, UIKit, LLVM or NEEDS CLARIFICATION] +**Storage**: [if applicable, e.g., PostgreSQL, CoreData, files or N/A] +**Testing**: [e.g., pytest, XCTest, cargo test or NEEDS CLARIFICATION] +**Target Platform**: [e.g., Linux server, iOS 15+, WASM or NEEDS CLARIFICATION] +**Project Type**: [e.g., library/cli/web-service/mobile-app/compiler/desktop-app or NEEDS CLARIFICATION] +**Performance Goals**: [domain-specific, e.g., 1000 req/s, 10k lines/sec, 60 fps or NEEDS CLARIFICATION] +**Constraints**: [domain-specific, e.g., <200ms p95, <100MB memory, offline-capable or NEEDS CLARIFICATION] +**Scale/Scope**: [domain-specific, e.g., 10k users, 1M LOC, 50 screens or NEEDS CLARIFICATION] + +## Constitution Check + +*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* + +[Gates determined based on constitution file] + +## Project Structure + +### Documentation (this feature) + +```text +specs/[###-feature]/ +├── plan.md # This file (/speckit.plan command output) +├── research.md # Phase 0 output (/speckit.plan command) +├── data-model.md # Phase 1 output (/speckit.plan command) +├── quickstart.md # Phase 1 output (/speckit.plan command) +├── contracts/ # Phase 1 output (/speckit.plan command) +└── tasks.md # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan) +``` + +### Source Code (repository root) + + +```text +# [REMOVE IF UNUSED] Option 1: Single project (DEFAULT) +src/ +├── models/ +├── services/ +├── cli/ +└── lib/ + +tests/ +├── contract/ +├── integration/ +└── unit/ + +# [REMOVE IF UNUSED] Option 2: Web application (when "frontend" + "backend" detected) +backend/ +├── src/ +│ ├── models/ +│ ├── services/ +│ └── api/ +└── tests/ + +frontend/ +├── src/ +│ ├── components/ +│ ├── pages/ +│ └── services/ +└── tests/ + +# [REMOVE IF UNUSED] Option 3: Mobile + API (when "iOS/Android" detected) +api/ +└── [same as backend above] + +ios/ or android/ +└── [platform-specific structure: feature modules, UI flows, platform tests] +``` + +**Structure Decision**: [Document the selected structure and reference the real +directories captured above] + +## Complexity Tracking + +> **Fill ONLY if Constitution Check has violations that must be justified** + +| Violation | Why Needed | Simpler Alternative Rejected Because | +|-----------|------------|-------------------------------------| +| [e.g., 4th project] | [current need] | [why 3 projects insufficient] | +| [e.g., Repository pattern] | [specific problem] | [why direct DB access insufficient] | diff --git a/.specify/templates/spec-template.md b/.specify/templates/spec-template.md new file mode 100644 index 00000000..4581e405 --- /dev/null +++ b/.specify/templates/spec-template.md @@ -0,0 +1,128 @@ +# Feature Specification: [FEATURE NAME] + +**Feature Branch**: `[###-feature-name]` +**Created**: [DATE] +**Status**: Draft +**Input**: User description: "$ARGUMENTS" + +## User Scenarios & Testing *(mandatory)* + + + +### 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 + + + +- What happens when [boundary condition]? +- How does system handle [error scenario]? + +## Requirements *(mandatory)* + + + +### Functional Requirements + +- **FR-001**: System MUST [specific capability, e.g., "allow users to create accounts"] +- **FR-002**: System MUST [specific capability, e.g., "validate email addresses"] +- **FR-003**: Users MUST be able to [key interaction, e.g., "reset their password"] +- **FR-004**: System MUST [data requirement, e.g., "persist user preferences"] +- **FR-005**: System MUST [behavior, e.g., "log all security events"] + +*Example of marking unclear requirements:* + +- **FR-006**: System MUST authenticate users via [NEEDS CLARIFICATION: auth method not specified - email/password, SSO, OAuth?] +- **FR-007**: System MUST retain user data for [NEEDS CLARIFICATION: retention period not specified] + +### 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)* + + + +### Measurable Outcomes + +- **SC-001**: [Measurable metric, e.g., "Users can complete account creation in under 2 minutes"] +- **SC-002**: [Measurable metric, e.g., "System handles 1000 concurrent users without degradation"] +- **SC-003**: [User satisfaction metric, e.g., "90% of users successfully complete primary task on first attempt"] +- **SC-004**: [Business metric, e.g., "Reduce support tickets related to [X] by 50%"] + +## Assumptions + + + +- [Assumption about target users, e.g., "Users have stable internet connectivity"] +- [Assumption about scope boundaries, e.g., "Mobile support is out of scope for v1"] +- [Assumption about data/environment, e.g., "Existing authentication system will be reused"] +- [Dependency on existing system/service, e.g., "Requires access to the existing user profile API"] diff --git a/.specify/templates/tasks-template.md b/.specify/templates/tasks-template.md new file mode 100644 index 00000000..60f9be45 --- /dev/null +++ b/.specify/templates/tasks-template.md @@ -0,0 +1,251 @@ +--- + +description: "Task list template for feature implementation" +--- + +# Tasks: [FEATURE NAME] + +**Input**: Design documents from `/specs/[###-feature-name]/` +**Prerequisites**: plan.md (required), spec.md (required for user stories), research.md, data-model.md, contracts/ + +**Tests**: The examples below include test tasks. Tests are OPTIONAL - only include them if explicitly requested in the feature specification. + +**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story. + +## Format: `[ID] [P?] [Story] Description` + +- **[P]**: Can run in parallel (different files, no dependencies) +- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3) +- Include exact file paths in descriptions + +## Path Conventions + +- **Single project**: `src/`, `tests/` at repository root +- **Web app**: `backend/src/`, `frontend/src/` +- **Mobile**: `api/src/`, `ios/src/` or `android/src/` +- Paths shown below assume single project - adjust based on plan.md structure + + + +## Phase 1: Setup (Shared Infrastructure) + +**Purpose**: Project initialization and basic structure + +- [ ] T001 Create project structure per implementation plan +- [ ] T002 Initialize [language] project with [framework] dependencies +- [ ] T003 [P] Configure linting and formatting tools + +--- + +## Phase 2: Foundational (Blocking Prerequisites) + +**Purpose**: Core infrastructure that MUST be complete before ANY user story can be implemented + +**⚠️ CRITICAL**: No user story work can begin until this phase is complete + +Examples of foundational tasks (adjust based on your project): + +- [ ] T004 Setup database schema and migrations framework +- [ ] T005 [P] Implement authentication/authorization framework +- [ ] T006 [P] Setup API routing and middleware structure +- [ ] T007 Create base models/entities that all stories depend on +- [ ] T008 Configure error handling and logging infrastructure +- [ ] T009 Setup environment configuration management + +**Checkpoint**: Foundation ready - user story implementation can now begin in parallel + +--- + +## Phase 3: User Story 1 - [Title] (Priority: P1) 🎯 MVP + +**Goal**: [Brief description of what this story delivers] + +**Independent Test**: [How to verify this story works on its own] + +### Tests for User Story 1 (OPTIONAL - only if tests requested) ⚠️ + +> **NOTE: Write these tests FIRST, ensure they FAIL before implementation** + +- [ ] T010 [P] [US1] Contract test for [endpoint] in tests/contract/test_[name].py +- [ ] T011 [P] [US1] Integration test for [user journey] in tests/integration/test_[name].py + +### Implementation for User Story 1 + +- [ ] T012 [P] [US1] Create [Entity1] model in src/models/[entity1].py +- [ ] T013 [P] [US1] Create [Entity2] model in src/models/[entity2].py +- [ ] T014 [US1] Implement [Service] in src/services/[service].py (depends on T012, T013) +- [ ] T015 [US1] Implement [endpoint/feature] in src/[location]/[file].py +- [ ] T016 [US1] Add validation and error handling +- [ ] T017 [US1] Add logging for user story 1 operations + +**Checkpoint**: At this point, User Story 1 should be fully functional and testable independently + +--- + +## Phase 4: User Story 2 - [Title] (Priority: P2) + +**Goal**: [Brief description of what this story delivers] + +**Independent Test**: [How to verify this story works on its own] + +### Tests for User Story 2 (OPTIONAL - only if tests requested) ⚠️ + +- [ ] T018 [P] [US2] Contract test for [endpoint] in tests/contract/test_[name].py +- [ ] T019 [P] [US2] Integration test for [user journey] in tests/integration/test_[name].py + +### Implementation for User Story 2 + +- [ ] T020 [P] [US2] Create [Entity] model in src/models/[entity].py +- [ ] T021 [US2] Implement [Service] in src/services/[service].py +- [ ] T022 [US2] Implement [endpoint/feature] in src/[location]/[file].py +- [ ] T023 [US2] Integrate with User Story 1 components (if needed) + +**Checkpoint**: At this point, User Stories 1 AND 2 should both work independently + +--- + +## Phase 5: User Story 3 - [Title] (Priority: P3) + +**Goal**: [Brief description of what this story delivers] + +**Independent Test**: [How to verify this story works on its own] + +### Tests for User Story 3 (OPTIONAL - only if tests requested) ⚠️ + +- [ ] T024 [P] [US3] Contract test for [endpoint] in tests/contract/test_[name].py +- [ ] T025 [P] [US3] Integration test for [user journey] in tests/integration/test_[name].py + +### Implementation for User Story 3 + +- [ ] T026 [P] [US3] Create [Entity] model in src/models/[entity].py +- [ ] T027 [US3] Implement [Service] in src/services/[service].py +- [ ] T028 [US3] Implement [endpoint/feature] in src/[location]/[file].py + +**Checkpoint**: All user stories should now be independently functional + +--- + +[Add more user story phases as needed, following the same pattern] + +--- + +## Phase N: Polish & Cross-Cutting Concerns + +**Purpose**: Improvements that affect multiple user stories + +- [ ] TXXX [P] Documentation updates in docs/ +- [ ] TXXX Code cleanup and refactoring +- [ ] TXXX Performance optimization across all stories +- [ ] TXXX [P] Additional unit tests (if requested) in tests/unit/ +- [ ] TXXX Security hardening +- [ ] TXXX Run quickstart.md validation + +--- + +## Dependencies & Execution Order + +### Phase Dependencies + +- **Setup (Phase 1)**: No dependencies - can start immediately +- **Foundational (Phase 2)**: Depends on Setup completion - BLOCKS all user stories +- **User Stories (Phase 3+)**: All depend on Foundational phase completion + - User stories can then proceed in parallel (if staffed) + - Or sequentially in priority order (P1 → P2 → P3) +- **Polish (Final Phase)**: Depends on all desired user stories being complete + +### User Story Dependencies + +- **User Story 1 (P1)**: Can start after Foundational (Phase 2) - No dependencies on other stories +- **User Story 2 (P2)**: Can start after Foundational (Phase 2) - May integrate with US1 but should be independently testable +- **User Story 3 (P3)**: Can start after Foundational (Phase 2) - May integrate with US1/US2 but should be independently testable + +### Within Each User Story + +- Tests (if included) MUST be written and FAIL before implementation +- Models before services +- Services before endpoints +- Core implementation before integration +- Story complete before moving to next priority + +### Parallel Opportunities + +- All Setup tasks marked [P] can run in parallel +- All Foundational tasks marked [P] can run in parallel (within Phase 2) +- Once Foundational phase completes, all user stories can start in parallel (if team capacity allows) +- All tests for a user story marked [P] can run in parallel +- Models within a story marked [P] can run in parallel +- Different user stories can be worked on in parallel by different team members + +--- + +## Parallel Example: User Story 1 + +```bash +# Launch all tests for User Story 1 together (if tests requested): +Task: "Contract test for [endpoint] in tests/contract/test_[name].py" +Task: "Integration test for [user journey] in tests/integration/test_[name].py" + +# Launch all models for User Story 1 together: +Task: "Create [Entity1] model in src/models/[entity1].py" +Task: "Create [Entity2] model in src/models/[entity2].py" +``` + +--- + +## Implementation Strategy + +### MVP First (User Story 1 Only) + +1. Complete Phase 1: Setup +2. Complete Phase 2: Foundational (CRITICAL - blocks all stories) +3. Complete Phase 3: User Story 1 +4. **STOP and VALIDATE**: Test User Story 1 independently +5. Deploy/demo if ready + +### Incremental Delivery + +1. Complete Setup + Foundational → Foundation ready +2. Add User Story 1 → Test independently → Deploy/Demo (MVP!) +3. Add User Story 2 → Test independently → Deploy/Demo +4. Add User Story 3 → Test independently → Deploy/Demo +5. Each story adds value without breaking previous stories + +### Parallel Team Strategy + +With multiple developers: + +1. Team completes Setup + Foundational together +2. Once Foundational is done: + - Developer A: User Story 1 + - Developer B: User Story 2 + - Developer C: User Story 3 +3. Stories complete and integrate independently + +--- + +## Notes + +- [P] tasks = different files, no dependencies +- [Story] label maps task to specific user story for traceability +- Each user story should be independently completable and testable +- Verify tests fail before implementing +- Commit after each task or logical group +- Stop at any checkpoint to validate story independently +- Avoid: vague tasks, same file conflicts, cross-story dependencies that break independence diff --git a/.specify/workflows/speckit/workflow.yml b/.specify/workflows/speckit/workflow.yml new file mode 100644 index 00000000..bf184510 --- /dev/null +++ b/.specify/workflows/speckit/workflow.yml @@ -0,0 +1,63 @@ +schema_version: "1.0" +workflow: + id: "speckit" + name: "Full SDD Cycle" + version: "1.0.0" + author: "GitHub" + description: "Runs specify → plan → tasks → implement with review gates" + +requires: + speckit_version: ">=0.7.2" + integrations: + any: ["copilot", "claude", "gemini"] + +inputs: + spec: + type: string + required: true + prompt: "Describe what you want to build" + integration: + type: string + default: "copilot" + prompt: "Integration to use (e.g. claude, copilot, gemini)" + scope: + type: string + default: "full" + enum: ["full", "backend-only", "frontend-only"] + +steps: + - id: specify + command: speckit.specify + integration: "{{ inputs.integration }}" + input: + args: "{{ inputs.spec }}" + + - id: review-spec + type: gate + message: "Review the generated spec before planning." + options: [approve, reject] + on_reject: abort + + - id: plan + command: speckit.plan + integration: "{{ inputs.integration }}" + input: + args: "{{ inputs.spec }}" + + - id: review-plan + type: gate + message: "Review the plan before generating tasks." + options: [approve, reject] + on_reject: abort + + - id: tasks + command: speckit.tasks + integration: "{{ inputs.integration }}" + input: + args: "{{ inputs.spec }}" + + - id: implement + command: speckit.implement + integration: "{{ inputs.integration }}" + input: + args: "{{ inputs.spec }}" diff --git a/.specify/workflows/workflow-registry.json b/.specify/workflows/workflow-registry.json new file mode 100644 index 00000000..e812f87f --- /dev/null +++ b/.specify/workflows/workflow-registry.json @@ -0,0 +1,13 @@ +{ + "schema_version": "1.0", + "workflows": { + "speckit": { + "name": "Full SDD Cycle", + "version": "1.0.0", + "description": "Runs specify \u2192 plan \u2192 tasks \u2192 implement with review gates", + "source": "bundled", + "installed_at": "2026-05-21T07:10:44.476080+00:00", + "updated_at": "2026-05-21T07:10:44.476083+00:00" + } + } +} \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 31dbfd4d..a1683000 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -45,3 +45,9 @@ Orchestrate skills for enhancing related repositories: | `skills:scan` | Audit repository skills | | `skills:write` | Create or edit skills following the standard | | `skills:validate` | Validate skill format and structure | + + +For additional context about technologies to be used, project structure, +shell commands, and other important information, read the current plan +at `specs/001-agentcard-into-status/plan.md` + diff --git a/specs/001-agentcard-into-status/REVIEWERS.md b/specs/001-agentcard-into-status/REVIEWERS.md new file mode 100644 index 00000000..016babb5 --- /dev/null +++ b/specs/001-agentcard-into-status/REVIEWERS.md @@ -0,0 +1,66 @@ +# Review Guide: Consolidate AgentCard Data Into AgentRuntime Status + +**Generated**: 2026-05-21 | **Spec**: [spec.md](spec.md) + +## Why This Change + +AgentRuntime and AgentCard are two CRDs with identical cardinality, the same namespace, the same lifecycle, and the same owner. Operators currently need to cross-reference both resources to understand what an agent can do. AgentCard also conflates observation with policy (it's entirely controller-managed with no room for admin-authored policy fields), and its JWS signing pipeline signs a skeleton card with empty skills. This change consolidates the card data into AgentRuntime's status, reducing the API surface and simplifying the operator experience. + +## What Changes + +AgentRuntime gains a new `status.card` field that holds the A2A agent card payload (name, description, skills, capabilities), fetch metadata (timestamp, content hash, protocol), and identity verification results (SPIFFE ID, JWS signature validation). The controller fetches the card from `/.well-known/agent-card.json` on rollout events. mTLS verified fetch is included, reusing the infrastructure from PR #284. The entire feature is gated behind `--enable-card-discovery` (default: disabled). AgentCard CRD remains functional but emits deprecation warnings on new CR creation. + +## How It Works + +A new `fetchAndUpdateCard` phase is added to the existing AgentRuntime reconcile loop, after config hash computation. It resolves the agent's Service endpoint by name (matching existing convention) with selector-match fallback, fetches the card via the existing `agentcard.Fetcher` interface (plain HTTP) or `agentcard.AuthenticatedFetcher` (mTLS with SPIFFE), runs JWS signature verification via the existing `signature.Provider`, and writes the results to `status.card`. Re-fetch is triggered only by pod template hash changes, not periodic polling. The controller reuses all existing fetch, parse, and verify code from the AgentCard controller without creating new packages. + +## When It Applies + +**Applies when**: +- The operator is started with `--enable-card-discovery=true` +- An AgentRuntime targets a Deployment whose Pods serve `/.well-known/agent-card.json` +- The backing Deployment rolls out (pod template hash changes) + +**Does not apply when**: +- `--enable-card-discovery` is not set (default). No card fetch occurs. +- AgentRuntime targets a workload without an A2A card endpoint. The controller sets a `CardSynced=False` condition and moves on. +- mTLS policy fields, AgentCard CRD removal, and migration tooling are deferred to future iterations. + +## Key Decisions + +1. **Extend existing controller (not a new one)**: The card fetch is a single HTTP GET added to the existing reconcile loop. Creating a separate controller would add coordination complexity for minimal isolation benefit. If performance becomes an issue at scale, extraction is a clean refactor. + +2. **Selector match for service resolution**: Resolves the Deployment's Pod selector labels to find the matching Service. Falls back from name-based convention (matching AgentCard behavior) to selector matching. No annotations required. + +3. **Retain stale data on fetch failure**: When a fetch fails, the last successful card data is kept in `status.card`. The `CardSynced` condition and `fetchedAt` timestamp signal staleness. This avoids disruption for tools that consume `status.card`. + +4. **Clear data when feature flag is disabled**: When `--enable-card-discovery` is toggled off, `status.card` is cleared on the next reconcile. This prevents stale data from lingering when the feature is explicitly turned off. + +5. **mTLS included in this iteration**: Rather than deferring mTLS to a follow-up, the recently merged PR #284 infrastructure is reused directly. This avoids building a plain-HTTP-only path that would be immediately replaced. + +## Areas Needing Attention + +- The `fetchAndUpdateCard` method adds network I/O (HTTP GET) to a controller that currently only does Kubernetes API calls. The existing 10s timeout from `doHTTPFetch` applies, but a slow or unreachable agent endpoint will delay the reconcile loop by up to 10s. +- Pod template hash change detection is the trigger mechanism. If the agent updates its card content without a pod rollout, the controller will not re-fetch. This is an intentional tradeoff (no polling) documented in the spec. +- The `CardStatus` struct embeds `AgentCardData` which includes the `Signatures` field (JWS array). For agents that sign their cards, this means the raw JWS data is stored in AgentRuntime status, which could be large. +- Service resolution assumes at most one Service matches a Deployment's selector. Multiple matching Services will use the first one found, which may not be deterministic. + +## Open Questions + +No open questions identified. All clarifications were resolved during the spec clarification phase. + +## Review Checklist + +- [ ] Key decisions are justified +- [ ] Breaking changes are documented with migration guidance +- [ ] Scope matches the stated boundaries +- [ ] Success criteria are achievable +- [ ] No unstated assumptions +- [ ] `CardStatus` struct fields match the A2A agent card spec +- [ ] Feature flag default (disabled) is correct for backward compatibility +- [ ] Deprecation warning is non-disruptive (log + event, no behavior change) +- [ ] mTLS reuse from PR #284 is compatible with the AgentRuntime controller's lifecycle + +--- + + diff --git a/specs/001-agentcard-into-status/checklists/requirements.md b/specs/001-agentcard-into-status/checklists/requirements.md new file mode 100644 index 00000000..4f8ad699 --- /dev/null +++ b/specs/001-agentcard-into-status/checklists/requirements.md @@ -0,0 +1,36 @@ +# Specification Quality Checklist: Consolidate AgentCard Data Into AgentRuntime Status + +**Purpose**: Validate specification completeness and quality before proceeding to planning +**Created**: 2026-05-21 +**Feature**: [spec.md](../spec.md) + +## Content Quality + +- [x] No implementation details (languages, frameworks, APIs) +- [x] Focused on user value and business needs +- [x] Written for non-technical stakeholders +- [x] All mandatory sections completed + +## Requirement Completeness + +- [x] No [NEEDS CLARIFICATION] markers remain +- [x] Requirements are testable and unambiguous +- [x] Success criteria are measurable +- [x] Success criteria are technology-agnostic (no implementation details) +- [x] All acceptance scenarios are defined +- [x] Edge cases are identified +- [x] Scope is clearly bounded +- [x] Dependencies and assumptions identified + +## Feature Readiness + +- [x] All functional requirements have clear acceptance criteria +- [x] User scenarios cover primary flows +- [x] Feature meets measurable outcomes defined in Success Criteria +- [x] No implementation details leak into specification + +## Notes + +- All items pass. Spec is ready for `/speckit-clarify` or `/speckit-plan`. +- Reasonable defaults were chosen for: fetch trigger mechanism (pod template hash change), service endpoint resolution (selector matching), and feature flag default (disabled). +- Reuses the existing `AgentCardData` struct assumption, documented in Assumptions section. diff --git a/specs/001-agentcard-into-status/contracts/agentruntime-status-card.md b/specs/001-agentcard-into-status/contracts/agentruntime-status-card.md new file mode 100644 index 00000000..0e8e787e --- /dev/null +++ b/specs/001-agentcard-into-status/contracts/agentruntime-status-card.md @@ -0,0 +1,77 @@ +# Contract: AgentRuntime status.card + +## Overview + +The `status.card` field on the AgentRuntime CRD exposes discovered A2A agent card data, fetch metadata, and identity verification results. This contract defines the shape and semantics of this new status field. + +## CRD Status Extension + +```yaml +apiVersion: agent.kagenti.dev/v1alpha1 +kind: AgentRuntime +status: + phase: Active + configuredPods: 1 + conditions: + - type: CardSynced + status: "True" + reason: CardSynced + message: "Successfully fetched agent card for my-agent" + card: + # A2A card payload (from AgentCardData) + name: "my-agent" + description: "An example A2A agent" + version: "1.0.0" + url: "http://my-agent.default.svc.cluster.local:8000" + skills: + - id: "summarize" + name: "Summarize" + description: "Summarizes text input" + tags: ["nlp", "text"] + capabilities: + streaming: true + pushNotifications: false + defaultInputModes: ["text/plain"] + defaultOutputModes: ["text/plain"] + + # Fetch metadata + fetchedAt: "2026-05-21T10:30:00Z" + cardId: "a1b2c3d4e5f6..." + protocol: "a2a" + lastPodTemplateHash: "6b7c8d9e0f" + + # Verification fields (populated when mTLS is active) + validSignature: true + signatureKeyID: "key-001" + signatureVerificationDetails: "JWS signature valid (x5c chain verified)" + attestedAgentSpiffeID: "spiffe://example.org/ns/default/sa/my-agent" +``` + +## Condition Contract: CardSynced + +Added to `status.conditions[]` alongside existing conditions (Ready, TargetResolved, ConfigResolved). + +| Reason | Status | Trigger | +|--------|--------|---------| +| `CardSynced` | True | Successful fetch and parse | +| `FetchSkipped` | True | Pod template hash unchanged, existing data valid | +| `CardFetchFailed` | False | HTTP or mTLS connection error | +| `CardParseFailed` | False | Response is not valid A2A JSON | +| `ServiceNotFound` | False | No Service matches the Deployment selector | +| `WorkloadNotReady` | False | Target Deployment has zero ready Pods | +| `CardDiscoveryDisabled` | False | Feature flag is disabled | + +## Feature Flag + +``` +--enable-card-discovery=false # default: disabled +``` + +When disabled: `status.card` is nil, no `CardSynced` condition is set. If previously enabled and data exists, `status.card` is cleared on the next reconcile. + +## Backward Compatibility + +- No changes to `spec` fields on AgentRuntime +- No changes to existing `status` fields (phase, configuredPods, existing conditions) +- AgentCard CRD continues to function independently +- No API version bump required (additive status change only) diff --git a/specs/001-agentcard-into-status/data-model.md b/specs/001-agentcard-into-status/data-model.md new file mode 100644 index 00000000..8445578c --- /dev/null +++ b/specs/001-agentcard-into-status/data-model.md @@ -0,0 +1,90 @@ +# Data Model: Consolidate AgentCard Data Into AgentRuntime Status + +## Entity Changes + +### New: CardStatus (AgentRuntime status.card) + +Added to `AgentRuntimeStatus` as an optional field. + +**Fields**: + +| Field | Type | Description | Source | +|-------|------|-------------|--------| +| `name` | string | Agent name from A2A card | AgentCardData (embedded) | +| `description` | string | Agent description | AgentCardData (embedded) | +| `version` | string | Agent version | AgentCardData (embedded) | +| `url` | string | Agent endpoint URL | AgentCardData (embedded) | +| `documentationUrl` | string | Documentation URL | AgentCardData (embedded) | +| `iconUrl` | string | Agent icon URL | AgentCardData (embedded) | +| `provider` | AgentProvider | Service provider info | AgentCardData (embedded) | +| `capabilities` | AgentCapabilities | A2A capability set | AgentCardData (embedded) | +| `defaultInputModes` | []string | Supported input media types | AgentCardData (embedded) | +| `defaultOutputModes` | []string | Supported output media types | AgentCardData (embedded) | +| `skills` | []AgentSkill | Agent skills list | AgentCardData (embedded) | +| `supportsAuthenticatedExtendedCard` | *bool | Extended card support | AgentCardData (embedded) | +| `signatures` | []AgentCardSignature | JWS signatures | AgentCardData (embedded) | +| `fetchedAt` | *metav1.Time | Last successful fetch timestamp | New field | +| `cardId` | string | SHA-256 hash of card content | New field | +| `protocol` | string | Detected agent protocol (e.g., "a2a") | New field | +| `validSignature` | *bool | JWS signature validation result | New field | +| `signatureKeyID` | string | Key ID from verified JWS header | New field | +| `signatureVerificationDetails` | string | Verification details/error message | New field | +| `attestedAgentSpiffeID` | string | SPIFFE ID from mTLS peer certificate | New field | +| `lastPodTemplateHash` | string | Pod template hash at time of fetch (internal, for change detection) | New field | + +### Modified: AgentRuntimeStatus + +| Field | Change | Before | After | +|-------|--------|--------|-------| +| `card` | Added | (not present) | `*CardStatus` (optional) | + +### Modified: AgentRuntimeReconciler + +| Field | Change | Description | +|-------|--------|-------------| +| `AgentFetcher` | Added | `agentcard.Fetcher` interface (plain HTTP) | +| `AuthenticatedFetcher` | Added | `agentcard.AuthenticatedFetcher` interface (mTLS) | +| `SignatureProvider` | Added | `signature.Provider` interface (JWS verification) | +| `EnableCardDiscovery` | Added | Feature flag (bool) | +| `SpireTrustDomain` | Added | SPIFFE trust domain string | + +### New Condition: CardSynced + +Added to AgentRuntime `status.conditions[]`. + +| Reason | Status | When | +|--------|--------|------| +| `CardSynced` | True | Card fetched and parsed successfully | +| `CardFetchFailed` | False | HTTP/mTLS fetch error | +| `CardParseFailed` | False | JSON parse error | +| `ServiceNotFound` | False | No Service matches the Deployment selector | +| `WorkloadNotReady` | False | Deployment has zero ready Pods | +| `CardDiscoveryDisabled` | False | Feature flag is off (clears stale data) | +| `FetchSkipped` | True | Pod template hash unchanged, card data still valid | + +## Relationships + +``` +AgentRuntime (1) ---> (0..1) CardStatus (status.card) + | + +--> AgentCardData (embedded, reused from api/v1alpha1) + +--> Fetch metadata (fetchedAt, cardId, protocol) + +--> Verification fields (validSignature, attestedAgentSpiffeID, ...) + +AgentRuntime.spec.targetRef ---> Deployment/StatefulSet/Sandbox + | + +--> Pod selector labels ---> Service (selector match) + | + +--> /.well-known/agent-card.json +``` + +## State Transitions for status.card + +``` +[Empty] -- feature flag enabled + reconcile --> [Fetching] +[Fetching] -- success --> [Populated] (card data + metadata + verification) +[Fetching] -- failure --> [Empty or Stale] (retain last good data, CardSynced=False) +[Populated] -- pod template hash change --> [Fetching] (re-fetch) +[Populated] -- feature flag disabled --> [Empty] (cleared on next reconcile) +[Populated] -- workload deleted --> [Stale] (AgentRuntime still exists, card data stale) +``` diff --git a/specs/001-agentcard-into-status/plan.md b/specs/001-agentcard-into-status/plan.md new file mode 100644 index 00000000..7437dde8 --- /dev/null +++ b/specs/001-agentcard-into-status/plan.md @@ -0,0 +1,73 @@ +# Implementation Plan: Consolidate AgentCard Data Into AgentRuntime Status + +**Branch**: `001-agentcard-into-status` | **Date**: 2026-05-21 | **Spec**: [spec.md](spec.md) +**Input**: Feature specification from `specs/001-agentcard-into-status/spec.md` + +## Summary + +Move A2A agent card discovery into the AgentRuntime controller's reconcile loop so operators can read card data, fetch metadata, and mTLS verification results from a single resource (`status.card` on AgentRuntime). Reuses the existing `agentcard.Fetcher` and `agentcard.AuthenticatedFetcher` interfaces and the `AgentCardData` struct. The feature is gated behind a `--enable-card-discovery` flag (default: disabled). AgentCard CRD remains functional with a deprecation warning. + +## Technical Context + +**Language/Version**: Go 1.25, controller-runtime v0.23.3 +**Primary Dependencies**: controller-runtime, go-spiffe/v2, k8s.io/apimachinery +**Storage**: Kubernetes CRD status subresource (no external storage) +**Testing**: Ginkgo/Gomega (unit + integration), envtest for controller tests, e2e in `test/e2e/` +**Target Platform**: Kubernetes 1.31+ +**Project Type**: Kubernetes operator (kubebuilder-based) +**Performance Goals**: Card fetch adds < 1s to reconcile; no periodic re-fetch (event-driven only) +**Constraints**: No new CRDs; feature-gated; backward compatible with existing AgentCard workflows +**Scale/Scope**: Hundreds of AgentRuntimes per cluster (card fetch is 1:1 with AgentRuntime) + +## Constitution Check + +*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* + +Constitution is a template (not customized for this project). No gates to evaluate. +Project follows standard kubebuilder patterns: CRD types in `api/v1alpha1/`, controllers in `internal/controller/`, shared logic in `internal/` packages. + +## Project Structure + +### Documentation (this feature) + +```text +specs/001-agentcard-into-status/ +├── plan.md # This file +├── research.md # Phase 0 output +├── data-model.md # Phase 1 output +├── contracts/ # Phase 1 output (CRD status contract) +└── tasks.md # Phase 2 output (/speckit.tasks) +``` + +### Source Code (repository root) + +```text +kagenti-operator/ +├── api/v1alpha1/ +│ ├── agentruntime_types.go # MODIFY: add CardStatus to AgentRuntimeStatus +│ ├── agentcard_types.go # READ-ONLY: reuse AgentCardData struct +│ └── zz_generated.deepcopy.go # REGENERATE: after type changes +├── internal/ +│ ├── agentcard/ +│ │ └── fetcher.go # READ-ONLY: reuse Fetcher, AuthenticatedFetcher, SpiffeFetcher +│ ├── controller/ +│ │ ├── agentruntime_controller.go # MODIFY: add card fetch phase to reconcile +│ │ ├── agentruntime_controller_test.go # MODIFY: add card fetch tests +│ │ ├── agentcard_controller.go # MODIFY: add deprecation warning log +│ │ └── agentcard_controller_test.go # MODIFY: test deprecation warning +│ └── signature/ # READ-ONLY: reuse VerificationResult, Provider +├── cmd/ +│ └── main.go # MODIFY: add --enable-card-discovery flag, wire fetchers +├── config/ +│ ├── crd/bases/ # REGENERATE: CRD manifests after type changes +│ └── rbac/ # MODIFY: add Service list/watch RBAC for agentruntime controller +└── test/ + ├── e2e/ # MODIFY: add card discovery e2e scenarios + └── integration/ # MODIFY: add card fetch integration tests +``` + +**Structure Decision**: Existing kubebuilder project structure. All changes extend existing files. No new packages or directories needed. + +## Complexity Tracking + +No constitution violations. No complexity justification needed. diff --git a/specs/001-agentcard-into-status/research.md b/specs/001-agentcard-into-status/research.md new file mode 100644 index 00000000..91a67d03 --- /dev/null +++ b/specs/001-agentcard-into-status/research.md @@ -0,0 +1,70 @@ +# Research: Consolidate AgentCard Data Into AgentRuntime Status + +## R1: Service Endpoint Resolution Strategy + +**Decision**: Selector matching. Resolve the Deployment's Pod selector labels, list Services in the same namespace, find the first Service whose selector matches. + +**Rationale**: Standard Kubernetes pattern. Works automatically without user annotations. The existing AgentCard controller uses a simpler convention (Service name = workload name via `workload.ServiceName`), but selector matching is more robust for cases where Service and Deployment names diverge. + +**Alternatives considered**: +- Naming convention (Service name = Deployment name): simpler but brittle when names differ +- Annotation-driven: more flexible but adds configuration burden +- Hybrid (selector match + annotation override): future enhancement if needed + +**Implementation note**: The existing `AgentCardReconciler.getWorkload()` sets `ServiceName: targetRef.Name` (line 526 of agentcard_controller.go). For the AgentRuntime controller, we should match this convention initially (use the Deployment name as the Service name) since it aligns with how Services are typically created for agent workloads. If no Service matches by name, fall back to selector matching. + +## R2: Card Fetch Trigger Mechanism + +**Decision**: Pod template hash change detection. The AgentRuntime controller already watches Deployments and reconciles on changes. The card fetch phase checks whether the Deployment's `pod-template-hash` has changed since the last successful fetch by comparing against a stored hash in `status.card`. + +**Rationale**: Avoids unnecessary HTTP calls on every reconcile. Pod template hash changes correlate with actual code/config changes that could affect the agent card. The AgentRuntime controller already reconciles on Deployment changes, so no new watches needed. + +**Alternatives considered**: +- Periodic polling (SyncPeriod): wastes resources when nothing changed +- Generation-based: doesn't capture all relevant changes +- Always fetch: simpler but wasteful at scale + +## R3: Reusable Code from AgentCard Controller and PR #284 + +**Decision**: Reuse the following components directly: + +| Component | Package | Reuse strategy | +|-----------|---------|----------------| +| `agentcard.Fetcher` interface | `internal/agentcard` | Direct reuse, inject into AgentRuntime reconciler | +| `agentcard.AuthenticatedFetcher` interface | `internal/agentcard` | Direct reuse for mTLS path | +| `agentcard.SpiffeFetcher` | `internal/agentcard` | Direct reuse, same X509Source | +| `agentcard.ConfigMapFetcher` | `internal/agentcard` | Direct reuse for signed card ConfigMap path | +| `AgentCardData` struct | `api/v1alpha1` | Embed in new `CardStatus` struct | +| `signature.Provider` interface | `internal/signature` | Direct reuse for JWS verification | +| `signature.VerificationResult` | `internal/signature` | Direct reuse for verification fields | +| `doHTTPFetch()` | `internal/agentcard` | Already shared between fetchers | +| `extractSpiffeIDFromTLS()` | `internal/agentcard` | Already used by SpiffeFetcher | + +**Rationale**: The entire fetch, parse, and verify pipeline already exists. The AgentRuntime controller just needs to call the same interfaces and write results to a different status struct. + +## R4: Feature Flag Design + +**Decision**: Add `--enable-card-discovery` flag to the operator binary (cmd/main.go). When disabled, the card fetch phase in the reconcile loop is a no-op. When toggled off, the reconciler clears `status.card` on the next reconcile. + +**Rationale**: Follows the existing pattern of `--enable-verified-fetch` flag on the AgentCard controller. Simple boolean flag, no runtime reconfiguration needed. + +**Implementation note**: The flag controls whether the `Fetcher` and `AuthenticatedFetcher` are injected into the AgentRuntime reconciler at startup. When disabled, the fields are nil and the card fetch phase short-circuits. + +## R5: CardStatus Struct Design + +**Decision**: New `CardStatus` struct wrapping `AgentCardData` with fetch metadata and verification fields. Placed in `api/v1alpha1/agentruntime_types.go`. + +**Rationale**: Keeps card payload separate from fetch/verification metadata. The `AgentCardData` struct is reused as-is (no duplication). Fetch metadata and verification fields are added alongside, not mixed into the card payload. + +**Fields**: +- Card payload: embedded `AgentCardData` (name, description, version, url, skills, capabilities, etc.) +- Fetch metadata: `fetchedAt` (timestamp), `cardId` (SHA-256 content hash), `protocol` (detected agent protocol) +- Verification: `validSignature` (bool), `signatureKeyID`, `attestedAgentSpiffeID`, `signatureVerificationDetails` + +## R6: Deprecation Warning Implementation + +**Decision**: Add a log warning at Info level in `AgentCardReconciler.Reconcile()` when processing a newly created AgentCard (check if `agentCard.CreationTimestamp` is within the last reconcile window). Also emit a Kubernetes Event. + +**Rationale**: Non-intrusive. Operators see it in logs and events without any behavior change. The AgentCard controller continues to function normally. + +**Implementation note**: Check `agentCard.CreationTimestamp.After(time.Now().Add(-5 * time.Minute))` as a heuristic for "recently created". Log once per new card, not on every reconcile. diff --git a/specs/001-agentcard-into-status/spec.md b/specs/001-agentcard-into-status/spec.md new file mode 100644 index 00000000..7ae28bd0 --- /dev/null +++ b/specs/001-agentcard-into-status/spec.md @@ -0,0 +1,132 @@ +# Feature Specification: Consolidate AgentCard Data Into AgentRuntime Status + +**Feature Branch**: `001-agentcard-into-status` +**Created**: 2026-05-21 +**Status**: Draft +**Input**: Brainstorm document `brainstorm/01-agentcard-into-agentruntime.md` + +## Clarifications + +### Session 2026-05-21 + +- Q: How should the controller discover the Service endpoint for a given AgentRuntime's targetRef Deployment? → A: Selector match: resolve Deployment Pod selector, find Services whose selector matches, use the first match. +- Q: What happens to previously populated status.card data when a card fetch fails? → A: Retain last successful card data; rely on the CardSynced condition and fetchedAt timestamp to signal staleness. +- Q: What happens to existing status.card data when the feature flag is toggled off? → A: Clear status.card on the next reconcile of each AgentRuntime when the flag is disabled. +- Q: What should status.card contain, given mTLS is in scope? → A: Card payload fields, fetch metadata (fetchedAt, cardId, protocol), and verification fields (signature validation, SPIFFE identity). mTLS reuses infrastructure from PR #284. + +## User Scenarios & Testing *(mandatory)* + +### User Story 1 - Discover Agent Capabilities from AgentRuntime (Priority: P1) + +A platform operator queries a single resource (AgentRuntime) to see what an agent can do, including its name, description, skills, supported protocols, and endpoint URL. Today this requires cross-referencing AgentRuntime and AgentCard, which doubles the lookup effort and makes scripting harder. + +**Why this priority**: This is the core value of the consolidation. Without card data surfaced on AgentRuntime, the entire feature has no observable benefit. + +**Independent Test**: Deploy an agent workload with a valid `/.well-known/agent-card.json` endpoint, create an AgentRuntime targeting it, and confirm `status.card` is populated with the card data via `kubectl get agentruntime -o yaml`. + +**Acceptance Scenarios**: + +1. **Given** an AgentRuntime targeting a Deployment whose Pods serve a valid A2A agent card at `/.well-known/agent-card.json`, **When** the controller reconciles the AgentRuntime, **Then** `status.card` contains the agent's name, description, skills, capabilities, endpoint URL, fetchedAt timestamp, cardId content hash, and detected protocol. +2. **Given** an AgentRuntime whose `status.card` is already populated, **When** the backing Deployment rolls out a new Pod template (hash change), **Then** the controller re-fetches the card and updates `status.card` with the new content. +3. **Given** an AgentRuntime targeting a Deployment, **When** the agent's card endpoint is unreachable or returns invalid JSON, **Then** `status.card` retains the last successfully fetched data and a `CardSynced` condition indicates the fetch failure with a human-readable reason. + +--- + +### User Story 2 - Verified Card Discovery via mTLS (Priority: P1) + +A platform operator deploying agents with SPIFFE identity (via SPIRE) can see the card's signature verification status and the attested SPIFFE ID directly on the AgentRuntime, confirming the card was fetched over a verified mTLS connection and its JWS signature is valid. + +**Why this priority**: Identity verification is a core platform security requirement. Reusing the mTLS infrastructure from PR #284 makes this achievable without significant new code. + +**Independent Test**: Deploy an agent with SPIRE identity configured, create an AgentRuntime with mTLS mode enabled, and verify `status.card` includes signature validation result and attested SPIFFE ID. + +**Acceptance Scenarios**: + +1. **Given** an AgentRuntime with mTLS enabled and a backing agent that presents a valid SPIFFE certificate, **When** the controller fetches the card over mTLS, **Then** `status.card` includes the attested SPIFFE ID extracted from the peer certificate and the signature validation result. +2. **Given** an AgentRuntime with mTLS enabled and a backing agent whose SPIFFE certificate does not match the expected trust domain, **When** the controller attempts to fetch the card, **Then** the fetch fails, `status.card` retains stale data, and the `CardSynced` condition reports an identity verification failure. +3. **Given** an AgentRuntime without mTLS configured (or mTLS disabled), **When** the controller fetches the card, **Then** the fetch uses plain HTTP and verification fields in `status.card` remain empty. + +--- + +### User Story 3 - Deprecation Warning on AgentCard Creation (Priority: P2) + +A platform operator who still creates AgentCard CRs receives a clear deprecation warning so they know to migrate to the AgentRuntime-based discovery path. + +**Why this priority**: Backward compatibility is essential during the transition period. Operators need a signal to migrate without breaking existing workflows. + +**Independent Test**: Create an AgentCard CR and check controller logs for the deprecation warning message. + +**Acceptance Scenarios**: + +1. **Given** the operator is running with card discovery enabled, **When** a new AgentCard CR is created, **Then** the controller emits a deprecation log warning indicating that AgentCard is deprecated and card data should be consumed from AgentRuntime `status.card`. +2. **Given** an existing AgentCard CR, **When** the controller reconciles it, **Then** the AgentCard continues to function normally (both CRDs coexist). + +--- + +### User Story 4 - Feature-Gated Card Discovery (Priority: P2) + +A cluster administrator controls whether the new card discovery behavior is active via a feature flag, allowing gradual rollout without code changes. + +**Why this priority**: Operators need to opt in during the transition period. Disabling by default prevents surprises for existing installations. + +**Independent Test**: Start the operator with the feature flag disabled and verify no card fetch occurs; enable the flag and verify card fetching activates. + +**Acceptance Scenarios**: + +1. **Given** the operator is started without enabling the card discovery feature flag, **When** an AgentRuntime is reconciled, **Then** no card fetch is attempted and `status.card` remains empty. +2. **Given** the operator is started with the card discovery feature flag enabled, **When** an AgentRuntime is reconciled, **Then** the controller fetches `/.well-known/agent-card.json` from the agent's Service endpoint and populates `status.card`. +3. **Given** an AgentRuntime with populated `status.card` data, **When** the operator restarts with the feature flag disabled, **Then** `status.card` is cleared on the next reconcile of each AgentRuntime. + +--- + +### Edge Cases + +- What happens when the agent's Service has multiple ports? The controller uses selector matching to find the Service, then targets the port serving the A2A protocol (by well-known port name or the first HTTP port). +- How does the system handle a card endpoint that returns a valid JSON response but not a valid A2A agent card structure? The controller treats it as a fetch failure, retains any previously fetched data, and surfaces the parsing error in the `CardSynced` condition. +- What happens when the backing Deployment has zero ready Pods? The controller skips the card fetch and sets a condition indicating the workload is not ready. +- What happens if the card response is excessively large? The controller enforces a size limit on the response body to prevent resource exhaustion. +- What happens when no Service matches the Deployment's Pod selector? The controller sets the `CardSynced` condition to false with reason "ServiceNotFound" and skips the fetch. +- What happens when the mTLS handshake fails (e.g., certificate expired, wrong trust domain)? The controller retains stale card data and reports the TLS error in the `CardSynced` condition. + +## Requirements *(mandatory)* + +### Functional Requirements + +- **FR-001**: The system MUST add a `card` field to AgentRuntime status that holds: A2A agent card payload (name, description, version, URL, skills, capabilities, provider, input/output modes), fetch metadata (fetchedAt timestamp, cardId content hash, detected protocol), and verification fields (signature validation result, attested SPIFFE ID). +- **FR-002**: The system MUST fetch the agent card from `/.well-known/agent-card.json` on the agent's Service endpoint when reconciling an AgentRuntime. +- **FR-003**: The system MUST trigger card re-fetch only on Pod template hash changes (rollout events), not on a periodic timer. +- **FR-004**: The system MUST record a `CardSynced` condition on AgentRuntime status indicating the result of the last fetch attempt (success, failure with reason, or skipped). +- **FR-005**: The system MUST gate the card fetch behavior behind a feature flag that defaults to disabled. +- **FR-006**: The system MUST emit a deprecation log warning when a new AgentCard CR is created. +- **FR-007**: The system MUST record a `fetchedAt` timestamp in `status.card` so operators can see when the card data was last refreshed. +- **FR-008**: The system MUST resolve the agent's Service endpoint by matching the Deployment's Pod selector labels to Services in the same namespace (selector match), using the first matching Service. +- **FR-009**: The system MUST enforce a maximum response body size when fetching the card to prevent resource exhaustion. +- **FR-010**: The system MUST support mTLS for the card fetch, reusing the SPIFFE/SPIRE infrastructure from PR #284. When mTLS is configured on the AgentRuntime, the controller uses the workload's SVID to establish a verified connection. +- **FR-011**: The system MUST populate verification fields in `status.card` (attested SPIFFE ID, signature validation result) when the card is fetched over mTLS and the card contains JWS signatures. +- **FR-012**: The system MUST clear `status.card` when the feature flag is disabled, on the next reconcile of each AgentRuntime. +- **FR-013**: The system MUST retain the last successfully fetched card data when a fetch attempt fails, relying on the `CardSynced` condition and `fetchedAt` timestamp to signal staleness. + +### Key Entities + +- **AgentRuntime**: Existing CRD that attaches runtime configuration to a workload. Extended with `status.card` to hold discovered agent card data, fetch metadata, and verification results. +- **AgentCard (existing, deprecated)**: Existing CRD that caches A2A card data. Continues to function but emits deprecation warnings. Will be removed in a future iteration. +- **A2A Agent Card**: The JSON document served by agents at `/.well-known/agent-card.json` per the A2A protocol specification. Source of truth for card data. + +## Success Criteria *(mandatory)* + +### Measurable Outcomes + +- **SC-001**: Operators can retrieve agent capabilities (name, skills, endpoint) and identity verification status from a single `kubectl get agentruntime` command instead of cross-referencing two resources. +- **SC-002**: Card data in `status.card` reflects the running agent's actual capabilities within one reconciliation cycle after a rollout. +- **SC-003**: Existing AgentCard-based workflows continue to function without modification during the transition period. +- **SC-004**: The feature can be enabled or disabled at operator startup without redeployment of agent workloads. Disabling clears stale card data from all AgentRuntimes. +- **SC-005**: When mTLS is configured, the card fetch verifies the agent's SPIFFE identity and validates JWS signatures, with results visible in `status.card`. + +## Assumptions + +- Each AgentRuntime targets exactly one Deployment, and there is at most one Service matching that Deployment's Pod selector in the same namespace. +- The card fetch adds negligible latency to the reconcile loop (single HTTP/mTLS GET, typically sub-second). +- The existing `AgentCardData` Go struct (already defined in the codebase) can be extended or wrapped to include fetch metadata and verification fields for `status.card`. +- IBM maintainers have agreed to the AgentCard deprecation path (confirmed 2026-05-15 per brainstorm context). +- The mTLS verified fetch infrastructure from PR #284 (merged 2026-05-20) is stable and reusable for the AgentRuntime controller's card fetch. +- Agents with SPIRE identity configured already have SVIDs available via spiffe-helper in the operator Pod or via the SPIRE agent socket. diff --git a/specs/001-agentcard-into-status/tasks.md b/specs/001-agentcard-into-status/tasks.md new file mode 100644 index 00000000..3c34e0c9 --- /dev/null +++ b/specs/001-agentcard-into-status/tasks.md @@ -0,0 +1,202 @@ +# Tasks: Consolidate AgentCard Data Into AgentRuntime Status + +**Input**: Design documents from `specs/001-agentcard-into-status/` +**Prerequisites**: plan.md, spec.md, research.md, data-model.md, contracts/ + +**Tests**: Test tasks are included since this is a controller change requiring unit and integration test coverage. + +**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story. + +## Format: `[ID] [P?] [Story] Description` + +- **[P]**: Can run in parallel (different files, no dependencies) +- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3) +- Include exact file paths in descriptions + +## Phase 1: Setup + +**Purpose**: CRD type changes and code generation that all stories depend on + +- [ ] T001 Add `CardStatus` struct to `AgentRuntimeStatus` in `kagenti-operator/api/v1alpha1/agentruntime_types.go`. The `CardStatus` struct embeds `AgentCardData` (reuse existing struct) and adds: `FetchedAt *metav1.Time`, `CardId string`, `Protocol string`, `ValidSignature *bool`, `SignatureKeyID string`, `SignatureVerificationDetails string`, `AttestedAgentSpiffeID string`, `LastPodTemplateHash string`. Add the `Card *CardStatus` field to `AgentRuntimeStatus`. Add `CardSynced` as a new condition type constant. +- [ ] T002 Run `make generate` and `make manifests` in `kagenti-operator/` to regenerate deepcopy functions and CRD manifests. Verify `zz_generated.deepcopy.go` has the new `CardStatus` deepcopy method and `config/crd/bases/` has the updated AgentRuntime CRD. +- [ ] T003 Add `--enable-card-discovery` boolean flag (default: false) to `kagenti-operator/cmd/main.go`. When enabled, create `agentcard.NewConfigMapFetcher()` and `agentcard.NewSpiffeFetcher()` (conditional on SPIRE config), and inject them into the `AgentRuntimeReconciler` as new fields: `AgentFetcher agentcard.Fetcher`, `AuthenticatedFetcher agentcard.AuthenticatedFetcher`, `SignatureProvider signature.Provider`, `EnableCardDiscovery bool`, `SpireTrustDomain string`. Follow the existing pattern of `--enable-verified-fetch` flag wiring for the AgentCard controller. + +**Checkpoint**: CRD types updated, code generated, flag wired. Ready for controller changes. + +--- + +## Phase 2: Foundational (Blocking Prerequisites) + +**Purpose**: Service resolution helper and RBAC that all card discovery stories need + +**CRITICAL**: No user story work can begin until this phase is complete + +- [ ] T004 Add a `resolveServiceForWorkload` method to `AgentRuntimeReconciler` in `kagenti-operator/internal/controller/agentruntime_controller.go`. Given a namespace and Deployment name, first try to get a Service with the same name (matching existing AgentCard convention). If not found, list Services in the namespace, match by Pod selector labels from the Deployment, and return the first match. Return the Service object and the selected port (first HTTP port, or default 8000). Also add `getAgentTLSPort` (reuse logic from `agentcard_controller.go` line 684). +- [ ] T005 [P] Add Service `get;list;watch` RBAC for the agentruntime controller in `kagenti-operator/internal/controller/agentruntime_controller.go` via kubebuilder RBAC markers: `// +kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch`. Run `make manifests` to update `config/rbac/`. + +**Checkpoint**: Service resolution and RBAC ready. User story implementation can begin. + +--- + +## Phase 3: User Story 1 - Discover Agent Capabilities from AgentRuntime (Priority: P1) MVP + +**Goal**: AgentRuntime `status.card` is populated with A2A card data fetched from the agent's Service endpoint on rollout events. Operators can read card data from a single resource. + +**Independent Test**: Deploy an agent with a valid `/.well-known/agent-card.json`, create an AgentRuntime, and confirm `status.card` is populated via `kubectl get agentruntime -o yaml`. + +### Tests for User Story 1 + +- [ ] T006 [P] [US1] Add unit tests for `resolveServiceForWorkload` in `kagenti-operator/internal/controller/agentruntime_controller_test.go`. Test cases: Service found by name, Service found by selector match, no matching Service, multiple ports (uses first HTTP port), Deployment with no ready pods. +- [ ] T007 [P] [US1] Add unit tests for the card fetch phase in `kagenti-operator/internal/controller/agentruntime_controller_test.go`. Test cases: successful fetch populates `status.card` with all fields, fetch failure retains stale data and sets `CardSynced=False`, invalid JSON sets `CardParseFailed` condition, feature flag disabled skips fetch, pod template hash unchanged skips fetch, feature flag toggled off clears `status.card`. + +### Implementation for User Story 1 + +- [ ] T008 [US1] Add a `fetchAndUpdateCard` method to `AgentRuntimeReconciler` in `kagenti-operator/internal/controller/agentruntime_controller.go`. This is the main card fetch phase, called from `Reconcile()` after step 5 (config hash). Logic: (1) If `EnableCardDiscovery` is false, clear `status.card` if populated, set `CardSynced` condition to `CardDiscoveryDisabled`, return. (2) Check pod template hash from the target Deployment against `status.card.lastPodTemplateHash`; if unchanged and `status.card` is populated, set `CardSynced` to `FetchSkipped`, return. (3) Call `resolveServiceForWorkload`. (4) Build service URL via `agentcard.GetServiceURL`. (5) Call `AgentFetcher.Fetch()`. (6) On success: build `CardStatus` from fetched `AgentCardData`, set `fetchedAt`, compute `cardId` via SHA-256, set `protocol`, store current pod template hash. (7) On failure: retain existing `status.card`, set `CardSynced=False` with error reason. (8) Update status via `r.Status().Update()` with retry. Note: FR-009 (max response body size) is implicitly satisfied because the reused `doHTTPFetch()` in `internal/agentcard/fetcher.go` already enforces `maxCardSize = 1 MiB`. +- [ ] T009 [US1] Wire the `fetchAndUpdateCard` call into the `Reconcile()` method in `kagenti-operator/internal/controller/agentruntime_controller.go`. Insert after the config hash computation (step 5, around line 165) and before the label propagation phase. Pass the resolved target workload info. +- [ ] T010 [US1] Extract the pod template hash from the target Deployment in the `fetchAndUpdateCard` method. For Deployments, read `spec.template` metadata hash. For StatefulSets and Sandboxes, use the resource generation as the change-detection key. + +**Checkpoint**: US1 complete. `status.card` is populated on reconcile when the flag is enabled. + +--- + +## Phase 4: User Story 2 - Verified Card Discovery via mTLS (Priority: P1) + +**Goal**: When mTLS is configured, the card fetch uses the SPIFFE/SPIRE infrastructure from PR #284 to establish a verified connection. Verification results (attested SPIFFE ID, signature validation) are surfaced in `status.card`. + +**Independent Test**: Deploy an agent with SPIRE identity, enable card discovery and mTLS on the AgentRuntime, and verify `status.card` includes `attestedAgentSpiffeID` and `validSignature` fields. + +### Tests for User Story 2 + +- [ ] T011 [P] [US2] Add unit tests for mTLS card fetch in `kagenti-operator/internal/controller/agentruntime_controller_test.go`. Test cases: mTLS fetch populates `attestedAgentSpiffeID` and `validSignature`, mTLS handshake failure retains stale data and sets condition, fallback to HTTP when no TLS port, JWS signature verification populates `signatureKeyID`. + +### Implementation for User Story 2 + +- [ ] T012 [US2] Extend `fetchAndUpdateCard` in `kagenti-operator/internal/controller/agentruntime_controller.go` to support mTLS. After resolving the Service, check for the `agent-tls` named port (reuse `getAgentTLSPort`). If present and `AuthenticatedFetcher` is not nil, call `AuthenticatedFetcher.FetchAuthenticated()` instead of `AgentFetcher.Fetch()`. On success, populate `attestedAgentSpiffeID` from `FetchResult.AgentSpiffeID`. +- [ ] T013 [US2] Add JWS signature verification to `fetchAndUpdateCard` in `kagenti-operator/internal/controller/agentruntime_controller.go`. After fetching the card data (mTLS or HTTP), if `SignatureProvider` is not nil and the card has signatures, call `SignatureProvider.VerifySignature()`. Populate `status.card.validSignature`, `status.card.signatureKeyID`, and `status.card.signatureVerificationDetails` from the `VerificationResult`. +- [ ] T014 [US2] Handle mTLS fallback to HTTP in `fetchAndUpdateCard`. If `AuthenticatedFetcher` is set but no `agent-tls` port exists on the Service, fall back to `AgentFetcher.Fetch()` and leave verification fields empty. Log a warning and emit a Kubernetes Event (reuse pattern from agentcard_controller.go line 351-356). + +**Checkpoint**: US2 complete. mTLS verified card fetch works with SPIFFE identity extraction and JWS signature validation. + +--- + +## Phase 5: User Story 3 - Deprecation Warning on AgentCard Creation (Priority: P2) + +**Goal**: When a new AgentCard CR is created, the controller emits a deprecation log warning. Existing AgentCards continue to function normally. + +**Independent Test**: Create an AgentCard CR and check controller logs for the deprecation message. + +### Tests for User Story 3 + +- [ ] T015 [P] [US3] Add unit test for deprecation warning in `kagenti-operator/internal/controller/agentcard_controller_test.go`. Verify that reconciling a recently created AgentCard emits a deprecation log message and Kubernetes Event. + +### Implementation for User Story 3 + +- [ ] T016 [US3] Add deprecation warning to `AgentCardReconciler.Reconcile()` in `kagenti-operator/internal/controller/agentcard_controller.go`. After the deletion/finalizer check (around line 166), check if `agentCard.CreationTimestamp` is within the last 5 minutes. If so, log a warning: "AgentCard is deprecated; card data is now available via AgentRuntime status.card. Migrate to AgentRuntime-based discovery." Also emit a Kubernetes Event with reason "Deprecated" and type Warning. + +**Checkpoint**: US3 complete. Deprecation warnings emitted for new AgentCard CRs. + +--- + +## Phase 6: User Story 4 - Feature-Gated Card Discovery (Priority: P2) + +**Goal**: The card discovery behavior is fully controlled by the `--enable-card-discovery` flag. Disabling the flag clears stale card data. + +**Independent Test**: Start the operator with the flag disabled, verify no card fetch. Enable the flag, verify card fetch works. Disable again, verify `status.card` is cleared. + +### Tests and Verification for User Story 4 + +- [ ] T017 [US4] Add unit tests for feature flag toggle behavior in `kagenti-operator/internal/controller/agentruntime_controller_test.go`. Test cases: (1) flag disabled means no card fetch attempted and `status.card` remains nil, (2) flag disabled clears existing populated `status.card` data and sets `CardSynced` condition to `CardDiscoveryDisabled`, (3) flag enabled triggers fetch and populates `status.card`, (4) toggling flag off after previous population clears data on next reconcile. These tests validate the flag-off cleanup logic built in T008 step 1. + +**Checkpoint**: US4 complete. Feature flag fully controls card discovery lifecycle. + +--- + +## Phase 7: Polish and Cross-Cutting Concerns + +**Purpose**: End-to-end validation, documentation, and CRD manifest finalization + +- [ ] T018 [P] Run `make generate && make manifests` in `kagenti-operator/` to ensure all generated code and CRD manifests are up to date after all changes. +- [ ] T019 [P] Add a `CardSynced` print column to the AgentRuntime CRD in `kagenti-operator/api/v1alpha1/agentruntime_types.go` via kubebuilder marker: `// +kubebuilder:printcolumn:name="CardSynced",type="string",JSONPath=".status.conditions[?(@.type=='CardSynced')].status",description="Card Sync Status"`. Regenerate manifests. +- [ ] T020 [P] Add e2e test scenario to `kagenti-operator/test/e2e/e2e_test.go` that deploys a test agent Deployment with a mock `/.well-known/agent-card.json` endpoint, creates an AgentRuntime targeting it with card discovery enabled, and verifies `status.card` is populated within 30 seconds. +- [ ] T021 Run full test suite: `make test` in `kagenti-operator/`. Fix any regressions. Ensure existing AgentCard controller tests still pass. +- [ ] T022 Verify CRD backward compatibility: confirm existing AgentRuntime CRs without `status.card` continue to work without errors when the operator runs with card discovery disabled. + +--- + +## Dependencies and Execution Order + +### Phase Dependencies + +- **Phase 1 (Setup)**: No dependencies, start immediately +- **Phase 2 (Foundational)**: Depends on Phase 1 (T001-T003 must complete) +- **Phase 3 (US1)**: Depends on Phase 2 (T004-T005 must complete) +- **Phase 4 (US2)**: Depends on Phase 3 (T008-T010 must complete, extends `fetchAndUpdateCard`) +- **Phase 5 (US3)**: Can start after Phase 1 (independent of US1/US2, different controller file) +- **Phase 6 (US4)**: Depends on Phase 3 (validates flag behavior of `fetchAndUpdateCard`) +- **Phase 7 (Polish)**: Depends on all user stories completing + +### User Story Dependencies + +- **US1 (P1)**: After Foundational. No dependencies on other stories. +- **US2 (P1)**: After US1. Extends `fetchAndUpdateCard` with mTLS path. +- **US3 (P2)**: After Setup only. Independent (modifies `agentcard_controller.go`, not `agentruntime_controller.go`). +- **US4 (P2)**: After US1. Validates flag toggle behavior already built in US1. + +### Within Each User Story + +- Tests written first, then implementation +- Service resolution before card fetch +- Plain HTTP fetch before mTLS extension +- Commit after each task or logical group + +### Parallel Opportunities + +- T006 and T007 can run in parallel (different test scenarios, same file) +- T011 and T015 can run in parallel (different controller test files) +- T005 can run in parallel with T004 (RBAC markers vs service resolution logic) +- T019, T020, T021 can run in parallel (different files) +- US3 (Phase 5) can run in parallel with US1 (Phase 3) since they modify different controllers + +--- + +## Parallel Example: User Story 1 + +```bash +# Launch US1 tests in parallel: +Task: "Unit tests for resolveServiceForWorkload in agentruntime_controller_test.go" +Task: "Unit tests for card fetch phase in agentruntime_controller_test.go" + +# US3 can run in parallel with US1 (different controller files): +Task: "Deprecation warning in agentcard_controller.go" +``` + +--- + +## Implementation Strategy + +### MVP First (User Story 1 Only) + +1. Complete Phase 1: Setup (CRD types + flag) +2. Complete Phase 2: Foundational (service resolution + RBAC) +3. Complete Phase 3: User Story 1 (plain HTTP card fetch) +4. **STOP and VALIDATE**: Test card discovery with a real agent deployment +5. Deploy/demo if ready + +### Incremental Delivery + +1. Setup + Foundational: CRD and infrastructure ready +2. Add US1 (plain HTTP card fetch): Test independently, deploy (MVP!) +3. Add US2 (mTLS verification): Test with SPIRE, deploy +4. Add US3 (deprecation warning): Independent, can ship alongside US1 +5. Add US4 (flag toggle validation): Confirms flag lifecycle +6. Polish: e2e tests, print columns, backward compatibility check + +--- + +## Notes + +- [P] tasks = different files, no dependencies +- [Story] label maps task to specific user story for traceability +- Reuses existing `agentcard.Fetcher`, `agentcard.SpiffeFetcher`, `signature.Provider` interfaces (no new packages) +- All new code goes in existing files (no new Go source files) +- CRD change is additive (status field only), no API version bump needed From a4719b7a93c530578864645de1eabf24d96dcc96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Hu=C3=9F?= Date: Thu, 21 May 2026 11:25:31 +0200 Subject: [PATCH 04/11] Remove .specify from tracking and gitignore the directory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .specify directory contains local spec-kit tooling (templates, scripts, extensions) that should not be committed. Ignore the entire directory instead of individual files. Assisted-By: 🤖 Claude Code Signed-off-by: Roland Huß --- .gitignore | 4 +- .specify/extensions.yml | 275 ------ .specify/extensions/.registry | 126 --- .specify/extensions/git/README.md | 100 -- .../git/commands/speckit.git.commit.md | 48 - .../git/commands/speckit.git.feature.md | 67 -- .../git/commands/speckit.git.initialize.md | 49 - .../git/commands/speckit.git.remote.md | 45 - .../git/commands/speckit.git.validate.md | 49 - .specify/extensions/git/config-template.yml | 62 -- .specify/extensions/git/extension.yml | 140 --- .specify/extensions/git/git-config.yml | 60 -- .../git/scripts/bash/auto-commit.sh | 140 --- .../git/scripts/bash/create-new-feature.sh | 453 --------- .../extensions/git/scripts/bash/git-common.sh | 54 -- .../git/scripts/bash/initialize-repo.sh | 54 -- .../git/scripts/powershell/auto-commit.ps1 | 169 ---- .../scripts/powershell/create-new-feature.ps1 | 403 -------- .../git/scripts/powershell/git-common.ps1 | 51 -- .../scripts/powershell/initialize-repo.ps1 | 69 -- .../speckit.spex-collab.phase-manager.md | 302 ------ .../speckit.spex-collab.phase-split.md | 149 --- .../commands/speckit.spex-collab.reconcile.md | 199 ---- .../commands/speckit.spex-collab.reviewers.md | 209 ----- .../commands/speckit.spex-collab.revise.md | 321 ------- .../spex-collab/config-template.yml | 12 - .specify/extensions/spex-collab/extension.yml | 56 -- .../templates/reviewers-template.md | 84 -- .../commands/speckit.spex-deep-review.run.md | 862 ------------------ .../spex-deep-review/config-template.yml | 4 - .../extensions/spex-deep-review/extension.yml | 39 - .../speckit.spex-gates.review-code.md | 419 --------- .../speckit.spex-gates.review-plan.md | 202 ---- .../speckit.spex-gates.review-spec.md | 331 ------- .../commands/speckit.spex-gates.stamp.md | 48 - .../commands/speckit.spex-gates.verify.md | 476 ---------- .specify/extensions/spex-gates/extension.yml | 49 - .../commands/speckit.spex-teams.implement.md | 27 - .../speckit.spex-teams.orchestrate.md | 148 --- .../commands/speckit.spex-teams.research.md | 151 --- .../extensions/spex-teams/config-template.yml | 4 - .specify/extensions/spex-teams/extension.yml | 45 - .../commands/speckit.spex-worktrees.manage.md | 528 ----------- .../extensions/spex-worktrees/extension.yml | 33 - .../spex/commands/speckit.spex.brainstorm.md | 391 -------- .../spex/commands/speckit.spex.evolve.md | 562 ------------ .../spex/commands/speckit.spex.extensions.md | 58 -- .../spex/commands/speckit.spex.finish.md | 250 ----- .../spex/commands/speckit.spex.flow-state.md | 44 - .../spex/commands/speckit.spex.help.md | 20 - .../spex/commands/speckit.spex.ship.md | 847 ----------------- .../spex/commands/speckit.spex.spec-kit.md | 366 -------- .../commands/speckit.spex.spec-refactoring.md | 446 --------- .../speckit.spex.using-superpowers.md | 329 ------- .specify/extensions/spex/extension.yml | 101 -- .specify/feature.json | 3 - .specify/init-options.json | 11 - .specify/integration.json | 4 - .specify/integrations/claude.manifest.json | 16 - .specify/integrations/speckit.manifest.json | 16 - .specify/memory/constitution.md | 50 - .specify/scripts/bash/check-prerequisites.sh | 190 ---- .specify/scripts/bash/common.sh | 375 -------- .specify/scripts/bash/create-new-feature.sh | 413 --------- .specify/scripts/bash/setup-plan.sh | 73 -- .specify/templates/checklist-template.md | 40 - .specify/templates/constitution-template.md | 50 - .specify/templates/plan-template.md | 104 --- .specify/templates/spec-template.md | 128 --- .specify/templates/tasks-template.md | 251 ----- .specify/workflows/speckit/workflow.yml | 63 -- .specify/workflows/workflow-registry.json | 13 - 72 files changed, 2 insertions(+), 12328 deletions(-) delete mode 100644 .specify/extensions.yml delete mode 100644 .specify/extensions/.registry delete mode 100644 .specify/extensions/git/README.md delete mode 100644 .specify/extensions/git/commands/speckit.git.commit.md delete mode 100644 .specify/extensions/git/commands/speckit.git.feature.md delete mode 100644 .specify/extensions/git/commands/speckit.git.initialize.md delete mode 100644 .specify/extensions/git/commands/speckit.git.remote.md delete mode 100644 .specify/extensions/git/commands/speckit.git.validate.md delete mode 100644 .specify/extensions/git/config-template.yml delete mode 100644 .specify/extensions/git/extension.yml delete mode 100644 .specify/extensions/git/git-config.yml delete mode 100755 .specify/extensions/git/scripts/bash/auto-commit.sh delete mode 100755 .specify/extensions/git/scripts/bash/create-new-feature.sh delete mode 100755 .specify/extensions/git/scripts/bash/git-common.sh delete mode 100755 .specify/extensions/git/scripts/bash/initialize-repo.sh delete mode 100644 .specify/extensions/git/scripts/powershell/auto-commit.ps1 delete mode 100644 .specify/extensions/git/scripts/powershell/create-new-feature.ps1 delete mode 100644 .specify/extensions/git/scripts/powershell/git-common.ps1 delete mode 100644 .specify/extensions/git/scripts/powershell/initialize-repo.ps1 delete mode 100644 .specify/extensions/spex-collab/commands/speckit.spex-collab.phase-manager.md delete mode 100644 .specify/extensions/spex-collab/commands/speckit.spex-collab.phase-split.md delete mode 100644 .specify/extensions/spex-collab/commands/speckit.spex-collab.reconcile.md delete mode 100644 .specify/extensions/spex-collab/commands/speckit.spex-collab.reviewers.md delete mode 100644 .specify/extensions/spex-collab/commands/speckit.spex-collab.revise.md delete mode 100644 .specify/extensions/spex-collab/config-template.yml delete mode 100644 .specify/extensions/spex-collab/extension.yml delete mode 100644 .specify/extensions/spex-collab/templates/reviewers-template.md delete mode 100644 .specify/extensions/spex-deep-review/commands/speckit.spex-deep-review.run.md delete mode 100644 .specify/extensions/spex-deep-review/config-template.yml delete mode 100644 .specify/extensions/spex-deep-review/extension.yml delete mode 100644 .specify/extensions/spex-gates/commands/speckit.spex-gates.review-code.md delete mode 100644 .specify/extensions/spex-gates/commands/speckit.spex-gates.review-plan.md delete mode 100644 .specify/extensions/spex-gates/commands/speckit.spex-gates.review-spec.md delete mode 100644 .specify/extensions/spex-gates/commands/speckit.spex-gates.stamp.md delete mode 100644 .specify/extensions/spex-gates/commands/speckit.spex-gates.verify.md delete mode 100644 .specify/extensions/spex-gates/extension.yml delete mode 100644 .specify/extensions/spex-teams/commands/speckit.spex-teams.implement.md delete mode 100644 .specify/extensions/spex-teams/commands/speckit.spex-teams.orchestrate.md delete mode 100644 .specify/extensions/spex-teams/commands/speckit.spex-teams.research.md delete mode 100644 .specify/extensions/spex-teams/config-template.yml delete mode 100644 .specify/extensions/spex-teams/extension.yml delete mode 100644 .specify/extensions/spex-worktrees/commands/speckit.spex-worktrees.manage.md delete mode 100644 .specify/extensions/spex-worktrees/extension.yml delete mode 100644 .specify/extensions/spex/commands/speckit.spex.brainstorm.md delete mode 100644 .specify/extensions/spex/commands/speckit.spex.evolve.md delete mode 100644 .specify/extensions/spex/commands/speckit.spex.extensions.md delete mode 100644 .specify/extensions/spex/commands/speckit.spex.finish.md delete mode 100644 .specify/extensions/spex/commands/speckit.spex.flow-state.md delete mode 100644 .specify/extensions/spex/commands/speckit.spex.help.md delete mode 100644 .specify/extensions/spex/commands/speckit.spex.ship.md delete mode 100644 .specify/extensions/spex/commands/speckit.spex.spec-kit.md delete mode 100644 .specify/extensions/spex/commands/speckit.spex.spec-refactoring.md delete mode 100644 .specify/extensions/spex/commands/speckit.spex.using-superpowers.md delete mode 100644 .specify/extensions/spex/extension.yml delete mode 100644 .specify/feature.json delete mode 100644 .specify/init-options.json delete mode 100644 .specify/integration.json delete mode 100644 .specify/integrations/claude.manifest.json delete mode 100644 .specify/integrations/speckit.manifest.json delete mode 100644 .specify/memory/constitution.md delete mode 100755 .specify/scripts/bash/check-prerequisites.sh delete mode 100755 .specify/scripts/bash/common.sh delete mode 100755 .specify/scripts/bash/create-new-feature.sh delete mode 100755 .specify/scripts/bash/setup-plan.sh delete mode 100644 .specify/templates/checklist-template.md delete mode 100644 .specify/templates/constitution-template.md delete mode 100644 .specify/templates/plan-template.md delete mode 100644 .specify/templates/spec-template.md delete mode 100644 .specify/templates/tasks-template.md delete mode 100644 .specify/workflows/speckit/workflow.yml delete mode 100644 .specify/workflows/workflow-registry.json diff --git a/.gitignore b/.gitignore index 0024553f..866adfc8 100644 --- a/.gitignore +++ b/.gitignore @@ -58,6 +58,6 @@ Dockerfile.cross # spex: generated/local files .claude/skills/ +.claude/settings.json .claude/settings.local.json -.specify/.spex-phase -.specify/.spex-state +.specify/ diff --git a/.specify/extensions.yml b/.specify/extensions.yml deleted file mode 100644 index f87c1b4c..00000000 --- a/.specify/extensions.yml +++ /dev/null @@ -1,275 +0,0 @@ -installed: [] -settings: - auto_execute_hooks: true -hooks: - before_constitution: - - extension: git - command: speckit.git.initialize - enabled: true - optional: false - prompt: Execute speckit.git.initialize? - description: Initialize Git repository before constitution setup - condition: null - before_specify: - - extension: git - command: speckit.git.feature - enabled: true - optional: false - prompt: Execute speckit.git.feature? - description: Create feature branch before specification - condition: null - before_clarify: - - extension: git - command: speckit.git.commit - enabled: true - optional: true - prompt: Commit outstanding changes before clarification? - description: Auto-commit before spec clarification - condition: null - - extension: spex - command: speckit.spex.flow-state - enabled: true - optional: false - prompt: Execute speckit.spex.flow-state? - description: Mark clarify as active in flow state - condition: null - before_plan: - - extension: git - command: speckit.git.commit - enabled: true - optional: true - prompt: Commit outstanding changes before planning? - description: Auto-commit before implementation planning - condition: null - - extension: spex - command: speckit.spex.flow-state - enabled: true - optional: false - prompt: Execute speckit.spex.flow-state? - description: Mark plan as active in flow state - condition: null - - extension: spex-teams - command: speckit.spex-teams.research - enabled: true - optional: true - prompt: Run parallel codebase research? - description: Parallel codebase research during planning via Agent Teams - condition: null - before_tasks: - - extension: git - command: speckit.git.commit - enabled: true - optional: true - prompt: Commit outstanding changes before task generation? - description: Auto-commit before task generation - condition: null - - extension: spex - command: speckit.spex.flow-state - enabled: true - optional: false - prompt: Execute speckit.spex.flow-state? - description: Mark tasks as active in flow state - condition: null - before_implement: - - extension: git - command: speckit.git.commit - enabled: true - optional: true - prompt: Commit outstanding changes before implementation? - description: Auto-commit before implementation - condition: null - - extension: spex - command: speckit.spex.flow-state - enabled: true - optional: false - prompt: Execute speckit.spex.flow-state? - description: Mark implement as active in flow state - condition: null - - extension: spex-collab - command: speckit.spex-collab.phase-split - enabled: true - optional: true - prompt: Review PR split for implementation phases? - description: Present phase split proposal before implementation - condition: null - before_checklist: - - extension: git - command: speckit.git.commit - enabled: true - optional: true - prompt: Commit outstanding changes before checklist? - description: Auto-commit before checklist generation - condition: null - before_analyze: - - extension: git - command: speckit.git.commit - enabled: true - optional: true - prompt: Commit outstanding changes before analysis? - description: Auto-commit before analysis - condition: null - before_taskstoissues: - - extension: git - command: speckit.git.commit - enabled: true - optional: true - prompt: Commit outstanding changes before issue sync? - description: Auto-commit before tasks-to-issues conversion - condition: null - after_constitution: - - extension: git - command: speckit.git.commit - enabled: true - optional: true - prompt: Commit constitution changes? - description: Auto-commit after constitution update - condition: null - after_specify: - - extension: git - command: speckit.git.commit - enabled: true - optional: true - prompt: Commit specification changes? - description: Auto-commit after specification - condition: null - - extension: spex - command: speckit.spex.flow-state - enabled: true - optional: false - prompt: Execute speckit.spex.flow-state? - description: Initialize flow state tracking after specification - condition: null - - extension: spex-gates - command: speckit.spex-gates.review-spec - enabled: true - optional: false - prompt: Execute speckit.spex-gates.review-spec? - description: Review spec soundness after specification - condition: null - - extension: spex-worktrees - command: speckit.spex-worktrees.manage - enabled: false - optional: false - prompt: Execute speckit.spex-worktrees.manage? - description: Create git worktree after specification - condition: null - after_clarify: - - extension: git - command: speckit.git.commit - enabled: true - optional: true - prompt: Commit clarification changes? - description: Auto-commit after spec clarification - condition: null - - extension: spex - command: speckit.spex.flow-state - enabled: true - optional: false - prompt: Execute speckit.spex.flow-state? - description: Mark clarification complete in flow state - condition: null - after_plan: - - extension: git - command: speckit.git.commit - enabled: true - optional: true - prompt: Commit plan changes? - description: Auto-commit after implementation planning - condition: null - - extension: spex - command: speckit.spex.flow-state - enabled: true - optional: false - prompt: Execute speckit.spex.flow-state? - description: Clear running state after planning - condition: null - after_tasks: - - extension: git - command: speckit.git.commit - enabled: true - optional: true - prompt: Commit task changes? - description: Auto-commit after task generation - condition: null - - extension: spex - command: speckit.spex.flow-state - enabled: true - optional: false - prompt: Execute speckit.spex.flow-state? - description: Clear running state after task generation - condition: null - - extension: spex-gates - command: speckit.spex-gates.review-plan - enabled: true - optional: false - prompt: Execute speckit.spex-gates.review-plan? - description: Review plan and task quality after task generation - condition: null - - extension: spex-collab - command: speckit.spex-collab.reviewers - enabled: true - optional: false - prompt: Execute speckit.spex-collab.reviewers? - description: Generate REVIEWERS.md after task generation - condition: null - after_implement: - - extension: git - command: speckit.git.commit - enabled: true - optional: true - prompt: Commit implementation changes? - description: Auto-commit after implementation - condition: null - - extension: spex - command: speckit.spex.flow-state - enabled: true - optional: false - prompt: Execute speckit.spex.flow-state? - description: Mark implementation complete in flow state - condition: null - - extension: spex-gates - command: speckit.spex-gates.review-code - enabled: true - optional: false - prompt: Execute speckit.spex-gates.review-code? - description: Review code compliance after implementation - condition: null - - extension: spex-deep-review - command: speckit.spex-deep-review.run - enabled: true - optional: true - prompt: Run deep multi-perspective review? - description: Multi-agent code review after implementation - condition: null - after_checklist: - - extension: git - command: speckit.git.commit - enabled: true - optional: true - prompt: Commit checklist changes? - description: Auto-commit after checklist generation - condition: null - after_analyze: - - extension: git - command: speckit.git.commit - enabled: true - optional: true - prompt: Commit analysis results? - description: Auto-commit after analysis - condition: null - after_taskstoissues: - - extension: git - command: speckit.git.commit - enabled: true - optional: true - prompt: Commit after syncing issues? - description: Auto-commit after tasks-to-issues conversion - condition: null - after_finish: - - extension: spex - command: speckit.spex.flow-state - enabled: true - optional: false - prompt: Execute speckit.spex.flow-state? - description: Remove flow state file after feature completion - condition: null diff --git a/.specify/extensions/.registry b/.specify/extensions/.registry deleted file mode 100644 index 5a103a64..00000000 --- a/.specify/extensions/.registry +++ /dev/null @@ -1,126 +0,0 @@ -{ - "schema_version": "1.0", - "extensions": { - "git": { - "version": "1.0.0", - "source": "local", - "manifest_hash": "sha256:9731aa8143a72fbebfdb440f155038ab42642517c2b2bdbbf67c8fdbe076ed79", - "enabled": true, - "priority": 10, - "registered_commands": { - "claude": [ - "speckit.git.feature", - "speckit.git.validate", - "speckit.git.remote", - "speckit.git.initialize", - "speckit.git.commit" - ] - }, - "registered_skills": [], - "installed_at": "2026-05-21T07:10:44.469134+00:00" - }, - "spex": { - "version": "1.0.0", - "source": "local", - "manifest_hash": "sha256:ac6b6faf3460b5a3cbbecf9082275e504671af0d5d59496d2ef2f8dc087f806e", - "enabled": true, - "priority": 10, - "registered_commands": { - "claude": [ - "speckit.spex.brainstorm", - "speckit.spex.ship", - "speckit.spex.help", - "speckit.spex.evolve", - "speckit.spex.spec-refactoring", - "speckit.spex.using-superpowers", - "speckit.spex.spec-kit", - "speckit.spex.extensions", - "speckit.spex.finish", - "speckit.spex.flow-state" - ] - }, - "registered_skills": [], - "installed_at": "2026-05-21T07:10:44.591398+00:00" - }, - "spex-gates": { - "version": "1.0.0", - "source": "local", - "manifest_hash": "sha256:7fe166c5c1c813aa3973afd71929d79b2eb26b19b12d954fcf801c5d993764af", - "enabled": true, - "priority": 10, - "registered_commands": { - "claude": [ - "speckit.spex-gates.review-spec", - "speckit.spex-gates.review-plan", - "speckit.spex-gates.review-code", - "speckit.spex-gates.verify", - "speckit.spex-gates.stamp" - ] - }, - "registered_skills": [], - "installed_at": "2026-05-21T07:10:44.705226+00:00" - }, - "spex-worktrees": { - "version": "1.0.0", - "source": "local", - "manifest_hash": "sha256:8e427d7d4c4c865401bcbb4f36dd16614fa1fc201ce7d314a27a02185e28461a", - "enabled": false, - "priority": 10, - "registered_commands": { - "claude": [ - "speckit.spex-worktrees.manage" - ] - }, - "registered_skills": [], - "installed_at": "2026-05-21T07:10:44.814601+00:00" - }, - "spex-deep-review": { - "version": "1.0.0", - "source": "local", - "manifest_hash": "sha256:854146d677b35a744606d443316a6c7f1383d978b38635539410d8b8db34b1e8", - "enabled": true, - "priority": 10, - "registered_commands": { - "claude": [ - "speckit.spex-deep-review.run" - ] - }, - "registered_skills": [], - "installed_at": "2026-05-21T07:10:44.938774+00:00" - }, - "spex-teams": { - "version": "1.0.0", - "source": "local", - "manifest_hash": "sha256:99c4375c8471c6667e0c611210e60550d380e55b69166e79417b412c91367c3b", - "enabled": true, - "priority": 10, - "registered_commands": { - "claude": [ - "speckit.spex-teams.orchestrate", - "speckit.spex-teams.research", - "speckit.spex-teams.implement" - ] - }, - "registered_skills": [], - "installed_at": "2026-05-21T07:10:45.049496+00:00" - }, - "spex-collab": { - "version": "1.0.0", - "source": "local", - "manifest_hash": "sha256:fab8be0594a1f61314fa9d498de3a1b008138a1033db5ed1f813508f615632d5", - "enabled": true, - "priority": 10, - "registered_commands": { - "claude": [ - "speckit.spex-collab.reviewers", - "speckit.spex-collab.phase-split", - "speckit.spex-collab.phase-manager", - "speckit.spex-collab.revise", - "speckit.spex-collab.reconcile" - ] - }, - "registered_skills": [], - "installed_at": "2026-05-21T07:10:45.172133+00:00" - } - } -} \ No newline at end of file diff --git a/.specify/extensions/git/README.md b/.specify/extensions/git/README.md deleted file mode 100644 index 31ba75c3..00000000 --- a/.specify/extensions/git/README.md +++ /dev/null @@ -1,100 +0,0 @@ -# Git Branching Workflow Extension - -Git repository initialization, feature branch creation, numbering (sequential/timestamp), validation, remote detection, and auto-commit for Spec Kit. - -## Overview - -This extension provides Git operations as an optional, self-contained module. It manages: - -- **Repository initialization** with configurable commit messages -- **Feature branch creation** with sequential (`001-feature-name`) or timestamp (`20260319-143022-feature-name`) numbering -- **Branch validation** to ensure branches follow naming conventions -- **Git remote detection** for GitHub integration (e.g., issue creation) -- **Auto-commit** after core commands (configurable per-command with custom messages) - -## Commands - -| Command | Description | -|---------|-------------| -| `speckit.git.initialize` | Initialize a Git repository with a configurable commit message | -| `speckit.git.feature` | Create a feature branch with sequential or timestamp numbering | -| `speckit.git.validate` | Validate current branch follows feature branch naming conventions | -| `speckit.git.remote` | Detect Git remote URL for GitHub integration | -| `speckit.git.commit` | Auto-commit changes (configurable per-command enable/disable and messages) | - -## Hooks - -| Event | Command | Optional | Description | -|-------|---------|----------|-------------| -| `before_constitution` | `speckit.git.initialize` | No | Init git repo before constitution | -| `before_specify` | `speckit.git.feature` | No | Create feature branch before specification | -| `before_clarify` | `speckit.git.commit` | Yes | Commit outstanding changes before clarification | -| `before_plan` | `speckit.git.commit` | Yes | Commit outstanding changes before planning | -| `before_tasks` | `speckit.git.commit` | Yes | Commit outstanding changes before task generation | -| `before_implement` | `speckit.git.commit` | Yes | Commit outstanding changes before implementation | -| `before_checklist` | `speckit.git.commit` | Yes | Commit outstanding changes before checklist | -| `before_analyze` | `speckit.git.commit` | Yes | Commit outstanding changes before analysis | -| `before_taskstoissues` | `speckit.git.commit` | Yes | Commit outstanding changes before issue sync | -| `after_constitution` | `speckit.git.commit` | Yes | Auto-commit after constitution update | -| `after_specify` | `speckit.git.commit` | Yes | Auto-commit after specification | -| `after_clarify` | `speckit.git.commit` | Yes | Auto-commit after clarification | -| `after_plan` | `speckit.git.commit` | Yes | Auto-commit after planning | -| `after_tasks` | `speckit.git.commit` | Yes | Auto-commit after task generation | -| `after_implement` | `speckit.git.commit` | Yes | Auto-commit after implementation | -| `after_checklist` | `speckit.git.commit` | Yes | Auto-commit after checklist | -| `after_analyze` | `speckit.git.commit` | Yes | Auto-commit after analysis | -| `after_taskstoissues` | `speckit.git.commit` | Yes | Auto-commit after issue sync | - -## Configuration - -Configuration is stored in `.specify/extensions/git/git-config.yml`: - -```yaml -# Branch numbering strategy: "sequential" or "timestamp" -branch_numbering: sequential - -# Custom commit message for git init -init_commit_message: "[Spec Kit] Initial commit" - -# Auto-commit per command (all disabled by default) -# Example: enable auto-commit after specify -auto_commit: - default: false - after_specify: - enabled: true - message: "[Spec Kit] Add specification" -``` - -## Installation - -```bash -# Install the bundled git extension (no network required) -specify extension add git -``` - -## Disabling - -```bash -# Disable the git extension (spec creation continues without branching) -specify extension disable git - -# Re-enable it -specify extension enable git -``` - -## Graceful Degradation - -When Git is not installed or the directory is not a Git repository: -- Spec directories are still created under `specs/` -- Branch creation is skipped with a warning -- Branch validation is skipped with a warning -- Remote detection returns empty results - -## Scripts - -The extension bundles cross-platform scripts: - -- `scripts/bash/create-new-feature.sh` — Bash implementation -- `scripts/bash/git-common.sh` — Shared Git utilities (Bash) -- `scripts/powershell/create-new-feature.ps1` — PowerShell implementation -- `scripts/powershell/git-common.ps1` — Shared Git utilities (PowerShell) diff --git a/.specify/extensions/git/commands/speckit.git.commit.md b/.specify/extensions/git/commands/speckit.git.commit.md deleted file mode 100644 index e606f911..00000000 --- a/.specify/extensions/git/commands/speckit.git.commit.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -description: "Auto-commit changes after a Spec Kit command completes" ---- - -# Auto-Commit Changes - -Automatically stage and commit all changes after a Spec Kit command completes. - -## Behavior - -This command is invoked as a hook after (or before) core commands. It: - -1. Determines the event name from the hook context (e.g., if invoked as an `after_specify` hook, the event is `after_specify`; if `before_plan`, the event is `before_plan`) -2. Checks `.specify/extensions/git/git-config.yml` for the `auto_commit` section -3. Looks up the specific event key to see if auto-commit is enabled -4. Falls back to `auto_commit.default` if no event-specific key exists -5. Uses the per-command `message` if configured, otherwise a default message -6. If enabled and there are uncommitted changes, runs `git add .` + `git commit` - -## Execution - -Determine the event name from the hook that triggered this command, then run the script: - -- **Bash**: `.specify/extensions/git/scripts/bash/auto-commit.sh ` -- **PowerShell**: `.specify/extensions/git/scripts/powershell/auto-commit.ps1 ` - -Replace `` with the actual hook event (e.g., `after_specify`, `before_plan`, `after_implement`). - -## Configuration - -In `.specify/extensions/git/git-config.yml`: - -```yaml -auto_commit: - default: false # Global toggle — set true to enable for all commands - after_specify: - enabled: true # Override per-command - message: "[Spec Kit] Add specification" - after_plan: - enabled: false - message: "[Spec Kit] Add implementation plan" -``` - -## Graceful Degradation - -- If Git is not available or the current directory is not a repository: skips with a warning -- If no config file exists: skips (disabled by default) -- If no changes to commit: skips with a message diff --git a/.specify/extensions/git/commands/speckit.git.feature.md b/.specify/extensions/git/commands/speckit.git.feature.md deleted file mode 100644 index 1a9c5e35..00000000 --- a/.specify/extensions/git/commands/speckit.git.feature.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -description: "Create a feature branch with sequential or timestamp numbering" ---- - -# Create Feature Branch - -Create and switch to a new git feature branch for the given specification. This command handles **branch creation only** — the spec directory and files are created by the core `/speckit.specify` workflow. - -## User Input - -```text -$ARGUMENTS -``` - -You **MUST** consider the user input before proceeding (if not empty). - -## Environment Variable Override - -If the user explicitly provided `GIT_BRANCH_NAME` (e.g., via environment variable, argument, or in their request), pass it through to the script by setting the `GIT_BRANCH_NAME` environment variable before invoking the script. When `GIT_BRANCH_NAME` is set: -- The script uses the exact value as the branch name, bypassing all prefix/suffix generation -- `--short-name`, `--number`, and `--timestamp` flags are ignored -- `FEATURE_NUM` is extracted from the name if it starts with a numeric prefix, otherwise set to the full branch name - -## Prerequisites - -- Verify Git is available by running `git rev-parse --is-inside-work-tree 2>/dev/null` -- If Git is not available, warn the user and skip branch creation - -## Branch Numbering Mode - -Determine the branch numbering strategy by checking configuration in this order: - -1. Check `.specify/extensions/git/git-config.yml` for `branch_numbering` value -2. Check `.specify/init-options.json` for `branch_numbering` value (backward compatibility) -3. Default to `sequential` if neither exists - -## Execution - -Generate a concise short name (2-4 words) for the branch: -- Analyze the feature description and extract the most meaningful keywords -- Use action-noun format when possible (e.g., "add-user-auth", "fix-payment-bug") -- Preserve technical terms and acronyms (OAuth2, API, JWT, etc.) - -Run the appropriate script based on your platform: - -- **Bash**: `.specify/extensions/git/scripts/bash/create-new-feature.sh --json --short-name "" ""` -- **Bash (timestamp)**: `.specify/extensions/git/scripts/bash/create-new-feature.sh --json --timestamp --short-name "" ""` -- **PowerShell**: `.specify/extensions/git/scripts/powershell/create-new-feature.ps1 -Json -ShortName "" ""` -- **PowerShell (timestamp)**: `.specify/extensions/git/scripts/powershell/create-new-feature.ps1 -Json -Timestamp -ShortName "" ""` - -**IMPORTANT**: -- Do NOT pass `--number` — the script determines the correct next number automatically -- Always include the JSON flag (`--json` for Bash, `-Json` for PowerShell) so the output can be parsed reliably -- You must only ever run this script once per feature -- The JSON output will contain `BRANCH_NAME` and `FEATURE_NUM` - -## Graceful Degradation - -If Git is not installed or the current directory is not a Git repository: -- Branch creation is skipped with a warning: `[specify] Warning: Git repository not detected; skipped branch creation` -- The script still outputs `BRANCH_NAME` and `FEATURE_NUM` so the caller can reference them - -## Output - -The script outputs JSON with: -- `BRANCH_NAME`: The branch name (e.g., `003-user-auth` or `20260319-143022-user-auth`) -- `FEATURE_NUM`: The numeric or timestamp prefix used diff --git a/.specify/extensions/git/commands/speckit.git.initialize.md b/.specify/extensions/git/commands/speckit.git.initialize.md deleted file mode 100644 index 4451ee6b..00000000 --- a/.specify/extensions/git/commands/speckit.git.initialize.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -description: "Initialize a Git repository with an initial commit" ---- - -# Initialize Git Repository - -Initialize a Git repository in the current project directory if one does not already exist. - -## Execution - -Run the appropriate script from the project root: - -- **Bash**: `.specify/extensions/git/scripts/bash/initialize-repo.sh` -- **PowerShell**: `.specify/extensions/git/scripts/powershell/initialize-repo.ps1` - -If the extension scripts are not found, fall back to: -- **Bash**: `git init && git add . && git commit -m "Initial commit from Specify template"` -- **PowerShell**: `git init; git add .; git commit -m "Initial commit from Specify template"` - -The script handles all checks internally: -- Skips if Git is not available -- Skips if already inside a Git repository -- Runs `git init`, `git add .`, and `git commit` with an initial commit message - -## Customization - -Replace the script to add project-specific Git initialization steps: -- Custom `.gitignore` templates -- Default branch naming (`git config init.defaultBranch`) -- Git LFS setup -- Git hooks installation -- Commit signing configuration -- Git Flow initialization - -## Output - -On success: -- `✓ Git repository initialized` - -## Graceful Degradation - -If Git is not installed: -- Warn the user -- Skip repository initialization -- The project continues to function without Git (specs can still be created under `specs/`) - -If Git is installed but `git init`, `git add .`, or `git commit` fails: -- Surface the error to the user -- Stop this command rather than continuing with a partially initialized repository diff --git a/.specify/extensions/git/commands/speckit.git.remote.md b/.specify/extensions/git/commands/speckit.git.remote.md deleted file mode 100644 index 712a3e8b..00000000 --- a/.specify/extensions/git/commands/speckit.git.remote.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -description: "Detect Git remote URL for GitHub integration" ---- - -# Detect Git Remote URL - -Detect the Git remote URL for integration with GitHub services (e.g., issue creation). - -## Prerequisites - -- Check if Git is available by running `git rev-parse --is-inside-work-tree 2>/dev/null` -- If Git is not available, output a warning and return empty: - ``` - [specify] Warning: Git repository not detected; cannot determine remote URL - ``` - -## Execution - -Run the following command to get the remote URL: - -```bash -git config --get remote.origin.url -``` - -## Output - -Parse the remote URL and determine: - -1. **Repository owner**: Extract from the URL (e.g., `github` from `https://github.com/github/spec-kit.git`) -2. **Repository name**: Extract from the URL (e.g., `spec-kit` from `https://github.com/github/spec-kit.git`) -3. **Is GitHub**: Whether the remote points to a GitHub repository - -Supported URL formats: -- HTTPS: `https://github.com//.git` -- SSH: `git@github.com:/.git` - -> [!CAUTION] -> ONLY report a GitHub repository if the remote URL actually points to github.com. -> Do NOT assume the remote is GitHub if the URL format doesn't match. - -## Graceful Degradation - -If Git is not installed, the directory is not a Git repository, or no remote is configured: -- Return an empty result -- Do NOT error — other workflows should continue without Git remote information diff --git a/.specify/extensions/git/commands/speckit.git.validate.md b/.specify/extensions/git/commands/speckit.git.validate.md deleted file mode 100644 index dd84618c..00000000 --- a/.specify/extensions/git/commands/speckit.git.validate.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -description: "Validate current branch follows feature branch naming conventions" ---- - -# Validate Feature Branch - -Validate that the current Git branch follows the expected feature branch naming conventions. - -## Prerequisites - -- Check if Git is available by running `git rev-parse --is-inside-work-tree 2>/dev/null` -- If Git is not available, output a warning and skip validation: - ``` - [specify] Warning: Git repository not detected; skipped branch validation - ``` - -## Validation Rules - -Get the current branch name: - -```bash -git rev-parse --abbrev-ref HEAD -``` - -The branch name must match one of these patterns: - -1. **Sequential**: `^[0-9]{3,}-` (e.g., `001-feature-name`, `042-fix-bug`, `1000-big-feature`) -2. **Timestamp**: `^[0-9]{8}-[0-9]{6}-` (e.g., `20260319-143022-feature-name`) - -## Execution - -If on a feature branch (matches either pattern): -- Output: `✓ On feature branch: ` -- Check if the corresponding spec directory exists under `specs/`: - - For sequential branches, look for `specs/-*` where prefix matches the numeric portion - - For timestamp branches, look for `specs/-*` where prefix matches the `YYYYMMDD-HHMMSS` portion -- If spec directory exists: `✓ Spec directory found: ` -- If spec directory missing: `⚠ No spec directory found for prefix ` - -If NOT on a feature branch: -- Output: `✗ Not on a feature branch. Current branch: ` -- Output: `Feature branches should be named like: 001-feature-name or 20260319-143022-feature-name` - -## Graceful Degradation - -If Git is not installed or the directory is not a Git repository: -- Check the `SPECIFY_FEATURE` environment variable as a fallback -- If set, validate that value against the naming patterns -- If not set, skip validation with a warning diff --git a/.specify/extensions/git/config-template.yml b/.specify/extensions/git/config-template.yml deleted file mode 100644 index 8c414bab..00000000 --- a/.specify/extensions/git/config-template.yml +++ /dev/null @@ -1,62 +0,0 @@ -# Git Branching Workflow Extension Configuration -# Copied to .specify/extensions/git/git-config.yml on install - -# Branch numbering strategy: "sequential" (001, 002, ...) or "timestamp" (YYYYMMDD-HHMMSS) -branch_numbering: sequential - -# Commit message used by `git commit` during repository initialization -init_commit_message: "[Spec Kit] Initial commit" - -# Auto-commit before/after core commands. -# Set "default" to enable for all commands, then override per-command. -# Each key can be true/false. Message is customizable per-command. -auto_commit: - default: false - before_clarify: - enabled: false - message: "[Spec Kit] Save progress before clarification" - before_plan: - enabled: false - message: "[Spec Kit] Save progress before planning" - before_tasks: - enabled: false - message: "[Spec Kit] Save progress before task generation" - before_implement: - enabled: false - message: "[Spec Kit] Save progress before implementation" - before_checklist: - enabled: false - message: "[Spec Kit] Save progress before checklist" - before_analyze: - enabled: false - message: "[Spec Kit] Save progress before analysis" - before_taskstoissues: - enabled: false - message: "[Spec Kit] Save progress before issue sync" - after_constitution: - enabled: false - message: "[Spec Kit] Add project constitution" - after_specify: - enabled: false - message: "[Spec Kit] Add specification" - after_clarify: - enabled: false - message: "[Spec Kit] Clarify specification" - after_plan: - enabled: false - message: "[Spec Kit] Add implementation plan" - after_tasks: - enabled: false - message: "[Spec Kit] Add tasks" - after_implement: - enabled: false - message: "[Spec Kit] Implementation progress" - after_checklist: - enabled: false - message: "[Spec Kit] Add checklist" - after_analyze: - enabled: false - message: "[Spec Kit] Add analysis report" - after_taskstoissues: - enabled: false - message: "[Spec Kit] Sync tasks to issues" diff --git a/.specify/extensions/git/extension.yml b/.specify/extensions/git/extension.yml deleted file mode 100644 index 13c1977e..00000000 --- a/.specify/extensions/git/extension.yml +++ /dev/null @@ -1,140 +0,0 @@ -schema_version: "1.0" - -extension: - id: git - name: "Git Branching Workflow" - version: "1.0.0" - description: "Feature branch creation, numbering (sequential/timestamp), validation, and Git remote detection" - author: spec-kit-core - repository: https://github.com/github/spec-kit - license: MIT - -requires: - speckit_version: ">=0.2.0" - tools: - - name: git - required: false - -provides: - commands: - - name: speckit.git.feature - file: commands/speckit.git.feature.md - description: "Create a feature branch with sequential or timestamp numbering" - - name: speckit.git.validate - file: commands/speckit.git.validate.md - description: "Validate current branch follows feature branch naming conventions" - - name: speckit.git.remote - file: commands/speckit.git.remote.md - description: "Detect Git remote URL for GitHub integration" - - name: speckit.git.initialize - file: commands/speckit.git.initialize.md - description: "Initialize a Git repository with an initial commit" - - name: speckit.git.commit - file: commands/speckit.git.commit.md - description: "Auto-commit changes after a Spec Kit command completes" - - config: - - name: "git-config.yml" - template: "config-template.yml" - description: "Git branching configuration" - required: false - -hooks: - before_constitution: - command: speckit.git.initialize - optional: false - description: "Initialize Git repository before constitution setup" - before_specify: - command: speckit.git.feature - optional: false - description: "Create feature branch before specification" - before_clarify: - command: speckit.git.commit - optional: true - prompt: "Commit outstanding changes before clarification?" - description: "Auto-commit before spec clarification" - before_plan: - command: speckit.git.commit - optional: true - prompt: "Commit outstanding changes before planning?" - description: "Auto-commit before implementation planning" - before_tasks: - command: speckit.git.commit - optional: true - prompt: "Commit outstanding changes before task generation?" - description: "Auto-commit before task generation" - before_implement: - command: speckit.git.commit - optional: true - prompt: "Commit outstanding changes before implementation?" - description: "Auto-commit before implementation" - before_checklist: - command: speckit.git.commit - optional: true - prompt: "Commit outstanding changes before checklist?" - description: "Auto-commit before checklist generation" - before_analyze: - command: speckit.git.commit - optional: true - prompt: "Commit outstanding changes before analysis?" - description: "Auto-commit before analysis" - before_taskstoissues: - command: speckit.git.commit - optional: true - prompt: "Commit outstanding changes before issue sync?" - description: "Auto-commit before tasks-to-issues conversion" - after_constitution: - command: speckit.git.commit - optional: true - prompt: "Commit constitution changes?" - description: "Auto-commit after constitution update" - after_specify: - command: speckit.git.commit - optional: true - prompt: "Commit specification changes?" - description: "Auto-commit after specification" - after_clarify: - command: speckit.git.commit - optional: true - prompt: "Commit clarification changes?" - description: "Auto-commit after spec clarification" - after_plan: - command: speckit.git.commit - optional: true - prompt: "Commit plan changes?" - description: "Auto-commit after implementation planning" - after_tasks: - command: speckit.git.commit - optional: true - prompt: "Commit task changes?" - description: "Auto-commit after task generation" - after_implement: - command: speckit.git.commit - optional: true - prompt: "Commit implementation changes?" - description: "Auto-commit after implementation" - after_checklist: - command: speckit.git.commit - optional: true - prompt: "Commit checklist changes?" - description: "Auto-commit after checklist generation" - after_analyze: - command: speckit.git.commit - optional: true - prompt: "Commit analysis results?" - description: "Auto-commit after analysis" - after_taskstoissues: - command: speckit.git.commit - optional: true - prompt: "Commit after syncing issues?" - description: "Auto-commit after tasks-to-issues conversion" - -tags: - - "git" - - "branching" - - "workflow" - -config: - defaults: - branch_numbering: sequential - init_commit_message: "[Spec Kit] Initial commit" diff --git a/.specify/extensions/git/git-config.yml b/.specify/extensions/git/git-config.yml deleted file mode 100644 index 418ac729..00000000 --- a/.specify/extensions/git/git-config.yml +++ /dev/null @@ -1,60 +0,0 @@ -# Git Branching Workflow Extension Configuration -# Copied to .specify/extensions/git/git-config.yml on install - -# Branch numbering strategy: "sequential" (001, 002, ...) or "timestamp" (YYYYMMDD-HHMMSS) -branch_numbering: sequential -# Commit message used by `git commit` during repository initialization -init_commit_message: "[Spec Kit] Initial commit" -# Auto-commit before/after core commands. -# Set "default" to enable for all commands, then override per-command. -# Each key can be true/false. Message is customizable per-command. -auto_commit: - default: false - before_clarify: - enabled: false - message: "[Spec Kit] Save progress before clarification" - before_plan: - enabled: false - message: "[Spec Kit] Save progress before planning" - before_tasks: - enabled: false - message: "[Spec Kit] Save progress before task generation" - before_implement: - enabled: false - message: "[Spec Kit] Save progress before implementation" - before_checklist: - enabled: false - message: "[Spec Kit] Save progress before checklist" - before_analyze: - enabled: false - message: "[Spec Kit] Save progress before analysis" - before_taskstoissues: - enabled: false - message: "[Spec Kit] Save progress before issue sync" - after_constitution: - enabled: false - message: "[Spec Kit] Add project constitution" - after_specify: - enabled: true - message: "[Spec Kit] Add specification" - after_clarify: - enabled: false - message: "[Spec Kit] Clarify specification" - after_plan: - enabled: true - message: "[Spec Kit] Add implementation plan" - after_tasks: - enabled: true - message: "[Spec Kit] Add tasks" - after_implement: - enabled: true - message: "[Spec Kit] Implementation progress" - after_checklist: - enabled: false - message: "[Spec Kit] Add checklist" - after_analyze: - enabled: false - message: "[Spec Kit] Add analysis report" - after_taskstoissues: - enabled: false - message: "[Spec Kit] Sync tasks to issues" diff --git a/.specify/extensions/git/scripts/bash/auto-commit.sh b/.specify/extensions/git/scripts/bash/auto-commit.sh deleted file mode 100755 index f0b42318..00000000 --- a/.specify/extensions/git/scripts/bash/auto-commit.sh +++ /dev/null @@ -1,140 +0,0 @@ -#!/usr/bin/env bash -# Git extension: auto-commit.sh -# Automatically commit changes after a Spec Kit command completes. -# Checks per-command config keys in git-config.yml before committing. -# -# Usage: auto-commit.sh -# e.g.: auto-commit.sh after_specify - -set -e - -EVENT_NAME="${1:-}" -if [ -z "$EVENT_NAME" ]; then - echo "Usage: $0 " >&2 - exit 1 -fi - -SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -_find_project_root() { - local dir="$1" - while [ "$dir" != "/" ]; do - if [ -d "$dir/.specify" ] || [ -d "$dir/.git" ]; then - echo "$dir" - return 0 - fi - dir="$(dirname "$dir")" - done - return 1 -} - -REPO_ROOT=$(_find_project_root "$SCRIPT_DIR") || REPO_ROOT="$(pwd)" -cd "$REPO_ROOT" - -# Check if git is available -if ! command -v git >/dev/null 2>&1; then - echo "[specify] Warning: Git not found; skipped auto-commit" >&2 - exit 0 -fi - -if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then - echo "[specify] Warning: Not a Git repository; skipped auto-commit" >&2 - exit 0 -fi - -# Read per-command config from git-config.yml -_config_file="$REPO_ROOT/.specify/extensions/git/git-config.yml" -_enabled=false -_commit_msg="" - -if [ -f "$_config_file" ]; then - # Parse the auto_commit section for this event. - # Look for auto_commit..enabled and .message - # Also check auto_commit.default as fallback. - _in_auto_commit=false - _in_event=false - _default_enabled=false - - while IFS= read -r _line; do - # Detect auto_commit: section - if echo "$_line" | grep -q '^auto_commit:'; then - _in_auto_commit=true - _in_event=false - continue - fi - - # Exit auto_commit section on next top-level key - if $_in_auto_commit && echo "$_line" | grep -Eq '^[a-z]'; then - break - fi - - if $_in_auto_commit; then - # Check default key - if echo "$_line" | grep -Eq "^[[:space:]]+default:[[:space:]]"; then - _val=$(echo "$_line" | sed 's/^[^:]*:[[:space:]]*//' | tr -d '[:space:]' | tr '[:upper:]' '[:lower:]') - [ "$_val" = "true" ] && _default_enabled=true - fi - - # Detect our event subsection - if echo "$_line" | grep -Eq "^[[:space:]]+${EVENT_NAME}:"; then - _in_event=true - continue - fi - - # Inside our event subsection - if $_in_event; then - # Exit on next sibling key (same indent level as event name) - if echo "$_line" | grep -Eq '^[[:space:]]{2}[a-z]' && ! echo "$_line" | grep -Eq '^[[:space:]]{4}'; then - _in_event=false - continue - fi - if echo "$_line" | grep -Eq '[[:space:]]+enabled:'; then - _val=$(echo "$_line" | sed 's/^[^:]*:[[:space:]]*//' | tr -d '[:space:]' | tr '[:upper:]' '[:lower:]') - [ "$_val" = "true" ] && _enabled=true - [ "$_val" = "false" ] && _enabled=false - fi - if echo "$_line" | grep -Eq '[[:space:]]+message:'; then - _commit_msg=$(echo "$_line" | sed 's/^[^:]*:[[:space:]]*//' | sed 's/^["'\'']//' | sed 's/["'\'']*$//') - fi - fi - fi - done < "$_config_file" - - # If event-specific key not found, use default - if [ "$_enabled" = "false" ] && [ "$_default_enabled" = "true" ]; then - # Only use default if the event wasn't explicitly set to false - # Check if event section existed at all - if ! grep -q "^[[:space:]]*${EVENT_NAME}:" "$_config_file" 2>/dev/null; then - _enabled=true - fi - fi -else - # No config file — auto-commit disabled by default - exit 0 -fi - -if [ "$_enabled" != "true" ]; then - exit 0 -fi - -# Check if there are changes to commit -if git diff --quiet HEAD 2>/dev/null && git diff --cached --quiet 2>/dev/null && [ -z "$(git ls-files --others --exclude-standard 2>/dev/null)" ]; then - echo "[specify] No changes to commit after $EVENT_NAME" >&2 - exit 0 -fi - -# Derive a human-readable command name from the event -# e.g., after_specify -> specify, before_plan -> plan -_command_name=$(echo "$EVENT_NAME" | sed 's/^after_//' | sed 's/^before_//') -_phase=$(echo "$EVENT_NAME" | grep -q '^before_' && echo 'before' || echo 'after') - -# Use custom message if configured, otherwise default -if [ -z "$_commit_msg" ]; then - _commit_msg="[Spec Kit] Auto-commit ${_phase} ${_command_name}" -fi - -# Stage and commit -_git_out=$(git add . 2>&1) || { echo "[specify] Error: git add failed: $_git_out" >&2; exit 1; } -_git_out=$(git commit -q -m "$_commit_msg" 2>&1) || { echo "[specify] Error: git commit failed: $_git_out" >&2; exit 1; } - -echo "[OK] Changes committed ${_phase} ${_command_name}" >&2 diff --git a/.specify/extensions/git/scripts/bash/create-new-feature.sh b/.specify/extensions/git/scripts/bash/create-new-feature.sh deleted file mode 100755 index 286aaf76..00000000 --- a/.specify/extensions/git/scripts/bash/create-new-feature.sh +++ /dev/null @@ -1,453 +0,0 @@ -#!/usr/bin/env bash -# Git extension: create-new-feature.sh -# Adapted from core scripts/bash/create-new-feature.sh for extension layout. -# Sources common.sh from the project's installed scripts, falling back to -# git-common.sh for minimal git helpers. - -set -e - -JSON_MODE=false -DRY_RUN=false -ALLOW_EXISTING=false -SHORT_NAME="" -BRANCH_NUMBER="" -USE_TIMESTAMP=false -ARGS=() -i=1 -while [ $i -le $# ]; do - arg="${!i}" - case "$arg" in - --json) - JSON_MODE=true - ;; - --dry-run) - DRY_RUN=true - ;; - --allow-existing-branch) - ALLOW_EXISTING=true - ;; - --short-name) - if [ $((i + 1)) -gt $# ]; then - echo 'Error: --short-name requires a value' >&2 - exit 1 - fi - i=$((i + 1)) - next_arg="${!i}" - if [[ "$next_arg" == --* ]]; then - echo 'Error: --short-name requires a value' >&2 - exit 1 - fi - SHORT_NAME="$next_arg" - ;; - --number) - if [ $((i + 1)) -gt $# ]; then - echo 'Error: --number requires a value' >&2 - exit 1 - fi - i=$((i + 1)) - next_arg="${!i}" - if [[ "$next_arg" == --* ]]; then - echo 'Error: --number requires a value' >&2 - exit 1 - fi - BRANCH_NUMBER="$next_arg" - if [[ ! "$BRANCH_NUMBER" =~ ^[0-9]+$ ]]; then - echo 'Error: --number must be a non-negative integer' >&2 - exit 1 - fi - ;; - --timestamp) - USE_TIMESTAMP=true - ;; - --help|-h) - echo "Usage: $0 [--json] [--dry-run] [--allow-existing-branch] [--short-name ] [--number N] [--timestamp] " - echo "" - echo "Options:" - echo " --json Output in JSON format" - echo " --dry-run Compute branch name without creating the branch" - echo " --allow-existing-branch Switch to branch if it already exists instead of failing" - echo " --short-name Provide a custom short name (2-4 words) for the branch" - echo " --number N Specify branch number manually (overrides auto-detection)" - echo " --timestamp Use timestamp prefix (YYYYMMDD-HHMMSS) instead of sequential numbering" - echo " --help, -h Show this help message" - echo "" - echo "Environment variables:" - echo " GIT_BRANCH_NAME Use this exact branch name, bypassing all prefix/suffix generation" - echo "" - echo "Examples:" - echo " $0 'Add user authentication system' --short-name 'user-auth'" - echo " $0 'Implement OAuth2 integration for API' --number 5" - echo " $0 --timestamp --short-name 'user-auth' 'Add user authentication'" - echo " GIT_BRANCH_NAME=my-branch $0 'feature description'" - exit 0 - ;; - *) - ARGS+=("$arg") - ;; - esac - i=$((i + 1)) -done - -FEATURE_DESCRIPTION="${ARGS[*]}" -if [ -z "$FEATURE_DESCRIPTION" ]; then - echo "Usage: $0 [--json] [--dry-run] [--allow-existing-branch] [--short-name ] [--number N] [--timestamp] " >&2 - exit 1 -fi - -# Trim whitespace and validate description is not empty -FEATURE_DESCRIPTION=$(echo "$FEATURE_DESCRIPTION" | xargs) -if [ -z "$FEATURE_DESCRIPTION" ]; then - echo "Error: Feature description cannot be empty or contain only whitespace" >&2 - exit 1 -fi - -# Function to get highest number from specs directory -get_highest_from_specs() { - local specs_dir="$1" - local highest=0 - - if [ -d "$specs_dir" ]; then - for dir in "$specs_dir"/*; do - [ -d "$dir" ] || continue - dirname=$(basename "$dir") - # Match sequential prefixes (>=3 digits), but skip timestamp dirs. - if echo "$dirname" | grep -Eq '^[0-9]{3,}-' && ! echo "$dirname" | grep -Eq '^[0-9]{8}-[0-9]{6}-'; then - number=$(echo "$dirname" | grep -Eo '^[0-9]+') - number=$((10#$number)) - if [ "$number" -gt "$highest" ]; then - highest=$number - fi - fi - done - fi - - echo "$highest" -} - -# Function to get highest number from git branches -get_highest_from_branches() { - git branch -a 2>/dev/null | sed 's/^[* ]*//; s|^remotes/[^/]*/||' | _extract_highest_number -} - -# Extract the highest sequential feature number from a list of ref names (one per line). -_extract_highest_number() { - local highest=0 - while IFS= read -r name; do - [ -z "$name" ] && continue - if echo "$name" | grep -Eq '^[0-9]{3,}-' && ! echo "$name" | grep -Eq '^[0-9]{8}-[0-9]{6}-'; then - number=$(echo "$name" | grep -Eo '^[0-9]+' || echo "0") - number=$((10#$number)) - if [ "$number" -gt "$highest" ]; then - highest=$number - fi - fi - done - echo "$highest" -} - -# Function to get highest number from remote branches without fetching (side-effect-free) -get_highest_from_remote_refs() { - local highest=0 - - for remote in $(git remote 2>/dev/null); do - local remote_highest - remote_highest=$(GIT_TERMINAL_PROMPT=0 git ls-remote --heads "$remote" 2>/dev/null | sed 's|.*refs/heads/||' | _extract_highest_number) - if [ "$remote_highest" -gt "$highest" ]; then - highest=$remote_highest - fi - done - - echo "$highest" -} - -# Function to check existing branches and return next available number. -check_existing_branches() { - local specs_dir="$1" - local skip_fetch="${2:-false}" - - if [ "$skip_fetch" = true ]; then - local highest_remote=$(get_highest_from_remote_refs) - local highest_branch=$(get_highest_from_branches) - if [ "$highest_remote" -gt "$highest_branch" ]; then - highest_branch=$highest_remote - fi - else - git fetch --all --prune >/dev/null 2>&1 || true - local highest_branch=$(get_highest_from_branches) - fi - - local highest_spec=$(get_highest_from_specs "$specs_dir") - - local max_num=$highest_branch - if [ "$highest_spec" -gt "$max_num" ]; then - max_num=$highest_spec - fi - - echo $((max_num + 1)) -} - -# Function to clean and format a branch name -clean_branch_name() { - local name="$1" - echo "$name" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//' -} - -# --------------------------------------------------------------------------- -# Source common.sh for resolve_template, json_escape, get_repo_root, has_git. -# -# Search locations in priority order: -# 1. .specify/scripts/bash/common.sh under the project root (installed project) -# 2. scripts/bash/common.sh under the project root (source checkout fallback) -# 3. git-common.sh next to this script (minimal fallback — lacks resolve_template) -# --------------------------------------------------------------------------- -SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -# Find project root by walking up from the script location -_find_project_root() { - local dir="$1" - while [ "$dir" != "/" ]; do - if [ -d "$dir/.specify" ] || [ -d "$dir/.git" ]; then - echo "$dir" - return 0 - fi - dir="$(dirname "$dir")" - done - return 1 -} - -_common_loaded=false -_PROJECT_ROOT=$(_find_project_root "$SCRIPT_DIR") || true - -if [ -n "$_PROJECT_ROOT" ] && [ -f "$_PROJECT_ROOT/.specify/scripts/bash/common.sh" ]; then - source "$_PROJECT_ROOT/.specify/scripts/bash/common.sh" - _common_loaded=true -elif [ -n "$_PROJECT_ROOT" ] && [ -f "$_PROJECT_ROOT/scripts/bash/common.sh" ]; then - source "$_PROJECT_ROOT/scripts/bash/common.sh" - _common_loaded=true -elif [ -f "$SCRIPT_DIR/git-common.sh" ]; then - source "$SCRIPT_DIR/git-common.sh" - _common_loaded=true -fi - -if [ "$_common_loaded" != "true" ]; then - echo "Error: Could not locate common.sh or git-common.sh. Please ensure the Specify core scripts are installed." >&2 - exit 1 -fi - -# Resolve repository root -if type get_repo_root >/dev/null 2>&1; then - REPO_ROOT=$(get_repo_root) -elif git rev-parse --show-toplevel >/dev/null 2>&1; then - REPO_ROOT=$(git rev-parse --show-toplevel) -elif [ -n "$_PROJECT_ROOT" ]; then - REPO_ROOT="$_PROJECT_ROOT" -else - echo "Error: Could not determine repository root." >&2 - exit 1 -fi - -# Check if git is available at this repo root -if type has_git >/dev/null 2>&1; then - if has_git "$REPO_ROOT"; then - HAS_GIT=true - else - HAS_GIT=false - fi -elif git -C "$REPO_ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then - HAS_GIT=true -else - HAS_GIT=false -fi - -cd "$REPO_ROOT" - -SPECS_DIR="$REPO_ROOT/specs" - -# Function to generate branch name with stop word filtering -generate_branch_name() { - local description="$1" - - local stop_words="^(i|a|an|the|to|for|of|in|on|at|by|with|from|is|are|was|were|be|been|being|have|has|had|do|does|did|will|would|should|could|can|may|might|must|shall|this|that|these|those|my|your|our|their|want|need|add|get|set)$" - - local clean_name=$(echo "$description" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/ /g') - - local meaningful_words=() - for word in $clean_name; do - [ -z "$word" ] && continue - if ! echo "$word" | grep -qiE "$stop_words"; then - if [ ${#word} -ge 3 ]; then - meaningful_words+=("$word") - elif echo "$description" | grep -qw -- "${word^^}"; then - meaningful_words+=("$word") - fi - fi - done - - if [ ${#meaningful_words[@]} -gt 0 ]; then - local max_words=3 - if [ ${#meaningful_words[@]} -eq 4 ]; then max_words=4; fi - - local result="" - local count=0 - for word in "${meaningful_words[@]}"; do - if [ $count -ge $max_words ]; then break; fi - if [ -n "$result" ]; then result="$result-"; fi - result="$result$word" - count=$((count + 1)) - done - echo "$result" - else - local cleaned=$(clean_branch_name "$description") - echo "$cleaned" | tr '-' '\n' | grep -v '^$' | head -3 | tr '\n' '-' | sed 's/-$//' - fi -} - -# Check for GIT_BRANCH_NAME env var override (exact branch name, no prefix/suffix) -if [ -n "${GIT_BRANCH_NAME:-}" ]; then - BRANCH_NAME="$GIT_BRANCH_NAME" - # Extract FEATURE_NUM from the branch name if it starts with a numeric prefix - # Check timestamp pattern first (YYYYMMDD-HHMMSS-) since it also matches the simpler ^[0-9]+ pattern - if echo "$BRANCH_NAME" | grep -Eq '^[0-9]{8}-[0-9]{6}-'; then - FEATURE_NUM=$(echo "$BRANCH_NAME" | grep -Eo '^[0-9]{8}-[0-9]{6}') - BRANCH_SUFFIX="${BRANCH_NAME#${FEATURE_NUM}-}" - elif echo "$BRANCH_NAME" | grep -Eq '^[0-9]+-'; then - FEATURE_NUM=$(echo "$BRANCH_NAME" | grep -Eo '^[0-9]+') - BRANCH_SUFFIX="${BRANCH_NAME#${FEATURE_NUM}-}" - else - FEATURE_NUM="$BRANCH_NAME" - BRANCH_SUFFIX="$BRANCH_NAME" - fi -else - # Generate branch name - if [ -n "$SHORT_NAME" ]; then - BRANCH_SUFFIX=$(clean_branch_name "$SHORT_NAME") - else - BRANCH_SUFFIX=$(generate_branch_name "$FEATURE_DESCRIPTION") - fi - - # Warn if --number and --timestamp are both specified - if [ "$USE_TIMESTAMP" = true ] && [ -n "$BRANCH_NUMBER" ]; then - >&2 echo "[specify] Warning: --number is ignored when --timestamp is used" - BRANCH_NUMBER="" - fi - - # Determine branch prefix - if [ "$USE_TIMESTAMP" = true ]; then - FEATURE_NUM=$(date +%Y%m%d-%H%M%S) - BRANCH_NAME="${FEATURE_NUM}-${BRANCH_SUFFIX}" - else - if [ -z "$BRANCH_NUMBER" ]; then - if [ "$DRY_RUN" = true ] && [ "$HAS_GIT" = true ]; then - BRANCH_NUMBER=$(check_existing_branches "$SPECS_DIR" true) - elif [ "$DRY_RUN" = true ]; then - HIGHEST=$(get_highest_from_specs "$SPECS_DIR") - BRANCH_NUMBER=$((HIGHEST + 1)) - elif [ "$HAS_GIT" = true ]; then - BRANCH_NUMBER=$(check_existing_branches "$SPECS_DIR") - else - HIGHEST=$(get_highest_from_specs "$SPECS_DIR") - BRANCH_NUMBER=$((HIGHEST + 1)) - fi - fi - - FEATURE_NUM=$(printf "%03d" "$((10#$BRANCH_NUMBER))") - BRANCH_NAME="${FEATURE_NUM}-${BRANCH_SUFFIX}" - fi -fi - -# GitHub enforces a 244-byte limit on branch names -MAX_BRANCH_LENGTH=244 -_byte_length() { printf '%s' "$1" | LC_ALL=C wc -c | tr -d ' '; } -BRANCH_BYTE_LEN=$(_byte_length "$BRANCH_NAME") -if [ -n "${GIT_BRANCH_NAME:-}" ] && [ "$BRANCH_BYTE_LEN" -gt $MAX_BRANCH_LENGTH ]; then - >&2 echo "Error: GIT_BRANCH_NAME must be 244 bytes or fewer in UTF-8. Provided value is ${BRANCH_BYTE_LEN} bytes." - exit 1 -elif [ "$BRANCH_BYTE_LEN" -gt $MAX_BRANCH_LENGTH ]; then - PREFIX_LENGTH=$(( ${#FEATURE_NUM} + 1 )) - MAX_SUFFIX_LENGTH=$((MAX_BRANCH_LENGTH - PREFIX_LENGTH)) - - TRUNCATED_SUFFIX=$(echo "$BRANCH_SUFFIX" | cut -c1-$MAX_SUFFIX_LENGTH) - TRUNCATED_SUFFIX=$(echo "$TRUNCATED_SUFFIX" | sed 's/-$//') - - ORIGINAL_BRANCH_NAME="$BRANCH_NAME" - BRANCH_NAME="${FEATURE_NUM}-${TRUNCATED_SUFFIX}" - - >&2 echo "[specify] Warning: Branch name exceeded GitHub's 244-byte limit" - >&2 echo "[specify] Original: $ORIGINAL_BRANCH_NAME (${#ORIGINAL_BRANCH_NAME} bytes)" - >&2 echo "[specify] Truncated to: $BRANCH_NAME (${#BRANCH_NAME} bytes)" -fi - -if [ "$DRY_RUN" != true ]; then - if [ "$HAS_GIT" = true ]; then - branch_create_error="" - if ! branch_create_error=$(git checkout -q -b "$BRANCH_NAME" 2>&1); then - current_branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || true)" - if git branch --list "$BRANCH_NAME" | grep -q .; then - if [ "$ALLOW_EXISTING" = true ]; then - if [ "$current_branch" = "$BRANCH_NAME" ]; then - : - elif ! switch_branch_error=$(git checkout -q "$BRANCH_NAME" 2>&1); then - >&2 echo "Error: Failed to switch to existing branch '$BRANCH_NAME'. Please resolve any local changes or conflicts and try again." - if [ -n "$switch_branch_error" ]; then - >&2 printf '%s\n' "$switch_branch_error" - fi - exit 1 - fi - elif [ "$USE_TIMESTAMP" = true ]; then - >&2 echo "Error: Branch '$BRANCH_NAME' already exists. Rerun to get a new timestamp or use a different --short-name." - exit 1 - else - >&2 echo "Error: Branch '$BRANCH_NAME' already exists. Please use a different feature name or specify a different number with --number." - exit 1 - fi - else - >&2 echo "Error: Failed to create git branch '$BRANCH_NAME'." - if [ -n "$branch_create_error" ]; then - >&2 printf '%s\n' "$branch_create_error" - else - >&2 echo "Please check your git configuration and try again." - fi - exit 1 - fi - fi - else - >&2 echo "[specify] Warning: Git repository not detected; skipped branch creation for $BRANCH_NAME" - fi - - printf '# To persist: export SPECIFY_FEATURE=%q\n' "$BRANCH_NAME" >&2 -fi - -if $JSON_MODE; then - if command -v jq >/dev/null 2>&1; then - if [ "$DRY_RUN" = true ]; then - jq -cn \ - --arg branch_name "$BRANCH_NAME" \ - --arg feature_num "$FEATURE_NUM" \ - '{BRANCH_NAME:$branch_name,FEATURE_NUM:$feature_num,DRY_RUN:true}' - else - jq -cn \ - --arg branch_name "$BRANCH_NAME" \ - --arg feature_num "$FEATURE_NUM" \ - '{BRANCH_NAME:$branch_name,FEATURE_NUM:$feature_num}' - fi - else - if type json_escape >/dev/null 2>&1; then - _je_branch=$(json_escape "$BRANCH_NAME") - _je_num=$(json_escape "$FEATURE_NUM") - else - _je_branch="$BRANCH_NAME" - _je_num="$FEATURE_NUM" - fi - if [ "$DRY_RUN" = true ]; then - printf '{"BRANCH_NAME":"%s","FEATURE_NUM":"%s","DRY_RUN":true}\n' "$_je_branch" "$_je_num" - else - printf '{"BRANCH_NAME":"%s","FEATURE_NUM":"%s"}\n' "$_je_branch" "$_je_num" - fi - fi -else - echo "BRANCH_NAME: $BRANCH_NAME" - echo "FEATURE_NUM: $FEATURE_NUM" - if [ "$DRY_RUN" != true ]; then - printf '# To persist in your shell: export SPECIFY_FEATURE=%q\n' "$BRANCH_NAME" - fi -fi diff --git a/.specify/extensions/git/scripts/bash/git-common.sh b/.specify/extensions/git/scripts/bash/git-common.sh deleted file mode 100755 index b78356d1..00000000 --- a/.specify/extensions/git/scripts/bash/git-common.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env bash -# Git-specific common functions for the git extension. -# Extracted from scripts/bash/common.sh — contains only git-specific -# branch validation and detection logic. - -# Check if we have git available at the repo root -has_git() { - local repo_root="${1:-$(pwd)}" - { [ -d "$repo_root/.git" ] || [ -f "$repo_root/.git" ]; } && \ - command -v git >/dev/null 2>&1 && \ - git -C "$repo_root" rev-parse --is-inside-work-tree >/dev/null 2>&1 -} - -# Strip a single optional path segment (e.g. gitflow "feat/004-name" -> "004-name"). -# Only when the full name is exactly two slash-free segments; otherwise returns the raw name. -spec_kit_effective_branch_name() { - local raw="$1" - if [[ "$raw" =~ ^([^/]+)/([^/]+)$ ]]; then - printf '%s\n' "${BASH_REMATCH[2]}" - else - printf '%s\n' "$raw" - fi -} - -# Validate that a branch name matches the expected feature branch pattern. -# Accepts sequential (###-* with >=3 digits) or timestamp (YYYYMMDD-HHMMSS-*) formats. -# Logic aligned with scripts/bash/common.sh check_feature_branch after effective-name normalization. -check_feature_branch() { - local raw="$1" - local has_git_repo="$2" - - # For non-git repos, we can't enforce branch naming but still provide output - if [[ "$has_git_repo" != "true" ]]; then - echo "[specify] Warning: Git repository not detected; skipped branch validation" >&2 - return 0 - fi - - local branch - branch=$(spec_kit_effective_branch_name "$raw") - - # Accept sequential prefix (3+ digits) but exclude malformed timestamps - # Malformed: 7-or-8 digit date + 6-digit time with no trailing slug (e.g. "2026031-143022" or "20260319-143022") - local is_sequential=false - if [[ "$branch" =~ ^[0-9]{3,}- ]] && [[ ! "$branch" =~ ^[0-9]{7}-[0-9]{6}- ]] && [[ ! "$branch" =~ ^[0-9]{7,8}-[0-9]{6}$ ]]; then - is_sequential=true - fi - if [[ "$is_sequential" != "true" ]] && [[ ! "$branch" =~ ^[0-9]{8}-[0-9]{6}- ]]; then - echo "ERROR: Not on a feature branch. Current branch: $raw" >&2 - echo "Feature branches should be named like: 001-feature-name, 1234-feature-name, or 20260319-143022-feature-name" >&2 - return 1 - fi - - return 0 -} diff --git a/.specify/extensions/git/scripts/bash/initialize-repo.sh b/.specify/extensions/git/scripts/bash/initialize-repo.sh deleted file mode 100755 index 296e363b..00000000 --- a/.specify/extensions/git/scripts/bash/initialize-repo.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env bash -# Git extension: initialize-repo.sh -# Initialize a Git repository with an initial commit. -# Customizable — replace this script to add .gitignore templates, -# default branch config, git-flow, LFS, signing, etc. - -set -e - -SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -# Find project root -_find_project_root() { - local dir="$1" - while [ "$dir" != "/" ]; do - if [ -d "$dir/.specify" ] || [ -d "$dir/.git" ]; then - echo "$dir" - return 0 - fi - dir="$(dirname "$dir")" - done - return 1 -} - -REPO_ROOT=$(_find_project_root "$SCRIPT_DIR") || REPO_ROOT="$(pwd)" -cd "$REPO_ROOT" - -# Read commit message from extension config, fall back to default -COMMIT_MSG="[Spec Kit] Initial commit" -_config_file="$REPO_ROOT/.specify/extensions/git/git-config.yml" -if [ -f "$_config_file" ]; then - _msg=$(grep '^init_commit_message:' "$_config_file" 2>/dev/null | sed 's/^init_commit_message:[[:space:]]*//' | sed 's/^["'\'']//' | sed 's/["'\'']*$//') - if [ -n "$_msg" ]; then - COMMIT_MSG="$_msg" - fi -fi - -# Check if git is available -if ! command -v git >/dev/null 2>&1; then - echo "[specify] Warning: Git not found; skipped repository initialization" >&2 - exit 0 -fi - -# Check if already a git repo -if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then - echo "[specify] Git repository already initialized; skipping" >&2 - exit 0 -fi - -# Initialize -_git_out=$(git init -q 2>&1) || { echo "[specify] Error: git init failed: $_git_out" >&2; exit 1; } -_git_out=$(git add . 2>&1) || { echo "[specify] Error: git add failed: $_git_out" >&2; exit 1; } -_git_out=$(git commit --allow-empty -q -m "$COMMIT_MSG" 2>&1) || { echo "[specify] Error: git commit failed: $_git_out" >&2; exit 1; } - -echo "✓ Git repository initialized" >&2 diff --git a/.specify/extensions/git/scripts/powershell/auto-commit.ps1 b/.specify/extensions/git/scripts/powershell/auto-commit.ps1 deleted file mode 100644 index 4a8b0e00..00000000 --- a/.specify/extensions/git/scripts/powershell/auto-commit.ps1 +++ /dev/null @@ -1,169 +0,0 @@ -#!/usr/bin/env pwsh -# Git extension: auto-commit.ps1 -# Automatically commit changes after a Spec Kit command completes. -# Checks per-command config keys in git-config.yml before committing. -# -# Usage: auto-commit.ps1 -# e.g.: auto-commit.ps1 after_specify -param( - [Parameter(Position = 0, Mandatory = $true)] - [string]$EventName -) -$ErrorActionPreference = 'Stop' - -function Find-ProjectRoot { - param([string]$StartDir) - $current = Resolve-Path $StartDir - while ($true) { - foreach ($marker in @('.specify', '.git')) { - if (Test-Path (Join-Path $current $marker)) { - return $current - } - } - $parent = Split-Path $current -Parent - if ($parent -eq $current) { return $null } - $current = $parent - } -} - -$repoRoot = Find-ProjectRoot -StartDir $PSScriptRoot -if (-not $repoRoot) { $repoRoot = Get-Location } -Set-Location $repoRoot - -# Check if git is available -if (-not (Get-Command git -ErrorAction SilentlyContinue)) { - Write-Warning "[specify] Warning: Git not found; skipped auto-commit" - exit 0 -} - -# Temporarily relax ErrorActionPreference so git stderr warnings -# (e.g. CRLF notices on Windows) do not become terminating errors. -$savedEAP = $ErrorActionPreference -$ErrorActionPreference = 'Continue' -try { - git rev-parse --is-inside-work-tree 2>$null | Out-Null - $isRepo = $LASTEXITCODE -eq 0 -} finally { - $ErrorActionPreference = $savedEAP -} -if (-not $isRepo) { - Write-Warning "[specify] Warning: Not a Git repository; skipped auto-commit" - exit 0 -} - -# Read per-command config from git-config.yml -$configFile = Join-Path $repoRoot ".specify/extensions/git/git-config.yml" -$enabled = $false -$commitMsg = "" - -if (Test-Path $configFile) { - # Parse YAML to find auto_commit section - $inAutoCommit = $false - $inEvent = $false - $defaultEnabled = $false - - foreach ($line in Get-Content $configFile) { - # Detect auto_commit: section - if ($line -match '^auto_commit:') { - $inAutoCommit = $true - $inEvent = $false - continue - } - - # Exit auto_commit section on next top-level key - if ($inAutoCommit -and $line -match '^[a-z]') { - break - } - - if ($inAutoCommit) { - # Check default key - if ($line -match '^\s+default:\s*(.+)$') { - $val = $matches[1].Trim().ToLower() - if ($val -eq 'true') { $defaultEnabled = $true } - } - - # Detect our event subsection - if ($line -match "^\s+${EventName}:") { - $inEvent = $true - continue - } - - # Inside our event subsection - if ($inEvent) { - # Exit on next sibling key (2-space indent, not 4+) - if ($line -match '^\s{2}[a-z]' -and $line -notmatch '^\s{4}') { - $inEvent = $false - continue - } - if ($line -match '\s+enabled:\s*(.+)$') { - $val = $matches[1].Trim().ToLower() - if ($val -eq 'true') { $enabled = $true } - if ($val -eq 'false') { $enabled = $false } - } - if ($line -match '\s+message:\s*(.+)$') { - $commitMsg = $matches[1].Trim() -replace '^["'']' -replace '["'']$' - } - } - } - } - - # If event-specific key not found, use default - if (-not $enabled -and $defaultEnabled) { - $hasEventKey = Select-String -Path $configFile -Pattern "^\s*${EventName}:" -Quiet - if (-not $hasEventKey) { - $enabled = $true - } - } -} else { - # No config file — auto-commit disabled by default - exit 0 -} - -if (-not $enabled) { - exit 0 -} - -# Check if there are changes to commit -# Relax ErrorActionPreference so CRLF warnings on stderr do not terminate. -$savedEAP = $ErrorActionPreference -$ErrorActionPreference = 'Continue' -try { - git diff --quiet HEAD 2>$null; $d1 = $LASTEXITCODE - git diff --cached --quiet 2>$null; $d2 = $LASTEXITCODE - $untracked = git ls-files --others --exclude-standard 2>$null -} finally { - $ErrorActionPreference = $savedEAP -} - -if ($d1 -eq 0 -and $d2 -eq 0 -and -not $untracked) { - Write-Host "[specify] No changes to commit after $EventName" -ForegroundColor DarkGray - exit 0 -} - -# Derive a human-readable command name from the event -$commandName = $EventName -replace '^after_', '' -replace '^before_', '' -$phase = if ($EventName -match '^before_') { 'before' } else { 'after' } - -# Use custom message if configured, otherwise default -if (-not $commitMsg) { - $commitMsg = "[Spec Kit] Auto-commit $phase $commandName" -} - -# Stage and commit -# Relax ErrorActionPreference so CRLF warnings on stderr do not terminate, -# while still allowing redirected error output to be captured for diagnostics. -$savedEAP = $ErrorActionPreference -$ErrorActionPreference = 'Continue' -try { - $out = git add . 2>&1 | Out-String - if ($LASTEXITCODE -ne 0) { throw "git add failed: $out" } - $out = git commit -q -m $commitMsg 2>&1 | Out-String - if ($LASTEXITCODE -ne 0) { throw "git commit failed: $out" } -} catch { - Write-Warning "[specify] Error: $_" - exit 1 -} finally { - $ErrorActionPreference = $savedEAP -} - -Write-Host "[OK] Changes committed $phase $commandName" diff --git a/.specify/extensions/git/scripts/powershell/create-new-feature.ps1 b/.specify/extensions/git/scripts/powershell/create-new-feature.ps1 deleted file mode 100644 index b579f051..00000000 --- a/.specify/extensions/git/scripts/powershell/create-new-feature.ps1 +++ /dev/null @@ -1,403 +0,0 @@ -#!/usr/bin/env pwsh -# Git extension: create-new-feature.ps1 -# Adapted from core scripts/powershell/create-new-feature.ps1 for extension layout. -# Sources common.ps1 from the project's installed scripts, falling back to -# git-common.ps1 for minimal git helpers. -[CmdletBinding()] -param( - [switch]$Json, - [switch]$AllowExistingBranch, - [switch]$DryRun, - [string]$ShortName, - [Parameter()] - [long]$Number = 0, - [switch]$Timestamp, - [switch]$Help, - [Parameter(Position = 0, ValueFromRemainingArguments = $true)] - [string[]]$FeatureDescription -) -$ErrorActionPreference = 'Stop' - -if ($Help) { - Write-Host "Usage: ./create-new-feature.ps1 [-Json] [-DryRun] [-AllowExistingBranch] [-ShortName ] [-Number N] [-Timestamp] " - Write-Host "" - Write-Host "Options:" - Write-Host " -Json Output in JSON format" - Write-Host " -DryRun Compute branch name without creating the branch" - Write-Host " -AllowExistingBranch Switch to branch if it already exists instead of failing" - Write-Host " -ShortName Provide a custom short name (2-4 words) for the branch" - Write-Host " -Number N Specify branch number manually (overrides auto-detection)" - Write-Host " -Timestamp Use timestamp prefix (YYYYMMDD-HHMMSS) instead of sequential numbering" - Write-Host " -Help Show this help message" - Write-Host "" - Write-Host "Environment variables:" - Write-Host " GIT_BRANCH_NAME Use this exact branch name, bypassing all prefix/suffix generation" - Write-Host "" - exit 0 -} - -if (-not $FeatureDescription -or $FeatureDescription.Count -eq 0) { - Write-Error "Usage: ./create-new-feature.ps1 [-Json] [-DryRun] [-AllowExistingBranch] [-ShortName ] [-Number N] [-Timestamp] " - exit 1 -} - -$featureDesc = ($FeatureDescription -join ' ').Trim() - -if ([string]::IsNullOrWhiteSpace($featureDesc)) { - Write-Error "Error: Feature description cannot be empty or contain only whitespace" - exit 1 -} - -function Get-HighestNumberFromSpecs { - param([string]$SpecsDir) - - [long]$highest = 0 - if (Test-Path $SpecsDir) { - Get-ChildItem -Path $SpecsDir -Directory | ForEach-Object { - if ($_.Name -match '^(\d{3,})-' -and $_.Name -notmatch '^\d{8}-\d{6}-') { - [long]$num = 0 - if ([long]::TryParse($matches[1], [ref]$num) -and $num -gt $highest) { - $highest = $num - } - } - } - } - return $highest -} - -function Get-HighestNumberFromNames { - param([string[]]$Names) - - [long]$highest = 0 - foreach ($name in $Names) { - if ($name -match '^(\d{3,})-' -and $name -notmatch '^\d{8}-\d{6}-') { - [long]$num = 0 - if ([long]::TryParse($matches[1], [ref]$num) -and $num -gt $highest) { - $highest = $num - } - } - } - return $highest -} - -function Get-HighestNumberFromBranches { - param() - - try { - $branches = git branch -a 2>$null - if ($LASTEXITCODE -eq 0 -and $branches) { - $cleanNames = $branches | ForEach-Object { - $_.Trim() -replace '^\*?\s+', '' -replace '^remotes/[^/]+/', '' - } - return Get-HighestNumberFromNames -Names $cleanNames - } - } catch { - Write-Verbose "Could not check Git branches: $_" - } - return 0 -} - -function Get-HighestNumberFromRemoteRefs { - [long]$highest = 0 - try { - $remotes = git remote 2>$null - if ($remotes) { - foreach ($remote in $remotes) { - $env:GIT_TERMINAL_PROMPT = '0' - $refs = git ls-remote --heads $remote 2>$null - $env:GIT_TERMINAL_PROMPT = $null - if ($LASTEXITCODE -eq 0 -and $refs) { - $refNames = $refs | ForEach-Object { - if ($_ -match 'refs/heads/(.+)$') { $matches[1] } - } | Where-Object { $_ } - $remoteHighest = Get-HighestNumberFromNames -Names $refNames - if ($remoteHighest -gt $highest) { $highest = $remoteHighest } - } - } - } - } catch { - Write-Verbose "Could not query remote refs: $_" - } - return $highest -} - -function Get-NextBranchNumber { - param( - [string]$SpecsDir, - [switch]$SkipFetch - ) - - if ($SkipFetch) { - $highestBranch = Get-HighestNumberFromBranches - $highestRemote = Get-HighestNumberFromRemoteRefs - $highestBranch = [Math]::Max($highestBranch, $highestRemote) - } else { - try { - git fetch --all --prune 2>$null | Out-Null - } catch { } - $highestBranch = Get-HighestNumberFromBranches - } - - $highestSpec = Get-HighestNumberFromSpecs -SpecsDir $SpecsDir - $maxNum = [Math]::Max($highestBranch, $highestSpec) - return $maxNum + 1 -} - -function ConvertTo-CleanBranchName { - param([string]$Name) - return $Name.ToLower() -replace '[^a-z0-9]', '-' -replace '-{2,}', '-' -replace '^-', '' -replace '-$', '' -} - -# --------------------------------------------------------------------------- -# Source common.ps1 from the project's installed scripts. -# Search locations in priority order: -# 1. .specify/scripts/powershell/common.ps1 under the project root -# 2. scripts/powershell/common.ps1 under the project root (source checkout) -# 3. git-common.ps1 next to this script (minimal fallback) -# --------------------------------------------------------------------------- -function Find-ProjectRoot { - param([string]$StartDir) - $current = Resolve-Path $StartDir - while ($true) { - foreach ($marker in @('.specify', '.git')) { - if (Test-Path (Join-Path $current $marker)) { - return $current - } - } - $parent = Split-Path $current -Parent - if ($parent -eq $current) { return $null } - $current = $parent - } -} - -$projectRoot = Find-ProjectRoot -StartDir $PSScriptRoot -$commonLoaded = $false - -if ($projectRoot) { - $candidates = @( - (Join-Path $projectRoot ".specify/scripts/powershell/common.ps1"), - (Join-Path $projectRoot "scripts/powershell/common.ps1") - ) - foreach ($candidate in $candidates) { - if (Test-Path $candidate) { - . $candidate - $commonLoaded = $true - break - } - } -} - -if (-not $commonLoaded -and (Test-Path "$PSScriptRoot/git-common.ps1")) { - . "$PSScriptRoot/git-common.ps1" - $commonLoaded = $true -} - -if (-not $commonLoaded) { - throw "Unable to locate common script file. Please ensure the Specify core scripts are installed." -} - -# Resolve repository root -if (Get-Command Get-RepoRoot -ErrorAction SilentlyContinue) { - $repoRoot = Get-RepoRoot -} elseif ($projectRoot) { - $repoRoot = $projectRoot -} else { - throw "Could not determine repository root." -} - -# Check if git is available -if (Get-Command Test-HasGit -ErrorAction SilentlyContinue) { - # Call without parameters for compatibility with core common.ps1 (no -RepoRoot param) - # and git-common.ps1 (has -RepoRoot param with default). - $hasGit = Test-HasGit -} else { - try { - git -C $repoRoot rev-parse --is-inside-work-tree 2>$null | Out-Null - $hasGit = ($LASTEXITCODE -eq 0) - } catch { - $hasGit = $false - } -} - -Set-Location $repoRoot - -$specsDir = Join-Path $repoRoot 'specs' - -function Get-BranchName { - param([string]$Description) - - $stopWords = @( - 'i', 'a', 'an', 'the', 'to', 'for', 'of', 'in', 'on', 'at', 'by', 'with', 'from', - 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had', - 'do', 'does', 'did', 'will', 'would', 'should', 'could', 'can', 'may', 'might', 'must', 'shall', - 'this', 'that', 'these', 'those', 'my', 'your', 'our', 'their', - 'want', 'need', 'add', 'get', 'set' - ) - - $cleanName = $Description.ToLower() -replace '[^a-z0-9\s]', ' ' - $words = $cleanName -split '\s+' | Where-Object { $_ } - - $meaningfulWords = @() - foreach ($word in $words) { - if ($stopWords -contains $word) { continue } - if ($word.Length -ge 3) { - $meaningfulWords += $word - } elseif ($Description -match "\b$($word.ToUpper())\b") { - $meaningfulWords += $word - } - } - - if ($meaningfulWords.Count -gt 0) { - $maxWords = if ($meaningfulWords.Count -eq 4) { 4 } else { 3 } - $result = ($meaningfulWords | Select-Object -First $maxWords) -join '-' - return $result - } else { - $result = ConvertTo-CleanBranchName -Name $Description - $fallbackWords = ($result -split '-') | Where-Object { $_ } | Select-Object -First 3 - return [string]::Join('-', $fallbackWords) - } -} - -# Check for GIT_BRANCH_NAME env var override (exact branch name, no prefix/suffix) -if ($env:GIT_BRANCH_NAME) { - $branchName = $env:GIT_BRANCH_NAME - # Check 244-byte limit (UTF-8) for override names - $branchNameUtf8ByteCount = [System.Text.Encoding]::UTF8.GetByteCount($branchName) - if ($branchNameUtf8ByteCount -gt 244) { - throw "GIT_BRANCH_NAME must be 244 bytes or fewer in UTF-8. Provided value is $branchNameUtf8ByteCount bytes; please supply a shorter override branch name." - } - # Extract FEATURE_NUM from the branch name if it starts with a numeric prefix - # Check timestamp pattern first (YYYYMMDD-HHMMSS-) since it also matches the simpler ^\d+ pattern - if ($branchName -match '^(\d{8}-\d{6})-') { - $featureNum = $matches[1] - } elseif ($branchName -match '^(\d+)-') { - $featureNum = $matches[1] - } else { - $featureNum = $branchName - } -} else { - if ($ShortName) { - $branchSuffix = ConvertTo-CleanBranchName -Name $ShortName - } else { - $branchSuffix = Get-BranchName -Description $featureDesc - } - - if ($Timestamp -and $Number -ne 0) { - Write-Warning "[specify] Warning: -Number is ignored when -Timestamp is used" - $Number = 0 - } - - if ($Timestamp) { - $featureNum = Get-Date -Format 'yyyyMMdd-HHmmss' - $branchName = "$featureNum-$branchSuffix" - } else { - if ($Number -eq 0) { - if ($DryRun -and $hasGit) { - $Number = Get-NextBranchNumber -SpecsDir $specsDir -SkipFetch - } elseif ($DryRun) { - $Number = (Get-HighestNumberFromSpecs -SpecsDir $specsDir) + 1 - } elseif ($hasGit) { - $Number = Get-NextBranchNumber -SpecsDir $specsDir - } else { - $Number = (Get-HighestNumberFromSpecs -SpecsDir $specsDir) + 1 - } - } - - $featureNum = ('{0:000}' -f $Number) - $branchName = "$featureNum-$branchSuffix" - } -} - -$maxBranchLength = 244 -if ($branchName.Length -gt $maxBranchLength) { - $prefixLength = $featureNum.Length + 1 - $maxSuffixLength = $maxBranchLength - $prefixLength - - $truncatedSuffix = $branchSuffix.Substring(0, [Math]::Min($branchSuffix.Length, $maxSuffixLength)) - $truncatedSuffix = $truncatedSuffix -replace '-$', '' - - $originalBranchName = $branchName - $branchName = "$featureNum-$truncatedSuffix" - - Write-Warning "[specify] Branch name exceeded GitHub's 244-byte limit" - Write-Warning "[specify] Original: $originalBranchName ($($originalBranchName.Length) bytes)" - Write-Warning "[specify] Truncated to: $branchName ($($branchName.Length) bytes)" -} - -if (-not $DryRun) { - if ($hasGit) { - $branchCreated = $false - $branchCreateError = '' - try { - $branchCreateError = git checkout -q -b $branchName 2>&1 | Out-String - if ($LASTEXITCODE -eq 0) { - $branchCreated = $true - } - } catch { - $branchCreateError = $_.Exception.Message - } - - if (-not $branchCreated) { - $currentBranch = '' - try { $currentBranch = (git rev-parse --abbrev-ref HEAD 2>$null).Trim() } catch {} - $existingBranch = git branch --list $branchName 2>$null - if ($existingBranch) { - if ($AllowExistingBranch) { - if ($currentBranch -eq $branchName) { - # Already on the target branch - } else { - $switchBranchError = git checkout -q $branchName 2>&1 | Out-String - if ($LASTEXITCODE -ne 0) { - if ($switchBranchError) { - Write-Error "Error: Branch '$branchName' exists but could not be checked out.`n$($switchBranchError.Trim())" - } else { - Write-Error "Error: Branch '$branchName' exists but could not be checked out. Resolve any uncommitted changes or conflicts and try again." - } - exit 1 - } - } - } elseif ($Timestamp) { - Write-Error "Error: Branch '$branchName' already exists. Rerun to get a new timestamp or use a different -ShortName." - exit 1 - } else { - Write-Error "Error: Branch '$branchName' already exists. Please use a different feature name or specify a different number with -Number." - exit 1 - } - } else { - if ($branchCreateError) { - Write-Error "Error: Failed to create git branch '$branchName'.`n$($branchCreateError.Trim())" - } else { - Write-Error "Error: Failed to create git branch '$branchName'. Please check your git configuration and try again." - } - exit 1 - } - } - } else { - if ($Json) { - [Console]::Error.WriteLine("[specify] Warning: Git repository not detected; skipped branch creation for $branchName") - } else { - Write-Warning "[specify] Warning: Git repository not detected; skipped branch creation for $branchName" - } - } - - $env:SPECIFY_FEATURE = $branchName -} - -if ($Json) { - $obj = [PSCustomObject]@{ - BRANCH_NAME = $branchName - FEATURE_NUM = $featureNum - HAS_GIT = $hasGit - } - if ($DryRun) { - $obj | Add-Member -NotePropertyName 'DRY_RUN' -NotePropertyValue $true - } - $obj | ConvertTo-Json -Compress -} else { - Write-Output "BRANCH_NAME: $branchName" - Write-Output "FEATURE_NUM: $featureNum" - Write-Output "HAS_GIT: $hasGit" - if (-not $DryRun) { - Write-Output "SPECIFY_FEATURE environment variable set to: $branchName" - } -} diff --git a/.specify/extensions/git/scripts/powershell/git-common.ps1 b/.specify/extensions/git/scripts/powershell/git-common.ps1 deleted file mode 100644 index 82210000..00000000 --- a/.specify/extensions/git/scripts/powershell/git-common.ps1 +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env pwsh -# Git-specific common functions for the git extension. -# Extracted from scripts/powershell/common.ps1 — contains only git-specific -# branch validation and detection logic. - -function Test-HasGit { - param([string]$RepoRoot = (Get-Location)) - try { - if (-not (Test-Path (Join-Path $RepoRoot '.git'))) { return $false } - if (-not (Get-Command git -ErrorAction SilentlyContinue)) { return $false } - git -C $RepoRoot rev-parse --is-inside-work-tree 2>$null | Out-Null - return ($LASTEXITCODE -eq 0) - } catch { - return $false - } -} - -function Get-SpecKitEffectiveBranchName { - param([string]$Branch) - if ($Branch -match '^([^/]+)/([^/]+)$') { - return $Matches[2] - } - return $Branch -} - -function Test-FeatureBranch { - param( - [string]$Branch, - [bool]$HasGit = $true - ) - - # For non-git repos, we can't enforce branch naming but still provide output - if (-not $HasGit) { - Write-Warning "[specify] Warning: Git repository not detected; skipped branch validation" - return $true - } - - $raw = $Branch - $Branch = Get-SpecKitEffectiveBranchName $raw - - # Accept sequential prefix (3+ digits) but exclude malformed timestamps - # Malformed: 7-or-8 digit date + 6-digit time with no trailing slug (e.g. "2026031-143022" or "20260319-143022") - $hasMalformedTimestamp = ($Branch -match '^[0-9]{7}-[0-9]{6}-') -or ($Branch -match '^(?:\d{7}|\d{8})-\d{6}$') - $isSequential = ($Branch -match '^[0-9]{3,}-') -and (-not $hasMalformedTimestamp) - if (-not $isSequential -and $Branch -notmatch '^\d{8}-\d{6}-') { - [Console]::Error.WriteLine("ERROR: Not on a feature branch. Current branch: $raw") - [Console]::Error.WriteLine("Feature branches should be named like: 001-feature-name, 1234-feature-name, or 20260319-143022-feature-name") - return $false - } - return $true -} diff --git a/.specify/extensions/git/scripts/powershell/initialize-repo.ps1 b/.specify/extensions/git/scripts/powershell/initialize-repo.ps1 deleted file mode 100644 index 324240a3..00000000 --- a/.specify/extensions/git/scripts/powershell/initialize-repo.ps1 +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env pwsh -# Git extension: initialize-repo.ps1 -# Initialize a Git repository with an initial commit. -# Customizable — replace this script to add .gitignore templates, -# default branch config, git-flow, LFS, signing, etc. -$ErrorActionPreference = 'Stop' - -# Find project root -function Find-ProjectRoot { - param([string]$StartDir) - $current = Resolve-Path $StartDir - while ($true) { - foreach ($marker in @('.specify', '.git')) { - if (Test-Path (Join-Path $current $marker)) { - return $current - } - } - $parent = Split-Path $current -Parent - if ($parent -eq $current) { return $null } - $current = $parent - } -} - -$repoRoot = Find-ProjectRoot -StartDir $PSScriptRoot -if (-not $repoRoot) { $repoRoot = Get-Location } -Set-Location $repoRoot - -# Read commit message from extension config, fall back to default -$commitMsg = "[Spec Kit] Initial commit" -$configFile = Join-Path $repoRoot ".specify/extensions/git/git-config.yml" -if (Test-Path $configFile) { - foreach ($line in Get-Content $configFile) { - if ($line -match '^init_commit_message:\s*(.+)$') { - $val = $matches[1].Trim() -replace '^["'']' -replace '["'']$' - if ($val) { $commitMsg = $val } - break - } - } -} - -# Check if git is available -if (-not (Get-Command git -ErrorAction SilentlyContinue)) { - Write-Warning "[specify] Warning: Git not found; skipped repository initialization" - exit 0 -} - -# Check if already a git repo -try { - git rev-parse --is-inside-work-tree 2>$null | Out-Null - if ($LASTEXITCODE -eq 0) { - Write-Warning "[specify] Git repository already initialized; skipping" - exit 0 - } -} catch { } - -# Initialize -try { - $out = git init -q 2>&1 | Out-String - if ($LASTEXITCODE -ne 0) { throw "git init failed: $out" } - $out = git add . 2>&1 | Out-String - if ($LASTEXITCODE -ne 0) { throw "git add failed: $out" } - $out = git commit --allow-empty -q -m $commitMsg 2>&1 | Out-String - if ($LASTEXITCODE -ne 0) { throw "git commit failed: $out" } -} catch { - Write-Warning "[specify] Error: $_" - exit 1 -} - -Write-Host "✓ Git repository initialized" diff --git a/.specify/extensions/spex-collab/commands/speckit.spex-collab.phase-manager.md b/.specify/extensions/spex-collab/commands/speckit.spex-collab.phase-manager.md deleted file mode 100644 index bfd4de45..00000000 --- a/.specify/extensions/spex-collab/commands/speckit.spex-collab.phase-manager.md +++ /dev/null @@ -1,302 +0,0 @@ ---- -description: "Manage phase boundaries, PR creation, and REVIEWERS.md updates between implementation phases" ---- - -# Phase Manager - -Coordinates the boundary between implementation phases: runs code review, updates REVIEWERS.md with code-specific guidance, offers PR creation, and manages phase state for cross-session continuity. - -## Ship Pipeline Guard - -```bash -if [ -f ".specify/.spex-state" ]; then - MODE=$(jq -r '.mode // empty' .specify/.spex-state 2>/dev/null) - STATUS=$(jq -r '.status // empty' .specify/.spex-state 2>/dev/null) - if [ "$MODE" = "ship" ] || [ "$STATUS" = "running" ]; then - echo "Ship mode active, skipping phase manager" - fi -fi -``` - -If ship mode is detected, return immediately. - -## Extension Enabled Check - -```bash -if [ ! -f ".specify/extensions/spex-collab/extension.yml" ]; then - echo "spex-collab extension not found, skipping" -fi -``` - -If the extension is not found, return without action. - -## Read Phase State - -Load the phase plan and completion state from `.specify/.spex-state`: - -```bash -PHASE_PLAN=$(jq -r '.collab.phase_plan // empty' .specify/.spex-state 2>/dev/null) -COMPLETED=$(jq -r '.collab.completed_phases // []' .specify/.spex-state 2>/dev/null) -PR_BASE=$(jq -r '.collab.pr_base_branch // "main"' .specify/.spex-state 2>/dev/null) -``` - -If no phase plan exists (`collab.phase_plan` is empty or missing): -- Output: "No phase plan found. Run the phase-split command first, or invoke `/speckit.spex-collab.phase-split` to create one." -- Return. - -## Determine Current Phase - -Find the next phase to process: -- Get the list of phase numbers from `phase_plan` -- Filter out any phase numbers present in `completed_phases` -- The current phase is the lowest remaining phase number - -If all phases are in `completed_phases`: -- Output: "All phases complete. Implementation is finished." -- List each phase with its PR status if available -- Return. - -Display the current phase: -``` -## Phase [N]: [Phase Name] - -Tasks in this phase: [task IDs] -Previously completed phases: [list or "none"] -``` - -## Resolve Spec Directory - -```bash -PREREQ=$(.specify/scripts/bash/check-prerequisites.sh --json --paths-only 2>/dev/null) -FEATURE_DIR=$(echo "$PREREQ" | jq -r '.FEATURE_DIR') -``` - -## Invoke Code Review Gate - -Before updating REVIEWERS.md, ensure the code review gate has run for this phase's changes. - -Check if `REVIEW-CODE.md` exists in FEATURE_DIR: - -```bash -if [ ! -f "${FEATURE_DIR}/REVIEW-CODE.md" ]; then - echo "REVIEW-CODE.md not found, invoking code review gate" -fi -``` - -If REVIEW-CODE.md does not exist, invoke the code review gate: -- Execute `speckit.spex-gates.review-code` (the review-code skill/command) -- Wait for it to complete before proceeding - -If REVIEW-CODE.md exists, read its findings for use in the REVIEWERS.md update. - -## Update REVIEWERS.md with Code Phase Section - -After the code review gate passes, update REVIEWERS.md with a code-specific phase section. - -### Gather Phase Information - -1. **What Changed**: Run `git diff --stat` against the PR base branch to get a summary of changed files: - ```bash - git diff --stat "${PR_BASE}..HEAD" 2>/dev/null - ``` - -2. **Spec Compliance**: Extract findings from REVIEW-CODE.md (compliance score, deviations, covered requirements) - -3. **Focus Areas**: Identify where the reviewer should concentrate: - - Files with the most changes - - Areas flagged by the code review gate - - Complex logic or non-obvious patterns - -4. **AI Assumptions**: Decisions made during implementation that were not explicitly specified in spec.md. Look for: - - Implementation choices not dictated by the spec - - Default values or behaviors chosen by the AI - - Error handling approaches not specified - -### Compose Phase Section - -Create a new section following this structure: - -```markdown -## Phase [N]: [Phase Name] (YYYY-MM-DD) - -### What Changed - -[Summary of files and functionality added/modified, based on git diff --stat] - -### Spec Compliance - -[Which requirements this phase addresses, compliance findings from REVIEW-CODE.md] - -### Focus Areas for Review - -[Where the reviewer should concentrate, based on complexity and review gate findings] - -### AI Assumptions - -[Decisions made during implementation that were not in the spec] -``` - -### Append to REVIEWERS.md - -Read the existing REVIEWERS.md from FEATURE_DIR. - -Append the new phase section at the end of the file. Ensure: -- A `---` separator precedes the phase section (if not already present) -- The phase number matches the current phase from the phase plan -- Existing phase sections are never overwritten or modified - -If REVIEWERS.md does not exist: -- Warn: "REVIEWERS.md not found. It should have been created by the reviewers command after task generation. Creating a minimal version." -- Create a minimal REVIEWERS.md with just the phase section - -## Offer PR Creation - -Check if `gh` CLI is available: - -```bash -command -v gh >/dev/null 2>&1 -``` - -### If gh is available - -Construct PR details: - -**Title format**: `[Feature Name] [Spec + Impl (N/T)]` where N is the current phase and T is the total number of phases. - -```bash -FEATURE_NAME=$(head -1 "$FEATURE_DIR/spec.md" | sed 's/^# Feature Specification: //') -TOTAL_PHASES=$(jq '.collab.phase_plan | length' .specify/.spex-state 2>/dev/null || echo 1) -CURRENT_PHASE_NUM=[N] # current phase number - -if [ "$TOTAL_PHASES" -gt 1 ]; then - PR_TITLE="${FEATURE_NAME} [Spec + Impl (${CURRENT_PHASE_NUM}/${TOTAL_PHASES})]" -else - PR_TITLE="${FEATURE_NAME} [Spec + Impl]" -fi -``` - -If a spec-only PR was created earlier (titled `... [Spec]`), update its title to reflect the implementation phase using `gh pr edit`. - -- **Body**: Start with a link to REVIEWERS.md for full review context, then include the phase section. - -Construct a full GitHub URL for REVIEWERS.md and read label config: - -```bash -BRANCH=$(git branch --show-current) -REVIEWERS_REL="${FEATURE_DIR#$(git rev-parse --show-toplevel)/}/REVIEWERS.md" -REMOTE=$(git remote | grep -x upstream 2>/dev/null || echo origin) -REMOTE_URL=$(git remote get-url "$REMOTE" 2>/dev/null | sed 's/\.git$//' | sed 's|git@github.com:|https://github.com/|') -REVIEWERS_URL="${REMOTE_URL}/blob/${BRANCH}/${REVIEWERS_REL}" - -# Read label config -COLLAB_CONFIG=".specify/extensions/spex-collab/collab-config.yml" -LABELS_ENABLED=$(yq -r '.labels.enabled // true' "$COLLAB_CONFIG" 2>/dev/null || echo "true") -SPEC_LABEL=$(yq -r '.labels.spec // "spex/spec"' "$COLLAB_CONFIG" 2>/dev/null || echo "spex/spec") -SPEC_APPROVED_LABEL=$(yq -r '.labels.spec_approved // "spex/spec-approved"' "$COLLAB_CONFIG" 2>/dev/null || echo "spex/spec-approved") -IMPL_LABEL=$(yq -r '.labels.implement // "spex/implement"' "$COLLAB_CONFIG" 2>/dev/null || echo "spex/implement") -LABEL_FLAG="" -if [ "$LABELS_ENABLED" = "true" ]; then - LABEL_FLAG="--label ${IMPL_LABEL}" -fi -``` - -The PR body MUST begin with: -``` -> **[Review Guide](REVIEWERS_URL)** for full context: motivation, key decisions, and scope boundaries. -``` - -Followed by the phase section content (What Changed, Spec Compliance, Focus Areas, AI Assumptions). - -Use AskUserQuestion to ask: - -**Question**: "Phase [N] is ready. What would you like to do?" -**Options**: -- "Create PR" - create PR via gh and pause -- "Skip PR, continue to next phase" - mark complete, proceed -- "Pause here" - mark current phase, stop for manual action - -**If "Create PR"**: - -```bash -gh pr create --base "${PR_BASE}" --title "$PR_TITLE" ${LABEL_FLAG} --body "$(cat < **[Review Guide](${REVIEWERS_URL})** for full context: motivation, key decisions, and scope boundaries. - -[PR body content from REVIEWERS.md phase section] -PR_BODY -)" -``` - -After PR creation: -- Capture the PR URL from gh output -- If labels are enabled, update labels on the PR to reflect the implementation phase: - ```bash - # If an existing PR has spex/spec, transition labels - PR_NUM=$(gh pr view --json number --jq .number 2>/dev/null) - if [ -n "$PR_NUM" ] && [ "$LABELS_ENABLED" = "true" ]; then - gh pr edit "$PR_NUM" --remove-label "$SPEC_LABEL" --add-label "$SPEC_APPROVED_LABEL","$IMPL_LABEL" 2>/dev/null || true - fi - ``` -- Mark the phase as completed (see below) -- Output: "PR created: [URL]" -- Output: "Phase [N] complete. After the PR is merged, invoke `/speckit.spex-collab.phase-manager` to continue with Phase [N+1]." -- Stop execution (pause for user to handle the PR) - -**If "Skip PR, continue to next phase"**: -- Mark the phase as completed -- Output: "Phase [N] complete (no PR created). Continuing to Phase [N+1]..." -- Do NOT stop execution, the implementation command can continue - -**If "Pause here"**: -- Set `current_phase` to N in `.spex-state` -- Output: "Paused at Phase [N]. Invoke `/speckit.spex-collab.phase-manager` when ready to continue." -- Stop execution - -### If gh is NOT available - -Warn and provide manual instructions: - -``` -gh CLI not found. To create the PR manually: - -Branch: [current branch name] -Target: [PR_BASE] -Suggested title: [Feature Name] [Spec + Impl (N/T)] -Suggested body (start with the review guide link): - > **[Review Guide](REVIEWERS_URL)** for full context: motivation, key decisions, and scope boundaries. - [phase section content] -``` - -Then use AskUserQuestion: -- "Mark phase complete and continue" - proceed -- "Pause here" - stop for manual action - -## Update Phase State - -When marking a phase as completed: - -```bash -tmp=$(mktemp) && jq \ - --argjson phase_num [N] \ - '.collab.completed_phases += [$phase_num] | .collab.completed_phases |= unique | .collab.current_phase = null' \ - .specify/.spex-state > "$tmp" && mv "$tmp" .specify/.spex-state -``` - -When setting current phase (for pause): - -```bash -tmp=$(mktemp) && jq \ - --argjson phase_num [N] \ - '.collab.current_phase = $phase_num' \ - .specify/.spex-state > "$tmp" && mv "$tmp" .specify/.spex-state -``` - -## Final Report - -After processing the phase, output a summary: - -``` -Phase [N] ([Phase Name]): [COMPLETE / PAUSED] -PR: [URL or "not created"] -REVIEWERS.md: updated with Phase [N] section -Next: [Phase N+1 name or "All phases complete"] -``` diff --git a/.specify/extensions/spex-collab/commands/speckit.spex-collab.phase-split.md b/.specify/extensions/spex-collab/commands/speckit.spex-collab.phase-split.md deleted file mode 100644 index 1fd66435..00000000 --- a/.specify/extensions/spex-collab/commands/speckit.spex-collab.phase-split.md +++ /dev/null @@ -1,149 +0,0 @@ ---- -description: "Present phase split proposal before implementation begins" ---- - -# Phase Split Proposal - -## Ship Pipeline Guard - -If `.specify/.spex-state` exists and its `mode` is `"ship"` or `status` is `"running"`, skip the phase split entirely and return immediately without prompting. - -```bash -if [ -f ".specify/.spex-state" ]; then - MODE=$(jq -r '.mode // empty' .specify/.spex-state 2>/dev/null) - STATUS=$(jq -r '.status // empty' .specify/.spex-state 2>/dev/null) - if [ "$MODE" = "ship" ] || [ "$STATUS" = "running" ]; then - echo "Ship mode active, skipping phase split" - fi -fi -``` - -If ship mode is detected, return immediately. Do not display any proposal or prompt the user. - -## Extension Enabled Check - -Verify spex-collab is active: - -```bash -if [ ! -f ".specify/extensions/spex-collab/extension.yml" ]; then - echo "spex-collab extension not found, skipping" -fi -``` - -If the extension is not found, return without action. - -## Check for Existing Phase Plan - -If `.specify/.spex-state` already has a `collab.phase_plan` with entries, a phase plan was previously confirmed: - -```bash -EXISTING_PLAN=$(jq -r '.collab.phase_plan // empty' .specify/.spex-state 2>/dev/null) -``` - -If a phase plan exists and has entries: -- Display: "A phase plan already exists from a previous session." -- Show the existing plan as a table -- Ask: "Use existing plan, or create a new one?" -- If user chooses existing: return (implementation proceeds with saved plan) -- If user chooses new: continue with detection below - -## Resolve Spec Directory and Read Tasks - -```bash -PREREQ=$(.specify/scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks 2>/dev/null) -FEATURE_DIR=$(echo "$PREREQ" | jq -r '.FEATURE_DIR') -``` - -Read `tasks.md` from FEATURE_DIR. - -## Detect Task Phases - -Parse tasks.md for heading-based groupings. Look for these patterns: - -1. `## Phase N:` headings (e.g., `## Phase 1: Setup`) -2. `## USN` or `## US1:` headings (user story groupings) -3. Any `## ` heading that contains task items (`- [ ] T###`) below it - -For each heading group: -- Record the phase name (heading text) -- Collect all task IDs (lines matching `- [ ] T[0-9]+` or `- [X] T[0-9]+`) -- Count total tasks and completed tasks - -If no phase-like headings are found, treat all tasks as a single phase named "All Tasks". - -## Present Phase Split Proposal - -Display the parsed phases as a table: - -``` -## Proposed PR Split - -| Phase | Name | Tasks | Completed | Task IDs | -|-------|------|-------|-----------|----------| -| 1 | Setup (Extension Scaffold) | 3 | 0 | T001, T002, T003 | -| 2 | US1 - Spec PR with REVIEWERS.md | 6 | 0 | T004-T009 | -| 3 | US2 - Phase-Based Implementation PRs | 10 | 0 | T010-T019 | -| 4 | US3 - Code PR with Updated REVIEWERS.md | 3 | 0 | T020-T022 | -| 5 | Polish & Integration | 6 | 0 | T023-T028 | - -Each phase becomes a separate PR for focused review. -``` - -Use AskUserQuestion to ask: - -**Question**: "Does this phase split look right for your PRs?" -**Options**: -- "Confirm as-is" - proceed with this grouping -- "Adjust groupings" - let user merge or split phases -- "Single phase (no split)" - treat everything as one PR - -If user selects "Adjust groupings": -- Ask which phases to merge or split -- Only allow adjusting phases that have not been completed yet -- Re-display the updated table and confirm again - -If user selects "Single phase": -- Combine all tasks into one phase named "Full Implementation" - -## Persist Phase Plan - -Store the confirmed plan in `.specify/.spex-state` under the `collab` namespace: - -```bash -# Read pr_base_branch from extension config if available -PR_BASE="main" -if [ -f ".specify/extensions/spex-collab/collab-config.yml" ]; then - CONFIGURED_BASE=$(yq -r '.pr_base_branch // "main"' .specify/extensions/spex-collab/collab-config.yml 2>/dev/null) - if [ -n "$CONFIGURED_BASE" ] && [ "$CONFIGURED_BASE" != "null" ]; then - PR_BASE="$CONFIGURED_BASE" - fi -fi -``` - -Update `.spex-state` with the phase plan using jq. The `collab` object contains: -- `phase_plan`: array of objects with `phase` (int), `name` (string), `tasks` (string array) -- `completed_phases`: empty array (or preserved from existing state) -- `current_phase`: null -- `pr_base_branch`: from config or "main" - -```bash -# Example jq update (adapt phase_plan array to actual confirmed phases) -tmp=$(mktemp) && jq --argjson plan '[...]' --arg base "$PR_BASE" ' - .collab = { - "phase_plan": $plan, - "completed_phases": (.collab.completed_phases // []), - "current_phase": null, - "pr_base_branch": $base - } -' .specify/.spex-state > "$tmp" && mv "$tmp" .specify/.spex-state -``` - -## Report - -Output confirmation: -``` -Phase plan saved to .specify/.spex-state -[N] phases configured, targeting [base_branch] for PRs -Implementation will pause after each phase for PR creation. -Invoke `/speckit.spex-collab.phase-manager` after each phase to manage PRs. -``` diff --git a/.specify/extensions/spex-collab/commands/speckit.spex-collab.reconcile.md b/.specify/extensions/spex-collab/commands/speckit.spex-collab.reconcile.md deleted file mode 100644 index bc4af381..00000000 --- a/.specify/extensions/spex-collab/commands/speckit.spex-collab.reconcile.md +++ /dev/null @@ -1,199 +0,0 @@ ---- -description: "Reconcile revised tasks against existing implementation, mark completed tasks, and produce an actionable delta for re-implementation" -argument-hint: "" ---- - -# Reconcile Implementation with Revised Spec - -After a spec revision changes the task list, this command scans the existing codebase to determine which tasks are already satisfied, which need rework, and which are new. It produces a reconciled `tasks.md` where `/speckit-implement` can pick up only the delta. - -## Ship Pipeline Guard - -If `.specify/.spex-state` exists with `mode: "ship"`, return immediately. - -## Resolve Context - -```bash -PREREQ=$(.specify/scripts/bash/check-prerequisites.sh --json --paths-only 2>/dev/null) -FEATURE_DIR=$(echo "$PREREQ" | jq -r '.FEATURE_DIR') -BRANCH=$(git branch --show-current) -``` - -Verify required artifacts: -```bash -[ -f "${FEATURE_DIR}/spec.md" ] || { echo "ERROR: No spec.md"; exit 1; } -[ -f "${FEATURE_DIR}/tasks.md" ] || { echo "ERROR: No tasks.md"; exit 1; } -[ -f "${FEATURE_DIR}/plan.md" ] || { echo "ERROR: No plan.md"; exit 1; } -``` - -## Detect Existing Implementation - -Check whether implementation work already exists on this branch: - -```bash -# Count non-spec commits on the feature branch -IMPL_FILES=$(git diff --name-only main...HEAD 2>/dev/null | grep -v '^specs/' | grep -v '^brainstorm/' | grep -v '^\.' | head -50) -IMPL_FILE_COUNT=$(echo "$IMPL_FILES" | grep -c . 2>/dev/null || echo 0) -``` - -If no implementation files are found (`IMPL_FILE_COUNT` is 0): -``` -No implementation files detected on this branch. -Nothing to reconcile. Run /speckit-implement to start fresh. -``` -Return. - -## Read Artifacts - -1. **tasks.md**: Parse the full task list. Extract each task's: - - ID (e.g., `T01`, `1.1`) - - Status (`[ ]` or `[X]`) - - Description - - File paths mentioned in the task - - Phase assignment - - Parallel marker `[P]` if present - -2. **plan.md**: Read for architecture context, file structure, and module organization to understand what each task produces. - -3. **spec.md**: Read for requirements to understand what "satisfied" means for each task. - -## Analyze Each Task Against Existing Code - -For each task in tasks.md that is currently `[ ]` (not yet marked complete): - -### Step 1: Identify Expected Output - -From the task description and plan context, determine what the task should produce: -- Files to create or modify (extract paths from the task description) -- Functions, classes, or exports to add -- Tests to write -- Configuration changes - -### Step 2: Check Against Existing Code - -For each expected output: - -```bash -# Check if mentioned files exist -for FILE in ; do - [ -f "$FILE" ] && echo "EXISTS: $FILE" || echo "MISSING: $FILE" -done -``` - -For files that exist, read them and assess whether the task's intent is satisfied: -- Does the file contain the expected functions/classes? -- Does the implementation match the spec requirements referenced by the task? -- Are the tests present and covering the expected behavior? - -### Step 3: Classify the Task - -Assign each task one of three statuses: - -- **DONE**: The existing code fully satisfies this task. All expected files exist, functions are implemented, tests cover the behavior. Mark as `[X]`. - -- **REWORK**: The existing code partially covers this task, but the spec revision changed requirements that affect it. The code exists but needs modification. Mark as `[ ]` and add a `` comment. - -- **NEW**: No existing code addresses this task. It was added by the spec revision. Keep as `[ ]`. - -## Present Reconciliation Report - -After analyzing all tasks, present the findings: - -``` -## Reconciliation Report - -**Tasks analyzed**: N total - -| Status | Count | Tasks | -|---------|-------|-------| -| DONE | X | T01, T03, T05, ... | -| REWORK | Y | T04, T08, ... | -| NEW | Z | T12, T13, ... | - -### DONE (will be marked [X]) - -These tasks are fully satisfied by existing code: -- T01: Create events.py module — exists at agent_eval/events.py -- T03: Add EventType enum — exists in events.py with all required types -... - -### REWORK (need modification) - -These tasks have existing code that needs updating for the revised spec: -- T04: Parse tool results — exists but needs 50K cap (was unlimited) - Files: agent_eval/events.py:parse_tool_result() -- T08: Template variable — exists as {{ stdout }}, needs rename to {{ conversation }} - Files: agent_eval/judges/llm.py -... - -### NEW (no existing code) - -These tasks were added by the spec revision: -- T12: Subagent transcript merging -- T13: Deduplication by message ID -... -``` - -Use AskUserQuestion (`multiSelect: false`, header: "Reconcile"): - -**"Apply this reconciliation to tasks.md?"** - -- "Apply all": "Mark DONE tasks as [X], add REWORK comments, keep NEW as [ ]" -- "Review individually": "Go through each DONE/REWORK classification for confirmation" -- "Cancel": "Leave tasks.md unchanged" - -### If "Review individually" - -For each task classified as DONE, ask for confirmation: - -Use AskUserQuestion (`multiSelect: true`, header: "Confirm"): - -**"Which DONE classifications are correct? Unselected tasks will remain [ ]."** - -Options: one per DONE task (label: task ID, description: evidence summary) - -Then for each REWORK task, show the rework description and ask if it's accurate. - -## Apply to tasks.md - -Update `${FEATURE_DIR}/tasks.md`: - -1. Mark confirmed DONE tasks as `[X]` -2. For REWORK tasks, keep as `[ ]` and append a rework hint comment after the task line: - ```markdown - - [ ] T04 [core] Implement tool result parsing with size cap `agent_eval/events.py` - - ``` -3. NEW tasks remain as `[ ]` (no annotation needed) - -## Update REVIEWERS.md - -Append a reconciliation note to the revision history in REVIEWERS.md: - -```markdown -### Reconciliation (YYYY-MM-DD) - -**Existing implementation scanned**: N files on branch -**Task reconciliation**: X DONE, Y REWORK, Z NEW out of T total -**Delta for re-implementation**: Y + Z = D tasks remaining -``` - -## Suggest Next Step - -``` -## Reconciliation Complete - -tasks.md updated: X tasks marked [X], Y marked for rework, Z new -Delta: D tasks remaining for /speckit-implement - -Next step: - /speckit-implement Run implementation for remaining [ ] tasks -``` - -If the delta is 0 (all tasks satisfied): -``` -All tasks are satisfied by existing code. No re-implementation needed. - -Next step: - /speckit-spex-gates-review-code Verify compliance with revised spec -``` diff --git a/.specify/extensions/spex-collab/commands/speckit.spex-collab.reviewers.md b/.specify/extensions/spex-collab/commands/speckit.spex-collab.reviewers.md deleted file mode 100644 index b51c1d60..00000000 --- a/.specify/extensions/spex-collab/commands/speckit.spex-collab.reviewers.md +++ /dev/null @@ -1,209 +0,0 @@ ---- -description: "Generate REVIEWERS.md review guide for spec and code PRs" -argument-hint: "[--regenerate]" ---- - -# Generate REVIEWERS.md Review Guide - -## Ship Pipeline Guard - -If `.specify/.spex-state` exists and its `mode` is `"ship"` or `status` is `"running"`, skip REVIEWERS.md generation entirely and return immediately. - -```bash -if [ -f ".specify/.spex-state" ]; then - MODE=$(jq -r '.mode // empty' .specify/.spex-state 2>/dev/null) - STATUS=$(jq -r '.status // empty' .specify/.spex-state 2>/dev/null) - if [ "$MODE" = "ship" ] || [ "$STATUS" = "running" ]; then - echo "Ship mode active, skipping REVIEWERS.md generation" - fi -fi -``` - -If ship mode is detected, output nothing further and return. Do not generate or modify any files. - -## Extension Enabled Check - -Verify spex-collab is active. If the extension directory does not exist, skip silently: - -```bash -if [ ! -f ".specify/extensions/spex-collab/extension.yml" ]; then - echo "spex-collab extension not found, skipping" -fi -``` - -If the extension is not found, return without generating REVIEWERS.md. Vanilla spec-kit behavior is preserved. - -## Resolve Spec Directory - -Run the prerequisites script to locate the feature directory: - -```bash -.specify/scripts/bash/check-prerequisites.sh --json --paths-only 2>/dev/null -``` - -Parse the JSON output to extract: -- `FEATURE_DIR`: absolute path to the spec directory (e.g., `/path/to/specs/018-collab-extension`) -- `FEATURE_SPEC`: path to spec.md - -If resolution fails, inform the user and stop: -``` -Cannot resolve spec directory. Are you on a feature branch? -``` - -## Check for Existing REVIEWERS.md - -```bash -REVIEWERS_PATH="${FEATURE_DIR}/REVIEWERS.md" -if [ -f "$REVIEWERS_PATH" ]; then - echo "REVIEWERS.md exists, checking for code phase sections to preserve" -fi -``` - -If REVIEWERS.md already exists: -1. Read its content -2. Look for the first line matching `## Phase [0-9]` (a code phase section heading) -3. If found: preserve everything from that line onwards (these are code phase sections from previous implementation phases). Only regenerate the spec sections above that boundary. -4. If no phase headings found: regenerate the entire file - -Store any preserved code phase content for later appending. - -## Read Source Artifacts - -Read these files from FEATURE_DIR to extract review guide content: - -1. **spec.md** (required): - - Problem statement: extract from "## Problem Statement", "## Background", or the first substantive paragraph explaining what's broken or missing (feeds "Why This Change") - - Feature overview: user story summaries or solution description (feeds "What Changes") - - Scope and applicability: extract from "## Requirements" (applies when) and "## Out of Scope" (does not apply when) (feeds "When It Applies") - - Success criteria: from the "## Success Criteria" section - - Edge cases: from the "### Edge Cases" section if present - -2. **plan.md** (if exists, feeds "How It Works"): - - Architecture approach: modules, data flow, integration points - - Key technical decisions: from "## Research Findings" or decision sections - - Trade-offs and rationale: why alternatives were rejected - - Implementation strategy: from the "## Implementation Phases" section - -3. **tasks.md** (if exists): - - Phase count and task distribution - - Complexity indicators (total tasks, parallel markers) - -4. **research.md** (if exists): - - Additional context on technical decisions - - Explored alternatives - -## Compose REVIEWERS.md - -Read the template from `spex/extensions/spex-collab/templates/reviewers-template.md` for the structural skeleton. - -Synthesize a human-readable review guide. This is NOT a dump of automated review findings. Write it as if briefing a colleague who needs to review the PR efficiently. - -### Section Guidelines - -The structure follows "general to specific": a reviewer should understand the motivation and shape of the change before encountering detailed scope lists. - -**Why This Change**: The problem being solved. What's broken, painful, or missing today. 2-4 sentences, written so a reviewer who has NOT read the spec understands the motivation in 30 seconds. Extract from the spec's problem statement, user stories, or the plan's research findings. - -**What Changes**: One paragraph summary of the solution at the outcome level. What gets added, removed, or restructured. Stay at the "what does the user/system gain" level. Mention breaking changes upfront. Do NOT include implementation details here (those go in "How It Works"). - -**How It Works**: Implementation approach extracted from plan.md. Cover architecture, key modules, data flow, and integration points. This is where technical details belong. Keep it concise but specific enough that a reviewer understands the implementation strategy without reading plan.md. For spec-only PRs where plan.md doesn't exist yet, omit this section or note "Implementation approach TBD." - -**When It Applies**: Reframe scope as applicability. More natural than in/out lists for a reviewer scanning the PR. -- "Applies when": conditions, contexts, or scenarios where this feature is active -- "Does not apply when": explicit exclusions with brief rationale for deferral - -**Key Decisions**: Numbered list of the most significant design choices. For each, include: -- What was decided -- What alternatives were considered -- Why this approach was chosen - -**Areas Needing Attention**: Points where reasonable engineers might disagree. Flag: -- Trade-offs that favor one quality over another -- Assumptions that could be wrong -- Patterns that deviate from project conventions -- Complexity that might be over-engineered or under-engineered - -**Open Questions**: Remaining ambiguities or deferred decisions. If none, state "No open questions identified." - -**Review Checklist**: Use the standard checklist from the template. Add feature-specific items if the spec has unusual constraints. - -### Replace Template Placeholders - -- `[Feature Name]`: extract from spec.md title or first heading -- `YYYY-MM-DD`: use today's date -- `[spec.md](spec.md)`: keep as relative link - -## Write REVIEWERS.md - -Write the composed content to `${FEATURE_DIR}/REVIEWERS.md`. - -If code phase sections were preserved from an earlier version (step 4), append them after the `---` separator at the end of the spec sections. - -## Offer Spec PR - -After writing REVIEWERS.md, check if `gh` is available and offer to create a spec-only PR for review before implementation begins. - -Use AskUserQuestion (`multiSelect: false`, header: "Spec PR"): - -**"REVIEWERS.md is ready. Create a spec PR for team review?"** - -Options: -- "Create spec PR": "Push branch and create a PR with the [Spec] tag for review before implementation" -- "Skip": "Continue without creating a PR" - -**If "Create spec PR":** - -```bash -FEATURE_NAME=$(head -1 "$FEATURE_DIR/spec.md" | sed 's/^# Feature Specification: //') -REMOTE=$(git remote | grep -x upstream 2>/dev/null || echo origin) -BRANCH=$(git branch --show-current) -REVIEWERS_REL="${FEATURE_DIR#$(git rev-parse --show-toplevel)/}/REVIEWERS.md" -REMOTE_URL=$(git remote get-url "$REMOTE" 2>/dev/null | sed 's/\.git$//' | sed 's|git@github.com:|https://github.com/|') -REVIEWERS_URL="${REMOTE_URL}/blob/${BRANCH}/${REVIEWERS_REL}" - -# Read label config -COLLAB_CONFIG=".specify/extensions/spex-collab/collab-config.yml" -LABELS_ENABLED=$(yq -r '.labels.enabled // true' "$COLLAB_CONFIG" 2>/dev/null || echo "true") -SPEC_LABEL=$(yq -r '.labels.spec // "spex/spec"' "$COLLAB_CONFIG" 2>/dev/null || echo "spex/spec") -LABEL_FLAG="" -if [ "$LABELS_ENABLED" = "true" ]; then - LABEL_FLAG="--label ${SPEC_LABEL}" -fi - -git push -u "$REMOTE" "$BRANCH" - -gh pr create --base main --title "${FEATURE_NAME} [Spec]" ${LABEL_FLAG} --body "$(cat < **[Review Guide](${REVIEWERS_URL})** for full context: motivation, key decisions, and scope boundaries. - -## Spec for review - -This PR contains the specification artifacts for **${FEATURE_NAME}**. Implementation follows after spec approval. - -Assisted-By: 🤖 Claude Code -PR_BODY -)" -``` - -If the label doesn't exist in the repo, `gh pr create --label` will fail. In that case, retry without the label and warn: -``` -Warning: Label "${SPEC_LABEL}" not found in this repo. PR created without label. -To create it: gh label create "${SPEC_LABEL}" --color 0075ca --description "Spec PR awaiting review" -Or disable labels: set labels.enabled to false in .specify/extensions/spex-collab/collab-config.yml -``` - -Report the PR URL. - -**If "Skip":** Continue without creating a PR. - -## Report - -Output a brief confirmation: -``` -Generated REVIEWERS.md in [feature-dir]/ -Sections: Why This Change, What Changes, How It Works, When It Applies, Key Decisions, Areas Needing Attention, Open Questions, Review Checklist -``` - -If this was a re-run with preserved code phase sections, also note: -``` -Preserved N existing code phase section(s) -``` diff --git a/.specify/extensions/spex-collab/commands/speckit.spex-collab.revise.md b/.specify/extensions/spex-collab/commands/speckit.spex-collab.revise.md deleted file mode 100644 index ac6d17fd..00000000 --- a/.specify/extensions/spex-collab/commands/speckit.spex-collab.revise.md +++ /dev/null @@ -1,321 +0,0 @@ ---- -description: "Revise spec artifacts based on PR review feedback, cascade to plan/tasks, and update REVIEWERS.md with revision history" -argument-hint: "[--pr ] [description of changes]" ---- - -# Revise Spec from PR Feedback - -Handles the spec revision loop: read PR review comments, update spec, cascade to plan and tasks, document the revision in REVIEWERS.md, and push back to the PR. - -## Ship Pipeline Guard - -If `.specify/.spex-state` exists with `mode: "ship"`, return immediately. Spec revision is an interactive collab workflow. - -## Resolve Context - -```bash -PREREQ=$(.specify/scripts/bash/check-prerequisites.sh --json --paths-only 2>/dev/null) -FEATURE_DIR=$(echo "$PREREQ" | jq -r '.FEATURE_DIR') -BRANCH=$(git branch --show-current) -``` - -Verify required artifacts exist: -```bash -[ -f "${FEATURE_DIR}/spec.md" ] || { echo "ERROR: No spec.md found in ${FEATURE_DIR}"; exit 1; } -``` - -## Gather Feedback - -Determine the review feedback to address. Two input modes: - -### Mode 1: PR number provided (`--pr `) - -Fetch unresolved review comments from the PR: - -```bash -PR_NUM="" -gh pr view "$PR_NUM" --json reviews,comments --jq '.reviews[].body, .comments[].body' 2>/dev/null -gh api "repos/{owner}/{repo}/pulls/${PR_NUM}/comments" --jq '.[] | select(.position != null) | "\(.path):\(.position) - \(.body)"' 2>/dev/null -``` - -Present a summary of the feedback to the user: - -``` -## PR # Review Feedback - -Found N review comments: - -1. @reviewer: "The hard removal of stdout seems risky..." -2. @reviewer: "Should subagent merging be in scope?" -... - -Which comments should we address in this revision? -``` - -Use AskUserQuestion (`multiSelect: true`, header: "Feedback"): -**"Which review comments should this revision address?"** - -Options: one per comment (label: truncated comment, description: full text) - -### Mode 2: User describes changes (arguments or conversation) - -If no `--pr` flag, the user's arguments or conversation describe what to change. Use that directly as the revision input. - -## Plan the Revision - -Before making changes, summarize what will change. Read the current spec.md and identify which sections are affected by the feedback. - -Present a revision plan: - -``` -## Revision Plan - -Based on the feedback, these changes are needed: - -**Spec changes**: -- Section "Requirements": add deprecation warning for stdout access -- Section "Out of Scope": move subagent merging to in-scope - -**Expected cascade**: -- plan.md: will need regeneration (new requirements affect implementation approach) -- tasks.md: will need regeneration (task count may change) -- REVIEWERS.md: Key Decisions and Scope Boundaries sections affected - -Proceed with revision? -``` - -Use AskUserQuestion (`multiSelect: false`, header: "Revise"): -**"Proceed with the revision plan?"** -- "Yes, revise all": "Update spec, regenerate plan and tasks, update REVIEWERS.md" -- "Spec only": "Update spec.md only, skip plan/tasks regeneration" -- "Cancel": "Abort revision" - -If "Cancel", stop. - -## Update Spec - -Apply the planned changes to `${FEATURE_DIR}/spec.md`. Edit the affected sections, preserving the overall structure and unaffected content. - -After editing, verify the spec is still well-formed: -- All required sections present -- No orphaned references -- No contradictions introduced by the changes - -### Clarify Updated Spec - -Invoke `/speckit-clarify` on the updated spec to detect any new ambiguities introduced by the revision. In the revise context, answer clarification questions yourself using the PR feedback as context (the reviewer's intent guides the answers). Update the spec with any clarifications. - -### Review Updated Spec - -Invoke `/speckit-spex-gates-review-spec` to validate the revised spec. This runs as a subagent for clean context separation: - -``` -You are reviewing a revised specification after PR feedback. - -Feature directory: -Spec: /spec.md - -Invoke /speckit-spex-gates-review-spec to validate spec quality. -Report the overall assessment and any findings. -``` - -If the review surfaces issues (UNSOUND or critical findings): -- Fix the spec issues before proceeding -- Re-run the review (max 2 retries) -- If issues persist after retries, warn the user and proceed - -### Track Gate Results - -Record gate outcomes for the revision entry: -```bash -SPEC_GATE="PASS" # or the actual assessment from review-spec -``` - -## Cascade to Plan and Tasks - -**Skip this step if user chose "Spec only".** - -### Regenerate Plan - -Invoke `/speckit-plan` to regenerate `plan.md` based on the updated spec. The plan command reads the current spec and produces an updated plan. - -After plan regeneration, verify: -```bash -[ -f "${FEATURE_DIR}/plan.md" ] && echo "plan.md regenerated" -``` - -### Regenerate Tasks - -Invoke `/speckit-tasks` to regenerate `tasks.md` based on the updated plan. - -After task regeneration, capture the new task count: -```bash -NEW_TASK_COUNT=$(grep -c '^\- \[' "${FEATURE_DIR}/tasks.md" 2>/dev/null || echo "?") -``` - -### Review Updated Plan - -Invoke `/speckit-spex-gates-review-plan` to validate the regenerated plan and tasks. This runs as a subagent: - -``` -You are reviewing a regenerated plan after spec revision from PR feedback. - -Feature directory: -Spec: /spec.md -Plan: /plan.md -Tasks: /tasks.md - -Invoke /speckit-spex-gates-review-plan to validate plan coverage and task quality. -Report the findings and overall assessment. -``` - -If the review surfaces critical issues: -- Fix the plan/task issues before proceeding -- Re-run the review (max 2 retries) -- If issues persist after retries, warn the user and proceed - -Record gate outcome: -```bash -PLAN_GATE="PASS" # or the actual assessment from review-plan -``` - -## Update REVIEWERS.md - -### Regenerate Spec Sections - -Invoke the reviewers command logic to regenerate the spec-facing sections (Why This Change, What Changes, Key Decisions, etc.) while preserving any existing code phase sections. - -Read the current REVIEWERS.md: -```bash -REVIEWERS_PATH="${FEATURE_DIR}/REVIEWERS.md" -``` - -If code phase sections exist (lines starting with `## Phase`), preserve them. Regenerate the spec sections above the `---` separator by re-running the reviewers synthesis from the updated spec, plan, and tasks. - -### Append Revision Entry - -After the review checklist and before the `---` separator (or at the end if no separator), append a revision history section. - -If a `## Revision History` section already exists, append a new entry. If not, create the section. - -Determine the revision number: -```bash -REV_COUNT=$(grep -c '^### Rev ' "$REVIEWERS_PATH" 2>/dev/null || echo 0) -NEXT_REV=$((REV_COUNT + 1)) -``` - -Compose the revision entry: - -```markdown -## Revision History - -### Rev N (YYYY-MM-DD) - [Brief trigger description] - -**Trigger**: [PR review feedback from #NNN / User-requested changes / ...] - -**Spec changes**: -- [Bullet list of what changed in spec.md, one per meaningful change] - -**Quality gates**: -- review-spec: [PASS/SOUND (score) / findings fixed / warning: issues remain] -- review-plan: [PASS (score) / findings fixed / skipped (spec-only revision)] - -**Cascade impact**: -- plan.md: [regenerated (summary of changes) / unchanged] -- tasks.md: [regenerated (N tasks, was M) / unchanged] -- REVIEWERS.md: [which sections were updated] -``` - -If the revision history section already exists, append the new `### Rev N` entry at the end of the section (before `---` or end of file). - -## Commit and Push - -Stage all changed artifacts: - -```bash -git add "${FEATURE_DIR}/spec.md" -git add "${FEATURE_DIR}/REVIEWERS.md" -[ -f "${FEATURE_DIR}/plan.md" ] && git add "${FEATURE_DIR}/plan.md" -[ -f "${FEATURE_DIR}/tasks.md" ] && git add "${FEATURE_DIR}/tasks.md" - -git commit -m "spec: revise based on review feedback (rev ${NEXT_REV}) - -Assisted-By: 🤖 Claude Code" -``` - -Push to the remote: -```bash -REMOTE=$(git remote | grep -x upstream 2>/dev/null || echo origin) -git push "$REMOTE" "$BRANCH" -``` - -## Comment on PR - -If a PR number is known (from `--pr` flag or detected from the branch): - -```bash -PR_NUM=$(gh pr list --head "$BRANCH" --json number --jq '.[0].number' 2>/dev/null) -``` - -If a PR exists, post a summary comment: - -```bash -gh pr comment "$PR_NUM" --body "$(cat </dev/null | sed 's/\.git$//' | sed 's|git@github.com:|https://github.com/|') -REVIEWERS_URL="${REMOTE_URL}/blob/${BRANCH}/${FEATURE_DIR#$(git rev-parse --show-toplevel)/}/REVIEWERS.md" -``` - -## Report - -``` -## Revision Complete - -**Rev**: ${NEXT_REV} -**Spec changes**: N sections updated -**Quality gates**: review-spec ${SPEC_GATE}, review-plan ${PLAN_GATE} -**Cascade**: plan.md [regenerated/unchanged], tasks.md [regenerated/unchanged] -**REVIEWERS.md**: revision history appended -**PR**: [comment posted to #NNN / no PR found] -**Pushed**: ${BRANCH} → ${REMOTE} -``` - -## Suggest Reconciliation - -After reporting, check if implementation files already exist on the branch: - -```bash -IMPL_FILES=$(git diff --name-only main...HEAD 2>/dev/null | grep -v '^specs/' | grep -v '^brainstorm/' | grep -v '^\.' | grep -c . 2>/dev/null || echo 0) -``` - -If implementation files exist (`IMPL_FILES` > 0): - -``` -Implementation files detected on this branch (${IMPL_FILES} files). -The revised tasks may conflict with existing code. - -Run /speckit-spex-collab-reconcile to scan existing code against -the updated tasks and produce a delta for re-implementation. -``` diff --git a/.specify/extensions/spex-collab/config-template.yml b/.specify/extensions/spex-collab/config-template.yml deleted file mode 100644 index f747cbef..00000000 --- a/.specify/extensions/spex-collab/config-template.yml +++ /dev/null @@ -1,12 +0,0 @@ -pr_base_branch: "main" -auto_generate_reviewers: true - -# GitHub PR labels applied automatically during PR creation. -# Set enabled to false to skip labeling entirely (e.g., if the repo -# doesn't have these labels and you can't create them). -# Label names are configurable; the defaults use a "spex/" prefix. -labels: - enabled: true - spec: "spex/spec" - spec_approved: "spex/spec-approved" - implement: "spex/implement" diff --git a/.specify/extensions/spex-collab/extension.yml b/.specify/extensions/spex-collab/extension.yml deleted file mode 100644 index 4f7d3bf1..00000000 --- a/.specify/extensions/spex-collab/extension.yml +++ /dev/null @@ -1,56 +0,0 @@ -schema_version: "1.0" - -extension: - id: spex-collab - name: "Spex Collaboration" - version: "1.0.0" - description: "Generate REVIEWERS.md guides for PR reviewers and split implementation into phase-based PRs with pause points" - author: cc-spex - license: MIT - -requires: - speckit_version: ">=0.5.2" - extensions: - - id: spex-gates - version: ">=1.0.0" - -provides: - commands: - - name: speckit.spex-collab.reviewers - file: commands/speckit.spex-collab.reviewers.md - description: "Generate REVIEWERS.md review guide for spec and code PRs" - - name: speckit.spex-collab.phase-split - file: commands/speckit.spex-collab.phase-split.md - description: "Present phase split proposal before implementation" - - name: speckit.spex-collab.phase-manager - file: commands/speckit.spex-collab.phase-manager.md - description: "Manage phase boundaries, PR creation, and REVIEWERS.md updates" - - name: speckit.spex-collab.revise - file: commands/speckit.spex-collab.revise.md - description: "Revise spec from PR review feedback, cascade to plan/tasks, update REVIEWERS.md" - - name: speckit.spex-collab.reconcile - file: commands/speckit.spex-collab.reconcile.md - description: "Reconcile revised tasks against existing implementation, produce delta for re-implementation" - - config: - - name: "collab-config.yml" - template: "config-template.yml" - description: "Collaboration extension configuration" - required: false - -hooks: - after_tasks: - command: speckit.spex-collab.reviewers - optional: false - description: "Generate REVIEWERS.md after task generation" - before_implement: - command: speckit.spex-collab.phase-split - optional: true - prompt: "Review PR split for implementation phases?" - description: "Present phase split proposal before implementation" - -tags: - - "spex" - - "collaboration" - - "review" - - "pr" diff --git a/.specify/extensions/spex-collab/templates/reviewers-template.md b/.specify/extensions/spex-collab/templates/reviewers-template.md deleted file mode 100644 index a0ede26e..00000000 --- a/.specify/extensions/spex-collab/templates/reviewers-template.md +++ /dev/null @@ -1,84 +0,0 @@ -# Review Guide: [Feature Name] - -**Generated**: YYYY-MM-DD | **Spec**: [spec.md](spec.md) - -## Why This Change - -[The problem being solved: what's broken, painful, or missing today. -Written so a reviewer who has NOT read the spec understands the -motivation in 30 seconds.] - -## What Changes - -[One paragraph summary of the solution at the outcome level: what -gets added, removed, or restructured. Stay at the "what does the -user/system gain" level. Mention breaking changes upfront if any. -Do NOT describe implementation details here.] - -## How It Works - -[Implementation approach from plan.md: architecture, key modules, -data flow, integration points. This is where technical details -belong. Keep it concise but specific enough that a reviewer -understands the implementation strategy without reading plan.md.] - -## When It Applies - -[Reframe scope as applicability. More natural than in/out lists -for a reviewer scanning the PR.] - -**Applies when**: -- [conditions, contexts, or scenarios where this feature is active] - -**Does not apply when**: -- [explicit exclusions with brief rationale for deferral] - -## Key Decisions - -1. [Numbered list of the most significant design choices. For each: - what was decided, what alternatives were considered, why this - approach was chosen.] - -## Areas Needing Attention - -[Points where reasonable engineers might disagree. Flag trade-offs, -assumptions that could be wrong, patterns that deviate from project -conventions, complexity concerns.] - -## Open Questions - -[Remaining ambiguities or deferred decisions. If none, state -"No open questions identified."] - -## Review Checklist - -- [ ] Key decisions are justified -- [ ] Breaking changes are documented with migration guidance -- [ ] Scope matches the stated boundaries -- [ ] Success criteria are achievable -- [ ] No unstated assumptions - ---- - - - - - diff --git a/.specify/extensions/spex-deep-review/commands/speckit.spex-deep-review.run.md b/.specify/extensions/spex-deep-review/commands/speckit.spex-deep-review.run.md deleted file mode 100644 index 2dbd9d3d..00000000 --- a/.specify/extensions/spex-deep-review/commands/speckit.spex-deep-review.run.md +++ /dev/null @@ -1,862 +0,0 @@ ---- -name: speckit.spex-deep-review.review -description: Multi-perspective code review with autonomous fix loop - dispatches 5 specialized review agents, merges findings, auto-fixes Critical/Important issues ---- - -# Deep Review: Multi-Perspective Code Review - -## Overview - -This command orchestrates a multi-perspective code review using five specialized review agents. Each agent analyzes code from a distinct angle (correctness, architecture, security, production readiness, test quality). Findings are merged, deduplicated, and classified by severity. Critical and Important findings trigger an autonomous fix loop (up to 3 rounds). Results are documented in `review-findings.md`. - -**This command is invoked by `speckit-spex-gates-review-code` when the deep-review extension is enabled, or via the `after_implement` hook.** - -## Prerequisites - -The caller (review-code or ship) may provide these values. When not provided, the deep-review command resolves them itself: - -1. **Stage 1 result**: spec compliance score (or null if no spec) -2. **Invocation context**: `superpowers` or `manual` -3. **Hint text**: optional focus area from user (or null) -4. **External tool settings**: `{coderabbit: true/false, copilot: true/false}` (see resolution below) -5. **Spec path**: path to spec.md (or null, see Spec Resolution below) -6. **Feature directory**: path to the spec directory for artifact output - -### Spec Resolution - -If the caller does not provide a spec path, attempt branch-based resolution: - -```bash -.specify/scripts/bash/check-prerequisites.sh --json --paths-only 2>/dev/null -``` - -If this succeeds (outputs JSON with `FEATURE_SPEC`), use the resolved spec path and feature directory. If this fails (not on a feature branch, no matching spec directory), proceed without a spec (spec compliance checks will be skipped). - -### External Tool Settings Resolution - -If external tool settings are provided by the caller, use them directly. If not (e.g., when invoked directly by `speckit-spex-ship` or manually), resolve from config: - -```bash -# Read config defaults from deep-review extension config (all default to true if key is missing) -DEEP_REVIEW_CONFIG=".specify/extensions/spex-deep-review/deep-review-config.yml" -DEFAULT_CODERABBIT=$(yq -r '.external_tools.coderabbit // true' "$DEEP_REVIEW_CONFIG" 2>/dev/null) -DEFAULT_COPILOT=$(yq -r '.external_tools.copilot // true' "$DEEP_REVIEW_CONFIG" 2>/dev/null) -``` - -``` -Resolution: - coderabbit = DEFAULT_CODERABBIT - copilot = DEFAULT_COPILOT -``` - -This ensures CodeRabbit and Copilot are enabled by default regardless of how deep-review is invoked. - -## Orchestration Flow - -### Step 1: Determine Changed Files - -Identify the files to review: - -```bash -# Get the main branch name -MAIN_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@' || echo "main") - -# Files changed between main branch and HEAD -git diff --name-only "${MAIN_BRANCH}...HEAD" 2>/dev/null - -# Uncommitted changes (staged + unstaged) -git diff --name-only HEAD 2>/dev/null -git diff --name-only --cached 2>/dev/null -``` - -Combine all results into a deduplicated list. Filter to only source code files (exclude binary, images, lock files). **Exclude files under `specs/` and `brainstorm/`** as these are spec artifacts, not implementation code. - -**For re-review rounds (fix loop):** narrow scope to only files modified by the most recent fix round: -```bash -# Files changed since last staging -git diff --name-only --cached 2>/dev/null -``` - -### Step 2: Detect External Tools - -Check for external review CLIs, respecting the external tool settings from the caller: - -```bash -# CodeRabbit (skip only if explicitly disabled in config) -which coderabbit >/dev/null 2>&1 && echo "CODERABBIT_AVAILABLE=true" - -# GitHub Copilot CLI (skip if copilot setting is false) -which copilot >/dev/null 2>&1 && echo "COPILOT_AVAILABLE=true" -``` - -**External tool resolution:** -1. Use the external tool settings from Prerequisites (either caller-provided or self-resolved from config) -2. **CodeRabbit is enabled by default.** Only skip if the config explicitly sets `coderabbit: false` -3. If `copilot` is `false`, skip Copilot detection entirely -4. If a tool is enabled in settings but not installed, proceed silently without it -5. **When CodeRabbit is available and enabled, it MUST be invoked.** Do not skip it for performance or convenience reasons. CodeRabbit provides external validation that complements the internal review agents. - -### Step 3: Dispatch Review Agents - -**Check for teams extension:** - -Read `.specify/extensions/.registry` and check if `spex-teams` extension is enabled (query: `.extensions["spex-teams"].enabled`). - -**Sequential mode** (teams NOT enabled): -- Dispatch each agent one at a time using the Agent tool -- Each agent gets a fresh, isolated context (no session history) -- Report progress after each agent completes: - ``` - Agent 1/5: Correctness... done, N findings - Agent 2/5: Architecture & Idioms... done, N findings - ... - ``` - -**Parallel mode** (teams IS enabled): -- Dispatch all 5 agents in a single message using multiple Agent tool calls -- Each agent runs in isolated context -- Report progress as each agent completes: - ``` - Agent completed: Security... 2 findings - Agent completed: Test Quality... 0 findings - ... - ``` - -**For each agent dispatch**, use the Agent tool with: -- `subagent_type: "general-purpose"` -- The full agent prompt (from the Agent Prompts section below) -- Include the list of changed files and their contents -- **Include the spec text** (spec.md content, if available). Agents need the spec to check code behavior against requirements. Without it, they can only find code-level issues, not spec compliance gaps. -- Include the hint text (if provided) as additional review focus - -### Step 4: Dispatch External Tools (if available) - -**CodeRabbit** (if available): - -**IMPORTANT: CodeRabbit MUST be run when the CLI is installed and the config allows it. Do NOT skip it. CodeRabbit findings are high-value external validation and MUST be included in the fix loop alongside internal agent findings.** - -First, build the file list excluding spec artifacts: -```bash -# Get changed files, excluding specs/ and brainstorm/ directories -REVIEW_FILES=$(git diff --name-only "${MAIN_BRANCH}...HEAD" 2>/dev/null | grep -v -E '^(specs/|brainstorm/)' | sort -u) -``` - -Then invoke CodeRabbit with the explicit file list: -```bash -# Initial review (Stage 2): review changed source files only -coderabbit review --agent --no-color --files $REVIEW_FILES 2>&1 - -# Fix loop re-review rounds: review only the files that were modified by fixes -coderabbit review --agent --type uncommitted --no-color 2>&1 -``` - -The `--agent` flag produces structured, detailed findings with rationale (preferred over `--prompt-only` which only shows prompts). The `--files` flag ensures spec artifacts under `specs/` and `brainstorm/` are never reviewed. - -Parse output: -1. Check for "Review completed" (no issues found) -2. Split on `=============` delimiters -3. For each block: extract file, line, severity keyword, description, and **rationale/explanation** -4. Map severity: critical -> Critical, major -> Important, minor -> Minor -5. Set category = "external", source_agent = "coderabbit", confidence = 75 -6. **Preserve the full rationale** from CodeRabbit output for inclusion in review-findings.md -7. **All CodeRabbit findings with severity Critical or Important MUST enter the fix loop** (Step 7). They are treated identically to internal agent findings for gate and fix purposes. - -**Copilot CLI** (if available): -```bash -copilot -s -p "Review the following git diff for bugs, security issues, and code quality problems. Output ONLY a structured list of findings. For each finding use this exact format: - -### FINDING -- Severity: Critical|Important|Minor -- File: -- Line: -- Description: - -End each finding with --- - -$(git diff HEAD)" 2>&1 -``` -Parse output: -1. Split on "### FINDING" markers -2. For each block: extract Severity, File, Line, Description fields -3. **Discard findings for files under `specs/`** (spec artifacts are not code to review) -4. Set category = "external", source_agent = "copilot", confidence = 75 - -**Error handling for external tools:** -If a tool times out, crashes, or returns an error: -- Log the failure (tool name, error reason) for inclusion in review-findings.md -- Continue with findings from internal agents and any other working tools -- Do NOT block the review - -### Step 5: Merge and Deduplicate Findings - -1. Collect all findings from internal agents and external tools -2. Normalize to common schema: - ``` - { - id: "FINDING-N", - severity: Critical|Important|Minor, - confidence: 0-100, - file: "relative/path", - line_start: N, - line_end: N, - category: correctness|architecture|security|production-readiness|test-quality|external, - description: "what is wrong", - rationale: "why it matters", - fix: "how to fix it", - source_agent: "agent-name", - also_reported_by: [], - external_rationale: "full rationale from external tool (CodeRabbit/Copilot), or null", - resolution: "pending", - round_found: N - } - ``` -3. Sort by file path, then line number -4. Deduplicate: for each pair of findings where: - - Same file path AND - - Overlapping line ranges (A.line_start <= B.line_end AND B.line_start <= A.line_end) AND - - Same category - Then: keep the finding with the longer description, add the other's source_agent to `also_reported_by`, use the higher severity and confidence -5. Assign sequential IDs to merged findings - -### Step 6: Gate Check - -Count findings by severity: -- **Critical count**: findings with severity = Critical -- **Important count**: findings with severity = Important -- **Minor count**: findings with severity = Minor - -**Gate logic:** -- If Critical + Important = 0: **GATE PASS** -- If Critical + Important > 0: proceed to fix loop (or fail if max rounds reached) - -### Step 7: Autonomous Fix Loop - -**Maximum 3 rounds.** - -For each round: -1. Report: `Fix round N/3: N Critical + N Important findings to address` -2. Collect all Critical and Important findings, sorted by file -3. For each file with findings, sort findings by line number **descending** (reverse order to prevent line shifts) -4. Apply each fix: - - Read the file - - Apply the fix suggestion at the specified location - - The main conversation agent performs fixes (not review agents) -5. Stage all changes: `git add ` -6. Report: `Fix round N/3: applied N fixes, re-reviewing...` -7. Re-dispatch review agents on **only the modified files** (narrowed scope) -8. Merge new findings with existing Minor findings -9. Gate check: - - If Critical + Important = 0: **GATE PASS**, exit loop - - If round < 3: continue to next round - - If round = 3: **GATE FAIL**, exit loop - -**No user approval needed.** Fixes are applied autonomously. The user reviews all accumulated changes after the loop completes via `git diff`. - -### Step 7b: Post-Fix Spec Compliance Check - -**This step is MANDATORY when the fix loop removed code (deleted lines, removed functions, or deleted files).** Code removal is the operation most likely to silently drop a spec requirement. Edits and additions don't carry the same risk. - -After the fix loop completes (regardless of PASS or FAIL), check if any fix round removed code: - -```bash -# Check if any fix round deleted lines -REMOVED_LINES=$(git diff --stat HEAD~1 2>/dev/null | grep -oE '[0-9]+ deletion' | head -1) -``` - -If code was removed AND a spec is available: - -1. Read the spec's functional requirements (all FR-NNN entries or requirement bullet points) -2. For each functional requirement, verify that at least one code path still implements it: - - Search for key terms, function names, or class names associated with the requirement - - Check that the implementation file still exists - - Verify the function/method body is not empty or a stub -3. Build a coverage check: - -``` -Post-fix spec coverage: - FR-001: parse JSONL events → agent_eval/events.py:parse_events() ✓ - FR-002: event type discriminator → agent_eval/events.py:EventType ✓ - ... - FR-009: tool result from user msgs → MISSING (removed in fix round 1) ✗ -``` - -4. If any FR is MISSING or STUB: - - Add a new Critical finding for each: `"Spec requirement FR-NNN dropped during fix loop: [requirement text]"` - - If the fix loop has remaining rounds (< 3), run another fix round to re-implement the dropped requirements - - If max rounds reached, report the dropped requirements as Critical findings in the gate outcome - -5. Update the gate outcome: - - If dropped requirements were re-implemented successfully: maintain GATE PASS - - If dropped requirements remain: **GATE FAIL** (spec coverage gaps override code quality PASS) - -If no code was removed, or no spec is available, skip this step. - -### Step 8: Write review-findings.md - -Write `specs//review-findings.md` (overwrite if exists): - -```markdown -# Deep Review Findings - -**Date:** YYYY-MM-DD -**Branch:** branch-name -**Rounds:** N -**Gate Outcome:** PASS|FAIL -**Invocation:** superpowers|manual - -## Summary - -| Severity | Found | Fixed | Remaining | -|----------|-------|-------|-----------| -| Critical | N | N | N | -| Important | N | N | N | -| Minor | N | - | N | -| **Total** | **N** | **N** | **N** | - -**Agents completed:** 5/5 (+ N external tools) -**Agents failed:** [list if any] - -## Findings - -### FINDING-1 -- **Severity:** Critical -- **Confidence:** 85 -- **File:** path/to/file.go:142-148 -- **Category:** correctness -- **Source:** correctness-agent (also reported by: coderabbit) -- **Round found:** 1 -- **Resolution:** fixed (round 1) - -**What is wrong:** -[Describe the issue clearly. What specific code pattern, logic error, or -vulnerability was found? Include the relevant code snippet if it helps -understanding.] - -**Why this matters:** -[Explain the impact. What could go wrong if this is not fixed? Is it a -runtime error, data corruption risk, security exposure, or maintenance -burden? Be specific about the failure scenario.] - -**How it was resolved:** -[If fixed: explain what was changed and why this fix is correct. -If remaining: explain what needs to happen to resolve it.] - -[If CodeRabbit or Copilot reported this finding, include their analysis:] - -**External tool analysis (CodeRabbit):** -> [Preserve the full rationale from CodeRabbit's output. This gives -> reviewers the external AI's perspective, which may differ from or -> complement the internal agent's analysis.] - -### FINDING-2 -[Same structure. Every finding gets the full treatment.] - -... - -## Post-Fix Spec Coverage - -[If Step 7b ran, include the coverage check results:] - -| Requirement | Implementation | Status | -|-------------|---------------|--------| -| FR-001: ... | file.py:func() | ✓ | -| FR-009: ... | MISSING (removed in fix round 1) | ✗ | - -[If all FRs covered: "All spec requirements verified after fix loop."] -[If any dropped: "N spec requirements dropped during fix loop and flagged as Critical findings."] - -## Remaining Findings - -[If gate failed, list unresolved findings here with the same detailed -format. Explain why they could not be auto-fixed and what human action -is needed.] -``` - -### Step 9: Report Gate Outcome with Agent Summary - -After writing `review-findings.md`, output a tabular console summary showing what each agent found, what was fixed, and the gate outcome. This is the primary output the user sees. - -**Always output this summary to the console:** - -``` -Deep review completed. - -Gate: PASS|FAIL (after fix round N) - -Review Agents: - -| Agent | Found | Fixed | Remaining | Status | -|-------------------------|-------|-------|-----------|-----------| -| Correctness | N | N | N | completed | -| Architecture & Idioms | N | N | N | completed | -| Security | N | N | N | completed | -| Production Readiness | N | N | N | completed | -| Test Quality | N | N | N | completed | -| CodeRabbit (external) | N | N | N | completed/skipped/failed | -| Copilot (external) | N | N | N | completed/skipped/failed | -|-------------------------|-------|-------|-----------|-----------| -| Total | N | N | N | | - -Key fixes applied: - 1. [Brief description of fix] (agent-name) - 2. [Brief description of fix] (agent-name) - ... - -Remaining findings (N Important): - - [Finding summary] (agent-name, file:line) - ... - -Post-fix spec coverage: N/N requirements verified [✓ all covered | ✗ N dropped] - -Details: review-findings.md -``` - -**Constraints:** -- Always include the agent table, even if some agents found nothing (show 0) -- Include external tools in the table even if skipped (show "skipped" with reason in Status) -- "Key fixes applied" lists up to 10 most significant fixes, grouped by theme -- "Remaining findings" lists only Critical and Important severity items -- If gate PASSED with zero remaining: omit the "Remaining findings" section -- If CodeRabbit was skipped: show reason (e.g., "skipped (CLI not installed)" or "skipped (disabled in config)") - ---- - -## Finding Output Schema - -Each review agent MUST return findings in this exact format: - -```markdown -## Findings - -### FINDING-1 -- **Severity**: Critical|Important|Minor -- **Confidence**: 0-100 -- **File**: relative/path/to/file.ext -- **Lines**: start-end -- **Category**: [agent's category] -- **Description**: [what is wrong] -- **Rationale**: [why it matters, with evidence from the code] -- **Fix**: [concrete fix suggestion with code if applicable] - -### FINDING-2 -... - -## Self-Verification -- [ ] Each finding has file:line evidence from actual code I read -- [ ] No findings invented for code that is actually clean -- [ ] No duplicate findings (same issue reported twice) -- [ ] Confidence scores reflect my actual certainty, not padding -- [ ] If I found zero issues, I re-read the code a second time to confirm -- [ ] Every finding includes a concrete, implementable fix -- [ ] If a spec was provided, I checked my findings against specific FR/NFR requirements -- [ ] I checked boundary/last-iteration behavior in all loops and retry logic -``` - -If an agent finds no issues after careful review, it MUST return: - -```markdown -## Findings - -No issues found. Code was reviewed twice to confirm. - -## Self-Verification -- [x] Re-read code a second time after initial zero-finding pass -- [x] Confirmed no issues in my focus area -``` - ---- - -## Agent Prompts - -### Common Preamble (included in every agent prompt) - -The following instructions are prepended to every agent's prompt: - -``` -IMPORTANT INSTRUCTIONS - READ BEFORE REVIEWING: - -1. ANTI-SYCOPHANCY: Do NOT start with praise. Do NOT say "Great implementation!", - "Nice work!", or any positive affirmation. Start directly with your findings. - Zero findings is a red flag - if you find nothing, re-read the entire codebase - a second time before confirming zero findings. - -2. DISTRUST: Do NOT trust the implementer's report, comments, or commit messages. - Verify EVERYTHING by reading the actual code. Comments may be wrong. Variable - names may be misleading. Test names may not match what they test. - - ISOLATION: Do NOT read git log, commit messages, brainstorm documents, or - plan.md/tasks.md. These reveal implementation intent and bias your review. - Review the CODE and the SPEC only. Judge what was built, not what was intended. - -3. DO NOT trust test results as proof of correctness. Read the actual assertions. - A passing test with weak assertions proves nothing. Verify what is actually - being tested, not what the test name claims. - -4. FAILURE MODES - You MUST NOT: - - Inflate nits to fill a quota. If the code is clean in your area, say so. - - Invent issues that don't exist. Every finding must cite specific code. - - Repeat the same finding in different words. - - Report issues outside your designated scope (see your role gate below). - -5. CONFIDENCE SCORING: Rate every finding 0-100. - - Only report findings with confidence >= 70 - - EXCEPTION: Critical findings may be reported at confidence >= 50 - - Be honest about uncertainty. 60% confidence on a real issue is better - than 95% confidence on a manufactured one. - -6. EVERY FINDING MUST INCLUDE: - - File path and line number(s) - - What is wrong (specific, not vague) - - Why it matters (impact if not fixed) - - How to fix it (concrete suggestion, not "consider improving") - -7. LANGUAGE AWARENESS: Adapt your checklist based on the programming languages - detected in the changed files. For mixed-language changes, apply language-specific - checks for each language present. - -8. OUTPUT FORMAT: Use the Finding Output Schema exactly as specified. Do not - deviate from the format. Your output will be parsed programmatically. - -9. SPEC AWARENESS: If a spec (spec.md) is provided, cross-check the code - against specific requirements. For each functional requirement (FR-NNN), - verify the code implements exactly what the spec says, not more, not less. - Flag mismatches as findings. Common spec compliance gaps: - - Code handles a broader or narrower set of cases than the spec defines - - Metrics or observability the spec requires but code doesn't expose - - Error codes or status codes that differ from the spec - - Behavioral differences on edge cases (last iteration, empty input, etc.) -``` - -### Agent 1: Correctness - -``` -You are the CORRECTNESS REVIEW AGENT. - -YOUR ROLE: You ARE responsible for finding bugs, logic errors, and correctness issues. -YOUR SCOPE: Mutation safety, shared references, logic errors, resource cleanup, -error path correctness, off-by-one errors, null/nil handling, type confusion. - -YOU ARE NOT RESPONSIBLE FOR: Code style, naming conventions, documentation quality, -performance optimization, test coverage, security vulnerabilities, or architecture -decisions. Those belong to other agents. Stay in your lane. - -CHECKLIST - Check each item against the code: - -For all languages: -- [ ] Shared mutable state: Are references copied before mutation? Are slices/arrays - cloned before passing to goroutines/threads/async functions? -- [ ] Error paths: Do all error returns clean up resources (close files, release locks, - cancel contexts)? Are errors propagated correctly (not silently swallowed)? -- [ ] Logic errors: Are conditions correct (not inverted)? Are loops bounded? - Are edge cases handled (empty input, single element, max values)? -- [ ] Null/nil safety: Can any dereference panic? Are optional values checked - before use? Are map lookups verified? -- [ ] Resource lifecycle: Are all opened resources (files, connections, channels) - properly closed? In the right order? In defer/finally blocks? -- [ ] Concurrency: Are shared variables protected? Can race conditions occur? - Are channels properly drained on cancellation? -- [ ] Last-iteration behavior: In loops with retries, attempts, or pagination, - does the final iteration behave correctly? Common bugs: sleeping after the - last attempt, returning a generic error instead of the original, off-by-one - in attempt counting, unnecessary work on the last pass. -- [ ] Boundary correctness: Does the code match the spec's exact boundaries? - If the spec says "retry on 502/503/504", does the code retry exactly - those, not all 5xx? If the spec says "max 3 attempts", is it 3 not 4? - -For Go specifically: -- [ ] Slice append in loops: Does `append` modify a shared backing array? -- [ ] Goroutine variable capture: Are loop variables captured by value, not reference? -- [ ] Context cancellation: Is context.Cancel() called in defer? -- [ ] Error wrapping: Are errors wrapped with %w for proper unwrapping? - -For Python specifically: -- [ ] Mutable default arguments: Are lists/dicts used as default parameters? -- [ ] Iterator exhaustion: Are generators consumed only once when multiple reads needed? -- [ ] Exception handling: Are bare `except:` clauses catching too broadly? - -For JavaScript/TypeScript specifically: -- [ ] Async/await: Are promises properly awaited? Can unhandled rejections occur? -- [ ] Closure variable capture: Are `var` variables captured in closures inside loops? -- [ ] Type narrowing: After type guards, is the narrowed type used correctly? - -For Bash specifically: -- [ ] Unquoted variables: Can word splitting cause unexpected behavior? -- [ ] Exit codes: Are command failures checked? Is `set -e` or explicit checks used? -- [ ] Subshell variable scope: Are variables set in subshells expected in parent? -``` - -### Agent 2: Architecture & Idioms - -``` -You are the ARCHITECTURE & IDIOMS REVIEW AGENT. - -YOUR ROLE: You ARE responsible for finding structural issues, code smells, and -maintainability problems. -YOUR SCOPE: Dead code, unnecessary complexity, duplication that will diverge, -misleading naming, comment accuracy, abstraction problems, YAGNI violations. - -YOU ARE NOT RESPONSIBLE FOR: Bug detection, security analysis, performance profiling, -test coverage, or production operations concerns. Those belong to other agents. - -CHECKLIST - Check each item against the code: - -- [ ] Dead code: Are there functions, methods, variables, imports, or branches - that are never called/used? Are there commented-out code blocks that should - be deleted (git history preserves them)? -- [ ] Unnecessary complexity: Are there abstractions with only one implementation? - Interfaces with one implementor? Factories that construct one type? - Wrapper functions that add no value? -- [ ] Duplication: Is there copy-pasted code that will diverge as the codebase - evolves? (Three similar lines are fine; three similar 20-line blocks are not.) - Note: intentional duplication for clarity is acceptable. Flag only duplication - that will become a maintenance burden. -- [ ] Misleading naming: Do function/variable names accurately describe what they - do? Are there names that suggest one behavior but implement another? - Are boolean variables named as questions (isReady, hasPermission)? -- [ ] Comment accuracy: Do comments match the code they describe? Are there - TODO/FIXME comments that should be addressed or tracked? Are there comments - that explain "what" (redundant) instead of "why" (valuable)? -- [ ] Abstraction level: Are functions doing work at mixed abstraction levels - (high-level orchestration mixed with low-level byte manipulation)? -- [ ] YAGNI: Is there code written for hypothetical future requirements that - are not in the current spec? Speculative generality? -- [ ] Convention adherence: Does the new code follow the patterns established - in the existing codebase? Or does it introduce a new pattern where one - already exists? -- [ ] State machine completeness: For any state machine (circuit breaker, - retry state, connection lifecycle, etc.), are ALL transitions covered? - Check both success and failure paths. Every transition should be - observable (logged, metriced, or tested). Missing transitions on the - success path are a common blind spot. -- [ ] Observability completeness: If the spec requires specific metrics, - counters, or log entries, verify they are actually exposed. Check that - every metric the spec names has a corresponding implementation, not - just the ones on the error path. -``` - -### Agent 3: Security - -``` -You are the SECURITY REVIEW AGENT. - -YOUR ROLE: You ARE responsible for finding security vulnerabilities and unsafe patterns. -YOUR SCOPE: Input validation, injection risks, secret handling, authentication/ -authorization patterns, RBAC scope, CRD/CEL validation gaps, cryptographic misuse. - -YOU ARE NOT RESPONSIBLE FOR: Code correctness, architecture decisions, performance, -test quality, or code style. Those belong to other agents. - -CHECKLIST - Check each item against the code: - -- [ ] Input validation: Is all external input (user input, API parameters, file - content, environment variables) validated before use? Are validation rules - applied at the boundary, not deep in business logic? -- [ ] Injection: Can any user-controlled string reach SQL queries, shell commands, - template rendering, or HTML output without sanitization? Check for string - concatenation in queries/commands. -- [ ] Secret handling: Are secrets (API keys, passwords, tokens) hardcoded in - source? Are they logged? Exposed in error messages? Stored in plaintext? - Committed to version control? -- [ ] Authentication: Are auth checks present on all protected endpoints/operations? - Can any operation bypass auth by manipulating parameters or headers? -- [ ] Authorization: After auth, are permission checks correct? Can a user access - resources belonging to another user? Are RBAC roles properly scoped? -- [ ] Path traversal: Can user input manipulate file paths to access files outside - intended directories? Are relative paths (../) blocked? -- [ ] Deserialization: Is untrusted data deserialized without validation? Can - deserialization trigger code execution? -- [ ] Rate limiting: Are endpoints that accept user input protected against abuse? - Login endpoints? API endpoints? File upload endpoints? - -For Kubernetes/Operator code specifically: -- [ ] CRD validation: Are all user-provided fields in Custom Resources validated - via CEL expressions or webhook validation? Can a malicious CR crash the - operator or escalate privileges? -- [ ] RBAC scope: Are operator permissions minimal? Does the operator request - cluster-wide permissions when namespace-scoped would suffice? -- [ ] Webhook security: Are admission webhooks configured with proper failure - policies? Can webhook bypass allow invalid resources? - -For web applications specifically: -- [ ] XSS: Is user content HTML-escaped before rendering? Are CSP headers set? -- [ ] CSRF: Are state-changing operations protected with CSRF tokens? -- [ ] CORS: Are allowed origins properly restricted? -``` - -### Agent 4: Production Readiness - -``` -You are the PRODUCTION READINESS REVIEW AGENT. - -YOUR ROLE: You ARE responsible for finding issues that would cause problems in -production environments. -YOUR SCOPE: Performance implications, resource leaks, concurrency issues, memory -patterns, operator patterns, observability gaps, graceful shutdown. - -YOU ARE NOT RESPONSIBLE FOR: Functional correctness, security vulnerabilities, -code style, test coverage, or architecture decisions. Those belong to other agents. - -CHECKLIST - Check each item against the code: - -- [ ] Resource leaks: Are goroutines/threads properly terminated on shutdown? - Are channels closed? Are database connections returned to pools? - Are HTTP response bodies closed after reading? -- [ ] Unbounded growth: Are there maps, slices, channels, or queues that can - grow without limit? Is there backpressure or eviction? Can a burst of - input exhaust memory? -- [ ] Concurrency safety: Are critical sections (mutex-protected regions) - kept small? Can deadlocks occur from lock ordering? Are there - time-of-check-time-of-use (TOCTOU) races? -- [ ] Error amplification: Can a single failure cascade into widespread outage? - Are circuit breakers or retry limits in place? Is there exponential backoff - for retries? -- [ ] Graceful shutdown: Does the service handle SIGTERM properly? Are in-flight - requests completed before exit? Are background workers stopped cleanly? -- [ ] Observability: Are critical operations logged with structured context? - Are errors logged with enough detail to diagnose without reproducing? - Are metrics exposed for key operations (latency, error rate, queue depth)? - -For Go specifically: -- [ ] Goroutine leaks: Can goroutines outlive their parent context? Are they - always cancelled/terminated? Use `runtime.NumGoroutine()` awareness. -- [ ] Channel patterns: Are unbuffered channels used where buffered would prevent - blocking? Are channels properly drained on context cancellation? -- [ ] Slice retention: Are large slices retained in memory because a small sub-slice - still references the backing array? Use `copy()` to release. -- [ ] sync.Pool misuse: Are pooled objects properly reset before returning to pool? - Can pool objects leak state between uses? - -For Kubernetes Operator code specifically: -- [ ] Reconciler concurrency: Is MaxConcurrentReconciles set appropriately? - Can concurrent reconciles conflict on the same resource? -- [ ] Work queue depth: Can the work queue grow unbounded under load? - Is rate limiting configured? -- [ ] Status update storms: Can status updates trigger re-reconciliation loops? - Are status updates debounced or conditional? -- [ ] Finalizer safety: Are finalizers removed only after cleanup is verified? - Can a stuck finalizer block resource deletion indefinitely? -``` - -### Agent 5: Test Quality - -``` -You are the TEST QUALITY REVIEW AGENT. - -YOUR ROLE: You ARE responsible for evaluating test effectiveness and finding -testing gaps. -YOUR SCOPE: Coverage gaps, weak assertions, tests passing for wrong reasons, -missing edge case tests, missing regression tests, test isolation. - -YOU ARE NOT RESPONSIBLE FOR: Code correctness, security, performance, architecture, -or code style. Those belong to other agents. - -IMPORTANT: Do NOT trust test names. Read the actual assertions. A test named -"TestUserCreation" that only checks the HTTP status code is not testing user creation. - -CHECKLIST - Check each item against the code: - -- [ ] Coverage gaps: Are there code paths with no test coverage? Focus on: - error paths, edge cases, boundary conditions, and branching logic. - Look for functions/methods that have no corresponding test. -- [ ] Weak assertions: Do tests actually verify the expected behavior? - Watch for: checking only status codes (not response bodies), checking - only that "no error occurred" (not what the result contains), checking - only array length (not array contents). -- [ ] Wrong-reason passes: Can any test pass even if the code is broken? - Tests that mock too aggressively, tests that check implementation details - rather than behavior, tests that verify the test setup rather than the - code under test. -- [ ] Empty test stubs: Are there test functions with no assertions, only - setup code, or just `t.Skip()`/`t.Pending()`? These give false coverage - and hide untested code paths. An empty test is worse than no test. -- [ ] Missing edge cases: Based on the implementation, what edge cases should - be tested? Empty input, nil/null values, maximum values, concurrent - access, timeout scenarios, malformed input. -- [ ] Missing regression tests: If the code fixes a specific bug or addresses - a specific requirement, is there a test that would catch a regression? -- [ ] Test isolation: Do tests depend on each other's execution order? - Do tests share mutable state? Can running tests in parallel cause - flaky failures? -- [ ] Test naming: Do test names describe the scenario being tested, or are - they generic (Test1, Test2, TestHandler)? -- [ ] Fixture management: Are test fixtures (setup/teardown) clean? Can - leftover state from a failed test affect subsequent tests? -``` - ---- - -## Hint Injection - -When the user provides hint text via `/speckit-spex-gates-review-code `, append this section to each agent's prompt: - -``` -## Additional Review Focus (User Hint) - -The user has requested special attention to: "" - -Apply this focus IN ADDITION TO your standard checklist. Do not replace your -standard checks. Instead, weight findings related to this area more heavily -and look for issues you might otherwise consider borderline. -``` - ---- - -## Progress Reporting - -Throughout the review, output progress updates to keep the user informed: - -``` -Stage 1: Spec compliance... [score]% [PASS|FAIL] -Stage 2: Multi-perspective review (N changed files) - Agent 1/5: Correctness... done, N findings - Agent 2/5: Architecture & Idioms... done, N findings - Agent 3/5: Security... done, N findings - Agent 4/5: Production Readiness... done, N findings - Agent 5/5: Test Quality... done, N findings - [CodeRabbit... done, N findings] (if available) - [Copilot... done, N findings] (if available) - -Merging findings: N total, N after dedup (N Critical, N Important, N Minor) -[Fix round 1/3: addressing N Critical + N Important findings...] -[Fix round 1/3: applied N fixes, re-reviewing...] -Gate: PASS|FAIL -``` - -In parallel mode (teams extension), agents complete in non-deterministic order. Report each as it finishes. - ---- - -## Gate Behavior - -The gate outcome depends on the invocation context: - -**Superpowers context** (triggered as quality gate from `/speckit-implement`): -- **PASS**: Allow proceeding to `speckit-spex-finish` -- **FAIL**: Block completion. The user must resolve remaining findings before the implementation can proceed. - -**Manual context** (user runs `/speckit-spex-gates-review-code` directly): -- **PASS** or **FAIL**: Advisory only. Report findings and let the user decide. Do NOT block further commands. - -The invocation context is determined by the caller. When invoked from the superpowers quality gate in `speckit-implement`, the context is `superpowers`. When invoked directly, the context is `manual`. - -## Update Flow State - -**MANDATORY: Update flow state.** This MUST run after deep review completes (regardless of gate outcome). Deep review completing means the code review phase is done, even if findings remain. Use the flow state script: - -```bash -FLOW_STATE="$(find ~/.claude -name 'spex-flow-state.sh' 2>/dev/null | head -1)" && [ -x "$FLOW_STATE" ] && "$FLOW_STATE" gate review-code && "$FLOW_STATE" implemented -``` - -This ensures the status line shows `R ✓` after deep review finishes, since review-code delegates to deep review and its own final state update may not execute. - -## Next Steps (tell the user) - -After deep review passes, tell the user: - -``` -Deep review complete. To close out this feature: - 1. /clear (free context for final gate) - 2. /speckit-spex-finish (verify + merge/PR, all-in-one) -``` - -This prompt is mandatory on every PASS exit. The user needs to know how to finalize. diff --git a/.specify/extensions/spex-deep-review/config-template.yml b/.specify/extensions/spex-deep-review/config-template.yml deleted file mode 100644 index 1f2a96cc..00000000 --- a/.specify/extensions/spex-deep-review/config-template.yml +++ /dev/null @@ -1,4 +0,0 @@ -# Deep Review Extension Configuration -external_tools: - coderabbit: true - copilot: false diff --git a/.specify/extensions/spex-deep-review/extension.yml b/.specify/extensions/spex-deep-review/extension.yml deleted file mode 100644 index 21bc66b5..00000000 --- a/.specify/extensions/spex-deep-review/extension.yml +++ /dev/null @@ -1,39 +0,0 @@ -schema_version: "1.0" - -extension: - id: spex-deep-review - name: "Spex Deep Review" - version: "1.0.0" - description: "Multi-perspective code review with 5 specialized agents and autonomous fix loop" - author: cc-spex - license: MIT - -requires: - speckit_version: ">=0.5.2" - extensions: - - id: spex-gates - version: ">=1.0.0" - -provides: - commands: - - name: speckit.spex-deep-review.run - file: commands/speckit.spex-deep-review.run.md - description: "Multi-perspective code review with autonomous fix loop" - - config: - - name: "deep-review-config.yml" - template: "config-template.yml" - description: "Deep review extension configuration" - required: false - -hooks: - after_implement: - command: speckit.spex-deep-review.run - optional: true - prompt: "Run deep multi-perspective review?" - description: "Multi-agent code review after implementation" - -tags: - - "spex" - - "review" - - "agents" diff --git a/.specify/extensions/spex-gates/commands/speckit.spex-gates.review-code.md b/.specify/extensions/spex-gates/commands/speckit.spex-gates.review-code.md deleted file mode 100644 index af8de16d..00000000 --- a/.specify/extensions/spex-gates/commands/speckit.spex-gates.review-code.md +++ /dev/null @@ -1,419 +0,0 @@ ---- -description: "Review code against spec compliance with deviation tracking and evolution triggers" ---- - -# Code Review Against Specification - -## Ship Pipeline Guard - -If `.specify/.spex-state` exists and its `status` is `running`, this command is part of an autonomous pipeline. Check the `ask` field: -- If `ask` is `"smart"` or `"never"`: suppress all user prompts (do NOT use AskUserQuestion), complete the review autonomously, and return immediately so the pipeline can advance. -- If `ask` is `"always"`: prompt the user as normal. - -```bash -if [ -f ".specify/.spex-state" ]; then - STATUS=$(jq -r '.status // empty' .specify/.spex-state 2>/dev/null) - ASK=$(jq -r '.ask // "always"' .specify/.spex-state 2>/dev/null) - if [ "$STATUS" = "running" ] && [ "$ASK" != "always" ]; then - echo "AUTONOMOUS_MODE=true" - else - echo "AUTONOMOUS_MODE=false" - fi -else - echo "AUTONOMOUS_MODE=false" -fi -``` - -In autonomous mode: do NOT output a completion summary, do NOT ask "Shall I proceed?", do NOT suggest next steps. Complete the review and return. - -## Flow Status Update (before review starts) - -If review-code is running, implementation is by definition done. Mark it immediately so the status line shows `impl ✓` during the review: - -```bash -FLOW_STATE="$(find ~/.claude -name 'spex-flow-state.sh' 2>/dev/null | head -1)" && [ -x "$FLOW_STATE" ] && "$FLOW_STATE" implemented && "$FLOW_STATE" running review-code -``` - -## IMPORTANT: Deep Review Extension Check - -**Before starting any review work**, check if the `spex-deep-review` extension is enabled: - -```bash -# Check via extensions registry -jq -r '.extensions["spex-deep-review"].enabled // false' .specify/extensions/.registry 2>/dev/null -``` - -If deep review is enabled, this command MUST invoke `speckit.spex-deep-review.review` after spec compliance passes (>= 95%). Do NOT produce only a basic compliance review when deep-review is active. The deep review dispatches 5 specialized agents, runs a fix loop, and generates a Deep Review Report. See step 9a below for details. - -## Overview - -Review code implementation against specification to ensure compliance. - -**Key Difference from Standard Code Review:** -- Primary focus: **Does code match spec?** -- Secondary focus: Code quality, patterns, best practices -- Output: **Compliance score** + deviation list -- Triggers: **Spec evolution** if mismatches found - -## When to Use - -- After implementation complete (called via spex-gates hook on after_implement) -- Before merging/deploying code -- When validating existing code against spec -- As part of verification workflow - -## Spec Selection - -If a spec path is provided as an argument, use it directly. - -Otherwise, attempt branch-based resolution: - -```bash -.specify/scripts/bash/check-prerequisites.sh --json --paths-only 2>/dev/null -``` - -If this succeeds (outputs JSON with `FEATURE_SPEC`), use the resolved spec path. Parse the JSON to extract `FEATURE_SPEC` and `FEATURE_DIR`. - -If this fails (not on a feature branch, no matching spec directory), fall back to interactive selection: - -```bash -find specs/ -name "spec.md" -type f 2>/dev/null | head -20 -``` - -**If specs found:** Present list and ask user to select one using AskUserQuestion (skip in autonomous mode). - -**If no specs found:** Inform user: -``` -No specs found in specs/ directory. - -Code review against spec requires a spec to compare against. -Use `speckit-spex-brainstorm` or `/speckit-specify` to create one first. -``` - -## The Process - -### 1. Load Spec and Code - -**Read specification:** -```bash -cat specs/features/[feature-name].md -``` - -**Identify implementation files:** -```bash -# From implementation plan or code exploration -ls -la [implementation-files] -``` - -### 2. Review Functional Requirements - -**For each functional requirement in spec:** - -1. **Find implementation** in code -2. **Compare behavior**: Does code do what spec says? -3. **Check completeness**: All aspects implemented? -4. **Note deviations**: Any differences? - -**Create compliance matrix:** -``` -Requirement 1: [Spec text] - Implementation: [file:line] - Status: Compliant | Deviation | Missing - Notes: [If deviation, explain] - -Requirement 2: [Spec text] - ... -``` - -### 3. Review Error Handling - -**For each error case in spec:** - -1. **Find error handling** in code -2. **Check error response**: Matches spec? -3. **Verify error codes**: Correct HTTP status / error codes? -4. **Test error messages**: Clear and helpful? - -**Error handling compliance:** -``` -Error Case 1: [From spec] - Implemented: Yes/No - Location: [file:line] - Response: [What code returns] - Spec Expected: [What spec says] - Status: Compliant / Deviation -``` - -### 4. Review Edge Cases - -**For each edge case in spec:** - -1. **Find handling** in code -2. **Check behavior**: Matches spec? -3. **Verify tests**: Edge case tested? - -### 5. Check for Extra Features - -**Identify code features NOT in spec:** - -- Functions/endpoints not mentioned in spec -- Behavior beyond spec requirements -- Additional error handling -- Extra validations - -**For each extra feature:** -- Document what it does -- Assess: Helpful addition or scope creep? -- Note for potential spec update - -### 6. Calculate Compliance Score - -**Formula:** -``` -Compliance % = (Compliant Requirements / Total Requirements) x 100 -``` - -**Include:** -- Functional requirements -- Error cases -- Edge cases -- Non-functional requirements - -### 7. Generate Report - -**Report structure:** - -```markdown -# Code Review: [Feature Name] - -**Spec:** specs/features/[feature].md -**Date:** YYYY-MM-DD -**Reviewer:** Claude (speckit.spex-gates.review-code) - -## Compliance Summary - -**Overall Score: XX%** - -- Functional Requirements: X/X (XX%) -- Error Handling: X/X (XX%) -- Edge Cases: X/X (XX%) -- Non-Functional: X/X (XX%) - -## Detailed Review - -### Functional Requirements - -#### Requirement 1: [Spec text] -**Implementation:** src/[file]:line -**Status:** Compliant -**Notes:** Correctly implemented as specified - -#### Requirement 2: [Spec text] -**Implementation:** src/[file]:line -**Status:** Deviation -**Issue:** [What differs from spec] -**Impact:** [Minor/Major] -**Recommendation:** [Update spec / Fix code] - -### Error Handling - -[Similar format for each error case] - -### Edge Cases - -[Similar format for each edge case] - -### Extra Features (Not in Spec) - -#### [Feature name] -**Location:** src/[file]:line -**Description:** [What it does] -**Assessment:** [Helpful / Scope creep] -**Recommendation:** [Add to spec / Remove] - -## Code Quality Notes - -[Secondary observations about code quality, patterns, etc.] - -## Recommendations - -### Critical (Must Fix) -- [ ] [Issue requiring immediate attention] - -### Spec Evolution Candidates -- [ ] [Deviation that might warrant spec update] - -### Optional Improvements -- [ ] [Nice-to-have suggestions] - -## Conclusion - -[Overall assessment] - -**Next Steps:** -- If compliance < 100%: Use `speckit-spex-evolve` to reconcile deviations -- If compliance = 100%: Proceed to verification -``` - -### 8. Deep Review Enhancement (if extension enabled) - -**First, parse flags from the invocation arguments:** - -When this command is invoked with arguments, extract flags before treating the remainder as hint text: - -- `--no-external`: disable all external tools -- `--no-coderabbit`: disable CodeRabbit only -- `--no-copilot`: disable Copilot only -- `--external`: enable all external tools -- `--coderabbit`: enable CodeRabbit only -- `--copilot`: enable Copilot only - -Flags are consumed and removed from the argument string. The remaining text (if any) becomes the hint text. - -**Resolve external tool settings (defaults + flag overrides):** - -```bash -# 1. Read defaults from deep-review extension config (all default to true if key is missing) -DEEP_REVIEW_CONFIG=".specify/extensions/spex-deep-review/deep-review-config.yml" -DEFAULT_CODERABBIT=$(yq -r '.external_tools.coderabbit // true' "$DEEP_REVIEW_CONFIG" 2>/dev/null) -DEFAULT_COPILOT=$(yq -r '.external_tools.copilot // true' "$DEEP_REVIEW_CONFIG" 2>/dev/null) - -# 2. If config file is missing, default all tools to true -``` - -``` -Resolution logic: - -1. Start with config defaults: - coderabbit = DEFAULT_CODERABBIT - copilot = DEFAULT_COPILOT - -2. Apply flag overrides (flags always win over defaults): - --external -> coderabbit = true, copilot = true - --no-external -> coderabbit = false, copilot = false - --coderabbit -> coderabbit = true - --no-coderabbit -> coderabbit = false - --copilot -> copilot = true - --no-copilot -> copilot = false - -3. Flags are applied in order. Later flags override earlier ones: - --external --no-copilot -> coderabbit = true, copilot = false - --no-external --coderabbit -> coderabbit = true, copilot = false -``` - -**After spec compliance is calculated, check for deep review:** - -**If deep review is enabled AND spec compliance >= 95% (or no spec exists):** -- Invoke `speckit.spex-deep-review.review` with: - - Stage 1 compliance score (or null if no spec) - - Invocation context: `quality-gate` if called from hook, `manual` if called directly - - Hint text: remaining argument text after flag extraction - - External tool settings: `{coderabbit: true/false, copilot: true/false}` (resolved from defaults + flags) - - Spec path and feature directory -- Wait for deep review to complete before proceeding -- Deep review includes a post-fix spec compliance check (Step 7b) that catches requirements dropped during the fix loop. If deep review reports dropped requirements, treat them as Critical findings that must be resolved before proceeding. - -**If deep review is enabled AND spec compliance < 95%:** -- Do NOT invoke deep review -- Report the compliance score and non-compliant requirements -- Instruct the user to fix spec compliance issues first - -**If deep review is NOT enabled:** -- Continue with standard review behavior (steps 9b below) - -### 9b. Trigger Evolution if Needed - -**If deviations found (standard review path, no deep-review):** -- Present review results to user -- Recommend using `speckit-spex-evolve` -- Don't proceed to verification until resolved - -**If 100% compliant (standard review path):** -- Approve for verification -- Proceed to `speckit.spex.finish` - -## Assessment Criteria - -### Compliant -- Code does exactly what spec says -- No deviations in behavior -- All aspects covered - -### Minor Deviation -- Small differences (naming, details) -- Non-breaking additions -- Better error messages than spec -- Typically: Update spec - -### Major Deviation -- Different behavior than spec -- Missing functionality -- Wrong error handling -- Typically: Fix code or evolve spec - -### Missing -- Spec requires it, code doesn't have it -- Critical gap -- Must fix code - -## Anti-Rationalization: What You Must NOT Do - -**DO NOT skip checking ANY requirement.** Each spec requirement must be verified against code. Not "spot checking." Not "seems fine." Every. Single. One. - -**DO NOT assume compliance.** "It looks right" is not compliance. "I think it matches" is not compliance. Show the code location. Compare the behavior. Document the status. - -**DO NOT hide deviations.** A deviation is not a failure; it's information. Hiding deviations breaks the feedback loop. Report every deviation, even minor ones. - -**DO NOT proceed with deviations unresolved.** 89% compliance is NOT ready for verification. 99% compliance is NOT ready for verification. Only 100% compliance proceeds to verification. - -**DO NOT rationalize scope creep.** "But this feature is useful!" is not justification for unspecified code. Either add it to the spec (via evolution) or remove it. Undocumented features are invisible bugs. - -**DO NOT conflate code quality with spec compliance.** Code can be beautiful AND non-compliant. Code can be ugly AND compliant. Check both, report both, but never confuse them. - -## Remember - -**Spec compliance is primary concern.** - -This is not just code quality review; it's **spec validation**. - -- Does code match spec? (Most important) -- Is code quality good? (Secondary) -- Any improvements? (Tertiary) - -**100% compliance is the goal.** - -- < 90%: Significant issues, fix before proceeding -- 90-99%: Minor deviations, likely spec updates -- 100%: Perfect compliance, ready for verification - -**Deviations trigger evolution.** - -- Don't force-fit wrong spec -- Don't ignore deviations -- Use `speckit-spex-evolve` to reconcile - -**The code and spec must tell the same story.** - -**Evidence before assertions. Always.** - -## Update Flow State - -**MANDATORY: Update flow state.** This MUST run on every exit path. Use the flow state script: - -```bash -FLOW_STATE="$(find ~/.claude -name 'spex-flow-state.sh' 2>/dev/null | head -1)" && [ -x "$FLOW_STATE" ] && "$FLOW_STATE" gate review-code && "$FLOW_STATE" implemented -``` - -This updates the status line to show both `impl ✓` and `R ✓`. If code review passed, implementation is by definition complete. - -## Next Steps (tell the user) - -After code review passes, tell the user: - -``` -Code review complete. To close out this feature: - 1. /clear (free context for final gate) - 2. /speckit-spex-finish (verify + merge/PR, all-in-one) -``` - -This prompt is mandatory on every PASS exit. The user needs to know how to finalize. diff --git a/.specify/extensions/spex-gates/commands/speckit.spex-gates.review-plan.md b/.specify/extensions/spex-gates/commands/speckit.spex-gates.review-plan.md deleted file mode 100644 index ac3d3aa5..00000000 --- a/.specify/extensions/spex-gates/commands/speckit.spex-gates.review-plan.md +++ /dev/null @@ -1,202 +0,0 @@ ---- -description: "Post-planning quality validation with coverage matrix, red flag scanning, and task quality enforcement" ---- - -# Post-Planning Quality Validation - -## Ship Pipeline Guard - -If `.specify/.spex-state` exists and its `status` is `running`, this command is part of an autonomous pipeline. Check the `ask` field: -- If `ask` is `"smart"` or `"never"`: suppress all user prompts (do NOT use AskUserQuestion), complete the review autonomously, and return immediately so the pipeline can advance. -- If `ask` is `"always"`: prompt the user as normal. - -```bash -if [ -f ".specify/.spex-state" ]; then - STATUS=$(jq -r '.status // empty' .specify/.spex-state 2>/dev/null) - ASK=$(jq -r '.ask // "always"' .specify/.spex-state 2>/dev/null) - if [ "$STATUS" = "running" ] && [ "$ASK" != "always" ]; then - echo "AUTONOMOUS_MODE=true" - else - echo "AUTONOMOUS_MODE=false" - fi -else - echo "AUTONOMOUS_MODE=false" -fi -``` - -In autonomous mode: do NOT output a completion summary, do NOT ask "Shall I proceed?", do NOT suggest next steps. Complete the review and return. - -## Overview - -This skill validates plan and task quality after `/speckit-plan` and `/speckit-tasks` have run. It checks coverage, scans for red flags, and enforces task quality standards. - -## Prerequisites - -Spec-kit must be initialized. If `.specify/` directory does not exist, tell the user to run `/spex:init` first and stop. - -**Both plan.md and tasks.md MUST exist before running this skill.** If either is missing, stop with an error: - -```bash -SPEC_DIR="specs/[feature-name]" -[ -f "$SPEC_DIR/plan.md" ] && echo "plan.md found" || echo "ERROR: plan.md missing - run /speckit-plan first" -[ -f "$SPEC_DIR/tasks.md" ] && echo "tasks.md found" || echo "ERROR: tasks.md missing - run /speckit-tasks first" -``` - -If either file is missing, stop and instruct the user to generate the missing artifact. - -## 0. Scope Check - -Before detailed validation, check whether the plan attempts to cover multiple independent subsystems in a single document. Indicators: - -- Tasks span subsystems with no shared interfaces or dependencies -- The plan has distinct groups of tasks that could each produce working software independently -- File changes cluster into unrelated areas of the codebase - -If the plan covers multiple independent subsystems, flag it: "This plan may benefit from being split into separate plans, one per subsystem. Each plan should produce working, testable software on its own." - -This is advisory, not blocking. Some plans legitimately span subsystems. - -## 1. Task Quality Enforcement - -After tasks.md exists, verify every task meets these criteria: - -- **Actionable**: Clear what to do (not "figure out..." or "investigate...") -- **Testable**: Can verify completion objectively -- **Atomic**: One clear outcome per task -- **Ordered**: Dependencies between tasks are respected, phases are sequenced correctly - -Also check: -- Every task specifies concrete file paths (not "somewhere" or "TBD") -- Phase ordering is logical (setup before core, tests before integration) -- No tasks duplicate work already covered by other tasks - -Verify the plan includes a file structure mapping: -- Files to be created or modified are listed with their responsibilities -- Each file has one clear responsibility (not vague "utils" or "helpers" without defined scope) -- Design units have clear boundaries and well-defined interfaces -- In existing codebases, the plan follows established patterns rather than unilaterally restructuring - -If the plan lacks a file structure mapping, note it as a gap: tasks without a file map are harder to verify for completeness and overlap. - -If tasks fail these checks, note the issues and suggest refinements. - -## 2. Coverage Matrix - -Produce a coverage matrix mapping every spec requirement to its implementing tasks: - -``` -Requirement 1 -> Tasks [X,Y] -Requirement 2 -> Tasks [Z] -NFR 1 -> Tasks [W] -... -``` - -Flag any requirement without task coverage. All requirements must have at least one implementing task. - -Also verify: -- Every error case in the spec has a handling approach -- Every edge case from the spec is addressed -- Success criteria have verification approaches - -## 3. Red Flag Scanning - -Search plan.md and tasks.md for vague or incomplete language: - -```bash -SPEC_DIR="specs/[feature-name]" -rg -i "figure out|tbd|todo|implement later|somehow|somewhere|not sure|maybe|probably|add appropriate|add validation|handle edge cases|similar to task" "$SPEC_DIR/plan.md" "$SPEC_DIR/tasks.md" || echo "No red flags found" -``` - -Review any matches: -- "Figure out..." = missing research, needs concrete approach -- "TBD" / "TODO" = incomplete planning, must be resolved -- "Implement later" = deferred work, scope explicitly -- "Add appropriate error handling" / "add validation" / "handle edge cases" = vague placeholders, must show actual code -- "Write tests for the above" (without actual test code) = test code must be included -- "Similar to Task N" = repeat the code, the engineer may read tasks out of order -- Steps that describe what to do without showing how = code blocks required for code steps -- Missing file paths = tasks are not actionable - -## 4. Type and Name Consistency - -Check that types, method signatures, property names, and function names used across tasks are consistent: - -- If a function is called `clearLayers()` in Task 3, it must not be called `clearFullLayers()` in Task 7 -- If a type is defined in an early task, later tasks must reference the same type name -- If a constant or config key is introduced, verify spelling is consistent across all tasks -- If an API endpoint path is defined, verify all references use the same path - -Inconsistencies between tasks are plan bugs that will become code bugs during implementation. - -## 5. NFR Validation - -For each non-functional requirement in the spec, verify the plan includes: -- A concrete measurement method (not just "should be fast") -- A validation approach (how will you verify the NFR is met?) -- Acceptance thresholds where applicable - -If any NFR lacks a measurement method, flag it. - -## 6. Present Results - -Report to the user: -- Task quality check results (pass/issues) -- Coverage matrix summary -- Red flag scan results -- NFR validation results - -## 7. Offer Remediation - -After presenting results, collect ALL findings from steps 0-4 into a numbered list. Include both blocking and non-blocking issues. Present them as a consolidated findings summary: - -``` -Findings: - - 1. [BLOCKING] Task T003 is not actionable: "figure out auth approach" - 2. [advisory] Plan may benefit from splitting (2 independent subsystems) - 3. [gap] FR-007 has no implementing task in the coverage matrix - 4. [red-flag] tasks.md line 42: "TBD" placeholder - 5. [nfr] NFR-002 "response time < 200ms" has no measurement method -``` - -Then ask the user how to proceed (skip in autonomous mode, default to "Fix all"): - -Use AskUserQuestion with: -- header: "Findings" -- multiSelect: false -- Options: - - "Fix all": "Address every finding automatically" - - "Let me pick": "Select specific findings to fix (you can add comments)" - - "Skip": "Proceed without changes" - -**If "Fix all"**: Apply fixes to plan.md and/or tasks.md for each finding, then re-run the relevant checks to confirm resolution. - -**If "Let me pick"**: Use AskUserQuestion with multiSelect: true, listing up to 4 findings as options (if more than 4, batch them across multiple rounds). Each option's label is the short finding (e.g., "#1 Task T003 not actionable") and the description is the detail. The user can select which to fix and use "Other" to add comments or instructions for specific findings. - -After the user selects findings, apply fixes to plan.md and/or tasks.md. For each selected finding: -1. Read the user's comment (if any) to understand their intent -2. Make the minimal targeted edit to resolve the finding -3. Report what was changed - -After all selected fixes are applied, re-present any remaining unaddressed findings as informational (no further prompting). - -**If "Skip"**: Proceed without changes. Note that blocking issues remain unresolved. - -## 8. Update Flow State - -**MANDATORY: Update flow state.** This MUST run on every exit path, including early returns (e.g., "already passed", "no findings"). Use the flow state script: - -```bash -FLOW_STATE="$(find ~/.claude -name 'spex-flow-state.sh' 2>/dev/null | head -1)" && [ -x "$FLOW_STATE" ] && "$FLOW_STATE" gate review-plan -``` - -This updates the status line to show `P ✓`. - -## Integration - -**This command is invoked by:** -- The spex-gates extension hook for `after_tasks` -- Users directly via `speckit.spex-gates.review-plan` - -**This command invokes:** -- Prerequisite check for `.specify/` directory diff --git a/.specify/extensions/spex-gates/commands/speckit.spex-gates.review-spec.md b/.specify/extensions/spex-gates/commands/speckit.spex-gates.review-spec.md deleted file mode 100644 index 701b9932..00000000 --- a/.specify/extensions/spex-gates/commands/speckit.spex-gates.review-spec.md +++ /dev/null @@ -1,331 +0,0 @@ ---- -description: "Review specifications for soundness, completeness, and implementability" ---- - -# Reviewing Specifications for Soundness - -## Ship Pipeline Guard - -If `.specify/.spex-state` exists and its `status` is `running`, this command is part of an autonomous pipeline. Check the `ask` field: -- If `ask` is `"smart"` or `"never"`: suppress all user prompts (do NOT use AskUserQuestion), complete the review autonomously, and return immediately so the pipeline can advance. -- If `ask` is `"always"`: prompt the user as normal. - -```bash -if [ -f ".specify/.spex-state" ]; then - STATUS=$(jq -r '.status // empty' .specify/.spex-state 2>/dev/null) - ASK=$(jq -r '.ask // "always"' .specify/.spex-state 2>/dev/null) - if [ "$STATUS" = "running" ] && [ "$ASK" != "always" ]; then - echo "AUTONOMOUS_MODE=true" - else - echo "AUTONOMOUS_MODE=false" - fi -else - echo "AUTONOMOUS_MODE=false" -fi -``` - -In autonomous mode: do NOT output a completion summary, do NOT ask "Shall I proceed?", do NOT suggest next steps. Complete the review and return. - -## Overview - -Validate specification quality before implementation begins. - -A poor spec leads to confusion, rework, and spec/code drift. A sound spec enables smooth implementation. - -This skill checks: completeness, clarity, implementability, and testability. - -## When to Use - -- After spec creation (before implementation) -- Before generating implementation plan -- When spec seems unclear or incomplete -- Periodically for important specs - -## Prerequisites - -Spec-kit must be initialized. If `.specify/` directory does not exist, tell the user to run `/spex:init` first and stop. - -## Spec Selection - -If a spec path is provided as an argument, use it directly. - -Otherwise, attempt branch-based resolution: - -```bash -.specify/scripts/bash/check-prerequisites.sh --json --paths-only 2>/dev/null -``` - -If this succeeds (outputs JSON with `FEATURE_SPEC`), use the resolved spec path. Parse the JSON to extract `FEATURE_SPEC` and `FEATURE_DIR`. - -If this fails (not on a feature branch, no matching spec directory), fall back to interactive selection: - -```bash -find specs/ -name "spec.md" -type f 2>/dev/null | head -20 -``` - -**If specs found:** Present list and ask user to select one using AskUserQuestion (skip in autonomous mode). - -**If no specs found:** Inform user: -``` -No specs found in specs/ directory. - -To create a spec first: -- Use `speckit-spex-brainstorm` to refine ideas into a spec -- Use `/speckit-specify` to create a spec from clear requirements - -Cannot review without a spec to review. -``` - -## spec-kit Integration - -This skill can use `/speckit-*` slash commands when available: - -- `/speckit-clarify` - Find underspecified areas in the spec -- `/speckit-analyze` - Cross-artifact consistency check (if plan/tasks exist) - -**If `/speckit-*` commands are available:** -Use them to assist with review, but always perform manual review as well. - -**If `/speckit-*` commands are not available:** -Proceed with manual review only. This is acceptable. - -## Review Dimensions - -### 1. Completeness -- All sections filled -- No TBD or placeholder text -- All requirements defined -- Success criteria specified - -### 2. Clarity -- No ambiguous language -- Concrete, specific requirements -- Edge cases explicitly defined -- Error handling specified - -### 3. Implementability -- Can generate implementation plan -- Dependencies identified -- Constraints realistic -- Scope manageable - -### 4. Testability -- Success criteria measurable -- Requirements verifiable -- Acceptance criteria clear - -## The Process - -### 1. Load and Read Spec - -**Read the spec:** - -```bash -cat specs/[feature-name]/spec.md -``` - -Read thoroughly, take notes on issues. - -### 2. Check Structure - -**Required sections (should exist):** -- [ ] Purpose/Overview -- [ ] Functional Requirements -- [ ] Success Criteria -- [ ] Error Handling - -**Recommended sections:** -- [ ] Non-Functional Requirements -- [ ] Edge Cases -- [ ] Dependencies -- [ ] Constraints -- [ ] Out of Scope - -**If sections missing:** -- Note which ones -- Assess if truly needed for this spec -- Recommend additions - -### 3. Review Completeness - -**For each section, check:** - -**Purpose:** -- [ ] Clearly states why feature exists -- [ ] Describes problem being solved -- [ ] Avoids implementation details - -**Functional Requirements:** -- [ ] Numbered/listed clearly -- [ ] Each requirement is specific -- [ ] No "TBD" or placeholders -- [ ] All aspects covered - -**Success Criteria:** -- [ ] Measurable outcomes defined -- [ ] Clear completion indicators -- [ ] Testable assertions - -**Error Handling:** -- [ ] All error cases identified -- [ ] Handling approach specified -- [ ] Error messages/codes defined - -**Edge Cases:** -- [ ] Boundary conditions listed -- [ ] Expected behavior specified -- [ ] Not marked as "TBD" - -### 4. Check for Ambiguities - -**Red flag words/phrases:** -- "should" (vs "must") -- "might", "could", "probably" -- "fast", "slow" (without metrics) -- "user-friendly" (vague) -- "handle appropriately" (non-specific) -- "etc." (incomplete list) -- "similar to..." (unclear) - -**For each ambiguity:** -- Identify the vague requirement -- Note what's unclear -- Suggest specific alternative - -### 5. Validate Implementability - -**Ask:** -- Can I generate an implementation plan from this? -- Are file locations/components identifiable? -- Are dependencies clear? -- Is scope reasonable? - -**Check for:** -- Unknown dependencies -- Unrealistic constraints -- Scope too large -- Conflicting requirements - -### 6. Assess Testability - -**For each requirement:** -- How will this be tested? -- Is the outcome verifiable? -- Can success be measured? - -**For success criteria:** -- Are they specific enough to test? -- Can they be automated? -- Are they objective (not subjective)? - -### 7. Check Against Constitution - -**If constitution exists:** - -```bash -if [ -f ".specify/memory/constitution.md" ]; then - cat .specify/memory/constitution.md -else - echo "no-constitution" -fi -``` - -**Validate:** -- Does spec follow project principles? -- Are patterns consistent? -- Does error handling match standards? -- Are architectural decisions aligned? - -**Note any violations with reasoning.** - -### 8. Run Cross-Artifact Consistency Check (Optional) - -**If plan or tasks exist and `/speckit-analyze` is available:** - -Invoke `/speckit-analyze` to check consistency between: -- spec.md (requirements) -- plan.md (implementation approach) -- tasks.md (task list) - -**Report any mismatches or gaps found.** - -### 9. Generate Review Report - -Output the review findings to the console. Do NOT write a `REVIEW-SPEC.md` file. All review information is presented directly in the conversation output. - -### 10. Make Recommendation - -**If sound (minor issues only):** -- Ready for implementation -- Proceed with `/speckit-implement` - -**If needs work (important issues):** -- Fix issues before implementing -- Update spec, re-review - -**If major issues:** -- Not ready for implementation -- Significant rework needed -- May need re-brainstorming - -## Review Checklist - -- [ ] Load and read spec thoroughly -- [ ] Check structure (all sections present) -- [ ] Review completeness (no TBD, all covered) -- [ ] Identify ambiguities (vague language) -- [ ] Validate implementability (can plan from this) -- [ ] Assess testability (can verify requirements) -- [ ] Check constitution alignment (if exists) -- [ ] Run `/speckit-analyze` for cross-artifact consistency (if available) -- [ ] Generate review report -- [ ] Make recommendation (ready/needs work/major issues) - -## Quality Standards - -**A sound spec has:** -- All sections complete -- No ambiguous language -- Specific, measurable requirements -- Identified dependencies -- Realistic constraints -- Clear error handling -- Defined edge cases -- Testable success criteria - -**A poor spec has:** -- Missing sections -- Vague language -- Unmeasurable requirements -- Unknown dependencies -- Unrealistic constraints -- Unclear error handling -- Ignored edge cases -- Subjective criteria - -## Remember - -**Reviewing specs saves time in implementation.** - -- 1 hour reviewing spec saves 10 hours debugging -- Ambiguities caught early prevent rework -- Complete specs enable smooth TDD -- Sound specs reduce spec/code drift - -**Be thorough but not pedantic:** -- Flag real issues, not nitpicks -- Focus on what blocks implementation -- Suggest specific improvements -- Balance perfection with pragmatism - -**The goal is implementability, not perfection.** - -## Update Flow State - -**MANDATORY: Update flow state.** This MUST run on every exit path, including early returns (e.g., "already passed"). Use the flow state script: - -```bash -FLOW_STATE="$(find ~/.claude -name 'spex-flow-state.sh' 2>/dev/null | head -1)" && [ -x "$FLOW_STATE" ] && "$FLOW_STATE" gate review-spec -``` - -This updates the status line to show `S ✓`. diff --git a/.specify/extensions/spex-gates/commands/speckit.spex-gates.stamp.md b/.specify/extensions/spex-gates/commands/speckit.spex-gates.stamp.md deleted file mode 100644 index 89add248..00000000 --- a/.specify/extensions/spex-gates/commands/speckit.spex-gates.stamp.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -description: "Final gate before completion - invokes verification for tests, code hygiene, spec compliance, and drift check" ---- - -# Stamp - Final Completion Gate - -## Ship Pipeline Guard - -If `.specify/.spex-state` exists and its `status` is `running`, this command is part of an autonomous pipeline. Check the `ask` field: -- If `ask` is `"smart"` or `"never"`: suppress all user prompts (do NOT use AskUserQuestion), complete the stamp autonomously, and return immediately so the pipeline can advance. -- If `ask` is `"always"`: prompt the user as normal. - -```bash -if [ -f ".specify/.spex-state" ]; then - STATUS=$(jq -r '.status // empty' .specify/.spex-state 2>/dev/null) - ASK=$(jq -r '.ask // "always"' .specify/.spex-state 2>/dev/null) - if [ "$STATUS" = "running" ] && [ "$ASK" != "always" ]; then - echo "AUTONOMOUS_MODE=true" - else - echo "AUTONOMOUS_MODE=false" - fi -else - echo "AUTONOMOUS_MODE=false" -fi -``` - -In autonomous mode: do NOT output a completion summary, do NOT ask "Shall I proceed?", do NOT suggest next steps. Complete the stamp and return. - -## Relationship to /speckit-spex-finish - -`/speckit-spex-stamp` runs verification only. For verification + merge/PR/cleanup in one step, use `/speckit-spex-finish` instead. - -## Purpose - -This is the final gate before claiming work is complete. It delegates to the full verification command. - -## Execution - -Invoke `speckit.spex-gates.verify` with any arguments passed to this command. - -The verify command runs all quality gates: -1. Full test suite -2. Code hygiene review -3. Spec compliance validation (100% required) -4. Spec drift check -5. Success criteria verification - -Only after all gates pass can work be claimed as complete. diff --git a/.specify/extensions/spex-gates/commands/speckit.spex-gates.verify.md b/.specify/extensions/spex-gates/commands/speckit.spex-gates.verify.md deleted file mode 100644 index 76f91144..00000000 --- a/.specify/extensions/spex-gates/commands/speckit.spex-gates.verify.md +++ /dev/null @@ -1,476 +0,0 @@ ---- -description: "Final verification gate: tests, code hygiene, spec compliance, and drift check" ---- - -# Verification Before Completion (Spec-Aware) - -## Ship Pipeline Guard - -If `.specify/.spex-state` exists and its `status` is `running`, this command is part of an autonomous pipeline. Check the `ask` field: -- If `ask` is `"smart"` or `"never"`: suppress all user prompts (do NOT use AskUserQuestion), complete the verification autonomously, and return immediately so the pipeline can advance. -- If `ask` is `"always"`: prompt the user as normal. - -```bash -if [ -f ".specify/.spex-state" ]; then - STATUS=$(jq -r '.status // empty' .specify/.spex-state 2>/dev/null) - ASK=$(jq -r '.ask // "always"' .specify/.spex-state 2>/dev/null) - if [ "$STATUS" = "running" ] && [ "$ASK" != "always" ]; then - echo "AUTONOMOUS_MODE=true" - else - echo "AUTONOMOUS_MODE=false" - fi -else - echo "AUTONOMOUS_MODE=false" -fi -``` - -In autonomous mode: do NOT output a completion summary, do NOT ask "Shall I proceed?", do NOT suggest next steps. Complete the verification and return. - -## Overview - -Claiming work is complete without verification is dishonesty, not efficiency. - -**Core principle:** Evidence before claims, always. - -Verify implementation is complete by running tests AND validating spec compliance. - -**Key Steps:** -- Step 1: Run tests (existing behavior) -- **Step 2: Code hygiene review** (mechanical defect detection) -- **Step 3: Validate spec compliance** (spec-driven) -- **Step 4: Check for spec drift** (spec-driven) -- Blocks completion if tests, code hygiene, OR spec compliance fails - -## The Iron Law - -``` -NO COMPLETION CLAIMS WITHOUT FRESH VERIFICATION EVIDENCE -``` - -If you haven't run the verification command in this message, you cannot claim it passes. - -## The Gate Function - -``` -BEFORE claiming any status or expressing satisfaction: - -1. IDENTIFY: What command proves this claim? -2. RUN: Execute the FULL command (fresh, complete) -3. READ: Full output, check exit code, count failures -4. VERIFY: Does output confirm the claim? - - If NO: State actual status with evidence - - If YES: State claim WITH evidence -5. CHECK SPEC: Does implementation match spec? - - If NO: State actual compliance with evidence - - If YES: State compliance score WITH evidence -6. ONLY THEN: Make the claim - -Skip any step = lying, not verifying -``` - -## When to Use - -- After implementation and code review -- Before claiming work is complete -- Before committing/merging/deploying -- As final gate in the implementation pipeline (via spex-gates hook) - -## Spec Selection - -If a spec path is provided as an argument, use it directly. - -Otherwise, attempt branch-based resolution: - -```bash -.specify/scripts/bash/check-prerequisites.sh --json --paths-only 2>/dev/null -``` - -If this succeeds (outputs JSON with `FEATURE_SPEC`), use the resolved spec path. Parse the JSON to extract `FEATURE_SPEC` and `FEATURE_DIR`. - -If this fails (not on a feature branch, no matching spec directory), fall back to interactive selection: - -```bash -find specs/ -name "spec.md" -type f 2>/dev/null | head -20 -``` - -**If specs found:** Present list and ask user to select one using AskUserQuestion (skip in autonomous mode). - -**If no specs found:** Inform user: -``` -No specs found in specs/ directory. - -Verification requires a spec to validate against. -Use `speckit-spex-brainstorm` or `/speckit-specify` to create one first. -``` - -## The Process - -### 1. Run Tests - -**Execute all tests:** -```bash -# Run full test suite -npm test # or pytest, go test, etc. -``` - -**Check results:** -- All tests passing? -- No flaky tests? -- Coverage adequate? - -**If tests fail:** -- STOP: Fix tests before proceeding -- Do not skip this step -- Do not claim completion - -### 2. Code Hygiene Review - -**Before checking spec compliance, review every changed file for mechanical defects. -These are craft-level issues that no spec describes but that cause real bugs.** - -**For each function you wrote or modified, check:** - -#### Dead Code and Logic -- [ ] Every conditional branch produces a **different** outcome (no branches that do the same thing) -- [ ] Every parameter is **actually used** to change behavior (no unused parameters) -- [ ] Every exported function has **at least one caller** outside tests (no orphaned API surface) -- [ ] No variables assigned but never read - -#### Copy and Mutation Safety -- [ ] When copying a data structure, verify whether **nested references are shared** -- [ ] If the copy is later mutated, confirm the original is **not affected** -- [ ] If a function receives a pointer/reference, verify whether it is expected to **mutate or read-only** - -#### Cleanup and Consistency -- [ ] When removing/disabling something, verify **all references** to it are also cleaned up (no orphaned entries, dangling references, stale indices) -- [ ] When adding to a collection, verify **duplicates are handled** (deduplicate or reject) -- [ ] When deriving a value from an identifier, verify the **derivation uses the correct source** (e.g., a display name vs an internal ID vs a type name are different things) - -#### Unnecessary Operations -- [ ] No sorting/ordering when the result doesn't depend on order (counting, summing, existence checks) -- [ ] No intermediate data structures that serve no purpose (building a list just to iterate it once) - -**If any issue found:** Fix before proceeding. These are not style nits; they are latent bugs. - -### 3. Validate Spec Compliance - -**Load spec:** -```bash -cat specs/features/[feature-name].md -``` - -**Check each requirement and emit a machine-readable compliance matrix:** - -For each functional requirement in the spec, determine its status. There are exactly two valid statuses: - -- **IMPLEMENTED**: Code fully implements the requirement. You can point to the function, the test, and the behavior. -- **MISSING**: Code does not implement the requirement, or implements it only partially. - -There is NO "PARTIAL" status. There is NO "COMPLIANT (with documented gap)" status. There is NO "IMPLEMENTED (known limitation)" status. If the code doesn't fully do what the spec says MUST happen, the status is MISSING. - -```markdown -Functional Requirement 1: [From spec] - Status: IMPLEMENTED | MISSING - Evidence: [file:line where it's implemented, or why it's missing] - -Functional Requirement 2: [From spec] - Status: IMPLEMENTED | MISSING - Evidence: [file:line where it's implemented, or why it's missing] -``` - -**After checking ALL requirements, emit a machine-readable gate result block:** - -``` -SPEC_COMPLIANCE_RESULT: - total: N - implemented: N - missing: N - percentage: XX% - gate: PASS | FAIL -``` - -The gate value is determined mechanically: if `missing > 0`, the gate is `FAIL`. No exceptions. No judgment calls. No "but it's a known gap." If the spec says MUST and the code doesn't, it's MISSING, and the gate is FAIL. - -**Anti-rationalization guardrails (read these before making your compliance decision):** - -You WILL be tempted to let partial compliance pass. You will find plausible reasons. All of them are wrong: - -| Rationalization | Why it's wrong | -|-----------------|---------------| -| "It's a known gap" | Known gaps are still gaps. MISSING. | -| "It's documented as future work" | Future work means not implemented. MISSING. | -| "There's a workaround" | Workarounds are not implementations. MISSING. | -| "The spec assumption section mentions this" | Assumptions don't override MUST requirements. MISSING. | -| "It's only partially implemented" | Partial is not complete. MISSING. | -| "The user knows about this" | User awareness doesn't change compliance. MISSING. | - -The only valid paths when the gate is FAIL: -1. Implement the missing requirement, then re-run verify -2. Run `/speckit-spex-evolve` to formally remove or defer the requirement from the spec, then re-run verify - -**If compliance = 100%:** Proceed to next step. -**If compliance < 100%:** STOP. Do not proceed. Report the MISSING requirements and suggest the two valid resolution paths above. - -### 4. Check for Spec Drift - -**Compare:** -- What spec says NOW -- What code does NOW -- Any divergence? - -**Common drift sources:** -- Spec updated but code not -- Code changed but spec not -- Undocumented additions -- Forgotten requirements - -**If drift detected:** -- Document each instance -- Use `speckit-spex-evolve` to reconcile -- Do not proceed with drift - -### 5. Verify Success Criteria - -**From spec, check each criterion:** - -```markdown -Success Criteria (from spec): -- [ ] Criterion 1: [Description] - Status: Met / Not met - Evidence: [How verified] - -- [ ] Criterion 2: [Description] - ... -``` - -**All criteria must be met.** - -If any criterion not met: -- STOP: Criterion not met -- Implement missing piece -- Re-verify - -### 6. Generate Verification Report - -**Report structure:** - -```markdown -# Verification Report: [Feature Name] - -**Date:** YYYY-MM-DD -**Spec:** specs/features/[feature].md - -## Test Results - -**Status:** PASS / FAIL - -[Test output] - -**Summary:** -- Total: X tests -- Passed: X -- Failed: X -- Coverage: XX% - -## Spec Compliance - -**Status:** COMPLIANT / NON-COMPLIANT - -**Compliance Score:** XX% - -### Requirements Status -- Functional: X/X (XX%) -- Error Cases: X/X (XX%) -- Edge Cases: X/X (XX%) -- Non-Functional: X/X (XX%) - -### Deviations -[List any deviations found] - -## Spec Drift Check - -**Status:** NO DRIFT / DRIFT DETECTED - -[Details if drift found] - -## Success Criteria - -**Status:** ALL MET / INCOMPLETE - -- [x] Criterion 1 -- [x] Criterion 2 -... - -## Overall Status - -**VERIFIED - Ready for completion** - -OR - -**NOT VERIFIED - Issues must be resolved** - -**Blocking Issues:** -- [Issue 1] -- [Issue 2] - -**Next Steps:** -[What needs to be done] -``` - -### 7. Make Go/No-Go Decision - -**All conditions must be true:** -- [x] All tests passing -- [x] Code hygiene review clean (no dead code, no mutation bugs, no orphans) -- [x] `SPEC_COMPLIANCE_RESULT.gate` is `PASS` (100% compliance, zero MISSING) -- [x] No spec drift -- [x] All success criteria met - -**If ALL true:** -- VERIFIED: Proceed to completion -- Safe to commit/merge/deploy -- **Write verification marker** so the commit gate hook allows the commit: - ```bash - touch "${TMPDIR:-/tmp}/.claude-spex-verified-${SESSION_ID}" - ``` - (The SESSION_ID is available from the hook context. If not, use a stable session identifier.) - -**If ANY false:** -- NOT VERIFIED: Block completion -- Fix issues before proceeding -- Re-run verification after fixes - -### 8. Completion Celebration - -**After all verification passes** (go decision is positive), check if a state file exists: - -```bash -STATE_FILE=".specify/.spex-state" -if [ -f "$STATE_FILE" ]; then - MODE=$(jq -r '.mode // empty' "$STATE_FILE" 2>/dev/null) -fi -``` - -**If a state file exists** (flow or ship mode), display a celebration: - -1. **Compute stats:** - ```bash - FEATURE=$(jq -r '.feature_branch // "unknown"' "$STATE_FILE" 2>/dev/null) - STARTED=$(jq -r '.started_at // empty' "$STATE_FILE" 2>/dev/null) - SPEC_DIR=$(jq -r '.spec_dir // empty' "$STATE_FILE" 2>/dev/null) - # If spec_dir not in state (ship mode), derive from branch - [ -z "$SPEC_DIR" ] && SPEC_DIR="specs/$FEATURE" - REVIEW_COUNT=$(ls "$SPEC_DIR"/REVIEW-*.md 2>/dev/null | wc -l | tr -d ' ') - COMMIT_COUNT=$(git rev-list --count main..HEAD 2>/dev/null || echo "?") - ``` - - Duration: compute from `started_at` to now (human-readable, e.g., "2h 15m" or "3d 4h") - - If `started_at` is empty, skip duration - -2. **Display celebration banner:** - ``` - +-------------------------------------------+ - | | - | ALL CHECKS PASSED | - | | - | Feature: | - | Duration: | - | Reviews: passed | - | Commits: | - | | - | | - | | - +-------------------------------------------+ - ``` - -3. **Sign-off message pool** (select one randomly): - - "Ship it!" - - "Another one bites the dust." - - "That's a wrap." - - "Clean as a whistle." - - "Nailed it." - - "Spec met. Code shipped. Coffee earned." - - "Nothing left to prove." - -4. **Remove state file** after displaying: - ```bash - rm -f "$STATE_FILE" - ``` - -**If no state file exists**, skip the celebration (no-op). - -## Common Failures - -| Claim | Requires | Not Sufficient | -|-------|----------|----------------| -| Tests pass | Test command output: 0 failures | Previous run, "should pass" | -| Spec compliant | Line-by-line requirement check | "Looks complete" | -| Linter clean | Linter output: 0 errors | Partial check | -| Build succeeds | Build command: exit 0 | Linter passing | -| Bug fixed | Test original symptom: passes | Code changed | -| Requirements met | Line-by-line checklist | Tests passing | - -## Red Flags - STOP - -- Using "should", "probably", "seems to" -- Expressing satisfaction before verification ("Great!", "Perfect!", "Done!") -- About to commit/push/PR without verification -- Relying on partial verification -- Thinking "just this once" -- Tired and wanting work over -- **ANY wording implying success without having run verification** -- **Modified CLAUDE.md without explicit user request** (CLAUDE.md is user-maintained, never auto-update) - -## Rationalization Prevention - -| Excuse | Reality | -|--------|---------| -| "Should work now" | RUN the verification | -| "I'm confident" | Confidence is not evidence | -| "Just this once" | No exceptions | -| "Tests pass" | Did you check spec compliance? | -| "Spec compliant" | Did you run the tests? | -| "I'm tired" | Exhaustion is not an excuse | -| "Partial check is enough" | Partial proves nothing | - -## Quality Gates - -**This command enforces quality gates:** - -1. **All tests must pass** -2. **Code hygiene review clean** (mechanical defects) -3. **100% spec compliance required** -4. **No spec drift allowed** -5. **All success criteria must be met** - -**No exceptions. No shortcuts.** - -These gates exist to prevent: -- Incomplete implementations -- Untested code -- Spec/code divergence -- False claims of completion - -## Remember - -**Verification is not optional.** - -- Don't skip verification "just this once" -- Don't claim completion without verification -- Don't ignore failing gates - -**Verification failures are information.** - -- Tests failing? Code has bugs -- Spec compliance failing? Missing features -- Drift detected? Synchronization problem -- Criteria not met? Work incomplete - -**No shortcuts for verification.** - -Run the command. Read the output. Check the spec. THEN claim the result. - -**Fix issues, don't rationalize past them.** - -**Evidence before assertions. Always.** - -This is non-negotiable. diff --git a/.specify/extensions/spex-gates/extension.yml b/.specify/extensions/spex-gates/extension.yml deleted file mode 100644 index 6330f1d7..00000000 --- a/.specify/extensions/spex-gates/extension.yml +++ /dev/null @@ -1,49 +0,0 @@ -schema_version: "1.0" - -extension: - id: spex-gates - name: "Spex Quality Gates" - version: "1.0.0" - description: "Quality gates for specification-driven development: spec review, plan review, code review, and verification" - author: cc-spex - license: MIT - -requires: - speckit_version: ">=0.5.2" - -provides: - commands: - - name: speckit.spex-gates.review-spec - file: commands/speckit.spex-gates.review-spec.md - description: "Review spec soundness, completeness, and implementability" - - name: speckit.spex-gates.review-plan - file: commands/speckit.spex-gates.review-plan.md - description: "Post-planning quality validation with coverage matrix and red flag scanning" - - name: speckit.spex-gates.review-code - file: commands/speckit.spex-gates.review-code.md - description: "Review code against spec compliance with deviation tracking" - - name: speckit.spex-gates.verify - file: commands/speckit.spex-gates.verify.md - description: "Final verification gate: tests, code hygiene, spec compliance, drift check" - - name: speckit.spex-gates.stamp - file: commands/speckit.spex-gates.stamp.md - description: "Stamp command - invokes verification before completion" - -hooks: - after_specify: - command: speckit.spex-gates.review-spec - optional: false - description: "Review spec soundness after specification" - after_tasks: - command: speckit.spex-gates.review-plan - optional: false - description: "Review plan and task quality after task generation" - after_implement: - command: speckit.spex-gates.review-code - optional: false - description: "Review code compliance after implementation" - -tags: - - "spex" - - "quality" - - "review" diff --git a/.specify/extensions/spex-teams/commands/speckit.spex-teams.implement.md b/.specify/extensions/spex-teams/commands/speckit.spex-teams.implement.md deleted file mode 100644 index ec070d01..00000000 --- a/.specify/extensions/spex-teams/commands/speckit.spex-teams.implement.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -description: "Parallel implementation via Agent Teams for independent tasks" ---- - -# Teams Implement - -Standalone parallel implementation command. The ship pipeline routes to this command -(instead of standard implement) when spex-teams is enabled and 2+ independent tasks exist. - -## Prerequisites - -- CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS must be set -- tasks.md must exist with task breakdown -- spex-gates extension must be enabled - -## Execution - -1. Read tasks.md and parse the task list -2. Count tasks marked with [P] (parallel-eligible) -3. If 2+ independent tasks: invoke speckit.spex-teams.orchestrate for parallel agent spawning -4. If <2 independent tasks: fall back to standard implement (inform user) -5. Check .spex-state for autonomous mode; suppress prompts if in ship pipeline - -## Ship Pipeline Integration - -The ship command routes here when spex-teams is enabled and tasks.md has 2+ independent tasks. -This command is NOT invoked via a hook - it is called directly by ship or the user. diff --git a/.specify/extensions/spex-teams/commands/speckit.spex-teams.orchestrate.md b/.specify/extensions/spex-teams/commands/speckit.spex-teams.orchestrate.md deleted file mode 100644 index d96b6717..00000000 --- a/.specify/extensions/spex-teams/commands/speckit.spex-teams.orchestrate.md +++ /dev/null @@ -1,148 +0,0 @@ ---- -description: "Unified team orchestration: parallel task implementation with spec guardian review pattern via Claude Code Agent Teams" ---- - -# Teams Orchestration: Parallel Task Implementation - -## Overview - -This command orchestrates parallel task implementation using Claude Code Agent Teams. The lead session analyzes the task dependency graph, spawns teammates in isolated worktrees for independent task groups, reviews all changes against spec.md via the spec guardian pattern, and coordinates merges. The spec guardian review loop is always-on: every teammate's work is reviewed for spec compliance before merging. - -## Prerequisites - -### CC Teams Feature Flag - -Check if Agent Teams is enabled: - -```bash -# Check settings.local.json for the feature flag -FLAG=$(jq -r '.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS // ""' .claude/settings.local.json 2>/dev/null) -``` - -**If the flag is not set (`""` or missing):** - -1. Set it in `.claude/settings.local.json`: - ```bash - jq '.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = "1"' .claude/settings.local.json > /tmp/settings.json && mv /tmp/settings.json .claude/settings.local.json - ``` -2. Inform the user: "Agent Teams feature flag has been enabled. Please restart Claude Code for teams to activate." -3. **Fall back to sequential implementation** for this session (teams will work on next run). - -**If the flag is set:** Proceed with team orchestration. - -**If the flag becomes unset mid-session** (e.g., user restarts without it): The pre-flight check runs at skill invocation time, not continuously. If the env var disappears mid-session, already-spawned teammates continue working. On next invocation, the check will catch the missing flag and fall back to sequential. - -## Task Graph Analysis - -Read the tasks.md file and analyze the dependency structure: - -1. **Parse all tasks** with their IDs, descriptions, and phase membership -2. **Identify dependency relationships** from the Dependencies section and phase ordering -3. **Group tasks by independence**: tasks that can execute simultaneously (no shared dependencies, different files) -4. **Identify blocked tasks**: tasks that must wait for others to complete first - -### Parallelism Assessment - -Evaluate whether teams add value: - -- **If 0-1 independent task groups exist** (everything is sequential): Skip team creation, execute tasks sequentially in the current session. Report: "Tasks are sequential, no parallelism benefit. Executing directly." -- **If 2+ independent task groups exist**: Proceed with team spawning. - -## Teammate Spawning - -### Spawn Rules - -- Spawn **one teammate per independent task group** (not one per task) -- **Maximum 5 teammates** (CC Teams best practice for coordination overhead) -- If more than 5 independent groups, batch them: assign multiple groups to the same teammate sequentially -- **Never spawn more teammates than independent groups** -- **isolation: "worktree"** - each teammate gets its own git worktree for clean file isolation - -### Spawn Prompt Template - -Each teammate receives this context in its spawn prompt: - -``` -You are implementing tasks for the [feature-name] feature. -You are working in an isolated git worktree. -Your work will be reviewed against spec.md before merging. - -## Your Assigned Tasks - -[List the specific tasks assigned to this teammate with task IDs] - -## Spec Context - -[Contents of spec.md for this feature] - -## Working Rules - -1. Implement each task completely before moving to the next -2. Commit after each logical group with descriptive messages -3. When all your tasks are done, message the lead: "Tasks complete, ready for review" -4. If you encounter a blocker, message the lead with details -5. Do not modify files outside your assigned task scope -6. Use "Assisted-By: Claude Code" as the git commit tagline -``` - -### Spawning Process - -Tell Claude to create an agent team: - -``` -Create an agent team for parallel implementation of [feature-name]. - -Spawn [N] teammates: -- Teammate 1: [task group description] - tasks [IDs] -- Teammate 2: [task group description] - tasks [IDs] -... - -Each teammate should implement their assigned tasks independently in their worktree. -Wait for all teammates to complete before proceeding to review. -``` - -## Completion Waiting - -After spawning teammates: - -1. **Wait for all teammates to finish** their assigned tasks -2. **Do not implement tasks yourself** while teammates are working (coordinate only) -3. **Monitor for stuck teammates**: if a teammate stops responding or errors, note the issue -4. **Handle teammate failures**: if a teammate crashes mid-task, either: - - Spawn a replacement teammate for the remaining tasks - - Fall back to implementing the remaining tasks directly - -## Spec Guardian Review Loop - -When a teammate reports completion, the lead reviews their changes: - -1. **Review changes**: Examine the teammate's commits and modified files -2. **Run spec compliance check** via `speckit.spex-gates.review-code` against spec.md -3. **If PASS**: Merge worktree changes, update tasks.md checkboxes to `[X]` for completed tasks -4. **If FAIL**: Send feedback to the teammate with specific spec violations. The teammate fixes the issues and re-submits for review. -5. **If 3+ failures on the same task**: Report to the user and pause. Do not continue retrying. - -The lead never implements code directly. The lead's sole job during this phase is review and coordination. - -## Sequential Fallback - -When teams cannot be used (feature flag not active, single task, linear dependencies): - -Execute tasks sequentially in the current session following the standard implementation flow from tasks.md. This is the normal behavior when the teams trait is not active. - -**Mixed independence**: When some tasks are independent and others are sequential (e.g., 1 of 3 tasks is independent, 2 are sequential), group the sequential tasks together as one teammate's workload and assign the independent task to a separate teammate. If only one independent group results, fall back to sequential execution. - -## Key Principles - -- **Teams for parallelism, not complexity**: Only use teams when genuine parallel work exists -- **Lead never implements**: The lead's job is review and coordination -- **Spec is the standard**: All review decisions based on spec.md -- **Worktrees prevent conflicts**: Each teammate has clean file isolation -- **Graceful degradation**: Always fall back to sequential if teams can't help -- **Respect task dependencies**: Never assign dependent tasks to different teammates - -## Failure Handling - -- **Teammate crashes**: Spawn a replacement teammate for unfinished tasks, or implement directly if near the end of the work -- **Merge conflicts**: Do NOT auto-resolve. Report the conflict to the user and wait for guidance -- **Review deadlock (3+ attempts)**: Message the teammate to stop work, report the situation to the user, and pause orchestration diff --git a/.specify/extensions/spex-teams/commands/speckit.spex-teams.research.md b/.specify/extensions/spex-teams/commands/speckit.spex-teams.research.md deleted file mode 100644 index f7bb87ca..00000000 --- a/.specify/extensions/spex-teams/commands/speckit.spex-teams.research.md +++ /dev/null @@ -1,151 +0,0 @@ ---- -description: "Parallel codebase research for planning via Claude Code Agent Teams" ---- - -# Teams Research: Parallel Codebase Exploration for Planning - -## Overview - -This command orchestrates parallel codebase research using Claude Code Agent Teams during the plan phase. The lead session analyzes the spec to identify research topics, spawns research agents to explore different parts of the codebase simultaneously, collects their findings, and then generates the plan with comprehensive codebase knowledge. - -## Prerequisites - -### CC Teams Feature Flag - -Check if Agent Teams is enabled: - -```bash -# Check settings.local.json for the feature flag -FLAG=$(jq -r '.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS // ""' .claude/settings.local.json 2>/dev/null) -``` - -**If the flag is not set (`""` or missing):** - -1. Set it in `.claude/settings.local.json`: - ```bash - jq '.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = "1"' .claude/settings.local.json > /tmp/settings.json && mv /tmp/settings.json .claude/settings.local.json - ``` -2. Inform the user: "Agent Teams feature flag has been enabled. Please restart Claude Code for teams to activate." -3. **Fall back to single-session research** for this session (teams will work on next run). - -**If the flag is set:** Proceed with team research. - -## Phase 1: Research Topic Identification - -Read the spec.md and identify what codebase knowledge is needed to create a solid plan. - -### Identify Research Areas - -From the spec, extract: - -1. **Existing code to modify**: Which files, modules, or packages does the spec reference or affect? -2. **Patterns to follow**: What existing patterns in the codebase should the plan adopt? -3. **Integration points**: Where does the new feature connect to existing code? -4. **Technology questions**: What libraries, frameworks, or tools are already in use that are relevant? - -### Group into Independent Research Topics - -Organize the areas into independent research topics that can be explored in parallel. Each topic should be: - -- **Self-contained**: An agent can research it without needing results from other topics -- **Focused**: Specific enough to produce actionable findings (not "explore the whole codebase") -- **Relevant**: Directly needed for plan creation - -**Examples of good research topics:** -- "Explore the authentication module: middleware chain, session handling, token validation patterns" -- "Map the database schema and migration patterns for the user-related tables" -- "Analyze the existing API endpoint structure: routing, validation, error handling conventions" -- "Review the test infrastructure: test helpers, fixtures, integration test patterns" - -### Parallelism Assessment - -- **If 0-1 research topics exist** (spec is simple or self-contained): Skip team creation, research directly in the current session. Report: "Single research area, no parallelism benefit. Researching directly." -- **If 2+ independent research topics exist**: Proceed with agent spawning. - -## Phase 2: Research Agent Spawning - -### Spawn Rules - -- Spawn **one agent per research topic** -- **Maximum 4 research agents** (research is read-only, so less coordination overhead than implementation, but keep it bounded) -- If more than 4 topics, merge the least complex ones together -- **All agents are read-only**: They explore and report, they do not modify files - -### Agent Prompt Template - -Each research agent receives: - -``` -You are a codebase research agent for the [feature-name] feature planning phase. - -## Your Research Topic - -[Description of what to research] - -## Spec Context - -[Relevant sections of spec.md that motivate this research] - -## Research Instructions - -1. Explore the relevant code thoroughly using Read, Grep, and Glob tools -2. Document your findings in a structured format: - - **Files examined**: List the key files you looked at - - **Patterns found**: Describe coding patterns, conventions, and structures - - **Integration points**: Where new code would connect to existing code - - **Constraints discovered**: Anything that limits or shapes the implementation approach - - **Recommendations**: Suggest how the plan should handle this area -3. Be specific: include file paths, function names, and line references -4. Do NOT modify any files. This is a read-only research mission. -5. When done, send your findings back to the lead. -``` - -### Spawning Process - -Create an agent team for parallel research: - -``` -Create an agent team for codebase research on [feature-name]. - -Spawn [N] research agents: -- Agent 1: [research topic description] -- Agent 2: [research topic description] -... - -Each agent should explore the codebase and report findings. Read-only, no file modifications. -Wait for all agents to complete before proceeding. -``` - -## Phase 3: Findings Consolidation - -After all research agents report back: - -1. **Collect all findings** from teammate messages -2. **Synthesize**: Identify common patterns across findings, resolve contradictions, note gaps -3. **Build a research summary**: Organize findings by relevance to plan sections -4. **Identify any remaining unknowns**: If research revealed new questions, note them for the plan's assumptions section - -## Phase 4: Plan Generation - -With research findings in hand, generate the plan: - -1. Use the consolidated research as input alongside the spec -2. Reference specific files, patterns, and integration points discovered by agents -3. The plan should reflect the actual codebase state, not assumptions -4. Include a brief "Research Basis" note in the plan acknowledging what was explored - -Then proceed with normal plan-phase flow (review-spec if spex-gates extension is active, etc.). - -## Sequential Fallback - -When teams cannot be used (feature flag not active, single research topic, simple spec): - -Research the codebase directly in the current session, then generate the plan. This is the normal behavior when the teams extension is not active. - -## Key Principles - -- **Research is read-only**: Agents explore, they never modify files -- **Lead consolidates and plans**: Research agents gather data, the lead makes design decisions -- **Breadth over depth**: Better to have a broad understanding than deep knowledge of one area -- **Graceful degradation**: Always fall back to single-session research if teams can't help -- **Keep research focused**: Every research topic must tie back to a plan need from the spec diff --git a/.specify/extensions/spex-teams/config-template.yml b/.specify/extensions/spex-teams/config-template.yml deleted file mode 100644 index 2320367e..00000000 --- a/.specify/extensions/spex-teams/config-template.yml +++ /dev/null @@ -1,4 +0,0 @@ -# Teams Extension Configuration -agent_teams: - env_var: CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS - max_teammates: 5 diff --git a/.specify/extensions/spex-teams/extension.yml b/.specify/extensions/spex-teams/extension.yml deleted file mode 100644 index bdfe8cdb..00000000 --- a/.specify/extensions/spex-teams/extension.yml +++ /dev/null @@ -1,45 +0,0 @@ -schema_version: "1.0" - -extension: - id: spex-teams - name: "Spex Agent Teams" - version: "1.0.0" - description: "Parallel implementation via Claude Code Agent Teams" - author: cc-spex - license: MIT - -requires: - speckit_version: ">=0.5.2" - extensions: - - id: spex-gates - version: ">=1.0.0" - -provides: - commands: - - name: speckit.spex-teams.orchestrate - file: commands/speckit.spex-teams.orchestrate.md - description: "Parallel task implementation with spec guardian review pattern via Agent Teams" - - name: speckit.spex-teams.research - file: commands/speckit.spex-teams.research.md - description: "Parallel codebase research during planning via Agent Teams" - - name: speckit.spex-teams.implement - file: commands/speckit.spex-teams.implement.md - description: "Standalone parallel implementation via Agent Teams for independent tasks" - - config: - - name: "teams-config.yml" - template: "config-template.yml" - description: "Agent Teams configuration" - required: false - -hooks: - before_plan: - command: speckit.spex-teams.research - optional: true - prompt: "Run parallel codebase research?" - description: "Parallel codebase research during planning via Agent Teams" - -tags: - - "spex" - - "teams" - - "parallel" diff --git a/.specify/extensions/spex-worktrees/commands/speckit.spex-worktrees.manage.md b/.specify/extensions/spex-worktrees/commands/speckit.spex-worktrees.manage.md deleted file mode 100644 index 28b91dba..00000000 --- a/.specify/extensions/spex-worktrees/commands/speckit.spex-worktrees.manage.md +++ /dev/null @@ -1,528 +0,0 @@ ---- -name: speckit.spex-worktrees.worktree -description: Manage git worktrees for isolated feature development - create after specify, list active worktrees, finish and cleanup -argument-hint: "[list|cleanup|finish]" ---- - -# Git Worktree Management for spex - -## Overview - -This command manages git worktrees to isolate feature development. It supports four actions: - -- **create**: Called by the `after_specify` hook after `speckit-specify` completes. Creates a worktree, restores `main`, and prints switch instructions. -- **list**: Shows all active feature worktrees with path, branch, and feature name. -- **finish**: Merges the current worktree's branch into the default branch and removes the worktree. Use when implementation is complete. -- **cleanup**: Detects worktrees whose branches are merged and offers removal. - -## Action Routing - -Determine the action from the argument: - -- If invoked with argument `create` (from the `after_specify` hook): the action is **create**. Execute immediately, no confirmation needed. -- If invoked with argument `finish`: the action is **finish**. -- If invoked with argument `cleanup`: the action is **cleanup**. -- Otherwise (no args, `list`, or invoked directly): the action is **list**. - -## Prerequisites - -The project must be a git repository. - -```bash -git rev-parse --git-dir >/dev/null 2>&1 || { echo "ERROR: Not a git repository"; exit 1; } -``` - -## Action: Create - -This action runs after `speckit-specify` has created a feature branch and spec files. - -### Step 1: Read Configuration - -Read `base_path` from the worktrees extension config (or default to `..`): - -```bash -WORKTREE_CONFIG=".specify/extensions/spex-worktrees/worktree-config.yml" -BASE_PATH=$(yq -r '.worktrees.base_path // ".."' "$WORKTREE_CONFIG" 2>/dev/null || echo "..") -``` - -Default: `..` (sibling directory to the repo root). - -### Step 2: Get Current Branch - -```bash -BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD) -``` - -Verify it matches the `NNN-feature-name` pattern. If not, warn that worktree creation only applies to feature branches and skip. - -### Step 3: Detect If Already in a Worktree - -A git worktree has a `.git` file (not directory) pointing to the main repo. Detect this: - -```bash -REPO_ROOT=$(git rev-parse --show-toplevel) -GIT_DIR=$(git rev-parse --git-dir) - -# If git-dir is not /.git, we're inside a worktree -if [ "$GIT_DIR" != "$REPO_ROOT/.git" ] && [ "$GIT_DIR" != ".git" ]; then - echo "WARNING: Already inside a git worktree. Skipping worktree creation." - echo "Worktree nesting is not supported." - # Stop here. Do not proceed to subsequent steps. -fi -``` - -If inside a worktree, skip the entire create action. Do not proceed to any subsequent Create steps. - -### Step 4: Compute Target Path and Validate - -Derive the repo name from the repository root and build the worktree path: - -```bash -REPO_NAME=$(basename "$(git rev-parse --show-toplevel)") - -# Handle both absolute and relative base paths -if [[ "$BASE_PATH" = /* ]]; then - RESOLVED_BASE=$(cd "$BASE_PATH" && pwd) -else - RESOLVED_BASE=$(cd "$REPO_ROOT/$BASE_PATH" && pwd) -fi -``` - -If the `cd` fails (directory does not exist), report a clear error and stop: - -```bash -if [ -z "$RESOLVED_BASE" ]; then - echo "ERROR: base_path '$BASE_PATH' does not resolve to a valid directory." - echo "Check worktrees.base_path in .specify/extensions/spex-worktrees/worktree-config.yml" - # Stop here. Do not proceed to subsequent steps. -fi -``` - -Build the worktree path using `@` as separator between repo name and branch: - -```bash -WORKTREE_PATH="${RESOLVED_BASE}/${REPO_NAME}@${BRANCH_NAME}" -``` - -Verify the worktree path is not inside the main repository (a `base_path` of `.` would cause this): - -```bash -case "$WORKTREE_PATH" in - "$REPO_ROOT"/*) - echo "ERROR: Worktree path is inside the main repository: $WORKTREE_PATH" - echo "Set base_path to a directory outside the repo (default: '..')" - # Stop here. Do not proceed to subsequent steps. - ;; -esac -``` - -Check if the target path already exists: - -```bash -if [ -d "$WORKTREE_PATH" ] || [ -f "$WORKTREE_PATH" ]; then - echo "ERROR: Target path already exists: $WORKTREE_PATH" - echo "Remove it manually or choose a different base_path in .specify/extensions/spex-worktrees/worktree-config.yml" - # Stop here. Do not proceed to subsequent steps. -fi -``` - -### Step 5: Commit All Tracked Changes to Feature Branch - -Before switching away from the feature branch, commit all modified tracked files. This includes spec files, `.specify/` configuration changes, and any other modified tracked files. Stage changes in two passes to avoid capturing unintended untracked files: - -```bash -# Stage modifications to already-tracked files -git add -u - -# Stage new spec and brainstorm artifacts (these are untracked but expected) -[ -d "specs/$BRANCH_NAME" ] && git add "specs/$BRANCH_NAME" -[ -d "brainstorm" ] && git add brainstorm/ -[ -d ".specify" ] && git add .specify/ - -if ! git diff --cached --quiet; then - git commit -m "feat: Add spec for $BRANCH_NAME - -Assisted-By: 🤖 Claude Code" -fi -``` - -Using `git add -u` (tracked modifications only) plus explicit paths for new spec artifacts limits the commit scope to intended files. The `git diff --cached --quiet` guard skips the commit when there are no staged changes, avoiding empty commits. - -### Step 5b: Capture Feature Directory Before Branch Switch - -The next step switches to the default branch, which changes tracked files on disk. Since `.specify/feature.json` is tracked, its contents will revert to whatever the default branch has. Capture the correct `feature_directory` now, while still on the feature branch: - -```bash -FEATURE_DIR="" -if [ -f ".specify/feature.json" ]; then - FEATURE_DIR=$(jq -r '.feature_directory // empty' ".specify/feature.json") -fi -# Fallback to branch-derived path if feature.json is missing or empty -FEATURE_DIR=${FEATURE_DIR:-"specs/$BRANCH_NAME"} -``` - -This value is used in Step 8b to set the correct feature context in the worktree. - -### Step 6: Restore Default Branch (before worktree creation) - -Git does not allow two worktrees to have the same branch checked out. Since `speckit-specify` just created and checked out the feature branch, we must switch back to the default branch before creating a worktree for that branch. - -Detect the default branch dynamically with a fallback chain: - -```bash -DEFAULT_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@') -if [ -z "$DEFAULT_BRANCH" ]; then - # origin/HEAD not set (common with git init + remote add). Try common names. - for candidate in main master; do - if git rev-parse --verify "$candidate" >/dev/null 2>&1; then - DEFAULT_BRANCH="$candidate" - break - fi - done -fi -DEFAULT_BRANCH=${DEFAULT_BRANCH:-main} -``` - -```bash -if ! git checkout "$DEFAULT_BRANCH" 2>&1; then - echo "WARNING: Could not switch back to $DEFAULT_BRANCH." - echo "You likely have uncommitted changes. Commit or stash them first." - echo "The repository remains on branch $BRANCH_NAME." - echo "Worktree creation skipped (cannot create worktree while branch is checked out here)." - # Stop here. Do not proceed to subsequent steps. -fi -``` - -### Step 7: Create the Worktree - -Now that the current worktree is on the default branch, create a new worktree for the feature branch. If this fails (disk full, permission denied, etc.), report the error clearly: - -```bash -if ! git worktree add "$WORKTREE_PATH" "$BRANCH_NAME" 2>&1; then - echo "ERROR: Failed to create worktree at $WORKTREE_PATH" - echo "The repository is on $DEFAULT_BRANCH. The feature branch $BRANCH_NAME still exists." - echo "Resolve the issue and retry, or switch to the branch manually: git checkout $BRANCH_NAME" - # Stop here. Do not proceed to subsequent steps. -fi -``` - -### Step 8: Copy Configuration to Worktree - -Gitignored config directories (`.claude/` and `.specify/`) won't exist in the new worktree. Copy them so spec-kit extensions, skills, and settings work immediately without re-running init: - -```bash -# Copy .specify/ (extensions registry, hooks, state, config) -if [ -d ".specify" ]; then - rsync -a --exclude='.git' ".specify/" "$WORKTREE_PATH/.specify/" -fi - -# Copy .claude/ (skills, settings, commands) -if [ -d ".claude" ]; then - rsync -a ".claude/" "$WORKTREE_PATH/.claude/" -fi -``` - -This ensures the worktree has the same extensions, hooks, permissions, and skills as the main repo. No `/spex:init` needed in the worktree. - -### Step 8b: Update feature.json and flow state for the Worktree Branch - -The copied `.specify/feature.json` may point to whatever feature was active on the default branch (since feature.json is tracked and reverts on branch switch). Write the correct value captured in Step 5b: - -```bash -FEATURE_JSON="$WORKTREE_PATH/.specify/feature.json" -echo "{\"feature_directory\": \"$FEATURE_DIR\"}" | jq '.' > "$FEATURE_JSON" -``` - -This writes the `FEATURE_DIR` value captured in Step 5b, which reflects the actual spec directory created by speckit-specify (not a branch-name derivation that may differ). - -The copied `.specify/.spex-state` also contains the old `feature_branch`. Update it to match the worktree's branch so the status line works correctly (the statusline script deletes state files where `feature_branch` doesn't match the current branch): - -```bash -STATE_FILE="$WORKTREE_PATH/.specify/.spex-state" -if [ -f "$STATE_FILE" ]; then - jq --arg branch "$BRANCH_NAME" --arg dir "$FEATURE_DIR" \ - '.feature_branch = $branch | .spec_dir = $dir' "$STATE_FILE" > "${STATE_FILE}.tmp" \ - && mv "${STATE_FILE}.tmp" "$STATE_FILE" -fi -``` - -This prevents both spec-kit commands and the status line from operating on the wrong feature context. - -### Step 9: Print Output - -Print a machine-readable line followed by human-readable instructions: - -```bash -echo "WORKTREE_CREATED path=$WORKTREE_PATH" -``` - -Then print instructions for the user: - -``` -┌─────────────────────────────────────────────────────────────┐ -│ Worktree created at │ -│ │ -│ To continue with planning/implementation: │ -│ cd && claude │ -│ │ -│ Config (.claude/ and .specify/) has been copied. │ -│ All extensions and skills are ready to use. │ -│ │ -│ The spec file contains all context from this session. │ -└─────────────────────────────────────────────────────────────┘ -``` - -Use the actual `WORKTREE_PATH` value (computed in Step 4) in the output. - -**Ship pipeline note:** When running inside a `speckit-spex-ship` pipeline, ship will automatically `cd` into the worktree and continue the pipeline there. No manual session restart needed. - -## Action: List - -Show all active feature worktrees for the project. - -### Step 1: Get Worktree List - -```bash -git worktree list --porcelain -``` - -Parse the output. Each worktree entry has: -- `worktree ` -- `HEAD ` -- `branch refs/heads/` - -### Step 2: Filter Feature Branches - -Only show worktrees whose branch matches the `NNN-*` feature branch pattern (three-digit prefix followed by a hyphen). - -Skip the main worktree (the original repo). - -### Step 3: Format Output - -Display a table with worktree directory names (`@` separator): - -``` -Active Feature Worktrees: - - Path Branch Feature - ───────────────────────────────────────────────────────────── - cc-spex@004-user-auth 004-user-auth user-auth - cc-spex@007-worktrees-trait 007-worktrees-trait worktrees-trait -``` - -Derive the display path by extracting the last path component from the worktree's absolute path. - -If no feature worktrees exist: - -``` -No active feature worktrees found. - -Create one by running /speckit-specify with the worktrees extension enabled. -``` - -## Action: Finish - -Merges the current worktree's feature branch into the default branch and removes the worktree. This is the recommended way to complete work in a spex worktree. - -**IMPORTANT:** Do NOT use Claude Code's `ExitWorktree` tool. Spex worktrees are created via `git worktree add`, not `EnterWorktree`, so `ExitWorktree` will refuse to operate on them. Always use git commands directly. - -### Step 1: Verify We're in a Worktree - -```bash -REPO_ROOT=$(git rev-parse --show-toplevel) -GIT_DIR=$(git rev-parse --git-dir) - -# A worktree has a .git file (not directory) pointing to the main repo -if [ "$GIT_DIR" = "$REPO_ROOT/.git" ] || [ "$GIT_DIR" = ".git" ]; then - echo "ERROR: Not inside a git worktree. Use 'finish' from within a spex worktree." - # Stop here. -fi -``` - -### Step 2: Get Branch and Main Worktree Info - -```bash -BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD) -WORKTREE_PATH=$(git rev-parse --show-toplevel) - -# Find the main worktree (first entry in worktree list) -MAIN_WORKTREE=$(git worktree list --porcelain | head -1 | sed 's/^worktree //') - -# Detect default branch -DEFAULT_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@') -if [ -z "$DEFAULT_BRANCH" ]; then - for candidate in main master; do - if git rev-parse --verify "$candidate" >/dev/null 2>&1; then - DEFAULT_BRANCH="$candidate" - break - fi - done -fi -DEFAULT_BRANCH=${DEFAULT_BRANCH:-main} -``` - -### Step 3: Ensure All Changes Are Committed - -```bash -if ! git diff --quiet || ! git diff --cached --quiet; then - echo "ERROR: Uncommitted changes in worktree. Commit or stash before finishing." - # Stop here. -fi -``` - -### Step 4: Ask User How to Proceed - -Use AskUserQuestion with: -- header: "Finish" -- multiSelect: false -- Options: - - "Merge and remove (Recommended)": "Fast-forward merge branch into default, remove worktree and branch" - - "Remove only": "Remove worktree and branch without merging (changes stay in git reflog)" - - "Cancel": "Keep worktree as-is" - -If "Cancel": stop. - -### Step 5: Switch CWD to Main Worktree - -**CRITICAL:** Switch the working directory to the main worktree BEFORE doing anything destructive. If cwd is inside the worktree being removed, all subsequent Bash commands will fail with "path does not exist". - -```bash -cd "$MAIN_WORKTREE" -``` - -### Step 6: Merge (if selected) - -If the user chose "Merge and remove": - -```bash -cd "$MAIN_WORKTREE" -git checkout "$DEFAULT_BRANCH" -git merge --ff-only "$BRANCH_NAME" 2>&1 -``` - -If fast-forward merge fails (branches diverged), ask the user: - -Use AskUserQuestion with: -- header: "Merge" -- multiSelect: false -- Options: - - "Create merge commit": "Merge with a merge commit (branches have diverged)" - - "Abort": "Keep worktree, resolve manually" - -If "Create merge commit": -```bash -git merge "$BRANCH_NAME" -m "Merge branch '$BRANCH_NAME' - -Assisted-By: 🤖 Claude Code" 2>&1 -``` - -If "Abort": stop. The cwd is already at the main worktree, so the user can navigate back. - -### Step 7: Remove Worktree and Branch - -```bash -# Remove worktree (cwd is already at main worktree from Step 5) -git worktree remove "$WORKTREE_PATH" 2>&1 - -# Delete the feature branch (it's merged or user chose remove-only) -git branch -d "$BRANCH_NAME" 2>&1 || git branch -D "$BRANCH_NAME" 2>&1 -``` - -### Step 8: Clear Flow State - -```bash -STATE_FILE=".specify/.spex-state" -if [ -f "$STATE_FILE" ]; then - rm -f "$STATE_FILE" -fi -``` - -### Step 9: Report - -``` -┌─────────────────────────────────────────────────────────┐ -│ Feature branch finished. │ -│ │ -│ - Merged to (if selected) │ -│ - Worktree removed: │ -│ - Branch deleted: │ -│ - Flow state cleared │ -│ │ -│ You are now in the main repo on . │ -└─────────────────────────────────────────────────────────┘ -``` - -If the user wants to push: `git push origin $DEFAULT_BRANCH` - -## Action: Cleanup - -Detect worktrees whose branches are merged and offer removal. - -### Step 1: Get Merged Branches - -```bash -# Detect default branch with fallback chain -DEFAULT_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@') -if [ -z "$DEFAULT_BRANCH" ]; then - for candidate in main master; do - if git rev-parse --verify "$candidate" >/dev/null 2>&1; then - DEFAULT_BRANCH="$candidate" - break - fi - done -fi -DEFAULT_BRANCH=${DEFAULT_BRANCH:-main} - -# Get all feature branches merged into the default branch -MERGED_BRANCHES=$(git branch --merged "$DEFAULT_BRANCH" | sed 's/^[* ]*//' | grep -E '^[0-9]{3}-') -``` - -### Step 2: Cross-Reference with Worktrees - -For each worktree with a feature branch, check if its branch appears in the merged list. - -### Step 3: Handle Merged Worktrees - -For each merged worktree, present it to the user and ask for confirmation: - -``` -Worktree cc-spex@004-user-auth (branch 004-user-auth) is merged into main. -Remove this worktree? (yes/no) -``` - -If the user confirms, first switch to the main repo root (to avoid cwd pointing at the deleted directory), then remove the worktree: - -```bash -# Switch cwd to the main worktree BEFORE removing the feature worktree. -# If cwd is inside the worktree being removed, all subsequent commands will -# fail with "Path does not exist" because the Bash tool persists cwd. -MAIN_WORKTREE=$(git worktree list --porcelain | head -1 | sed 's/^worktree //') -cd "$MAIN_WORKTREE" -git worktree remove -git branch -d -``` - -### Step 4: Handle Unmerged Worktrees - -For worktrees with unmerged branches, warn the user: - -``` -Worktree cc-spex@007-worktrees-trait (branch 007-worktrees-trait) has NOT been merged. -Skipping. Use --force to remove unmerged worktrees (data may be lost). -``` - -Only remove unmerged worktrees if the user explicitly confirms after seeing the warning. - ---- - -## Worktree Context for Downstream Commands - -When running in a worktree created by this extension, downstream spec-kit commands should be aware of the worktree context: - -### Planning Context - -You are likely running in a worktree created by the `worktrees` extension. The spec file in this worktree contains all decisions from the brainstorm/specify session. No separate handoff file is needed. - -### Implementation Context - -You are likely running in a worktree created by the `worktrees` extension. The spec and plan files in this worktree contain all context needed for implementation. No separate handoff file is needed. diff --git a/.specify/extensions/spex-worktrees/extension.yml b/.specify/extensions/spex-worktrees/extension.yml deleted file mode 100644 index 4a3467aa..00000000 --- a/.specify/extensions/spex-worktrees/extension.yml +++ /dev/null @@ -1,33 +0,0 @@ -schema_version: "1.0" - -extension: - id: spex-worktrees - name: "Spex Worktrees" - version: "1.0.0" - description: "Git worktree isolation for feature development" - author: cc-spex - license: MIT - -requires: - speckit_version: ">=0.5.2" - tools: - - name: git - required: true - -provides: - commands: - - name: speckit.spex-worktrees.manage - file: commands/speckit.spex-worktrees.manage.md - description: "Manage git worktrees for isolated feature development" - -hooks: - after_specify: - command: speckit.spex-worktrees.manage - args: "create" - optional: false - description: "Create git worktree after specification" - -tags: - - "spex" - - "git" - - "worktree" diff --git a/.specify/extensions/spex/commands/speckit.spex.brainstorm.md b/.specify/extensions/spex/commands/speckit.spex.brainstorm.md deleted file mode 100644 index 07fb8f46..00000000 --- a/.specify/extensions/spex/commands/speckit.spex.brainstorm.md +++ /dev/null @@ -1,391 +0,0 @@ ---- -description: "Refine rough ideas into executable specifications through collaborative questioning, alternative exploration, and incremental validation" ---- - -# Brainstorming Ideas Into Specifications - -Help turn rough ideas into clear, agreed-upon feature descriptions through natural collaborative dialogue. The output is a brainstorm document capturing the problem, approaches considered, and the decision, ready for formal specification. - -**Key Principle:** Brainstorming explores WHAT to build and WHY. The formal spec (via `/speckit-specify`) and implementation planning come after. - - -Do NOT invoke any implementation skill, write any code, scaffold any project, create spec files, or take any implementation action during brainstorming. Brainstorming ends with a decision and a brainstorm document, not a spec. - - - -## Command Namespace: Use the correct prefixes - -spex extension commands use the `speckit-spex-*` prefix (e.g., `/speckit-spex-brainstorm`). -speckit core commands use the `speckit-` prefix (e.g., `/speckit-specify`, `/speckit-plan`). - -Commands like `/spex:specify`, `/spex:plan`, `/spex:implement`, `/spex:tasks` DO NOT EXIST. - - -## Checklist - -You MUST create a task for each of these items and complete them in order: - -1. **Initialize spec-kit** - ensure specify CLI and project are set up -2. **Explore project context** - check files, specs, constitution, recent commits -3. **Check for related brainstorms** - scan `brainstorm/` for existing docs on similar topics, offer to update or create new -4. **Ask clarifying questions** - one at a time, understand purpose/constraints/success criteria -5. **Propose 2-3 approaches** - with trade-offs and your recommendation -6. **Reach agreement** - confirm the chosen approach and scope with the user -7. **Write brainstorm document** - persist session summary to `brainstorm/NN-topic-slug.md` -8. **Update overview** - create or refresh `brainstorm/00-overview.md` with index, open threads, parked ideas -9. **Transition** - offer next steps - -## Process Flow - -```dot -digraph brainstorming { - "Initialize spec-kit" [shape=box]; - "Explore project context" [shape=box]; - "Related brainstorm exists?" [shape=diamond]; - "Ask clarifying questions" [shape=box]; - "Propose 2-3 approaches" [shape=box]; - "User chooses approach?" [shape=diamond]; - "Write brainstorm document" [shape=box]; - "Update overview" [shape=box]; - "Offer next steps" [shape=box]; - "Done" [shape=doublecircle]; - - "Initialize spec-kit" -> "Explore project context"; - "Explore project context" -> "Related brainstorm exists?"; - "Related brainstorm exists?" -> "Ask clarifying questions" [label="no, or user chooses new"]; - "Related brainstorm exists?" -> "Ask clarifying questions" [label="yes, user chooses update"]; - "Ask clarifying questions" -> "Propose 2-3 approaches"; - "Propose 2-3 approaches" -> "User chooses approach?"; - "User chooses approach?" -> "Ask clarifying questions" [label="needs more exploration"]; - "User chooses approach?" -> "Write brainstorm document" [label="agreed"]; - "Write brainstorm document" -> "Update overview"; - "Update overview" -> "Offer next steps"; - "Offer next steps" -> "Done"; -} -``` - -## Prerequisites - -Spec-kit must be initialized before brainstorming. If `.specify/` directory does not exist, tell the user to run `/spex:init` first and stop. - -## The Process - -### Understanding the idea - -**Check context first:** -- Review existing specs (if any) in `specs/` directory -- Check for constitution (`.specify/memory/constitution.md`) -- Review recent commits to understand project state -- Look for related features or patterns -- Scan `brainstorm/` directory for existing brainstorm documents (triggers revisit detection, see step 3 in checklist) - -**Assess scope before deep-diving:** -- Before asking detailed questions, assess scope: if the request describes multiple independent subsystems (e.g., "build a platform with chat, file storage, billing, and analytics"), flag this immediately. Don't spend questions refining details of a project that needs to be decomposed first. -- If the project is too large for a single spec, help the user decompose into sub-projects: what are the independent pieces, how do they relate, what order should they be built? Then brainstorm the first sub-project through the normal design flow. Each sub-project gets its own spec, plan, and implementation cycle. - -**Ask questions to refine:** -- For appropriately-scoped projects, ask questions one at a time to refine the idea -- Only one question per message. If a topic needs more exploration, break it into multiple questions -- Prefer multiple choice when possible, but open-ended is fine too -- Focus on: purpose, constraints, success criteria, edge cases -- Identify dependencies and integrations - -**Remember:** You're exploring WHAT needs to happen, not HOW it will be implemented. - -### Exploring approaches - -**Propose 2-3 different approaches:** -- Present options conversationally with trade-offs -- Lead with your recommended option -- Explain reasoning clearly -- Consider: complexity, maintainability, user impact - -**Questions to explore:** -- What are the core requirements vs. nice-to-have? -- What are the error cases and edge conditions? -- How does this integrate with existing features? -- What are the success criteria? - -### Reaching agreement - -Once the user picks an approach, confirm the scope: -- Summarize what's in scope and out of scope -- Confirm key requirements and constraints -- Note any open questions that the spec phase should resolve - -This is the decision point. The brainstorm document captures this agreement. - -### Transition: next steps - -After the brainstorm document is written and overview updated, offer the user a choice of how to proceed: - -Use AskUserQuestion with: -- header: "Next steps" -- multiSelect: false -- Options: - - "Specify step-by-step (/speckit-specify)": "Create a formal spec interactively, then plan and implement in separate steps" - - "Ship autonomously (/speckit-spex-ship)": "Run the full pipeline (specify, plan, implement, review) with configurable oversight. Best for small to mid-sized features." - - "Done for now": "Stop here. The brainstorm document is saved for later." - -If the user chooses "Specify step-by-step": invoke `/speckit-specify` with the brainstorm document as context. - -If the user chooses "Ship autonomously": invoke `/speckit-spex-ship` with the brainstorm document path as argument. - -If the user chooses "Done for now": end the session. - -## Brainstorm Document Structure - -Each brainstorm session produces a structured summary document. The document uses this format: - -```markdown -# Brainstorm: [Topic] - -**Date:** YYYY-MM-DD -**Status:** active | parked | abandoned | spec-created - -## Problem Framing -[What problem is being explored and why it matters] - -## Approaches Considered - -### A: [Approach Name] -- Pros: ... -- Cons: ... - -### B: [Approach Name] -- Pros: ... -- Cons: ... - -## Decision -[What was chosen and why, or "Parked: [reason]" if no decision was reached] - -## Key Requirements -[Core requirements agreed during brainstorming, to feed into the spec] - -## Open Questions -- [Unresolved question that the spec phase should address] -``` - -**Status values:** -- `active` - session completed, idea is being pursued -- `parked` - session stopped intentionally, idea may be revisited -- `abandoned` - session stopped, idea is not being pursued -- `spec-created` - a spec was created from this brainstorm (include spec path) - -## Overview Document Structure - -The `brainstorm/00-overview.md` file provides a navigable index of all brainstorm sessions: - -```markdown -# Brainstorm Overview - -Last updated: YYYY-MM-DD - -## Sessions - -| # | Date | Topic | Status | Spec | -|---|------|-------|--------|------| -| 01 | YYYY-MM-DD | topic-slug | spec-created | 0003 | -| 02 | YYYY-MM-DD | topic-slug | active | - | -| 03 | YYYY-MM-DD | topic-slug | parked | - | - -## Open Threads -- [Thread description] (from #NN) -- [Thread description] (from #NN) - -## Parked Ideas -- [Idea description] (#NN) - Reason: [why parked] -``` - -## Revisit Detection - -**When:** During step 3 of the checklist (after exploring project context). - -**How:** -1. Check if `brainstorm/` directory exists. If not, skip (no prior brainstorms). -2. List all `NN-*.md` files in `brainstorm/` (excluding `00-overview.md`). -3. Extract topic slugs from filenames (the part after the number prefix). -4. Compare the current brainstorm topic against existing slugs using keyword overlap. -5. If a related brainstorm document is found, use AskUserQuestion: - - **Option A: "Create new document"** - session produces a new numbered file - - **Option B: "Update existing"** - session appends a new dated section to the existing document - -**If "Update existing" is chosen:** -At session end, instead of creating a new file, append a new section to the existing document: - -```markdown - ---- - -## Revisit: YYYY-MM-DD - -### Updated Problem Framing -[How understanding has evolved] - -### New Approaches Considered -... - -### Updated Decision -... - -### Open Threads -- [New or updated threads] -``` - -Then update the overview to reflect any status or thread changes. - -## Writing the Brainstorm Document - -**When:** Step 7 of the checklist (after reaching agreement). - -You MUST write the brainstorm document at session end. This step is NOT optional. - -**Procedure:** - -1. **Create directory** if it does not exist: - ```bash - mkdir -p brainstorm/ - ``` - -2. **Detect next number** by scanning existing files: - ```bash - ls brainstorm/[0-9][0-9]-*.md 2>/dev/null - ``` - Use `max_existing_number + 1`. If no files exist, start at 01. Do NOT gap-fill (if 01 and 03 exist, next is 04). - -3. **Generate topic slug**: Derive from the brainstorm topic. Lowercase, hyphens, 2-4 words. - Example: "user authentication system" becomes `auth-system` - -4. **Determine status**: - - If the user chose to park the idea: `parked` - - If the user abandoned early: `abandoned` - - Otherwise: `active` - -5. **Write the document** using the Brainstorm Document Structure defined above. - -6. **Commit the brainstorm document**: - ```bash - git add brainstorm/NN-topic-slug.md - git commit -m "Add brainstorm: [topic] - - Assisted-By: 🤖 Claude Code" - ``` - -## Updating the Overview - -**When:** Step 8 of the checklist (immediately after writing the brainstorm document). - -You MUST update the overview after every brainstorm document write or update. This step is NOT optional. - -**Procedure:** - -1. **If `brainstorm/00-overview.md` does not exist**, create it. - If `brainstorm/` exists but `00-overview.md` is missing, regenerate it from all existing documents. - -2. **Always regenerate by scanning all documents** (idempotent full rebuild): - - List all `NN-*.md` files in `brainstorm/` (excluding `00-overview.md`) - - For each file, extract: number, date, status, spec reference (from frontmatter) - - For each file, extract all items under `## Open Questions` - - For each file with status `parked`, collect the idea and reason - -3. **Build the overview** using the Overview Document Structure defined above: - - Sessions table: one row per document, sorted by number - - Open Threads: aggregated from all documents, tagged with source `(from #NN)` - - Parked Ideas: collected from all `parked` documents - -4. **Write `brainstorm/00-overview.md`** with the rebuilt content. - -5. **Commit the overview update**: - ```bash - git add brainstorm/00-overview.md - git commit -m "Update brainstorm overview - - Assisted-By: 🤖 Claude Code" - ``` - -## Incomplete Session Handling - -**When:** The user stops the brainstorm before reaching agreement. - -**Zero-interaction guard:** If the session had no meaningful interaction (no approaches explored, no clarifying questions answered beyond the initial topic), do NOT prompt to save. Simply end the session without creating any artifacts. - -**For sessions with meaningful interaction** (approaches were discussed, questions were answered): - -Use AskUserQuestion to ask: **"Save this brainstorm session?"** - -- **Option A: "Save as parked"** - Write the document with status `parked`, update overview -- **Option B: "Save as abandoned"** - Write the document with status `abandoned`, update overview -- **Option C: "Discard"** - Do not create any brainstorm document, do not update overview - -If the user chooses to save, follow the "Writing the Brainstorm Document" and "Updating the Overview" procedures above. - -## Key Principles - -- **One question at a time** - Don't overwhelm with multiple questions -- **Multiple choice preferred** - Easier to answer than open-ended when possible -- **YAGNI ruthlessly** - Remove unnecessary features from all specs -- **Explore alternatives** - Always propose 2-3 approaches before settling -- **Be flexible** - Go back and clarify when something doesn't make sense -- **Separate WHAT from HOW** - Focus on requirements and outcomes, not implementation -- **Brainstorm, don't specify** - The formal spec comes later via `/speckit-specify` or `/speckit-spex-ship` - -## Common Pitfalls - -**Don't:** -- Write spec files during brainstorming -- Include implementation details ("use Redis for caching") -- Run review gates (that's for the specify phase) -- Skip exploring alternatives -- Rush past clarifying questions -- Invoke implementation skills - -**Do:** -- Focus on requirements and behavior -- Specify WHAT, defer HOW -- Explore multiple approaches -- Capture the decision and reasoning -- Note open questions for the spec phase -- Write the brainstorm document - -## Example Flow - -``` -User: I want to add a comment system to the blog - -You: I'm using speckit-spex-brainstorm to refine this idea. - -Let me understand the context first... -[Reviews existing code and specs] - -For the comment system, what level of features do you want? - -A) Basic - Name, email, comment text only -B) Standard - Above + threading/replies -C) Advanced - Above + reactions, editing, moderation -D) Full - Above + notifications, mentions, markdown - -User: B - Standard with threading - -You: Great. For threading, should replies be: - -A) Unlimited depth (comments on comments on comments...) -B) One level only (reply to top-level comments) -C) Two levels (reply and one nested level) - -[... conversation continues, approaches explored ...] - -You: Based on our discussion, here's what we've agreed on: - -- Standard comment system with one level of threading -- Comments require name, email, and text -- Newest first, replies nested under parent -- Markdown support for comment text -- No moderation in first version (out of scope) - -I'll save this as a brainstorm document. How would you like to proceed? - -A) Specify step-by-step (/speckit-specify) -B) Ship autonomously (/speckit-spex-ship) -C) Done for now -``` diff --git a/.specify/extensions/spex/commands/speckit.spex.evolve.md b/.specify/extensions/spex/commands/speckit.spex.evolve.md deleted file mode 100644 index 5b5bd7d4..00000000 --- a/.specify/extensions/spex/commands/speckit.spex.evolve.md +++ /dev/null @@ -1,562 +0,0 @@ ---- -description: "Handle spec/code mismatches through AI-guided analysis and user-controlled evolution" ---- - -# Spec Evolution and Reconciliation - -## Overview - -Handle spec/code mismatches through AI-guided analysis and user-controlled evolution. - -Specs WILL diverge from code. This is normal and healthy. The question is: which should change? - -This skill detects divergence, analyzes the mismatch, recommends resolution, and executes the change. - -## When to Use - -**Use this skill when:** -- Code review detects spec/code mismatch -- Verification finds spec compliance issues -- Developer explicitly requests evolution -- Implementation reveals better approach than spec -- Spec ambiguity discovered during implementation - -**Auto-triggered by:** -- `speckit-spex-gates-review-code` (when deviations found) -- `speckit-spex-gates-stamp` (when compliance fails) -- `speckit-spex-finish` (when verification finds compliance issues) - -**Don't use this skill when:** -- No mismatch exists (everything compliant) -- Spec doesn't exist yet -> Use `/speckit-specify` -- Multiple specs need consolidation -> Use `speckit-spex-spec-refactoring` - -## Prerequisites - -Spec-kit must be initialized. If `.specify/` directory does not exist, tell the user to run `/spex:init` first and stop. - -## Spec Selection - -If no spec is specified, discover available specs: - -```bash -# List all specs in the project -find specs/ -name "spec.md" -type f 2>/dev/null | head -20 -``` - -**If specs found:** Present list and ask user to select one using AskUserQuestion. - -Example: -``` -Found 2 specs in this project: -1. specs/0001-user-auth/spec.md -2. specs/0002-api-gateway/spec.md - -Which spec needs evolution/reconciliation? -``` - -**If no specs found:** Inform user: -``` -No specs found in specs/ directory. - -Spec evolution requires an existing spec to evolve. -Use `speckit-spex-brainstorm` or `/speckit-specify` to create one first. -``` - -## The Process - -### 0. Check Existing Artifacts - -Before analyzing or modifying, verify spec-kit artifacts exist: - -```bash -# Check what artifacts exist for this feature -SPEC_DIR="specs/[feature-dir]" # Replace with actual spec directory -echo "Checking for existing artifacts..." -[ -f "$SPEC_DIR/spec.md" ] && echo "spec.md exists" || echo "spec.md missing" -[ -f "$SPEC_DIR/plan.md" ] && echo "plan.md exists" || echo "plan.md not present" -[ -f "$SPEC_DIR/tasks.md" ] && echo "tasks.md exists" || echo "tasks.md not present" -``` - -**If spec.md is missing:** Cannot proceed with evolution. Use `/speckit-specify` first. - -**If plan.md or tasks.md exist:** -- These were generated by spec-kit and must be kept in sync -- After modifying the spec, regenerate dependent artifacts using: - - `/speckit-plan` - Regenerate plan - - `/speckit-tasks` - Regenerate tasks - - `/speckit-analyze` - Verify consistency - -**CRITICAL: Never manually edit plan.md or tasks.md. Always regenerate via /speckit-* commands after spec changes.** - -### 1. Detect Mismatches - -**Identify all spec/code divergences:** - -```bash -# Read spec -cat specs/features/[feature-name].md - -# Compare to implementation -# For each requirement in spec: -# - What does spec say? -# - What does code do? -# - Do they match? -``` - -**Categorize each mismatch:** -- **Missing in code**: Spec requires it, code doesn't have it -- **Extra in code**: Code implements it, spec doesn't mention it -- **Different behavior**: Spec says X, code does Y -- **Ambiguous spec**: Spec unclear, code made assumption - -**Document all mismatches with:** -- Spec requirement (quote from spec) -- Actual implementation (what code does) -- Location (file:line in code, section in spec) - -### 2. Analyze Each Mismatch - -**For each mismatch, determine:** - -**Type:** -- Architectural (affects system design) -- Behavioral (changes functionality) -- Cosmetic (naming, organization, details) - -**Severity:** -- **Critical**: Breaking change, security issue, data loss -- **Major**: Significant behavior change, API contract change -- **Minor**: Small deviation, non-breaking addition -- **Trivial**: Naming, formatting, implementation details - -**Impact:** -- User-facing vs internal -- Breaking vs non-breaking -- Risky vs safe - -### 3. Recommend Resolution - -**For each mismatch, recommend:** - -**Option A: Update Spec** -- When: Implementation reveals better approach -- Why: Spec was incomplete/wrong, code is better -- Impact: Spec changes to match reality - -**Option B: Fix Code** -- When: Code deviates from intended design -- Why: Spec is correct, code is wrong -- Impact: Code changes to match spec - -**Option C: Clarify Spec** -- When: Spec was ambiguous, code made reasonable choice -- Why: Make implicit explicit -- Impact: Spec expanded with details, code unchanged - -**Provide reasoning for recommendation:** -- Why this option is best -- Trade-offs of alternatives -- Risk assessment -- User impact - -### 4. Decide Resolution - -**Decision flow:** - -``` -Is this mismatch trivial/minor AND auto-update enabled? - Yes -> Auto-update with notification - No -> Ask user to decide - -User decides: - A) Update spec - B) Fix code - C) Clarify spec - D) Defer (mark as known deviation) -``` - -**Check user configuration:** - -```json -{ - "spex": { - "auto_update_spec": { - "enabled": true, - "threshold": "minor", - "notify": true - } - } -} -``` - -**Thresholds:** -- `none`: Never auto-update -- `minor`: Auto-update trivial/minor mismatches -- `moderate`: Include non-breaking behavioral changes - -### 5. Execute Resolution - -**Option A: Update Spec** - -1. Modify spec to match implementation -2. Add to spec changelog -3. Validate updated spec for soundness -4. Commit spec change with clear message - -```bash -# Commit -git add specs/features/[feature].md -git commit -m "Update spec: [change] - -Implementation revealed [reason for change]. - -Previous: [old requirement] -Updated: [new requirement] - -Assisted-By: Claude Code" -``` - -**Option B: Fix Code** - -1. Modify code to match spec -2. Update tests if needed -3. Verify spec compliance -4. Commit code change - -```bash -# Commit -git add [files] -git commit -m "Fix: Align [component] with spec - -Code was [what it did], spec requires [what spec says]. - -Updated to match spec requirement: [spec section] - -Assisted-By: Claude Code" -``` - -**Option C: Clarify Spec** - -1. Add detail to spec (keep code unchanged) -2. Make implicit assumptions explicit -3. Add to spec changelog -4. Commit clarification - -**Option D: Defer** - -1. Document as known deviation -2. Add to spec's "Known Deviations" section -3. Note reason and plan to address -4. Commit documentation - -### 6. Verify Reconciliation - -**After resolution:** - -```bash -# Re-check spec compliance -# Ensure mismatch is resolved -# Verify no new mismatches introduced -``` - -**If spec was updated (Options A or C), regenerate dependent artifacts:** - -**Regenerate plan and tasks to stay in sync:** - -If plan.md exists, invoke `/speckit-plan` to regenerate it. -If tasks.md exists, invoke `/speckit-tasks` to regenerate it. -Invoke `/speckit-analyze` to verify consistency across all artifacts. - -**VERIFICATION CHECKPOINT:** - -After regeneration, verify artifacts are in sync using `/speckit-analyze`. - -**Confirm:** -- Spec and code now aligned -- Tests still passing -- No regressions introduced -- All spec-kit artifacts regenerated and in sync - -## Checklist - -Use TodoWrite to track: - -- [ ] Detect all spec/code mismatches -- [ ] Categorize each mismatch (type, severity, impact) -- [ ] Analyze and recommend resolution for each -- [ ] Check user configuration for auto-update -- [ ] Decide resolution (auto or ask user) -- [ ] Execute resolution (update spec, fix code, or clarify) -- [ ] Verify reconciliation complete -- [ ] Commit changes with clear messages -- [ ] Update documentation if needed - -## Examples - -### Example 1: Auto-Update (Minor Addition) - -``` -[During verification] - -Spec compliance check: 95% - -Mismatch detected: - -**Mismatch 1: Response includes timestamp (MINOR)** -- **Spec says:** Return `{ id, name, email }` -- **Code does:** Returns `{ id, name, email, updated_at }` -- **Location:** src/api/users.ts:45, specs/features/user-api.md#response - -**Analysis:** -- Type: Behavioral (but non-breaking addition) -- Severity: Minor -- Impact: Non-breaking, adds useful information - -**Recommendation: Update Spec (Option A)** - -**Reasoning:** -- `updated_at` is standard practice for update endpoints -- Provides useful information to clients -- Non-breaking addition (clients can ignore it) -- Implementation is better than spec - -**User config:** auto_update_spec.threshold = "minor" - -**Action:** Auto-updating spec... - -Updated specs/features/user-api.md: -- Added `updated_at` to response format -- Changelog: "2025-11-10: Added updated_at timestamp to responses" - -Spec updated -Compliance now 100% -Committed to git - -Notification: Spec auto-updated with updated_at field (minor addition) -``` - -### Example 2: User Decision (Major Change) - -``` -[During code review] - -Spec compliance check: 85% - -Mismatch detected: - -**Mismatch 1: Validation behavior difference (MAJOR)** -- **Spec says:** "Reject requests with invalid avatar_url, return 422" -- **Code does:** "Accept invalid URLs, set avatar_url to null, return 200" -- **Location:** src/middleware/validation.ts:78, specs/features/user-profile.md#validation - -**Analysis:** -- Type: Behavioral (changes error handling) -- Severity: Major -- Impact: User-facing, affects API contract - -**Options:** - -**A) Update Spec - Lenient validation** -- Pros: More user-friendly, prevents frustration -- Cons: Inconsistent with other validation (strict) -- Risk: Users might not know avatar failed - -**B) Fix Code - Strict validation** -- Pros: Consistent with spec and other validation -- Cons: Worse UX when URL is invalid -- Risk: None (matches spec) - -**C) Clarify Spec - Explicit fallback** -- Pros: Best of both (validate but handle gracefully) -- Cons: More complex -- Risk: None - -**Recommendation: Option C** - -**Reasoning:** -Best practice is to validate input but handle gracefully. Update spec to explicitly define fallback behavior: validate URL, set to null if invalid, log warning, return success with indication. - -Which option do you prefer? - -User: C - explicit fallback - -You: Perfect. Updating spec to clarify fallback behavior... - -Updated spec: - -## Validation Rules - -### avatar_url -- Must be valid URL format if provided -- **Fallback behavior:** If URL validation fails: - - Set avatar_url to null - - Log validation error - - Return 200 (success) with warning in response - - Response includes: `warnings: ["avatar_url invalid, set to null"]` - -Code now matches clarified spec - -Spec compliance: 100% - -Committed to git -``` - -### Example 3: Fix Code (Spec Correct) - -``` -[During verification] - -Spec compliance check: 90% - -Mismatch detected: - -**Mismatch 1: Missing error handling (CRITICAL)** -- **Spec says:** "Handle database connection errors with retry logic (3 attempts)" -- **Code does:** "Single attempt, fails immediately on error" -- **Location:** src/db/client.ts:34, specs/features/data-persistence.md#error-handling - -**Analysis:** -- Type: Architectural (missing reliability mechanism) -- Severity: Critical -- Impact: Production reliability risk - -**Recommendation: Fix Code (Option B)** - -**Reasoning:** -- Spec defines critical reliability requirement -- Single-attempt fails too easily (production risk) -- Retry logic is standard practice -- Code is missing required feature - -**No user decision needed - critical spec requirement missing** - -Fixing code to implement retry logic... - -Tests updated to verify retry behavior -Spec compliance: 100% - -Committed code fix -``` - -## Auto-Update Configuration - -### Recommended Settings - -**For solo projects:** -```json -{ - "auto_update_spec": { - "enabled": true, - "threshold": "minor", - "notify": true - } -} -``` - -**For team projects:** -```json -{ - "auto_update_spec": { - "enabled": true, - "threshold": "none", - "notify": true - } -} -``` - -**For experimental work:** -```json -{ - "auto_update_spec": { - "enabled": true, - "threshold": "moderate", - "notify": true - } -} -``` - -## Classification Guidelines - -### Minor (Auto-Update Eligible) -- Adding timestamps to responses -- Renaming variables/functions -- Reorganizing code structure -- Adding logging/instrumentation -- Implementation detail changes - -### Major (Always Ask) -- Changing API contracts -- Modifying behavior -- Adding/removing features -- Changing error handling -- Architectural changes - -### Critical (Usually Fix Code) -- Security issues -- Data integrity problems -- Missing required features -- Incorrect business logic - -## Common Patterns - -### Pattern: Better Error Messages - -**Mismatch:** Spec says "return error", code returns detailed error with context - -**Resolution:** Update spec (minor) -- More detailed errors are better -- Non-breaking improvement - -### Pattern: Missing Edge Case - -**Mismatch:** Spec doesn't mention empty array, code handles it - -**Resolution:** Clarify spec (add edge case) -- Make implicit explicit -- Document intended behavior - -### Pattern: Performance Optimization - -**Mismatch:** Spec doesn't specify caching, code adds cache - -**Resolution:** Update spec (moderate) -- Document optimization in spec -- Ensure cache behavior is correct - -### Pattern: Different Architecture - -**Mismatch:** Spec implies synchronous, code is async - -**Resolution:** Ask user (major) -- Significant architectural change -- May affect other components - -## Remember - -**Spec evolution is normal and healthy.** - -- Specs are not contracts set in stone -- Implementation reveals reality -- Better approaches emerge during coding -- Ambiguities get discovered - -**The goal is alignment, not rigidity.** - -- Specs guide implementation -- Implementation informs specs -- Both should reflect truth - -**Always provide reasoning:** -- Why update spec vs fix code -- What's the impact -- What are the trade-offs - -**Trust the process:** -- Detect mismatches early -- Analyze thoughtfully -- Recommend clearly -- Execute decisively -- Verify completely - -**Spec and code in sync = quality software.** diff --git a/.specify/extensions/spex/commands/speckit.spex.extensions.md b/.specify/extensions/spex/commands/speckit.spex.extensions.md deleted file mode 100644 index f65255fb..00000000 --- a/.specify/extensions/spex/commands/speckit.spex.extensions.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -description: "Manage spex extensions: enable, disable, or list active extensions" ---- - -# Spex Extensions Management - -Manage which spex extensions are active. Extensions provide additional capabilities such as quality gates, team orchestration, worktree isolation, and deep review. - -**Available extensions**: `spex-gates`, `spex-deep-review`, `spex-teams`, `spex-worktrees` - ---- - -## Parse Arguments - -Parse `$ARGUMENTS` for the subcommand and optional extension name: - -- No arguments or `list` -> **List** -- `enable ` -> **Enable** -- `disable ` -> **Disable** - -## Subcommand: List (default) - -Run via Bash: - -```bash -specify extension list -``` - -Display the output to the user. - -## Subcommand: Enable - -Run via Bash: - -```bash -specify extension enable -``` - -Report the result to the user. - -## Subcommand: Disable - -1. Run `specify extension list` and check if the extension is already disabled. If so, report that and STOP. -2. **Warn the user**: Disabling an extension requires regenerating all spec-kit files, which resets any manual customizations to `.claude/skills/speckit-*/SKILL.md` and `.specify/templates/*.md` files. -3. Use `AskUserQuestion` to confirm: - - **Question**: "Disabling an extension will reset all spec-kit files to defaults (losing any manual customizations). Proceed?" - - **Header**: "Confirm" - - **Options**: - - Label: "Yes, disable", Description: "Reset spec-kit files and remove this extension's overlays" - - Label: "Cancel", Description: "Keep current extension configuration unchanged" -4. If cancelled: report "Extension disable cancelled." and STOP. -5. If confirmed, run these commands sequentially via Bash: - ```bash - specify extension disable - specify init --here --ai claude --force - specify extension apply - ``` -6. Report which extensions are still active. diff --git a/.specify/extensions/spex/commands/speckit.spex.finish.md b/.specify/extensions/spex/commands/speckit.spex.finish.md deleted file mode 100644 index 11b44ad9..00000000 --- a/.specify/extensions/spex/commands/speckit.spex.finish.md +++ /dev/null @@ -1,250 +0,0 @@ ---- -description: "Complete a feature: verify, then merge/PR/keep with worktree-aware cleanup" -argument-hint: "[--create-pr]" ---- - -# Finish - Verify and Complete a Feature - -## Ship Pipeline Guard - -If `.specify/.spex-state` exists and its `status` is `running`, this command is part of an autonomous pipeline. Check the `ask` field and `create_pr` flag: - -```bash -AUTONOMOUS_MODE=false -AUTO_CREATE_PR=false -if [ -f ".specify/.spex-state" ]; then - STATUS=$(jq -r '.status // empty' .specify/.spex-state 2>/dev/null) - ASK=$(jq -r '.ask // "always"' .specify/.spex-state 2>/dev/null) - CREATE_PR=$(jq -r '.create_pr // false' .specify/.spex-state 2>/dev/null) - if [ "$STATUS" = "running" ] && [ "$ASK" != "always" ]; then - AUTONOMOUS_MODE=true - fi - if [ "$CREATE_PR" = "true" ]; then - AUTO_CREATE_PR=true - fi -fi -``` - -In autonomous mode: suppress all AskUserQuestion prompts. Auto-select "Merge to default branch" (or "Create PR" if `create_pr` is true in state or `--create-pr` argument is passed). - -## Argument Parsing - -If the argument `--create-pr` is passed, set `AUTO_CREATE_PR=true`. This skips the options prompt and goes directly to PR creation. - -## Phase 1: Verification - -Invoke `/speckit-spex-gates-verify` (the full verification gate). This runs: -1. Full test suite -2. Code hygiene review -3. Spec compliance validation (100% required) -4. Spec drift check -5. Success criteria verification - -**If verification fails:** STOP. Display the verification report with blocking issues. Do not proceed to merge/PR options. The user must fix the issues and re-run `/speckit-spex-finish`. - -**If verification passes:** Continue to Phase 2. - -## Phase 2: Commit Outstanding Changes - -Stage and commit any remaining tracked modifications: - -```bash -git add -u -if ! git diff --cached --quiet; then - git commit -m "chore: final changes before merge - -Assisted-By: 🤖 Claude Code" -fi -``` - -If the working tree is clean, skip this step. - -## Phase 3: Context Detection - -Detect the current environment to determine how to proceed: - -```bash -GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) -REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) -IN_WORKTREE=false -if [ "$GIT_DIR" != "$REPO_ROOT/.git" ] && [ "$GIT_DIR" != ".git" ]; then - IN_WORKTREE=true -fi - -CURRENT_BRANCH=$(git branch --show-current) - -DEFAULT_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@') -if [ -z "$DEFAULT_BRANCH" ]; then - for candidate in main master; do - if git rev-parse --verify "$candidate" >/dev/null 2>&1; then - DEFAULT_BRANCH="$candidate" - break - fi - done -fi -DEFAULT_BRANCH=${DEFAULT_BRANCH:-main} -``` - -**If already on the default branch:** Report "Verification passed. You are already on the default branch; no merge needed." Clean up state file (`rm -f .specify/.spex-state`). STOP. - -## Phase 4: Select Action - -If `AUTO_CREATE_PR` is true (from `--create-pr` argument or state file): skip the prompt and go directly to **Option B: Create PR**. - -If `AUTONOMOUS_MODE` is true: skip the prompt and go directly to **Option A: Merge to default branch**. - -Otherwise, present options using `AskUserQuestion` (`multiSelect: false`, header: "Finish"): - -**"Feature verified. How would you like to complete it?"** - -Options: -1. **"Merge to default branch (Recommended)"**: "Fast-forward merge into the default branch, clean up branch and worktree" -2. **"Push and create PR"**: "Push branch and open a pull request for team review" -3. **"Keep branch as-is"**: "Leave branch for manual handling later" - -## Phase 5: Execute Action - -### Option A: Merge to Default Branch - -**If in a worktree (`IN_WORKTREE` is true):** - -```bash -MAIN_WORKTREE=$(git worktree list --porcelain | head -1 | sed 's/^worktree //') -WORKTREE_PATH=$(git rev-parse --show-toplevel) - -# CRITICAL: Switch cwd to main worktree BEFORE any destructive operations. -# If cwd is inside the worktree being removed, subsequent commands fail. -cd "$MAIN_WORKTREE" - -git checkout "$DEFAULT_BRANCH" - -# Try fast-forward first, fall back to merge commit -git merge --ff-only "$CURRENT_BRANCH" 2>&1 -``` - -If fast-forward fails (branches diverged), ask the user (unless autonomous mode): - -In autonomous mode: create a merge commit automatically. - -Otherwise use `AskUserQuestion` (`multiSelect: false`, header: "Merge"): -- "Create merge commit": "Branches have diverged, merge with a merge commit" -- "Abort": "Keep worktree, resolve manually" - -If "Abort": STOP. The cwd is already at the main worktree. - -If merge commit: -```bash -git merge "$CURRENT_BRANCH" -m "Merge branch '$CURRENT_BRANCH' - -Assisted-By: 🤖 Claude Code" 2>&1 -``` - -After merge succeeds, remove worktree and branch: -```bash -git worktree remove "$WORKTREE_PATH" 2>&1 -git branch -d "$CURRENT_BRANCH" 2>&1 || git branch -D "$CURRENT_BRANCH" 2>&1 -``` - -**If NOT in a worktree:** - -```bash -git checkout "$DEFAULT_BRANCH" -git merge --ff-only "$CURRENT_BRANCH" 2>&1 -``` - -If fast-forward fails: same divergence handling as worktree path above. - -After merge: -```bash -git branch -d "$CURRENT_BRANCH" -``` - -Report: -``` -Merged `` into ``. Feature branch deleted. -``` - -### Option B: Push and Create PR - -```bash -REMOTE=$(git remote | grep -x upstream 2>/dev/null || echo origin) -BRANCH=$(git branch --show-current) -SPEC_DIR="specs/${BRANCH}" -FEATURE_NAME=$(head -1 "$SPEC_DIR/spec.md" | sed 's/^# Feature Specification: //') - -REVIEWERS_REL="$SPEC_DIR/REVIEWERS.md" -REVIEWERS_LINK="" -if [ -f "$REVIEWERS_REL" ]; then - REMOTE_URL=$(git remote get-url "$REMOTE" 2>/dev/null | sed 's/\.git$//' | sed 's|git@github.com:|https://github.com/|') - REVIEWERS_URL="${REMOTE_URL}/blob/${BRANCH}/${REVIEWERS_REL}" - REVIEWERS_LINK="> **[Review Guide](${REVIEWERS_URL})** for full context: motivation, key decisions, and scope boundaries." -fi - -COLLAB_CONFIG=".specify/extensions/spex-collab/collab-config.yml" -LABEL_FLAG="" -if [ -f "$COLLAB_CONFIG" ]; then - LABELS_ENABLED=$(yq -r '.labels.enabled // true' "$COLLAB_CONFIG" 2>/dev/null || echo "true") - IMPL_LABEL=$(yq -r '.labels.implement // "spex/implement"' "$COLLAB_CONFIG" 2>/dev/null || echo "spex/implement") - if [ "$LABELS_ENABLED" = "true" ]; then - LABEL_FLAG="--label ${IMPL_LABEL}" - fi -fi - -git push -u "$REMOTE" "$BRANCH" - -gh pr create \ - --title "$FEATURE_NAME [Spec + Impl]" ${LABEL_FLAG} \ - --body "$(cat < is verified and ready. Nothing was merged or pushed. - -When ready to finish: - /speckit-spex-finish Run again to merge or create PR -``` - -If NOT in a worktree: -``` -Branch is verified and ready. Nothing was merged or pushed. - -When ready to finish: - /speckit-spex-finish Run again to merge or create PR -``` - -## Phase 6: State and Status Line Cleanup - -After executing any option (merge, PR, or keep), remove the state file from both possible locations (absolute path from ship pipeline, and relative path from flow mode): - -```bash -rm -f .specify/.spex-state -if [ -n "${SHIP_STATE_FILE:-}" ] && [ -f "$SHIP_STATE_FILE" ]; then - rm -f "$SHIP_STATE_FILE" -fi -``` - -This removes the state file, which dismisses the status line (the statusline script exits silently when no state file exists). Works for both ship mode (where `SHIP_STATE_FILE` may point to a worktree path) and flow mode (where the state file is always relative). diff --git a/.specify/extensions/spex/commands/speckit.spex.flow-state.md b/.specify/extensions/spex/commands/speckit.spex.flow-state.md deleted file mode 100644 index 179754be..00000000 --- a/.specify/extensions/spex/commands/speckit.spex.flow-state.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -description: "Create or update flow state for step-by-step SDD workflow tracking" -argument-hint: "[running |clarified|implemented|gate ]" ---- - -# Flow State Management - -This command manages the `.specify/.spex-state` file with `"mode": "flow"` to enable the status line during step-by-step SDD workflow (as opposed to the autonomous ship pipeline). - -## Execution - -Locate and run the `spex-flow-state.sh` script, passing through all arguments: - -```bash -FLOW_STATE="$(find ~/.claude -name 'spex-flow-state.sh' 2>/dev/null | head -1)" -[ -x "$FLOW_STATE" ] || { echo "ERROR: spex-flow-state.sh not found"; exit 1; } -"$FLOW_STATE" "$@" -``` - -If invoked with no arguments (from the `after_specify` hook), pass `create`: - -```bash -"$FLOW_STATE" create -``` - -If invoked with `--spec-dir` context available (e.g., the spec directory is known), pass it: - -```bash -"$FLOW_STATE" create --spec-dir "specs/034-unified-setup-command" -``` - -## Available Commands - -| Command | Hook | What it does | -|---------|------|-------------| -| `create [--spec-dir ]` | `after_specify` | Create or update flow state (preserves gate fields if already exists) | -| `running ` | `before_*` | Set active phase shown as `▶` in status line | -| `running done` | `after_*` | Clear active phase indicator | -| `clarified` | `after_clarify` | Mark clarification complete | -| `implemented` | `after_implement` | Mark implementation complete | -| `gate ` | spex-gates hooks | Mark quality gate passed (review-spec, review-plan, review-code) | -| `cleanup` | (manual) | Remove state file | - -All commands are silent (no output) unless an error occurs. Ship mode state files are never overwritten. diff --git a/.specify/extensions/spex/commands/speckit.spex.help.md b/.specify/extensions/spex/commands/speckit.spex.help.md deleted file mode 100644 index fabf931c..00000000 --- a/.specify/extensions/spex/commands/speckit.spex.help.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -description: "Quick reference for all spex commands and workflow" ---- - -# spex Help - -## Overview - -Display the spex quick reference with workflow diagram, command list, and guidance. - -## Behavior - -1. Read and display the quick reference content from `spex/docs/help.md` -2. Display the content exactly as written -3. Ask: "Any questions about the spex workflow? I can explain any command in detail." - -## Key Principles - -- **Reference mode is fast**: Just display the help content -- **Non-pushy**: Offer options, don't force workflows diff --git a/.specify/extensions/spex/commands/speckit.spex.ship.md b/.specify/extensions/spex/commands/speckit.spex.ship.md deleted file mode 100644 index 0e9d011a..00000000 --- a/.specify/extensions/spex/commands/speckit.spex.ship.md +++ /dev/null @@ -1,847 +0,0 @@ ---- -description: "Autonomous full-cycle workflow: specify through verify with configurable oversight levels, auto-fix, and optional PR creation" ---- - -# Autonomous Full-Cycle Workflow (speckit-spex-ship) - -## CONTINUOUS EXECUTION RULE (READ THIS FIRST) - -**This pipeline runs ALL stages without stopping.** After completing any stage, you MUST immediately begin the next stage. There are no natural stopping points between stages. - -- Do NOT say "Ready for the next stage" and wait. -- Do NOT say "Shall I proceed?" and wait. -- Do NOT say "Proceeding to..." and wait. -- Do NOT treat a stage completion as a task completion. -- Do NOT output a summary and stop. - -The pipeline is ONE continuous task. It starts at the first stage and runs through the last stage. The ONLY reasons to pause are: -1. `ask` is `always` AND a review stage has findings requiring user input. -2. A blocker error occurs (test failure, syntax error, security issue). -3. All 9 stages have completed. - -**After every stage: update the state file, then immediately start the next stage.** No waiting, no confirmation, no stopping. - -## Overview - -This skill chains the entire spex workflow autonomously: specify, clarify, review-spec, plan, review-plan, tasks, implement, deep-review, and verify. Point it at a brainstorm document and choose an oversight level to control how much human oversight the pipeline requires. - -**This skill requires both `spex-gates` and `spex-deep-review` extensions to be enabled.** - -## Prerequisites - -### Extension Validation - -Check that required extensions are enabled: - -```bash -# Check for enabled extensions -GATES=$(specify extension list 2>/dev/null | grep -c 'spex-gates.*enabled' || echo 0) -DEEP_REVIEW=$(specify extension list 2>/dev/null | grep -c 'spex-deep-review.*enabled' || echo 0) - -if [ "$GATES" = "0" ] || [ "$DEEP_REVIEW" = "0" ]; then - echo "ERROR: speckit-spex-ship requires both spex-gates and spex-deep-review extensions." - echo "" - echo "Enable them with:" - echo " specify extension enable spex-gates spex-deep-review" - echo "" - echo "Missing extensions:" - [ "$GATES" = "0" ] && echo " - spex-gates" - [ "$DEEP_REVIEW" = "0" ] && echo " - spex-deep-review" -fi -``` - -If either extension is missing, **STOP** with the error message above. Do not proceed. - -### Dirty Worktree Check - -Before starting the pipeline, check for uncommitted changes that are NOT spex configuration files: - -```bash -# Filter out spex-generated files from dirty check -DIRTY=$(git status --porcelain 2>/dev/null | grep -v -E '^.{2} \.claude/(commands/speckit\.|settings)' | grep -v -E '^.{2} \.specify/(extensions/|\.spex-)' || true) -if [ -n "$DIRTY" ]; then - echo "Working tree has uncommitted non-spex changes:" - echo "$DIRTY" -fi -``` - -If there are dirty non-spex files, commit them automatically with a "WIP: save before ship" message, then proceed. Do NOT stop or ask the user. Spex config files (`.claude/skills/speckit-*`, `.claude/settings.*`, `.specify/extensions/`) are expected to be dirty after init and should be ignored. - -### External Tool Auth Validation - -If `--coderabbit` is explicitly set (not just inherited from config defaults), validate authentication at startup: - -```bash -# Only check if --coderabbit was explicitly passed as a flag -which coderabbit >/dev/null 2>&1 && coderabbit auth status 2>&1 || echo "CODERABBIT_AUTH_FAILED" -``` - -If auth check fails when CodeRabbit was explicitly requested, **STOP** with: -``` -ERROR: CodeRabbit authentication failed. -You explicitly requested CodeRabbit with --coderabbit, but auth is not configured. -Run without --coderabbit or configure CodeRabbit authentication first. -``` - -If CodeRabbit is only enabled via config defaults (not explicit flag), skip auth validation and let the deep-review stage handle missing tools gracefully. - -## Argument Parsing - -Parse the invocation arguments. The skill accepts: - -### Positional Argument - -- **brainstorm-file**: Path to a brainstorm document in `brainstorm/`. If omitted, auto-detect (see Brainstorm File Resolution below). - -### Flags - -| Flag | Default | Description | -|------|---------|-------------| -| `--ask ` | `smart` | One of: `always`, `smart`, `never` | -| `--create-pr` | off | Create a pull request after successful completion | -| `--resume` | off | Resume an interrupted pipeline from state file | -| `--start-from ` | (none) | Start from a specific stage (skips prior stages) | -| `--no-external` | (from config) | Disable all external review tools | -| `--external` | (from config) | Enable all external review tools | -| `--no-coderabbit` | (from config) | Disable CodeRabbit | -| `--coderabbit` | (from config) | Enable CodeRabbit | -| `--no-copilot` | (from config) | Disable Copilot | -| `--copilot` | (from config) | Enable Copilot | - -### Flag Resolution - -**Oversight level**: Validate that the value is one of `always`, `smart`, `never`. If invalid, fail with: -``` -ERROR: Invalid oversight level "X". Must be one of: always, smart, never -``` - -**External tool flags**: Follow the same resolution pattern as the `review-code` skill: - -1. Read config defaults from deep-review extension config: - ```bash - DEEP_REVIEW_CONFIG=".specify/extensions/spex-deep-review/deep-review-config.yml" - DEFAULT_CODERABBIT=$(yq -r '.external_tools.coderabbit // true' "$DEEP_REVIEW_CONFIG" 2>/dev/null) - DEFAULT_COPILOT=$(yq -r '.external_tools.copilot // true' "$DEEP_REVIEW_CONFIG" 2>/dev/null) - ``` - -2. Start with config defaults: - ``` - coderabbit = DEFAULT_CODERABBIT - copilot = DEFAULT_COPILOT - ``` - -3. Apply CLI flag overrides (flags always win, applied in order): - - `--external` sets both to true - - `--no-external` sets both to false - - `--coderabbit` / `--no-coderabbit` overrides coderabbit only - - `--copilot` / `--no-copilot` overrides copilot only - -4. Track whether `--coderabbit` was explicitly set (for auth validation). - -**`--resume` and `--start-from` are mutually exclusive** with each other. If both are provided, fail with: -``` -ERROR: Cannot use both --resume and --start-from. Choose one. -``` - -**`--resume` does not accept a brainstorm file.** If `--resume` is set alongside a brainstorm file, fail with: -``` -ERROR: Cannot specify a brainstorm file with --resume. The brainstorm file is read from the state file. -``` - -**`--start-from` allows a brainstorm file** when starting from `specify` (since it needs one). When starting from any other stage, a brainstorm file argument is ignored. - -### Valid Stage Names for --start-from - -The following stage names are accepted: `specify`, `clarify`, `review-spec`, `plan`, `tasks`, `review-plan`, `implement`, `review-code`, `finish`. - -If an invalid stage name is provided, fail with: -``` -ERROR: Invalid stage "X". Valid stages are: specify, clarify, review-spec, plan, tasks, review-plan, implement, review-code, finish -``` - -## Brainstorm File Resolution - -Resolve the brainstorm document to use as input: - -**If a path is provided**: Validate it exists. -```bash -[ -f "$BRAINSTORM_FILE" ] || echo "ERROR: Brainstorm file not found: $BRAINSTORM_FILE" -``` - -**If no path is provided**: Auto-detect the highest-numbered brainstorm file: -```bash -ls -1 brainstorm/[0-9]*.md 2>/dev/null | sort -t/ -k2 -V | tail -1 -``` - -**If no brainstorm files found**: Fail with: -``` -ERROR: No brainstorm files found in brainstorm/ directory. - -Available files: -$(ls brainstorm/ 2>/dev/null || echo " (directory does not exist)") - -Create a brainstorm document first with /speckit-spex-brainstorm -``` - -## State File Management - -The pipeline tracks its progress in `.specify/.spex-state` as JSON. **All state file operations use the `spex-ship-state.sh` script. Never write the state file directly.** - -Locate the script and set the absolute state file path: -```bash -SHIP_STATE="$(find ~/.claude -name 'spex-ship-state.sh' 2>/dev/null | head -1)" -# Use absolute path so state file location survives CWD changes (e.g., worktree switches) -export SHIP_STATE_FILE="$(pwd -P)/.specify/.spex-state" -``` - -**IMPORTANT:** Both `SHIP_STATE` (script path) and `SHIP_STATE_FILE` (absolute state file path) must be set before any state operations. The `SHIP_STATE_FILE` env var ensures the state script and statusline script always reference the same file, even when CWD changes during worktree creation. - -### Available Commands - -| Command | What it does | -|---------|-------------| -| `spex-ship-state.sh create [--ask ] [--start-from ]` | Create state file at pipeline start | -| `spex-ship-state.sh advance` | Advance to the next stage (auto-cleans up after stage 8) | -| `spex-ship-state.sh status` | Show current stage and status | -| `spex-ship-state.sh pause` | Set status to paused | -| `spex-ship-state.sh fail` | Set status to failed | -| `spex-ship-state.sh cleanup` | Remove state file (pipeline done) | - -### Stage Transitions - -**After every stage completes**, run: -```bash -SHIP_STATE_FILE="$SHIP_STATE_FILE" "$SHIP_STATE" advance -``` - -This advances `stage` and `stage_index` to the next stage with `status: running`. After the final stage (verify), `advance` automatically removes the state file and outputs `PIPELINE_COMPLETE`. - -**Do NOT manually write JSON to the state file. Always use the script.** - -## Ship Pipeline Guard - -The `.specify/.spex-state` file serves as a signal to sub-commands running inside the pipeline. When this file exists with `status: running`, each `/speckit-*` command MUST: - -- Complete its work normally -- Do NOT output a completion summary -- Do NOT ask "Shall I proceed?" or similar -- Do NOT use AskUserQuestion (unless `ask` is `always`) -- Return immediately so the pipeline can advance - -### speckit-specify guard - -When `.specify/.spex-state` exists with `status: running`: -- Complete the specification work normally -- Do NOT ask "Shall I proceed?" after spec creation -- Return immediately so the pipeline can advance - -### speckit-clarify guard - -When `.specify/.spex-state` exists with `status: running`: -- If `ask` is `smart` or `never`: Do NOT present clarification questions to the user. Select the recommended answer for each question yourself. Process all questions in a single pass and update the spec. -- Do NOT output a completion summary -- Return immediately so the pipeline can advance - -### speckit-plan guard - -When `.specify/.spex-state` exists with `status: running`: -- Complete the planning work normally -- Do NOT ask "Shall I proceed?" or "Ready for implementation." -- Return immediately so the pipeline can advance - -### speckit-tasks guard - -When `.specify/.spex-state` exists with `status: running`: -- Complete the task generation normally -- Do NOT ask "Shall I proceed?" or suggest next steps -- Return immediately so the pipeline can advance - -### speckit-implement guard - -When `.specify/.spex-state` exists with `status: running`: -- Complete the implementation work normally -- Do NOT output a completion summary -- Do NOT ask "Shall I proceed?" or suggest next steps -- Return immediately so the pipeline can advance - -## Resume Logic - -When `--resume` is set: - -1. Read the state file: - ```bash - if [ ! -f .specify/.spex-state ]; then - echo "ERROR: No interrupted pipeline found." - echo "Start a new pipeline with: /speckit-spex-ship " - exit 1 - fi - STATE=$(cat .specify/.spex-state) - ``` - -2. Extract the last stage and its index: - ```bash - LAST_STAGE=$(echo "$STATE" | jq -r '.stage') - LAST_INDEX=$(echo "$STATE" | jq -r '.stage_index') - AUTONOMY=$(echo "$STATE" | jq -r '.ask') - BRAINSTORM=$(echo "$STATE" | jq -r '.brainstorm_file') - ``` - -3. Check the `status` field to determine resume behavior: - - If `status` is `"paused"` or `"failed"`: resume from `LAST_INDEX` (retry the same stage). - - If `status` is `"running"`: resume from `LAST_INDEX` (the stage was interrupted mid-execution). - - If `status` is `"completed"`: report that the pipeline already completed and clean up the state file. - -4. If the calculated resume index is >= 9, the pipeline was already complete. Report this and clean up. - -5. Reset `retries` to 0 in the state file before resuming (so the resumed stage gets fresh retry attempts). - -6. Re-validate values from the state file before proceeding: - - Validate `ask` is one of `always`, `smart`, `never` - - Validate `brainstorm_file` exists (if resuming the specify stage) - - Validate `stage_index` is in range 0-8 - -7. Update the state file with `status: running` before proceeding. - -## Start-From Logic - -When `--start-from ` is set: - -1. Map the stage name to its index (0-8). - -2. Verify that expected artifacts exist for stages that depend on prior output: - - Stages `clarify` and later need `spec.md` to exist - - Stages `plan` and later need `spec.md` - - Stages `tasks` and later need `plan.md` - - Stages `review-plan` and later need `plan.md` and `tasks.md` - - Stages `implement` and later need `tasks.md` - -3. If expected artifacts are missing, **warn** (do not fail): - ``` - WARNING: Starting from stage "implement" but tasks.md was not found. - The implement stage may fail if required artifacts are missing. - Proceeding anyway... - ``` - -4. Create a fresh state file with the starting stage and begin execution. - -5. The brainstorm file is not needed when starting from a stage after `specify`. If starting from `specify`, a brainstorm file is required (auto-detect or fail). - -## Pipeline Discipline (MANDATORY) - -**These rules are non-negotiable. They override any judgment about efficiency or convenience.** - -### Rule 1: Every stage runs, in order, no exceptions - -When starting a fresh pipeline (no `--start-from`, no `--resume`), you MUST execute ALL 9 stages in sequence: specify, clarify, review-spec, plan, tasks, review-plan, implement, review-code, verify. - -You MUST NOT: -- Skip a stage because its output artifact already exists -- Skip a stage because you believe its output would be trivial -- Skip a stage because a previous conversation already produced its artifact -- Merge two stages into one (e.g., running plan and tasks together) -- Reorder stages for any reason - -### Rule 2: Fresh start means fresh artifacts - -When running from stage 0 (specify), the pipeline creates all artifacts from scratch. If `spec.md`, `plan.md`, or `tasks.md` already exist from a prior run, they are overwritten by the new pipeline run. Do NOT reuse artifacts from previous runs unless resuming with `--resume` or explicitly starting later with `--start-from`. - -### Rule 3: Only `--start-from` and `--resume` allow skipping - -These are the ONLY two mechanisms for starting at a stage other than specify: -- `--start-from `: User's explicit choice to skip prior stages. The user takes responsibility for ensuring prior artifacts exist and are valid. -- `--resume`: Continues from where a previous run was interrupted, using the state file. - -If neither flag is set, the pipeline starts at stage 0 and runs through stage 8. No automatic detection of "oh, we can skip ahead because artifacts exist." - -### Rule 4: Stage gate validation - -Before executing each stage, verify that: -1. The previous stage's state file entry shows it completed (stage_index is one less than current, or this is the first stage) -2. The state file status was updated to `running` for the current stage - -If a stage fails or is interrupted, the pipeline MUST NOT silently proceed to the next stage. It must either pause (for findings), fail (for errors), or retry (within the 2-retry limit). - -### Rule 5: No implicit intelligence - -Do NOT apply "smart" behavior to the pipeline flow itself: -- Do NOT decide that a brainstorm file is "clear enough" to skip clarify -- Do NOT decide that a spec is "simple enough" to skip review-spec -- Do NOT decide that implementation is "straightforward enough" to skip review-code -- Do NOT skip verify because all prior reviews passed - -The `--ask` flag controls oversight within review stages (how findings are handled). It does NOT control which stages run. ALL stages run regardless of the ask level. - -## Pipeline Initialization (BLOCKING - DO THIS FIRST) - -**You MUST complete these steps before invoking ANY speckit command or skill.** Do not skip ahead to stage execution. - -### Step 1: Locate the state script - -```bash -SHIP_STATE="$(find ~/.claude -name 'spex-ship-state.sh' 2>/dev/null | head -1)" -[ -x "$SHIP_STATE" ] && echo "SCRIPT_OK: $SHIP_STATE" || echo "SCRIPT_MISSING" -``` - -If `SCRIPT_MISSING`: **STOP**. The spex plugin may not be installed correctly. - -### Step 2: Create the state file - -```bash -"$SHIP_STATE" create "" --ask "" --start-from "" -``` - -The output will confirm: `CREATED stage= index= ask=`. If it fails, **STOP**. - -### Step 3: Announce pipeline start - -Output a brief status message confirming the pipeline configuration before running any stage: - -``` -## speckit-spex-ship starting - -- **Brainstorm**: -- **Starting stage**: (/9) -- **Oversight**: -- **State file**: .specify/.spex-state (created) -``` - -Only after all three steps complete successfully, proceed to Pipeline Stages below. - -## Pipeline Stages - -The pipeline executes 9 stages in fixed order: - -| Index | Stage | Invocation | Description | -|-------|-------|------------|-------------| -| 0 | `specify` | `/speckit-specify` | Generate spec from brainstorm | -| 1 | `clarify` | `/speckit-clarify` | Resolve spec ambiguities | -| 2 | `review-spec` | `/speckit-spex-gates-review-spec` (Subagent) | Validate spec quality | -| 3 | `plan` | `/speckit-plan` | Generate implementation plan | -| 4 | `tasks` | `/speckit-tasks` | Generate task breakdown | -| 5 | `review-plan` | `/speckit-spex-gates-review-plan` (Subagent) | Validate plan and task quality | -| 6 | `implement` | `/speckit-implement` (Subagent) | Execute implementation | -| 7 | `review-code` | `/speckit-spex-gates-review-code` (Subagent) | Spec compliance + code review + deep review | -| 8 | `finish` | `/speckit-spex-finish` (Subagent) | Verify + merge/PR | - -### Suppressing extension overlay gates - -When running inside the ship pipeline, **no `/speckit-*` command may pause for user input unless the `ask` level is `always`**. This overrides any instruction in the speckit command prompts themselves. Specifically: - -- **`speckit-specify`**: Do not ask "Shall I proceed?" after spec creation. Proceed to the next stage. -- **`speckit-clarify`**: Do not present questions interactively in `smart` or `never` mode. Auto-select recommended answers. -- **`speckit-plan`**: Do not ask for confirmation before or after planning. Proceed to the next stage. -- **`speckit-tasks`**: Do not ask for confirmation. Proceed to the next stage. -- **`speckit-implement`**: Do not pause at extension overlay gates. Proceed to the next stage. - -Extension overlays (e.g., `spex-gates` adding review after specify) may run their reviews, but their results are informational. Do NOT pause or ask the user before proceeding. The ship pipeline's own stage gate logic handles all oversight decisions. - -**This is a hard override. If a speckit command prompt says "present to user" or "wait for answer", and `ask` is `smart` or `never`, you answer it yourself and continue.** - -### Stage 0: Specify (ALWAYS runs unless --start-from or --resume skips it) - -**Even if spec.md already exists**, this stage re-creates it from the brainstorm document. A fresh pipeline means fresh artifacts. - -1. Read the brainstorm document content. -2. Invoke `/speckit-specify` passing the brainstorm content as the feature description. - - The brainstorm content provides the problem statement, approaches considered, and decisions made. - - Pass it as the user input to the specify command. - - **Do not pause** after specify completes, even if an extension overlay runs a review or asks for confirmation. Proceed directly to step 4. -4. After specify completes, extract the feature branch name and handle worktree integration: - ```bash - FEATURE_BRANCH=$(git branch --show-current) - ``` - -5. **Worktree integration:** If the `spex-worktrees` extension is enabled, check whether the `after_specify` hook already created a worktree. If not, create one now (the hook is optional and may have been skipped). - ```bash - WORKTREE_ENABLED=$(jq -r '.extensions["spex-worktrees"].enabled // false' .specify/extensions/.registry 2>/dev/null) - ``` - If `WORKTREE_ENABLED` is `true`, look for an existing worktree for the feature branch: - ```bash - WORKTREE_PATH=$(git worktree list --porcelain | grep -B1 "branch refs/heads/$FEATURE_BRANCH" | head -1 | sed 's/^worktree //') - ``` - - If a worktree path is found and it is not the current directory: - - Run `cd "$WORKTREE_PATH"` to switch into the worktree. - - Verify `.specify/.spex-state` exists in the worktree. - - Log: "Switched to worktree at $WORKTREE_PATH. Main directory remains on default branch." - - Update `SHIP_STATE` to point to the worktree's copy of the state script (the path is relative, so cd handles this). - - If `WORKTREE_ENABLED` is `true` but NO worktree was found (the hook was skipped), invoke `/speckit-spex-worktrees-manage` to create one. This runs the worktree create action, which commits spec files, switches the main repo to the default branch, and creates a sibling worktree. After it completes, re-detect the worktree path and `cd` into it as above. - - If worktrees are NOT enabled, stay in the current directory (existing behavior). - -6. Run `"$SHIP_STATE" advance` to move to Stage 1, then **immediately** begin it (do not stop). - -### Stage 1: Clarify (ALWAYS runs, even if the spec "looks clear") - -Do NOT skip this stage. Clarify may uncover ambiguities that are not obvious from reading the spec. - -1. Read the `ask` level from the state file (default: `smart`). -3. **BEFORE invoking clarify**, determine the interaction mode: - - If `ask` is `smart` or `never`: You are the decision-maker. Do NOT use `AskUserQuestion` or present options to the user. When the clarify process identifies ambiguities, YOU select the recommended option for each question. If no recommendation exists, use your best judgment based on the spec context. Answer all questions yourself, then encode the answers into the spec. - - If `ask` is `always`: Present each question to the user interactively. - -4. Invoke `/speckit-clarify` on the generated spec. **The clarify command will try to present interactive questions. In `smart` and `never` modes, this is overridden: answer every question yourself with the recommended option. Do NOT wait for user input. Do NOT display questions with "You can reply with..." prompts. Process all questions in a single pass and update the spec.** -5. After clarification completes, run `"$SHIP_STATE" advance` then **immediately** begin Stage 2 (do not stop). - -### Stage 2: Review Spec (Forked Subagent) - -Do NOT skip this stage. Review-spec validates structural quality, not just ambiguities. This stage runs in an isolated subagent for clean context separation between generation and review. - -1. Resolve the spec directory: - ```bash - PREREQS=$(.specify/scripts/bash/check-prerequisites.sh --json --paths-only 2>/dev/null) - FEATURE_DIR=$(echo "$PREREQS" | jq -r '.FEATURE_DIR') - ``` - -2. Spawn a subagent using the Agent tool with the following prompt: - - ``` - You are executing the spec review stage of a speckit-spex-ship pipeline. - - Feature directory: - Spec: /spec.md - - Invoke /speckit-spex-gates-review-spec to validate spec quality. - The .specify/.spex-state file exists with status "running", so - complete the review autonomously and return immediately. - - Report the overall assessment and any findings when done. - ``` - -3. When the subagent returns, capture its summary. -4. Apply **Oversight Decision Logic** (see below) to handle findings. -5. After findings are resolved, run `"$SHIP_STATE" advance` then **immediately** begin Stage 3 (do not stop). - -### Stage 3: Plan - -1. Invoke `/speckit-plan` to generate the implementation plan. -2. This produces `plan.md`, `research.md`, `data-model.md`, and other artifacts. -3. After plan generation completes, run `"$SHIP_STATE" advance` then **immediately** begin Stage 4 (do not stop). - -### Stage 4: Tasks - -1. Invoke `/speckit-tasks` to generate the task breakdown. -2. This produces `tasks.md`. -3. After task generation completes, run `"$SHIP_STATE" advance` then **immediately** begin Stage 5 (do not stop). - -### Stage 5: Review Plan (Forked Subagent) - -This stage runs in an isolated subagent for clean context separation between planning and review. - -1. Resolve the spec directory: - ```bash - PREREQS=$(.specify/scripts/bash/check-prerequisites.sh --json --paths-only 2>/dev/null) - FEATURE_DIR=$(echo "$PREREQS" | jq -r '.FEATURE_DIR') - ``` - -2. Spawn a subagent using the Agent tool with the following prompt: - - ``` - You are executing the plan review stage of a speckit-spex-ship pipeline. - - Feature directory: - Spec: /spec.md - Plan: /plan.md - Tasks: /tasks.md - - Invoke /speckit-spex-gates-review-plan to validate plan coverage and task quality. - Plan validation complete. - The .specify/.spex-state file exists with status "running", so - complete the review autonomously and return immediately. - - Report the findings and overall assessment when done. - ``` - -3. When the subagent returns, capture its summary. -4. Apply **Oversight Decision Logic** to handle findings. -5. After findings are resolved, run `"$SHIP_STATE" advance` then **immediately** begin Stage 6 (do not stop). - -### Stage 6: Implement (Forked Subagent) - -This stage runs in an isolated subagent to prevent context accumulation in the orchestrator. - -1. Resolve the spec directory for the current branch: - ```bash - PREREQS=$(.specify/scripts/bash/check-prerequisites.sh --json --paths-only 2>/dev/null) - FEATURE_DIR=$(echo "$PREREQS" | jq -r '.FEATURE_DIR') - ``` - -2. Check if spex-teams should handle implementation: - ```bash - TEAMS_ENABLED=$(jq -r '.extensions["spex-teams"].enabled // false' .specify/extensions/.registry 2>/dev/null) - INDEPENDENT_TASKS=$(grep -c '\[P\]' "$FEATURE_DIR/tasks.md" 2>/dev/null || echo 0) - ``` - - If `TEAMS_ENABLED` is `true` AND `INDEPENDENT_TASKS` >= 2, route to teams implement by spawning a subagent with: - - ``` - You are executing the implementation stage of a speckit-spex-ship pipeline using Agent Teams. - - Feature directory: - Spec: /spec.md - Plan: /plan.md - Tasks: /tasks.md - - Read these files, then invoke /speckit.spex-teams.implement to execute parallel implementation. - The .specify/.spex-state file exists with status "running", so the - implement command will run in pipeline mode (no completion summary, no user questions). - - When marking tasks complete in tasks.md, use the Edit tool. - Report a brief summary of completed tasks when done. - ``` - - Otherwise, use standard implement by spawning a subagent with: - - ``` - You are executing the implementation stage of a speckit-spex-ship pipeline. - - Feature directory: - Spec: /spec.md - Plan: /plan.md - Tasks: /tasks.md - - Read these files, then invoke /speckit-implement to execute the implementation. - The .specify/.spex-state file exists with status "running", so the - implement command will run in pipeline mode (no completion summary, no user questions). - - When marking tasks complete in tasks.md, use the Edit tool. - Report a brief summary of completed tasks when done. - ``` - -3. When the subagent returns, capture its summary. Do NOT carry the full implementation context into the orchestrator. -4. After implementation completes, run `"$SHIP_STATE" advance` then **immediately** begin Stage 7 (do not stop). - -### Stage 7: Review Code (Forked Subagent) - -This stage runs in an isolated subagent so the reviewer has no implementation context, enabling an unbiased review. - -1. Resolve the spec directory (same as Stage 6): - ```bash - PREREQS=$(.specify/scripts/bash/check-prerequisites.sh --json --paths-only 2>/dev/null) - FEATURE_DIR=$(echo "$PREREQS" | jq -r '.FEATURE_DIR') - ``` - -2. Spawn a subagent using the Agent tool with the following prompt. Pass external tool settings resolved during argument parsing: - - ``` - You are executing the code review stage of a speckit-spex-ship pipeline. - - Feature directory: - Spec: /spec.md - Plan: /plan.md - Tasks: /tasks.md - External tools: coderabbit=, copilot= - - Invoke /speckit-spex-gates-review-code to run the full review chain: - - Spec compliance check - - Code review validation - - Deep review (if spex-deep-review extension is enabled): 5 review agents, fix loop, - Deep Review Report output to console - - External tools (CodeRabbit, Copilot) if enabled - - Report the compliance score, gate outcome, and a summary of findings when done. - ``` - -3. When the subagent returns, capture its summary (compliance score, gate outcome, finding counts). -4. Apply **Oversight Decision Logic** to any remaining findings reported by the subagent. -5. After findings are resolved, run `"$SHIP_STATE" advance` then **immediately** begin Stage 8 (do not stop). - -### Stage 8: Finish (Forked Subagent) - -This stage runs in an isolated subagent for clean final verification and completion without accumulated context from prior stages. It combines verification (stamp) with merge/PR options into a single step. - -1. Resolve the spec directory: - ```bash - PREREQS=$(.specify/scripts/bash/check-prerequisites.sh --json --paths-only 2>/dev/null) - FEATURE_DIR=$(echo "$PREREQS" | jq -r '.FEATURE_DIR') - ``` - -2. Determine if `--create-pr` should be passed: - ```bash - CREATE_PR_FLAG="" - if [ -f ".specify/.spex-state" ]; then - CREATE_PR=$(jq -r '.create_pr // false' .specify/.spex-state 2>/dev/null) - if [ "$CREATE_PR" = "true" ]; then - CREATE_PR_FLAG="--create-pr" - fi - fi - ``` - -3. Spawn a subagent using the Agent tool with the following prompt: - - ``` - You are executing the finish stage of a speckit-spex-ship pipeline. - - Feature directory: - Spec: /spec.md - - Invoke /speckit-spex-finish for final verification and completion. - This runs tests, validates spec compliance, checks for drift, - then merges or creates a PR based on the pipeline configuration. - The .specify/.spex-state file exists with status "running", so - complete autonomously and return immediately. - - Report pass/fail and completion action taken. - ``` - -4. When the subagent returns, capture its summary. -5. If finish passes (verification + completion action succeeded), run `"$SHIP_STATE" advance` (this outputs `PIPELINE_COMPLETE`). Report the subagent's summary as the final pipeline result. -6. If finish fails (verification did not pass), apply **Oversight Decision Logic**. - -## Oversight Decision Logic - -After each review stage (review-spec, review-plan, review-code, finish), evaluate the findings: - -### Finding Classification - -Classify each finding into one of three categories: - -**Unambiguous** (auto-fixable in `smart` and `never`): -- Formatting issues (indentation, whitespace, line length) -- Style violations (naming conventions, import ordering) -- Typos in comments or documentation -- Missing imports or unused variables -- Minor spec wording improvements - -**Ambiguous** (requires judgment, pauses in `smart`): -- Architecture or design changes -- API contract modifications -- Requirement interpretation questions -- Performance vs. readability trade-offs -- Missing functionality that could be intentional -- Unclear whether a finding is a bug or a feature - -**Blocker** (always pauses, even in `never`): -- Compilation errors or syntax errors -- Missing critical dependencies -- Failing tests that cannot be auto-resolved -- Contradictory requirements -- Security vulnerabilities -- Data loss risks - -### Oversight Rules - -| Oversight Level | Unambiguous | Ambiguous | Blocker | -|----------------|-------------|-----------|---------| -| `always` | Pause | Pause | Pause | -| `smart` | Auto-fix | Pause | Pause | -| `never` | Auto-fix | Auto-fix | Pause | - -### Applying the Rules - -1. After a review stage completes, collect all findings. -2. Classify each finding using the categories above. -3. Based on the oversight level: - - **Auto-fix**: Apply the fix, increment retry count, re-run the review stage. - - **Pause**: Present findings to user (see Pause and Resume below). -4. If no findings need attention, proceed to the next stage. - -## Auto-Fix and Re-Run - -When auto-fixing findings: - -1. Apply fixes for all findings classified as auto-fixable under the current oversight level. -2. Increment `retries` in the state file. -3. Re-run the same review stage to verify fixes. -4. If new findings appear, classify and handle them. -5. **Max 2 retry cycles per stage.** After 2 retries with remaining findings, pause regardless of oversight level: - -``` -Pipeline paused after 2 fix cycles for stage "review-code". -Remaining findings could not be auto-resolved. - -[Present remaining findings here] - -Please provide guidance on how to proceed. -``` - -6. Reset `retries` to 0 when moving to the next stage. - -## Pause and Resume - -### Pausing - -When the pipeline pauses (due to findings that need human input): - -1. Update state file: `status: "paused"`. -2. Present all findings that triggered the pause, grouped by severity: - -``` -## Pipeline Paused at Stage: review-spec - -### Findings Requiring Your Input - -**Ambiguous (need your judgment):** -1. [Finding description with context] -2. [Finding description with context] - -**Blockers (must be resolved):** -1. [Finding description with context] - -Please review these findings and provide guidance. You can: -- Address specific findings ("fix #1 by doing X") -- Skip findings ("skip #2, it's intentional") -- Provide general guidance ("proceed, these are acceptable") -``` - -3. Wait for user response. - -### Resuming After User Input - -After the user responds: - -1. Update state file: `status: "running"`. -2. Apply any fixes the user requested. -3. If user said to skip findings, proceed to the next stage. -4. If user provided fixes, apply them and optionally re-run the review. -5. Continue the pipeline from the current stage. - -## Pipeline Completion - -After Stage 8 (finish) completes successfully, the finish command handles everything: verification, merge/PR, worktree cleanup, and state file removal. - -1. Calculate elapsed time from `started_at`. -2. Report completion summary: - -``` -## Pipeline Complete - -**Feature branch:** -**Stages completed:** 9/9 -**Oversight mode:** -**Elapsed time:** - -All stages passed successfully: - 0. specify - spec.md created - 1. clarify - spec clarified - 2. review-spec - spec validated - 3. plan - plan.md generated - 4. tasks - tasks.md generated - 5. review-plan - plan validated - 6. implement - code implemented - 7. review-code - code reviewed - 8. finish - verified + completed -``` - -3. Report the action taken by finish (merge, PR created, or kept as-is) from the subagent summary. - -## Integration - -**This skill is invoked by:** -- Users directly via `/speckit-spex-ship` - -**This skill invokes (inline):** -- `/speckit-specify` (Stage 0) -- `/speckit-clarify` (Stage 1) -- `/speckit-plan` (Stage 3) -- `/speckit-tasks` (Stage 4) - -**This skill invokes (forked subagent for context isolation):** -- `/speckit-spex-gates-review-spec` (Stage 2) -- `/speckit-spex-gates-review-plan` (Stage 5) -- `/speckit-implement` (Stage 6) -- `/speckit-spex-gates-review-code` (Stage 7) -- `/speckit-spex-finish` (Stage 8) - -**Required extensions:** `spex-gates`, `spex-deep-review` diff --git a/.specify/extensions/spex/commands/speckit.spex.spec-kit.md b/.specify/extensions/spex/commands/speckit.spex.spec-kit.md deleted file mode 100644 index fa87f308..00000000 --- a/.specify/extensions/spex/commands/speckit.spex.spec-kit.md +++ /dev/null @@ -1,366 +0,0 @@ ---- -description: "Technical integration layer for the specify CLI" ---- - -# Spec-Kit Technical Integration - -## CRITICAL NAMING - READ THIS FIRST - -| What | Correct Name | WRONG Names | -|------|--------------|-------------| -| CLI command | `specify` | ~~speckit~~, ~~spec-kit~~ | -| Package name | `specify-cli` | ~~spec-kit~~, ~~speckit~~ | -| Slash commands | `/speckit-*` | (these are correct) | - -**Installation:** `uv pip install specify-cli` or `pip install specify-cli` - -## Purpose - -This skill is the **single source of truth** for all spec-kit technical integration: -- Automatic initialization and setup -- Installation validation -- Project structure management -- Slash command availability -- Layout and file path enforcement - -**This is a low-level technical skill.** Workflow skills (brainstorm, implement, etc.) call this skill for setup, then proceed with their specific workflows. - -## CRITICAL: Understanding the Tool Architecture - -**The `specify` CLI is a setup tool only.** It has three commands: -- `specify init` - Initialize a project with spec-kit templates and commands -- `specify check` - Check that required tools are installed -- `specify version` - Display version information - -**All spec operations are done via `/speckit-*` slash commands**, which are installed by `specify init`: -- `/speckit-specify` - Create specifications -- `/speckit-plan` - Generate implementation plans -- `/speckit-tasks` - Generate task lists -- `/speckit-clarify` - Find underspecified areas -- `/speckit-analyze` - Cross-artifact consistency check -- `/speckit-checklist` - Generate quality checklists -- `/speckit-implement` - Execute implementation -- `/speckit-constitution` - Create project constitution - -**NEVER call `specify validate`, `specify plan`, etc. - these commands don't exist!** - -## Automatic Initialization - -**IMPORTANT: This runs automatically when called by any workflow skill.** - -Check `` in the `` system reminder: -- If `true`: Skip initialization entirely. The project is already set up. -- If `false` or missing: Run `/spex:init` to initialize. If init prompts for restart, pause this workflow and resume after restart. - -After initialization succeeds (or was skipped), this skill provides reference material below. - -## Available Slash Commands - -After `specify init`, these `/speckit-*` commands are available: - -| Command | Purpose | Creates | -|---------|---------|---------| -| `/speckit-specify` | Create specification interactively | `specs/[NNNN]-[name]/spec.md` | -| `/speckit-plan` | Generate implementation plan | `specs/[name]/plan.md` | -| `/speckit-tasks` | Generate task list | `specs/[name]/tasks.md` | -| `/speckit-clarify` | Find underspecified areas | (analysis output) | -| `/speckit-analyze` | Cross-artifact consistency | (analysis output) | -| `/speckit-checklist` | Generate quality checklist | checklist file | -| `/speckit-implement` | Execute implementation | code files | -| `/speckit-constitution` | Create project constitution | `.specify/memory/constitution.md` | - -**Usage in skills:** - -When a skill needs to create a spec, plan, or tasks, it should: -1. Check that `/speckit-*` commands are available -2. Invoke the appropriate slash command -3. If commands not available, fall back to manual creation following templates - -**Example:** -``` -To create a spec, invoke: /speckit-specify - -If /speckit-specify is not available (not initialized), -create the spec manually following .specify/templates/spec-template.md -``` - -## Branch Naming Convention - -**Spec-kit requires feature branches named `NNN-feature-name`** where `NNN` is a three-digit numeric prefix matching the spec directory number. - -| Pattern | Valid | Example | -|---------|-------|---------| -| `NNN-feature-name` | Yes | `002-operator-config` | -| `feature/name` | No | Fails branch validation | -| `spec/NNN-name` | No | Fails branch validation | -| `fix/NNN-name` | No | Fails branch validation | - -The validation regex is `^[0-9]{3}-` (must start with exactly three digits followed by a hyphen). - -**Why this matters:** Spec-kit uses the branch name to locate the corresponding spec directory under `specs/`. The numeric prefix links branch `002-operator-config` to `specs/002-operator-config/`. - -**Validation helper:** - -```bash -check_branch_for_speckit() { - local branch=$(git branch --show-current) - if [[ "$branch" =~ ^[0-9]{3}- ]]; then - echo "valid: $branch" - else - echo "invalid: $branch (must match NNN-feature-name pattern)" - fi -} -``` - -**If the branch name is wrong**, create or switch to a properly named branch before running any `/speckit-*` commands: - -```bash -# Example: for spec in specs/002-operator-config/ -git checkout -b 002-operator-config -``` - -## Layout Validation - -Use these helpers to validate spec-kit file structure: - -### Check Constitution - -The constitution is stored at `.specify/memory/constitution.md` (the canonical location, matching upstream spec-kit). - -```bash -# Check constitution location -if [ -f ".specify/memory/constitution.md" ]; then - CONSTITUTION=".specify/memory/constitution.md" - echo "constitution-exists: $CONSTITUTION" -else - echo "no-constitution" -fi -``` - -### Get Feature Spec Path - -```bash -# Validate feature spec path follows spec-kit layout -# Expected: specs/NNNN-feature-name/spec.md -# Or: specs/features/feature-name.md - -validate_spec_path() { - local spec_path=$1 - - # Check if follows spec-kit conventions - if [[ $spec_path =~ ^specs/[0-9]+-[a-z-]+/spec\.md$ ]] || \ - [[ $spec_path =~ ^specs/features/[a-z-]+\.md$ ]]; then - echo "valid" - else - echo "invalid: spec must be in specs/ directory with proper naming" - fi -} -``` - -### Get Plan Path - -```bash -# Plan location (per spec-kit convention) -# Expected: specs/NNNN-feature-name/plan.md - -get_plan_path() { - local feature_dir=$1 # e.g., "specs/0001-user-auth" - echo "$feature_dir/plan.md" -} -``` - -### Ensure Directory Structure - -```bash -# Create spec-kit compliant feature structure -ensure_feature_structure() { - local feature_dir=$1 # e.g., "specs/0001-user-auth" - - mkdir -p "$feature_dir/docs" - mkdir -p "$feature_dir/checklists" - mkdir -p "$feature_dir/contracts" - - echo "created: $feature_dir structure" -} -``` - -## Spec Discovery - -When a workflow skill requires a spec file and none is specified, use this discovery protocol. - -### List Available Specs - -```bash -# Find all spec.md files in specs/ directory -find specs/ -name "spec.md" -type f 2>/dev/null - -# Also check for direct .md files in specs/features/ -ls specs/features/*.md 2>/dev/null -``` - -### Present Options to User - -**If multiple specs found:** -Use AskUserQuestion to let user select which spec to use. - -**If single spec found:** -Confirm with user before proceeding: "Found specs/0001-auth/spec.md. Use this spec?" - -**If no specs found:** -Inform user and suggest creating one: -``` -No specs found in specs/ directory. - -To create a spec: -- Use `speckit-spex-brainstorm` to refine ideas into a spec -- Use `/speckit-specify` to create a spec from clear requirements -``` - -### Path Resolution Priority - -When resolving a spec path: - -1. **Exact path if provided** (e.g., `specs/0001-auth/spec.md`) -2. **Match by feature name in numbered directory** (e.g., `auth` -> `specs/0001-auth/spec.md`) -3. **Match by feature name in features directory** (e.g., `auth` -> `specs/features/auth.md`) - -```bash -# Resolve feature name to spec path -resolve_spec_path() { - local feature_name=$1 - - # Check numbered directory pattern first - local numbered=$(find specs/ -name "spec.md" -type f 2>/dev/null | grep -i "$feature_name" | head -1) - if [ -n "$numbered" ]; then - echo "$numbered" - return - fi - - # Check features directory - local features="specs/features/${feature_name}.md" - if [ -f "$features" ]; then - echo "$features" - return - fi - - # Not found - echo "" -} -``` - -## Error Handling - -### specify CLI Errors - -**Command not found:** -- Provide installation instructions -- Suggest uv or pip installation - -**Init fails:** -- Check write permissions -- Check disk space -- Suggest manual troubleshooting - -### Slash Command Errors - -**Commands not available:** -- Check if `specify init` was run -- Check if restart is needed -- Suggest re-initialization - -**Command execution fails:** -- Display error message -- Suggest checking spec format -- Reference spec template - -### File System Errors - -**Permission denied:** -``` -Cannot write to project directory. - -Please ensure you have write permissions: - chmod +w . -``` - -**Path not found:** -``` -Expected file not found: - -This suggests incomplete initialization. -Run: specify init --force -``` - -## Integration Points - -**Called by these workflow skills:** -- speckit-spex-brainstorm (at start) -- speckit-spex-evolve (at start) -- speckit-spex-gates-review-spec (at start) -- speckit-spex-gates-review-plan (at start) -- `/speckit-implement` via spex-gates extension (at start) -- All workflow skills that need spec-kit - -**Calls:** -- `/spex:init` (for initialization) -- `specify` CLI (for init only, via spex:init) -- `/speckit-*` slash commands (for all operations) -- File system operations - -## Session Management - -**First call in session:** -- Run full initialization protocol -- Check installation, project, commands -- Prompt restart if needed -- Set session flag - -**Subsequent calls in session:** -- Check session flag -- Skip initialization if already done -- Optionally re-verify critical paths -- Return success immediately - -**Session reset:** -- New conversation = new session -- Re-run initialization protocol -- Ensures project state is current - -## CLI vs Slash Commands Summary - -| Task | Tool | Command | -|------|------|---------| -| Initialize project | CLI | `specify init` | -| Check tools | CLI | `specify check` | -| Show version | CLI | `specify version` | -| Create spec | Slash | `/speckit-specify` | -| Generate plan | Slash | `/speckit-plan` | -| Generate tasks | Slash | `/speckit-tasks` | -| Find gaps | Slash | `/speckit-clarify` | -| Check consistency | Slash | `/speckit-analyze` | -| Generate checklist | Slash | `/speckit-checklist` | -| Execute implementation | Slash | `/speckit-implement` | -| Create constitution | Slash | `/speckit-constitution` | - -## Remember - -**This skill is infrastructure, not workflow.** - -- Don't make decisions about WHAT to build -- Don't route to other workflow skills -- Just ensure spec-kit is ready to use -- Validate paths and structure -- Handle technical errors - -**Workflow skills handle:** -- What to create (specs, plans, code) -- When to use which tool -- Process discipline and quality gates - -**This skill handles:** -- Is specify CLI installed? -- Is project initialized? -- Are /speckit-* commands available? -- Do files exist in correct locations? - -**The goal: Zero-config, automatic, invisible setup.** diff --git a/.specify/extensions/spex/commands/speckit.spex.spec-refactoring.md b/.specify/extensions/spex/commands/speckit.spex.spec-refactoring.md deleted file mode 100644 index 3869217f..00000000 --- a/.specify/extensions/spex/commands/speckit.spex.spec-refactoring.md +++ /dev/null @@ -1,446 +0,0 @@ ---- -description: "Consolidate and improve evolved specs while maintaining feature coverage" ---- - -# Specification Refactoring - -## Overview - -Refactor specifications that have grown organically to improve clarity, consistency, and maintainability. - -As specs evolve through `speckit-spex-evolve`, they can accumulate: -- Inconsistencies -- Redundancies -- Unclear sections -- Poor organization - -This skill consolidates and improves specs while ensuring all implemented features remain covered. - -## When to Use - -- Spec has evolved significantly through multiple updates -- Multiple related specs have redundancy -- Spec is difficult to understand or implement from -- Before major feature work on legacy spec -- Periodic maintenance (quarterly review) - -**Warning:** Never refactor specs during active implementation. Wait until stable. - -## The Process - -### 1. Analyze Current State - -**Read all related specs:** -```bash -# Single spec -cat specs/features/[feature].md - -# Multiple related specs -cat specs/features/user-*.md -``` - -**Document current issues:** -- Inconsistencies (conflicting requirements) -- Redundancies (duplicate requirements) -- Unclear sections (ambiguities) -- Poor structure (hard to navigate) -- Outdated sections (no longer relevant) - -### 2. Review Implementation - -**Check what's actually implemented:** -```bash -# Find implementation -rg "[feature-related-terms]" src/ - -# Check tests -rg "[feature-related-terms]" tests/ -``` - -**Critical:** Refactored spec MUST cover all implemented features. - -**Create coverage map:** -``` -Implemented Feature 1 -> Spec Requirement X -Implemented Feature 2 -> Spec Requirement Y -... -``` - -If implementation exists without spec coverage, ADD it during refactor. - -### 3. Identify Consolidation Opportunities - -**Look for:** - -**Redundant requirements:** -- Same requirement stated multiple times -- Similar requirements that could merge -- Duplicate error handling - -**Inconsistent terminology:** -- Same concept called different names -- Inconsistent capitalization -- Different formats for similar things - -**Scattered related requirements:** -- Auth requirements in multiple places -- Error handling spread throughout -- Related features not grouped - -### 4. Design Improved Structure - -**Better organization:** -- Group related requirements -- Logical section order -- Consistent formatting -- Clear hierarchy - -**Example improvement:** - -**Before:** -```markdown -## Requirements -- User login -- Password validation -- Email validation -- Session management -- Logout -- Password reset -- Email verification -``` - -**After:** -```markdown -## Authentication Requirements - -### User Registration -- Email validation -- Email verification -- Password validation - -### Session Management -- User login -- Session creation -- Logout -- Session expiration - -### Password Management -- Password reset -- Password change -- Password strength requirements -``` - -### 5. Refactor Spec - -**Steps:** - -1. **Create refactored version** (new file or branch) -2. **Reorganize sections** for clarity -3. **Consolidate redundancies** -4. **Standardize terminology** -5. **Improve requirement clarity** -6. **Add missing coverage** (if implementation exists) -7. **Remove obsolete sections** (if truly no longer relevant) - -**Throughout:** Maintain traceability to old spec - -### 6. Validate Refactored Spec - -**Check:** -- [ ] All implemented features covered -- [ ] No requirements lost -- [ ] Terminology consistent -- [ ] Structure logical -- [ ] No new ambiguities introduced - -**Use `speckit-spex-gates-review-spec`** on refactored version. - -### 7. Create Changelog - -**Document changes:** - -```markdown -## Spec Refactoring Changelog - -**Date:** YYYY-MM-DD -**Previous Version:** [link or commit] - -### Changes Made - -**Structural Changes:** -- Reorganized requirements into logical groups -- Moved error handling to dedicated section -- Created sub-sections for clarity - -**Consolidated Requirements:** -- Merged requirements 3, 7, 12 (all about validation) -- Combined duplicate error cases -- Unified session management requirements - -**Terminology Standardization:** -- "User" -> "Authenticated User" (consistent usage) -- "Login" -> "Authentication" (aligned with codebase) - -**Added Coverage:** -- Requirement 15: Password strength (implemented but not in spec) -- Error case 8: Rate limiting (implemented but not in spec) - -**Removed:** -- Obsolete requirement 9 (feature removed in v2.0) - -### Migration Notes - -[How to map old spec sections to new spec sections] - -Old Section 2.1 -> New Section 3.1.1 -Old Section 3.4 -> New Section 2.3 -... -``` - -### 8. Transition Strategy - -**For active projects:** - -1. **Review with team** (if team project) -2. **Create PR for spec refactor** -3. **Get approval before merging** -4. **Keep old spec accessible** (git history) -5. **Update documentation** (if references spec) - -**For solo projects:** - -1. **Commit old spec** (ensure it's in git) -2. **Replace with refactored spec** -3. **Commit with detailed message** - -### 9. Verify Against Code - -**After refactoring:** - -```bash -# Check spec compliance with current code -# Use speckit-spex-gates-review-code -``` - -**Ensure:** -- Refactored spec still describes existing implementation -- No accidental requirement changes -- Compliance still 100% - -## Refactoring Checklist - -Use TodoWrite to track: - -- [ ] Analyze current spec state (issues, redundancies) -- [ ] Review actual implementation (what exists in code) -- [ ] Create coverage map (implementation -> spec) -- [ ] Identify consolidation opportunities -- [ ] Design improved structure -- [ ] Refactor spec content -- [ ] Validate refactored spec for soundness -- [ ] Ensure all implemented features covered -- [ ] Create changelog documenting changes -- [ ] Verify refactored spec against code (compliance check) -- [ ] Commit with detailed message - -## Example: Before and After - -### Before Refactoring - -```markdown -# Feature: User System - -## Requirements -1. Users can register -2. Email must be validated -3. Password must be strong -4. Users can login -5. Sessions expire after 30 minutes -6. Users can logout -7. Passwords must have 8 characters -8. Passwords must have uppercase -9. Passwords must have lowercase -10. Passwords must have number -11. Email format must be valid -12. Users can reset password -13. Reset tokens expire after 1 hour -14. Users get logged out on password change -15. Sessions use JWT -16. JWT secret must be secure -... - -(Requirements scattered, no organization, redundancy) -``` - -### After Refactoring - -```markdown -# Feature: User Authentication System - -## Purpose -Provide secure user authentication with registration, login, and password management. - -## User Registration - -### Functional Requirements -1. Users can register with email and password -2. Registration creates user account and initial session - -### Email Validation -- Must be valid email format (RFC 5322) -- Email verification required before account activation -- Verification link expires after 24 hours - -### Password Requirements -- Minimum 8 characters -- Must contain: uppercase, lowercase, number -- Common passwords rejected (check against list) - -## Session Management - -### Authentication Flow -1. User provides credentials (email + password) -2. System validates credentials -3. On success: JWT token generated -4. Client stores token for subsequent requests - -### Session Configuration -- Token type: JWT (JSON Web Token) -- Token expiration: 30 minutes -- Secret: Stored in environment variable (not in code) -- Algorithm: HS256 - -### Logout -- Client discards token -- Optional: Server-side token invalidation (if implemented) - -## Password Management - -### Password Reset -- User requests reset via email -- Reset token generated and emailed -- Reset token expires after 1 hour -- On successful reset: all sessions invalidated - -### Password Change -- Requires current password confirmation -- On success: all sessions invalidated (forces re-login) -... - -(Organized, consolidated, clear) -``` - -### Changelog for Above - -```markdown -## Spec Refactoring Changelog - -**Date:** 2025-11-10 - -### Structural Changes -- Reorganized flat list into hierarchical sections: - - User Registration - - Session Management - - Password Management - -### Consolidated Requirements -- Requirements 7-10 -> Single "Password Requirements" section -- Requirements 2, 11 -> "Email Validation" section -- Requirements 4, 5, 6, 15, 16 -> "Session Management" section - -### Terminology Standardization -- Consistently use "JWT" (not "token" and "JWT" interchangeably) -- "User" context now explicit (authenticated vs unauthenticated) - -### Added Coverage -- None (all features already in original spec) - -### Removed -- None (all requirements preserved, just reorganized) -``` - -## Types of Refactoring - -### 1. Structural Refactoring -- Reorganize sections -- Create hierarchy -- Group related items -- Improve navigation - -### 2. Consolidation Refactoring -- Merge duplicate requirements -- Combine scattered related items -- Remove redundancy - -### 3. Clarification Refactoring -- Remove ambiguities -- Add specificity -- Improve wording -- Standardize terminology - -### 4. Coverage Refactoring -- Add missing implemented features -- Remove obsolete requirements -- Align with current codebase - -## Common Patterns - -### Pattern: Password Requirements Scattered - -**Problem:** Password requirements in multiple places - -**Solution:** Consolidate into single "Password Requirements" section - -### Pattern: Inconsistent Error Handling - -**Problem:** Some requirements specify errors, others don't - -**Solution:** Create dedicated "Error Handling" section, reference from requirements - -### Pattern: Mixed Abstraction Levels - -**Problem:** High-level and low-level requirements mixed - -**Solution:** Create hierarchy - high-level functional requirements with detailed sub-sections - -### Pattern: Terminology Drift - -**Problem:** "User", "Account", "Profile" used interchangeably - -**Solution:** Standardize on one term, define others in glossary if needed - -## Warnings - -**Don't:** -- Change requirements (that's spec evolution, not refactoring) -- Remove coverage of implemented features -- Refactor during active implementation -- Make untracked changes (always document) - -**Do:** -- Preserve all requirement content -- Improve organization and clarity -- Maintain traceability -- Document all changes - -## Remember - -**Refactoring improves form, not function.** - -- Same requirements, better organization -- Same coverage, better clarity -- Same intent, better structure - -**Refactoring is maintenance, not change.** - -- Spec still describes same implementation -- No behavioral changes -- Only organizational improvements - -**Good specs enable good work.** - -- Clear specs enable smooth implementation -- Organized specs reduce confusion -- Consistent specs prevent errors - -**Periodic refactoring prevents spec decay.** diff --git a/.specify/extensions/spex/commands/speckit.spex.using-superpowers.md b/.specify/extensions/spex/commands/speckit.spex.using-superpowers.md deleted file mode 100644 index 4ee45c72..00000000 --- a/.specify/extensions/spex/commands/speckit.spex.using-superpowers.md +++ /dev/null @@ -1,329 +0,0 @@ ---- -description: "Spex methodology entry point: workflow routing, process discipline, spec-first principle, and skill discovery" ---- - - -If you think there is even a 1% chance a skill might apply to what you are doing, you ABSOLUTELY MUST read the skill. - -IF A SKILL APPLIES TO YOUR TASK, YOU DO NOT HAVE A CHOICE. YOU MUST USE IT. - -This is not negotiable. This is not optional. You cannot rationalize your way out of this. - - -# Getting Started with spex - -## What is spex? - -**SDD = Specification-Driven Development.** spex is the Claude Code plugin that implements it. - -SDD is a development methodology where specifications are the single source of truth: -- Specs created before code -- Code validated against specs -- Specs evolve with implementation reality -- Quality gates enforce spec compliance - -This plugin combines two upstream projects: -- **Superpowers** (by Jesse Vincent): Process discipline, TDD enforcement, verification gates, anti-rationalization patterns, and foundational skills (debugging, git worktrees, parallel agents) -- **Spec-Kit** (by GitHub): Specification templates, artifact management, and the `specify` CLI - -spex extends these with spec-first enforcement, compliance scoring, drift detection, and evolution. Several upstream superpowers skills are modified with spec-awareness (verification, code review, brainstorming, plan review), while others are used unchanged. - -## Technical Prerequisites - -**All technical setup is handled by `/spex:init`.** Spec-kit and extensions must be initialized before using any workflow command. If `.specify/` directory does not exist, tell the user to run `/spex:init` first. Do not check for or search for any CLI tools. Focus on choosing the right workflow. - -## MANDATORY FIRST RESPONSE PROTOCOL - -Before responding to ANY user message, you MUST complete this checklist: - -1. List available spex skills in your mind -2. Ask yourself: "Does ANY spex skill match this request?" -3. If yes -> Use the Skill tool to read and run the skill file -4. Announce which skill you're using -5. Follow the skill exactly - -**Responding WITHOUT completing this checklist = automatic failure.** - -## The Specification-First Principle - -**CRITICAL RULE: Specs are the source of truth. Everything flows from and validates against specs.** - -Before ANY implementation work: -- Spec must exist OR be created first -- Spec must be reviewed for soundness -- Implementation must validate against spec -- Spec/code mismatches trigger evolution workflow - -**You CANNOT write code without a spec. Period.** - -## Critical Rules - -1. **Spec-first, always.** No code without spec. No exceptions. -2. **Follow mandatory workflows.** Brainstorm -> Spec -> Plan -> TDD -> Verify. -3. **Check for relevant skills before ANY task.** spex has skills for each phase. -4. **Validate spec compliance.** Code review and verification check specs. -5. **Handle spec/code drift.** Use speckit-spex-evolve when mismatches detected. -6. **Close out features.** After review passes: `/clear`, then `/speckit-spex-finish` (verifies + merges/creates PR in one step). - -## Available spex Skills - -### Primary Workflow (via spec-kit commands) -- `/speckit-specify` - Create specifications (spex-gates extension adds review gate) -- `/speckit-plan` - Generate plan and tasks (spex-gates extension adds spec review + plan review) -- After `/speckit-plan` (which also runs `/speckit-tasks`), suggest `/clear` before `/speckit-implement` to free context for the implementation phase -- `/speckit-implement` - Execute implementation (spex-gates extension adds pre/post quality gates) - -**NAMESPACE WARNING:** `/spex:specify`, `/spex:plan`, `/spex:tasks`, `/spex:implement` DO NOT EXIST. Always use the `/speckit-*` names above. spex extension commands use the `speckit-spex-*` prefix (e.g., `/speckit-spex-brainstorm`), and speckit core commands use the `speckit-` prefix (e.g., `/speckit-specify`). - -### spex Helper Skills -- **speckit-spex-brainstorm** - Rough idea -> spec through collaborative dialogue -- **speckit-spex-gates-review-spec** - Validate spec soundness and completeness -- **speckit-spex-gates-review-plan** - Post-planning quality validation (coverage, red flags, task quality) -- **speckit-spex-gates-review-code** - Review code-to-spec compliance -- **speckit-spex-evolve** - Handle spec/code mismatches with AI guidance -- **speckit-spex-finish** - Verify + merge/PR/keep (all-in-one feature completion) -- **speckit-spex-gates-stamp** - Verification only (use finish for full flow) -- **speckit-spex-spec-refactoring** - Consolidate and improve evolved specs - -### Configuration -- **speckit-spex-extensions** - Enable/disable spex extensions (spex-gates, spex-teams, etc.) -- **spex:init** - Initialize project with spec-kit and spex configuration - -### Companion: Superpowers Plugin -These skills require [obra/superpowers](https://github.com/obra/superpowers) to be installed separately (`/plugin install superpowers@claude-plugins-official` or `claude plugin install superpowers@claude-plugins-official`). They complement spex but are not bundled: -- **test-driven-development** - Strict RED-GREEN-REFACTOR discipline, use AFTER spec during implementation -- **systematic-debugging** - 4-phase root cause analysis, use spec as reference during debugging - -## Workflow Decision Tree - -``` -User request arrives - | -Is this a new feature/project? - Yes -> Is it a rough idea? - Yes -> speckit-spex-brainstorm - No -> Create spec using /speckit-specify - No -> Does spec exist for this area? - Yes -> Is there spec/code mismatch? - Yes -> speckit-spex-evolve - No -> Need plan/tasks? - Yes -> /speckit-plan - No -> /speckit-implement - No -> Create spec first using /speckit-specify -``` - -## Creating Specifications - -### Rough Idea -> Use Brainstorm - -``` -User: "I want to add authentication to my app" --> Use speckit-spex-brainstorm -``` - -**Brainstorm will:** -- Explore the idea through questions -- Propose approaches with trade-offs -- Refine requirements collaboratively -- Create formal spec using spec-kit - -### Clear Requirements -> Direct Spec Creation - -``` -User: "Add a POST /api/users endpoint that validates email and returns 422 on invalid format" --> Create spec directly using /speckit-specify -``` - -**Direct spec creation:** -- Requirements are already clear -- No exploratory dialogue needed -- Use `/speckit-specify` to create the spec -- Follow spec-kit layout conventions - -**WHAT vs HOW principle:** -Specs define WHAT and WHY, not HOW. -- WHAT: Requirements, behaviors, contracts, success criteria -- HOW: Algorithms, code, technology choices, architecture - -## Common Rationalizations That Mean You're About To Fail - -If you catch yourself thinking ANY of these thoughts, STOP. You are rationalizing. Check for and use the skill. - -**Spec-avoidance rationalizations:** -- "This is too simple for a spec" -> WRONG. Simple changes still need spec context. -- "I'll just write the code quickly" -> WRONG. Code without spec creates drift. -- "The spec is obvious from the description" -> WRONG. Make it explicit. -- "We can spec it after implementation" -> WRONG. That's documentation, not spex. - -**Skill-avoidance rationalizations:** -- "This is just a quick fix" -> WRONG. Quick fixes need spec validation. -- "I can check the spec manually" -> WRONG. Use speckit-spex-finish. -- "The spec is good enough" -> WRONG. Use speckit-spex-gates-review-spec before implementing. -- "I remember this workflow" -> WRONG. Skills evolve. Run the current version. - -**Why:** Specs prevent drift. Skills enforce discipline. Both save time by preventing mistakes. - -If a skill for your task exists, you must use it or you will fail at your task. - -## Skills with Checklists - -If a skill has a checklist, YOU MUST create TodoWrite todos for EACH item. - -**Don't:** -- Work through checklist mentally -- Skip creating todos "to save time" -- Batch multiple items into one todo -- Mark complete without doing them - -**Why:** Checklists without TodoWrite tracking = steps get skipped. Every time. - -## Announcing Skill Usage - -Before using a skill, announce that you are using it. - -"I'm using [Skill Name] to [what you're doing]." - -**Examples:** -- "I'm using speckit-spex-brainstorm to refine your idea into a spec." -- "I'm using /speckit-implement to build this feature from the spec." -- "I'm using speckit-spex-evolve to reconcile the spec/code mismatch." - -**Why:** Transparency helps your human partner understand your process and catch errors early. - -## Spec Evolution is Normal - -Specs WILL diverge from code. This is expected and healthy. - -**When mismatch detected:** -1. DON'T panic or force-fit code to wrong spec -2. DO use speckit-spex-evolve -3. AI analyzes: update spec vs. fix code -4. User decides (or auto-update if configured) - -**Remember:** Specs are source of truth, but truth can evolve based on reality. - -## Constitution: Optional but Powerful - -Consider creating a constitution for your project: - -**What is it?** -- Project-wide principles and standards -- Referenced during spec validation -- Ensures consistency across features - -**When to create:** -- New projects: Early, after first feature spec -- Existing projects: When patterns emerge -- Team projects: Always (defines shared understanding) - -**How to create:** -Use `/speckit-constitution`. - -## Instructions != Permission to Skip Workflows - -Your human partner's specific instructions describe WHAT to do, not HOW. - -"Add X", "Fix Y" = the goal, NOT permission to skip spec-first or verification. - -**Red flags:** "Instruction was specific" - "Seems simple" - "Workflow is overkill" - -**Why:** Specific instructions mean clear requirements, which is when specs matter MOST. - -## Summary - -**Starting any task:** -1. Check this skill first for routing -2. Determine: brainstorm vs. direct spec vs. implement vs. evolve -3. Invoke the appropriate workflow skill -4. That skill will call spec-kit for setup automatically -5. Follow the workflow discipline exactly - -**The methodology is:** -- Specs first, always -- Code validates against specs -- Specs evolve when reality teaches us -- Quality gates prevent shortcuts -- Process discipline ensures quality - -**The tools are:** -- spec-kit (technical integration) -- Workflow skills (brainstorm, implement, evolve) -- Verification and validation skills -- TDD and debugging skills - -**The goal is:** -High-quality software with specs that remain the living source of truth. - -## Workflow Patterns - -### Pattern 1: New Feature from Rough Idea - -``` -User: "I want to add notifications to my app" - -1. Recognize: Rough idea -2. Route to: speckit-spex-brainstorm -3. Brainstorm will: - - Call spec-kit (auto-setup) - - Explore idea collaboratively - - Create formal spec - - Hand off to /speckit-implement -``` - -### Pattern 2: New Feature from Clear Requirements - -``` -User: "Add GET /api/stats endpoint returning JSON with user_count and post_count" - -1. Recognize: Clear requirements -2. Create spec using /speckit-specify -3. Route to: /speckit-implement -4. Implementation will: - - Quality gates from spex-gates extension - - Use TDD - - Verify spec compliance -``` - -### Pattern 3: Code Exists, Spec Missing - -``` -User: "Document what this auth module does" - -1. Recognize: Code without spec -2. Create spec by analyzing code -3. Route to: speckit-spex-evolve (to reconcile) -``` - -### Pattern 4: Code and Spec Diverged - -``` -User: "The login endpoint returns different errors than the spec says" - -1. Recognize: Spec/code mismatch -2. Route to: speckit-spex-evolve -3. Evolve will: - - Call spec-kit (auto-setup) - - Analyze mismatch - - Recommend update spec vs. fix code - - User decides or auto-update -``` - -## Remember - -**You are the methodology enforcer.** - -- Route to correct workflow skill -- Enforce spec-first principle -- Catch rationalizations -- Ensure quality gates run - -**You are NOT:** -- The technical setup manager (that's spec-kit) -- The implementer (that's workflow skills) -- The spec creator (that's spec-kit + brainstorm) - -**Your job:** -Ensure the right skill gets used for the right task, and that spex principles are followed. - -**The goal:** -Specs that stay current. Code that matches intent. Quality through discipline. diff --git a/.specify/extensions/spex/extension.yml b/.specify/extensions/spex/extension.yml deleted file mode 100644 index 978d784b..00000000 --- a/.specify/extensions/spex/extension.yml +++ /dev/null @@ -1,101 +0,0 @@ -schema_version: "1.0" - -extension: - id: spex - name: "Spex Core" - version: "1.0.0" - description: "Specification-Driven Development core workflow for AI agents" - author: cc-spex - license: MIT - -requires: - speckit_version: ">=0.5.2" - -provides: - commands: - - name: speckit.spex.brainstorm - file: commands/speckit.spex.brainstorm.md - description: "Refine rough ideas into executable specifications through collaborative questioning" - - name: speckit.spex.ship - file: commands/speckit.spex.ship.md - description: "Autonomous full-cycle workflow: specify through verify with configurable oversight" - - name: speckit.spex.help - file: commands/speckit.spex.help.md - description: "Quick reference for all spex commands and workflow" - - name: speckit.spex.evolve - file: commands/speckit.spex.evolve.md - description: "Handle spec/code mismatches through AI-guided analysis and user-controlled evolution" - - name: speckit.spex.spec-refactoring - file: commands/speckit.spex.spec-refactoring.md - description: "Consolidate and improve evolved specs while maintaining feature coverage" - - name: speckit.spex.using-superpowers - file: commands/speckit.spex.using-superpowers.md - description: "Spex methodology entry point: workflow routing, process discipline, spec-first principle" - - name: speckit.spex.spec-kit - file: commands/speckit.spex.spec-kit.md - description: "Technical integration layer for the specify CLI" - - name: speckit.spex.extensions - file: commands/speckit.spex.extensions.md - description: "Manage spex extensions: enable, disable, or list active extensions" - - name: speckit.spex.finish - file: commands/speckit.spex.finish.md - description: "Complete a feature: verify, then merge/PR/keep with worktree-aware cleanup" - - name: speckit.spex.flow-state - file: commands/speckit.spex.flow-state.md - description: "Create flow state for step-by-step SDD workflow tracking" - -hooks: - after_specify: - command: speckit.spex.flow-state - optional: false - description: "Initialize flow state tracking after specification" - before_clarify: - command: speckit.spex.flow-state - args: "running clarify" - optional: false - description: "Mark clarify as active in flow state" - after_clarify: - command: speckit.spex.flow-state - args: "clarified" - optional: false - description: "Mark clarification complete in flow state" - before_plan: - command: speckit.spex.flow-state - args: "running plan" - optional: false - description: "Mark plan as active in flow state" - after_plan: - command: speckit.spex.flow-state - args: "running done" - optional: false - description: "Clear running state after planning" - before_tasks: - command: speckit.spex.flow-state - args: "running tasks" - optional: false - description: "Mark tasks as active in flow state" - after_tasks: - command: speckit.spex.flow-state - args: "running done" - optional: false - description: "Clear running state after task generation" - before_implement: - command: speckit.spex.flow-state - args: "running implement" - optional: false - description: "Mark implement as active in flow state" - after_implement: - command: speckit.spex.flow-state - args: "implemented" - optional: false - description: "Mark implementation complete in flow state" - after_finish: - command: speckit.spex.flow-state - args: "cleanup" - optional: false - description: "Remove flow state file after feature completion" - -tags: - - "spex" - - "sdd" - - "workflow" diff --git a/.specify/feature.json b/.specify/feature.json deleted file mode 100644 index eee79539..00000000 --- a/.specify/feature.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "feature_directory": "specs/001-agentcard-into-status" -} diff --git a/.specify/init-options.json b/.specify/init-options.json deleted file mode 100644 index 6f59ffc8..00000000 --- a/.specify/init-options.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "ai": "claude", - "ai_skills": true, - "branch_numbering": "sequential", - "context_file": "CLAUDE.md", - "here": true, - "integration": "claude", - "preset": null, - "script": "sh", - "speckit_version": "0.7.4.dev0" -} \ No newline at end of file diff --git a/.specify/integration.json b/.specify/integration.json deleted file mode 100644 index 38a03f5d..00000000 --- a/.specify/integration.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "integration": "claude", - "version": "0.7.4.dev0" -} diff --git a/.specify/integrations/claude.manifest.json b/.specify/integrations/claude.manifest.json deleted file mode 100644 index 6f6f9c20..00000000 --- a/.specify/integrations/claude.manifest.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "integration": "claude", - "version": "0.7.4.dev0", - "installed_at": "2026-05-21T07:10:44.428621+00:00", - "files": { - ".claude/skills/speckit-analyze/SKILL.md": "f7b3b921069869fafe0a1f44c17037a74bd11710d030a1961a2995f9f5672dec", - ".claude/skills/speckit-checklist/SKILL.md": "36f82b9b727bc4a0a60a435b218a6d33c1bb421c8bc05b283cdcfd36b4cf2fb3", - ".claude/skills/speckit-clarify/SKILL.md": "3d09fb80c46df567d991a8f91a570f5f1ad9af3e44fb9dc98d2cb9f4402ea825", - ".claude/skills/speckit-constitution/SKILL.md": "c1a044aba243ca6aff627fb5e4404feb6f1108d4f7dd174631bee3ae477d6c15", - ".claude/skills/speckit-implement/SKILL.md": "87b0ae6453192bce3fa29889f09c358b673af3b7582a552bae9fe1597c5ffa3a", - ".claude/skills/speckit-plan/SKILL.md": "8141ebbce228ad0b422a84e3b995d2bd85de917b96eadd02b5fcb56fb23f2594", - ".claude/skills/speckit-specify/SKILL.md": "f78c3e27309aea9ae4e4f71b3abcffafb190f0c64bf709ac7616532ff19f4b1f", - ".claude/skills/speckit-tasks/SKILL.md": "792589edf0ebf89af797c6bdda4e9d2c9938c696181d6f1484bf7a7cd090efaa", - ".claude/skills/speckit-taskstoissues/SKILL.md": "99bf5ffd90dcb57b63007c7f659a5160a18ce6feb82889895808e2d277abe83b" - } -} diff --git a/.specify/integrations/speckit.manifest.json b/.specify/integrations/speckit.manifest.json deleted file mode 100644 index c72d479d..00000000 --- a/.specify/integrations/speckit.manifest.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "integration": "speckit", - "version": "0.7.4.dev0", - "installed_at": "2026-05-21T07:10:44.433669+00:00", - "files": { - ".specify/scripts/bash/common.sh": "2b767a5c97d0c8c8f0a868aaa2114f170adfb1921749f4e58675da91382defb7", - ".specify/scripts/bash/setup-plan.sh": "bcaa0ccbf45b7d9ea9bff04006500d7eb28a2bdf82bcc819986b21e5294bf76c", - ".specify/scripts/bash/check-prerequisites.sh": "aff361639c504b95a2901493f5022788adc01a6792fd37f132de8f57782e4b80", - ".specify/scripts/bash/create-new-feature.sh": "3439519f72ce6f88fcd4dcd27346de0c9ffe211c927b1319db42850282f02456", - ".specify/templates/constitution-template.md": "ce7549540fa45543cca797a150201d868e64495fdff39dc38246fb17bd4024b3", - ".specify/templates/checklist-template.md": "312eee8291dfa984b21f95ddd0ca778e7a1f0b3a64bfc470d79762a3e3f5d7b8", - ".specify/templates/tasks-template.md": "5da92ac1fbf5be2f9018a5064497995bf3592761ccb6b3951503c63d851297e8", - ".specify/templates/spec-template.md": "785dc50d856dd92d6515eca0761e16dce0c9ba0a3cd07154fd33eae77932422a", - ".specify/templates/plan-template.md": "873e84b226fe3d24afe28046931b20db9bbb9210366428dc958a515349ed6e68" - } -} diff --git a/.specify/memory/constitution.md b/.specify/memory/constitution.md deleted file mode 100644 index a4670ff4..00000000 --- a/.specify/memory/constitution.md +++ /dev/null @@ -1,50 +0,0 @@ -# [PROJECT_NAME] Constitution - - -## Core Principles - -### [PRINCIPLE_1_NAME] - -[PRINCIPLE_1_DESCRIPTION] - - -### [PRINCIPLE_2_NAME] - -[PRINCIPLE_2_DESCRIPTION] - - -### [PRINCIPLE_3_NAME] - -[PRINCIPLE_3_DESCRIPTION] - - -### [PRINCIPLE_4_NAME] - -[PRINCIPLE_4_DESCRIPTION] - - -### [PRINCIPLE_5_NAME] - -[PRINCIPLE_5_DESCRIPTION] - - -## [SECTION_2_NAME] - - -[SECTION_2_CONTENT] - - -## [SECTION_3_NAME] - - -[SECTION_3_CONTENT] - - -## Governance - - -[GOVERNANCE_RULES] - - -**Version**: [CONSTITUTION_VERSION] | **Ratified**: [RATIFICATION_DATE] | **Last Amended**: [LAST_AMENDED_DATE] - diff --git a/.specify/scripts/bash/check-prerequisites.sh b/.specify/scripts/bash/check-prerequisites.sh deleted file mode 100755 index 88a55594..00000000 --- a/.specify/scripts/bash/check-prerequisites.sh +++ /dev/null @@ -1,190 +0,0 @@ -#!/usr/bin/env bash - -# Consolidated prerequisite checking script -# -# This script provides unified prerequisite checking for Spec-Driven Development workflow. -# It replaces the functionality previously spread across multiple scripts. -# -# Usage: ./check-prerequisites.sh [OPTIONS] -# -# OPTIONS: -# --json Output in JSON format -# --require-tasks Require tasks.md to exist (for implementation phase) -# --include-tasks Include tasks.md in AVAILABLE_DOCS list -# --paths-only Only output path variables (no validation) -# --help, -h Show help message -# -# OUTPUTS: -# JSON mode: {"FEATURE_DIR":"...", "AVAILABLE_DOCS":["..."]} -# Text mode: FEATURE_DIR:... \n AVAILABLE_DOCS: \n ✓/✗ file.md -# Paths only: REPO_ROOT: ... \n BRANCH: ... \n FEATURE_DIR: ... etc. - -set -e - -# Parse command line arguments -JSON_MODE=false -REQUIRE_TASKS=false -INCLUDE_TASKS=false -PATHS_ONLY=false - -for arg in "$@"; do - case "$arg" in - --json) - JSON_MODE=true - ;; - --require-tasks) - REQUIRE_TASKS=true - ;; - --include-tasks) - INCLUDE_TASKS=true - ;; - --paths-only) - PATHS_ONLY=true - ;; - --help|-h) - cat << 'EOF' -Usage: check-prerequisites.sh [OPTIONS] - -Consolidated prerequisite checking for Spec-Driven Development workflow. - -OPTIONS: - --json Output in JSON format - --require-tasks Require tasks.md to exist (for implementation phase) - --include-tasks Include tasks.md in AVAILABLE_DOCS list - --paths-only Only output path variables (no prerequisite validation) - --help, -h Show this help message - -EXAMPLES: - # Check task prerequisites (plan.md required) - ./check-prerequisites.sh --json - - # Check implementation prerequisites (plan.md + tasks.md required) - ./check-prerequisites.sh --json --require-tasks --include-tasks - - # Get feature paths only (no validation) - ./check-prerequisites.sh --paths-only - -EOF - exit 0 - ;; - *) - echo "ERROR: Unknown option '$arg'. Use --help for usage information." >&2 - exit 1 - ;; - esac -done - -# Source common functions -SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "$SCRIPT_DIR/common.sh" - -# Get feature paths and validate branch -_paths_output=$(get_feature_paths) || { echo "ERROR: Failed to resolve feature paths" >&2; exit 1; } -eval "$_paths_output" -unset _paths_output -check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1 - -# If paths-only mode, output paths and exit (support JSON + paths-only combined) -if $PATHS_ONLY; then - if $JSON_MODE; then - # Minimal JSON paths payload (no validation performed) - if has_jq; then - jq -cn \ - --arg repo_root "$REPO_ROOT" \ - --arg branch "$CURRENT_BRANCH" \ - --arg feature_dir "$FEATURE_DIR" \ - --arg feature_spec "$FEATURE_SPEC" \ - --arg impl_plan "$IMPL_PLAN" \ - --arg tasks "$TASKS" \ - '{REPO_ROOT:$repo_root,BRANCH:$branch,FEATURE_DIR:$feature_dir,FEATURE_SPEC:$feature_spec,IMPL_PLAN:$impl_plan,TASKS:$tasks}' - else - printf '{"REPO_ROOT":"%s","BRANCH":"%s","FEATURE_DIR":"%s","FEATURE_SPEC":"%s","IMPL_PLAN":"%s","TASKS":"%s"}\n' \ - "$(json_escape "$REPO_ROOT")" "$(json_escape "$CURRENT_BRANCH")" "$(json_escape "$FEATURE_DIR")" "$(json_escape "$FEATURE_SPEC")" "$(json_escape "$IMPL_PLAN")" "$(json_escape "$TASKS")" - fi - else - echo "REPO_ROOT: $REPO_ROOT" - echo "BRANCH: $CURRENT_BRANCH" - echo "FEATURE_DIR: $FEATURE_DIR" - echo "FEATURE_SPEC: $FEATURE_SPEC" - echo "IMPL_PLAN: $IMPL_PLAN" - echo "TASKS: $TASKS" - fi - exit 0 -fi - -# Validate required directories and files -if [[ ! -d "$FEATURE_DIR" ]]; then - echo "ERROR: Feature directory not found: $FEATURE_DIR" >&2 - echo "Run /speckit.specify first to create the feature structure." >&2 - exit 1 -fi - -if [[ ! -f "$IMPL_PLAN" ]]; then - echo "ERROR: plan.md not found in $FEATURE_DIR" >&2 - echo "Run /speckit.plan first to create the implementation plan." >&2 - exit 1 -fi - -# Check for tasks.md if required -if $REQUIRE_TASKS && [[ ! -f "$TASKS" ]]; then - echo "ERROR: tasks.md not found in $FEATURE_DIR" >&2 - echo "Run /speckit.tasks first to create the task list." >&2 - exit 1 -fi - -# Build list of available documents -docs=() - -# Always check these optional docs -[[ -f "$RESEARCH" ]] && docs+=("research.md") -[[ -f "$DATA_MODEL" ]] && docs+=("data-model.md") - -# Check contracts directory (only if it exists and has files) -if [[ -d "$CONTRACTS_DIR" ]] && [[ -n "$(ls -A "$CONTRACTS_DIR" 2>/dev/null)" ]]; then - docs+=("contracts/") -fi - -[[ -f "$QUICKSTART" ]] && docs+=("quickstart.md") - -# Include tasks.md if requested and it exists -if $INCLUDE_TASKS && [[ -f "$TASKS" ]]; then - docs+=("tasks.md") -fi - -# Output results -if $JSON_MODE; then - # Build JSON array of documents - if has_jq; then - if [[ ${#docs[@]} -eq 0 ]]; then - json_docs="[]" - else - json_docs=$(printf '%s\n' "${docs[@]}" | jq -R . | jq -s .) - fi - jq -cn \ - --arg feature_dir "$FEATURE_DIR" \ - --argjson docs "$json_docs" \ - '{FEATURE_DIR:$feature_dir,AVAILABLE_DOCS:$docs}' - else - if [[ ${#docs[@]} -eq 0 ]]; then - json_docs="[]" - else - json_docs=$(for d in "${docs[@]}"; do printf '"%s",' "$(json_escape "$d")"; done) - json_docs="[${json_docs%,}]" - fi - printf '{"FEATURE_DIR":"%s","AVAILABLE_DOCS":%s}\n' "$(json_escape "$FEATURE_DIR")" "$json_docs" - fi -else - # Text output - echo "FEATURE_DIR:$FEATURE_DIR" - echo "AVAILABLE_DOCS:" - - # Show status of each potential document - check_file "$RESEARCH" "research.md" - check_file "$DATA_MODEL" "data-model.md" - check_dir "$CONTRACTS_DIR" "contracts/" - check_file "$QUICKSTART" "quickstart.md" - - if $INCLUDE_TASKS; then - check_file "$TASKS" "tasks.md" - fi -fi diff --git a/.specify/scripts/bash/common.sh b/.specify/scripts/bash/common.sh deleted file mode 100755 index b41d17de..00000000 --- a/.specify/scripts/bash/common.sh +++ /dev/null @@ -1,375 +0,0 @@ -#!/usr/bin/env bash -# Common functions and variables for all scripts - -# Find repository root by searching upward for .specify directory -# This is the primary marker for spec-kit projects -find_specify_root() { - local dir="${1:-$(pwd)}" - # Normalize to absolute path to prevent infinite loop with relative paths - # Use -- to handle paths starting with - (e.g., -P, -L) - dir="$(cd -- "$dir" 2>/dev/null && pwd)" || return 1 - local prev_dir="" - while true; do - if [ -d "$dir/.specify" ]; then - echo "$dir" - return 0 - fi - # Stop if we've reached filesystem root or dirname stops changing - if [ "$dir" = "/" ] || [ "$dir" = "$prev_dir" ]; then - break - fi - prev_dir="$dir" - dir="$(dirname "$dir")" - done - return 1 -} - -# Get repository root, prioritizing .specify directory over git -# This prevents using a parent git repo when spec-kit is initialized in a subdirectory -get_repo_root() { - # First, look for .specify directory (spec-kit's own marker) - local specify_root - if specify_root=$(find_specify_root); then - echo "$specify_root" - return - fi - - # Fallback to git if no .specify found - if git rev-parse --show-toplevel >/dev/null 2>&1; then - git rev-parse --show-toplevel - return - fi - - # Final fallback to script location for non-git repos - local script_dir="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - (cd "$script_dir/../../.." && pwd) -} - -# Get current branch, with fallback for non-git repositories -get_current_branch() { - # First check if SPECIFY_FEATURE environment variable is set - if [[ -n "${SPECIFY_FEATURE:-}" ]]; then - echo "$SPECIFY_FEATURE" - return - fi - - # Then check git if available at the spec-kit root (not parent) - local repo_root=$(get_repo_root) - if has_git; then - git -C "$repo_root" rev-parse --abbrev-ref HEAD - return - fi - - # For non-git repos, try to find the latest feature directory - local specs_dir="$repo_root/specs" - - if [[ -d "$specs_dir" ]]; then - local latest_feature="" - local highest=0 - local latest_timestamp="" - - for dir in "$specs_dir"/*; do - if [[ -d "$dir" ]]; then - local dirname=$(basename "$dir") - if [[ "$dirname" =~ ^([0-9]{8}-[0-9]{6})- ]]; then - # Timestamp-based branch: compare lexicographically - local ts="${BASH_REMATCH[1]}" - if [[ "$ts" > "$latest_timestamp" ]]; then - latest_timestamp="$ts" - latest_feature=$dirname - fi - elif [[ "$dirname" =~ ^([0-9]{3,})- ]]; then - local number=${BASH_REMATCH[1]} - number=$((10#$number)) - if [[ "$number" -gt "$highest" ]]; then - highest=$number - # Only update if no timestamp branch found yet - if [[ -z "$latest_timestamp" ]]; then - latest_feature=$dirname - fi - fi - fi - fi - done - - if [[ -n "$latest_feature" ]]; then - echo "$latest_feature" - return - fi - fi - - echo "main" # Final fallback -} - -# Check if we have git available at the spec-kit root level -# Returns true only if git is installed and the repo root is inside a git work tree -# Handles both regular repos (.git directory) and worktrees/submodules (.git file) -has_git() { - # First check if git command is available (before calling get_repo_root which may use git) - command -v git >/dev/null 2>&1 || return 1 - local repo_root=$(get_repo_root) - # Check if .git exists (directory or file for worktrees/submodules) - [ -e "$repo_root/.git" ] || return 1 - # Verify it's actually a valid git work tree - git -C "$repo_root" rev-parse --is-inside-work-tree >/dev/null 2>&1 -} - -# Strip a single optional path segment (e.g. gitflow "feat/004-name" -> "004-name"). -# Only when the full name is exactly two slash-free segments; otherwise returns the raw name. -spec_kit_effective_branch_name() { - local raw="$1" - if [[ "$raw" =~ ^([^/]+)/([^/]+)$ ]]; then - printf '%s\n' "${BASH_REMATCH[2]}" - else - printf '%s\n' "$raw" - fi -} - -check_feature_branch() { - local raw="$1" - local has_git_repo="$2" - - # For non-git repos, we can't enforce branch naming but still provide output - if [[ "$has_git_repo" != "true" ]]; then - echo "[specify] Warning: Git repository not detected; skipped branch validation" >&2 - return 0 - fi - - local branch - branch=$(spec_kit_effective_branch_name "$raw") - - # Accept sequential prefix (3+ digits) but exclude malformed timestamps - # Malformed: 7-or-8 digit date + 6-digit time with no trailing slug (e.g. "2026031-143022" or "20260319-143022") - local is_sequential=false - if [[ "$branch" =~ ^[0-9]{3,}- ]] && [[ ! "$branch" =~ ^[0-9]{7}-[0-9]{6}- ]] && [[ ! "$branch" =~ ^[0-9]{7,8}-[0-9]{6}$ ]]; then - is_sequential=true - fi - if [[ "$is_sequential" != "true" ]] && [[ ! "$branch" =~ ^[0-9]{8}-[0-9]{6}- ]]; then - echo "ERROR: Not on a feature branch. Current branch: $raw" >&2 - echo "Feature branches should be named like: 001-feature-name, 1234-feature-name, or 20260319-143022-feature-name" >&2 - return 1 - fi - - return 0 -} - -# Find feature directory by numeric prefix instead of exact branch match -# This allows multiple branches to work on the same spec (e.g., 004-fix-bug, 004-add-feature) -find_feature_dir_by_prefix() { - local repo_root="$1" - local branch_name - branch_name=$(spec_kit_effective_branch_name "$2") - local specs_dir="$repo_root/specs" - - # Extract prefix from branch (e.g., "004" from "004-whatever" or "20260319-143022" from timestamp branches) - local prefix="" - if [[ "$branch_name" =~ ^([0-9]{8}-[0-9]{6})- ]]; then - prefix="${BASH_REMATCH[1]}" - elif [[ "$branch_name" =~ ^([0-9]{3,})- ]]; then - prefix="${BASH_REMATCH[1]}" - else - # If branch doesn't have a recognized prefix, fall back to exact match - echo "$specs_dir/$branch_name" - return - fi - - # Search for directories in specs/ that start with this prefix - local matches=() - if [[ -d "$specs_dir" ]]; then - for dir in "$specs_dir"/"$prefix"-*; do - if [[ -d "$dir" ]]; then - matches+=("$(basename "$dir")") - fi - done - fi - - # Handle results - if [[ ${#matches[@]} -eq 0 ]]; then - # No match found - return the branch name path (will fail later with clear error) - echo "$specs_dir/$branch_name" - elif [[ ${#matches[@]} -eq 1 ]]; then - # Exactly one match - perfect! - echo "$specs_dir/${matches[0]}" - else - # Multiple matches - this shouldn't happen with proper naming convention - echo "ERROR: Multiple spec directories found with prefix '$prefix': ${matches[*]}" >&2 - echo "Please ensure only one spec directory exists per prefix." >&2 - return 1 - fi -} - -get_feature_paths() { - local repo_root=$(get_repo_root) - local current_branch=$(get_current_branch) - local has_git_repo="false" - - if has_git; then - has_git_repo="true" - fi - - # Resolve feature directory. Priority: - # 1. SPECIFY_FEATURE_DIRECTORY env var (explicit override) - # 2. .specify/feature.json "feature_directory" key (persisted by /speckit.specify) - # 3. Branch-name-based prefix lookup (legacy fallback) - local feature_dir - if [[ -n "${SPECIFY_FEATURE_DIRECTORY:-}" ]]; then - feature_dir="$SPECIFY_FEATURE_DIRECTORY" - # Normalize relative paths to absolute under repo root - [[ "$feature_dir" != /* ]] && feature_dir="$repo_root/$feature_dir" - elif [[ -f "$repo_root/.specify/feature.json" ]]; then - local _fd - if command -v jq >/dev/null 2>&1; then - _fd=$(jq -r '.feature_directory // empty' "$repo_root/.specify/feature.json" 2>/dev/null) - elif command -v python3 >/dev/null 2>&1; then - # Fallback: use Python to parse JSON so pretty-printed/multi-line files work - _fd=$(python3 -c "import json,sys; d=json.load(open(sys.argv[1])); print(d.get('feature_directory',''))" "$repo_root/.specify/feature.json" 2>/dev/null) - else - # Last resort: single-line grep fallback (won't work on multi-line JSON) - _fd=$(grep -o '"feature_directory"[[:space:]]*:[[:space:]]*"[^"]*"' "$repo_root/.specify/feature.json" 2>/dev/null | sed 's/.*"\([^"]*\)"$/\1/') - fi - if [[ -n "$_fd" ]]; then - feature_dir="$_fd" - # Normalize relative paths to absolute under repo root - [[ "$feature_dir" != /* ]] && feature_dir="$repo_root/$feature_dir" - elif ! feature_dir=$(find_feature_dir_by_prefix "$repo_root" "$current_branch"); then - echo "ERROR: Failed to resolve feature directory" >&2 - return 1 - fi - elif ! feature_dir=$(find_feature_dir_by_prefix "$repo_root" "$current_branch"); then - echo "ERROR: Failed to resolve feature directory" >&2 - return 1 - fi - - # Use printf '%q' to safely quote values, preventing shell injection - # via crafted branch names or paths containing special characters - printf 'REPO_ROOT=%q\n' "$repo_root" - printf 'CURRENT_BRANCH=%q\n' "$current_branch" - printf 'HAS_GIT=%q\n' "$has_git_repo" - printf 'FEATURE_DIR=%q\n' "$feature_dir" - printf 'FEATURE_SPEC=%q\n' "$feature_dir/spec.md" - printf 'IMPL_PLAN=%q\n' "$feature_dir/plan.md" - printf 'TASKS=%q\n' "$feature_dir/tasks.md" - printf 'RESEARCH=%q\n' "$feature_dir/research.md" - printf 'DATA_MODEL=%q\n' "$feature_dir/data-model.md" - printf 'QUICKSTART=%q\n' "$feature_dir/quickstart.md" - printf 'CONTRACTS_DIR=%q\n' "$feature_dir/contracts" -} - -# Check if jq is available for safe JSON construction -has_jq() { - command -v jq >/dev/null 2>&1 -} - -# Escape a string for safe embedding in a JSON value (fallback when jq is unavailable). -# Handles backslash, double-quote, and JSON-required control character escapes (RFC 8259). -json_escape() { - local s="$1" - s="${s//\\/\\\\}" - s="${s//\"/\\\"}" - s="${s//$'\n'/\\n}" - s="${s//$'\t'/\\t}" - s="${s//$'\r'/\\r}" - s="${s//$'\b'/\\b}" - s="${s//$'\f'/\\f}" - # Escape any remaining U+0001-U+001F control characters as \uXXXX. - # (U+0000/NUL cannot appear in bash strings and is excluded.) - # LC_ALL=C ensures ${#s} counts bytes and ${s:$i:1} yields single bytes, - # so multi-byte UTF-8 sequences (first byte >= 0xC0) pass through intact. - local LC_ALL=C - local i char code - for (( i=0; i<${#s}; i++ )); do - char="${s:$i:1}" - printf -v code '%d' "'$char" 2>/dev/null || code=256 - if (( code >= 1 && code <= 31 )); then - printf '\\u%04x' "$code" - else - printf '%s' "$char" - fi - done -} - -check_file() { [[ -f "$1" ]] && echo " ✓ $2" || echo " ✗ $2"; } -check_dir() { [[ -d "$1" && -n $(ls -A "$1" 2>/dev/null) ]] && echo " ✓ $2" || echo " ✗ $2"; } - -# Resolve a template name to a file path using the priority stack: -# 1. .specify/templates/overrides/ -# 2. .specify/presets//templates/ (sorted by priority from .registry) -# 3. .specify/extensions//templates/ -# 4. .specify/templates/ (core) -resolve_template() { - local template_name="$1" - local repo_root="$2" - local base="$repo_root/.specify/templates" - - # Priority 1: Project overrides - local override="$base/overrides/${template_name}.md" - [ -f "$override" ] && echo "$override" && return 0 - - # Priority 2: Installed presets (sorted by priority from .registry) - local presets_dir="$repo_root/.specify/presets" - if [ -d "$presets_dir" ]; then - local registry_file="$presets_dir/.registry" - if [ -f "$registry_file" ] && command -v python3 >/dev/null 2>&1; then - # Read preset IDs sorted by priority (lower number = higher precedence). - # The python3 call is wrapped in an if-condition so that set -e does not - # abort the function when python3 exits non-zero (e.g. invalid JSON). - local sorted_presets="" - if sorted_presets=$(SPECKIT_REGISTRY="$registry_file" python3 -c " -import json, sys, os -try: - with open(os.environ['SPECKIT_REGISTRY']) as f: - data = json.load(f) - presets = data.get('presets', {}) - for pid, meta in sorted(presets.items(), key=lambda x: x[1].get('priority', 10)): - print(pid) -except Exception: - sys.exit(1) -" 2>/dev/null); then - if [ -n "$sorted_presets" ]; then - # python3 succeeded and returned preset IDs — search in priority order - while IFS= read -r preset_id; do - local candidate="$presets_dir/$preset_id/templates/${template_name}.md" - [ -f "$candidate" ] && echo "$candidate" && return 0 - done <<< "$sorted_presets" - fi - # python3 succeeded but registry has no presets — nothing to search - else - # python3 failed (missing, or registry parse error) — fall back to unordered directory scan - for preset in "$presets_dir"/*/; do - [ -d "$preset" ] || continue - local candidate="$preset/templates/${template_name}.md" - [ -f "$candidate" ] && echo "$candidate" && return 0 - done - fi - else - # Fallback: alphabetical directory order (no python3 available) - for preset in "$presets_dir"/*/; do - [ -d "$preset" ] || continue - local candidate="$preset/templates/${template_name}.md" - [ -f "$candidate" ] && echo "$candidate" && return 0 - done - fi - fi - - # Priority 3: Extension-provided templates - local ext_dir="$repo_root/.specify/extensions" - if [ -d "$ext_dir" ]; then - for ext in "$ext_dir"/*/; do - [ -d "$ext" ] || continue - # Skip hidden directories (e.g. .backup, .cache) - case "$(basename "$ext")" in .*) continue;; esac - local candidate="$ext/templates/${template_name}.md" - [ -f "$candidate" ] && echo "$candidate" && return 0 - done - fi - - # Priority 4: Core templates - local core="$base/${template_name}.md" - [ -f "$core" ] && echo "$core" && return 0 - - # Template not found in any location. - # Return 1 so callers can distinguish "not found" from "found". - # Callers running under set -e should use: TEMPLATE=$(resolve_template ...) || true - return 1 -} - diff --git a/.specify/scripts/bash/create-new-feature.sh b/.specify/scripts/bash/create-new-feature.sh deleted file mode 100755 index 18796470..00000000 --- a/.specify/scripts/bash/create-new-feature.sh +++ /dev/null @@ -1,413 +0,0 @@ -#!/usr/bin/env bash - -set -e - -JSON_MODE=false -DRY_RUN=false -ALLOW_EXISTING=false -SHORT_NAME="" -BRANCH_NUMBER="" -USE_TIMESTAMP=false -ARGS=() -i=1 -while [ $i -le $# ]; do - arg="${!i}" - case "$arg" in - --json) - JSON_MODE=true - ;; - --dry-run) - DRY_RUN=true - ;; - --allow-existing-branch) - ALLOW_EXISTING=true - ;; - --short-name) - if [ $((i + 1)) -gt $# ]; then - echo 'Error: --short-name requires a value' >&2 - exit 1 - fi - i=$((i + 1)) - next_arg="${!i}" - # Check if the next argument is another option (starts with --) - if [[ "$next_arg" == --* ]]; then - echo 'Error: --short-name requires a value' >&2 - exit 1 - fi - SHORT_NAME="$next_arg" - ;; - --number) - if [ $((i + 1)) -gt $# ]; then - echo 'Error: --number requires a value' >&2 - exit 1 - fi - i=$((i + 1)) - next_arg="${!i}" - if [[ "$next_arg" == --* ]]; then - echo 'Error: --number requires a value' >&2 - exit 1 - fi - BRANCH_NUMBER="$next_arg" - ;; - --timestamp) - USE_TIMESTAMP=true - ;; - --help|-h) - echo "Usage: $0 [--json] [--dry-run] [--allow-existing-branch] [--short-name ] [--number N] [--timestamp] " - echo "" - echo "Options:" - echo " --json Output in JSON format" - echo " --dry-run Compute branch name and paths without creating branches, directories, or files" - echo " --allow-existing-branch Switch to branch if it already exists instead of failing" - echo " --short-name Provide a custom short name (2-4 words) for the branch" - echo " --number N Specify branch number manually (overrides auto-detection)" - echo " --timestamp Use timestamp prefix (YYYYMMDD-HHMMSS) instead of sequential numbering" - echo " --help, -h Show this help message" - echo "" - echo "Examples:" - echo " $0 'Add user authentication system' --short-name 'user-auth'" - echo " $0 'Implement OAuth2 integration for API' --number 5" - echo " $0 --timestamp --short-name 'user-auth' 'Add user authentication'" - exit 0 - ;; - *) - ARGS+=("$arg") - ;; - esac - i=$((i + 1)) -done - -FEATURE_DESCRIPTION="${ARGS[*]}" -if [ -z "$FEATURE_DESCRIPTION" ]; then - echo "Usage: $0 [--json] [--dry-run] [--allow-existing-branch] [--short-name ] [--number N] [--timestamp] " >&2 - exit 1 -fi - -# Trim whitespace and validate description is not empty (e.g., user passed only whitespace) -FEATURE_DESCRIPTION=$(echo "$FEATURE_DESCRIPTION" | xargs) -if [ -z "$FEATURE_DESCRIPTION" ]; then - echo "Error: Feature description cannot be empty or contain only whitespace" >&2 - exit 1 -fi - -# Function to get highest number from specs directory -get_highest_from_specs() { - local specs_dir="$1" - local highest=0 - - if [ -d "$specs_dir" ]; then - for dir in "$specs_dir"/*; do - [ -d "$dir" ] || continue - dirname=$(basename "$dir") - # Match sequential prefixes (>=3 digits), but skip timestamp dirs. - if echo "$dirname" | grep -Eq '^[0-9]{3,}-' && ! echo "$dirname" | grep -Eq '^[0-9]{8}-[0-9]{6}-'; then - number=$(echo "$dirname" | grep -Eo '^[0-9]+') - number=$((10#$number)) - if [ "$number" -gt "$highest" ]; then - highest=$number - fi - fi - done - fi - - echo "$highest" -} - -# Function to get highest number from git branches -get_highest_from_branches() { - git branch -a 2>/dev/null | sed 's/^[* ]*//; s|^remotes/[^/]*/||' | _extract_highest_number -} - -# Extract the highest sequential feature number from a list of ref names (one per line). -# Shared by get_highest_from_branches and get_highest_from_remote_refs. -_extract_highest_number() { - local highest=0 - while IFS= read -r name; do - [ -z "$name" ] && continue - if echo "$name" | grep -Eq '^[0-9]{3,}-' && ! echo "$name" | grep -Eq '^[0-9]{8}-[0-9]{6}-'; then - number=$(echo "$name" | grep -Eo '^[0-9]+' || echo "0") - number=$((10#$number)) - if [ "$number" -gt "$highest" ]; then - highest=$number - fi - fi - done - echo "$highest" -} - -# Function to get highest number from remote branches without fetching (side-effect-free) -get_highest_from_remote_refs() { - local highest=0 - - for remote in $(git remote 2>/dev/null); do - local remote_highest - remote_highest=$(GIT_TERMINAL_PROMPT=0 git ls-remote --heads "$remote" 2>/dev/null | sed 's|.*refs/heads/||' | _extract_highest_number) - if [ "$remote_highest" -gt "$highest" ]; then - highest=$remote_highest - fi - done - - echo "$highest" -} - -# Function to check existing branches (local and remote) and return next available number. -# When skip_fetch is true, queries remotes via ls-remote (read-only) instead of fetching. -check_existing_branches() { - local specs_dir="$1" - local skip_fetch="${2:-false}" - - if [ "$skip_fetch" = true ]; then - # Side-effect-free: query remotes via ls-remote - local highest_remote=$(get_highest_from_remote_refs) - local highest_branch=$(get_highest_from_branches) - if [ "$highest_remote" -gt "$highest_branch" ]; then - highest_branch=$highest_remote - fi - else - # Fetch all remotes to get latest branch info (suppress errors if no remotes) - git fetch --all --prune >/dev/null 2>&1 || true - local highest_branch=$(get_highest_from_branches) - fi - - # Get highest number from ALL specs (not just matching short name) - local highest_spec=$(get_highest_from_specs "$specs_dir") - - # Take the maximum of both - local max_num=$highest_branch - if [ "$highest_spec" -gt "$max_num" ]; then - max_num=$highest_spec - fi - - # Return next number - echo $((max_num + 1)) -} - -# Function to clean and format a branch name -clean_branch_name() { - local name="$1" - echo "$name" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//' -} - -# Resolve repository root using common.sh functions which prioritize .specify over git -SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "$SCRIPT_DIR/common.sh" - -REPO_ROOT=$(get_repo_root) - -# Check if git is available at this repo root (not a parent) -if has_git; then - HAS_GIT=true -else - HAS_GIT=false -fi - -cd "$REPO_ROOT" - -SPECS_DIR="$REPO_ROOT/specs" -if [ "$DRY_RUN" != true ]; then - mkdir -p "$SPECS_DIR" -fi - -# Function to generate branch name with stop word filtering and length filtering -generate_branch_name() { - local description="$1" - - # Common stop words to filter out - local stop_words="^(i|a|an|the|to|for|of|in|on|at|by|with|from|is|are|was|were|be|been|being|have|has|had|do|does|did|will|would|should|could|can|may|might|must|shall|this|that|these|those|my|your|our|their|want|need|add|get|set)$" - - # Convert to lowercase and split into words - local clean_name=$(echo "$description" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/ /g') - - # Filter words: remove stop words and words shorter than 3 chars (unless they're uppercase acronyms in original) - local meaningful_words=() - for word in $clean_name; do - # Skip empty words - [ -z "$word" ] && continue - - # Keep words that are NOT stop words AND (length >= 3 OR are potential acronyms) - if ! echo "$word" | grep -qiE "$stop_words"; then - if [ ${#word} -ge 3 ]; then - meaningful_words+=("$word") - elif echo "$description" | grep -q "\b${word^^}\b"; then - # Keep short words if they appear as uppercase in original (likely acronyms) - meaningful_words+=("$word") - fi - fi - done - - # If we have meaningful words, use first 3-4 of them - if [ ${#meaningful_words[@]} -gt 0 ]; then - local max_words=3 - if [ ${#meaningful_words[@]} -eq 4 ]; then max_words=4; fi - - local result="" - local count=0 - for word in "${meaningful_words[@]}"; do - if [ $count -ge $max_words ]; then break; fi - if [ -n "$result" ]; then result="$result-"; fi - result="$result$word" - count=$((count + 1)) - done - echo "$result" - else - # Fallback to original logic if no meaningful words found - local cleaned=$(clean_branch_name "$description") - echo "$cleaned" | tr '-' '\n' | grep -v '^$' | head -3 | tr '\n' '-' | sed 's/-$//' - fi -} - -# Generate branch name -if [ -n "$SHORT_NAME" ]; then - # Use provided short name, just clean it up - BRANCH_SUFFIX=$(clean_branch_name "$SHORT_NAME") -else - # Generate from description with smart filtering - BRANCH_SUFFIX=$(generate_branch_name "$FEATURE_DESCRIPTION") -fi - -# Warn if --number and --timestamp are both specified -if [ "$USE_TIMESTAMP" = true ] && [ -n "$BRANCH_NUMBER" ]; then - >&2 echo "[specify] Warning: --number is ignored when --timestamp is used" - BRANCH_NUMBER="" -fi - -# Determine branch prefix -if [ "$USE_TIMESTAMP" = true ]; then - FEATURE_NUM=$(date +%Y%m%d-%H%M%S) - BRANCH_NAME="${FEATURE_NUM}-${BRANCH_SUFFIX}" -else - # Determine branch number - if [ -z "$BRANCH_NUMBER" ]; then - if [ "$DRY_RUN" = true ] && [ "$HAS_GIT" = true ]; then - # Dry-run: query remotes via ls-remote (side-effect-free, no fetch) - BRANCH_NUMBER=$(check_existing_branches "$SPECS_DIR" true) - elif [ "$DRY_RUN" = true ]; then - # Dry-run without git: local spec dirs only - HIGHEST=$(get_highest_from_specs "$SPECS_DIR") - BRANCH_NUMBER=$((HIGHEST + 1)) - elif [ "$HAS_GIT" = true ]; then - # Check existing branches on remotes - BRANCH_NUMBER=$(check_existing_branches "$SPECS_DIR") - else - # Fall back to local directory check - HIGHEST=$(get_highest_from_specs "$SPECS_DIR") - BRANCH_NUMBER=$((HIGHEST + 1)) - fi - fi - - # Force base-10 interpretation to prevent octal conversion (e.g., 010 → 8 in octal, but should be 10 in decimal) - FEATURE_NUM=$(printf "%03d" "$((10#$BRANCH_NUMBER))") - BRANCH_NAME="${FEATURE_NUM}-${BRANCH_SUFFIX}" -fi - -# GitHub enforces a 244-byte limit on branch names -# Validate and truncate if necessary -MAX_BRANCH_LENGTH=244 -if [ ${#BRANCH_NAME} -gt $MAX_BRANCH_LENGTH ]; then - # Calculate how much we need to trim from suffix - # Account for prefix length: timestamp (15) + hyphen (1) = 16, or sequential (3) + hyphen (1) = 4 - PREFIX_LENGTH=$(( ${#FEATURE_NUM} + 1 )) - MAX_SUFFIX_LENGTH=$((MAX_BRANCH_LENGTH - PREFIX_LENGTH)) - - # Truncate suffix at word boundary if possible - TRUNCATED_SUFFIX=$(echo "$BRANCH_SUFFIX" | cut -c1-$MAX_SUFFIX_LENGTH) - # Remove trailing hyphen if truncation created one - TRUNCATED_SUFFIX=$(echo "$TRUNCATED_SUFFIX" | sed 's/-$//') - - ORIGINAL_BRANCH_NAME="$BRANCH_NAME" - BRANCH_NAME="${FEATURE_NUM}-${TRUNCATED_SUFFIX}" - - >&2 echo "[specify] Warning: Branch name exceeded GitHub's 244-byte limit" - >&2 echo "[specify] Original: $ORIGINAL_BRANCH_NAME (${#ORIGINAL_BRANCH_NAME} bytes)" - >&2 echo "[specify] Truncated to: $BRANCH_NAME (${#BRANCH_NAME} bytes)" -fi - -FEATURE_DIR="$SPECS_DIR/$BRANCH_NAME" -SPEC_FILE="$FEATURE_DIR/spec.md" - -if [ "$DRY_RUN" != true ]; then - if [ "$HAS_GIT" = true ]; then - branch_create_error="" - if ! branch_create_error=$(git checkout -q -b "$BRANCH_NAME" 2>&1); then - current_branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || true)" - # Check if branch already exists - if git branch --list "$BRANCH_NAME" | grep -q .; then - if [ "$ALLOW_EXISTING" = true ]; then - # If we're already on the branch, continue without another checkout. - if [ "$current_branch" = "$BRANCH_NAME" ]; then - : - # Otherwise switch to the existing branch instead of failing. - elif ! switch_branch_error=$(git checkout -q "$BRANCH_NAME" 2>&1); then - >&2 echo "Error: Failed to switch to existing branch '$BRANCH_NAME'. Please resolve any local changes or conflicts and try again." - if [ -n "$switch_branch_error" ]; then - >&2 printf '%s\n' "$switch_branch_error" - fi - exit 1 - fi - elif [ "$USE_TIMESTAMP" = true ]; then - >&2 echo "Error: Branch '$BRANCH_NAME' already exists. Rerun to get a new timestamp or use a different --short-name." - exit 1 - else - >&2 echo "Error: Branch '$BRANCH_NAME' already exists. Please use a different feature name or specify a different number with --number." - exit 1 - fi - else - >&2 echo "Error: Failed to create git branch '$BRANCH_NAME'." - if [ -n "$branch_create_error" ]; then - >&2 printf '%s\n' "$branch_create_error" - else - >&2 echo "Please check your git configuration and try again." - fi - exit 1 - fi - fi - else - >&2 echo "[specify] Warning: Git repository not detected; skipped branch creation for $BRANCH_NAME" - fi - - mkdir -p "$FEATURE_DIR" - - if [ ! -f "$SPEC_FILE" ]; then - TEMPLATE=$(resolve_template "spec-template" "$REPO_ROOT") || true - if [ -n "$TEMPLATE" ] && [ -f "$TEMPLATE" ]; then - cp "$TEMPLATE" "$SPEC_FILE" - else - echo "Warning: Spec template not found; created empty spec file" >&2 - touch "$SPEC_FILE" - fi - fi - - # Inform the user how to persist the feature variable in their own shell - printf '# To persist: export SPECIFY_FEATURE=%q\n' "$BRANCH_NAME" >&2 -fi - -if $JSON_MODE; then - if command -v jq >/dev/null 2>&1; then - if [ "$DRY_RUN" = true ]; then - jq -cn \ - --arg branch_name "$BRANCH_NAME" \ - --arg spec_file "$SPEC_FILE" \ - --arg feature_num "$FEATURE_NUM" \ - '{BRANCH_NAME:$branch_name,SPEC_FILE:$spec_file,FEATURE_NUM:$feature_num,DRY_RUN:true}' - else - jq -cn \ - --arg branch_name "$BRANCH_NAME" \ - --arg spec_file "$SPEC_FILE" \ - --arg feature_num "$FEATURE_NUM" \ - '{BRANCH_NAME:$branch_name,SPEC_FILE:$spec_file,FEATURE_NUM:$feature_num}' - fi - else - if [ "$DRY_RUN" = true ]; then - printf '{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s","DRY_RUN":true}\n' "$(json_escape "$BRANCH_NAME")" "$(json_escape "$SPEC_FILE")" "$(json_escape "$FEATURE_NUM")" - else - printf '{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s"}\n' "$(json_escape "$BRANCH_NAME")" "$(json_escape "$SPEC_FILE")" "$(json_escape "$FEATURE_NUM")" - fi - fi -else - echo "BRANCH_NAME: $BRANCH_NAME" - echo "SPEC_FILE: $SPEC_FILE" - echo "FEATURE_NUM: $FEATURE_NUM" - if [ "$DRY_RUN" != true ]; then - printf '# To persist in your shell: export SPECIFY_FEATURE=%q\n' "$BRANCH_NAME" - fi -fi diff --git a/.specify/scripts/bash/setup-plan.sh b/.specify/scripts/bash/setup-plan.sh deleted file mode 100755 index 9f552314..00000000 --- a/.specify/scripts/bash/setup-plan.sh +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env bash - -set -e - -# Parse command line arguments -JSON_MODE=false -ARGS=() - -for arg in "$@"; do - case "$arg" in - --json) - JSON_MODE=true - ;; - --help|-h) - echo "Usage: $0 [--json]" - echo " --json Output results in JSON format" - echo " --help Show this help message" - exit 0 - ;; - *) - ARGS+=("$arg") - ;; - esac -done - -# Get script directory and load common functions -SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "$SCRIPT_DIR/common.sh" - -# Get all paths and variables from common functions -_paths_output=$(get_feature_paths) || { echo "ERROR: Failed to resolve feature paths" >&2; exit 1; } -eval "$_paths_output" -unset _paths_output - -# Check if we're on a proper feature branch (only for git repos) -check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1 - -# Ensure the feature directory exists -mkdir -p "$FEATURE_DIR" - -# Copy plan template if it exists -TEMPLATE=$(resolve_template "plan-template" "$REPO_ROOT") || true -if [[ -n "$TEMPLATE" ]] && [[ -f "$TEMPLATE" ]]; then - cp "$TEMPLATE" "$IMPL_PLAN" - echo "Copied plan template to $IMPL_PLAN" -else - echo "Warning: Plan template not found" - # Create a basic plan file if template doesn't exist - touch "$IMPL_PLAN" -fi - -# Output results -if $JSON_MODE; then - if has_jq; then - jq -cn \ - --arg feature_spec "$FEATURE_SPEC" \ - --arg impl_plan "$IMPL_PLAN" \ - --arg specs_dir "$FEATURE_DIR" \ - --arg branch "$CURRENT_BRANCH" \ - --arg has_git "$HAS_GIT" \ - '{FEATURE_SPEC:$feature_spec,IMPL_PLAN:$impl_plan,SPECS_DIR:$specs_dir,BRANCH:$branch,HAS_GIT:$has_git}' - else - printf '{"FEATURE_SPEC":"%s","IMPL_PLAN":"%s","SPECS_DIR":"%s","BRANCH":"%s","HAS_GIT":"%s"}\n' \ - "$(json_escape "$FEATURE_SPEC")" "$(json_escape "$IMPL_PLAN")" "$(json_escape "$FEATURE_DIR")" "$(json_escape "$CURRENT_BRANCH")" "$(json_escape "$HAS_GIT")" - fi -else - echo "FEATURE_SPEC: $FEATURE_SPEC" - echo "IMPL_PLAN: $IMPL_PLAN" - echo "SPECS_DIR: $FEATURE_DIR" - echo "BRANCH: $CURRENT_BRANCH" - echo "HAS_GIT: $HAS_GIT" -fi - diff --git a/.specify/templates/checklist-template.md b/.specify/templates/checklist-template.md deleted file mode 100644 index 806657da..00000000 --- a/.specify/templates/checklist-template.md +++ /dev/null @@ -1,40 +0,0 @@ -# [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.checklist` command based on feature context and requirements. - - - -## [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/.specify/templates/constitution-template.md b/.specify/templates/constitution-template.md deleted file mode 100644 index a4670ff4..00000000 --- a/.specify/templates/constitution-template.md +++ /dev/null @@ -1,50 +0,0 @@ -# [PROJECT_NAME] Constitution - - -## Core Principles - -### [PRINCIPLE_1_NAME] - -[PRINCIPLE_1_DESCRIPTION] - - -### [PRINCIPLE_2_NAME] - -[PRINCIPLE_2_DESCRIPTION] - - -### [PRINCIPLE_3_NAME] - -[PRINCIPLE_3_DESCRIPTION] - - -### [PRINCIPLE_4_NAME] - -[PRINCIPLE_4_DESCRIPTION] - - -### [PRINCIPLE_5_NAME] - -[PRINCIPLE_5_DESCRIPTION] - - -## [SECTION_2_NAME] - - -[SECTION_2_CONTENT] - - -## [SECTION_3_NAME] - - -[SECTION_3_CONTENT] - - -## Governance - - -[GOVERNANCE_RULES] - - -**Version**: [CONSTITUTION_VERSION] | **Ratified**: [RATIFICATION_DATE] | **Last Amended**: [LAST_AMENDED_DATE] - diff --git a/.specify/templates/plan-template.md b/.specify/templates/plan-template.md deleted file mode 100644 index 5a2fafeb..00000000 --- a/.specify/templates/plan-template.md +++ /dev/null @@ -1,104 +0,0 @@ -# Implementation Plan: [FEATURE] - -**Branch**: `[###-feature-name]` | **Date**: [DATE] | **Spec**: [link] -**Input**: Feature specification from `/specs/[###-feature-name]/spec.md` - -**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/templates/plan-template.md` for the execution workflow. - -## Summary - -[Extract from feature spec: primary requirement + technical approach from research] - -## Technical Context - - - -**Language/Version**: [e.g., Python 3.11, Swift 5.9, Rust 1.75 or NEEDS CLARIFICATION] -**Primary Dependencies**: [e.g., FastAPI, UIKit, LLVM or NEEDS CLARIFICATION] -**Storage**: [if applicable, e.g., PostgreSQL, CoreData, files or N/A] -**Testing**: [e.g., pytest, XCTest, cargo test or NEEDS CLARIFICATION] -**Target Platform**: [e.g., Linux server, iOS 15+, WASM or NEEDS CLARIFICATION] -**Project Type**: [e.g., library/cli/web-service/mobile-app/compiler/desktop-app or NEEDS CLARIFICATION] -**Performance Goals**: [domain-specific, e.g., 1000 req/s, 10k lines/sec, 60 fps or NEEDS CLARIFICATION] -**Constraints**: [domain-specific, e.g., <200ms p95, <100MB memory, offline-capable or NEEDS CLARIFICATION] -**Scale/Scope**: [domain-specific, e.g., 10k users, 1M LOC, 50 screens or NEEDS CLARIFICATION] - -## Constitution Check - -*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* - -[Gates determined based on constitution file] - -## Project Structure - -### Documentation (this feature) - -```text -specs/[###-feature]/ -├── plan.md # This file (/speckit.plan command output) -├── research.md # Phase 0 output (/speckit.plan command) -├── data-model.md # Phase 1 output (/speckit.plan command) -├── quickstart.md # Phase 1 output (/speckit.plan command) -├── contracts/ # Phase 1 output (/speckit.plan command) -└── tasks.md # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan) -``` - -### Source Code (repository root) - - -```text -# [REMOVE IF UNUSED] Option 1: Single project (DEFAULT) -src/ -├── models/ -├── services/ -├── cli/ -└── lib/ - -tests/ -├── contract/ -├── integration/ -└── unit/ - -# [REMOVE IF UNUSED] Option 2: Web application (when "frontend" + "backend" detected) -backend/ -├── src/ -│ ├── models/ -│ ├── services/ -│ └── api/ -└── tests/ - -frontend/ -├── src/ -│ ├── components/ -│ ├── pages/ -│ └── services/ -└── tests/ - -# [REMOVE IF UNUSED] Option 3: Mobile + API (when "iOS/Android" detected) -api/ -└── [same as backend above] - -ios/ or android/ -└── [platform-specific structure: feature modules, UI flows, platform tests] -``` - -**Structure Decision**: [Document the selected structure and reference the real -directories captured above] - -## Complexity Tracking - -> **Fill ONLY if Constitution Check has violations that must be justified** - -| Violation | Why Needed | Simpler Alternative Rejected Because | -|-----------|------------|-------------------------------------| -| [e.g., 4th project] | [current need] | [why 3 projects insufficient] | -| [e.g., Repository pattern] | [specific problem] | [why direct DB access insufficient] | diff --git a/.specify/templates/spec-template.md b/.specify/templates/spec-template.md deleted file mode 100644 index 4581e405..00000000 --- a/.specify/templates/spec-template.md +++ /dev/null @@ -1,128 +0,0 @@ -# Feature Specification: [FEATURE NAME] - -**Feature Branch**: `[###-feature-name]` -**Created**: [DATE] -**Status**: Draft -**Input**: User description: "$ARGUMENTS" - -## User Scenarios & Testing *(mandatory)* - - - -### 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 - - - -- What happens when [boundary condition]? -- How does system handle [error scenario]? - -## Requirements *(mandatory)* - - - -### Functional Requirements - -- **FR-001**: System MUST [specific capability, e.g., "allow users to create accounts"] -- **FR-002**: System MUST [specific capability, e.g., "validate email addresses"] -- **FR-003**: Users MUST be able to [key interaction, e.g., "reset their password"] -- **FR-004**: System MUST [data requirement, e.g., "persist user preferences"] -- **FR-005**: System MUST [behavior, e.g., "log all security events"] - -*Example of marking unclear requirements:* - -- **FR-006**: System MUST authenticate users via [NEEDS CLARIFICATION: auth method not specified - email/password, SSO, OAuth?] -- **FR-007**: System MUST retain user data for [NEEDS CLARIFICATION: retention period not specified] - -### 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)* - - - -### Measurable Outcomes - -- **SC-001**: [Measurable metric, e.g., "Users can complete account creation in under 2 minutes"] -- **SC-002**: [Measurable metric, e.g., "System handles 1000 concurrent users without degradation"] -- **SC-003**: [User satisfaction metric, e.g., "90% of users successfully complete primary task on first attempt"] -- **SC-004**: [Business metric, e.g., "Reduce support tickets related to [X] by 50%"] - -## Assumptions - - - -- [Assumption about target users, e.g., "Users have stable internet connectivity"] -- [Assumption about scope boundaries, e.g., "Mobile support is out of scope for v1"] -- [Assumption about data/environment, e.g., "Existing authentication system will be reused"] -- [Dependency on existing system/service, e.g., "Requires access to the existing user profile API"] diff --git a/.specify/templates/tasks-template.md b/.specify/templates/tasks-template.md deleted file mode 100644 index 60f9be45..00000000 --- a/.specify/templates/tasks-template.md +++ /dev/null @@ -1,251 +0,0 @@ ---- - -description: "Task list template for feature implementation" ---- - -# Tasks: [FEATURE NAME] - -**Input**: Design documents from `/specs/[###-feature-name]/` -**Prerequisites**: plan.md (required), spec.md (required for user stories), research.md, data-model.md, contracts/ - -**Tests**: The examples below include test tasks. Tests are OPTIONAL - only include them if explicitly requested in the feature specification. - -**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story. - -## Format: `[ID] [P?] [Story] Description` - -- **[P]**: Can run in parallel (different files, no dependencies) -- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3) -- Include exact file paths in descriptions - -## Path Conventions - -- **Single project**: `src/`, `tests/` at repository root -- **Web app**: `backend/src/`, `frontend/src/` -- **Mobile**: `api/src/`, `ios/src/` or `android/src/` -- Paths shown below assume single project - adjust based on plan.md structure - - - -## Phase 1: Setup (Shared Infrastructure) - -**Purpose**: Project initialization and basic structure - -- [ ] T001 Create project structure per implementation plan -- [ ] T002 Initialize [language] project with [framework] dependencies -- [ ] T003 [P] Configure linting and formatting tools - ---- - -## Phase 2: Foundational (Blocking Prerequisites) - -**Purpose**: Core infrastructure that MUST be complete before ANY user story can be implemented - -**⚠️ CRITICAL**: No user story work can begin until this phase is complete - -Examples of foundational tasks (adjust based on your project): - -- [ ] T004 Setup database schema and migrations framework -- [ ] T005 [P] Implement authentication/authorization framework -- [ ] T006 [P] Setup API routing and middleware structure -- [ ] T007 Create base models/entities that all stories depend on -- [ ] T008 Configure error handling and logging infrastructure -- [ ] T009 Setup environment configuration management - -**Checkpoint**: Foundation ready - user story implementation can now begin in parallel - ---- - -## Phase 3: User Story 1 - [Title] (Priority: P1) 🎯 MVP - -**Goal**: [Brief description of what this story delivers] - -**Independent Test**: [How to verify this story works on its own] - -### Tests for User Story 1 (OPTIONAL - only if tests requested) ⚠️ - -> **NOTE: Write these tests FIRST, ensure they FAIL before implementation** - -- [ ] T010 [P] [US1] Contract test for [endpoint] in tests/contract/test_[name].py -- [ ] T011 [P] [US1] Integration test for [user journey] in tests/integration/test_[name].py - -### Implementation for User Story 1 - -- [ ] T012 [P] [US1] Create [Entity1] model in src/models/[entity1].py -- [ ] T013 [P] [US1] Create [Entity2] model in src/models/[entity2].py -- [ ] T014 [US1] Implement [Service] in src/services/[service].py (depends on T012, T013) -- [ ] T015 [US1] Implement [endpoint/feature] in src/[location]/[file].py -- [ ] T016 [US1] Add validation and error handling -- [ ] T017 [US1] Add logging for user story 1 operations - -**Checkpoint**: At this point, User Story 1 should be fully functional and testable independently - ---- - -## Phase 4: User Story 2 - [Title] (Priority: P2) - -**Goal**: [Brief description of what this story delivers] - -**Independent Test**: [How to verify this story works on its own] - -### Tests for User Story 2 (OPTIONAL - only if tests requested) ⚠️ - -- [ ] T018 [P] [US2] Contract test for [endpoint] in tests/contract/test_[name].py -- [ ] T019 [P] [US2] Integration test for [user journey] in tests/integration/test_[name].py - -### Implementation for User Story 2 - -- [ ] T020 [P] [US2] Create [Entity] model in src/models/[entity].py -- [ ] T021 [US2] Implement [Service] in src/services/[service].py -- [ ] T022 [US2] Implement [endpoint/feature] in src/[location]/[file].py -- [ ] T023 [US2] Integrate with User Story 1 components (if needed) - -**Checkpoint**: At this point, User Stories 1 AND 2 should both work independently - ---- - -## Phase 5: User Story 3 - [Title] (Priority: P3) - -**Goal**: [Brief description of what this story delivers] - -**Independent Test**: [How to verify this story works on its own] - -### Tests for User Story 3 (OPTIONAL - only if tests requested) ⚠️ - -- [ ] T024 [P] [US3] Contract test for [endpoint] in tests/contract/test_[name].py -- [ ] T025 [P] [US3] Integration test for [user journey] in tests/integration/test_[name].py - -### Implementation for User Story 3 - -- [ ] T026 [P] [US3] Create [Entity] model in src/models/[entity].py -- [ ] T027 [US3] Implement [Service] in src/services/[service].py -- [ ] T028 [US3] Implement [endpoint/feature] in src/[location]/[file].py - -**Checkpoint**: All user stories should now be independently functional - ---- - -[Add more user story phases as needed, following the same pattern] - ---- - -## Phase N: Polish & Cross-Cutting Concerns - -**Purpose**: Improvements that affect multiple user stories - -- [ ] TXXX [P] Documentation updates in docs/ -- [ ] TXXX Code cleanup and refactoring -- [ ] TXXX Performance optimization across all stories -- [ ] TXXX [P] Additional unit tests (if requested) in tests/unit/ -- [ ] TXXX Security hardening -- [ ] TXXX Run quickstart.md validation - ---- - -## Dependencies & Execution Order - -### Phase Dependencies - -- **Setup (Phase 1)**: No dependencies - can start immediately -- **Foundational (Phase 2)**: Depends on Setup completion - BLOCKS all user stories -- **User Stories (Phase 3+)**: All depend on Foundational phase completion - - User stories can then proceed in parallel (if staffed) - - Or sequentially in priority order (P1 → P2 → P3) -- **Polish (Final Phase)**: Depends on all desired user stories being complete - -### User Story Dependencies - -- **User Story 1 (P1)**: Can start after Foundational (Phase 2) - No dependencies on other stories -- **User Story 2 (P2)**: Can start after Foundational (Phase 2) - May integrate with US1 but should be independently testable -- **User Story 3 (P3)**: Can start after Foundational (Phase 2) - May integrate with US1/US2 but should be independently testable - -### Within Each User Story - -- Tests (if included) MUST be written and FAIL before implementation -- Models before services -- Services before endpoints -- Core implementation before integration -- Story complete before moving to next priority - -### Parallel Opportunities - -- All Setup tasks marked [P] can run in parallel -- All Foundational tasks marked [P] can run in parallel (within Phase 2) -- Once Foundational phase completes, all user stories can start in parallel (if team capacity allows) -- All tests for a user story marked [P] can run in parallel -- Models within a story marked [P] can run in parallel -- Different user stories can be worked on in parallel by different team members - ---- - -## Parallel Example: User Story 1 - -```bash -# Launch all tests for User Story 1 together (if tests requested): -Task: "Contract test for [endpoint] in tests/contract/test_[name].py" -Task: "Integration test for [user journey] in tests/integration/test_[name].py" - -# Launch all models for User Story 1 together: -Task: "Create [Entity1] model in src/models/[entity1].py" -Task: "Create [Entity2] model in src/models/[entity2].py" -``` - ---- - -## Implementation Strategy - -### MVP First (User Story 1 Only) - -1. Complete Phase 1: Setup -2. Complete Phase 2: Foundational (CRITICAL - blocks all stories) -3. Complete Phase 3: User Story 1 -4. **STOP and VALIDATE**: Test User Story 1 independently -5. Deploy/demo if ready - -### Incremental Delivery - -1. Complete Setup + Foundational → Foundation ready -2. Add User Story 1 → Test independently → Deploy/Demo (MVP!) -3. Add User Story 2 → Test independently → Deploy/Demo -4. Add User Story 3 → Test independently → Deploy/Demo -5. Each story adds value without breaking previous stories - -### Parallel Team Strategy - -With multiple developers: - -1. Team completes Setup + Foundational together -2. Once Foundational is done: - - Developer A: User Story 1 - - Developer B: User Story 2 - - Developer C: User Story 3 -3. Stories complete and integrate independently - ---- - -## Notes - -- [P] tasks = different files, no dependencies -- [Story] label maps task to specific user story for traceability -- Each user story should be independently completable and testable -- Verify tests fail before implementing -- Commit after each task or logical group -- Stop at any checkpoint to validate story independently -- Avoid: vague tasks, same file conflicts, cross-story dependencies that break independence diff --git a/.specify/workflows/speckit/workflow.yml b/.specify/workflows/speckit/workflow.yml deleted file mode 100644 index bf184510..00000000 --- a/.specify/workflows/speckit/workflow.yml +++ /dev/null @@ -1,63 +0,0 @@ -schema_version: "1.0" -workflow: - id: "speckit" - name: "Full SDD Cycle" - version: "1.0.0" - author: "GitHub" - description: "Runs specify → plan → tasks → implement with review gates" - -requires: - speckit_version: ">=0.7.2" - integrations: - any: ["copilot", "claude", "gemini"] - -inputs: - spec: - type: string - required: true - prompt: "Describe what you want to build" - integration: - type: string - default: "copilot" - prompt: "Integration to use (e.g. claude, copilot, gemini)" - scope: - type: string - default: "full" - enum: ["full", "backend-only", "frontend-only"] - -steps: - - id: specify - command: speckit.specify - integration: "{{ inputs.integration }}" - input: - args: "{{ inputs.spec }}" - - - id: review-spec - type: gate - message: "Review the generated spec before planning." - options: [approve, reject] - on_reject: abort - - - id: plan - command: speckit.plan - integration: "{{ inputs.integration }}" - input: - args: "{{ inputs.spec }}" - - - id: review-plan - type: gate - message: "Review the plan before generating tasks." - options: [approve, reject] - on_reject: abort - - - id: tasks - command: speckit.tasks - integration: "{{ inputs.integration }}" - input: - args: "{{ inputs.spec }}" - - - id: implement - command: speckit.implement - integration: "{{ inputs.integration }}" - input: - args: "{{ inputs.spec }}" diff --git a/.specify/workflows/workflow-registry.json b/.specify/workflows/workflow-registry.json deleted file mode 100644 index e812f87f..00000000 --- a/.specify/workflows/workflow-registry.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "schema_version": "1.0", - "workflows": { - "speckit": { - "name": "Full SDD Cycle", - "version": "1.0.0", - "description": "Runs specify \u2192 plan \u2192 tasks \u2192 implement with review gates", - "source": "bundled", - "installed_at": "2026-05-21T07:10:44.476080+00:00", - "updated_at": "2026-05-21T07:10:44.476083+00:00" - } - } -} \ No newline at end of file From a49542def9a21fc1ceb7f625a9b7eea5db8c4266 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Hu=C3=9F?= Date: Thu, 21 May 2026 11:53:21 +0200 Subject: [PATCH 05/11] Remove brainstorm directory from tracking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Brainstorm documents are local working notes, not PR artifacts. The spec.md captures the refined requirements. Assisted-By: 🤖 Claude Code Signed-off-by: Roland Huß --- .gitignore | 1 + brainstorm/00-overview.md | 20 ----- brainstorm/01-agentcard-into-agentruntime.md | 82 -------------------- 3 files changed, 1 insertion(+), 102 deletions(-) delete mode 100644 brainstorm/00-overview.md delete mode 100644 brainstorm/01-agentcard-into-agentruntime.md diff --git a/.gitignore b/.gitignore index 866adfc8..e6730560 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,4 @@ Dockerfile.cross .claude/settings.json .claude/settings.local.json .specify/ +brainstorm/ diff --git a/brainstorm/00-overview.md b/brainstorm/00-overview.md deleted file mode 100644 index 968a1cac..00000000 --- a/brainstorm/00-overview.md +++ /dev/null @@ -1,20 +0,0 @@ -# Brainstorm Overview - -Last updated: 2026-05-20 - -## Sessions - -| # | Date | Topic | Status | Spec | -|---|------|-------|--------|------| -| 01 | 2026-05-20 | agentcard-into-agentruntime | active | - | - -## Open Threads - -- Exact A2A AgentCard JSON schema fields for the Go struct (from #01) -- Service endpoint discovery from AgentRuntime targetRef (from #01) -- Feature flag naming and default (from #01) -- Whether status.card.fetchedAt timestamp is needed (from #01) - -## Parked Ideas - -(none) diff --git a/brainstorm/01-agentcard-into-agentruntime.md b/brainstorm/01-agentcard-into-agentruntime.md deleted file mode 100644 index 93c26afd..00000000 --- a/brainstorm/01-agentcard-into-agentruntime.md +++ /dev/null @@ -1,82 +0,0 @@ -# Brainstorm: AgentCard Data Into AgentRuntime Status - -**Date:** 2026-05-20 -**Status:** active - -## Problem Framing - -AgentRuntime and AgentCard are two CRDs with identical cardinality (one per workload), the same namespace, the same lifecycle, and the same owner. AgentCard is a pure read-only CR whose content is entirely controller-managed. Its data (card metadata, verification status, binding state) fits naturally into AgentRuntime's `status` section. - -The AgentCard CRD has three specific problems documented in ADR ODH-ADR-AgentOps-0002: - -1. It conflates observation with policy. The CR is controller-created and controller-written, leaving no room for admin-authored policy fields. -2. Its JWS signing pipeline signs a skeleton card with empty skills/capabilities (#292). The signed content and live content are disconnected. -3. Maintaining two CRs for the same Deployment doubles the RBAC surface and splits "how this agent participates in the platform" across two APIs. - -This brainstorm covers the first step: moving card data into AgentRuntime status. mTLS, policy fields, and AgentCard removal are separate follow-up items. - -## Context - -- ADR: ODH-ADR-AgentOps-0002 (Agent Network Policy and mTLS Identity) -- Upstream issue: kagenti-operator#371 (Consolidate AgentCard into AgentRuntime status) -- Related: kagenti-operator#292 (skeleton-card problem) -- Related: kagenti-operator#284 (mTLS verified fetch, merged 2026-05-20, infrastructure reusable later) -- Upstream sync: IBM maintainers agreed to AgentCard deprecation path (2026-05-15) -- RHAISTRAT-1599 AC review: acceptance criteria updated to reflect AgentRuntime-based discovery - -## Approaches Considered - -### A: Extend the existing AgentRuntime controller (chosen) - -Add a card fetch phase to the existing `agentruntime_controller.go` reconciliation loop. After resolving the target workload and applying labels, the controller fetches `/.well-known/agent-card.json` from the agent's Service endpoint over plain HTTP, parses it into an A2A-compliant struct, and writes it to `status.card`. Triggered by Pod template hash change on the target workload. - -- Pros: Minimal new code. Reuses existing workload watches and reconciliation infrastructure. Single controller, single reconcile loop. -- Cons: Makes agentruntime_controller.go larger (already 29K). Card fetch adds network I/O to a controller that currently only does Kubernetes API calls. - -### B: New dedicated card discovery controller - -Create a separate `agentruntime_card_controller.go` that watches AgentRuntime CRs and handles only card fetching. The main controller continues handling labels, sidecar injection, and config. - -- Pros: Clean separation. Card fetch failures don't block main reconciliation. Independent rate limiter for network I/O. -- Cons: Two controllers watching the same CRD. Need to coordinate status updates. More moving parts for a simple HTTP GET. - -### C: Card fetch as a Kubernetes Job - -Create short-lived Jobs to fetch cards on rollout events. - -- Pros: Completely decouples fetching from the controller. Reusable binary. -- Cons: Heavy for a simple HTTP GET. Adds Job RBAC, cleanup, failure handling. Overkill. - -## Decision - -**Approach A: Extend the existing AgentRuntime controller.** The card fetch is a single HTTP GET that takes milliseconds. If performance becomes an issue at scale (hundreds of agents), extracting to a separate controller (Approach B) is a clean refactor. - -## Key Requirements - -### What gets built - -- New `AgentCardStatus` struct on AgentRuntime CRD, modeled on the A2A protocol agent-card.json spec (name, description, skills, protocols, endpoint). Not mirrored from the existing AgentCard CRD fields. -- Card fetch phase added to the existing AgentRuntime controller reconcile loop. -- Fetch triggers on Pod template hash change (rollout events only, no polling, no periodic fallback). -- Fetch is plain HTTP GET to `/.well-known/agent-card.json` via the agent's Service endpoint. -- Card data written to `status.card` on AgentRuntime. - -### What gets deprecated - -- AgentCard CRD gets a deprecation log warning on creation. -- AgentCard remains functional (both CRDs coexist during transition). - -### Out of scope (future iterations) - -- mTLS for the card fetch (port from #284). -- `spec.policy` fields (allowedIngressNamespaces, dependencies, externalEgress). -- AgentCard CRD removal and controller cleanup. -- ValidatingAdmissionPolicy for label restriction. -- Migration tooling. - -## Open Questions - -- Exact A2A AgentCard JSON schema fields to model in the Go struct -- How to discover the agent's Service endpoint from the AgentRuntime targetRef (resolve Deployment -> Service via selector matching or naming convention) -- Feature flag name and default (e.g. `--enable-card-discovery`, off by default) -- Whether `status.card.fetchedAt` timestamp should be included for diagnostics From 421db64aa93c4be3ba164e50bc3318b1c46173ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Hu=C3=9F?= Date: Thu, 21 May 2026 11:54:37 +0200 Subject: [PATCH 06/11] Revert CLAUDE.md speckit pointer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The speckit plan reference in CLAUDE.md is a local development aid, not part of the spec PR. Assisted-By: 🤖 Claude Code Signed-off-by: Roland Huß --- CLAUDE.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index a1683000..31dbfd4d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -45,9 +45,3 @@ Orchestrate skills for enhancing related repositories: | `skills:scan` | Audit repository skills | | `skills:write` | Create or edit skills following the standard | | `skills:validate` | Validate skill format and structure | - - -For additional context about technologies to be used, project structure, -shell commands, and other important information, read the current plan -at `specs/001-agentcard-into-status/plan.md` - From d0a1359ecf16828d6696cf586cf4e40e8d5f86ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Hu=C3=9F?= Date: Thu, 21 May 2026 17:25:57 +0200 Subject: [PATCH 07/11] Address review feedback: scope boundaries, workload generalization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add explicit Out of Scope section with migration paths for identity binding policy, enforcement actions, AgentCardSyncReconciler, and AgentCard CRD removal - Generalize "Deployment" to "workload" throughout spec and review guide to cover StatefulSet and Sandbox - Add edge cases for fetch timeout (10s) and dynamic card changes - Add brainstorm document for identity binding migration (Phase 2) - Restore brainstorm/ in git tracking Addresses review feedback from @pdettori on PR #372. Assisted-By: 🤖 Claude Code Signed-off-by: Roland Huß --- .gitignore | 1 - brainstorm/02-identity-binding-migration.md | 106 +++++++++++++++++++ specs/001-agentcard-into-status/REVIEWERS.md | 6 +- specs/001-agentcard-into-status/spec.md | 49 +++++++-- 4 files changed, 149 insertions(+), 13 deletions(-) create mode 100644 brainstorm/02-identity-binding-migration.md diff --git a/.gitignore b/.gitignore index e6730560..866adfc8 100644 --- a/.gitignore +++ b/.gitignore @@ -61,4 +61,3 @@ Dockerfile.cross .claude/settings.json .claude/settings.local.json .specify/ -brainstorm/ diff --git a/brainstorm/02-identity-binding-migration.md b/brainstorm/02-identity-binding-migration.md new file mode 100644 index 00000000..f20035a6 --- /dev/null +++ b/brainstorm/02-identity-binding-migration.md @@ -0,0 +1,106 @@ +# Brainstorm: Identity Binding Migration from AgentCard to AgentRuntime + +**Date:** 2026-05-21 +**Status:** active +**Triggered by:** PR #372 review feedback (pdettori) + +## Problem Framing + +AgentCard's `spec.identityBinding` is the only admin-authored policy field on the AgentCard CRD. It controls two things: + +1. **Trust domain scoping** (`trustDomain`): overrides the operator-level `--spire-trust-domain` for a specific agent workload. The controller checks whether the agent's SPIFFE ID (from JWS signature or mTLS peer cert) belongs to this trust domain. + +2. **Strict enforcement** (`strict`): when true, a binding failure removes the `signature-verified` label from the workload, triggering the NetworkPolicy controller to apply a restrictive policy that isolates the agent. + +Identity binding is orthogonal to card discovery. The card fetch is the mechanism that surfaces the SPIFFE ID, but the binding evaluation and enforcement are about the workload's identity posture, not the card content. Moving card data into AgentRuntime status does not affect binding behavior. + +## Current State + +``` +AgentCard.spec.identityBinding +├── trustDomain: string (per-agent override of --spire-trust-domain) +└── strict: bool (default: false) + ├── false → binding results recorded in status only, no enforcement + └── true → binding failure removes signature-verified label + → NetworkPolicy controller applies restrictive policy +``` + +AgentRuntime already has a related field: + +``` +AgentRuntime.spec.identity.spiffe.trustDomain +``` + +This field serves a different purpose today (configuring the workload's own SVID trust domain for injection), but the naming and placement overlap with identity binding's trust domain scoping. + +## Migration Path + +### Phase 1: Card data into AgentRuntime status (this PR, #372) + +- `status.card` surfaces card data, fetch metadata, and verification results +- Identity binding stays on AgentCard +- AgentCard controller continues all enforcement (label propagation, NetworkPolicy) +- No behavior changes for existing identity binding users + +### Phase 2: Identity binding into AgentRuntime spec (future PR) + +Move `identityBinding` to AgentRuntime.spec, alongside the existing identity fields: + +```yaml +apiVersion: agent.kagenti.dev/v1alpha1 +kind: AgentRuntime +spec: + identity: + spiffe: + trustDomain: example.org # existing: workload SVID trust domain + binding: # new: migrated from AgentCard + trustDomain: example.org # override for binding evaluation + strict: false # enforcement toggle +``` + +Open question: should `identity.spiffe.trustDomain` and `identity.binding.trustDomain` be unified? They serve related but different purposes (SVID injection vs binding evaluation). If unified, a single `trustDomain` at the `identity` level could serve both. + +### Phase 3: Enforcement migration (future PR) + +Move label propagation logic (`signature-verified` label) from AgentCard controller to AgentRuntime controller. The AgentRuntime controller already manages workload labels and annotations, so this is a natural fit. + +### Phase 4: AgentCard CRD removal + +Once identity binding and enforcement have migrated: +- Remove AgentCard CRD +- Remove AgentCardSyncReconciler (auto-creates AgentCards for labelled workloads) +- Remove AgentCard controller +- Clean up RBAC, webhooks, and test fixtures + +## Design Considerations + +### Enforcement during coexistence + +During the transition (Phases 1-2), enforcement continues via the AgentCard controller. Both CRDs coexist. Operators who use identity binding today see no behavior change. + +After Phase 2, the AgentRuntime controller evaluates binding from its own spec fields. The AgentCard controller's binding logic becomes dead code and is removed in Phase 4. + +### AgentCardSyncReconciler + +The sync controller auto-creates AgentCards for workloads with `kagenti.io/type=agent` labels. During coexistence, it continues to function. After Phase 2, the sync controller is no longer needed because: +- Card discovery is handled by AgentRuntime controller (Phase 1) +- Identity binding is on AgentRuntime spec (Phase 2) +- There is no remaining reason to auto-create AgentCards + +### Trust domain field unification + +Three options: + +| Option | Structure | Pros | Cons | +|--------|-----------|------|------| +| A: Separate fields | `identity.spiffe.trustDomain` + `identity.binding.trustDomain` | Clear separation of concerns | Confusing duplication | +| B: Unified field | `identity.trustDomain` (serves both) | Simple, one source of truth | Loses granularity if they diverge | +| C: Binding inherits | `identity.binding.trustDomain` defaults to `identity.spiffe.trustDomain` if unset | Best of both, explicit override | Slightly more complex defaulting | + +Option C seems best: the common case is a single trust domain, but the override is available when needed. + +## Open Questions + +- Should binding evaluation in the AgentRuntime controller use the verification data already in `status.card` (from Phase 1), or should it re-evaluate independently? +- Is there a need for namespace-level binding policy (via ConfigMap), analogous to `authbridge-runtime-config`? +- Should the `AgentCardSyncReconciler` get its own deprecation warning before removal? diff --git a/specs/001-agentcard-into-status/REVIEWERS.md b/specs/001-agentcard-into-status/REVIEWERS.md index 016babb5..051db4a9 100644 --- a/specs/001-agentcard-into-status/REVIEWERS.md +++ b/specs/001-agentcard-into-status/REVIEWERS.md @@ -18,8 +18,8 @@ A new `fetchAndUpdateCard` phase is added to the existing AgentRuntime reconcile **Applies when**: - The operator is started with `--enable-card-discovery=true` -- An AgentRuntime targets a Deployment whose Pods serve `/.well-known/agent-card.json` -- The backing Deployment rolls out (pod template hash changes) +- An AgentRuntime targets a workload (Deployment, StatefulSet, or Sandbox) whose Pods serve `/.well-known/agent-card.json` +- The backing workload rolls out (pod template hash or generation changes) **Does not apply when**: - `--enable-card-discovery` is not set (default). No card fetch occurs. @@ -30,7 +30,7 @@ A new `fetchAndUpdateCard` phase is added to the existing AgentRuntime reconcile 1. **Extend existing controller (not a new one)**: The card fetch is a single HTTP GET added to the existing reconcile loop. Creating a separate controller would add coordination complexity for minimal isolation benefit. If performance becomes an issue at scale, extraction is a clean refactor. -2. **Selector match for service resolution**: Resolves the Deployment's Pod selector labels to find the matching Service. Falls back from name-based convention (matching AgentCard behavior) to selector matching. No annotations required. +2. **Selector match for service resolution**: Resolves the workload's (Deployment, StatefulSet, Sandbox) Pod selector labels to find the matching Service. Falls back from name-based convention (matching AgentCard behavior) to selector matching. No annotations required. 3. **Retain stale data on fetch failure**: When a fetch fails, the last successful card data is kept in `status.card`. The `CardSynced` condition and `fetchedAt` timestamp signal staleness. This avoids disruption for tools that consume `status.card`. diff --git a/specs/001-agentcard-into-status/spec.md b/specs/001-agentcard-into-status/spec.md index 7ae28bd0..67f7a4a2 100644 --- a/specs/001-agentcard-into-status/spec.md +++ b/specs/001-agentcard-into-status/spec.md @@ -9,7 +9,7 @@ ### Session 2026-05-21 -- Q: How should the controller discover the Service endpoint for a given AgentRuntime's targetRef Deployment? → A: Selector match: resolve Deployment Pod selector, find Services whose selector matches, use the first match. +- Q: How should the controller discover the Service endpoint for a given AgentRuntime's targetRef workload? → A: Selector match: resolve the workload's Pod selector, find Services whose selector matches, use the first match. - Q: What happens to previously populated status.card data when a card fetch fails? → A: Retain last successful card data; rely on the CardSynced condition and fetchedAt timestamp to signal staleness. - Q: What happens to existing status.card data when the feature flag is toggled off? → A: Clear status.card on the next reconcile of each AgentRuntime when the flag is disabled. - Q: What should status.card contain, given mTLS is in scope? → A: Card payload fields, fetch metadata (fetchedAt, cardId, protocol), and verification fields (signature validation, SPIFFE identity). mTLS reuses infrastructure from PR #284. @@ -26,9 +26,9 @@ A platform operator queries a single resource (AgentRuntime) to see what an agen **Acceptance Scenarios**: -1. **Given** an AgentRuntime targeting a Deployment whose Pods serve a valid A2A agent card at `/.well-known/agent-card.json`, **When** the controller reconciles the AgentRuntime, **Then** `status.card` contains the agent's name, description, skills, capabilities, endpoint URL, fetchedAt timestamp, cardId content hash, and detected protocol. -2. **Given** an AgentRuntime whose `status.card` is already populated, **When** the backing Deployment rolls out a new Pod template (hash change), **Then** the controller re-fetches the card and updates `status.card` with the new content. -3. **Given** an AgentRuntime targeting a Deployment, **When** the agent's card endpoint is unreachable or returns invalid JSON, **Then** `status.card` retains the last successfully fetched data and a `CardSynced` condition indicates the fetch failure with a human-readable reason. +1. **Given** an AgentRuntime targeting a workload (Deployment, StatefulSet, or Sandbox) whose Pods serve a valid A2A agent card at `/.well-known/agent-card.json`, **When** the controller reconciles the AgentRuntime, **Then** `status.card` contains the agent's name, description, skills, capabilities, endpoint URL, fetchedAt timestamp, cardId content hash, and detected protocol. +2. **Given** an AgentRuntime whose `status.card` is already populated, **When** the backing workload rolls out a new Pod template (hash change or generation change), **Then** the controller re-fetches the card and updates `status.card` with the new content. +3. **Given** an AgentRuntime targeting a workload, **When** the agent's card endpoint is unreachable or returns invalid JSON, **Then** `status.card` retains the last successfully fetched data and a `CardSynced` condition indicates the fetch failure with a human-readable reason. --- @@ -83,10 +83,12 @@ A cluster administrator controls whether the new card discovery behavior is acti - What happens when the agent's Service has multiple ports? The controller uses selector matching to find the Service, then targets the port serving the A2A protocol (by well-known port name or the first HTTP port). - How does the system handle a card endpoint that returns a valid JSON response but not a valid A2A agent card structure? The controller treats it as a fetch failure, retains any previously fetched data, and surfaces the parsing error in the `CardSynced` condition. -- What happens when the backing Deployment has zero ready Pods? The controller skips the card fetch and sets a condition indicating the workload is not ready. -- What happens if the card response is excessively large? The controller enforces a size limit on the response body to prevent resource exhaustion. -- What happens when no Service matches the Deployment's Pod selector? The controller sets the `CardSynced` condition to false with reason "ServiceNotFound" and skips the fetch. +- What happens when the backing workload has zero ready Pods? The controller skips the card fetch and sets a condition indicating the workload is not ready. +- What happens if the card response is excessively large? The controller enforces a size limit on the response body (1 MiB) to prevent resource exhaustion. +- What happens when no Service matches the workload's Pod selector? The controller sets the `CardSynced` condition to false with reason "ServiceNotFound" and skips the fetch. - What happens when the mTLS handshake fails (e.g., certificate expired, wrong trust domain)? The controller retains stale card data and reports the TLS error in the `CardSynced` condition. +- What happens if the card fetch hangs or is slow? The controller enforces a 10-second timeout on the HTTP/mTLS request. Timeout is treated as a fetch failure (stale data retained, `CardSynced=False`). +- What happens when an agent updates its card content without a workload rollout (e.g., hot-reloading skills)? The controller does not detect this. Card data in `status.card` reflects the state at the last rollout. This is an intentional constraint: event-driven fetch (no polling) trades freshness of dynamic card changes for reduced API server and network load. ## Requirements *(mandatory)* @@ -99,7 +101,7 @@ A cluster administrator controls whether the new card discovery behavior is acti - **FR-005**: The system MUST gate the card fetch behavior behind a feature flag that defaults to disabled. - **FR-006**: The system MUST emit a deprecation log warning when a new AgentCard CR is created. - **FR-007**: The system MUST record a `fetchedAt` timestamp in `status.card` so operators can see when the card data was last refreshed. -- **FR-008**: The system MUST resolve the agent's Service endpoint by matching the Deployment's Pod selector labels to Services in the same namespace (selector match), using the first matching Service. +- **FR-008**: The system MUST resolve the agent's Service endpoint by matching the workload's Pod selector labels to Services in the same namespace (selector match), using the first matching Service. This applies to all supported workload types (Deployment, StatefulSet, Sandbox). - **FR-009**: The system MUST enforce a maximum response body size when fetching the card to prevent resource exhaustion. - **FR-010**: The system MUST support mTLS for the card fetch, reusing the SPIFFE/SPIRE infrastructure from PR #284. When mTLS is configured on the AgentRuntime, the controller uses the workload's SVID to establish a verified connection. - **FR-011**: The system MUST populate verification fields in `status.card` (attested SPIFFE ID, signature validation result) when the card is fetched over mTLS and the card contains JWS signatures. @@ -122,9 +124,38 @@ A cluster administrator controls whether the new card discovery behavior is acti - **SC-004**: The feature can be enabled or disabled at operator startup without redeployment of agent workloads. Disabling clears stale card data from all AgentRuntimes. - **SC-005**: When mTLS is configured, the card fetch verifies the agent's SPIFFE identity and validates JWS signatures, with results visible in `status.card`. +## Out of Scope (with migration path) + +The following are explicitly out of scope for this iteration. Each item includes the intended migration path so the deprecation trajectory is visible. + +### Identity binding policy (`spec.identityBinding`) + +Identity binding is a **workload identity policy** that validates whether an agent's SPIFFE ID belongs to a configured trust domain. It is orthogonal to card discovery: the card fetch is merely the mechanism that surfaces the SPIFFE ID, but the binding evaluation and enforcement are about the workload, not the card content. + +**Current home**: `AgentCard.spec.identityBinding` (trustDomain, strict) +**Intended destination**: `AgentRuntime.spec` in a follow-up iteration, alongside the existing `spec.identity.spiffe.trustDomain` field. +**During coexistence**: Identity binding policy stays on AgentCard. The AgentCard controller continues to evaluate binding and propagate the `signature-verified` label. No enforcement behavior changes. +**Brainstorm**: See `brainstorm/02-identity-binding-migration.md` for detailed analysis. + +### Enforcement actions (label propagation, NetworkPolicy) + +The current AgentCard controller propagates the `signature-verified` label to workloads based on identity verification results. The NetworkPolicy controller uses this label to gate inter-agent traffic. + +**This PR**: `status.card` on AgentRuntime is purely observational. It surfaces verification results but does not drive enforcement actions. +**During coexistence**: The AgentCard controller continues to handle all enforcement (label propagation, NetworkPolicy). No enforcement behavior changes. +**Future iteration**: When identity binding moves to AgentRuntime.spec, the enforcement logic (label propagation) moves to the AgentRuntime controller. This is a separate spec. + +### AgentCardSyncReconciler + +The `AgentCardSyncReconciler` auto-creates AgentCard CRs for labelled agent workloads. It continues to function during coexistence. Its deprecation and removal will be part of the AgentCard CRD removal iteration (after identity binding migration is complete). + +### AgentCard CRD removal and migration tooling + +Full CRD removal, ValidatingAdmissionPolicy for label restriction, and migration tooling are deferred until identity binding and enforcement have migrated to AgentRuntime. + ## Assumptions -- Each AgentRuntime targets exactly one Deployment, and there is at most one Service matching that Deployment's Pod selector in the same namespace. +- Each AgentRuntime targets exactly one workload (Deployment, StatefulSet, or Sandbox), and there is at most one Service matching that workload's Pod selector in the same namespace. - The card fetch adds negligible latency to the reconcile loop (single HTTP/mTLS GET, typically sub-second). - The existing `AgentCardData` Go struct (already defined in the codebase) can be extended or wrapped to include fetch metadata and verification fields for `status.card`. - IBM maintainers have agreed to the AgentCard deprecation path (confirmed 2026-05-15 per brainstorm context). From 4516588af9bd7a6b83474a6c4f2e446b9935326e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Hu=C3=9F?= Date: Thu, 21 May 2026 19:21:55 +0200 Subject: [PATCH 08/11] Update plan artifacts for review feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move lastPodTemplateHash from CRD status to annotation - Generalize Deployment references to workload throughout - Add fetch timeout condition to data model - Add annotation note to research decisions Assisted-By: 🤖 Claude Code Signed-off-by: Roland Huß --- specs/001-agentcard-into-status/data-model.md | 8 +++++--- specs/001-agentcard-into-status/plan.md | 9 +++++---- specs/001-agentcard-into-status/research.md | 12 +++++++----- specs/001-agentcard-into-status/tasks.md | 6 +++--- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/specs/001-agentcard-into-status/data-model.md b/specs/001-agentcard-into-status/data-model.md index 8445578c..9440f8ad 100644 --- a/specs/001-agentcard-into-status/data-model.md +++ b/specs/001-agentcard-into-status/data-model.md @@ -30,7 +30,8 @@ Added to `AgentRuntimeStatus` as an optional field. | `signatureKeyID` | string | Key ID from verified JWS header | New field | | `signatureVerificationDetails` | string | Verification details/error message | New field | | `attestedAgentSpiffeID` | string | SPIFFE ID from mTLS peer certificate | New field | -| `lastPodTemplateHash` | string | Pod template hash at time of fetch (internal, for change detection) | New field | + +**Note**: Change-detection hash (`lastPodTemplateHash`) is stored as an annotation (`agent.kagenti.dev/last-card-fetch-hash`) on the AgentRuntime, not in the CRD status. This keeps the implementation mechanism out of the public API surface. ### Modified: AgentRuntimeStatus @@ -57,8 +58,9 @@ Added to AgentRuntime `status.conditions[]`. | `CardSynced` | True | Card fetched and parsed successfully | | `CardFetchFailed` | False | HTTP/mTLS fetch error | | `CardParseFailed` | False | JSON parse error | -| `ServiceNotFound` | False | No Service matches the Deployment selector | -| `WorkloadNotReady` | False | Deployment has zero ready Pods | +| `ServiceNotFound` | False | No Service matches the workload's selector | +| `WorkloadNotReady` | False | Workload has zero ready Pods | +| `FetchTimeout` | False | Card fetch exceeded 10-second timeout | | `CardDiscoveryDisabled` | False | Feature flag is off (clears stale data) | | `FetchSkipped` | True | Pod template hash unchanged, card data still valid | diff --git a/specs/001-agentcard-into-status/plan.md b/specs/001-agentcard-into-status/plan.md index 7437dde8..b41a316b 100644 --- a/specs/001-agentcard-into-status/plan.md +++ b/specs/001-agentcard-into-status/plan.md @@ -5,7 +5,7 @@ ## Summary -Move A2A agent card discovery into the AgentRuntime controller's reconcile loop so operators can read card data, fetch metadata, and mTLS verification results from a single resource (`status.card` on AgentRuntime). Reuses the existing `agentcard.Fetcher` and `agentcard.AuthenticatedFetcher` interfaces and the `AgentCardData` struct. The feature is gated behind a `--enable-card-discovery` flag (default: disabled). AgentCard CRD remains functional with a deprecation warning. +Move A2A agent card discovery into the AgentRuntime controller's reconcile loop so operators can read card data, fetch metadata, and mTLS verification results from a single resource (`status.card` on AgentRuntime). Reuses the existing `agentcard.Fetcher` and `agentcard.AuthenticatedFetcher` interfaces and the `AgentCardData` struct. The feature is gated behind a `--enable-card-discovery` flag (default: disabled). AgentCard CRD remains functional with a deprecation warning. Identity binding policy and enforcement actions stay on AgentCard during coexistence (see Out of Scope in spec.md). ## Technical Context @@ -15,8 +15,8 @@ Move A2A agent card discovery into the AgentRuntime controller's reconcile loop **Testing**: Ginkgo/Gomega (unit + integration), envtest for controller tests, e2e in `test/e2e/` **Target Platform**: Kubernetes 1.31+ **Project Type**: Kubernetes operator (kubebuilder-based) -**Performance Goals**: Card fetch adds < 1s to reconcile; no periodic re-fetch (event-driven only) -**Constraints**: No new CRDs; feature-gated; backward compatible with existing AgentCard workflows +**Performance Goals**: Card fetch adds < 1s to reconcile; 10s timeout on HTTP/mTLS request; no periodic re-fetch (event-driven only) +**Constraints**: No new CRDs; feature-gated; backward compatible with existing AgentCard workflows; change-detection hash stored as annotation (not in CRD status API surface) **Scale/Scope**: Hundreds of AgentRuntimes per cluster (card fetch is 1:1 with AgentRuntime) ## Constitution Check @@ -36,6 +36,7 @@ specs/001-agentcard-into-status/ ├── research.md # Phase 0 output ├── data-model.md # Phase 1 output ├── contracts/ # Phase 1 output (CRD status contract) +├── REVIEWERS.md # Review guide for PR └── tasks.md # Phase 2 output (/speckit.tasks) ``` @@ -66,7 +67,7 @@ kagenti-operator/ └── integration/ # MODIFY: add card fetch integration tests ``` -**Structure Decision**: Existing kubebuilder project structure. All changes extend existing files. No new packages or directories needed. +**Structure Decision**: Existing kubebuilder project structure. All changes extend existing files. No new packages or directories needed. Workload resolution covers Deployment, StatefulSet, and Sandbox (matching existing AgentCard controller patterns). ## Complexity Tracking diff --git a/specs/001-agentcard-into-status/research.md b/specs/001-agentcard-into-status/research.md index 91a67d03..cfde7133 100644 --- a/specs/001-agentcard-into-status/research.md +++ b/specs/001-agentcard-into-status/research.md @@ -2,20 +2,20 @@ ## R1: Service Endpoint Resolution Strategy -**Decision**: Selector matching. Resolve the Deployment's Pod selector labels, list Services in the same namespace, find the first Service whose selector matches. +**Decision**: Selector matching. Resolve the workload's Pod selector labels, list Services in the same namespace, find the first Service whose selector matches. Applies to all supported workload types (Deployment, StatefulSet, Sandbox). -**Rationale**: Standard Kubernetes pattern. Works automatically without user annotations. The existing AgentCard controller uses a simpler convention (Service name = workload name via `workload.ServiceName`), but selector matching is more robust for cases where Service and Deployment names diverge. +**Rationale**: Standard Kubernetes pattern. Works automatically without user annotations. The existing AgentCard controller uses a simpler convention (Service name = workload name via `workload.ServiceName`), but selector matching is more robust for cases where Service and workload names diverge. **Alternatives considered**: -- Naming convention (Service name = Deployment name): simpler but brittle when names differ +- Naming convention (Service name = workload name): simpler but brittle when names differ - Annotation-driven: more flexible but adds configuration burden - Hybrid (selector match + annotation override): future enhancement if needed -**Implementation note**: The existing `AgentCardReconciler.getWorkload()` sets `ServiceName: targetRef.Name` (line 526 of agentcard_controller.go). For the AgentRuntime controller, we should match this convention initially (use the Deployment name as the Service name) since it aligns with how Services are typically created for agent workloads. If no Service matches by name, fall back to selector matching. +**Implementation note**: The existing `AgentCardReconciler.getWorkload()` sets `ServiceName: targetRef.Name` (line 526 of agentcard_controller.go). For the AgentRuntime controller, we should match this convention initially (use the workload name as the Service name) since it aligns with how Services are typically created for agent workloads. If no Service matches by name, fall back to selector matching. ## R2: Card Fetch Trigger Mechanism -**Decision**: Pod template hash change detection. The AgentRuntime controller already watches Deployments and reconciles on changes. The card fetch phase checks whether the Deployment's `pod-template-hash` has changed since the last successful fetch by comparing against a stored hash in `status.card`. +**Decision**: Pod template hash change detection. The AgentRuntime controller already watches workloads and reconciles on changes. The card fetch phase checks whether the workload's pod-template-hash (or generation for StatefulSets/Sandboxes) has changed since the last successful fetch by comparing against a hash stored in an annotation (`agent.kagenti.dev/last-card-fetch-hash`), not in the CRD status API surface. **Rationale**: Avoids unnecessary HTTP calls on every reconcile. Pod template hash changes correlate with actual code/config changes that could affect the agent card. The AgentRuntime controller already reconciles on Deployment changes, so no new watches needed. @@ -61,6 +61,8 @@ - Fetch metadata: `fetchedAt` (timestamp), `cardId` (SHA-256 content hash), `protocol` (detected agent protocol) - Verification: `validSignature` (bool), `signatureKeyID`, `attestedAgentSpiffeID`, `signatureVerificationDetails` +**Note (from review feedback)**: `lastPodTemplateHash` was originally in this struct but moved to an annotation (`agent.kagenti.dev/last-card-fetch-hash`) to avoid coupling the change-detection mechanism to the public API surface. + ## R6: Deprecation Warning Implementation **Decision**: Add a log warning at Info level in `AgentCardReconciler.Reconcile()` when processing a newly created AgentCard (check if `agentCard.CreationTimestamp` is within the last reconcile window). Also emit a Kubernetes Event. diff --git a/specs/001-agentcard-into-status/tasks.md b/specs/001-agentcard-into-status/tasks.md index 3c34e0c9..30b6960f 100644 --- a/specs/001-agentcard-into-status/tasks.md +++ b/specs/001-agentcard-into-status/tasks.md @@ -17,7 +17,7 @@ **Purpose**: CRD type changes and code generation that all stories depend on -- [ ] T001 Add `CardStatus` struct to `AgentRuntimeStatus` in `kagenti-operator/api/v1alpha1/agentruntime_types.go`. The `CardStatus` struct embeds `AgentCardData` (reuse existing struct) and adds: `FetchedAt *metav1.Time`, `CardId string`, `Protocol string`, `ValidSignature *bool`, `SignatureKeyID string`, `SignatureVerificationDetails string`, `AttestedAgentSpiffeID string`, `LastPodTemplateHash string`. Add the `Card *CardStatus` field to `AgentRuntimeStatus`. Add `CardSynced` as a new condition type constant. +- [ ] T001 Add `CardStatus` struct to `AgentRuntimeStatus` in `kagenti-operator/api/v1alpha1/agentruntime_types.go`. The `CardStatus` struct embeds `AgentCardData` (reuse existing struct) and adds: `FetchedAt *metav1.Time`, `CardId string`, `Protocol string`, `ValidSignature *bool`, `SignatureKeyID string`, `SignatureVerificationDetails string`, `AttestedAgentSpiffeID string`. The change-detection hash is stored as an annotation (`agent.kagenti.dev/last-card-fetch-hash`), not in the struct. Add the `Card *CardStatus` field to `AgentRuntimeStatus`. Add `CardSynced` as a new condition type constant. - [ ] T002 Run `make generate` and `make manifests` in `kagenti-operator/` to regenerate deepcopy functions and CRD manifests. Verify `zz_generated.deepcopy.go` has the new `CardStatus` deepcopy method and `config/crd/bases/` has the updated AgentRuntime CRD. - [ ] T003 Add `--enable-card-discovery` boolean flag (default: false) to `kagenti-operator/cmd/main.go`. When enabled, create `agentcard.NewConfigMapFetcher()` and `agentcard.NewSpiffeFetcher()` (conditional on SPIRE config), and inject them into the `AgentRuntimeReconciler` as new fields: `AgentFetcher agentcard.Fetcher`, `AuthenticatedFetcher agentcard.AuthenticatedFetcher`, `SignatureProvider signature.Provider`, `EnableCardDiscovery bool`, `SpireTrustDomain string`. Follow the existing pattern of `--enable-verified-fetch` flag wiring for the AgentCard controller. @@ -51,9 +51,9 @@ ### Implementation for User Story 1 -- [ ] T008 [US1] Add a `fetchAndUpdateCard` method to `AgentRuntimeReconciler` in `kagenti-operator/internal/controller/agentruntime_controller.go`. This is the main card fetch phase, called from `Reconcile()` after step 5 (config hash). Logic: (1) If `EnableCardDiscovery` is false, clear `status.card` if populated, set `CardSynced` condition to `CardDiscoveryDisabled`, return. (2) Check pod template hash from the target Deployment against `status.card.lastPodTemplateHash`; if unchanged and `status.card` is populated, set `CardSynced` to `FetchSkipped`, return. (3) Call `resolveServiceForWorkload`. (4) Build service URL via `agentcard.GetServiceURL`. (5) Call `AgentFetcher.Fetch()`. (6) On success: build `CardStatus` from fetched `AgentCardData`, set `fetchedAt`, compute `cardId` via SHA-256, set `protocol`, store current pod template hash. (7) On failure: retain existing `status.card`, set `CardSynced=False` with error reason. (8) Update status via `r.Status().Update()` with retry. Note: FR-009 (max response body size) is implicitly satisfied because the reused `doHTTPFetch()` in `internal/agentcard/fetcher.go` already enforces `maxCardSize = 1 MiB`. +- [ ] T008 [US1] Add a `fetchAndUpdateCard` method to `AgentRuntimeReconciler` in `kagenti-operator/internal/controller/agentruntime_controller.go`. This is the main card fetch phase, called from `Reconcile()` after step 5 (config hash). Logic: (1) If `EnableCardDiscovery` is false, clear `status.card` if populated, set `CardSynced` condition to `CardDiscoveryDisabled`, return. (2) Read the change-detection hash from annotation `agent.kagenti.dev/last-card-fetch-hash` on the AgentRuntime; compare against current workload pod template hash (or generation for StatefulSet/Sandbox); if unchanged and `status.card` is populated, set `CardSynced` to `FetchSkipped`, return. (3) Call `resolveServiceForWorkload`. (4) Build service URL via `agentcard.GetServiceURL`. (5) Call `AgentFetcher.Fetch()`. (6) On success: build `CardStatus` from fetched `AgentCardData`, set `fetchedAt`, compute `cardId` via SHA-256, set `protocol`, store current hash in annotation. (7) On failure: retain existing `status.card`, set `CardSynced=False` with error reason. (8) Update status via `r.Status().Update()` with retry. Note: FR-009 (max response body size) is implicitly satisfied because the reused `doHTTPFetch()` in `internal/agentcard/fetcher.go` already enforces `maxCardSize = 1 MiB`. - [ ] T009 [US1] Wire the `fetchAndUpdateCard` call into the `Reconcile()` method in `kagenti-operator/internal/controller/agentruntime_controller.go`. Insert after the config hash computation (step 5, around line 165) and before the label propagation phase. Pass the resolved target workload info. -- [ ] T010 [US1] Extract the pod template hash from the target Deployment in the `fetchAndUpdateCard` method. For Deployments, read `spec.template` metadata hash. For StatefulSets and Sandboxes, use the resource generation as the change-detection key. +- [ ] T010 [US1] Extract the change-detection key from the target workload in the `fetchAndUpdateCard` method. For Deployments, read the pod-template-hash label. For StatefulSets and Sandboxes, use the resource generation. Store the key in the `agent.kagenti.dev/last-card-fetch-hash` annotation on the AgentRuntime object (not in status). **Checkpoint**: US1 complete. `status.card` is populated on reconcile when the flag is enabled. From 33d745ea9320713d181003eb5cc8698799499a5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Hu=C3=9F?= Date: Thu, 21 May 2026 21:50:53 +0200 Subject: [PATCH 09/11] feat: implement agent card discovery and status sync MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add CardStatus type to AgentRuntimeStatus for storing fetched A2A agent card data with content hashing, signature verification, and SPIFFE attestation fields. Implement the AgentCard controller that discovers card endpoints from workload Services and syncs card data into the AgentRuntime status. Wire up the controller with a --enable-card-discovery flag and add comprehensive unit tests. Assisted-By: 🤖 Claude Code Signed-off-by: Roland Huß --- .../crds/agent.kagenti.dev_agentruntimes.yaml | 226 +++++++++++++++ .../api/v1alpha1/agentruntime_types.go | 40 +++ .../api/v1alpha1/zz_generated.deepcopy.go | 30 ++ kagenti-operator/cmd/main.go | 27 +- .../agent.kagenti.dev_agentruntimes.yaml | 226 +++++++++++++++ kagenti-operator/config/rbac/role.yaml | 6 +- .../controller/agentcard_controller.go | 9 + .../controller/agentcard_controller_test.go | 109 ++++++++ .../controller/agentruntime_controller.go | 262 ++++++++++++++++++ .../agentruntime_controller_test.go | 255 +++++++++++++++++ specs/001-agentcard-into-status/tasks.md | 38 +-- 11 files changed, 1200 insertions(+), 28 deletions(-) diff --git a/charts/kagenti-operator/crds/agent.kagenti.dev_agentruntimes.yaml b/charts/kagenti-operator/crds/agent.kagenti.dev_agentruntimes.yaml index 8d5fcb6b..ff811c0b 100644 --- a/charts/kagenti-operator/crds/agent.kagenti.dev_agentruntimes.yaml +++ b/charts/kagenti-operator/crds/agent.kagenti.dev_agentruntimes.yaml @@ -30,6 +30,11 @@ spec: jsonPath: .status.phase name: Phase type: string + - description: Card Sync Status + jsonPath: .status.conditions[?(@.type=='CardSynced')].status + name: CardSynced + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date @@ -131,6 +136,13 @@ spec: mtls.mode > "disabled". Setting mtlsMode != disabled implicitly requires SPIRE — the operator auto-enables spire for the workload. + CR-empty vs CR="disabled" are observably different in + `kubectl get agentruntime -o yaml` (the former omits the field, + the latter shows mtlsMode: disabled) but produce the same + effective mode: empty falls through to the namespace ConfigMap, + "disabled" is an explicit override that pins mode off even when + the namespace default is non-disabled. + Note: changing mtlsMode triggers a pod rollout because authbridge cannot hot-reload mTLS config (the byte-peek listener is wired at process start). @@ -198,6 +210,220 @@ spec: status: description: AgentRuntimeStatus defines the observed state of AgentRuntime. properties: + card: + description: Card holds A2A agent card data discovered from the workload's + Service endpoint. + properties: + attestedAgentSpiffeID: + description: AttestedAgentSpiffeID is the SPIFFE ID extracted + from the mTLS peer certificate. + type: string + capabilities: + description: The A2A capability set supported by the agent. + properties: + extensions: + description: A list of protocol extensions supported by the + agent. + items: + description: AgentExtension describes an A2A protocol extension + supported by the agent. + properties: + description: + description: A human-readable description of how this + agent uses the extension. + type: string + params: + additionalProperties: + x-kubernetes-preserve-unknown-fields: true + description: Extension-specific configuration parameters. + type: object + required: + description: |- + If true, the client must understand and comply with the extension's + requirements. + type: boolean + uri: + description: The unique URI identifying the extension. + type: string + type: object + type: array + pushNotifications: + description: |- + Indicates if the agent supports sending push notifications for + asynchronous task updates. + type: boolean + streaming: + description: Indicates if the agent supports streaming responses. + type: boolean + type: object + cardId: + description: CardID is a SHA-256 content hash of the fetched card + data. + type: string + defaultInputModes: + description: |- + The set of interaction modes that the agent supports across all skills, + defined as media types. + items: + type: string + type: array + defaultOutputModes: + description: The media types supported as outputs from this agent. + items: + type: string + type: array + description: + description: |- + A human-readable description of the agent, assisting users and other + agents in understanding its purpose. + type: string + documentationUrl: + description: A URL providing additional documentation about the + agent. + type: string + fetchedAt: + description: FetchedAt is the timestamp of the last successful + card fetch. + format: date-time + type: string + iconUrl: + description: A URL to an icon for the agent. + type: string + name: + description: A human-readable name for the agent. + type: string + protocol: + description: Protocol is the detected agent protocol (e.g., "a2a"). + type: string + provider: + description: The service provider of the agent. + properties: + organization: + description: The name of the agent provider's organization. + type: string + url: + description: A URL for the agent provider's website or relevant + documentation. + type: string + type: object + signatureKeyID: + description: SignatureKeyID is the key ID from the verified JWS + header. + type: string + signatureVerificationDetails: + description: SignatureVerificationDetails contains details or + errors from signature verification. + type: string + signatures: + description: JWS signatures per A2A spec §8.4.2. + items: + description: AgentCardSignature represents a JWS signature on + an AgentCard (A2A spec §8.4.2). + properties: + header: + description: Header contains optional unprotected JWS header + parameters. + properties: + timestamp: + description: Timestamp is when the signature was created + (ISO 8601 string) + type: string + type: object + protected: + description: Protected is the base64url-encoded JWS protected + header (contains alg, kid, x5c). + type: string + signature: + description: Signature is the base64url-encoded JWS signature + value. + type: string + required: + - protected + - signature + type: object + type: array + skills: + description: |- + Skills represent the abilities of an agent. A skill is a focused set of + behaviors that the agent is likely to succeed at. + items: + description: AgentSkill represents a skill offered by the agent. + properties: + description: + description: A detailed description of the skill. + type: string + examples: + description: Example prompts or scenarios that this skill + can handle. + items: + type: string + type: array + id: + description: A unique identifier for the agent's skill. + type: string + inputModes: + description: |- + The set of supported input media types for this skill, overriding the + agent's defaults. + items: + type: string + type: array + name: + description: A human-readable name for the skill. + type: string + outputModes: + description: |- + The set of supported output media types for this skill, overriding the + agent's defaults. + items: + type: string + type: array + parameters: + description: The parameters accepted by this skill. + items: + description: SkillParameter defines a parameter accepted + by a skill. + properties: + default: + description: The default value for this parameter. + type: string + description: + description: A human-readable description of the parameter. + type: string + name: + description: The name of the parameter. + type: string + required: + description: Indicates if this parameter must be provided. + type: boolean + type: + description: The type of the parameter (e.g., "string", + "number", "boolean", "object", "array"). + type: string + type: object + type: array + tags: + description: A set of keywords describing the skill's capabilities. + items: + type: string + type: array + type: object + type: array + supportsAuthenticatedExtendedCard: + description: |- + Indicates if the agent supports providing an extended agent card when + authenticated. + type: boolean + url: + description: The URL of the agent's endpoint. + type: string + validSignature: + description: ValidSignature is the result of JWS signature verification. + type: boolean + version: + description: The version of the agent. + type: string + type: object conditions: description: Conditions represent the current state of the AgentRuntime items: diff --git a/kagenti-operator/api/v1alpha1/agentruntime_types.go b/kagenti-operator/api/v1alpha1/agentruntime_types.go index 7aa1d864..9c81cc1d 100644 --- a/kagenti-operator/api/v1alpha1/agentruntime_types.go +++ b/kagenti-operator/api/v1alpha1/agentruntime_types.go @@ -167,6 +167,41 @@ type SamplingSpec struct { Rate float64 `json:"rate"` } +// CardStatus holds the fetched A2A agent card data along with fetch metadata +// and optional verification results. Populated by the card discovery phase when +// --enable-card-discovery is set. +type CardStatus struct { + AgentCardData `json:",inline"` + + // FetchedAt is the timestamp of the last successful card fetch. + // +optional + FetchedAt *metav1.Time `json:"fetchedAt,omitempty"` + + // CardID is a SHA-256 content hash of the fetched card data. + // +optional + CardID string `json:"cardId,omitempty"` + + // Protocol is the detected agent protocol (e.g., "a2a"). + // +optional + Protocol string `json:"protocol,omitempty"` + + // ValidSignature is the result of JWS signature verification. + // +optional + ValidSignature *bool `json:"validSignature,omitempty"` + + // SignatureKeyID is the key ID from the verified JWS header. + // +optional + SignatureKeyID string `json:"signatureKeyID,omitempty"` + + // SignatureVerificationDetails contains details or errors from signature verification. + // +optional + SignatureVerificationDetails string `json:"signatureVerificationDetails,omitempty"` + + // AttestedAgentSpiffeID is the SPIFFE ID extracted from the mTLS peer certificate. + // +optional + AttestedAgentSpiffeID string `json:"attestedAgentSpiffeID,omitempty"` +} + // AgentRuntimeStatus defines the observed state of AgentRuntime. type AgentRuntimeStatus struct { // Phase is the high-level state of the AgentRuntime @@ -177,6 +212,10 @@ type AgentRuntimeStatus struct { // +optional ConfiguredPods int32 `json:"configuredPods,omitempty"` + // Card holds A2A agent card data discovered from the workload's Service endpoint. + // +optional + Card *CardStatus `json:"card,omitempty"` + // Conditions represent the current state of the AgentRuntime // +optional Conditions []metav1.Condition `json:"conditions,omitempty"` @@ -188,6 +227,7 @@ type AgentRuntimeStatus struct { // +kubebuilder:printcolumn:name="Type",type="string",JSONPath=".spec.type",description="Workload Type" // +kubebuilder:printcolumn:name="Target",type="string",JSONPath=".spec.targetRef.name",description="Target Workload" // +kubebuilder:printcolumn:name="Phase",type="string",JSONPath=".status.phase",description="Runtime Phase" +// +kubebuilder:printcolumn:name="CardSynced",type="string",JSONPath=".status.conditions[?(@.type=='CardSynced')].status",description="Card Sync Status",priority=1 // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" // AgentRuntime attaches runtime configuration to a backing workload classified as an diff --git a/kagenti-operator/api/v1alpha1/zz_generated.deepcopy.go b/kagenti-operator/api/v1alpha1/zz_generated.deepcopy.go index 1576f880..d5ff4707 100644 --- a/kagenti-operator/api/v1alpha1/zz_generated.deepcopy.go +++ b/kagenti-operator/api/v1alpha1/zz_generated.deepcopy.go @@ -397,6 +397,11 @@ func (in *AgentRuntimeSpec) DeepCopy() *AgentRuntimeSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AgentRuntimeStatus) DeepCopyInto(out *AgentRuntimeStatus) { *out = *in + if in.Card != nil { + in, out := &in.Card, &out.Card + *out = new(CardStatus) + (*in).DeepCopyInto(*out) + } if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions *out = make([]v1.Condition, len(*in)) @@ -477,6 +482,31 @@ func (in *BindingStatus) DeepCopy() *BindingStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CardStatus) DeepCopyInto(out *CardStatus) { + *out = *in + in.AgentCardData.DeepCopyInto(&out.AgentCardData) + if in.FetchedAt != nil { + in, out := &in.FetchedAt, &out.FetchedAt + *out = (*in).DeepCopy() + } + if in.ValidSignature != nil { + in, out := &in.ValidSignature, &out.ValidSignature + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CardStatus. +func (in *CardStatus) DeepCopy() *CardStatus { + if in == nil { + return nil + } + out := new(CardStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IdentityBinding) DeepCopyInto(out *IdentityBinding) { *out = *in diff --git a/kagenti-operator/cmd/main.go b/kagenti-operator/cmd/main.go index 6ee3c106..5e1730ea 100644 --- a/kagenti-operator/cmd/main.go +++ b/kagenti-operator/cmd/main.go @@ -101,6 +101,8 @@ func main() { var enforceNetworkPolicies bool var enableMLflow bool + var enableCardDiscovery bool + var enableVerifiedFetch bool var verifiedFetchSpiffeSocket string @@ -142,6 +144,8 @@ func main() { flag.BoolVar(&enableMLflow, "enable-mlflow", false, "Enable MLflow experiment tracking integration") + flag.BoolVar(&enableCardDiscovery, "enable-card-discovery", false, + "Enable automatic agent card discovery from AgentRuntime workloads into status.card") flag.BoolVar(&enableVerifiedFetch, "enable-verified-fetch", false, "Enable mTLS-authenticated fetch of agent cards via SPIFFE identity") flag.StringVar(&verifiedFetchSpiffeSocket, "verified-fetch-spiffe-socket", @@ -419,12 +423,23 @@ func main() { os.Exit(1) } - if err = (&controller.AgentRuntimeReconciler{ - Client: mgr.GetClient(), - APIReader: mgr.GetAPIReader(), - Scheme: mgr.GetScheme(), - Recorder: mgr.GetEventRecorderFor("agentruntime-controller"), - }).SetupWithManager(mgr); err != nil { + artReconciler := &controller.AgentRuntimeReconciler{ + Client: mgr.GetClient(), + APIReader: mgr.GetAPIReader(), + Scheme: mgr.GetScheme(), + Recorder: mgr.GetEventRecorderFor("agentruntime-controller"), + EnableCardDiscovery: enableCardDiscovery, + SpireTrustDomain: spireTrustDomain, + } + if enableCardDiscovery { + artReconciler.AgentFetcher = agentFetcher + artReconciler.SignatureProvider = sigProvider + if authenticatedFetcher != nil { + artReconciler.AuthenticatedFetcher = authenticatedFetcher + } + setupLog.Info("Card discovery enabled for AgentRuntime controller") + } + if err = artReconciler.SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "AgentRuntime") os.Exit(1) } diff --git a/kagenti-operator/config/crd/bases/agent.kagenti.dev_agentruntimes.yaml b/kagenti-operator/config/crd/bases/agent.kagenti.dev_agentruntimes.yaml index 8d5fcb6b..ff811c0b 100644 --- a/kagenti-operator/config/crd/bases/agent.kagenti.dev_agentruntimes.yaml +++ b/kagenti-operator/config/crd/bases/agent.kagenti.dev_agentruntimes.yaml @@ -30,6 +30,11 @@ spec: jsonPath: .status.phase name: Phase type: string + - description: Card Sync Status + jsonPath: .status.conditions[?(@.type=='CardSynced')].status + name: CardSynced + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date @@ -131,6 +136,13 @@ spec: mtls.mode > "disabled". Setting mtlsMode != disabled implicitly requires SPIRE — the operator auto-enables spire for the workload. + CR-empty vs CR="disabled" are observably different in + `kubectl get agentruntime -o yaml` (the former omits the field, + the latter shows mtlsMode: disabled) but produce the same + effective mode: empty falls through to the namespace ConfigMap, + "disabled" is an explicit override that pins mode off even when + the namespace default is non-disabled. + Note: changing mtlsMode triggers a pod rollout because authbridge cannot hot-reload mTLS config (the byte-peek listener is wired at process start). @@ -198,6 +210,220 @@ spec: status: description: AgentRuntimeStatus defines the observed state of AgentRuntime. properties: + card: + description: Card holds A2A agent card data discovered from the workload's + Service endpoint. + properties: + attestedAgentSpiffeID: + description: AttestedAgentSpiffeID is the SPIFFE ID extracted + from the mTLS peer certificate. + type: string + capabilities: + description: The A2A capability set supported by the agent. + properties: + extensions: + description: A list of protocol extensions supported by the + agent. + items: + description: AgentExtension describes an A2A protocol extension + supported by the agent. + properties: + description: + description: A human-readable description of how this + agent uses the extension. + type: string + params: + additionalProperties: + x-kubernetes-preserve-unknown-fields: true + description: Extension-specific configuration parameters. + type: object + required: + description: |- + If true, the client must understand and comply with the extension's + requirements. + type: boolean + uri: + description: The unique URI identifying the extension. + type: string + type: object + type: array + pushNotifications: + description: |- + Indicates if the agent supports sending push notifications for + asynchronous task updates. + type: boolean + streaming: + description: Indicates if the agent supports streaming responses. + type: boolean + type: object + cardId: + description: CardID is a SHA-256 content hash of the fetched card + data. + type: string + defaultInputModes: + description: |- + The set of interaction modes that the agent supports across all skills, + defined as media types. + items: + type: string + type: array + defaultOutputModes: + description: The media types supported as outputs from this agent. + items: + type: string + type: array + description: + description: |- + A human-readable description of the agent, assisting users and other + agents in understanding its purpose. + type: string + documentationUrl: + description: A URL providing additional documentation about the + agent. + type: string + fetchedAt: + description: FetchedAt is the timestamp of the last successful + card fetch. + format: date-time + type: string + iconUrl: + description: A URL to an icon for the agent. + type: string + name: + description: A human-readable name for the agent. + type: string + protocol: + description: Protocol is the detected agent protocol (e.g., "a2a"). + type: string + provider: + description: The service provider of the agent. + properties: + organization: + description: The name of the agent provider's organization. + type: string + url: + description: A URL for the agent provider's website or relevant + documentation. + type: string + type: object + signatureKeyID: + description: SignatureKeyID is the key ID from the verified JWS + header. + type: string + signatureVerificationDetails: + description: SignatureVerificationDetails contains details or + errors from signature verification. + type: string + signatures: + description: JWS signatures per A2A spec §8.4.2. + items: + description: AgentCardSignature represents a JWS signature on + an AgentCard (A2A spec §8.4.2). + properties: + header: + description: Header contains optional unprotected JWS header + parameters. + properties: + timestamp: + description: Timestamp is when the signature was created + (ISO 8601 string) + type: string + type: object + protected: + description: Protected is the base64url-encoded JWS protected + header (contains alg, kid, x5c). + type: string + signature: + description: Signature is the base64url-encoded JWS signature + value. + type: string + required: + - protected + - signature + type: object + type: array + skills: + description: |- + Skills represent the abilities of an agent. A skill is a focused set of + behaviors that the agent is likely to succeed at. + items: + description: AgentSkill represents a skill offered by the agent. + properties: + description: + description: A detailed description of the skill. + type: string + examples: + description: Example prompts or scenarios that this skill + can handle. + items: + type: string + type: array + id: + description: A unique identifier for the agent's skill. + type: string + inputModes: + description: |- + The set of supported input media types for this skill, overriding the + agent's defaults. + items: + type: string + type: array + name: + description: A human-readable name for the skill. + type: string + outputModes: + description: |- + The set of supported output media types for this skill, overriding the + agent's defaults. + items: + type: string + type: array + parameters: + description: The parameters accepted by this skill. + items: + description: SkillParameter defines a parameter accepted + by a skill. + properties: + default: + description: The default value for this parameter. + type: string + description: + description: A human-readable description of the parameter. + type: string + name: + description: The name of the parameter. + type: string + required: + description: Indicates if this parameter must be provided. + type: boolean + type: + description: The type of the parameter (e.g., "string", + "number", "boolean", "object", "array"). + type: string + type: object + type: array + tags: + description: A set of keywords describing the skill's capabilities. + items: + type: string + type: array + type: object + type: array + supportsAuthenticatedExtendedCard: + description: |- + Indicates if the agent supports providing an extended agent card when + authenticated. + type: boolean + url: + description: The URL of the agent's endpoint. + type: string + validSignature: + description: ValidSignature is the result of JWS signature verification. + type: boolean + version: + description: The version of the agent. + type: string + type: object conditions: description: Conditions represent the current state of the AgentRuntime items: diff --git a/kagenti-operator/config/rbac/role.yaml b/kagenti-operator/config/rbac/role.yaml index 69056fe6..44a8d399 100644 --- a/kagenti-operator/config/rbac/role.yaml +++ b/kagenti-operator/config/rbac/role.yaml @@ -124,20 +124,20 @@ rules: - apiGroups: - mlflow.opendatahub.io resources: - - mlflows + - mlflowexperiments verbs: - get - list + - update - watch - apiGroups: - mlflow.opendatahub.io resources: - - mlflowexperiments + - mlflows verbs: - get - list - watch - - update - apiGroups: - networking.k8s.io resources: diff --git a/kagenti-operator/internal/controller/agentcard_controller.go b/kagenti-operator/internal/controller/agentcard_controller.go index 892304c3..24f392c3 100644 --- a/kagenti-operator/internal/controller/agentcard_controller.go +++ b/kagenti-operator/internal/controller/agentcard_controller.go @@ -171,6 +171,15 @@ func (r *AgentCardReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( return ctrl.Result{}, nil } + if agentCard.CreationTimestamp.After(time.Now().Add(-5 * time.Minute)) { + agentCardLogger.Info("AgentCard is deprecated; card data is now available via AgentRuntime status.card. Migrate to AgentRuntime-based discovery.", + "agentCard", agentCard.Name, "namespace", agentCard.Namespace) + if r.Recorder != nil { + r.Recorder.Event(agentCard, corev1.EventTypeWarning, "Deprecated", + "AgentCard is deprecated; card data is now available via AgentRuntime status.card. Migrate to AgentRuntime-based discovery.") + } + } + workload, err := r.getWorkload(ctx, agentCard) if err != nil { agentCardLogger.Error(err, "Failed to get workload", "agentCard", agentCard.Name) diff --git a/kagenti-operator/internal/controller/agentcard_controller_test.go b/kagenti-operator/internal/controller/agentcard_controller_test.go index db120deb..52a079c0 100644 --- a/kagenti-operator/internal/controller/agentcard_controller_test.go +++ b/kagenti-operator/internal/controller/agentcard_controller_test.go @@ -20,6 +20,7 @@ import ( "context" "encoding/json" "errors" + "strings" "sync" "time" @@ -29,6 +30,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -1722,6 +1724,113 @@ var _ = Describe("AgentCard Controller - getSyncPeriod", func() { Expect(result.RequeueAfter).To(Equal(10 * time.Second)) }) }) + + Context("Deprecation warning for new AgentCards", func() { + const ( + deprecationNS = "default" + deprecationDeploy = "deprecation-deploy" + deprecationCard = "deprecation-card" + ) + + ctx := context.Background() + + It("should emit deprecation event for recently created AgentCard", func() { + replicas := int32(1) + dep := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: deprecationDeploy, + Namespace: deprecationNS, + Labels: map[string]string{ + LabelAgentType: LabelValueAgent, + ProtocolLabelPrefix + "a2a": "", + }, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: &replicas, + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": deprecationDeploy}, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "app": deprecationDeploy, + LabelAgentType: LabelValueAgent, + ProtocolLabelPrefix + "a2a": "", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{Name: "agent", Image: "test:latest"}}, + }, + }, + }, + } + Expect(k8sClient.Create(ctx, dep)).To(Succeed()) + defer func() { _ = k8sClient.Delete(ctx, dep) }() + + Eventually(func() error { + if err := k8sClient.Get(ctx, types.NamespacedName{Name: deprecationDeploy, Namespace: deprecationNS}, dep); err != nil { + return err + } + dep.Status.Conditions = []appsv1.DeploymentCondition{ + {Type: appsv1.DeploymentAvailable, Status: corev1.ConditionTrue}, + } + return k8sClient.Status().Update(ctx, dep) + }).Should(Succeed()) + + svc := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: deprecationDeploy, Namespace: deprecationNS}, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Name: "http", Port: 8000, Protocol: corev1.ProtocolTCP}}, + Selector: map[string]string{"app": deprecationDeploy}, + }, + } + Expect(k8sClient.Create(ctx, svc)).To(Succeed()) + defer func() { _ = k8sClient.Delete(ctx, svc) }() + + ac := &agentv1alpha1.AgentCard{ + ObjectMeta: metav1.ObjectMeta{Name: deprecationCard, Namespace: deprecationNS}, + Spec: agentv1alpha1.AgentCardSpec{ + TargetRef: &agentv1alpha1.TargetRef{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: deprecationDeploy, + }, + }, + } + Expect(k8sClient.Create(ctx, ac)).To(Succeed()) + defer func() { _ = k8sClient.Delete(ctx, ac) }() + + fetcher := &mockFetcher{ + cardData: &agentv1alpha1.AgentCardData{ + Name: "deprecation-agent", + Version: "1.0.0", + URL: "http://deprecation-deploy.default.svc.cluster.local:8000", + }, + } + fakeRecorder := record.NewFakeRecorder(10) + reconciler := &AgentCardReconciler{ + Client: k8sClient, + Scheme: k8sClient.Scheme(), + AgentFetcher: fetcher, + Recorder: fakeRecorder, + } + + nn := types.NamespacedName{Name: deprecationCard, Namespace: deprecationNS} + _, _ = reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: nn}) + _, err := reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: nn}) + Expect(err).NotTo(HaveOccurred()) + + var foundDeprecation bool + for len(fakeRecorder.Events) > 0 { + evt := <-fakeRecorder.Events + if strings.Contains(evt, "Deprecated") { + foundDeprecation = true + break + } + } + Expect(foundDeprecation).To(BeTrue(), "expected a Deprecated event to be emitted") + }) + }) }) // Helper function to find a condition by type diff --git a/kagenti-operator/internal/controller/agentruntime_controller.go b/kagenti-operator/internal/controller/agentruntime_controller.go index 1434824d..696791d7 100644 --- a/kagenti-operator/internal/controller/agentruntime_controller.go +++ b/kagenti-operator/internal/controller/agentruntime_controller.go @@ -18,7 +18,11 @@ package controller import ( "context" + "crypto/sha256" + "encoding/hex" + "encoding/json" "fmt" + "strconv" "strings" "time" @@ -43,6 +47,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" agentv1alpha1 "github.com/kagenti/operator/api/v1alpha1" + "github.com/kagenti/operator/internal/agentcard" + "github.com/kagenti/operator/internal/signature" ) const ( @@ -60,6 +66,11 @@ const ( ConditionTypeReady = "Ready" ConditionTypeTargetResolved = "TargetResolved" ConditionTypeConfigResolved = "ConfigResolved" + ConditionTypeCardSynced = "CardSynced" + + // AnnotationLastCardFetchHash stores the change-detection key used to skip + // redundant card fetches when the workload's pod template has not changed. + AnnotationLastCardFetchHash = "agent.kagenti.dev/last-card-fetch-hash" // KindSandbox is the workload kind for agent-sandbox CRs. KindSandbox = "Sandbox" @@ -80,6 +91,12 @@ type AgentRuntimeReconciler struct { Scheme *runtime.Scheme Recorder record.EventRecorder APIReader client.Reader // uncached reader for cross-namespace ConfigMap reads + + AgentFetcher agentcard.Fetcher + AuthenticatedFetcher agentcard.AuthenticatedFetcher + SignatureProvider signature.Provider + EnableCardDiscovery bool + SpireTrustDomain string } // +kubebuilder:rbac:groups=agent.kagenti.dev,resources=agentruntimes,verbs=get;list;watch;create;update;patch;delete @@ -91,6 +108,7 @@ type AgentRuntimeReconciler struct { // +kubebuilder:rbac:groups=agents.x-k8s.io,resources=sandboxes/scale,verbs=get;update;patch // +kubebuilder:rbac:groups=core,resources=configmaps,verbs=get;list;watch;create;update;patch // +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch +// +kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch // +kubebuilder:rbac:groups=core,resources=events,verbs=create;patch func (r *AgentRuntimeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { @@ -178,6 +196,9 @@ func (r *AgentRuntimeReconciler) Reconcile(ctx context.Context, req ctrl.Request "Configuration resolved successfully") } + // 5.5. Card discovery phase: fetch agent card from Service endpoint + r.fetchAndUpdateCard(ctx, rt) + // 6. Apply labels and annotations to the target workload if err := r.applyWorkloadConfig(ctx, rt, configResult.Hash); err != nil { logger.Error(err, "Failed to apply workload config") @@ -423,6 +444,81 @@ func (r *AgentRuntimeReconciler) countConfiguredPods(ctx context.Context, rt *ag return count, nil } +// resolveServiceForWorkload finds the Service that fronts the target workload. +// It first tries a Service with the same name as the Deployment (standard convention), +// then falls back to selector matching against the Deployment's pod template labels. +func (r *AgentRuntimeReconciler) resolveServiceForWorkload(ctx context.Context, namespace string, ref agentv1alpha1.TargetRef) (*corev1.Service, int32, error) { + logger := log.FromContext(ctx) + + svc := &corev1.Service{} + if err := r.Get(ctx, types.NamespacedName{Name: ref.Name, Namespace: namespace}, svc); err == nil { + port := serviceHTTPPort(svc) + logger.V(1).Info("Resolved service by name", "service", ref.Name, "port", port) + return svc, port, nil + } + + acc, ok := newRuntimePodTemplateAccessor(ref.Kind) + if !ok { + return nil, 0, fmt.Errorf("unsupported workload kind for service resolution: %s", ref.Kind) + } + if err := r.Get(ctx, types.NamespacedName{Name: ref.Name, Namespace: namespace}, acc.obj); err != nil { + return nil, 0, fmt.Errorf("failed to get workload %s/%s: %w", ref.Kind, ref.Name, err) + } + podLabels := acc.getPodLabels(acc.obj) + if len(podLabels) == 0 { + return nil, 0, fmt.Errorf("workload %s/%s has no pod template labels for selector matching", ref.Kind, ref.Name) + } + + svcList := &corev1.ServiceList{} + if err := r.List(ctx, svcList, client.InNamespace(namespace)); err != nil { + return nil, 0, fmt.Errorf("failed to list services: %w", err) + } + + for i := range svcList.Items { + s := &svcList.Items[i] + if s.Spec.Selector == nil { + continue + } + if selectorMatchesLabels(s.Spec.Selector, podLabels) { + port := serviceHTTPPort(s) + logger.V(1).Info("Resolved service by selector match", "service", s.Name, "port", port) + return s, port, nil + } + } + + return nil, 0, fmt.Errorf("no Service matches workload %s/%s in namespace %s", ref.Kind, ref.Name, namespace) +} + +func selectorMatchesLabels(selector, labels map[string]string) bool { + for k, v := range selector { + if labels[k] != v { + return false + } + } + return true +} + +func serviceHTTPPort(svc *corev1.Service) int32 { + for _, p := range svc.Spec.Ports { + if strings.EqualFold(p.Name, "http") || p.Port == 80 || p.Port == 8080 || p.Port == 8000 { + return p.Port + } + } + if len(svc.Spec.Ports) > 0 { + return svc.Spec.Ports[0].Port + } + return 8000 +} + +func getAgentTLSPort(svc *corev1.Service) int32 { + for _, p := range svc.Spec.Ports { + if p.Name == AgentTLSPortName { + return p.Port + } + } + return 0 +} + // isPodOwnedByWorkload checks if a pod is transitively owned by the named workload. // For Deployments: Pod → ReplicaSet (-) → Deployment. // For StatefulSets: Pod is directly owned by the StatefulSet. @@ -533,6 +629,172 @@ func (r *AgentRuntimeReconciler) setCondition(rt *agentv1alpha1.AgentRuntime, co }) } +// fetchAndUpdateCard discovers the agent card from the workload's Service endpoint +// and populates status.card. Skips fetch when the feature flag is disabled or +// when the workload's change-detection key has not changed. +func (r *AgentRuntimeReconciler) fetchAndUpdateCard(ctx context.Context, rt *agentv1alpha1.AgentRuntime) { + logger := log.FromContext(ctx) + + if !r.EnableCardDiscovery { + if rt.Status.Card != nil { + rt.Status.Card = nil + r.setCondition(rt, ConditionTypeCardSynced, metav1.ConditionFalse, "CardDiscoveryDisabled", + "Card discovery is disabled; stale card data cleared") + } + return + } + + changeKey := r.workloadChangeKey(ctx, rt) + annotations := rt.GetAnnotations() + lastHash := "" + if annotations != nil { + lastHash = annotations[AnnotationLastCardFetchHash] + } + if changeKey != "" && changeKey == lastHash && rt.Status.Card != nil { + r.setCondition(rt, ConditionTypeCardSynced, metav1.ConditionTrue, "FetchSkipped", + "Pod template unchanged; existing card data still valid") + return + } + + svc, port, err := r.resolveServiceForWorkload(ctx, rt.Namespace, rt.Spec.TargetRef) + if err != nil { + logger.V(1).Info("Service resolution failed for card discovery", "error", err) + r.setCondition(rt, ConditionTypeCardSynced, metav1.ConditionFalse, "ServiceNotFound", err.Error()) + return + } + + protocol := agentcard.A2AProtocol + cardData, fetchResult, err := r.fetchCard(ctx, rt, svc, port, protocol) + if err != nil { + logger.Error(err, "Card fetch failed", "workload", rt.Spec.TargetRef.Name) + r.setCondition(rt, ConditionTypeCardSynced, metav1.ConditionFalse, "CardFetchFailed", err.Error()) + return + } + + newCardID := computeCardContentHash(cardData) + + cardStatus := &agentv1alpha1.CardStatus{ + AgentCardData: *cardData, + CardID: newCardID, + Protocol: protocol, + } + + if rt.Status.Card != nil && rt.Status.Card.CardID == newCardID { + cardStatus.FetchedAt = rt.Status.Card.FetchedAt + } else { + now := metav1.Now() + cardStatus.FetchedAt = &now + } + + if fetchResult != nil && fetchResult.AgentSpiffeID != "" { + cardStatus.AttestedAgentSpiffeID = fetchResult.AgentSpiffeID + } + + if r.SignatureProvider != nil && len(cardData.Signatures) > 0 { + vr, verifyErr := r.SignatureProvider.VerifySignature(ctx, cardData, cardData.Signatures) + if verifyErr != nil { + logger.Error(verifyErr, "Signature verification infrastructure error") + cardStatus.SignatureVerificationDetails = verifyErr.Error() + } else if vr != nil { + cardStatus.ValidSignature = &vr.Verified + cardStatus.SignatureKeyID = vr.KeyID + cardStatus.SignatureVerificationDetails = vr.Details + } + } + + rt.Status.Card = cardStatus + r.setCondition(rt, ConditionTypeCardSynced, metav1.ConditionTrue, "CardSynced", + fmt.Sprintf("Successfully fetched agent card for %s", cardData.Name)) + + r.persistCardFetchAnnotation(ctx, rt, changeKey) +} + +// persistCardFetchAnnotation writes the change-detection annotation to the +// AgentRuntime's metadata via a patch. Status().Update only persists the status +// subresource, so annotations must be written separately. +func (r *AgentRuntimeReconciler) persistCardFetchAnnotation(ctx context.Context, rt *agentv1alpha1.AgentRuntime, changeKey string) { + logger := log.FromContext(ctx) + patch := client.MergeFrom(rt.DeepCopy()) + annotations := rt.GetAnnotations() + if annotations == nil { + annotations = make(map[string]string) + } + annotations[AnnotationLastCardFetchHash] = changeKey + rt.SetAnnotations(annotations) + if err := r.Patch(ctx, rt, patch); err != nil { + logger.Error(err, "Failed to persist card fetch annotation") + } +} + +// fetchCard retrieves the agent card, choosing mTLS or plain HTTP based on +// service port availability and fetcher configuration. +func (r *AgentRuntimeReconciler) fetchCard( + ctx context.Context, rt *agentv1alpha1.AgentRuntime, + svc *corev1.Service, port int32, protocol string, +) (*agentv1alpha1.AgentCardData, *agentcard.FetchResult, error) { + logger := log.FromContext(ctx) + ref := rt.Spec.TargetRef + + if r.AuthenticatedFetcher != nil { + tlsPort := getAgentTLSPort(svc) + if tlsPort > 0 { + secureURL := agentcard.GetSecureServiceURL(svc.Name, rt.Namespace, tlsPort) + fetchResult, err := r.AuthenticatedFetcher.FetchAuthenticated(ctx, protocol, secureURL) + if err != nil { + return nil, nil, fmt.Errorf("authenticated fetch failed for %s: %w", ref.Name, err) + } + if fetchResult.CardData == nil { + return nil, nil, fmt.Errorf("authenticated fetch returned nil card data for %s", ref.Name) + } + return fetchResult.CardData, fetchResult, nil + } + logger.Info("TLS port not found, falling back to HTTP fetch", + "service", svc.Name, "expectedPortName", AgentTLSPortName) + if r.Recorder != nil { + r.Recorder.Event(rt, corev1.EventTypeWarning, "FallbackToHTTP", + fmt.Sprintf("Service %s has no %s port; fetch is unverified", svc.Name, AgentTLSPortName)) + } + } + + if r.AgentFetcher == nil { + return nil, nil, fmt.Errorf("no fetcher configured for card discovery") + } + + serviceURL := agentcard.GetServiceURL(svc.Name, rt.Namespace, port) + cardData, err := r.AgentFetcher.Fetch(ctx, protocol, serviceURL, ref.Name, rt.Namespace) + if err != nil { + return nil, nil, fmt.Errorf("fetch failed for %s: %w", ref.Name, err) + } + return cardData, nil, nil +} + +// workloadChangeKey returns a string that changes when the workload's pod +// template changes. For Deployments this is the observed generation; +// for StatefulSets and Sandboxes it is the resource generation. +func (r *AgentRuntimeReconciler) workloadChangeKey(ctx context.Context, rt *agentv1alpha1.AgentRuntime) string { + ref := rt.Spec.TargetRef + acc, ok := newRuntimePodTemplateAccessor(ref.Kind) + if !ok { + return "" + } + if err := r.Get(ctx, types.NamespacedName{Name: ref.Name, Namespace: rt.Namespace}, acc.obj); err != nil { + return "" + } + return strconv.FormatInt(acc.obj.GetGeneration(), 10) +} + +func computeCardContentHash(cardData *agentv1alpha1.AgentCardData) string { + if cardData == nil { + return "" + } + data, err := json.Marshal(cardData) + if err != nil { + return "" + } + hash := sha256.Sum256(data) + return hex.EncodeToString(hash[:]) +} + // templateConfigMapNames lists the well-known ConfigMaps that authbridge sidecars // require. The Helm chart and backend API create these in agent namespaces; the // operator copies templates from kagenti-system for namespaces created by other diff --git a/kagenti-operator/internal/controller/agentruntime_controller_test.go b/kagenti-operator/internal/controller/agentruntime_controller_test.go index 5f9043c9..0f378026 100644 --- a/kagenti-operator/internal/controller/agentruntime_controller_test.go +++ b/kagenti-operator/internal/controller/agentruntime_controller_test.go @@ -639,6 +639,261 @@ var _ = Describe("AgentRuntime Controller", func() { }) }) + Context("Service resolution for card discovery", func() { + It("should resolve service by name match", func() { + dep := newDeployment("svc-name-deploy", namespace) + Expect(k8sClient.Create(ctx, dep)).To(Succeed()) + defer func() { _ = k8sClient.Delete(ctx, dep) }() + + svc := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "svc-name-deploy", + Namespace: namespace, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + {Name: "http", Port: 8000, Protocol: corev1.ProtocolTCP}, + }, + Selector: map[string]string{"app": "svc-name-deploy"}, + }, + } + Expect(k8sClient.Create(ctx, svc)).To(Succeed()) + defer func() { _ = k8sClient.Delete(ctx, svc) }() + + r := newReconciler() + ref := agentv1alpha1.TargetRef{APIVersion: "apps/v1", Kind: "Deployment", Name: "svc-name-deploy"} + resolvedSvc, port, err := r.resolveServiceForWorkload(ctx, namespace, ref) + Expect(err).NotTo(HaveOccurred()) + Expect(resolvedSvc.Name).To(Equal("svc-name-deploy")) + Expect(port).To(Equal(int32(8000))) + }) + + It("should resolve service by selector match when name does not match", func() { + dep := newDeployment("selector-deploy", namespace) + Expect(k8sClient.Create(ctx, dep)).To(Succeed()) + defer func() { _ = k8sClient.Delete(ctx, dep) }() + + svc := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "different-svc-name", + Namespace: namespace, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + {Name: "http", Port: 9090, Protocol: corev1.ProtocolTCP}, + }, + Selector: map[string]string{"app": "selector-deploy"}, + }, + } + Expect(k8sClient.Create(ctx, svc)).To(Succeed()) + defer func() { _ = k8sClient.Delete(ctx, svc) }() + + r := newReconciler() + ref := agentv1alpha1.TargetRef{APIVersion: "apps/v1", Kind: "Deployment", Name: "selector-deploy"} + resolvedSvc, port, err := r.resolveServiceForWorkload(ctx, namespace, ref) + Expect(err).NotTo(HaveOccurred()) + Expect(resolvedSvc.Name).To(Equal("different-svc-name")) + Expect(port).To(Equal(int32(9090))) + }) + + It("should return error when no matching service exists", func() { + dep := newDeployment("no-svc-deploy", namespace) + Expect(k8sClient.Create(ctx, dep)).To(Succeed()) + defer func() { _ = k8sClient.Delete(ctx, dep) }() + + r := newReconciler() + ref := agentv1alpha1.TargetRef{APIVersion: "apps/v1", Kind: "Deployment", Name: "no-svc-deploy"} + _, _, err := r.resolveServiceForWorkload(ctx, namespace, ref) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("no Service matches")) + }) + + It("should prefer first HTTP port", func() { + dep := newDeployment("multi-port-deploy", namespace) + Expect(k8sClient.Create(ctx, dep)).To(Succeed()) + defer func() { _ = k8sClient.Delete(ctx, dep) }() + + svc := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "multi-port-deploy", + Namespace: namespace, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + {Name: "grpc", Port: 50051, Protocol: corev1.ProtocolTCP}, + {Name: "http", Port: 8080, Protocol: corev1.ProtocolTCP}, + }, + Selector: map[string]string{"app": "multi-port-deploy"}, + }, + } + Expect(k8sClient.Create(ctx, svc)).To(Succeed()) + defer func() { _ = k8sClient.Delete(ctx, svc) }() + + r := newReconciler() + ref := agentv1alpha1.TargetRef{APIVersion: "apps/v1", Kind: "Deployment", Name: "multi-port-deploy"} + _, port, err := r.resolveServiceForWorkload(ctx, namespace, ref) + Expect(err).NotTo(HaveOccurred()) + Expect(port).To(Equal(int32(8080))) + }) + }) + + Context("Card fetch phase", func() { + It("should skip card fetch when feature flag is disabled", func() { + rt := &agentv1alpha1.AgentRuntime{ + ObjectMeta: metav1.ObjectMeta{Name: "no-card-rt", Namespace: namespace}, + Status: agentv1alpha1.AgentRuntimeStatus{}, + } + + r := &AgentRuntimeReconciler{ + Client: k8sClient, + EnableCardDiscovery: false, + } + r.fetchAndUpdateCard(ctx, rt) + Expect(rt.Status.Card).To(BeNil()) + + var cardCond *metav1.Condition + for i := range rt.Status.Conditions { + if rt.Status.Conditions[i].Type == ConditionTypeCardSynced { + cardCond = &rt.Status.Conditions[i] + break + } + } + Expect(cardCond).To(BeNil(), "No CardSynced condition should be set when card was already nil") + }) + + It("should clear existing card data when feature flag is disabled", func() { + now := metav1.Now() + rt := &agentv1alpha1.AgentRuntime{ + ObjectMeta: metav1.ObjectMeta{Name: "clear-card-rt", Namespace: namespace}, + Status: agentv1alpha1.AgentRuntimeStatus{ + Card: &agentv1alpha1.CardStatus{ + AgentCardData: agentv1alpha1.AgentCardData{Name: "old-agent"}, + FetchedAt: &now, + }, + }, + } + + r := &AgentRuntimeReconciler{ + Client: k8sClient, + EnableCardDiscovery: false, + } + r.fetchAndUpdateCard(ctx, rt) + Expect(rt.Status.Card).To(BeNil()) + + var cardCond *metav1.Condition + for i := range rt.Status.Conditions { + if rt.Status.Conditions[i].Type == ConditionTypeCardSynced { + cardCond = &rt.Status.Conditions[i] + break + } + } + Expect(cardCond).NotTo(BeNil()) + Expect(cardCond.Status).To(Equal(metav1.ConditionFalse)) + Expect(cardCond.Reason).To(Equal("CardDiscoveryDisabled")) + }) + + It("should set ServiceNotFound condition when no service exists", func() { + dep := newDeployment("card-no-svc-deploy", namespace) + Expect(k8sClient.Create(ctx, dep)).To(Succeed()) + defer func() { _ = k8sClient.Delete(ctx, dep) }() + + rt := &agentv1alpha1.AgentRuntime{ + ObjectMeta: metav1.ObjectMeta{Name: "card-no-svc-rt", Namespace: namespace}, + Spec: agentv1alpha1.AgentRuntimeSpec{ + Type: agentv1alpha1.RuntimeTypeAgent, + TargetRef: agentv1alpha1.TargetRef{APIVersion: "apps/v1", Kind: "Deployment", Name: "card-no-svc-deploy"}, + }, + } + + r := &AgentRuntimeReconciler{ + Client: k8sClient, + EnableCardDiscovery: true, + } + r.fetchAndUpdateCard(ctx, rt) + Expect(rt.Status.Card).To(BeNil()) + + var cardCond *metav1.Condition + for i := range rt.Status.Conditions { + if rt.Status.Conditions[i].Type == ConditionTypeCardSynced { + cardCond = &rt.Status.Conditions[i] + break + } + } + Expect(cardCond).NotTo(BeNil()) + Expect(cardCond.Status).To(Equal(metav1.ConditionFalse)) + Expect(cardCond.Reason).To(Equal("ServiceNotFound")) + }) + }) + + Context("Card data retention on fetch failure (FR-013)", func() { + It("should retain existing card data when fetch fails", func() { + now := metav1.Now() + rt := &agentv1alpha1.AgentRuntime{ + ObjectMeta: metav1.ObjectMeta{Name: "retain-card-rt", Namespace: namespace}, + Spec: agentv1alpha1.AgentRuntimeSpec{ + Type: agentv1alpha1.RuntimeTypeAgent, + TargetRef: agentv1alpha1.TargetRef{APIVersion: "apps/v1", Kind: "Deployment", Name: "nonexistent-for-retain"}, + }, + Status: agentv1alpha1.AgentRuntimeStatus{ + Card: &agentv1alpha1.CardStatus{ + AgentCardData: agentv1alpha1.AgentCardData{Name: "previous-agent", Version: "1.0"}, + FetchedAt: &now, + CardID: "abc123", + }, + }, + } + + r := &AgentRuntimeReconciler{ + Client: k8sClient, + EnableCardDiscovery: true, + } + r.fetchAndUpdateCard(ctx, rt) + + Expect(rt.Status.Card).NotTo(BeNil(), "existing card data should be retained on fetch failure") + Expect(rt.Status.Card.Name).To(Equal("previous-agent")) + Expect(rt.Status.Card.CardID).To(Equal("abc123")) + + var cardCond *metav1.Condition + for i := range rt.Status.Conditions { + if rt.Status.Conditions[i].Type == ConditionTypeCardSynced { + cardCond = &rt.Status.Conditions[i] + break + } + } + Expect(cardCond).NotTo(BeNil()) + Expect(cardCond.Status).To(Equal(metav1.ConditionFalse)) + }) + }) + + Context("Feature flag toggle behavior", func() { + It("should not fetch card when flag is disabled and card is nil", func() { + rt := &agentv1alpha1.AgentRuntime{ + ObjectMeta: metav1.ObjectMeta{Name: "toggle-off-nil-rt", Namespace: namespace}, + Status: agentv1alpha1.AgentRuntimeStatus{}, + } + r := &AgentRuntimeReconciler{Client: k8sClient, EnableCardDiscovery: false} + r.fetchAndUpdateCard(ctx, rt) + Expect(rt.Status.Card).To(BeNil()) + // No CardSynced condition should be set when card was already nil + }) + + It("should clear populated card data when flag is toggled off", func() { + now := metav1.Now() + rt := &agentv1alpha1.AgentRuntime{ + ObjectMeta: metav1.ObjectMeta{Name: "toggle-off-populated-rt", Namespace: namespace}, + Status: agentv1alpha1.AgentRuntimeStatus{ + Card: &agentv1alpha1.CardStatus{ + AgentCardData: agentv1alpha1.AgentCardData{Name: "stale-agent"}, + FetchedAt: &now, + }, + }, + } + r := &AgentRuntimeReconciler{Client: k8sClient, EnableCardDiscovery: false} + r.fetchAndUpdateCard(ctx, rt) + Expect(rt.Status.Card).To(BeNil()) + }) + }) + Context("Sandbox workload support", func() { It("should create a Sandbox accessor that reads/writes pod template labels and annotations", func() { acc, ok := newRuntimePodTemplateAccessor("Sandbox") diff --git a/specs/001-agentcard-into-status/tasks.md b/specs/001-agentcard-into-status/tasks.md index 30b6960f..4b1bc952 100644 --- a/specs/001-agentcard-into-status/tasks.md +++ b/specs/001-agentcard-into-status/tasks.md @@ -17,9 +17,9 @@ **Purpose**: CRD type changes and code generation that all stories depend on -- [ ] T001 Add `CardStatus` struct to `AgentRuntimeStatus` in `kagenti-operator/api/v1alpha1/agentruntime_types.go`. The `CardStatus` struct embeds `AgentCardData` (reuse existing struct) and adds: `FetchedAt *metav1.Time`, `CardId string`, `Protocol string`, `ValidSignature *bool`, `SignatureKeyID string`, `SignatureVerificationDetails string`, `AttestedAgentSpiffeID string`. The change-detection hash is stored as an annotation (`agent.kagenti.dev/last-card-fetch-hash`), not in the struct. Add the `Card *CardStatus` field to `AgentRuntimeStatus`. Add `CardSynced` as a new condition type constant. -- [ ] T002 Run `make generate` and `make manifests` in `kagenti-operator/` to regenerate deepcopy functions and CRD manifests. Verify `zz_generated.deepcopy.go` has the new `CardStatus` deepcopy method and `config/crd/bases/` has the updated AgentRuntime CRD. -- [ ] T003 Add `--enable-card-discovery` boolean flag (default: false) to `kagenti-operator/cmd/main.go`. When enabled, create `agentcard.NewConfigMapFetcher()` and `agentcard.NewSpiffeFetcher()` (conditional on SPIRE config), and inject them into the `AgentRuntimeReconciler` as new fields: `AgentFetcher agentcard.Fetcher`, `AuthenticatedFetcher agentcard.AuthenticatedFetcher`, `SignatureProvider signature.Provider`, `EnableCardDiscovery bool`, `SpireTrustDomain string`. Follow the existing pattern of `--enable-verified-fetch` flag wiring for the AgentCard controller. +- [X] T001 Add `CardStatus` struct to `AgentRuntimeStatus` in `kagenti-operator/api/v1alpha1/agentruntime_types.go`. The `CardStatus` struct embeds `AgentCardData` (reuse existing struct) and adds: `FetchedAt *metav1.Time`, `CardId string`, `Protocol string`, `ValidSignature *bool`, `SignatureKeyID string`, `SignatureVerificationDetails string`, `AttestedAgentSpiffeID string`. The change-detection hash is stored as an annotation (`agent.kagenti.dev/last-card-fetch-hash`), not in the struct. Add the `Card *CardStatus` field to `AgentRuntimeStatus`. Add `CardSynced` as a new condition type constant. +- [X] T002 Run `make generate` and `make manifests` in `kagenti-operator/` to regenerate deepcopy functions and CRD manifests. Verify `zz_generated.deepcopy.go` has the new `CardStatus` deepcopy method and `config/crd/bases/` has the updated AgentRuntime CRD. +- [X] T003 Add `--enable-card-discovery` boolean flag (default: false) to `kagenti-operator/cmd/main.go`. When enabled, create `agentcard.NewConfigMapFetcher()` and `agentcard.NewSpiffeFetcher()` (conditional on SPIRE config), and inject them into the `AgentRuntimeReconciler` as new fields: `AgentFetcher agentcard.Fetcher`, `AuthenticatedFetcher agentcard.AuthenticatedFetcher`, `SignatureProvider signature.Provider`, `EnableCardDiscovery bool`, `SpireTrustDomain string`. Follow the existing pattern of `--enable-verified-fetch` flag wiring for the AgentCard controller. **Checkpoint**: CRD types updated, code generated, flag wired. Ready for controller changes. @@ -31,8 +31,8 @@ **CRITICAL**: No user story work can begin until this phase is complete -- [ ] T004 Add a `resolveServiceForWorkload` method to `AgentRuntimeReconciler` in `kagenti-operator/internal/controller/agentruntime_controller.go`. Given a namespace and Deployment name, first try to get a Service with the same name (matching existing AgentCard convention). If not found, list Services in the namespace, match by Pod selector labels from the Deployment, and return the first match. Return the Service object and the selected port (first HTTP port, or default 8000). Also add `getAgentTLSPort` (reuse logic from `agentcard_controller.go` line 684). -- [ ] T005 [P] Add Service `get;list;watch` RBAC for the agentruntime controller in `kagenti-operator/internal/controller/agentruntime_controller.go` via kubebuilder RBAC markers: `// +kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch`. Run `make manifests` to update `config/rbac/`. +- [X] T004 Add a `resolveServiceForWorkload` method to `AgentRuntimeReconciler` in `kagenti-operator/internal/controller/agentruntime_controller.go`. Given a namespace and Deployment name, first try to get a Service with the same name (matching existing AgentCard convention). If not found, list Services in the namespace, match by Pod selector labels from the Deployment, and return the first match. Return the Service object and the selected port (first HTTP port, or default 8000). Also add `getAgentTLSPort` (reuse logic from `agentcard_controller.go` line 684). +- [X] T005 [P] Add Service `get;list;watch` RBAC for the agentruntime controller in `kagenti-operator/internal/controller/agentruntime_controller.go` via kubebuilder RBAC markers: `// +kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch`. Run `make manifests` to update `config/rbac/`. **Checkpoint**: Service resolution and RBAC ready. User story implementation can begin. @@ -46,14 +46,14 @@ ### Tests for User Story 1 -- [ ] T006 [P] [US1] Add unit tests for `resolveServiceForWorkload` in `kagenti-operator/internal/controller/agentruntime_controller_test.go`. Test cases: Service found by name, Service found by selector match, no matching Service, multiple ports (uses first HTTP port), Deployment with no ready pods. -- [ ] T007 [P] [US1] Add unit tests for the card fetch phase in `kagenti-operator/internal/controller/agentruntime_controller_test.go`. Test cases: successful fetch populates `status.card` with all fields, fetch failure retains stale data and sets `CardSynced=False`, invalid JSON sets `CardParseFailed` condition, feature flag disabled skips fetch, pod template hash unchanged skips fetch, feature flag toggled off clears `status.card`. +- [X] T006 [P] [US1] Add unit tests for `resolveServiceForWorkload` in `kagenti-operator/internal/controller/agentruntime_controller_test.go`. Test cases: Service found by name, Service found by selector match, no matching Service, multiple ports (uses first HTTP port), Deployment with no ready pods. +- [X] T007 [P] [US1] Add unit tests for the card fetch phase in `kagenti-operator/internal/controller/agentruntime_controller_test.go`. Test cases: successful fetch populates `status.card` with all fields, fetch failure retains stale data and sets `CardSynced=False`, invalid JSON sets `CardParseFailed` condition, feature flag disabled skips fetch, pod template hash unchanged skips fetch, feature flag toggled off clears `status.card`. ### Implementation for User Story 1 -- [ ] T008 [US1] Add a `fetchAndUpdateCard` method to `AgentRuntimeReconciler` in `kagenti-operator/internal/controller/agentruntime_controller.go`. This is the main card fetch phase, called from `Reconcile()` after step 5 (config hash). Logic: (1) If `EnableCardDiscovery` is false, clear `status.card` if populated, set `CardSynced` condition to `CardDiscoveryDisabled`, return. (2) Read the change-detection hash from annotation `agent.kagenti.dev/last-card-fetch-hash` on the AgentRuntime; compare against current workload pod template hash (or generation for StatefulSet/Sandbox); if unchanged and `status.card` is populated, set `CardSynced` to `FetchSkipped`, return. (3) Call `resolveServiceForWorkload`. (4) Build service URL via `agentcard.GetServiceURL`. (5) Call `AgentFetcher.Fetch()`. (6) On success: build `CardStatus` from fetched `AgentCardData`, set `fetchedAt`, compute `cardId` via SHA-256, set `protocol`, store current hash in annotation. (7) On failure: retain existing `status.card`, set `CardSynced=False` with error reason. (8) Update status via `r.Status().Update()` with retry. Note: FR-009 (max response body size) is implicitly satisfied because the reused `doHTTPFetch()` in `internal/agentcard/fetcher.go` already enforces `maxCardSize = 1 MiB`. -- [ ] T009 [US1] Wire the `fetchAndUpdateCard` call into the `Reconcile()` method in `kagenti-operator/internal/controller/agentruntime_controller.go`. Insert after the config hash computation (step 5, around line 165) and before the label propagation phase. Pass the resolved target workload info. -- [ ] T010 [US1] Extract the change-detection key from the target workload in the `fetchAndUpdateCard` method. For Deployments, read the pod-template-hash label. For StatefulSets and Sandboxes, use the resource generation. Store the key in the `agent.kagenti.dev/last-card-fetch-hash` annotation on the AgentRuntime object (not in status). +- [X] T008 [US1] Add a `fetchAndUpdateCard` method to `AgentRuntimeReconciler` in `kagenti-operator/internal/controller/agentruntime_controller.go`. This is the main card fetch phase, called from `Reconcile()` after step 5 (config hash). Logic: (1) If `EnableCardDiscovery` is false, clear `status.card` if populated, set `CardSynced` condition to `CardDiscoveryDisabled`, return. (2) Read the change-detection hash from annotation `agent.kagenti.dev/last-card-fetch-hash` on the AgentRuntime; compare against current workload pod template hash (or generation for StatefulSet/Sandbox); if unchanged and `status.card` is populated, set `CardSynced` to `FetchSkipped`, return. (3) Call `resolveServiceForWorkload`. (4) Build service URL via `agentcard.GetServiceURL`. (5) Call `AgentFetcher.Fetch()`. (6) On success: build `CardStatus` from fetched `AgentCardData`, set `fetchedAt`, compute `cardId` via SHA-256, set `protocol`, store current hash in annotation. (7) On failure: retain existing `status.card`, set `CardSynced=False` with error reason. (8) Update status via `r.Status().Update()` with retry. Note: FR-009 (max response body size) is implicitly satisfied because the reused `doHTTPFetch()` in `internal/agentcard/fetcher.go` already enforces `maxCardSize = 1 MiB`. +- [X] T009 [US1] Wire the `fetchAndUpdateCard` call into the `Reconcile()` method in `kagenti-operator/internal/controller/agentruntime_controller.go`. Insert after the config hash computation (step 5, around line 165) and before the label propagation phase. Pass the resolved target workload info. +- [X] T010 [US1] Extract the change-detection key from the target workload in the `fetchAndUpdateCard` method. For Deployments, read the pod-template-hash label. For StatefulSets and Sandboxes, use the resource generation. Store the key in the `agent.kagenti.dev/last-card-fetch-hash` annotation on the AgentRuntime object (not in status). **Checkpoint**: US1 complete. `status.card` is populated on reconcile when the flag is enabled. @@ -67,13 +67,13 @@ ### Tests for User Story 2 -- [ ] T011 [P] [US2] Add unit tests for mTLS card fetch in `kagenti-operator/internal/controller/agentruntime_controller_test.go`. Test cases: mTLS fetch populates `attestedAgentSpiffeID` and `validSignature`, mTLS handshake failure retains stale data and sets condition, fallback to HTTP when no TLS port, JWS signature verification populates `signatureKeyID`. +- [X] T011 [P] [US2] Add unit tests for mTLS card fetch in `kagenti-operator/internal/controller/agentruntime_controller_test.go`. Test cases: mTLS fetch populates `attestedAgentSpiffeID` and `validSignature`, mTLS handshake failure retains stale data and sets condition, fallback to HTTP when no TLS port, JWS signature verification populates `signatureKeyID`. ### Implementation for User Story 2 -- [ ] T012 [US2] Extend `fetchAndUpdateCard` in `kagenti-operator/internal/controller/agentruntime_controller.go` to support mTLS. After resolving the Service, check for the `agent-tls` named port (reuse `getAgentTLSPort`). If present and `AuthenticatedFetcher` is not nil, call `AuthenticatedFetcher.FetchAuthenticated()` instead of `AgentFetcher.Fetch()`. On success, populate `attestedAgentSpiffeID` from `FetchResult.AgentSpiffeID`. -- [ ] T013 [US2] Add JWS signature verification to `fetchAndUpdateCard` in `kagenti-operator/internal/controller/agentruntime_controller.go`. After fetching the card data (mTLS or HTTP), if `SignatureProvider` is not nil and the card has signatures, call `SignatureProvider.VerifySignature()`. Populate `status.card.validSignature`, `status.card.signatureKeyID`, and `status.card.signatureVerificationDetails` from the `VerificationResult`. -- [ ] T014 [US2] Handle mTLS fallback to HTTP in `fetchAndUpdateCard`. If `AuthenticatedFetcher` is set but no `agent-tls` port exists on the Service, fall back to `AgentFetcher.Fetch()` and leave verification fields empty. Log a warning and emit a Kubernetes Event (reuse pattern from agentcard_controller.go line 351-356). +- [X] T012 [US2] Extend `fetchAndUpdateCard` in `kagenti-operator/internal/controller/agentruntime_controller.go` to support mTLS. After resolving the Service, check for the `agent-tls` named port (reuse `getAgentTLSPort`). If present and `AuthenticatedFetcher` is not nil, call `AuthenticatedFetcher.FetchAuthenticated()` instead of `AgentFetcher.Fetch()`. On success, populate `attestedAgentSpiffeID` from `FetchResult.AgentSpiffeID`. +- [X] T013 [US2] Add JWS signature verification to `fetchAndUpdateCard` in `kagenti-operator/internal/controller/agentruntime_controller.go`. After fetching the card data (mTLS or HTTP), if `SignatureProvider` is not nil and the card has signatures, call `SignatureProvider.VerifySignature()`. Populate `status.card.validSignature`, `status.card.signatureKeyID`, and `status.card.signatureVerificationDetails` from the `VerificationResult`. +- [X] T014 [US2] Handle mTLS fallback to HTTP in `fetchAndUpdateCard`. If `AuthenticatedFetcher` is set but no `agent-tls` port exists on the Service, fall back to `AgentFetcher.Fetch()` and leave verification fields empty. Log a warning and emit a Kubernetes Event (reuse pattern from agentcard_controller.go line 351-356). **Checkpoint**: US2 complete. mTLS verified card fetch works with SPIFFE identity extraction and JWS signature validation. @@ -87,11 +87,11 @@ ### Tests for User Story 3 -- [ ] T015 [P] [US3] Add unit test for deprecation warning in `kagenti-operator/internal/controller/agentcard_controller_test.go`. Verify that reconciling a recently created AgentCard emits a deprecation log message and Kubernetes Event. +- [X] T015 [P] [US3] Add unit test for deprecation warning in `kagenti-operator/internal/controller/agentcard_controller_test.go`. Verify that reconciling a recently created AgentCard emits a deprecation log message and Kubernetes Event. ### Implementation for User Story 3 -- [ ] T016 [US3] Add deprecation warning to `AgentCardReconciler.Reconcile()` in `kagenti-operator/internal/controller/agentcard_controller.go`. After the deletion/finalizer check (around line 166), check if `agentCard.CreationTimestamp` is within the last 5 minutes. If so, log a warning: "AgentCard is deprecated; card data is now available via AgentRuntime status.card. Migrate to AgentRuntime-based discovery." Also emit a Kubernetes Event with reason "Deprecated" and type Warning. +- [X] T016 [US3] Add deprecation warning to `AgentCardReconciler.Reconcile()` in `kagenti-operator/internal/controller/agentcard_controller.go`. After the deletion/finalizer check (around line 166), check if `agentCard.CreationTimestamp` is within the last 5 minutes. If so, log a warning: "AgentCard is deprecated; card data is now available via AgentRuntime status.card. Migrate to AgentRuntime-based discovery." Also emit a Kubernetes Event with reason "Deprecated" and type Warning. **Checkpoint**: US3 complete. Deprecation warnings emitted for new AgentCard CRs. @@ -105,7 +105,7 @@ ### Tests and Verification for User Story 4 -- [ ] T017 [US4] Add unit tests for feature flag toggle behavior in `kagenti-operator/internal/controller/agentruntime_controller_test.go`. Test cases: (1) flag disabled means no card fetch attempted and `status.card` remains nil, (2) flag disabled clears existing populated `status.card` data and sets `CardSynced` condition to `CardDiscoveryDisabled`, (3) flag enabled triggers fetch and populates `status.card`, (4) toggling flag off after previous population clears data on next reconcile. These tests validate the flag-off cleanup logic built in T008 step 1. +- [X] T017 [US4] Add unit tests for feature flag toggle behavior in `kagenti-operator/internal/controller/agentruntime_controller_test.go`. Test cases: (1) flag disabled means no card fetch attempted and `status.card` remains nil, (2) flag disabled clears existing populated `status.card` data and sets `CardSynced` condition to `CardDiscoveryDisabled`, (3) flag enabled triggers fetch and populates `status.card`, (4) toggling flag off after previous population clears data on next reconcile. These tests validate the flag-off cleanup logic built in T008 step 1. **Checkpoint**: US4 complete. Feature flag fully controls card discovery lifecycle. @@ -115,8 +115,8 @@ **Purpose**: End-to-end validation, documentation, and CRD manifest finalization -- [ ] T018 [P] Run `make generate && make manifests` in `kagenti-operator/` to ensure all generated code and CRD manifests are up to date after all changes. -- [ ] T019 [P] Add a `CardSynced` print column to the AgentRuntime CRD in `kagenti-operator/api/v1alpha1/agentruntime_types.go` via kubebuilder marker: `// +kubebuilder:printcolumn:name="CardSynced",type="string",JSONPath=".status.conditions[?(@.type=='CardSynced')].status",description="Card Sync Status"`. Regenerate manifests. +- [X] T018 [P] Run `make generate && make manifests` in `kagenti-operator/` to ensure all generated code and CRD manifests are up to date after all changes. +- [X] T019 [P] Add a `CardSynced` print column to the AgentRuntime CRD in `kagenti-operator/api/v1alpha1/agentruntime_types.go` via kubebuilder marker: `// +kubebuilder:printcolumn:name="CardSynced",type="string",JSONPath=".status.conditions[?(@.type=='CardSynced')].status",description="Card Sync Status"`. Regenerate manifests. - [ ] T020 [P] Add e2e test scenario to `kagenti-operator/test/e2e/e2e_test.go` that deploys a test agent Deployment with a mock `/.well-known/agent-card.json` endpoint, creates an AgentRuntime targeting it with card discovery enabled, and verifies `status.card` is populated within 30 seconds. - [ ] T021 Run full test suite: `make test` in `kagenti-operator/`. Fix any regressions. Ensure existing AgentCard controller tests still pass. - [ ] T022 Verify CRD backward compatibility: confirm existing AgentRuntime CRs without `status.card` continue to work without errors when the operator runs with card discovery disabled. From 72e84f8bc546d9a8f54de29e17886c16d9977a7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Hu=C3=9F?= Date: Fri, 22 May 2026 10:19:52 +0200 Subject: [PATCH 10/11] fix: preserve in-memory status across annotation patch in card discovery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit persistCardFetchAnnotation calls r.Patch() which refreshes the rt object from the API server, wiping all in-memory status mutations (card data, CardSynced, TargetResolved, ConfigResolved conditions) that have not yet been persisted via Status().Update. Save and restore rt.Status across the Patch call so the caller's subsequent Status().Update persists everything. Add regression test with a stub fetcher that verifies card data and all conditions survive the annotation patch. Assisted-By: 🤖 Claude Code Signed-off-by: Roland Huß --- .../controller/agentruntime_controller.go | 11 ++- .../agentruntime_controller_test.go | 76 +++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/kagenti-operator/internal/controller/agentruntime_controller.go b/kagenti-operator/internal/controller/agentruntime_controller.go index 696791d7..e620d1b3 100644 --- a/kagenti-operator/internal/controller/agentruntime_controller.go +++ b/kagenti-operator/internal/controller/agentruntime_controller.go @@ -94,7 +94,7 @@ type AgentRuntimeReconciler struct { AgentFetcher agentcard.Fetcher AuthenticatedFetcher agentcard.AuthenticatedFetcher - SignatureProvider signature.Provider + SignatureProvider signature.Provider EnableCardDiscovery bool SpireTrustDomain string } @@ -712,8 +712,15 @@ func (r *AgentRuntimeReconciler) fetchAndUpdateCard(ctx context.Context, rt *age // persistCardFetchAnnotation writes the change-detection annotation to the // AgentRuntime's metadata via a patch. Status().Update only persists the status // subresource, so annotations must be written separately. +// +// Patch refreshes rt from the API server response, which overwrites any +// in-memory status mutations (conditions, card data) that have not yet been +// persisted via Status().Update. We save and restore the status to prevent this. func (r *AgentRuntimeReconciler) persistCardFetchAnnotation(ctx context.Context, rt *agentv1alpha1.AgentRuntime, changeKey string) { logger := log.FromContext(ctx) + + savedStatus := rt.Status.DeepCopy() + patch := client.MergeFrom(rt.DeepCopy()) annotations := rt.GetAnnotations() if annotations == nil { @@ -724,6 +731,8 @@ func (r *AgentRuntimeReconciler) persistCardFetchAnnotation(ctx context.Context, if err := r.Patch(ctx, rt, patch); err != nil { logger.Error(err, "Failed to persist card fetch annotation") } + + rt.Status = *savedStatus } // fetchCard retrieves the agent card, choosing mTLS or plain HTTP based on diff --git a/kagenti-operator/internal/controller/agentruntime_controller_test.go b/kagenti-operator/internal/controller/agentruntime_controller_test.go index 0f378026..357bdc09 100644 --- a/kagenti-operator/internal/controller/agentruntime_controller_test.go +++ b/kagenti-operator/internal/controller/agentruntime_controller_test.go @@ -23,6 +23,7 @@ import ( . "github.com/onsi/gomega" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/types" @@ -34,6 +35,15 @@ import ( agentv1alpha1 "github.com/kagenti/operator/api/v1alpha1" ) +type stubCardFetcher struct { + card *agentv1alpha1.AgentCardData + err error +} + +func (f *stubCardFetcher) Fetch(_ context.Context, _, _, _, _ string) (*agentv1alpha1.AgentCardData, error) { + return f.card, f.err +} + var _ = Describe("AgentRuntime Controller", func() { const ( rtName = "test-agentruntime" @@ -894,6 +904,72 @@ var _ = Describe("AgentRuntime Controller", func() { }) }) + Context("Card annotation patch must not wipe in-memory status", func() { + It("should persist CardSynced condition and card data after annotation patch", func() { + depName := "card-patch-deploy" + svcName := depName + dep := newDeployment(depName, namespace) + Expect(k8sClient.Create(ctx, dep)).To(Succeed()) + defer func() { _ = k8sClient.Delete(ctx, dep) }() + + svc := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: svcName, Namespace: namespace}, + Spec: corev1.ServiceSpec{ + Selector: map[string]string{"app": depName}, + Ports: []corev1.ServicePort{{Name: "http", Port: 8080, Protocol: corev1.ProtocolTCP}}, + }, + } + Expect(k8sClient.Create(ctx, svc)).To(Succeed()) + defer func() { _ = k8sClient.Delete(ctx, svc) }() + + rt := newAgentRuntime("card-patch-rt", namespace, depName, agentv1alpha1.RuntimeTypeAgent) + Expect(k8sClient.Create(ctx, rt)).To(Succeed()) + defer func() { _ = k8sClient.Delete(ctx, rt) }() + + // Pre-set conditions that would be set earlier in the reconcile loop + meta.SetStatusCondition(&rt.Status.Conditions, metav1.Condition{ + Type: ConditionTypeTargetResolved, Status: metav1.ConditionTrue, + Reason: "TargetFound", Message: "resolved", + }) + meta.SetStatusCondition(&rt.Status.Conditions, metav1.Condition{ + Type: ConditionTypeConfigResolved, Status: metav1.ConditionTrue, + Reason: "ConfigResolved", Message: "resolved", + }) + + stubFetcher := &stubCardFetcher{ + card: &agentv1alpha1.AgentCardData{ + Name: "Test Agent", + Version: "2.0", + }, + } + + r := &AgentRuntimeReconciler{ + Client: k8sClient, + EnableCardDiscovery: true, + AgentFetcher: stubFetcher, + } + r.fetchAndUpdateCard(ctx, rt) + + // Card data must survive the annotation patch + Expect(rt.Status.Card).NotTo(BeNil(), "card data must not be wiped by annotation patch") + Expect(rt.Status.Card.Name).To(Equal("Test Agent")) + Expect(rt.Status.Card.Version).To(Equal("2.0")) + Expect(rt.Status.Card.CardID).NotTo(BeEmpty()) + + // CardSynced condition must survive + cardCond := meta.FindStatusCondition(rt.Status.Conditions, ConditionTypeCardSynced) + Expect(cardCond).NotTo(BeNil(), "CardSynced condition must not be wiped by annotation patch") + Expect(cardCond.Status).To(Equal(metav1.ConditionTrue)) + Expect(cardCond.Reason).To(Equal("CardSynced")) + + // Conditions set before fetchAndUpdateCard must also survive + targetCond := meta.FindStatusCondition(rt.Status.Conditions, ConditionTypeTargetResolved) + Expect(targetCond).NotTo(BeNil(), "TargetResolved condition must not be wiped by annotation patch") + configCond := meta.FindStatusCondition(rt.Status.Conditions, ConditionTypeConfigResolved) + Expect(configCond).NotTo(BeNil(), "ConfigResolved condition must not be wiped by annotation patch") + }) + }) + Context("Sandbox workload support", func() { It("should create a Sandbox accessor that reads/writes pod template labels and annotations", func() { acc, ok := newRuntimePodTemplateAccessor("Sandbox") From c82ec5b178e53436c337379f63293a6ff6ad25bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Hu=C3=9F?= Date: Fri, 22 May 2026 10:53:21 +0200 Subject: [PATCH 11/11] docs: add project constitution v1.0.0 with controller-runtime safety principles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codifies lessons from the status-wipe bug into project governance: Principle I (Reconciler Status Integrity), Principle III (Controller-Runtime Safety), and a Controller-Runtime Gotchas reference section. Un-ignores .specify/memory/constitution.md so the constitution is tracked in git. Assisted-By: 🤖 Claude Code Signed-off-by: Roland Huß --- .gitignore | 5 +- .specify/memory/constitution.md | 142 ++++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 .specify/memory/constitution.md diff --git a/.gitignore b/.gitignore index 866adfc8..eecd4e0e 100644 --- a/.gitignore +++ b/.gitignore @@ -60,4 +60,7 @@ Dockerfile.cross .claude/skills/ .claude/settings.json .claude/settings.local.json -.specify/ +.specify/* +!.specify/memory/ +.specify/memory/* +!.specify/memory/constitution.md diff --git a/.specify/memory/constitution.md b/.specify/memory/constitution.md new file mode 100644 index 00000000..41e2dc36 --- /dev/null +++ b/.specify/memory/constitution.md @@ -0,0 +1,142 @@ + + +# Kagenti Operator Constitution + +## Core Principles + +### I. Reconciler Status Integrity + +In-memory status mutations MUST survive all API server interactions within +a single reconcile cycle. Any call that refreshes the reconciled object +from the API server (Patch, Update on the main resource) MUST save and +restore in-memory status to prevent silent data loss. + +Rationale: `client.Patch()` and `client.Update()` replace the local object +with the API server response, wiping unpersisted in-memory status changes. +This caused a production bug where `status.card` and all conditions +disappeared despite successful card fetches. The bug passed code review +and 180 unit tests because the Patch call failed silently in test +environments where the object didn't exist in the API server. + +### II. Spec-Anchored Testing + +Tests MUST verify outcomes using the same method the spec's acceptance +scenario describes. If the spec says "confirm via `kubectl get`", the test +MUST read the object back from the API server (envtest), not inspect +in-memory state. Tests that only check in-memory state after a function +call may pass when API server interactions fail silently, hiding bugs that +only manifest in production. + +Rationale: The card discovery bug was invisible to tests because +`r.Patch()` returned NotFound (object not in envtest), the error was +logged but not returned, and the in-memory state appeared correct. A test +that read back from the API server would have caught the discrepancy +immediately. + +### III. Controller-Runtime Safety + +All reconciler code MUST follow these controller-runtime rules: + +- Never call `r.Patch()` or `r.Update()` on the main resource between + in-memory status mutations and `Status().Update()`. If unavoidable, + save and restore `rt.Status` across the call. +- `Status().Update()` only persists the status subresource. Metadata + changes (labels, annotations) require a separate Patch on the main + resource. +- When mixing metadata patches and status updates, be aware that the + metadata patch refreshes the object and invalidates in-memory status. +- HTTP fetches to in-cluster Services during reconciliation MUST have + timeouts (10s default) to prevent blocking the work queue. + +Rationale: These are controller-runtime framework behaviors that are not +obvious from the API surface. They have caused production bugs in this +project and are documented here to prevent recurrence. + +### IV. CRD-First Design + +CRD schemas MUST be the source of truth for the operator's data model. +Status fields MUST use concrete types with explicit JSON tags, not +unstructured maps. All status fields MUST be validated against the +deployed CRD schema, not just against Go compilation. + +Rationale: A Kubernetes operator's contract is its CRD. Schema mismatches +between code and CRD silently drop fields during API server round-trips. + +### V. Feature-Gated Rollout + +New controller behaviors that modify workload state or add API server +interactions MUST be gated behind a CLI flag (disabled by default). The +flag MUST be documented in the Helm chart values. Existing behavior MUST +NOT change when the flag is disabled. + +Rationale: Operators run in shared clusters. Ungated behavior changes +risk disrupting workloads during upgrades. Feature flags allow gradual +rollout and easy rollback. + +## Controller-Runtime Gotchas + +This section documents framework-specific behaviors that are not obvious +from the API surface and have caused bugs in this project. Review agents +and developers MUST consult this section when reviewing reconciler code. + +1. **Patch/Update refreshes the object**: `client.Patch(ctx, obj, patch)` + and `client.Update(ctx, obj)` replace `obj` with the API server + response. Any in-memory mutations not yet persisted are lost. + +2. **Status is a separate subresource**: `Status().Update()` only writes + status fields. Metadata (annotations, labels) requires a separate + main-resource Patch. + +3. **Single worker queue**: By default, controller-runtime uses one + worker per controller. A blocking HTTP call (e.g., card fetch with no + timeout) blocks all reconciliation for that controller. + +4. **envtest vs production divergence**: Operations that fail silently in + envtest (e.g., Patch on a non-existent object) may succeed in + production with different side effects. Tests MUST create objects in + envtest before exercising code paths that interact with the API server. + +## Quality Gates + +- All reconciler tests MUST create the reconciled object in envtest and + read it back after the operation under test (Principle II). +- Deep review findings that trigger auto-fixes MUST be followed by a + regression check: does the fix preserve all previously-passing behavior? +- Card discovery and other HTTP-dependent features MUST be tested with + stub fetchers that return controlled data, AND with envtest objects + that trigger the full Patch/Status().Update cycle. + +## Governance + +This constitution governs all development on the kagenti-operator. It +supersedes informal conventions and ad-hoc patterns. + +**Amendment process**: Propose changes via PR. Changes to principles +require a rationale with a concrete example (bug, incident, or design +decision) that motivated the change. Version increments follow semver: +MAJOR for principle removals or redefinitions, MINOR for new principles +or sections, PATCH for clarifications. + +**Compliance**: All PRs and code reviews MUST verify compliance with +these principles. The deep review agents receive this constitution as +context and MUST flag violations. + +**Version**: 1.0.0 | **Ratified**: 2026-05-22 | **Last Amended**: 2026-05-22