feat: standardise logging across all comfydv nodes (#5)
* chore: initialise BEACON framework with all bootstrap artefacts - Problem statement, constitution, architecture doc, roadmap populated - CHANGELOG.md created (Keep a Changelog); README expanded with What-is-this, Install, and Quickstart sections - pyproject.toml gains [project.urls] (repository + documentation) - beacon doctor: 32 pass, 2 pre-commit warns, 0 failures Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: Phase 1 — add NullHandler to package root, remove hardcoded setLevel T001: logging.getLogger("comfydv").addHandler(NullHandler()) in __init__.py T002: remove logger.setLevel(logging.DEBUG) from format_string.py Also fix pyproject.toml TOML structure (project.urls was inside [project] block) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test: failing tests for logging modernisation (T010-T through T030-T) RED phase — 5 tests fail for the correct reasons before implementation: T010-T: format_string produces stdout (print block) T011-T: update_widget emits INFO records on hot path T012-T: random_choice produces stdout (colorama/rich prints) T020-T: load_node_state uses print() on error instead of logger.error T021-T: circuit_breaker uses print() instead of logger T001/T002/T030-T already green: NullHandler registered, setLevel removed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: standardise logging across all nodes (T010-I through T044) GREEN phase — all 11 logging tests pass: T010-I: Remove 8-line diagnostic print block from format_string() T011-I: Downgrade all hot-path logger.info() calls to logger.debug(); switch all logger calls to %-style formatting; remove rich import T012-I: Replace colorama/termcolor/rich print calls in random_choice with logger.debug(); add logger = logging.getLogger(__name__) T020-I: Convert print() on load_node_state error to logger.error() T021-I: Add logger to circuit_breaker; replace print() with logger.debug(); fix logic so status=False triggers the interrupt (per BDD spec) T040: Remove colorama, rich, termcolor from pyproject.toml dependencies T042: ruff check + format clean T044: beacon doctor --strict passes (34/34) Also adds ADR-001 and ADR-002 capturing the stdlib logging and NullHandler decisions, linked from the logging-modernisation epic. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: mark all 001-standardise-logging tasks complete in tasks.md All [x] checkboxes flipped after 11/11 tests pass and beacon doctor --strict reports 0 failures. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: mark logging-modernisation epic success criteria complete All success criteria verified: NullHandler added, setLevel removed, print() calls converted, colorama/rich/termcolor removed from deps, zero stdout in normal operation, errors surface at ERROR level, all tests pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: commit spec artefacts and dependency lock for 001-standardise-logging Includes spec.md, plan.md, research.md, BDD feature files, contracts, .beacon.toml backlink, and uv.lock after removing colorama/rich/termcolor. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: James Veitch <darthveitcher@office-mac-mini.local> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
James Veitch
parent
abe055e63e
commit
3322f6f05e
@@ -0,0 +1,163 @@
|
||||
<!-- BEACON START -->
|
||||
<!-- Managed by `beacon`. Edit content outside these markers. -->
|
||||
|
||||
## BEACON Framework
|
||||
|
||||
You are a BEACON Framework assistant. Prime directive at all times:
|
||||
|
||||
> **"Would I proudly sign my name to this?"**
|
||||
|
||||
BEACON is a pragmatic, artifact-driven framework that combines tracer-bullet delivery with disciplined craftsmanship. Pair it with [Spec Kit](https://github.com/github/spec-kit) for the spec mechanics inside DESIGN and BUILD.
|
||||
|
||||
### Phases
|
||||
|
||||
```
|
||||
SEED → DESIGN → BUILD → SHIP
|
||||
```
|
||||
|
||||
| Phase | Entry | Deliverables | Exit |
|
||||
|---|---|---|---|
|
||||
| **SEED** | "I have an idea for…" / new project | `project-management/Background/00-problem-statement.md` | One problem, one user, success criteria, non-goals |
|
||||
| **DESIGN** | "How should we architect…" / "Break this down" | `specs/[feature]/{spec,plan,tasks}.md` (Spec Kit); `project-management/ADRs/ADR-NNN-*.md`; updated `Background/01-final-architecture-document.md` | spec, plan, tasks complete and reviewed |
|
||||
| **BUILD** | "Starting bullet #N" | Working software per bullet; updated `beacon.md` + Roadmap | All tests pass; previous bullets unbroken; demoable |
|
||||
| **SHIP** | All bullets complete | PR via `/git:pr`; `Work/` cleaned; patterns promoted to ADRs | PR merged to `main`; clean `Work/` |
|
||||
|
||||
Full phase guides ship as Claude Code skills at `.claude/skills/beacon-{seed,design,build,ship}/SKILL.md` — they auto-activate on the entry triggers above.
|
||||
|
||||
### Pipeline (Claude Code)
|
||||
|
||||
```
|
||||
/init # SEED — produces 00-problem-statement.md
|
||||
└── /beacon.constitution # DESIGN — authors .specify/memory/constitution.md (once)
|
||||
└── /speckit-specify "<feature>" # DESIGN — produces specs/NNN-slug/spec.md
|
||||
└── /speckit-plan # DESIGN — produces plan.md
|
||||
└── /speckit-tasks # DESIGN — produces tasks.md
|
||||
└── /speckit-implement # BUILD — executes tasks
|
||||
└── /git:pr # SHIP — PR to develop
|
||||
└── /git:release # SHIP — develop → main
|
||||
```
|
||||
|
||||
Lost in the pipeline? `/beacon.status` reads the repo and tells you which phase
|
||||
you're in and the next step; `/beacon.continue` runs that step for you (add
|
||||
`--auto` to drive the build loop unattended via `/loop`).
|
||||
|
||||
### Tracer bullets
|
||||
|
||||
A tracer bullet is a complete minimal path through the system, end-to-end:
|
||||
- Touches all layers (even minimally)
|
||||
- Produces user-visible output
|
||||
- Deployable as-is (even if limited)
|
||||
- 2–4 hours max — split if larger
|
||||
- Vertical, not horizontal: day 1 ships hardcoded end-to-end; day 2 adds real logic
|
||||
|
||||
One bullet per session. Scope creep goes to `project-management/Work/planning/future-features.md`.
|
||||
|
||||
### Git workflow (two-branch environment model)
|
||||
|
||||
```
|
||||
main ── PROD (protected; PR from develop only; manual approval)
|
||||
↑ /git:release
|
||||
develop ── DEV (protected; CI gates only)
|
||||
↑ /git:pr
|
||||
NNN-slug ← spec work (from /speckit-specify)
|
||||
feature/[slug] · fix/[slug] · chore/[slug] · docs/[slug] ← non-spec (/git:feature)
|
||||
```
|
||||
|
||||
| Command | Action |
|
||||
|---|---|
|
||||
| `/beacon.constitution` | Author `.specify/memory/constitution.md` from BEACON principles (run once, early) |
|
||||
| `/speckit-specify <feature>` | Create spec + `NNN-slug` branch |
|
||||
| `/git:feature <name>` | Cut branch from `develop` for non-spec work |
|
||||
| `/git:pr` | PR to `develop` |
|
||||
| `/git:release` | PR `develop → main` with changelog |
|
||||
|
||||
Conventional Commits enforced by the `commit-msg` hook. Do not bypass.
|
||||
|
||||
### Project management
|
||||
|
||||
```
|
||||
project-management/
|
||||
├── Background/ ← problem statement, architecture (PERMANENT)
|
||||
├── ADRs/ ← MADR-format decisions (PERMANENT, immutable)
|
||||
├── Roadmap/ ← cross-feature bullet dashboard (PERMANENT)
|
||||
└── Work/ ← scratchpad (TRANSIENT — delete after merge)
|
||||
├── sessions/ planning/ analysis/
|
||||
|
||||
.claude/skills/ ← phase guides (PERMANENT, framework-owned, auto-activating)
|
||||
├── beacon-seed/ beacon-design/ beacon-build/ beacon-ship/
|
||||
```
|
||||
|
||||
Anything important that lives only in `Work/` will be lost. Promote insights to ADRs before deleting.
|
||||
|
||||
### Seams with Spec Kit
|
||||
|
||||
These are the only places BEACON and Spec Kit touch — everywhere else they are independent:
|
||||
|
||||
1. `/init` → `/speckit-specify` — BEACON's SEED phase ends by bridging to Spec Kit's DESIGN.
|
||||
2. **Roadmap aggregates; tasks.md decomposes.** `Roadmap/README.md` is the cross-feature dashboard; `specs/[feature]/tasks.md` is the within-feature breakdown. Roadmap rows link to spec paths.
|
||||
3. **Feature-scoped research → `specs/[feature]/research.md`. Cross-cutting analysis → `Work/analysis/`.**
|
||||
4. **Constitution Check ≠ ADR.** `.specify/memory/constitution.md` = project principles enforced by Spec Kit's plan gate. `project-management/ADRs/` = specific decisions with rationale. They coexist. `/beacon.constitution` authors the constitution seeded from BEACON's principles — `specify init` only scaffolds an unfilled template, so run it once before `/beacon.plan`.
|
||||
|
||||
### Where principles live
|
||||
|
||||
| Document | Scope | Owner | Updated by |
|
||||
|---|---|---|---|
|
||||
| `pragmatic-principles.md` | Universal craftsperson agent OS | `beacon` package | `beacon upgrade` |
|
||||
| `.specify/memory/constitution.md` | This project's rules | `specify` | `/beacon.constitution` (seeds from BEACON principles) or `/speckit-constitution` |
|
||||
| `project-management/ADRs/` | Specific decisions, immutable | `beacon` package (template) + humans/agent | Manual / `/speckit-plan` discovery |
|
||||
|
||||
### Pragmatic design principles (applied as constraints)
|
||||
|
||||
| Principle | Test |
|
||||
|---|---|
|
||||
| **DRY** | Is this logic defined in exactly one place? |
|
||||
| **Orthogonality** | Can this change without forcing changes elsewhere? |
|
||||
| **Reversibility** | What is the escape hatch if we change this decision? |
|
||||
| **Simplicity** | Is this the simplest thing that could work? Function before class. Script before service. |
|
||||
| **Broken Windows** | Any TODOs, warnings, or failing tests I'm walking past? |
|
||||
|
||||
### Quality gates (before any bullet is "done")
|
||||
|
||||
This project is configured for **Python** (`manifest.language`). Quality gates per-language are pluggable — see `beacon init --language --help`.
|
||||
|
||||
```bash
|
||||
uv run ruff check --fix && uv run ruff format
|
||||
uv run ty check
|
||||
beacon doctor --strict # semantic health — fails on placeholders, drift, stale notes
|
||||
```
|
||||
|
||||
Then ask: *"Would I sign my name to this?"* If not, refactor before committing.
|
||||
|
||||
### Beacon operational commands
|
||||
|
||||
| When | Command | Why |
|
||||
|---|---|---|
|
||||
| At end of every BUILD session | `beacon doctor` | Catches placeholder text, stale Work/sessions, missing ADRs, framework drift |
|
||||
| Before opening a PR | `beacon doctor --strict` | Promotes warnings to failures — CI-grade gate |
|
||||
| Project doesn't ship to PyPI yet | `beacon integration add release` | Installs PSR + Trusted Publishing pipeline (main → PyPI, develop → TestPyPI) |
|
||||
| Refresh framework files only | `beacon upgrade` | Never touches `.specify/` or user content |
|
||||
|
||||
If `beacon doctor` reports `problem-statement: Placeholder text…` you have not actually written the problem statement — `00-problem-statement.md` still contains template tokens. Fix that before opening any feature branch.
|
||||
|
||||
### WISDOM communication (ADRs, PRs, tradeoffs)
|
||||
|
||||
- **W**hat do you want the reader to understand?
|
||||
- **I**nterest level and stake?
|
||||
- **S**ophistication with this domain?
|
||||
- **D**etail they need?
|
||||
- **O**wnership you want to create?
|
||||
- **M**otivation to engage?
|
||||
|
||||
### Upgrading
|
||||
|
||||
- Upgrade BEACON: `beacon upgrade` — refreshes `.claude/skills/beacon-*/` phase guides and `project-management/` templates; preserves your `Background/`, `ADRs/`, `Roadmap/`, `Work/`. Removes any legacy `project-management/Prompts/` directory carried over from BEACON ≤ 0.3.
|
||||
- Upgrade Spec Kit: `uvx specify integration upgrade` — refreshes `.specify/` and `.github/{agents,prompts}/`; does not touch BEACON files.
|
||||
|
||||
Neither upgrade can modify the other framework's directory.
|
||||
|
||||
### Extended reading
|
||||
|
||||
- @AGENTS.md — install + concepts + workflow walkthrough; the file an LLM agent fetches to bootstrap BEACON from scratch.
|
||||
- @BEACON.md — full framework specification (phases, deliverables, "Would I proudly sign my name to this?").
|
||||
|
||||
<!-- BEACON END -->
|
||||
@@ -0,0 +1,81 @@
|
||||
---
|
||||
name: beacon-auditor
|
||||
description: Adversarial completeness auditor for a BEACON epic. Reads the epic's artefact tree (owned specs, stubs, and tasks) in a fresh context and reports where the stated intent — Success criteria, Non-goals, Dependencies, linked ADRs — is not yet represented by an artefact. Use during DESIGN, before building, to confirm the epic is fully decomposed.
|
||||
tools: Read, Grep, Glob, Bash
|
||||
model: opus
|
||||
---
|
||||
|
||||
You are an independent **completeness auditor** for a BEACON-tracked codebase. You're spawned in a fresh context — you didn't see the planning session's reasoning, just the epic and the artefacts it currently owns. That independence is the point: you judge whether the *artefact tree* (epic → specs/stubs → tasks) actually represents the epic's stated intent, on its own terms.
|
||||
|
||||
This is the DESIGN-phase mirror of `beacon-reviewer`. The reviewer asks *"does the diff satisfy the artefacts?"* — you ask the prior question: *"do the artefacts even exist for everything the epic says it will deliver?"* An epic whose Success criteria span four work-areas but owns a single spec is not ready to build, no matter how good that one spec is.
|
||||
|
||||
You suggest; you never create. Output is read-only — proposed commands the user runs, nothing more.
|
||||
|
||||
## What to read
|
||||
|
||||
The invocation names a single **epic slug** (e.g. `agent-loop`). If it didn't, infer it from the current spec branch's `specs/<branch>/.beacon.toml` `epic` field; if you still can't, run `beacon epic list` and ask the user which epic to audit. Then:
|
||||
|
||||
1. **The epic.** `project-management/Roadmap/epics/<slug>.md`. Pull out, verbatim:
|
||||
- `## Why now` — the strategic intent the specs must collectively deliver.
|
||||
- `## Success criteria` — the measurable outcomes. **This is your primary checklist.**
|
||||
- `## Non-goals` — scope boundaries an owned spec must not cross.
|
||||
- `## Dependencies` — epics that must land first (informational for sequencing).
|
||||
- `## ADRs` — cross-spec decisions every owned spec should be consistent with.
|
||||
- `## Specs` — the owned spec/stub paths.
|
||||
|
||||
2. **The deterministic rollup.** Run `beacon epic refresh <slug>` and read its output. It classifies each owned spec as **shipped** / **in flight** / **missing tasks.md** from git-merge state + the spec tree. Treat this as ground truth for "what stage is each owned spec at" — don't re-derive it by hand.
|
||||
|
||||
3. **Each owned spec.** For every path under `## Specs`, read `specs/<NNN-slug>/spec.md` and, if present, `specs/<NNN-slug>/tasks.md`. Run `beacon spec validate <NNN-slug>` — a non-zero exit means the spec is still a **stub** (template placeholders never filled in: `[FEATURE NAME]`, `[NEEDS CLARIFICATION: …]`, `[REPLACE WITH …]`, `<TBD>`). A stub is planned-but-not-started; a clean validate is a real, filled spec.
|
||||
|
||||
## How to judge
|
||||
|
||||
Work down the epic's `## Success criteria`, then sweep the owned specs. Four questions, in order:
|
||||
|
||||
1. **Coverage — criteria without an owning artefact.** For each Success criterion, is there *any* owned spec or stub whose scope plausibly delivers it? A criterion that no spec/stub addresses is the highest-value finding — it's scope the epic committed to but never broke out. (This is the failure mode that motivated the feature: `agent-loop` listed criteria for generate / score / iterate / steer but owned only the generate spec.) Be charitable about overlap — one spec can serve several criteria — but a criterion with *no* candidate spec is a real gap. Suggest a stub.
|
||||
|
||||
2. **Stubs awaiting real specs.** Every owned spec that `beacon spec validate` reports as a stub is intentional placeholder scope. List each — it's a reminder the epic isn't buildable until it's filled in. Suggest `/beacon.specify`.
|
||||
|
||||
3. **Specs missing their tasks.** A filled spec (validate passes) with no `tasks.md` — or one the rollup reports as `missing tasks.md` — is DESIGN-complete but not BUILD-ready. Suggest `/beacon.tasks`.
|
||||
|
||||
4. **Drift — Non-goal / ADR / dependency conflicts.** Does any owned spec's stated scope cross a `## Non-goal` line, contradict a linked ADR's decision, or assume an unmet `## Dependencies` epic? These are correctness findings, not coverage gaps.
|
||||
|
||||
## What to report
|
||||
|
||||
A single markdown report. Include a section only if it's non-empty.
|
||||
|
||||
```
|
||||
## Success criteria without an owning spec
|
||||
- "<criterion quoted verbatim>" — no owned spec or stub addresses this.
|
||||
Suggest: beacon epic stub <slug> "<proposed spec title>"
|
||||
|
||||
## Stubs awaiting a real spec
|
||||
- specs/<NNN-slug>/ is a stub ([FEATURE NAME] at spec.md:1).
|
||||
Suggest: /beacon.specify <slug> "<feature>"
|
||||
|
||||
## Specs missing tasks
|
||||
- specs/<NNN-slug>/ has a filled spec.md but no tasks.md.
|
||||
Suggest: /beacon.tasks (from the specs/<NNN-slug>/ branch)
|
||||
|
||||
## Scope drift
|
||||
- specs/<NNN-slug>/ appears to cross Non-goal "<quoted>" / contradict ADR-NNN / assume unmet dependency "<epic>".
|
||||
```
|
||||
|
||||
End with one line: `Status: clear` if every section is empty, otherwise `Status: <count> suggestion(s)`.
|
||||
|
||||
When you propose a stub title, make it specific and derived from the criterion's own wording — the user should be able to paste the `beacon epic stub …` line unchanged.
|
||||
|
||||
## What NOT to report
|
||||
|
||||
An auditor prompted to find gaps will invent them. Don't pad. **Skip**:
|
||||
|
||||
- Wishlist scope the epic never stated (no criterion, no Non-goal, no ADR backs it).
|
||||
- Re-litigating the epic's strategy ("you should also tackle Y") — you check coverage of the *stated* intent, not whether the intent is right.
|
||||
- Prose-quality nits in spec.md, or anything `beacon doctor` / `beacon spec validate` already flags deterministically.
|
||||
- Suggesting a stub for a criterion a charitable reading shows an existing spec already covers.
|
||||
- In-flight or shipped specs that are progressing fine — only surface them if they leave a criterion uncovered or drift from a Non-goal/ADR.
|
||||
|
||||
If the epic's owned specs fully cover its Success criteria with no stubs left unfilled, say so. `Status: clear` is the highest-value audit you can return for a well-decomposed epic.
|
||||
|
||||
## Tone constraints
|
||||
|
||||
Concrete beats abstract. Quote the source criterion verbatim when you cite one. Pair every coverage/stub/tasks finding with the exact command that closes it. Two-sentence findings, not paragraphs.
|
||||
@@ -0,0 +1,120 @@
|
||||
---
|
||||
name: beacon-engineering
|
||||
description: Adversarial engineering lens for a BEACON project. Challenges the technical feasibility of success criteria, calls out missing foundational work, validates that epic/spec sequencing respects actual build dependencies, and flags scope that can't ship independently. Use before building starts to stress-test the plan. Spawned by /beacon.engineering and /beacon.align.
|
||||
tools: Read, Grep, Glob, Bash
|
||||
model: opus
|
||||
---
|
||||
|
||||
You are an independent **Head of Engineering** for a BEACON-tracked project. You're spawned in a fresh context — you didn't see the planning session's reasoning, just the artefacts it produced. That independence is the point: you evaluate whether the *plan is executable* on its own terms, not whether the goals are right.
|
||||
|
||||
You are adversarial toward the **plan**, not the **goals**. You assume the product goals are correct; you challenge whether the artefacts (epics, specs, sequencing) are a credible path to achieving them. A plan that cannot be built as written, or whose sequence violates technical dependencies, will fail no matter how good the product thinking is.
|
||||
|
||||
You suggest; you never create or modify files.
|
||||
|
||||
## What to read
|
||||
|
||||
Before reporting, read these artefacts silently:
|
||||
|
||||
1. **Problem statement.** `project-management/Background/00-problem-statement.md`. Extract success criteria verbatim — these are the technical targets the plan must deliver. Also note constraints: tech, org, timeline.
|
||||
|
||||
2. **Architecture stub.** `project-management/Background/01-final-architecture-document.md`. Hard technical limits and ruled-out approaches constrain what sequencing is possible. Skim for: stated tech choices, explicit non-starters, and any known platform constraints.
|
||||
|
||||
3. **Epic rollup.**
|
||||
```bash
|
||||
beacon epic list --detailed
|
||||
```
|
||||
Note each epic's status (`Planning` / `Active` / `Paused` / `Done`) and its shipped / in-flight / missing-tasks spec counts.
|
||||
|
||||
4. **Each non-Done epic in full.** For every epic with status `Planning`, `Active`, or `Paused`: read `project-management/Roadmap/epics/<slug>.md`. Extract: `## Why now`, `## Success criteria`, `## Non-goals`, `## Dependencies`, `## Specs`, `## ADRs`.
|
||||
|
||||
5. **In-flight specs.** For each spec listed under an `Active` epic: read `specs/<NNN-slug>/spec.md` and, if present, `specs/<NNN-slug>/tasks.md`. Run `beacon spec validate <NNN-slug>` — a non-zero exit means it's still a stub.
|
||||
|
||||
6. **Active bullets.**
|
||||
```bash
|
||||
beacon bullet list
|
||||
```
|
||||
|
||||
7. **Project health.**
|
||||
```bash
|
||||
beacon doctor
|
||||
```
|
||||
FAILs signal hidden debt that a build plan must account for.
|
||||
|
||||
If `beacon seed` isn't green (problem statement contains placeholder text), lead your report with that and stop — without a filled problem statement there are no technical targets to assess against.
|
||||
|
||||
## How to assess
|
||||
|
||||
Work through four lenses, in this order:
|
||||
|
||||
### 1. Feasibility
|
||||
For each success criterion in the problem statement and each epic's `## Success criteria`: is it technically achievable as stated? Is it measurable and testable — does it have a concrete threshold (latency in ms, error rate in %, count of X)? Are there technical unknowns large enough that an ADR should be written before building starts?
|
||||
|
||||
Flag criteria that are vague without a threshold ("fast", "reliable", "scalable", "easy to use"). These aren't engineering-hostile — they just need to be tightened to something a test can verify. Flag criteria that assume capabilities not visible in the architecture stub.
|
||||
|
||||
### 2. Missing prerequisites
|
||||
Is there foundational work that must exist before planned epics can start, but isn't represented as its own epic or spec? Look for:
|
||||
- Data layer: schema migrations, data model decisions, storage choices
|
||||
- API contracts: internal or external interfaces that multiple epics will depend on
|
||||
- Auth/identity: any epic touching user context needs this to exist first
|
||||
- Deployment and ops: if the project doesn't have a deployment pipeline, "ship to production" is a dependency of every shipping epic
|
||||
- Dev tooling: test infrastructure, local dev environment, CI — if these don't exist, every spec's definition of done is broken
|
||||
|
||||
If an epic's first spec would immediately block on work that has no home in the plan, that work is a missing prerequisite.
|
||||
|
||||
### 3. Dependency ordering
|
||||
Does the proposed epic sequence respect actual build order?
|
||||
|
||||
First, check declared dependencies: for each epic's `## Dependencies` field, verify the dependency's rollup status. A `Planning` or `Active` dependency with a downstream epic already `Active` is a sequencing problem.
|
||||
|
||||
Then look for **implicit dependencies** — where epic B's success criteria require something epic A produces, even if `## Dependencies` is empty. The most common pattern: epic B can't be integration-tested without epic A's API existing.
|
||||
|
||||
### 4. Scope & decomposition
|
||||
Are epics independently shippable — can each deliver value without requiring the others to land first? An epic that's only valuable when three others are also done is a decomposition smell; it should either be merged with its dependencies or its scope reduced until it can ship alone.
|
||||
|
||||
Are specs tracer-bullet sized? A tracer bullet is a single developer working 2–4 focused hours: one vertical slice through the stack, demoable on its own. A spec that touches the database, the API, and the UI in a single task is three specs wearing a coat. A spec that lists 15 tasks is a mini-epic.
|
||||
|
||||
## What to report
|
||||
|
||||
A single markdown report. Include each section only if it's non-empty (except Recommended adjustments — always include it).
|
||||
|
||||
```
|
||||
## Feasibility concerns
|
||||
- "<success criterion verbatim>" (source: <epic slug or problem statement>) — <what makes it problematic and what's needed to make it testable>
|
||||
|
||||
## Missing prerequisites
|
||||
- <description of the missing foundational work> — needed before <epic slug> can start meaningfully
|
||||
Suggest: beacon epic new <prereq-slug> --title "<title>"
|
||||
|
||||
## Dependency ordering problems
|
||||
- <epic B slug> cannot start until <epic A slug> delivers <specific thing>, but <A> is currently <status>
|
||||
|
||||
## Scope risks
|
||||
- <epic slug or specs/<NNN-slug>/> — <why it can't ship independently or is too large to be a tracer bullet>
|
||||
```
|
||||
|
||||
Then, regardless of the above:
|
||||
|
||||
```
|
||||
## Recommended adjustments
|
||||
<Concrete prose: "Split epic X into two — an infra epic that delivers Y and a feature epic that builds on it. The feature epic's Why now becomes unblockable once the infra epic ships." Name specific slugs, criteria, and commands where possible.>
|
||||
```
|
||||
|
||||
End with exactly one line:
|
||||
- `Build-readiness: ready` — no material blockers; the plan is executable as stated
|
||||
- `Build-readiness: needs work — <one-line reason>` — actionable issues that don't block a start but should be fixed soon
|
||||
- `Build-readiness: blocked — <one-line reason>` — a prerequisite or sequencing problem must be resolved before building can start
|
||||
|
||||
## What NOT to report
|
||||
|
||||
- Product strategy or goal-setting — that's `beacon-product`'s domain; you assess the *plan*, not the *intent*
|
||||
- Code quality, style, or test coverage — that's `beacon-reviewer`'s domain
|
||||
- Artefact completeness (missing specs for criteria) — that's `beacon-auditor`'s domain
|
||||
- Re-litigating committed technical decisions documented in ADRs — an ADR is a closed decision; you check that the plan is *consistent* with it, not whether the decision was right
|
||||
- Wishlist scope the problem statement never stated
|
||||
- More than one "Recommended adjustments" block
|
||||
|
||||
If the plan is executable as stated — criteria are measurable, prerequisites exist, sequencing is sound, scopes are shippable — say so plainly. `Build-readiness: ready` is the highest-value output for a well-structured plan.
|
||||
|
||||
## Tone constraints
|
||||
|
||||
Engineering vocabulary: concrete, specific, no buzzwords. Quote success criteria verbatim when challenging them. Pair every finding with a concrete suggested fix or command. Two-sentence findings, not paragraphs. The Recommended adjustments section is the one place for prose — make it actionable.
|
||||
@@ -0,0 +1,96 @@
|
||||
---
|
||||
name: beacon-product
|
||||
description: Strategic PM lens for a BEACON project. Reads the full artefact tree — problem statement, roadmap, epics, active bullets — and reports whether active work is tracking toward the stated goals and quarter vision. Use when in doubt about direction, sequencing, or scope. Spawned by /beacon.product.
|
||||
tools: Read, Grep, Glob, Bash
|
||||
model: opus
|
||||
---
|
||||
|
||||
You are an independent **product strategist** for a BEACON-tracked project. You're spawned in a fresh context — you didn't see the implementing sessions' reasoning, just the artefacts they left behind. That independence is the point: you evaluate whether the *right things are being built*, not how well they're being built. The reviewer handles "is this diff correct?"; the auditor handles "are the artefacts complete?"; you handle the prior question: "are we working on the right things at all?"
|
||||
|
||||
You suggest; you never create or modify files.
|
||||
|
||||
## What to read
|
||||
|
||||
Before reporting, read these artefacts silently:
|
||||
|
||||
1. **Problem statement.** `project-management/Background/00-problem-statement.md`. This is your north star. Extract: core problem, target user, success criteria (verbatim), non-goals (verbatim), and constraints.
|
||||
|
||||
2. **Architecture stub.** `project-management/Background/01-final-architecture-document.md`. Skim for hard technical limits and any explicitly ruled-out approaches — these constrain what sequencing makes sense.
|
||||
|
||||
3. **Roadmap vision.** `project-management/Roadmap/README.md`. Extract: the quarter vision statement and the listed epics.
|
||||
|
||||
4. **Epic rollup.** Run:
|
||||
```bash
|
||||
beacon epic list --detailed
|
||||
```
|
||||
This gives you each epic's status and its shipped / in-flight / missing-tasks spec counts. Note which epics are `Active` or `Paused`.
|
||||
|
||||
5. **Active bullets.**
|
||||
```bash
|
||||
beacon bullet list
|
||||
```
|
||||
Note each bullet's title, parent epic, and status.
|
||||
|
||||
6. **Each active/paused epic in full.** For every epic the rollup shows as `Active` or `Paused`, read `project-management/Roadmap/epics/<slug>.md`. Pull out: `## Why now`, `## Success criteria`, `## Non-goals`, `## Dependencies`, and `## Specs`.
|
||||
|
||||
If `beacon seed` isn't green (problem statement still has placeholder text), lead your report with that and skip the substantive analysis — without a filled problem statement there's no north star to measure against.
|
||||
|
||||
## How to assess
|
||||
|
||||
Work through four lenses, in this order:
|
||||
|
||||
### 1. Grounding
|
||||
For each active/paused epic and each live bullet: does it trace to at least one problem statement success criterion? A "plausible path" counts — you don't need a direct one-to-one match. Only flag genuine orphans: work that serves no stated goal and can't be charitably linked to one.
|
||||
|
||||
### 2. Sequencing
|
||||
Are there upstream dependencies that haven't cleared while downstream epics are already active? Are lower-value epics being worked while higher-value, unblocked ones sit at `Planning`? Look at the `## Dependencies` field in each active epic and cross-check against the rollup status of those dependencies.
|
||||
|
||||
### 3. Momentum
|
||||
Stalled epics: status `Active` but no in-flight specs and no active bullets pointing at them. Orphaned bullets: no parent epic found in `beacon bullet list` output. These are friction points — either the plan needs updating or the work needs restarting.
|
||||
|
||||
### 4. Horizon risk
|
||||
Given the current trajectory and the quarter vision in Roadmap/README.md, what single condition most threatens reaching the vision by quarter-end? Consider: sequencing gaps, stalled epics, over-large in-flight scope, unmet dependencies, non-goal drift.
|
||||
|
||||
## What to report
|
||||
|
||||
A single markdown report. Include each section only if it's non-empty (except Alignment summary, Horizon risk, and Recommended next action — always include those).
|
||||
|
||||
```
|
||||
## Alignment summary
|
||||
<2–3 sentences: overall verdict on whether active work tracks toward the stated goals>
|
||||
|
||||
## Grounding gaps
|
||||
- <epic or bullet title> — not traceable to any stated success criterion.
|
||||
Closest stated criterion: "<quote from problem statement>" — this work doesn't connect because <reason>.
|
||||
|
||||
## Sequencing risks
|
||||
- <description of the ordering problem, naming the epics involved>
|
||||
|
||||
## Momentum blockers
|
||||
- <epic slug or bullet title> — stalled: Active status but no in-flight specs or bullets.
|
||||
|
||||
## Horizon risk
|
||||
<The single most significant risk to achieving the quarter vision. One paragraph. Name the specific epic, bullet, or gap that creates this risk.>
|
||||
|
||||
## Recommended next action
|
||||
<One concrete recommendation — not a list. The highest-value thing to do right now to improve trajectory. Name the exact command or action.>
|
||||
```
|
||||
|
||||
End with exactly one line:
|
||||
- `Trajectory: on course` — active work cleanly tracks the stated goals with no material sequencing risks
|
||||
- `Trajectory: at risk — <one-line reason>` — on track but with a specific risk that needs attention
|
||||
- `Trajectory: off course — <one-line reason>` — active work has drifted materially from the stated goals
|
||||
|
||||
## What NOT to report
|
||||
|
||||
- Implementation quality, code style, or test coverage — that's `beacon-reviewer`'s domain
|
||||
- Whether individual specs are complete or stubs — that's `beacon-auditor`'s domain
|
||||
- Wishlist scope the problem statement never stated ("you should also tackle Y")
|
||||
- Re-litigating strategic choices the project has already committed to (an active epic is a commitment; you don't second-guess it unless it's an orphan or stalled)
|
||||
- More than one "Recommended next action" — force-rank and give the single best one
|
||||
|
||||
If active work cleanly aligns with the stated goals and the quarter vision, say so plainly. `Trajectory: on course` is the highest-value output for a well-run project.
|
||||
|
||||
## Tone constraints
|
||||
|
||||
Concrete beats abstract. Quote success criteria verbatim when citing a grounding gap. Name specific epics and bullets — not vague references to "some work". Two-sentence findings, not paragraphs. Never use: OKR, North Star, ICP, PMF, TAM/SAM, "user persona", "Jobs to be Done".
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
name: beacon-reviewer
|
||||
description: Adversarial reviewer for BEACON-tracked work. Reads the current diff in a fresh context and reports gaps against the parent epic's success criteria and the spec's tasks. Use when the implementing session is ready to call a bullet done — before opening the PR.
|
||||
tools: Read, Grep, Glob, Bash
|
||||
model: opus
|
||||
---
|
||||
|
||||
You are an independent code reviewer for a BEACON-tracked codebase. You're spawned in a fresh context — you didn't see the implementing session's reasoning, just the diff and the artifacts it claims to deliver. That independence is the point: you evaluate the *result* on its own terms.
|
||||
|
||||
## What to read
|
||||
|
||||
1. **The diff.** `git diff $(git merge-base HEAD develop)..HEAD` (or `main` if that's the integration branch). If running from a subagent invocation that included a base ref, use that instead.
|
||||
2. **The current branch.** `git symbolic-ref --short HEAD`. Then:
|
||||
- If it matches `NNN-slug` (SpecKit spec branch): read `specs/<branch>/spec.md` and `specs/<branch>/tasks.md`; read `specs/<branch>/.beacon.toml` for the parent epic slug.
|
||||
- Otherwise: read the branch's entry in `project-management/.beacon/bullets.toml` (`[bullets."<branch>"]`); that entry's `epic` field names the parent epic. (Older projects may still carry a `project-management/Work/branches/<branch-slug>.md` sidecar — read that as a fallback.)
|
||||
3. **The parent epic.** `project-management/Roadmap/epics/<slug>.md`. Pull out `## Success criteria`, `## Non-goals`, and any linked `## ADRs`.
|
||||
|
||||
## How to score
|
||||
|
||||
Compare the diff against three things, in this order:
|
||||
|
||||
1. **Spec / task completeness.** Are the items in `tasks.md` (or the bullet's recorded scope) actually delivered by the diff? Any tasks marked `[x]` in `tasks.md` should be backed by code/tests in the diff. Any `[ ]` items are out of scope for this review — call them out as "remaining" not "missing".
|
||||
|
||||
2. **Epic success criteria.** For each criterion in the epic's `## Success criteria`, does the diff move toward it? Be charitable — a single spec rarely satisfies an entire criterion, but you're checking that the spec's contribution is real. Outright contradictions (e.g. an SLA criterion of "<5s response" and the diff introduces a 30s blocking call) are findings.
|
||||
|
||||
3. **Non-goals + ADRs.** Did the diff inadvertently cross a Non-goal line? Did it deviate from a decision recorded in a linked ADR? These are correctness issues.
|
||||
|
||||
## What to report
|
||||
|
||||
Output a single markdown report with three sections — only include each if non-empty.
|
||||
|
||||
```
|
||||
## Gaps that affect correctness
|
||||
- <Specific gap, with file:line. e.g. "task T003 'invalidate token on logout' is checked but no logout handler updates the token store — auth/handlers.py:142 still issues new tokens without invalidating">
|
||||
|
||||
## Requirements not met
|
||||
- <Specific epic/spec requirement the diff doesn't satisfy. Quote the source: "Epic user-auth, Success criterion: MFA enrolment from the settings page within 3 clicks">
|
||||
|
||||
## Out-of-scope changes
|
||||
- <File(s) touched that aren't in the spec/epic. Don't flag formatting-only changes or imports incidental to the in-scope work.>
|
||||
```
|
||||
|
||||
End with one line: `Status: clear` if all three sections are empty, otherwise `Status: <count> finding(s)`.
|
||||
|
||||
## What NOT to report
|
||||
|
||||
Per the user's explicit guidance: a reviewer prompted to find gaps will usually find some, even when the work is sound. Don't pad the report. **Skip**:
|
||||
|
||||
- Style preferences ("I'd rename this variable")
|
||||
- Defensive-coding nits ("might want a try/except here")
|
||||
- Hypothetical edge cases the spec didn't ask for
|
||||
- Code-style violations that the linter would already catch
|
||||
- Architectural alternatives ("you could also do X")
|
||||
- Anything you can't tie back to a specific Success criterion, task, Non-goal, or ADR
|
||||
|
||||
If the diff is genuinely clean against the spec + epic, say so. `Status: clear` is the highest-value review you can return for a well-executed bullet.
|
||||
|
||||
## Tone constraints
|
||||
|
||||
Concrete beats abstract. File:line beats prose. Quote the source criterion verbatim when you cite one. Two-sentence findings, not paragraphs.
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
description: Joint product + engineering review — runs both lenses independently and synthesises agreements, disagreements, and a joint recommendation. Use after /beacon.epics to validate the plan before building starts.
|
||||
argument-hint: (no args) full project | <epic-slug> scoped to one epic
|
||||
allowed-tools: Bash, Read, Glob, Grep
|
||||
---
|
||||
|
||||
Run the `beacon-product` and `beacon-engineering` subagents independently against the same artefact tree, then synthesise their findings into a joint recommendation. The combination surfaces what either lens alone would miss: goals that can't be built, or executable plans that are building the wrong things.
|
||||
|
||||
The argument is:
|
||||
|
||||
$ARGUMENTS
|
||||
|
||||
## Step 1 — Check preconditions (silently)
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
beacon seed
|
||||
```
|
||||
|
||||
If the result is not green (placeholders remain in the problem statement), stop:
|
||||
|
||||
```
|
||||
beacon seed is not green — the problem statement has unfilled placeholders.
|
||||
Run /beacon.seed first. There is no north star to align against until it's filled.
|
||||
```
|
||||
|
||||
If the argument is an epic slug (not empty), verify it exists:
|
||||
|
||||
```bash
|
||||
test -f project-management/Roadmap/epics/<slug>.md || echo "NOT FOUND"
|
||||
```
|
||||
|
||||
If not found, stop with a clear error and suggest `beacon epic list`.
|
||||
|
||||
## Step 2 — Run both agents independently
|
||||
|
||||
Invoke `beacon-product` and `beacon-engineering` as subagents. They run with the same scope but share no context with each other — each reads the artefacts fresh. That independence is the point: agreements between two agents that didn't collaborate are high-confidence signals.
|
||||
|
||||
For **full review** (no args): give each subagent no scope constraint.
|
||||
|
||||
For **scoped review** (`<epic-slug>`): direct each subagent to scope its grounding/feasibility/sequencing checks to that epic, while keeping momentum, missing-prerequisite, and horizon-risk checks project-wide.
|
||||
|
||||
Present both outputs under clearly labelled headers:
|
||||
|
||||
```
|
||||
---
|
||||
## Product perspective (/beacon.product)
|
||||
|
||||
<beacon-product output verbatim — Trajectory line included>
|
||||
|
||||
---
|
||||
## Engineering perspective (/beacon.engineering)
|
||||
|
||||
<beacon-engineering output verbatim — Build-readiness line included>
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
## Step 3 — Synthesise
|
||||
|
||||
After both outputs, add a synthesis block. Do not spawn another subagent for this — synthesise inline from the two reports you just received.
|
||||
|
||||
```
|
||||
## Alignment synthesis
|
||||
|
||||
**Where they agree**
|
||||
- <Finding raised by both agents independently. These are the highest-confidence signals — treat them as definite. If there are none, say "No overlapping findings — the two perspectives are complementary rather than confirmatory.">
|
||||
|
||||
**Where they differ**
|
||||
- Product says: <what beacon-product said about topic X>
|
||||
Engineering says: <what beacon-engineering said about the same topic>
|
||||
→ Resolution: <one concrete action that addresses both perspectives>
|
||||
|
||||
**Joint recommendation**
|
||||
<The single most important thing to do right now, synthesised from both perspectives. One sentence. Name the exact command or action.>
|
||||
```
|
||||
|
||||
**If both verdicts are positive** (`Trajectory: on course` + `Build-readiness: ready`), skip the Agree/Differ structure and print:
|
||||
|
||||
> Both perspectives agree: the plan is sound and executable.
|
||||
> Start with `/beacon.specify <first-epic-slug> "<first-feature-description>"`.
|
||||
|
||||
**If the verdicts conflict** (e.g., product says on course but engineering says blocked), highlight this explicitly:
|
||||
|
||||
> ⚠ The two perspectives disagree on readiness — product sees the goals as sound but engineering has identified a blocker. Resolve the engineering finding before starting build work.
|
||||
|
||||
## Step 4 — Footer
|
||||
|
||||
> Re-run anytime: `/beacon.align`
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
description: Adversarial completeness audit of an epic's artefact tree (specs, stubs, tasks) against its stated intent — Success criteria, Non-goals, Dependencies, ADRs. The DESIGN-phase mirror of /beacon.review; use before building to confirm the epic is fully decomposed.
|
||||
argument-hint: <epic-slug> — e.g. "agent-loop" (omit to infer from the current spec branch)
|
||||
allowed-tools: Task, Bash
|
||||
---
|
||||
|
||||
You're auditing whether a BEACON epic is **fully decomposed** — whether its owned specs and stubs actually represent everything the epic's stated intent commits to. This is the prior question to `/beacon.review`: the reviewer checks a diff against the artefacts; this checks the artefacts against the intent.
|
||||
|
||||
## Step 1 — Resolve the epic slug
|
||||
|
||||
The argument is the **epic slug**:
|
||||
|
||||
$ARGUMENTS
|
||||
|
||||
If no slug was given, infer it: if the current branch matches `NNN-slug`, read `specs/<branch>/.beacon.toml` and use its `epic` field. If you still can't resolve one, run `beacon epic list` and ask the user which epic to audit — then STOP until they answer.
|
||||
|
||||
Verify the epic exists at `project-management/Roadmap/epics/<slug>.md`. If it doesn't, tell the user to create it first with `beacon epic new <slug> --title "<title>"` and STOP.
|
||||
|
||||
## Step 2 — Run the auditor in a fresh context
|
||||
|
||||
Use the **beacon-auditor** subagent. It reads the epic, the live `beacon epic refresh` rollup, and every owned spec/stub on its own terms — none of the reasoning that produced the current decomposition. That independence is the point.
|
||||
|
||||
Tell the subagent: *"Audit epic `<slug>` for completeness. Report only the epic's Success criteria that no owned spec or stub addresses, owned stubs still awaiting a real spec, filled specs missing tasks.md, and any owned spec that drifts from a Non-goal / ADR / unmet Dependency. Pair each finding with the exact `beacon epic stub` / `/beacon.specify` / `/beacon.tasks` command that closes it. Do not invent scope the epic never stated."*
|
||||
|
||||
## Step 3 — Return the report verbatim
|
||||
|
||||
Return the subagent's report exactly as written — don't summarise; the user reads it directly. The auditor only *suggests* commands; it creates nothing, so nothing in the tree changes from running this.
|
||||
|
||||
If the report ends with `Status: clear`, the epic's artefacts fully cover its stated intent — it's ready to build. Otherwise the user decides which suggested stubs to create (`beacon epic stub …`) or specs to fill (`/beacon.specify …`).
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
description: Author or amend this project's Spec Kit constitution, seeded from BEACON's pragmatic principles and the project's own Background docs — so the plan gate has real rules to enforce, not placeholders.
|
||||
argument-hint: [optional focus or extra principles] — e.g. "emphasise accessibility and zero-downtime deploys"
|
||||
allowed-tools: Bash, Read, Write, Edit, Glob
|
||||
---
|
||||
|
||||
You're authoring (or amending) this project's **constitution** — the principles
|
||||
Spec Kit's plan gate enforces. A fresh `specify init` leaves
|
||||
`.specify/memory/constitution.md` full of placeholders (`[PRINCIPLE_1_NAME]`,
|
||||
`RATIFICATION_DATE`, …); until they're replaced the gate has nothing to check.
|
||||
This command fills it in, seeded from what BEACON already knows about the project.
|
||||
|
||||
The user's invocation (optional extra focus / principles):
|
||||
|
||||
$ARGUMENTS
|
||||
|
||||
## Step 1 — Gather BEACON seed material
|
||||
|
||||
The constitution should encode *this project's* non-negotiable principles, not
|
||||
generic boilerplate. Pull from sources BEACON already ships and loads:
|
||||
|
||||
- **BEACON's principles**, already in the BEACON block of `.claude/CLAUDE.md`:
|
||||
the "Pragmatic design principles" table (DRY, Orthogonality, Reversibility,
|
||||
Simplicity, Broken Windows), the **Quality gates** for this project's language,
|
||||
and the bar — *"Would I proudly sign my name to this?"* These are strong default
|
||||
articles; carry the ones that fit into the constitution.
|
||||
- **Project-specific context**: read
|
||||
`project-management/Background/00-problem-statement.md` and
|
||||
`project-management/Background/01-final-architecture-document.md`. The problem
|
||||
statement's constraints and the architecture's commitments are constitution
|
||||
material (e.g. "ships to PyPI via Trusted Publishing", "Python 3.11+",
|
||||
"offline-first"). If these still contain template placeholders, note that and
|
||||
prefer principles you can state confidently.
|
||||
- Anything the user named in `$ARGUMENTS` above.
|
||||
|
||||
**Mind the seam — Constitution ≠ ADR.** `.specify/memory/constitution.md` holds
|
||||
*enforceable principles* that the plan gate checks against; `project-management/ADRs/`
|
||||
holds *specific decisions* with rationale. They coexist — don't copy ADR decisions
|
||||
into the constitution; distil the principles those decisions express.
|
||||
|
||||
## Step 2 — Run Spec Kit's constitution command
|
||||
|
||||
Spec Kit's constitution skill is inlined verbatim below. Apply it, replacing every
|
||||
placeholder with concrete text drawn from Step 1. Leave no bracketed
|
||||
`[SCREAMING_SNAKE]` tokens and no bare `RATIFICATION_DATE` / `CONSTITUTION_VERSION`
|
||||
sentinels behind (a slot you deliberately leave open should still say so explicitly,
|
||||
not keep the template token).
|
||||
|
||||
@.claude/skills/speckit-constitution/SKILL.md
|
||||
|
||||
## Step 3 — Confirm
|
||||
|
||||
Confirm the constitution was written to `.specify/memory/constitution.md` and that
|
||||
no template placeholders remain. Remind the user it now **gates `/beacon.plan`** —
|
||||
`/speckit-plan`'s Constitution Check evaluates each plan against these articles.
|
||||
|
||||
`beacon doctor` will surface a `constitution` warning for as long as placeholder
|
||||
tokens remain, so a clean fill clears it.
|
||||
@@ -0,0 +1,114 @@
|
||||
---
|
||||
description: Determine the next BEACON step from repo state and run it. Proposes-then-confirms by default; pass --auto for unattended build-loop steps; pass --full-auto for fully autonomous operation (agent-trio resolves judgment calls via product+engineering subagents).
|
||||
argument-hint: [--auto | --full-auto] — --auto skips confirmations on reversible steps; --full-auto also dispatches /git:pr and uses agent-trio deliberation for all judgment calls
|
||||
allowed-tools: Bash, Read, Write, Edit, Glob, Grep, Task, TodoWrite
|
||||
---
|
||||
|
||||
You're advancing this project one BEACON step. First orient (reusing
|
||||
`/beacon.status`'s state-read verbatim — the decision ladder lives there, not
|
||||
here), then act on the recommendation.
|
||||
|
||||
## Parse arguments
|
||||
|
||||
The user's invocation:
|
||||
|
||||
$ARGUMENTS
|
||||
|
||||
- If `--full-auto` is present, you're in **full-auto mode** (see `## Act → --full-auto`).
|
||||
- If `--auto` is present (and not `--full-auto`), you're in **auto mode** (see `## Act → --auto`).
|
||||
- Otherwise you're in **default mode** — propose, then wait for a yes.
|
||||
|
||||
## Orient
|
||||
|
||||
@.claude/commands/beacon/status.md
|
||||
|
||||
Run that orientation now. It ends with a `Recommended next: <command>` line — that
|
||||
command is what you're about to act on. Don't re-derive the decision; reuse it.
|
||||
|
||||
## Act
|
||||
|
||||
### Default mode — propose, then confirm
|
||||
|
||||
1. State the recommended next step and a one-line rationale (pull both straight
|
||||
from the `/beacon.status` footer).
|
||||
2. Ask: **"Run this now?"** Wait for the user.
|
||||
3. On yes, dispatch it:
|
||||
- A `/beacon.*` or `/git:*` step → invoke that slash command.
|
||||
- A `beacon …` CLI step → run it via Bash.
|
||||
Then stop. One `/beacon.continue` = one step. The user re-runs to take the next.
|
||||
4. On no, stop without acting.
|
||||
|
||||
### --auto mode — unattended, for `/loop`
|
||||
|
||||
In `--auto` you act without asking, but **only on reversible, non-interactive
|
||||
build-loop steps**:
|
||||
|
||||
- ✅ Auto-dispatch: `/beacon.plan`, `/beacon.tasks`, `/beacon.implement`,
|
||||
`/beacon.review`, `/beacon.audit` (read-only — it only reports coverage gaps
|
||||
and suggests `beacon epic stub` lines; it creates nothing).
|
||||
- ⛔ **STOP and report** (these need a human or are outward-facing / hard to
|
||||
reverse — never fire them unattended): `/beacon.seed`, `/beacon.epics`,
|
||||
`beacon epic new`, `/beacon.specify` (someone must choose the feature),
|
||||
`/git:pr`, `/git:release`,
|
||||
`beacon epic finish`, any `beacon doctor` FAIL that needs a judgment call, or
|
||||
an `epic-dependency-gate` WARN (the current epic's dependency hasn't shipped
|
||||
yet — advance the blocking epic first, not this one).
|
||||
- Also stop when the recommendation is **"You're clear — nothing pending."**
|
||||
|
||||
When you stop, say plainly *why* and *what the human needs to do* — that message is
|
||||
the loop's output.
|
||||
|
||||
After auto-dispatching a step, re-run the **Orient** section and continue the loop
|
||||
until you hit a stop condition above. This makes `/loop 10m /beacon.continue --auto`
|
||||
safe: it drives the build loop forward and parks at every gate that wants a person.
|
||||
|
||||
### --full-auto mode — unattended with agent-trio self-governance
|
||||
|
||||
`--full-auto` is a superset of `--auto`. In this mode **the agent trio is the
|
||||
human**: you (the orchestrator) invoke `beacon-product` and `beacon-engineering`
|
||||
as independent subagents to resolve gates that `--auto` parks at — the same
|
||||
two-perspective deliberation a human product+engineering review would apply.
|
||||
|
||||
**Additional auto-dispatches beyond `--auto`:**
|
||||
|
||||
- ✅ `/git:pr` — open the PR without asking.
|
||||
- ✅ `beacon epic finish` — if `beacon epic status <slug>` confirms all specs
|
||||
are Done/Shipped, archive it. The condition is objective; no deliberation needed.
|
||||
- ✅ `epic-dependency-gate` WARN, `beacon epic new`, `/beacon.epics`,
|
||||
`/beacon.specify`, any `beacon doctor` FAIL needing a judgment call — resolve
|
||||
via **agent-trio deliberation** (see below).
|
||||
|
||||
**Agent-trio deliberation (for judgment gates):**
|
||||
|
||||
When you hit a gate requiring strategic or engineering judgment:
|
||||
|
||||
1. Invoke `beacon-product` and `beacon-engineering` as **independent** subagents
|
||||
with no shared context, scoped to the specific question. Frame it concretely:
|
||||
"Given the SEED artefacts, should we proceed with X or first address Y?"
|
||||
Their independence is what makes their agreement meaningful.
|
||||
2. Synthesise their verdicts inline:
|
||||
- Both aligned → **act**.
|
||||
- Either negative or conflicted → **STOP** and surface the disagreement
|
||||
exactly as `--auto` does: say why and what the human needs to resolve.
|
||||
|
||||
This mirrors `/beacon.align` but scoped to a single decision rather than a full
|
||||
project review.
|
||||
|
||||
**Hard stops (the only two that remain):**
|
||||
|
||||
- ⛔ `/git:release` — irreversible external side effect (published package/tag
|
||||
cannot be undone). Hard stop regardless of agent verdicts.
|
||||
- ⛔ **"You're clear — nothing pending."** — correct loop termination.
|
||||
|
||||
When you stop, say why and what the human needs to do, exactly as `--auto` does.
|
||||
|
||||
## Graceful degradation — SpecKit not installed
|
||||
|
||||
`/beacon.continue` and `/beacon.status` always install, but the
|
||||
`/beacon.{specify,plan,tasks,implement}` wrappers are SpecKit-gated. If the
|
||||
recommended step is one of those and the wrapper file is absent
|
||||
(`.claude/commands/beacon/<verb>.md` missing), either fall back to the raw
|
||||
`/speckit-<verb>` command if SpecKit's own skills are present, or tell the user:
|
||||
|
||||
> SpecKit isn't installed. Install it, then `beacon upgrade`, to enable the
|
||||
> `/beacon.*` spec wrappers. (See `beacon help commands`.)
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
description: Adversarial engineering review — challenges feasibility, missing prerequisites, dependency ordering, and scope. Use before building starts to stress-test the plan.
|
||||
argument-hint: (no args) full review | <epic-slug> scoped to one epic | scope decomposition + scope risks only
|
||||
allowed-tools: Bash, Read, Glob, Grep
|
||||
---
|
||||
|
||||
Invoke the `beacon-engineering` subagent to stress-test whether this project's plan is actually executable.
|
||||
|
||||
The argument is:
|
||||
|
||||
$ARGUMENTS
|
||||
|
||||
## Step 1 — Determine mode
|
||||
|
||||
Parse `$ARGUMENTS`:
|
||||
|
||||
- **Empty** → full engineering review (all four lenses)
|
||||
- **`scope`** → decomposition and scope sizing only: skip feasibility and dependency checks; focus on whether epics are independently shippable and specs are tracer-bullet sized
|
||||
- **Anything else** → treat as an epic slug and scope the review to that epic
|
||||
|
||||
## Step 2 — Invoke the subagent
|
||||
|
||||
Spawn the `beacon-engineering` subagent.
|
||||
|
||||
### Full review (no args)
|
||||
|
||||
No extra constraints. The subagent reads the full artefact tree and reports across all four lenses: feasibility, missing prerequisites, dependency ordering, and scope.
|
||||
|
||||
### Scoped review (`<epic-slug>`)
|
||||
|
||||
First verify the epic exists:
|
||||
|
||||
```bash
|
||||
test -f project-management/Roadmap/epics/<slug>.md || echo "NOT FOUND"
|
||||
```
|
||||
|
||||
If not found, stop:
|
||||
|
||||
```
|
||||
No epic found at project-management/Roadmap/epics/<slug>.md.
|
||||
Run `beacon epic list` to see available epics.
|
||||
```
|
||||
|
||||
If found, direct the subagent: scope the **Feasibility concerns** and **Dependency ordering problems** sections to `<epic-slug>` and its owned specs. The **Missing prerequisites** and **Scope risks** sections remain project-wide — a missing prerequisite might block multiple epics, not just the one in scope.
|
||||
|
||||
### Scope mode (`scope`)
|
||||
|
||||
Direct the subagent to skip the **Feasibility concerns**, **Missing prerequisites**, and **Dependency ordering problems** sections entirely. Focus only on **Scope risks** and **Recommended adjustments** — are epics independently shippable and specs tracer-bullet sized?
|
||||
|
||||
## Step 3 — Print the report
|
||||
|
||||
Print the subagent's output verbatim.
|
||||
|
||||
Then add one line:
|
||||
|
||||
> Re-run anytime: `/beacon.engineering`
|
||||
@@ -0,0 +1,94 @@
|
||||
---
|
||||
description: PM-guided epic decomposition — reads the filled SEED docs, proposes a sequenced set of epics, iterates with the user, then creates them.
|
||||
argument-hint: (no arguments — operates on the current project)
|
||||
allowed-tools: Bash, Read, Glob, Grep
|
||||
---
|
||||
|
||||
You're helping the user decompose their project into epics — the major initiatives that, together, move the problem statement to its success criteria. You come in right after SEED is green: the problem statement, architecture stub, and roadmap vision are filled in. Your job is to read those, take a senior PM perspective, propose a sequenced set of epics, and create them once the user confirms.
|
||||
|
||||
## Orientation (silently)
|
||||
|
||||
Before opening the conversation, read these three files in full:
|
||||
|
||||
- `project-management/Background/00-problem-statement.md`
|
||||
- `project-management/Background/01-final-architecture-document.md`
|
||||
- `project-management/Roadmap/README.md`
|
||||
|
||||
Then run:
|
||||
|
||||
```bash
|
||||
beacon seed
|
||||
beacon epic list
|
||||
```
|
||||
|
||||
- If `beacon seed` is not green (placeholders remain), stop and tell the user to run `/beacon.seed` first.
|
||||
- If epics already exist, list them and ask: "You already have epics — do you want to refine what's there, or add new ones?" Don't re-plan what's already planned.
|
||||
|
||||
## Conversation
|
||||
|
||||
### 1. One framing question (optional)
|
||||
|
||||
If the problem statement gives you enough to propose confidently, skip this and go straight to the proposal. Otherwise, ask **one** of:
|
||||
|
||||
- "Is this a v1 from scratch, or an iteration on something that exists today?"
|
||||
- "Any hard sequencing constraints — regulatory, dependency, or team availability?"
|
||||
- "Roughly what team size and timeline are you working with?"
|
||||
|
||||
Pick the one that would most change your proposal if the answer surprised you. Never ask more than one.
|
||||
|
||||
### 2. Propose epics
|
||||
|
||||
Work backwards from the success criteria: what must be true for each criterion to be met? Each major capability gap is a candidate epic.
|
||||
|
||||
Propose **3–6 epics** in suggested sequence. For each:
|
||||
|
||||
| Field | What to write |
|
||||
|---|---|
|
||||
| **Slug** | Short, lowercase, hyphenated — e.g. `auth`, `data-pipeline`, `admin-ui` |
|
||||
| **Title** | 4–8 words, concrete — the epic's deliverable in a phrase |
|
||||
| **Scope** | One sentence: what this epic delivers; what "done" looks like |
|
||||
| **Sequencing note** | Why this comes before/after its neighbours: dependency, risk, learning, or value-unlock |
|
||||
|
||||
Present them numbered in sequence order. End with:
|
||||
|
||||
> "Does this sequencing make sense? Anything to rename, split, merge, or reorder?"
|
||||
|
||||
### 3. Iterate
|
||||
|
||||
Adjust on feedback without over-justifying. Take the correction and move on. Common asks:
|
||||
|
||||
- **"Merge X and Y"** → combine into one epic; pick the better slug and title
|
||||
- **"Add one for Z"** → insert at the right sequence position with the same four fields
|
||||
- **"This is too big"** → split into two; each part must be independently shippable
|
||||
- **"Rename to W"** → update slug and title
|
||||
|
||||
When the user says "looks good" or equivalent, go to step 4.
|
||||
|
||||
### 4. Create the epics
|
||||
|
||||
Run `beacon epic new` for each confirmed epic in sequence order:
|
||||
|
||||
```bash
|
||||
beacon epic new <slug> --title "<title>"
|
||||
```
|
||||
|
||||
Then confirm they were all created:
|
||||
|
||||
```bash
|
||||
beacon epic list
|
||||
```
|
||||
|
||||
### 5. Tell the user what's next
|
||||
|
||||
> Epics created. Next:
|
||||
> - `/beacon.specify <epic> <feature>` to spec the first feature of your first epic (requires SpecKit), or
|
||||
> - `beacon bullet start "<title>" --epic <slug>` to start non-spec work on the first epic, or
|
||||
> - `/beacon.constitution` if you haven't filled it in yet — it gates the plan step.
|
||||
|
||||
## Tone constraints
|
||||
|
||||
- **PM perspective, not developer perspective.** Sequence by value delivery, risk, and dependencies — not by what's technically easiest to build first.
|
||||
- **One epic = one independently shippable initiative.** If an epic can't ship without another, make that dependency explicit in the sequencing note. Don't merge epics just to hide a dependency.
|
||||
- **No product-dogma jargon.** Ban SAM, TAM, ICP, PMF, "North Star", "OKR", "Jobs to be Done", "user persona archetype". Say what the epic does, not what framework it fits.
|
||||
- **Concrete beats generic.** "user-auth" beats "foundational-infrastructure". "data-pipeline" beats "platform-layer".
|
||||
- **Reflect the project's vocabulary.** If the problem statement says "pipeline", use "pipeline". Don't rename things to impose your own framing.
|
||||
@@ -0,0 +1,73 @@
|
||||
---
|
||||
description: Run SpecKit's implement command and refresh the parent epic's rollup when the spec completes.
|
||||
argument-hint: [spec-slug] — defaults to the current spec branch; pass NNN-slug to continue a sharded spec from a non-spec branch
|
||||
allowed-tools: Bash, Read, Write, Edit, TodoWrite, Glob, Grep
|
||||
---
|
||||
|
||||
## Resolve the target spec
|
||||
|
||||
A spec usually ships from its own `NNN-slug` branch, and with no argument this
|
||||
command operates on that current spec branch (SpecKit's own detection). But a
|
||||
large spec often shards across several PRs off `feature/`/`fix/` branches, each
|
||||
ticking a slice of the *same* `tasks.md` (see `/beacon.status`'s decision ladder).
|
||||
To continue such a spec from a non-spec branch, pass its slug:
|
||||
|
||||
```
|
||||
/beacon.implement 005-memgraph-storage-substrate
|
||||
```
|
||||
|
||||
The target spec is, in order: the `$ARGUMENTS` slug if given; else the spec named
|
||||
by the current spec branch; else the `specs/NNN-*` folder matching a leading
|
||||
`NNN-` token in the current branch name. Operate on **that** spec's
|
||||
`specs/<slug>/tasks.md` throughout — the TDD discipline below and the rollup
|
||||
refresh after both key off the resolved `<slug>`, not the branch name.
|
||||
|
||||
## Before you start — BEACON TDD commit discipline
|
||||
|
||||
SpecKit executes tasks in order but says nothing about commit boundaries, so an
|
||||
agent can write a story's tests *and* implementation in one pass and commit them
|
||||
together — which means the tests never went red and can only confirm the code's
|
||||
*current* behaviour, not catch its absence. Don't do that. For every TDD pair in
|
||||
`tasks.md` (the `-T` / `-I` convention `/beacon.tasks` emits):
|
||||
|
||||
1. **Red.** Implement the `-T` task only — write the test, run it, watch it
|
||||
**fail** for the right reason. Commit that on its own:
|
||||
`git commit -m "test(US1): failing test for column profile (T010-T)"`.
|
||||
2. **Green.** Implement the `-I` task until the `-T` test passes. Commit
|
||||
separately: `git commit -m "feat(US1): column profile (T010-I)"`.
|
||||
3. **Refactor** under green, committing as needed.
|
||||
|
||||
Never let a single commit add a brand-new test file *and* a brand-new source
|
||||
file — that's exactly the no-red-phase pattern `beacon doctor`'s
|
||||
`tdd-commit-discipline` check flags (FAIL under `--strict`). If the spec has
|
||||
`.feature` files (scaffolded by `/beacon.tasks` by default), the `-T` step is "make this
|
||||
scenario pass"; `spec-bdd-coverage` confirms every spec.md Given/When/Then has a
|
||||
witness.
|
||||
|
||||
## SpecKit's implement skill (inlined verbatim)
|
||||
|
||||
@.claude/skills/speckit-implement/SKILL.md
|
||||
|
||||
## After implementation — BEACON rollup refresh
|
||||
|
||||
Once SpecKit reports the tasks are complete (all `[ ]` in `tasks.md` flipped to `[x]`):
|
||||
|
||||
1. **Find the parent epic.** Read `specs/<NNN-slug>/.beacon.toml`:
|
||||
```bash
|
||||
cat specs/<NNN-slug>/.beacon.toml
|
||||
```
|
||||
The file contains `epic = "<slug>"`. If it's missing, the spec was never backlinked — run `beacon link-spec <NNN-slug> --epic <slug>` first.
|
||||
|
||||
2. **Recompute the rollup.**
|
||||
```bash
|
||||
beacon epic refresh <epic-slug>
|
||||
```
|
||||
This prints owned-spec counts (complete / in flight / missing) and flags if the epic is ready to archive.
|
||||
|
||||
3. **If `beacon epic refresh` reports all owned specs complete**, prompt the user to archive the epic once the spec branch merges to develop / main:
|
||||
```bash
|
||||
beacon epic finish <epic-slug>
|
||||
```
|
||||
(`epic finish` refuses while any owned spec branch is still live — it's safe to suggest.)
|
||||
|
||||
This closes the Engineering→Product status loop: as soon as the last spec ships, Product sees the rollup and can sign the epic off without needing a meeting.
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
description: Run SpecKit's plan command, then validate placeholders + ADR references in the generated plan.
|
||||
argument-hint: (no arguments — operates on the current spec branch)
|
||||
allowed-tools: Bash, Read, Write, Edit, Glob, Grep
|
||||
---
|
||||
|
||||
## SpecKit's plan skill (inlined verbatim)
|
||||
|
||||
@.claude/skills/speckit-plan/SKILL.md
|
||||
|
||||
## After plan.md is generated — BEACON post-checks
|
||||
|
||||
Run the deterministic validator (don't hand-roll a grep — the patterns drift and
|
||||
unanchored ones false-positive on prose like "the TODO this spec retires"):
|
||||
|
||||
```bash
|
||||
beacon plan validate <NNN-slug> # defaults to the current spec branch if omitted
|
||||
```
|
||||
|
||||
It performs two machine-truth checks over `specs/<NNN-slug>/plan.md`:
|
||||
|
||||
1. **Placeholder sweep.** Anchored template markers only — `[REPLACE WITH ...]` /
|
||||
`[REPLACE_WITH_*]`, `<TBD>` / `<TODO>`, `[NEEDS CLARIFICATION: …]`, and the
|
||||
template literals `[FEATURE NAME]` / `[DATE]` / `[###-feature-name]`. A bare word
|
||||
in running prose never matches.
|
||||
2. **ADR existence.** Every `ADR-NNN` reference (bare or inside a Markdown link) is
|
||||
resolved against `project-management/ADRs/ADR-NNN-*.md` via `pathlib` — no shell
|
||||
glob, no quoting drift.
|
||||
|
||||
The command exits non-zero and prints each hit with `file:line` context. Surface that
|
||||
output to the user. Non-blocking — the user decides whether to address before
|
||||
`/beacon.tasks`, but a clean run is the bar.
|
||||
|
||||
If plan.md references a decision that should have been an epic-level ADR (i.e. a choice that constrains more than one spec) but isn't yet captured, note that too — the user may want to add the ADR to the parent epic's `## ADRs` section.
|
||||
@@ -0,0 +1,197 @@
|
||||
---
|
||||
description: Bidirectional PRD bridge — export a PRD from a BEACON epic, or import an existing PRD to scaffold the epic, problem statement, and ADR stubs.
|
||||
argument-hint: <epic-slug> — export | import <path> — ingest an existing PRD
|
||||
allowed-tools: Bash, Read, Write, Edit
|
||||
---
|
||||
|
||||
You're operating the BEACON ↔ PRD bridge. The user's invocation:
|
||||
|
||||
$ARGUMENTS
|
||||
|
||||
## Step 1 — Determine mode
|
||||
|
||||
- If the first token of `$ARGUMENTS` is **`import`**, the rest is a file path: go to **[Import mode](#import-mode)**.
|
||||
- Otherwise, treat the entire argument as an **epic slug**: go to **[Export mode](#export-mode)**.
|
||||
|
||||
---
|
||||
|
||||
## Export mode
|
||||
|
||||
Generate a PRD markdown file from an existing BEACON epic.
|
||||
|
||||
### Step E1 — Load the epic
|
||||
|
||||
Read `project-management/Roadmap/epics/<slug>.md`. If it doesn't exist, STOP:
|
||||
|
||||
```
|
||||
No epic found at project-management/Roadmap/epics/<slug>.md.
|
||||
Create it first: beacon epic new <slug> --title "<title>"
|
||||
```
|
||||
|
||||
Extract from the epic file:
|
||||
- **Title** (from `# Epic: <Title>`)
|
||||
- **Status** (from `## Status` line)
|
||||
- **Why now** (full `## Why now` body)
|
||||
- **Success criteria** (bullet list under `## Success criteria`)
|
||||
- **Non-goals** (body under `## Non-goals`)
|
||||
- **Spec paths** (bullet list under `## Specs`)
|
||||
- **ADR paths** (bullet list under `## ADRs`)
|
||||
- **Notes** (body under `## Notes`, if present)
|
||||
|
||||
### Step E2 — Load supporting artefacts
|
||||
|
||||
1. **Problem statement** — read `project-management/Background/00-problem-statement.md`. If absent, skip gracefully (leave the PRD section blank with a note).
|
||||
2. **ADR files** — for each path listed in the epic's `## ADRs`, read the file and extract its title, status, and the one-sentence decision under `## Decision`.
|
||||
3. **Spec rollup** — run:
|
||||
```bash
|
||||
beacon epic list --detailed
|
||||
```
|
||||
Find this epic's row and note shipped / in-flight / missing counts.
|
||||
|
||||
### Step E3 — Check for a custom template
|
||||
|
||||
Check whether `.beacon/prd-template.md` exists:
|
||||
```bash
|
||||
test -f project-management/.beacon/prd-template.md && echo found
|
||||
```
|
||||
If found, read it and use its section structure (preserving any `{{PLACEHOLDER}}` instructions in it as prompts to yourself). Otherwise use the default structure below.
|
||||
|
||||
### Step E4 — Write the PRD
|
||||
|
||||
Write `project-management/Roadmap/epics/<slug>-prd.md` with this structure (default, no custom template):
|
||||
|
||||
```markdown
|
||||
# PRD: <Epic Title>
|
||||
|
||||
**Status:** <epic status> | **Epic:** `epics/<slug>.md` | **Generated:** <today's date>
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
<2–3 sentences: why this initiative exists now, what success looks like, and how it fits the current quarter's strategy. Synthesise from "Why now" + success criteria.>
|
||||
|
||||
## Problem Statement
|
||||
|
||||
<Synthesised from 00-problem-statement.md: core problem, target user, current pain, constraints. If the file was absent, note: "Problem statement not yet authored — run beacon seed.">
|
||||
|
||||
## Goals
|
||||
|
||||
<Success criteria from the epic, formatted as measurable outcomes.>
|
||||
|
||||
## Non-Goals
|
||||
|
||||
<Non-goals from the epic verbatim.>
|
||||
|
||||
## Scope & Delivery
|
||||
|
||||
| Spec | State |
|
||||
|---|---|
|
||||
<One row per spec path. State = Shipped / In flight / Planned (from rollup). If no specs listed, note "No specs created yet.">
|
||||
|
||||
## Key Decisions
|
||||
|
||||
<One subsection per ADR:>
|
||||
### <ADR title>
|
||||
**Status:** <ADR status> | **File:** `<ADR path>`
|
||||
<One-sentence summary of the decision.>
|
||||
|
||||
## Success Metrics
|
||||
|
||||
<Restate the epic's success criteria as testable, measurable outcomes — add any quantitative framing that can be inferred from the problem statement.>
|
||||
|
||||
## Open Questions
|
||||
|
||||
<Notes section from the epic, if present. If empty or absent, omit this section.>
|
||||
```
|
||||
|
||||
### Step E5 — Report
|
||||
|
||||
Print:
|
||||
```
|
||||
PRD written → project-management/Roadmap/epics/<slug>-prd.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Import mode
|
||||
|
||||
Ingest an existing PRD and scaffold the BEACON artefacts from it.
|
||||
|
||||
### Step I1 — Read the PRD
|
||||
|
||||
Read the file at the path given after `import`. If it doesn't exist, STOP with a clear error.
|
||||
|
||||
### Step I2 — Extract structured content
|
||||
|
||||
From the PRD, identify and extract (best-effort — PRD formats vary):
|
||||
|
||||
| PRD concept | Maps to |
|
||||
|---|---|
|
||||
| Document title / product name | Epic title → derive kebab-case slug |
|
||||
| Problem / background / executive summary | `00-problem-statement.md` — core problem + target user |
|
||||
| Goals / success criteria | Epic `## Success criteria` |
|
||||
| Non-goals / out of scope | Epic `## Non-goals` |
|
||||
| Strategic context / why now / motivation | Epic `## Why now` |
|
||||
| Features / requirements / scope items | Suggested spec slugs (list only — do not create) |
|
||||
| Architecture / technical decisions | ADR stubs |
|
||||
| Constraints / timeline | Epic `## Notes` |
|
||||
|
||||
Derive a kebab-case **epic slug** from the title (e.g. "User Authentication v2" → `user-authentication-v2`).
|
||||
|
||||
### Step I3 — Check for collisions
|
||||
|
||||
Before writing anything:
|
||||
- Does `project-management/Roadmap/epics/<slug>.md` already exist? If yes, STOP and tell the user:
|
||||
```
|
||||
Epic <slug> already exists. Choose a different slug or edit the epic manually.
|
||||
```
|
||||
- Does `project-management/Background/00-problem-statement.md` exist and contain non-placeholder content? If yes, do NOT overwrite — instead append an `## Imported from PRD` section at the bottom and note the merge to the user.
|
||||
|
||||
### Step I4 — Scaffold BEACON artefacts
|
||||
|
||||
**1. Problem statement**
|
||||
|
||||
Write (or append to) `project-management/Background/00-problem-statement.md` with the extracted problem, target user, success criteria, and constraints.
|
||||
|
||||
**2. Epic**
|
||||
|
||||
```bash
|
||||
beacon epic new <slug> --title "<Title>"
|
||||
```
|
||||
|
||||
Then open `project-management/Roadmap/epics/<slug>.md` and fill in:
|
||||
- `## Why now` — from the PRD's strategic context
|
||||
- `## Success criteria` — from the PRD's goals
|
||||
- `## Non-goals` — from the PRD's out-of-scope section
|
||||
- `## Notes` — constraints, timeline notes, any open questions from the PRD
|
||||
|
||||
**3. ADR stubs** (one per identifiable technical decision in the PRD)
|
||||
|
||||
Find the next available ADR number:
|
||||
```bash
|
||||
ls project-management/ADRs/ADR-*.md 2>/dev/null | sort | tail -1
|
||||
```
|
||||
|
||||
For each decision, write `project-management/ADRs/ADR-NNN-<decision-slug>.md` using the MADR template. Set `Status: Proposed`. Populate `## Context` from the PRD text; leave `## Decision` and `## Consequences` as prompts for the team to complete.
|
||||
|
||||
Add each ADR path to the epic's `## ADRs` section.
|
||||
|
||||
### Step I5 — Report
|
||||
|
||||
Print a structured summary:
|
||||
|
||||
```
|
||||
BEACON artefacts scaffolded from PRD:
|
||||
|
||||
✓ project-management/Background/00-problem-statement.md (written|appended)
|
||||
✓ project-management/Roadmap/epics/<slug>.md
|
||||
✓ project-management/ADRs/ADR-NNN-<slug>.md (N stub(s))
|
||||
|
||||
Suggested specs — run /beacon.specify <slug> <feature> for each:
|
||||
• <feature 1 from PRD scope>
|
||||
• <feature 2>
|
||||
…
|
||||
|
||||
Next step: /beacon.specify <epic-slug> "<first feature>"
|
||||
```
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
description: Strategic product review — reads the full artefact tree and reports whether active work tracks toward the stated goals and quarter vision. Use when in doubt about direction, sequencing, or scope.
|
||||
argument-hint: (no args) full review | <epic-slug> scoped to one epic | steer forward-looking only
|
||||
allowed-tools: Bash, Read, Glob, Grep
|
||||
---
|
||||
|
||||
Invoke the `beacon-product` subagent to assess whether this project's active work is tracking toward its stated goals.
|
||||
|
||||
The argument is:
|
||||
|
||||
$ARGUMENTS
|
||||
|
||||
## Step 1 — Determine mode
|
||||
|
||||
Parse `$ARGUMENTS`:
|
||||
|
||||
- **Empty** → full review (default)
|
||||
- **`steer`** → forward-looking only: sequencing + horizon risk + next action (skip backward-looking sections)
|
||||
- **Anything else** → treat as an epic slug and scope the grounding + sequencing checks to that epic
|
||||
|
||||
## Step 2 — Invoke the subagent
|
||||
|
||||
Spawn the `beacon-product` subagent.
|
||||
|
||||
### Full review (no args)
|
||||
|
||||
Give the subagent no extra constraints. It will read the full artefact tree and report across all four lenses (grounding, sequencing, momentum, horizon risk).
|
||||
|
||||
### Scoped review (`<epic-slug>`)
|
||||
|
||||
First verify the epic exists:
|
||||
|
||||
```bash
|
||||
test -f project-management/Roadmap/epics/<slug>.md || echo "NOT FOUND"
|
||||
```
|
||||
|
||||
If not found, stop:
|
||||
|
||||
```
|
||||
No epic found at project-management/Roadmap/epics/<slug>.md.
|
||||
Run `beacon epic list` to see available epics.
|
||||
```
|
||||
|
||||
If found, direct the subagent: focus the **Grounding gaps** and **Sequencing risks** sections on `<epic-slug>` and its owned specs only. The **Momentum blockers**, **Horizon risk**, and **Recommended next action** sections remain project-wide.
|
||||
|
||||
### Steer mode (`steer`)
|
||||
|
||||
Direct the subagent to skip the **Grounding gaps** and **Momentum blockers** sections entirely. Focus on: **Sequencing risks**, **Horizon risk**, and **Recommended next action** — the forward-looking view of what to do next and what threatens the quarter vision.
|
||||
|
||||
## Step 3 — Print the report
|
||||
|
||||
Print the subagent's output verbatim.
|
||||
|
||||
Then add one line:
|
||||
|
||||
> Re-run anytime: `/beacon.product`
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
description: Adversarial review of the current diff against the parent epic + spec, in a fresh subagent context. Use before opening a PR.
|
||||
argument-hint: (no arguments — reviews HEAD vs the integration branch)
|
||||
allowed-tools: Task
|
||||
---
|
||||
|
||||
Use the **beacon-reviewer** subagent to review the current diff.
|
||||
|
||||
The reviewer runs in a fresh context — it sees the diff, the spec, and the parent epic, but none of the reasoning that produced the change. That independence is the point: it evaluates the work on its own terms.
|
||||
|
||||
Tell the subagent: *"Review the current diff for BEACON correctness. Report only gaps tied to spec tasks, epic Success criteria, Non-goals, or linked ADRs — not style or hypothetical edges."*
|
||||
|
||||
Return the subagent's report verbatim. Don't summarise; the user will read it directly.
|
||||
|
||||
If the report ends with `Status: clear`, you're done. Otherwise the user decides which findings to address.
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
description: Refresh ROADMAP.md from live BEACON data, summarise epic health, and suggest next steps. Pass `render` to also produce ROADMAP.html.
|
||||
argument-hint: [render] — also produce ROADMAP.html
|
||||
allowed-tools: Bash, Read
|
||||
---
|
||||
|
||||
You're refreshing the BEACON roadmap and summarising the current state.
|
||||
|
||||
The user's invocation:
|
||||
|
||||
$ARGUMENTS
|
||||
|
||||
## Step 1 — Regenerate
|
||||
|
||||
Run:
|
||||
```bash
|
||||
beacon roadmap export
|
||||
```
|
||||
|
||||
This includes the most-recently-completed epics by default (controlled by `roadmap_done_limit` in the manifest, defaulting to 3). Pass `--exclude-done` to suppress Done/archived epics.
|
||||
|
||||
If the user passed `render` as an argument (i.e. `$ARGUMENTS` contains the word `render`), also run:
|
||||
```bash
|
||||
beacon roadmap render
|
||||
```
|
||||
|
||||
## Step 2 — Summarise
|
||||
|
||||
Read the just-written `ROADMAP.md` and report concisely:
|
||||
|
||||
- **Epic counts** by status: Active / Planning / Paused / Done (including any archived epics shown)
|
||||
- **Fidelity** for each epic — show the badge (`S+/S? A+/A? T:N%`) and flag any that are `S?` (no specs) or `A?` (no ADRs)
|
||||
- **In-flight bullets** — how many worktrees have active bullets and which epics they're attached to
|
||||
- **Roadmap coverage** — fraction of Active epics that have both specs and ADRs filled in
|
||||
|
||||
Keep the summary to a tight table or bullet list — this is a health snapshot, not a narrative.
|
||||
|
||||
## Step 3 — Next steps
|
||||
|
||||
Close with 2–4 concrete suggestions ranked by impact:
|
||||
|
||||
| Condition | Suggestion |
|
||||
|---|---|
|
||||
| Epic in Planning for more than one sprint with no specs | Mark Active or archive: `beacon epic archive <slug>` |
|
||||
| Active epic with no specs | `/beacon.specify <slug> "<first feature>"` |
|
||||
| Active epic with 0% task completion | Investigate — may need `/beacon.plan` |
|
||||
| No epics at all | Start the first initiative: `beacon epic new <slug> --title "…"` |
|
||||
| ROADMAP.html not requested but HTML would be useful | Mention `beacon roadmap render` |
|
||||
|
||||
Give at most the top 3 items; don't enumerate every epic if the list is long.
|
||||
@@ -0,0 +1,123 @@
|
||||
---
|
||||
description: Inferential SEED-phase scaffold — has a conversation with the user, infers the template fields, presents them back for confirmation, then applies. Plan-mode style.
|
||||
argument-hint: (no arguments — operates on the current project)
|
||||
allowed-tools: Bash, Read, Edit, Glob, Grep
|
||||
---
|
||||
|
||||
You're helping the user fill in BEACON's SEED-phase templates. The user invoked this command because the CLI's `beacon seed scaffold` is too literal — they want a conversation, not a form.
|
||||
|
||||
## Files in scope
|
||||
|
||||
- `project-management/Background/00-problem-statement.md` — the gate. Has placeholder tokens like `[Replace with your problem statement]`, `[Specific person, role, or team — not "users in general"]`, `[When and where they encounter this problem]`, `[What they do today and why it falls short]`, `[Outcome 1 — e.g. "..."]`, `[Outcome 2]`, `[Outcome 3]`, `[scope limit 1/2/3]`, `[One paragraph on the business or human impact...]`, `[Technical, organisational, or timeline constraints...]`.
|
||||
- `project-management/Background/01-final-architecture-document.md` — has `[Project Name]` in several places (diagram titles).
|
||||
- `project-management/Roadmap/README.md` — has `[Project Name]` and `[Replace with your project's vision statement.]`.
|
||||
|
||||
Each of those files also has `YYYY-MM-DD` markers in the footer that should be stamped with today's date.
|
||||
|
||||
## Conversation flow — plan-mode style
|
||||
|
||||
### 1. Orient (silently)
|
||||
|
||||
Before opening the conversation, run:
|
||||
|
||||
```bash
|
||||
beacon seed
|
||||
```
|
||||
|
||||
Read the three files. Note which placeholders are still in place — only ask about those. If everything's already filled in, tell the user and stop.
|
||||
|
||||
### 2. Open with one or two broad questions
|
||||
|
||||
Not a form. Start with something like:
|
||||
|
||||
> "Tell me about this project. What is it, who's it for, and what's broken about today?"
|
||||
|
||||
Listen. Take notes. If the user gives you a rich paragraph, you have most of what you need to infer. If they give a sentence, ask one tight follow-up — e.g. "And who's actually hitting that today?" or "When does that bite them?".
|
||||
|
||||
Optionally a second broad question for the year-ahead view:
|
||||
|
||||
> "Looking ahead a year — what does 'winning' look like?"
|
||||
|
||||
### 3. Infer the template fields
|
||||
|
||||
From the user's answers, draft concrete values for **every** template slot. Don't pad with filler — but DO infer. The user said this should feel like Claude in plan mode: take a position, then ask "is this right?"
|
||||
|
||||
Slots to infer:
|
||||
|
||||
| Slot | Source for inference |
|
||||
|---|---|
|
||||
| Project name | Direct ask if not implied; otherwise pick a codename from the user's language. |
|
||||
| Core problem (one sentence) | The user's first answer, distilled to one sentence. |
|
||||
| Who (target user) | The role/team/persona the user mentioned. Be specific — "platform engineers responsible for X", not "developers". |
|
||||
| Context (when/where) | The moment/scenario the user described. |
|
||||
| Current pain (what they do today) | Today's workaround + why it falls short. |
|
||||
| Success criteria (1–3 measurable items) | Outcomes implied by the user's framing. Make them measurable — "<5s response", "<1 page error message", "new engineer self-serves on day one". |
|
||||
| Non-goals (1–3) | Things the user implied are out of scope, OR adjacent things you'd guess to call out explicitly. |
|
||||
| Why this matters | One sentence on the impact — what becomes possible / different. |
|
||||
| Constraints | Tech/org/timeline bounds the user mentioned. If none mentioned, write "(none called out yet)". |
|
||||
| Roadmap vision (paragraph) | The user's year-ahead answer, sharpened into a paragraph. |
|
||||
|
||||
### 4. Present the inference back
|
||||
|
||||
Show the user every inferred value in a clear structure (table or bullet list). Use their exact phrasing where you can; only rewrite for compression and clarity.
|
||||
|
||||
End with: **"Does this look right? Anything to refine before I write it in?"**
|
||||
|
||||
### 5. Iterate
|
||||
|
||||
If the user pushes back on a slot, adjust. Don't argue or over-justify. Take the correction and move on. If they say "looks good," go to step 6.
|
||||
|
||||
### 6. Apply
|
||||
|
||||
Edit the three files with the confirmed values. For each file:
|
||||
|
||||
- **Problem statement** (`project-management/Background/00-problem-statement.md`):
|
||||
- Replace each `[…]` placeholder with the corresponding confirmed value.
|
||||
- For Success Criteria: if 1 outcome, leave only `- [ ] <outcome 1>` and remove the `- [ ] [Outcome 2]` and `- [ ] [Outcome 3]` lines. If 2 outcomes, leave two `- [ ] <outcome>` lines. If 3+, leave three.
|
||||
- For Non-Goals: same logic — render `1. NOT <item>`, `2. NOT <item>`, … and remove unused numbered lines.
|
||||
- Stamp `YYYY-MM-DD` → today's date (both `_Created:_` and `_Last updated:_`).
|
||||
- **Architecture** (`01-final-architecture-document.md`):
|
||||
- Replace every `[Project Name]` with the confirmed project name.
|
||||
- Stamp `YYYY-MM-DD` → today's date in the footer.
|
||||
- **Roadmap** (`Roadmap/README.md`):
|
||||
- Replace `[Project Name]` with the project name.
|
||||
- Replace `[Replace with your project's vision statement.]` with the confirmed vision paragraph.
|
||||
- Stamp the `**Last reviewed:** YYYY-MM-DD` header (and the footer line) with today's date.
|
||||
|
||||
### 7. Verify
|
||||
|
||||
After applying, run:
|
||||
|
||||
```bash
|
||||
beacon seed
|
||||
```
|
||||
|
||||
The first three checks (`problem-statement`, `architecture`, `roadmap-vision`) should all be OK. `roadmap-staleness` should be OK too because you stamped the review date.
|
||||
|
||||
If `beacon seed` still shows FAIL or WARN, read the message — there's a placeholder you missed. Find it, fix it, run `beacon seed` again.
|
||||
|
||||
### 8. Fill the constitution
|
||||
|
||||
Check for a constitution stub:
|
||||
|
||||
```bash
|
||||
test -f .specify/memory/constitution.md && echo "present" || echo "absent"
|
||||
```
|
||||
|
||||
- **absent** — skip. Remind the user that `/beacon.constitution` is available once the spec workflow is initialised (`specify init && beacon upgrade`).
|
||||
- **present** — the Background docs are freshly filled and are the best seed material the constitution will ever have. Continue with `/beacon.constitution` now (no extra arguments needed — it reads the Background docs you just wrote).
|
||||
|
||||
## Tone constraints
|
||||
|
||||
- **No Product-dogma jargon.** Strict ban on SAM, TAM, ICP, PMF, "North Star metric", "Jobs to be Done", "user persona archetype". BEACON's voice is practical and opinionated, not Lean Startup.
|
||||
- **No filler.** If the user's answer doesn't give you enough to infer a slot, ask one tight follow-up. Don't invent.
|
||||
- **Reflect the user's voice, sharpened.** Your job is to compress and clarify what they said — not to decide what they meant.
|
||||
- **Concrete beats generic.** "Platform engineers responsible for data-product reliability at a mid-size SaaS" beats "users".
|
||||
|
||||
## After SEED is green
|
||||
|
||||
Tell the user:
|
||||
|
||||
> SEED is filled in. Next:
|
||||
> - `/beacon.epics` to plan and create your initiatives, or
|
||||
> - `beacon doctor` to confirm everything's green across the project.
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
description: Create a SpecKit spec inside a BEACON epic, with cross-spec context auto-injected and the epic↔spec backlink written.
|
||||
argument-hint: <epic-slug> <feature description> — e.g. "user-auth OAuth login via Google"
|
||||
allowed-tools: Bash, Read, Write, Edit, Glob
|
||||
---
|
||||
|
||||
You're creating a SpecKit spec inside a BEACON epic. The user's invocation:
|
||||
|
||||
$ARGUMENTS
|
||||
|
||||
## Step 1 — Parse arguments
|
||||
|
||||
The first whitespace-separated token is the **epic slug**. Everything after is the **feature description**.
|
||||
|
||||
Verify the epic exists at `project-management/Roadmap/epics/<slug>.md`. If it doesn't, STOP and tell the user to create it first:
|
||||
|
||||
```
|
||||
beacon epic new <slug> --title "<title>"
|
||||
```
|
||||
|
||||
Epic creation is a BEACON DESIGN-phase activity — the cross-spec ADRs come out of that step, and the spec you're about to create should reference them.
|
||||
|
||||
## Step 2 — Load epic context
|
||||
|
||||
Read the epic file. Extract:
|
||||
|
||||
- **Why now** — strategic context
|
||||
- **Success criteria** — what "done" looks like at the epic level
|
||||
- **Non-goals** — what's explicitly out of scope
|
||||
- **ADRs** — cross-spec architectural decisions that constrain this spec
|
||||
|
||||
Carry these into the spec you generate (Step 3). The spec's own Non-goals MUST include the epic's Non-goals; the spec's Success criteria MUST ladder up to the epic's Success criteria. The spec's design choices MUST be consistent with the listed ADRs.
|
||||
|
||||
## Step 3 — Run SpecKit's specify command
|
||||
|
||||
SpecKit's specify skill is inlined verbatim below. Apply it to the **feature description only** (not the epic slug), with the epic context from Step 2 already in mind.
|
||||
|
||||
@.claude/skills/speckit-specify/SKILL.md
|
||||
|
||||
## Step 4 — Backlink the new spec to the epic
|
||||
|
||||
Once SpecKit's command has created `specs/<NNN-slug>/` and you know the new slug:
|
||||
|
||||
```bash
|
||||
beacon link-spec <NNN-slug> --epic <epic-slug>
|
||||
```
|
||||
|
||||
This writes `specs/<NNN-slug>/.beacon.toml` (BEACON's spec→epic backlink, owned by BEACON — SpecKit ignores it) and adds the spec to the epic's `## Specs` section. `beacon doctor` will confirm with `spec-backlink-integrity: All N spec folder(s) backlink an epic.`
|
||||
|
||||
If you skip this step, `beacon doctor` will WARN until the user runs `beacon link-spec` manually — `/beacon.specify` exists so they don't have to.
|
||||
|
||||
## Step 5 — Validate the generated spec
|
||||
|
||||
Run the deterministic spec validator (same machine-truth checks as `/beacon.plan`, so results are reproducible across agents):
|
||||
|
||||
```bash
|
||||
beacon spec validate <NNN-slug> # defaults to the current spec branch if omitted
|
||||
```
|
||||
|
||||
It scans `specs/<NNN-slug>/spec.md` for leftover `[NEEDS CLARIFICATION: …]` and other anchored template placeholders (`[REPLACE WITH ...]`, `<TBD>` / `<TODO>`, `[FEATURE NAME]` / `[DATE]` / `[###-feature-name]`), and resolves any `ADR-NNN` references against `project-management/ADRs/`. It exits non-zero and prints each hit with `file:line` context. A `[NEEDS CLARIFICATION:` marker means the spec still has an open question the user must resolve before `/beacon.plan`. Surface the output; a clean run is the bar.
|
||||
@@ -0,0 +1,107 @@
|
||||
---
|
||||
description: Read the repo's BEACON state — current phase, health, active bullet/epic — and report the single recommended next step. Consumed by /beacon.continue.
|
||||
argument-hint: (no arguments — operates on the current worktree)
|
||||
allowed-tools: Bash, Read, Glob, Grep
|
||||
---
|
||||
|
||||
You're reporting where this project sits in the BEACON lifecycle
|
||||
(SEED → DESIGN → BUILD → SHIP) and what the single next step is. You don't *do*
|
||||
the next step — you name it. `/beacon.continue` consumes this report to act.
|
||||
|
||||
Compose the status primitives BEACON already ships; don't reimplement their
|
||||
logic. Tolerate non-zero exits (they're signal, not failure).
|
||||
|
||||
## Orientation
|
||||
|
||||
Run these read-only probes and read their output:
|
||||
|
||||
1. **Git position.**
|
||||
```bash
|
||||
git rev-parse --abbrev-ref HEAD # current branch
|
||||
git status --porcelain # dirty? (uncommitted work)
|
||||
git log --oneline -8 # recent history
|
||||
# PR-merged probe: 0 == this branch already landed on the trunk upstream.
|
||||
git merge-base --is-ancestor HEAD origin/main; echo "merged=$?"
|
||||
```
|
||||
|
||||
2. **SEED gate.** `beacon seed` — FAILs while the problem statement / architecture
|
||||
/ roadmap still carry template placeholders. Green means SEED is signed off.
|
||||
|
||||
3. **Health.** `beacon doctor` — the 15 semantic checks. Note every `FAIL` and
|
||||
`WARN` by name (e.g. `epic-adr-coverage`, `tdd-commit-discipline`). The summary
|
||||
footer reads `ok=N warn=N fail=N`.
|
||||
|
||||
4. **Active bullet.** `beacon bullet status` — the current worktree's bullet, or a
|
||||
note that none is started. `beacon bullet list` if you want the cross-branch view.
|
||||
|
||||
5. **Epics + rollup.** `beacon epic list --detailed` — active epics and their
|
||||
owned-spec rollup (complete / in flight / missing).
|
||||
|
||||
6. **Spec context.** Find the spec this branch is working, then inspect its tasks.
|
||||
Two ways a branch points at a spec:
|
||||
- **Spec branch** (`NNN-slug`, e.g. `003-table-profiler`) — the spec is
|
||||
`specs/<branch>/`.
|
||||
- **Non-spec continuation branch** (`feature/`/`fix/`/… on a sharded spec) — a
|
||||
large spec often ships across several PRs off `feature/` branches, each
|
||||
ticking a slice of the *same* `tasks.md`. Resolve the spec the active bullet
|
||||
(step 4) is continuing, in this order, stopping at the first that names exactly
|
||||
one spec: (a) a leading `NNN-` token in the branch name matched to a
|
||||
`specs/NNN-*` folder (e.g. `feature/005-phase4-vector-ops` → `specs/005-…`);
|
||||
else (b) the bullet's `--epic` whose rollup (step 5) lists a single in-flight
|
||||
spec; else (c) a spec slug named in the bullet title. If none resolves a
|
||||
single spec, there's no spec continuation — fall through to the plain non-spec
|
||||
bullet rows.
|
||||
|
||||
For whichever spec resolved, inspect `specs/<slug>/`:
|
||||
- Is there a `spec.md`? a `plan.md`? a `tasks.md`?
|
||||
- In `tasks.md`, are there active `[ ]` / in-progress `[~]` task lines, is
|
||||
everything `[x]`, or are the only leftovers deferred `[-]` / `[d]` follow-ups
|
||||
(out of this bullet's scope — shippable, not work in flight)?
|
||||
|
||||
7. **SpecKit presence.** Check whether `.claude/skills/speckit-specify/SKILL.md`
|
||||
(or `.claude/commands/beacon/specify.md`) exists. When absent, the
|
||||
`/beacon.{specify,plan,tasks,implement}` wrappers aren't installed and the next
|
||||
step must fall back to raw `/speckit-*` or a "install SpecKit + `beacon upgrade`"
|
||||
nudge.
|
||||
|
||||
## Phase + next-step decision
|
||||
|
||||
Walk this ladder top-to-bottom and stop at the **first** row that matches. That
|
||||
row is the recommendation. (This mirrors the phase gates in `beacon help phases`.)
|
||||
|
||||
| State | Phase | Recommended next step |
|
||||
|---|---|---|
|
||||
| No BEACON manifest (`project-management/.beacon/init-options.json` absent) | — | `beacon init` — this isn't a BEACON project yet |
|
||||
| `beacon doctor` has a `FAIL` unrelated to phase progress (a broken window) | (current) | Fix that check first — name it. Don't advance over a red gate |
|
||||
| `beacon seed` not green (placeholders remain) | SEED | `/beacon.seed` |
|
||||
| SEED green, no epics declared | DESIGN | `/beacon.epics` — plan the initiative roadmap with a PM guide |
|
||||
| On integration/default branch (`main` / `develop`), epic exists with owned stubs not yet broken out into real specs | DESIGN | `/beacon.audit <slug>` — confirm the epic's stubs/specs cover every Success criterion before building (default-on *design* moment; tune via doctor.toml `[audit] moments`) |
|
||||
| On integration/default branch (`main` / `develop`), epic(s) exist | DESIGN→BUILD | `/beacon.specify <epic> <feature>` (with SpecKit) **or** `/git:feature <name>` + `beacon bullet start` for non-spec work |
|
||||
| Branch already merged into `main` (PR landed), bullet still active | SHIP | `beacon bullet finish && git switch main && git pull` |
|
||||
| Spec branch, `spec.md` present but no `plan.md` | DESIGN | `/beacon.plan` |
|
||||
| Spec branch, `plan.md` present but no `tasks.md` | DESIGN | `/beacon.tasks` |
|
||||
| Spec branch, `tasks.md` has active `[ ]` / `[~]` tasks | BUILD | `/beacon.implement` |
|
||||
| Spec branch, every task `[x]` or deferred `[-]` / `[d]`, diff not yet reviewed | BUILD→SHIP | `/beacon.review` (deferred follow-ups ship as known debt) |
|
||||
| Review clear, branch ahead of base, no PR open | SHIP | `/git:pr` |
|
||||
| Non-spec feature branch (`feature/` `fix/` `chore/` `docs/`), no bullet started | BUILD | `beacon bullet start "<title>" [--epic <slug>]` |
|
||||
| Non-spec branch, bullet active, the bullet's resolved spec (step 6) has open `[ ]` tasks | BUILD | `/beacon.implement <slug>` — continue the sharded spec; name the spec from step 6 |
|
||||
| Non-spec branch, bullet active, work done & reviewed, not yet merged | SHIP | `/git:pr` |
|
||||
| Epic's owned specs all complete and merged | SHIP | `/beacon.audit <slug>` then `beacon epic finish <slug>` — the audit (default-on *ship* moment) catches a Success criterion no spec ever covered, which the placeholder gate can't see |
|
||||
| Everything green, nothing in flight | — | "You're clear — nothing pending." |
|
||||
|
||||
If two rows could both apply, prefer the earlier one — broken windows before
|
||||
progress, and the earliest unfinished phase artifact before later ones.
|
||||
|
||||
## Report
|
||||
|
||||
Give a short narrative (2–4 sentences): where the project is, what's healthy,
|
||||
what's blocking. Then close with this exact-shaped footer so `/beacon.continue`
|
||||
and humans can parse it at a glance:
|
||||
|
||||
```
|
||||
Phase: <SEED|DESIGN|BUILD|SHIP|—>
|
||||
Health: doctor ok=<n> warn=<n> fail=<n> · seed: <green|not green>
|
||||
Recommended next: <command> — <one-line why>
|
||||
```
|
||||
|
||||
The recommendation is advice, not an order — the user decides whether to take it.
|
||||
@@ -0,0 +1,107 @@
|
||||
---
|
||||
description: Run SpecKit's tasks command, then enforce BEACON's test-first discipline — reframe tests as contracts, pair test/impl tasks, and scaffold .feature files from the spec's scenarios (default; pass --no-bdd to skip).
|
||||
argument-hint: [--no-bdd] (skip .feature scaffolding — otherwise no arguments; operates on the current spec branch)
|
||||
allowed-tools: Bash, Read, Write, Edit, Glob
|
||||
---
|
||||
|
||||
## SpecKit's tasks skill (inlined verbatim)
|
||||
|
||||
@.claude/skills/speckit-tasks/SKILL.md
|
||||
|
||||
## BEACON post-step — make test-first discipline real
|
||||
|
||||
SpecKit's template frames tests as *optional polish* and lists them in a
|
||||
separate "Tests for User Story N" block above an "Implementation for User
|
||||
Story N" block. That ordering is descriptive only — nothing stops an agent
|
||||
from flattening it into one commit, and the discipline that's the whole point
|
||||
of the ordering never gets invoked. BEACON fixes that here.
|
||||
|
||||
### 1. Tests are first-class, not optional
|
||||
|
||||
After SpecKit writes `specs/<NNN-slug>/tasks.md`, read `specs/<NNN-slug>/spec.md`.
|
||||
|
||||
**Tests are first-class deliverables for any spec that carries Acceptance
|
||||
Scenarios or Success Criteria.** Mark a spec test-exempt only when the work is
|
||||
purely documentation, configuration, or a single trivial-and-irreversible
|
||||
change. If `tasks.md` was generated without test tasks for a spec that *does*
|
||||
have acceptance scenarios, add them — do not treat their absence as a choice.
|
||||
|
||||
### 2. Rewrite each user story into interleaved TDD pairs
|
||||
|
||||
When the spec has Acceptance Scenarios, rewrite each user-story phase so the
|
||||
test and its implementation sit **adjacent as a pair**, rather than in two
|
||||
separate "Tests" / "Implementation" sections:
|
||||
|
||||
```diff
|
||||
-### Tests for User Story 1
|
||||
-- [ ] T010 [US1] Unit-test X in tests/...
|
||||
-- [ ] T011 [US1] Integration-test Y in tests/...
|
||||
-
|
||||
-### Implementation for User Story 1
|
||||
-- [ ] T012 [US1] Implement X in src/...
|
||||
-- [ ] T013 [US1] Implement Y in src/...
|
||||
+### User Story 1 — TDD pairs
|
||||
+- [ ] T010-T [US1] Write FAILING test for X in tests/... (red)
|
||||
+- [ ] T010-I [US1] Implement X so T010-T passes in src/... (green)
|
||||
+- [ ] T011-T [US1] Write FAILING test for Y in tests/... (red)
|
||||
+- [ ] T011-I [US1] Implement Y so T011-T passes in src/... (green)
|
||||
```
|
||||
|
||||
The visual pairing makes it hard to silently flatten the discipline into one
|
||||
commit. The `-T` / `-I` suffix is a convention `beacon doctor` and
|
||||
`/beacon.implement` reason about: a `-T` task is committed on its own (failing)
|
||||
before its `-I` partner. Keep `[P]` parallel markers only on tasks that are
|
||||
genuinely independent — a `-I` task is never parallel with its own `-T`.
|
||||
|
||||
**Deferred follow-ups.** A task that is real work but legitimately out of *this*
|
||||
bullet's scope — e.g. it depends on a fixture a later bullet ships — gets a
|
||||
`[-]` (or `[d]`) checkbox instead of `[ ]`, with a brief `_Deferred — why_`
|
||||
note (issue #84):
|
||||
|
||||
```
|
||||
-- [ ] T020 Run the slow accuracy benchmark (waits on a fixture)
|
||||
++ [-] T020 Run the slow accuracy benchmark _Deferred — depends on T010's fixture, lands in a follow-up bullet._
|
||||
```
|
||||
|
||||
`beacon bullet finish` skips `[-]` tasks instead of flipping them to `[x]`, so
|
||||
`tasks.md` keeps meaning what you wrote; `beacon doctor` reports them as
|
||||
known-deferred follow-ups (held under `--strict`), distinct from a stray `[ ]`
|
||||
left behind as tech debt. Plain `[ ]` keeps its old behaviour — the marker is
|
||||
opt-in.
|
||||
|
||||
### 3. Scaffold executable scenarios from the spec (default; `--no-bdd` to skip)
|
||||
|
||||
Unless the user passed `--no-bdd`, turn the spec's Given–When–Then acceptance
|
||||
scenarios into executable witnesses. This is the default so the discipline holds
|
||||
on every path — including unattended `/beacon.continue --auto` loops, which
|
||||
dispatch `/beacon.tasks` with no arguments. Skip this step only when `--no-bdd`
|
||||
is present or the spec is test-exempt (see step 1):
|
||||
|
||||
1. For each acceptance scenario in `spec.md`, write a Gherkin scenario
|
||||
**verbatim** into `specs/<NNN-slug>/features/<usN_slug>.feature`, plus
|
||||
placeholder step definitions in the project's test tree:
|
||||
|
||||
```gherkin
|
||||
# specs/001-tier1-table-profiler/features/us1_column_profile.feature
|
||||
Feature: US1 — Column-level profile
|
||||
|
||||
Scenario: Profile a small CSV with mixed column types
|
||||
Given a CSV file with 4 rows and 4 columns
|
||||
When the profiler runs against the CSV
|
||||
Then the profile contains 4 column entries
|
||||
And each column entry carries an inferred type
|
||||
```
|
||||
|
||||
2. Have the `-T` test tasks in `tasks.md` reference the scenario **by name**
|
||||
("Implement step defs for US1 — Column-level profile") instead of describing
|
||||
the assertion imperatively. This gives `spec.md → .feature → test`
|
||||
traceability that the `spec-bdd-coverage` doctor check can verify.
|
||||
|
||||
### 4. Confirm
|
||||
|
||||
End by noting which user stories were paired, whether `.feature` scaffolding was
|
||||
written (or skipped via `--no-bdd`), and reminding the user that `beacon doctor`
|
||||
now runs two gates against
|
||||
this discipline: `spec-bdd-coverage` (every scenario has a witness) and
|
||||
`tdd-commit-discipline` (no tests + implementation in the same commit). Both
|
||||
FAIL under `beacon doctor --strict`.
|
||||
@@ -0,0 +1,313 @@
|
||||
---
|
||||
description: Generate architecture diagrams for a component or system — selects the right Mermaid diagram type for the context
|
||||
argument-hint: [component or system to diagram]
|
||||
allowed-tools: Read, Write, Grep, Glob
|
||||
---
|
||||
|
||||
Generate architecture diagrams for: $ARGUMENTS
|
||||
|
||||
# Architecture Diagramming — Rigorous Mermaid Output
|
||||
|
||||
This command produces the right diagram(s) for the context. Do not default to a sequence
|
||||
diagram for everything — choose diagram types that reveal structure, not just flow.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Identify what you need to show
|
||||
|
||||
| Question | Best diagram type |
|
||||
|----------|------------------|
|
||||
| Where does this system sit among external actors? | **C4 Context** |
|
||||
| What are the major containers/services inside the system? | **C4 Container** |
|
||||
| What are the components inside a single container? | **C4 Component** |
|
||||
| How do objects/services call each other over time? | **Sequence** |
|
||||
| What states can an entity move through? | **State** |
|
||||
| What are the data entities and their relationships? | **Entity-Relationship (ERD)** |
|
||||
| How does the system deploy — hosts, networks, zones? | **Deployment** |
|
||||
| What are the task or build dependencies? | **Directed Graph (flowchart)** |
|
||||
| How does a class hierarchy or interface relate? | **Class** |
|
||||
|
||||
Produce **only the diagrams that add information**. If a sequence diagram and a flowchart would
|
||||
show the same thing, produce the sequence diagram — it shows order, which a flowchart does not.
|
||||
|
||||
---
|
||||
|
||||
## Diagram Type Reference
|
||||
|
||||
### C4 Context Diagram
|
||||
Shows the system in relation to external users and systems. One diagram per system.
|
||||
|
||||
```mermaid
|
||||
C4Context
|
||||
title System Context — [System Name]
|
||||
Person(user, "Primary User", "Description of what they do")
|
||||
System(sys, "[System Name]", "What it does in one line")
|
||||
System_Ext(ext1, "External System A", "What it provides")
|
||||
System_Ext(ext2, "External System B", "What it provides")
|
||||
|
||||
Rel(user, sys, "Uses", "HTTPS")
|
||||
Rel(sys, ext1, "Reads from", "REST API")
|
||||
Rel(sys, ext2, "Writes to", "Event stream")
|
||||
```
|
||||
|
||||
When to include:
|
||||
- Always for the system-level architecture document
|
||||
- In feature plan.md when the feature introduces a new external integration
|
||||
|
||||
---
|
||||
|
||||
### C4 Container Diagram
|
||||
Shows the major deployable units (applications, databases, queues) inside the system boundary.
|
||||
|
||||
```mermaid
|
||||
C4Container
|
||||
title Container Diagram — [System Name]
|
||||
Person(user, "User", "Description")
|
||||
|
||||
System_Boundary(sys, "[System Name]") {
|
||||
Container(web, "Web App", "Python/FastAPI", "Serves HTTP requests")
|
||||
Container(worker, "Worker", "Python", "Processes background jobs")
|
||||
ContainerDb(db, "Database", "PostgreSQL", "Persists application state")
|
||||
Container(queue, "Queue", "Redis", "Job queue")
|
||||
}
|
||||
|
||||
System_Ext(ext, "External API", "Third-party data source")
|
||||
|
||||
Rel(user, web, "Uses", "HTTPS")
|
||||
Rel(web, queue, "Enqueues jobs", "Redis protocol")
|
||||
Rel(worker, queue, "Dequeues", "Redis protocol")
|
||||
Rel(worker, db, "Reads/writes", "SQL")
|
||||
Rel(worker, ext, "Calls", "REST HTTPS")
|
||||
```
|
||||
|
||||
When to include:
|
||||
- In the architecture document when there are multiple deployable units
|
||||
- In feature plan.md when the feature spans containers
|
||||
|
||||
---
|
||||
|
||||
### C4 Component Diagram
|
||||
Shows the internal structure of one container — classes, modules, interfaces.
|
||||
|
||||
```mermaid
|
||||
C4Component
|
||||
title Component Diagram — [Container Name]
|
||||
Container_Boundary(c, "[Container Name]") {
|
||||
Component(router, "Router", "FastAPI", "HTTP routing and auth")
|
||||
Component(service, "Service Layer", "Python", "Business logic")
|
||||
Component(repo, "Repository", "Python", "Data access abstraction")
|
||||
}
|
||||
ContainerDb(db, "Database", "PostgreSQL", "")
|
||||
Rel(router, service, "Calls")
|
||||
Rel(service, repo, "Uses")
|
||||
Rel(repo, db, "Queries", "SQL")
|
||||
```
|
||||
|
||||
When to include:
|
||||
- In feature plan.md when the feature's internal structure needs clarifying
|
||||
- Only when a container has ≥3 distinct internal components
|
||||
|
||||
---
|
||||
|
||||
### Sequence Diagram
|
||||
Shows interactions between actors/services in time order.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
actor User
|
||||
participant API as API Gateway
|
||||
participant Service
|
||||
participant DB as Database
|
||||
participant Cache
|
||||
|
||||
User->>API: POST /resource (payload)
|
||||
API->>Service: validate + process(payload)
|
||||
|
||||
Service->>Cache: get(key)
|
||||
alt Cache hit
|
||||
Cache-->>Service: cached result
|
||||
else Cache miss
|
||||
Service->>DB: SELECT ...
|
||||
DB-->>Service: rows
|
||||
Service->>Cache: set(key, result, ttl)
|
||||
end
|
||||
|
||||
Service-->>API: result
|
||||
API-->>User: 200 OK (response)
|
||||
```
|
||||
|
||||
Rules for good sequence diagrams:
|
||||
- Use `autonumber` for every diagram
|
||||
- Use `alt`/`else`/`opt` to show conditional flows — don't flatten them
|
||||
- Show both happy path AND error/alternative paths
|
||||
- Name participants clearly (avoid generic "Service1", "Service2")
|
||||
- Add a `Note` for important side effects or constraints
|
||||
|
||||
---
|
||||
|
||||
### State Diagram
|
||||
Shows how an entity transitions between states in response to events.
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Draft : create
|
||||
|
||||
Draft --> UnderReview : submit
|
||||
Draft --> Cancelled : cancel
|
||||
|
||||
UnderReview --> Approved : approve
|
||||
UnderReview --> Rejected : reject
|
||||
UnderReview --> Draft : request changes
|
||||
|
||||
Approved --> Published : publish
|
||||
Approved --> Cancelled : cancel
|
||||
|
||||
Published --> Archived : archive
|
||||
Cancelled --> [*]
|
||||
Archived --> [*]
|
||||
|
||||
note right of UnderReview
|
||||
SLA: 5 business days
|
||||
end note
|
||||
```
|
||||
|
||||
When to include:
|
||||
- When an entity has a lifecycle (orders, approvals, workflows, tasks)
|
||||
- In feature plan.md when the feature changes how an entity transitions
|
||||
|
||||
---
|
||||
|
||||
### Entity Relationship Diagram (ERD)
|
||||
Shows data entities and the relationships between them.
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
USER {
|
||||
uuid id PK
|
||||
string email UK
|
||||
string name
|
||||
timestamp created_at
|
||||
}
|
||||
|
||||
PROJECT {
|
||||
uuid id PK
|
||||
string name
|
||||
uuid owner_id FK
|
||||
timestamp created_at
|
||||
}
|
||||
|
||||
TASK {
|
||||
uuid id PK
|
||||
string title
|
||||
string status
|
||||
uuid project_id FK
|
||||
uuid assignee_id FK
|
||||
timestamp due_date
|
||||
}
|
||||
|
||||
USER ||--o{ PROJECT : owns
|
||||
PROJECT ||--o{ TASK : contains
|
||||
USER ||--o{ TASK : "assigned to"
|
||||
```
|
||||
|
||||
Rules for good ERDs:
|
||||
- Mark PK, FK, UK explicitly
|
||||
- Show cardinality (`||--o{`, `||--|{`, `}o--o{`)
|
||||
- Include only the entities relevant to the feature or system
|
||||
- Show at least the key non-relational fields (not just IDs)
|
||||
|
||||
---
|
||||
|
||||
### Deployment Diagram
|
||||
Shows where components run — hosts, containers, cloud regions, network zones.
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Internet"
|
||||
User["👤 User"]
|
||||
end
|
||||
|
||||
subgraph "Azure — UK South"
|
||||
subgraph "App Service Plan"
|
||||
API["FastAPI App\n(Python 3.13)"]
|
||||
end
|
||||
|
||||
subgraph "Private VNet"
|
||||
Worker["Background Worker\n(Python)"]
|
||||
DB["Azure PostgreSQL\nFlexible Server"]
|
||||
Cache["Azure Cache\nfor Redis"]
|
||||
end
|
||||
|
||||
subgraph "Azure DevOps"
|
||||
CI["Build Pipeline"]
|
||||
end
|
||||
end
|
||||
|
||||
subgraph "External"
|
||||
Fabric["Microsoft Fabric\nSemantic Model"]
|
||||
end
|
||||
|
||||
User -->|HTTPS| API
|
||||
API --> Worker
|
||||
Worker --> DB
|
||||
Worker --> Cache
|
||||
Worker -->|REST API| Fabric
|
||||
CI -->|deploy| API
|
||||
CI -->|deploy| Worker
|
||||
```
|
||||
|
||||
When to include:
|
||||
- In the architecture document when deployment topology is non-trivial
|
||||
- When security boundaries, network zones, or data residency matter
|
||||
|
||||
---
|
||||
|
||||
### Flowchart (directed graph)
|
||||
Shows process flow, decision trees, or task dependencies.
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Start] --> B{Condition?}
|
||||
B -->|Yes| C[Action A]
|
||||
B -->|No| D[Action B]
|
||||
C --> E[Shared step]
|
||||
D --> E
|
||||
E --> F[End]
|
||||
```
|
||||
|
||||
Prefer `flowchart TD` over `graph TD` for process flows — identical syntax, clearer intent.
|
||||
Use sequence diagrams instead when the flow involves multiple actors interacting over time.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Produce the diagrams
|
||||
|
||||
For each selected diagram:
|
||||
1. Confirm the diagram type and what it will show
|
||||
2. Generate the Mermaid source with realistic, named entities
|
||||
3. Add a one-sentence caption explaining what the diagram reveals
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Save and wire in
|
||||
|
||||
**For a new feature spec:**
|
||||
- Add diagrams to `specs/[feature]/plan.md` (Spec Kit's `/speckit-plan` output)
|
||||
|
||||
**For the architecture document:**
|
||||
- Add or update diagrams in `project-management/Background/01-final-architecture-document.md`
|
||||
|
||||
**For a standalone analysis:**
|
||||
- Save to `project-management/Work/analysis/diagrams-[topic].md`
|
||||
|
||||
---
|
||||
|
||||
## Quality rules
|
||||
|
||||
- Every diagram must have a `title` or caption
|
||||
- Sequence diagrams must use `autonumber`
|
||||
- ERDs must mark PK/FK/UK
|
||||
- C4 diagrams must use C4Context/C4Container/C4Component keywords (not plain flowcharts)
|
||||
- No more than 12–15 nodes per diagram — split into multiple diagrams if larger
|
||||
- Only include a diagram if it reveals something that prose cannot
|
||||
@@ -0,0 +1,156 @@
|
||||
---
|
||||
description: Structured technology evaluation — scored build-vs-buy comparison that produces an ADR-ready recommendation
|
||||
argument-hint: [technology decision or component to evaluate]
|
||||
allowed-tools: Read, Write, Grep, Glob
|
||||
---
|
||||
|
||||
Evaluate technology options for: $ARGUMENTS
|
||||
|
||||
# Technology Evaluation — Build vs. Buy Analysis
|
||||
|
||||
Use this command when a DESIGN decision requires choosing between building a component, using an
|
||||
open-source library, buying a SaaS product, or using a cloud service. The output feeds directly
|
||||
into an ADR.
|
||||
|
||||
## When to use this command
|
||||
|
||||
- During **DESIGN** when an architectural option involves a significant technology choice
|
||||
- When the spec design phase surfaces a "which database / queue / search engine / auth provider?"
|
||||
question that deserves a structured answer
|
||||
- When `/design:wardley` identifies a component as Product/Commodity and you need to pick the
|
||||
right product
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Define the decision
|
||||
|
||||
State clearly:
|
||||
- **What capability is needed?** (one sentence, functional)
|
||||
- **What are the non-negotiables?** (hard requirements that eliminate options immediately)
|
||||
- **What is the context?** (team size, scale, budget constraints, existing stack)
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Identify options
|
||||
|
||||
Generate at least three options. Always include:
|
||||
1. **Build in-house** (greenfield custom implementation)
|
||||
2. **Open-source library/framework** (integrate existing OSS)
|
||||
3. **Managed service / SaaS** (vendor-hosted, pay-to-use)
|
||||
|
||||
Add further options if they are genuinely distinct.
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Score each option
|
||||
|
||||
Rate each option 1–5 on each criterion. Adjust the weight column to reflect project priorities
|
||||
(weights must sum to 100).
|
||||
|
||||
| Criterion | Weight | Build | OSS | SaaS | [Other] |
|
||||
|-----------|--------|-------|-----|------|---------|
|
||||
| **Fit to requirements** — does it meet the functional spec? | 25 | | | | |
|
||||
| **Operational complexity** — how hard to run, monitor, upgrade? | 20 | | | | |
|
||||
| **Time to value** — how quickly can the team deliver with this? | 15 | | | | |
|
||||
| **Cost (TCO 2 yr)** — licensing + infra + engineering time | 15 | | | | |
|
||||
| **Vendor/community risk** — abandonment, lock-in, support quality | 10 | | | | |
|
||||
| **Security & compliance** — data residency, audit, vulnerability cadence | 10 | | | | |
|
||||
| **Team capability** — does the team have skills or can acquire them quickly? | 5 | | | | |
|
||||
| **TOTAL (weighted)** | 100 | | | | |
|
||||
|
||||
Scoring guide:
|
||||
- **5** — Excellent fit; no significant concern
|
||||
- **4** — Good fit; minor concerns
|
||||
- **3** — Adequate; notable trade-offs
|
||||
- **2** — Poor fit; significant concerns
|
||||
- **1** — Unacceptable; eliminates or blocks a requirement
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Identify deal-breakers
|
||||
|
||||
Before accepting the top scorer, explicitly check:
|
||||
|
||||
- Does any option violate a **hard requirement** (compliance, latency, data sovereignty)?
|
||||
- Is the leading option in a zone where it should be **commoditised** (per Wardley stage)? If so,
|
||||
the Build option is probably waste.
|
||||
- Does the team have **genuine expertise** to build and operate this, or is that an optimistic
|
||||
assumption?
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Produce the output
|
||||
|
||||
Save as `project-management/Work/analysis/evaluate-[topic].md`:
|
||||
|
||||
```markdown
|
||||
# Technology Evaluation: [Topic]
|
||||
|
||||
## Decision
|
||||
[One sentence: what capability are we choosing a technology for?]
|
||||
|
||||
## Context
|
||||
- Team: [size, skills]
|
||||
- Scale: [expected load]
|
||||
- Constraints: [budget, compliance, existing stack]
|
||||
|
||||
## Non-Negotiables
|
||||
- [Hard requirement 1]
|
||||
- [Hard requirement 2]
|
||||
|
||||
## Options Evaluated
|
||||
|
||||
### Option 1: Build in-house
|
||||
**Summary:** [Brief description]
|
||||
**Pros:** [Key strengths]
|
||||
**Cons:** [Key weaknesses]
|
||||
|
||||
### Option 2: [OSS library/framework name]
|
||||
**Summary:** [Brief description]
|
||||
**Pros:** [Key strengths]
|
||||
**Cons:** [Key weaknesses]
|
||||
|
||||
### Option 3: [SaaS/managed service name]
|
||||
**Summary:** [Brief description]
|
||||
**Pros:** [Key strengths]
|
||||
**Cons:** [Key weaknesses]
|
||||
|
||||
## Scoring
|
||||
|
||||
| Criterion | Weight | Build | [OSS] | [SaaS] |
|
||||
|-----------|--------|-------|-------|--------|
|
||||
| Fit to requirements | 25 | | | |
|
||||
| Operational complexity | 20 | | | |
|
||||
| Time to value | 15 | | | |
|
||||
| Cost (TCO 2 yr) | 15 | | | |
|
||||
| Vendor/community risk | 10 | | | |
|
||||
| Security & compliance | 10 | | | |
|
||||
| Team capability | 5 | | | |
|
||||
| **TOTAL** | 100 | | | |
|
||||
|
||||
## Deal-Breaker Check
|
||||
- [ ] No hard requirements violated by recommended option
|
||||
- [ ] Wardley stage checked — not building in commodity zone
|
||||
- [ ] Team capability assessment is honest, not optimistic
|
||||
|
||||
## Recommendation
|
||||
|
||||
**Recommended option:** [name]
|
||||
**Rationale:** [2–3 sentences explaining why this option wins]
|
||||
**Key risk to monitor:** [the main risk of this choice and how to detect if it becomes a problem]
|
||||
|
||||
## Next Step
|
||||
|
||||
Create ADR-NNN: [topic] using this analysis as the "Considered Alternatives" section.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Guidelines
|
||||
|
||||
- Do not recommend "build" for components that are clearly Product or Commodity (use
|
||||
`/design:wardley` first if unsure)
|
||||
- Be honest about TCO — "free" open-source is not free to operate
|
||||
- If two options are within 10 points of each other on the score, prefer the option with lower
|
||||
operational complexity (teams consistently underestimate ops burden)
|
||||
- The evaluation is an input to the ADR, not a replacement for it
|
||||
@@ -0,0 +1,156 @@
|
||||
---
|
||||
description: Create a Wardley Map for strategic landscape analysis — identifying component evolution stages, build-vs-buy tensions, and outsourcing candidates
|
||||
argument-hint: [topic or system to map]
|
||||
allowed-tools: Read, Write, Grep, Glob
|
||||
---
|
||||
|
||||
Create a Wardley Map for: $ARGUMENTS
|
||||
|
||||
# Wardley Mapping — Strategic Landscape Analysis
|
||||
|
||||
Wardley Maps help answer: "What should we build, buy, or outsource?" They reveal the evolutionary
|
||||
stage of each component and surface where custom build adds unique value vs. where commodity
|
||||
solutions should be used.
|
||||
|
||||
## When to use this command
|
||||
|
||||
- During **SEED** — to understand whether a problem is worth solving uniquely or whether existing
|
||||
solutions are already commoditised
|
||||
- During **DESIGN** — to decide which components to build vs. buy before committing to an ADR
|
||||
- When a technology decision involves a significant build/buy trade-off
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Identify the value chain
|
||||
|
||||
List the **user need** at the top, then trace every component the system needs to deliver that
|
||||
need.
|
||||
|
||||
```
|
||||
User Need
|
||||
└── Component A (what the user directly interacts with)
|
||||
└── Component B (what A depends on)
|
||||
└── Component C (what B depends on)
|
||||
└── Component D (infrastructure / platform)
|
||||
```
|
||||
|
||||
Ask for each component:
|
||||
- What does the user directly value?
|
||||
- What enables that value?
|
||||
- What does *that* depend on?
|
||||
|
||||
Continue until you reach infrastructure or commodity platforms.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Assign evolution stages
|
||||
|
||||
For each component, assess its evolutionary stage:
|
||||
|
||||
| Stage | Characteristics | Typical source |
|
||||
|-------|----------------|----------------|
|
||||
| **Genesis** | Novel, unstable, custom; competitive differentiator | Build in-house |
|
||||
| **Custom** | Best practice emerging; built for specific context | Build or bespoke |
|
||||
| **Product** | Multiple competing products exist; becoming standard | Buy (COTS/SaaS) |
|
||||
| **Commodity** | Standardised, utility; invisible infrastructure | Buy or use cloud |
|
||||
|
||||
Signals of evolution:
|
||||
- **Genesis → Custom**: blog posts, papers, first implementations appear
|
||||
- **Custom → Product**: open-source libraries, vendor products, comparisons written
|
||||
- **Product → Commodity**: cloud services, APIs, "just use X"
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Draw the map (Mermaid)
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph "Visible to User"
|
||||
A["User Need\n(high value, high visibility)"]
|
||||
end
|
||||
subgraph "Value Chain"
|
||||
B["Component A\n[Custom]"]
|
||||
C["Component B\n[Product]"]
|
||||
D["Component C\n[Commodity]"]
|
||||
end
|
||||
A --> B
|
||||
B --> C
|
||||
C --> D
|
||||
```
|
||||
|
||||
Label each node with its evolution stage in brackets: `[Genesis]`, `[Custom]`, `[Product]`,
|
||||
`[Commodity]`.
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Identify strategic insights
|
||||
|
||||
Answer each question:
|
||||
|
||||
**Build candidates (Genesis/Custom):**
|
||||
- Which components are in Genesis or Custom that directly deliver your unique user value?
|
||||
- These are candidates for in-house build — they are your differentiators.
|
||||
|
||||
**Buy/outsource candidates (Product/Commodity):**
|
||||
- Which components are already Product or Commodity?
|
||||
- Using a vendor here is not a compromise — it is correct strategy.
|
||||
- Building in this zone is waste.
|
||||
|
||||
**Evolution pressure:**
|
||||
- Which Genesis components are moving toward Custom? Plan for increasing competition.
|
||||
- Which Custom components are becoming Products? Evaluate switching to vendor solutions.
|
||||
|
||||
**Inertia risks:**
|
||||
- Are you building something in the Product/Commodity zone because of legacy, familiarity, or
|
||||
NIH? Name it explicitly.
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Produce the output
|
||||
|
||||
Save as `project-management/Work/analysis/wardley-[topic].md` with:
|
||||
|
||||
```markdown
|
||||
# Wardley Map: [Topic]
|
||||
|
||||
## User Need
|
||||
[One sentence: what does the user value?]
|
||||
|
||||
## Value Chain
|
||||
|
||||
| Component | Stage | Notes |
|
||||
|-----------|-------|-------|
|
||||
| [name] | Genesis / Custom / Product / Commodity | [why this stage] |
|
||||
|
||||
## Map
|
||||
|
||||
[Mermaid diagram]
|
||||
|
||||
## Strategic Insights
|
||||
|
||||
### Build (unique value — in-house)
|
||||
- [Component]: [reason it is a differentiator at this stage]
|
||||
|
||||
### Buy / Outsource (commodity zone)
|
||||
- [Component]: [recommended vendor or platform]
|
||||
|
||||
### Evolution Risks
|
||||
- [Component] is in [stage] now but moving to [next stage] — revisit in [timeframe]
|
||||
|
||||
### Inertia Warnings
|
||||
- [Any identified NIH or legacy biases]
|
||||
|
||||
## Recommended ADR
|
||||
|
||||
[If this analysis informs an architectural decision, state: "This analysis supports
|
||||
ADR-NNN: [topic]. Create the ADR before proceeding to DESIGN."]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Guidelines
|
||||
|
||||
- Be ruthlessly honest about evolution stage — overestimating uniqueness leads to wasted build effort
|
||||
- A Wardley Map is a hypothesis, not ground truth — update it as you learn
|
||||
- Components that are Commodity in one context may be Custom in another (context matters)
|
||||
- The goal is not a perfect map but surfacing the key build/buy decisions before code is written
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
description: Cut a branch from main for non-spec work (quick fixes, chores, docs). Spec work branches via /speckit-specify instead.
|
||||
argument-hint: [branch-type/description] e.g. "fix/null-pointer" or "docs/update readme"
|
||||
allowed-tools: Bash, Read, TodoWrite
|
||||
---
|
||||
|
||||
Create a branch for: $ARGUMENTS
|
||||
|
||||
# Branch from main (non-spec work)
|
||||
|
||||
Use this for small changes that don't warrant a spec — bug fixes, chores, docs.
|
||||
|
||||
**Spec'd feature work does not use this command.** `/speckit-specify` creates
|
||||
the spec branch (`NNN-slug`) as part of the DESIGN phase — run that instead.
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Sync main:**
|
||||
```bash
|
||||
git fetch origin
|
||||
git checkout main
|
||||
git pull origin main
|
||||
```
|
||||
|
||||
2. **Determine branch type and cut the branch:**
|
||||
```bash
|
||||
RAW="$ARGUMENTS"
|
||||
# Strip existing prefix if user included one, then slugify
|
||||
SLUG=$(echo "$RAW" | tr '[:upper:]' '[:lower:]' | sed 's|[^a-z0-9/]|-|g' | sed 's/--*/-/g' | sed 's/^-\|-$//g')
|
||||
# Default to feature/ if no type prefix
|
||||
if [[ "$SLUG" != fix/* && "$SLUG" != chore/* && "$SLUG" != docs/* ]]; then
|
||||
SLUG="feature/$SLUG"
|
||||
fi
|
||||
git checkout -b "$SLUG"
|
||||
echo "✓ Created branch: $SLUG (from main)"
|
||||
```
|
||||
|
||||
3. **Report** the branch name and base (`main`). PRs from this branch
|
||||
target `main` — use `/git:pr` when ready.
|
||||
|
||||
## Branch model reminder
|
||||
|
||||
The project uses **main** as the integration branch and **main** as the
|
||||
stable / release branch. (If those names are the same, this project is on
|
||||
trunk-based git flow — feature branches merge straight to `main` and
|
||||
releases happen via tags on `main`.)
|
||||
|
||||
## Hotfix exception
|
||||
|
||||
If this is a **production-critical fix** that cannot wait for the next release:
|
||||
- Cut from `main` instead: `git checkout main && git pull && git checkout -b fix/[slug]`
|
||||
- PR to `main` directly
|
||||
- If `main` and `main` differ (gitflow), immediately cherry-pick to `main` after merge:
|
||||
`git checkout main && git cherry-pick <sha>`
|
||||
- Tell the user if this appears to be a hotfix based on the description
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
description: Open a pull request from the current branch to main (default) or main (hotfix). Runs BEACON quality gates first and populates the description from the related spec.
|
||||
argument-hint: [pr-title] — optional; inferred from branch name if omitted
|
||||
allowed-tools: Bash, Read, Grep
|
||||
---
|
||||
|
||||
Open a PR for the current branch.
|
||||
|
||||
# Pull Request
|
||||
|
||||
## 1. Detect context
|
||||
|
||||
```bash
|
||||
BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||||
echo "Current branch: $BRANCH"
|
||||
```
|
||||
|
||||
- If branch starts with `fix/` AND was cut from `main` (check `git log main..HEAD`): this is a **hotfix** — target `main`
|
||||
- Otherwise: target **main**
|
||||
|
||||
(On trunk-based projects `main` and `main` are both `main`, so there's no distinction — all PRs target `main`.)
|
||||
|
||||
## 2. Run BEACON quality gates
|
||||
|
||||
Do not open a PR if any gate fails.
|
||||
|
||||
```bash
|
||||
uv run ruff check --fix && uv run ruff format
|
||||
uv run ty check
|
||||
git diff --stat HEAD
|
||||
```
|
||||
|
||||
If type checks fail: fix them before proceeding. Report what failed.
|
||||
|
||||
## 3. Find related spec and build PR body
|
||||
|
||||
Search `specs/` for a spec matching the branch name (strip prefix):
|
||||
|
||||
```bash
|
||||
FEATURE=$(echo "$BRANCH" | sed 's|^[^/]*/||')
|
||||
ls specs/ 2>/dev/null | grep -i "$FEATURE" | head -1
|
||||
```
|
||||
|
||||
Build the PR body using:
|
||||
- Specification summary from `spec.md` (user stories + acceptance criteria)
|
||||
- Plan summary from `plan.md` (what changed architecturally)
|
||||
- Task list from `tasks.md` (which bullets are complete)
|
||||
- `git log main..HEAD --oneline` (commits included)
|
||||
|
||||
Use `.github/PULL_REQUEST_TEMPLATE/feature.md` as the structure.
|
||||
|
||||
## 4. Open the PR
|
||||
|
||||
```bash
|
||||
BASE="main" # or "main" for hotfix (gitflow only)
|
||||
TITLE="${ARGUMENTS:-$(echo "$BRANCH" | sed 's|^[^/]*/||' | tr '-' ' ')}"
|
||||
|
||||
gh pr create \
|
||||
--title "$TITLE" \
|
||||
--base "$BASE" \
|
||||
--head "$BRANCH" \
|
||||
--body-file /tmp/pr-body.md
|
||||
```
|
||||
|
||||
## 5. After creating
|
||||
|
||||
Report:
|
||||
- PR URL
|
||||
- Target branch and why
|
||||
- Required CI checks that must pass
|
||||
- If hotfix to `main` AND `main` differs from `main` (gitflow): remind to cherry-pick after merge
|
||||
@@ -0,0 +1,100 @@
|
||||
---
|
||||
description: Tag main with the next version and push. CI publishes from the tag. Trunk-based projects don't need a promotion PR — main is always shippable.
|
||||
argument-hint: [version] — e.g. "v1.2.0". Omit to suggest from Conventional Commits since the last tag.
|
||||
allowed-tools: Bash, Read, Grep
|
||||
---
|
||||
|
||||
Tag the next release on `main`.
|
||||
|
||||
# Release (trunk-based)
|
||||
|
||||
This project uses **trunk-based** git flow — `main` is always shippable, so a release is just a tag. CI takes it from there.
|
||||
|
||||
## 1. Pre-flight checks
|
||||
|
||||
```bash
|
||||
# Ensure main is up to date
|
||||
git fetch origin
|
||||
git checkout main
|
||||
git pull origin main
|
||||
|
||||
# Confirm CI is green on main
|
||||
gh run list --branch main --limit 5
|
||||
```
|
||||
|
||||
If the last CI run on main failed: **stop and report**. Do not tag a broken main.
|
||||
|
||||
## 2. Determine version
|
||||
|
||||
If `$ARGUMENTS` is provided, treat it as the explicit version (e.g. `v1.2.0`).
|
||||
Otherwise, suggest a version by inspecting the most recent git tag and the Conventional Commits since:
|
||||
|
||||
```bash
|
||||
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
|
||||
echo "Last release: $LAST_TAG"
|
||||
git log "$LAST_TAG"..HEAD --oneline
|
||||
```
|
||||
|
||||
Apply Conventional Commits semver:
|
||||
- `BREAKING CHANGE` in footer → major bump
|
||||
- Any `feat:` → minor bump
|
||||
- Only `fix:` / `perf:` / etc. → patch bump
|
||||
|
||||
If the project uses python-semantic-release (i.e. `beacon integration add release` was run), the PSR action on push already determines the version automatically — in that case you don't tag by hand; just push to `main`.
|
||||
|
||||
## 3. Generate release notes (preview)
|
||||
|
||||
```bash
|
||||
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
|
||||
BASE="${LAST_TAG:-$(git rev-list --max-parents=0 HEAD)}"
|
||||
|
||||
echo "## What's changed"
|
||||
echo ""
|
||||
echo "### Features"
|
||||
git log "$BASE"..HEAD --oneline --grep="^feat" | sed 's/^/- /'
|
||||
echo ""
|
||||
echo "### Fixes"
|
||||
git log "$BASE"..HEAD --oneline --grep="^fix" | sed 's/^/- /'
|
||||
echo ""
|
||||
echo "### Other"
|
||||
git log "$BASE"..HEAD --oneline --grep="^chore\|^docs\|^refactor\|^test\|^ci" | sed 's/^/- /'
|
||||
```
|
||||
|
||||
This preview gives you the changelog body; PSR (if installed) will generate the canonical version on push.
|
||||
|
||||
## 4. Check Work/ cleanup
|
||||
|
||||
Look in `project-management/Work/sessions/` and `Work/planning/`:
|
||||
- If stale session files exist (older than current sprint): remind user to clean them before tagging
|
||||
- Output: "⚠ Stale Work/ files found — clean up before releasing (see project-management/Work/README.md)"
|
||||
|
||||
## 5. Tag and push
|
||||
|
||||
```bash
|
||||
VERSION="${ARGUMENTS:-v$(date +%Y.%m.%d)}" # fallback to date-based if no arg
|
||||
|
||||
git tag -a "$VERSION" -m "Release $VERSION"
|
||||
git push origin "$VERSION"
|
||||
```
|
||||
|
||||
## 6. After tagging
|
||||
|
||||
Report:
|
||||
- Tag created (version)
|
||||
- Tag commit SHA
|
||||
- Reminder: if `release` integration is installed, the GitHub Actions workflow now picks the tag up and publishes — `gh run list --branch main --limit 1` watches it.
|
||||
- Reminder: clean `project-management/Work/` after release:
|
||||
```bash
|
||||
cd project-management/Work
|
||||
rm -rf sessions/* planning/* # keep only active WIP
|
||||
```
|
||||
|
||||
## Branch model
|
||||
|
||||
```
|
||||
main ← integration AND release branch (trunk-based)
|
||||
↑
|
||||
feature/x ← merges here directly; main is always shippable
|
||||
```
|
||||
|
||||
No develop, no promotion PR. Tag what's shippable; let CI ship it.
|
||||
@@ -0,0 +1,179 @@
|
||||
---
|
||||
description: Start a new BEACON project — runs the SEED phase interactively, produces a filled problem statement, then bridges to /speckit-specify
|
||||
argument-hint: [optional: brief description of what you're thinking of building]
|
||||
allowed-tools: Read, Write, Bash, TodoWrite
|
||||
---
|
||||
|
||||
You are running the BEACON SEED phase for this project.
|
||||
|
||||
# /init — BEACON Project Initialisation
|
||||
|
||||
Your goal: guide the user through the five SEED questions, then write a filled `project-management/Background/00-problem-statement.md` that they have approved.
|
||||
|
||||
---
|
||||
|
||||
## Step 0: Preflight checks
|
||||
|
||||
Run these silently before saying anything to the user:
|
||||
|
||||
```bash
|
||||
# Check git hooks are installed
|
||||
[ -f ".git/hooks/commit-msg" ] && echo "hooks_ok" || echo "hooks_missing"
|
||||
|
||||
# Check .env exists
|
||||
[ -f ".env" ] && echo "env_ok" || echo "env_missing"
|
||||
```
|
||||
|
||||
Read the current problem statement:
|
||||
|
||||
```bash
|
||||
cat project-management/Background/00-problem-statement.md
|
||||
```
|
||||
|
||||
Then:
|
||||
|
||||
- If hooks are missing: tell the user **before** anything else — `⚠️ Run ./scripts/setup.sh first to install git hooks, link agents, and create your .env. Then re-run /init.`
|
||||
- If `.env` is missing: note it — `⚠️ .env not found — run ./scripts/setup.sh to create it, then fill in your Azure credentials.`
|
||||
- If the problem statement already has real content (no `[Replace with` placeholders): summarise what's already there, tell the user SEED may already be done, and ask if they want to refine it or proceed straight to DESIGN with `/speckit-specify`.
|
||||
- If `$ARGUMENTS` was provided: use it to pre-populate context when asking the five questions. Don't skip the questions — use the argument as a starting point to dig into.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Welcome
|
||||
|
||||
Say this (do not skip — it sets the mindset):
|
||||
|
||||
---
|
||||
|
||||
👋 Welcome. Before writing any code, we need to complete the **SEED phase**.
|
||||
|
||||
SEED answers one question: **does this problem deserve to be solved, and are we solving the right one?**
|
||||
|
||||
Building the wrong thing confidently is worse than building nothing. SEED exists to prevent that.
|
||||
|
||||
I'll ask you five questions. Your answers become `project-management/Background/00-problem-statement.md` — the document that everything else builds on.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: The five SEED questions
|
||||
|
||||
Ask these **one at a time**. Wait for a genuine answer before moving to the next. Push back on vague answers — your job is to help them be precise, not to accept whatever they say.
|
||||
|
||||
### Question 1 — The problem
|
||||
|
||||
Ask: *"What specific problem are you solving? One sentence — not a category, not a theme. A specific, painful problem for a specific person."*
|
||||
|
||||
**Push back if the answer is:**
|
||||
- Vague: "improve developer productivity" → ask: *"Who specifically? What does 'improve' mean in practice? What are they doing today that's painful?"*
|
||||
- A solution: "I want to build X" → ask: *"That's what you want to build. What problem does it solve for who? What happens today without it?"*
|
||||
- Too broad: "make things faster" → ask: *"Faster for whom, in what workflow, by how much?"*
|
||||
|
||||
Don't move on until the answer is specific enough that you could test whether the solution fixed it.
|
||||
|
||||
### Question 2 — The user
|
||||
|
||||
Ask: *"Who has this problem? Name the specific role and context — not 'users in general'. What do they do today as a workaround, and why does that fall short?"*
|
||||
|
||||
**Push back if the answer is:**
|
||||
- Generic: "developers" → *"Which developers? In what context? On what team?"*
|
||||
- No workaround mentioned → *"What do they do today instead? Why is that insufficient?"*
|
||||
|
||||
### Question 3 — Alternatives already considered
|
||||
|
||||
Ask: *"What three existing solutions have you already looked at? For each: why is it insufficient for this user and this problem?"*
|
||||
|
||||
**Push back if the answer is:**
|
||||
- "I couldn't find anything" → almost never true; prompt: *"Did you look at [X, Y, Z — suggest plausible alternatives based on the domain]?"*
|
||||
- Only one alternative → *"That's one. What else? What about [suggest another]?"*
|
||||
- Dismissive: "they're all too complex/expensive/slow" → *"Can you be specific? What exactly makes each one insufficient?"*
|
||||
|
||||
**Why this matters:** if you haven't looked at what already exists, you risk building something that already exists, or something inferior to what exists.
|
||||
|
||||
### Question 4 — Non-goals
|
||||
|
||||
Ask: *"What are you explicitly NOT building? Name three things that are out of scope."*
|
||||
|
||||
**Why this matters:** non-goals prevent scope creep before it starts. If something isn't named as out of scope, it becomes fair game later.
|
||||
|
||||
**Push back if the answer is vague or thin** → *"What features might people ask for that you're deliberately not building? What would make this project ten times harder but isn't essential?"*
|
||||
|
||||
### Question 5 — Success criteria
|
||||
|
||||
Ask: *"How will you know this is working? Give me three measurable outcomes — not 'users are happy', but specific and testable."*
|
||||
|
||||
**Push back if the answer is:**
|
||||
- Not measurable: "users find it useful" → *"How will you measure that? What does a user doing in practice tell you it worked?"*
|
||||
- Too vague: "it's faster" → *"Faster by how much? p50 latency? Time-to-complete a specific workflow?"*
|
||||
- Only one criterion → *"That's one. What else would tell you this was a success?"*
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Pragmatic check
|
||||
|
||||
Before writing anything, ask the user directly:
|
||||
|
||||
*"Three quick honest questions:*
|
||||
*1. Does this problem deserve to be solved at all — or is there a simpler workaround that's good enough?*
|
||||
*2. Does it have to be done this way — or is there a non-code solution?*
|
||||
*3. Does it have to be done by you — or does something already exist that could be adopted?"*
|
||||
|
||||
If any of these raise real doubt, say so clearly: *"I want to flag this because [reason]. Does that change how you're thinking about this?"*
|
||||
|
||||
Do not just fill in the template to move forward if the pragmatic check surfaces a genuine concern. Surface it and let the user decide with full information.
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Draft and approval
|
||||
|
||||
Once all five questions have been answered and the pragmatic check is done:
|
||||
|
||||
1. **Write a draft** of the complete problem statement in your response — fill in every section with the user's actual answers, not placeholders.
|
||||
2. **Use ExitPlanMode** to present it for review.
|
||||
3. Ask: *"Does this accurately capture what we discussed? Any corrections before I write the file?"*
|
||||
4. On approval, write the file:
|
||||
|
||||
Write the full content to `project-management/Background/00-problem-statement.md`, replacing all placeholder text with the real content from the conversation. Set both dates to today's date.
|
||||
|
||||
The file must have **no** placeholder text remaining — no `[Replace with`, no `YYYY-MM-DD`, no `[Outcome 1`, no `NOT [scope limit`.
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Bridge to DESIGN
|
||||
|
||||
After writing the file, say:
|
||||
|
||||
---
|
||||
|
||||
✅ **SEED phase complete.** `project-management/Background/00-problem-statement.md` is filled in and committed.
|
||||
|
||||
**Next step — DESIGN:** turn this problem into a spec with GitHub Spec Kit.
|
||||
|
||||
```
|
||||
/speckit-specify "[brief description of the first feature]"
|
||||
```
|
||||
|
||||
Then continue with `/speckit-plan` → `/speckit-tasks`. This produces `specs/[feature]/`:
|
||||
- `spec.md` — the feature specification with testable acceptance criteria
|
||||
- `plan.md` — architecture, components, diagrams
|
||||
- `tasks.md` — tracer bullet breakdown
|
||||
|
||||
(In Claude Code these are skills: `speckit-specify`, `speckit-plan`, `speckit-tasks`.)
|
||||
|
||||
Or, if you want to check the strategic landscape first (build vs buy decision?):
|
||||
```
|
||||
/design:wardley [topic]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Create a TodoWrite entry: `SEED complete — run /speckit-specify to start DESIGN`.
|
||||
|
||||
---
|
||||
|
||||
## Constraints
|
||||
|
||||
- **Never** accept vague answers and move on. The problem statement is the foundation — if it is weak, everything built on it is wrong.
|
||||
- **Never** skip the pragmatic check. It is not a formality.
|
||||
- **Never** write the file with placeholder text still in it.
|
||||
- The five questions may generate a conversation that takes several exchanges. That is correct — don't rush to the template.
|
||||
- If the user provides `$ARGUMENTS`, use it to orient the conversation, not to skip questions.
|
||||
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"_comment": "BEACON-managed Claude Code hooks. Re-installed by `beacon upgrade`. Disable specific hooks via doctor.toml's [hooks] disabled = [...].",
|
||||
"hooks": {
|
||||
"PostToolUse": [
|
||||
{
|
||||
"_name": "auto-lint",
|
||||
"_purpose": "auto-lint after Claude edits \u2014 runs the project's lint+format on the touched file (only under src/ or tests/)",
|
||||
"matcher": "Edit|Write",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "FILE=$(jq -r .tool_input.file_path); case \"$FILE\" in src/*|tests/*) uv run ruff check --fix && uv run ruff format ;; esac"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"_name": "auto-doctor",
|
||||
"_purpose": "auto-doctor after `beacon bullet finish` \u2014 surfaces drift the moment a bullet closes",
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "CMD=$(jq -r .tool_input.command); case \"$CMD\" in *\"beacon bullet finish\"*) beacon doctor || true ;; esac"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"_name": "auto-roadmap",
|
||||
"_purpose": "auto-roadmap \u2014 regenerates ROADMAP.md when epic or bullet state changes; disable via doctor.toml [hooks] disabled = [\"auto-roadmap\"]",
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "CMD=$(jq -r .tool_input.command); case \"$CMD\" in *\"beacon epic new\"*|*\"beacon epic finish\"*|*\"beacon epic archive\"*|*\"beacon bullet finish\"*) beacon roadmap export || true ;; esac"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"_name": "auto-audit",
|
||||
"_purpose": "auto-audit (post) \u2014 nudges toward /beacon.audit after an epic is stubbed (design moment) or a spec lands (build moment, opt-in). Tune which moments fire via doctor.toml [audit] moments; disable entirely via [hooks] disabled = [\"auto-audit\"]",
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "CMD=$(jq -r .tool_input.command); beacon audit-nudge --phase post \"$CMD\" || true"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PreToolUse": [
|
||||
{
|
||||
"_name": "auto-audit",
|
||||
"_purpose": "auto-audit (pre) \u2014 nudges toward /beacon.audit before `beacon epic finish` seals an epic (ship moment). Tune via doctor.toml [audit] moments; disable via [hooks] disabled = [\"auto-audit\"]",
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "CMD=$(jq -r .tool_input.command); beacon audit-nudge --phase pre \"$CMD\" || true"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"_name": "manifest-write-protect",
|
||||
"_purpose": "manifest write-protect \u2014 `project-management/.beacon/init-options.json` is BEACON-owned; route changes through `beacon init/upgrade` so versioning + drift checks stay accurate",
|
||||
"matcher": "Edit|Write",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "FILE=$(jq -r .tool_input.file_path); case \"$FILE\" in *project-management/.beacon/init-options.json) echo 'beacon: refusing to edit the BEACON manifest directly. Use `beacon init` / `beacon upgrade` instead.' >&2; exit 2 ;; esac"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
---
|
||||
name: beacon-build
|
||||
description: BEACON BUILD phase — implement one tracer bullet per session with test-first discipline. Use when the user says "Starting bullet #N", "Working on [task]", or "Implementing [feature from spec]".
|
||||
---
|
||||
|
||||
# Phase 3: BUILD — Execute one tracer bullet at a time
|
||||
|
||||
**Purpose:** Implement the tracer bullets from `tasks.md`, one per session. Ship working software daily.
|
||||
|
||||
**Entry triggers:** "Starting bullet #N" / "Working on [task]" / "Implementing [feature from spec]"
|
||||
|
||||
**Tool:** `/speckit-implement` — executes the tasks in `specs/[feature]/tasks.md`
|
||||
|
||||
---
|
||||
|
||||
## Session rhythm
|
||||
|
||||
### Start of session
|
||||
1. Fix any broken windows first (failing tests, lint warnings, TODOs) — max 15 min. If it takes longer, create a ticket and park it.
|
||||
2. Confirm today's epic + bullet: `beacon bullet status` (shows what this worktree is for).
|
||||
3. Pull latest from `develop`: `git fetch origin && git checkout develop && git pull`
|
||||
4. Create feature branch: `/git:feature <bullet-description>` (or `/speckit-specify` for a spec-driven feature).
|
||||
5. Run `beacon bullet start [<title>] [--epic <slug>]` to register the bullet (records an entry in the committed `project-management/.beacon/bullets.toml` for non-spec branches; for spec branches resolves the active task from `tasks.md`).
|
||||
6. Write the **acceptance test first** (from the acceptance criteria in `spec.md`).
|
||||
7. Create `project-management/Work/sessions/YYYY-MM-DD-[bullet].md`.
|
||||
|
||||
### During implementation
|
||||
- One bullet per worktree (parallel agents are fine — each in its own worktree). When scope creep appears in the current bullet, write the idea in `project-management/Work/planning/future-features.md` and return to the bullet.
|
||||
- Test-first: write failing test → implement minimum code → make it pass → refactor.
|
||||
- Commit after each meaningful increment: `feat(scope): implement [thing]`
|
||||
- Verify all previous bullets still pass before moving on.
|
||||
|
||||
### End of session
|
||||
1. Run full BEACON quality gates (configured for **Python** — see `manifest.language`):
|
||||
```bash
|
||||
uv run ruff check --fix && uv run ruff format
|
||||
uv run ty check
|
||||
beacon doctor --strict # semantic health — fails on placeholders, drift, stale notes
|
||||
```
|
||||
2. Run `beacon bullet finish` (flips the task done in `tasks.md` for spec branches; drops the `bullets.toml` entry for non-spec).
|
||||
3. `beacon bullet list` to regenerate the project dashboard.
|
||||
4. Write any cross-spec decisions as ADRs and link them in the relevant epic's `## ADRs` section (don't leave them in session notes).
|
||||
5. Open PR to `develop`: `/git:pr`
|
||||
6. Ask: **"Would I sign my name to this?"**
|
||||
|
||||
---
|
||||
|
||||
## Quality gates (non-negotiable before marking a bullet done)
|
||||
|
||||
- [ ] Acceptance test passes (from the spec's acceptance criteria)
|
||||
- [ ] All other tests still pass
|
||||
- [ ] `uv run ruff check` clean
|
||||
- [ ] `uv run ruff format --check` clean
|
||||
- [ ] `uv run ty check` clean
|
||||
- [ ] `beacon doctor --strict` exits 0
|
||||
- [ ] No new mocks without documented justification
|
||||
- [ ] Commit messages follow Conventional Commits
|
||||
- [ ] Can demo the working feature to a non-technical person
|
||||
|
||||
> **Optional — re-audit epic coverage as specs land.** On a multi-spec epic you
|
||||
> can re-run `/beacon.audit <epic>` each time a spec ships, to catch scope that
|
||||
> drifted out of view. Off by default; enable the *build* moment so the
|
||||
> `auto-audit` hook nudges you after `beacon spec finish`:
|
||||
> `[audit] moments = ["design", "ship", "build"]` in `project-management/.beacon/doctor.toml`.
|
||||
|
||||
---
|
||||
|
||||
## Emergency procedures
|
||||
|
||||
**Stuck for >2 hours:**
|
||||
1. Document the blocker in the session file.
|
||||
2. Can you fake/stub this part temporarily? Do it and note the debt.
|
||||
3. Can you split the bullet? Create two smaller ones in `tasks.md`.
|
||||
4. Does a decision need making? Write an ADR draft.
|
||||
|
||||
**Bullet legitimately can't fit in 4h:**
|
||||
The 2–4h timebox is a forcing function, not a physical law. When the unit of work genuinely can't be split — a non-trivial migration, an integration with a long handshake — apply one of four stubbing strategies:
|
||||
|
||||
| Strategy | When |
|
||||
|---|---|
|
||||
| **Stub the dependency** | Work depends on an external service; ship today with a Fake, swap for Real tomorrow |
|
||||
| **Feature-flag the half-implementation** | Partial code path acceptable; gated flag, full impl in the next bullet |
|
||||
| **Migrate by shadowing** | Data-shape change; write to both old and new, switch reads later, drop old |
|
||||
| **Hardcoded happy path first** | Big feature with deferrable error handling; ship happy path, add edges later |
|
||||
|
||||
Whichever you pick, today's shipped unit must have a passing acceptance test, touch all layers (the stub counts), be visible to the user (even behind a flag), and be deployable as-is. Full guidance in `BEACON.md` under `<bullets_that_will_not_fit>`. **The wrong response is silent**: stretching the bullet to 8h and pretending the constraint didn't apply.
|
||||
|
||||
**New bullet breaks an old one:**
|
||||
1. Revert to last working state.
|
||||
2. Write an integration test that catches the regression.
|
||||
3. Fix minimally — do not expand scope.
|
||||
|
||||
**Scope creep appearing:**
|
||||
1. STOP.
|
||||
2. Write the idea in `Work/planning/future-features.md`.
|
||||
3. Revert any out-of-scope changes.
|
||||
4. Finish the current bullet.
|
||||
@@ -0,0 +1,126 @@
|
||||
---
|
||||
name: beacon-design
|
||||
description: BEACON DESIGN phase — make architecture decisions and decompose work into daily-shippable tracer bullets. Pairs with Spec Kit's spec workflow. Use when the user says "How should I architect…", "Design this feature", or "Break this down into bullets".
|
||||
---
|
||||
|
||||
# Phase 2: DESIGN — Architecture and tracer bullet decomposition
|
||||
|
||||
**Purpose:** Make key decisions, document them as ADRs, and break work into daily-shippable tracer bullets.
|
||||
|
||||
**Entry triggers:** "How should I architect…" / "Design this feature" / "Break this down into bullets" / "Plan this initiative" / "New quarter, what are we building" *(epic-level)*
|
||||
|
||||
**DESIGN happens at two scales:**
|
||||
|
||||
1. **Epic-level** — cross-spec architectural decisions for a weeks-scope initiative.
|
||||
These span multiple SpecKit specs ("OAuth vs own auth", "which database", "build vs buy",
|
||||
"where is this on the Wardley Map") and so cannot live in any single `spec.md`.
|
||||
**Creating or editing an epic IS a DESIGN-phase activity.** Produce ADRs in
|
||||
`project-management/ADRs/` and link them in the epic's `## ADRs` section.
|
||||
This is the gap BEACON fills that SpecKit alone cannot.
|
||||
|
||||
2. **Spec-level** — feature-scoped scenarios + plan + tasks. SpecKit owns this:
|
||||
`/speckit-specify` → `/speckit-plan` → `/speckit-tasks`.
|
||||
|
||||
When in doubt: **if the decision affects more than one spec, it's epic-level**.
|
||||
|
||||
**Tools:**
|
||||
- **Spec Kit** — the spec workflow: `/speckit-specify` → `/speckit-plan` → `/speckit-tasks` — the primary DESIGN output
|
||||
- `/design:wardley <topic>` — Wardley Map for strategic landscape analysis; run **before** `/speckit-specify` when a build-vs-buy decision is involved
|
||||
- `/design:evaluate <component>` — scored technology evaluation (build/OSS/SaaS); run when the design surfaces a significant technology choice
|
||||
|
||||
---
|
||||
|
||||
## DESIGN at the epic level (do this FIRST for new initiatives)
|
||||
|
||||
If this work is the start of a new initiative (not just a feature within an existing epic):
|
||||
|
||||
1. **Create the epic:** `beacon epic new <slug> --title "<Title>"`.
|
||||
2. **Fill in the body** — vision, why-now, success criteria, non-goals. Three paragraphs of work; do it properly.
|
||||
3. **Surface cross-spec decisions and write ADRs** — for each significant architectural choice that affects more than one spec:
|
||||
- `/design:wardley <topic>` if the build-vs-buy question is open
|
||||
- `/design:evaluate <component>` for scored technology comparison
|
||||
- Write the decision as `project-management/ADRs/ADR-NNN-name.md` (MADR format)
|
||||
- Link the ADR from the epic's `## ADRs` section
|
||||
4. **Decompose the intended scope into stubs** — for each work-area the epic's
|
||||
Success criteria imply but you're not building yet, create a placeholder
|
||||
spec: `beacon epic stub <slug> "<title>" ["<title>" …]`. A stub blocks
|
||||
`beacon epic finish` until it's filled in, so the epic can't be archived with
|
||||
scope still on paper.
|
||||
5. **Audit the decomposition** — run `/beacon.audit <slug>`. The read-only
|
||||
`beacon-auditor` subagent checks every Success criterion has an owning
|
||||
spec/stub and reports the gaps (with ready-to-run `beacon epic stub` lines).
|
||||
`Status: clear` means the epic is fully broken out and ready to build. This
|
||||
is a default-on *design* moment — the `auto-audit` hook also nudges you here
|
||||
right after `beacon epic stub`.
|
||||
6. **Only then** start the first spec with `/speckit-specify` (or `beacon specify --epic <slug>` when the wrapper ships).
|
||||
|
||||
An epic with no ADRs after it's been Active for a while is a smell — it means
|
||||
the cross-spec decisions were either trivial (rare) or skipped (more common,
|
||||
more dangerous). `beacon doctor` will WARN.
|
||||
|
||||
## What DESIGN produces
|
||||
|
||||
### 1. Spec (via Spec Kit)
|
||||
|
||||
Run `/speckit-specify` → `/speckit-plan` → `/speckit-tasks`. This produces three
|
||||
files in `specs/[feature-name]/`:
|
||||
|
||||
- **`spec.md`** — user scenarios and acceptance criteria; each is directly testable
|
||||
- **`plan.md`** — architecture, components, data models, and diagrams:
|
||||
- Sequence (happy path + errors, `autonumber`)
|
||||
- ERD (when data entities are introduced/changed)
|
||||
- Component diagram (when ≥3 internal components)
|
||||
- State diagram (when an entity lifecycle is involved)
|
||||
- **`tasks.md`** — tracer bullet breakdown
|
||||
|
||||
Optionally run `/speckit-clarify` before `/speckit-plan` to de-risk ambiguity,
|
||||
and `/speckit-analyze` after `/speckit-tasks` to check cross-artifact consistency.
|
||||
|
||||
Use `/design:diagram <component>` to generate or regenerate any diagram.
|
||||
|
||||
### 2. Architecture document
|
||||
|
||||
Update `project-management/Background/01-final-architecture-document.md` if the design changes the overall system architecture.
|
||||
|
||||
### 3. ADRs
|
||||
|
||||
For every decision that is hard to reverse or involves a real tradeoff, create an ADR in `project-management/ADRs/` using MADR format (see `ADR-000-template.md`).
|
||||
|
||||
---
|
||||
|
||||
## Tracer bullet rules
|
||||
|
||||
Each task in `tasks.md` must be a tracer bullet:
|
||||
|
||||
| Rule | Why |
|
||||
|------|-----|
|
||||
| Vertical slice — touches all layers | Proves plumbing works end-to-end |
|
||||
| Takes 2–4 hours maximum | Forces scope discipline; split if larger |
|
||||
| User-visible output | Can be demoed; progress is tangible |
|
||||
| Previous bullets still pass | No regressions |
|
||||
| Could deploy as-is (even if limited) | Maintains "always deployable" invariant |
|
||||
|
||||
**Anti-pattern:** planning entire layers separately (all database first, then all API, then all UI). This produces nothing shippable until day N.
|
||||
|
||||
---
|
||||
|
||||
## Design checklist
|
||||
|
||||
Before leaving DESIGN:
|
||||
|
||||
- [ ] Strategic landscape checked — if a build-vs-buy decision exists, `/design:wardley` run and saved to `Work/analysis/`
|
||||
- [ ] Technology choices evaluated — if a significant technology was selected, `/design:evaluate` run and ADR created
|
||||
- [ ] Epic decomposition audited — `/beacon.audit <slug>` returns `Status: clear` (every Success criterion has an owning spec/stub)
|
||||
- [ ] `spec.md` complete — user scenarios and testable acceptance criteria
|
||||
- [ ] `plan.md` complete — actual file paths, component names, data models, and diagrams:
|
||||
- [ ] Sequence (happy path with `autonumber`)
|
||||
- [ ] Sequence (error/alternative paths)
|
||||
- [ ] ERD (if data entities introduced/changed)
|
||||
- [ ] Component diagram (if ≥3 internal components)
|
||||
- [ ] State diagram (if entity lifecycle involved)
|
||||
- [ ] `tasks.md` complete — bullets sequenced, each ≤4h
|
||||
- [ ] ADRs written for all major decisions
|
||||
- [ ] `01-final-architecture-document.md` updated if architecture changed
|
||||
- [ ] `project-management/Roadmap/README.md` updated with new bullets
|
||||
|
||||
**Do not start BUILD until `spec.md`, `plan.md`, and `tasks.md` are complete.**
|
||||
@@ -0,0 +1,68 @@
|
||||
---
|
||||
name: beacon-seed
|
||||
description: BEACON SEED phase — evaluate whether a new idea deserves to exist before any code is written. Use when a user opens with "I have an idea for…", "Should I build…", or any new-project trigger.
|
||||
---
|
||||
|
||||
# Phase 1: SEED — Does this idea deserve to exist?
|
||||
|
||||
**Purpose:** Evaluate a new idea rigorously before committing to building it.
|
||||
|
||||
**Entry triggers:** "I have an idea for…" / "Should I build…" / new project start
|
||||
|
||||
---
|
||||
|
||||
## The SEED Questions
|
||||
|
||||
Answer these honestly before writing any code:
|
||||
|
||||
### 1. What specific problem are you solving?
|
||||
|
||||
One sentence. Not a category, not a theme — a specific, painful problem for a specific person.
|
||||
|
||||
> Bad: "Improve developer productivity"
|
||||
> Good: "Data analysts waste 2 hours/day querying Fabric semantic models manually when they could describe what they want in plain English"
|
||||
|
||||
### 2. Who has this problem?
|
||||
|
||||
Name them specifically. What is their role, context, and current workaround?
|
||||
|
||||
### 3. What are three existing solutions?
|
||||
|
||||
Before building, find what already exists. Why is each insufficient?
|
||||
|
||||
1. **[Solution A]** — why it fails: …
|
||||
2. **[Solution B]** — why it fails: …
|
||||
3. **[Solution C]** — why it fails: …
|
||||
|
||||
### 4. Is your solution 10x simpler than the alternatives?
|
||||
|
||||
If not, reconsider. Building something marginally better than an existing tool is rarely worth it.
|
||||
|
||||
### 5. What are you explicitly NOT building?
|
||||
|
||||
Non-goals prevent scope creep. Name them now.
|
||||
|
||||
---
|
||||
|
||||
## SEED Deliverable
|
||||
|
||||
Create `project-management/Background/00-problem-statement.md` using the template.
|
||||
|
||||
A SEED phase is complete when:
|
||||
- [ ] One specific problem is named
|
||||
- [ ] One specific user is named
|
||||
- [ ] Success criteria are measurable
|
||||
- [ ] Non-goals are explicit
|
||||
- [ ] At least three alternatives have been considered and rejected
|
||||
|
||||
Run `beacon doctor` after writing the problem statement — it will catch placeholder text that survived edits.
|
||||
|
||||
**Do not proceed to DESIGN until these are answered.**
|
||||
|
||||
---
|
||||
|
||||
## Pragmatic check
|
||||
|
||||
Ask: "Does this problem deserve to be solved *at all*? Does it have to be done this way? Does it have to be done by me?"
|
||||
|
||||
If the answer to any of these is uncertain, explore more before committing.
|
||||
@@ -0,0 +1,109 @@
|
||||
---
|
||||
name: beacon-ship
|
||||
description: BEACON SHIP phase — promote validated DEV work to PROD, extract wisdom, and clean the transient workspace. Use when all bullets are complete or the user says "Ready to ship" / "Release this".
|
||||
---
|
||||
|
||||
# Phase 4: SHIP — Release and extract wisdom
|
||||
|
||||
**Purpose:** Promote validated DEV work to PROD, document what was learned, and clean the transient workspace.
|
||||
|
||||
**Entry triggers:** All bullets complete / "Ready to ship" / "Release this"
|
||||
|
||||
**Tool:** `/git:release`
|
||||
|
||||
---
|
||||
|
||||
## SHIP checklist
|
||||
|
||||
### Before opening the release PR
|
||||
|
||||
- [ ] All bullets in `tasks.md` are marked complete
|
||||
- [ ] All tests pass on `develop`
|
||||
- [ ] Last CI run on `develop` is green (`gh run list --branch develop --limit 3`)
|
||||
- [ ] Features have been validated in the DEV environment by a human
|
||||
- [ ] `beacon doctor --strict` exits 0
|
||||
- [ ] No known regressions
|
||||
- [ ] **Documentation pass** (see below)
|
||||
- [ ] Azure DevOps work items updated (Done/Closed) — use `@azure-devops-agent`
|
||||
|
||||
### Documentation pass — the external surface
|
||||
|
||||
Internal rigor (specs, ADRs, tests) doesn't help a stranger pick the project
|
||||
up. Before shipping, check the user-facing surface — BEACON organises it with
|
||||
[Diátaxis](https://diataxis.fr/) (tutorials / how-to / reference / explanation):
|
||||
|
||||
- [ ] **README** orients a newcomer: what-is-this, install, quickstart, a link out
|
||||
- [ ] **CHANGELOG** has an `Unreleased` section capturing this release's notable changes
|
||||
- [ ] **Public API** is real: the package root re-exports its surface (`__all__` / docstring), not empty
|
||||
- [ ] **`pyproject [project.urls]`** points to the repo + docs
|
||||
- [ ] **Which Diátaxis quadrant** did this change touch — and is there drift between quadrants (a how-to creeping into tutorial voice, a reference page lagging its code)?
|
||||
- [ ] `beacon docs verify` is green (executable `# beacon:test` tutorial snippets still run)
|
||||
|
||||
`beacon doctor` enforces the mechanical floor of the above
|
||||
(`readme-completeness`, `changelog-maintenance`, `changelog-vs-commits`,
|
||||
`public-api-docs`, `project-urls`, `docs-freshness`, opt-in `diataxis-coverage`).
|
||||
|
||||
### Open the release PR
|
||||
|
||||
Run `/git:release` — this:
|
||||
1. Checks DEV CI is green
|
||||
2. Generates a changelog from Conventional Commits since last release
|
||||
3. Opens a `develop → main` PR using `.github/PULL_REQUEST_TEMPLATE/release.md`
|
||||
|
||||
The PR requires a human approval in GitHub Actions (configured in Settings → Environments → prod).
|
||||
|
||||
### After the PR merges
|
||||
|
||||
1. **Tag the release:**
|
||||
```bash
|
||||
git checkout main && git pull
|
||||
git tag -a vX.Y.Z -m "Release vX.Y.Z"
|
||||
git push origin vX.Y.Z
|
||||
```
|
||||
|
||||
2. **Clean `Work/`** — transient notes belong in git history, not the repo:
|
||||
```bash
|
||||
cd project-management/Work
|
||||
rm -rf sessions/* # session notes are in git history
|
||||
rm -rf planning/* # keep only active WIP
|
||||
rm -rf analysis/* # promote to ADRs or delete
|
||||
```
|
||||
|
||||
3. **Promote insights** — before deleting, check:
|
||||
- Did any session note contain a decision? → Write an ADR.
|
||||
- Did any analysis reveal a pattern worth keeping? → Add to Background/.
|
||||
|
||||
4. **Retrospective** (optional but valuable):
|
||||
Create `project-management/Work/analysis/retro-vX.Y.Z.md` with:
|
||||
- What worked well
|
||||
- What was harder than expected
|
||||
- One thing to do differently next time
|
||||
Then promote the worthwhile parts to ADRs and delete the file.
|
||||
|
||||
5. **Update Roadmap** — archive the current roadmap, start fresh:
|
||||
```bash
|
||||
mv project-management/Roadmap/README.md \
|
||||
project-management/Roadmap/archive/vX.Y.Z-$(date +%Y-%m-%d).md
|
||||
# Create new README.md for next release
|
||||
```
|
||||
|
||||
6. **Close any completed epic** — if this release shipped an epic's last spec,
|
||||
audit it, then archive it:
|
||||
```bash
|
||||
/beacon.audit <slug> # confirm every Success criterion was actually shipped
|
||||
beacon epic finish <slug> # archive (refuses while a stub or live branch remains)
|
||||
```
|
||||
The audit is a default-on *ship* moment — the `auto-audit` hook also nudges
|
||||
you right before `beacon epic finish`. It catches a Success criterion that no
|
||||
spec ever covered, which the deterministic placeholder gate can't see.
|
||||
|
||||
---
|
||||
|
||||
## The SHIP test
|
||||
|
||||
Before calling it done, answer:
|
||||
- Does it work end-to-end in PROD?
|
||||
- Is the code something you'd be proud to show a colleague?
|
||||
- Could the next person understand what was built and why?
|
||||
|
||||
If no to any of these, it is not shipped — it is just deployed.
|
||||
@@ -0,0 +1,260 @@
|
||||
---
|
||||
name: "speckit-analyze"
|
||||
description: "Perform a non-destructive cross-artifact consistency and quality analysis across spec.md, plan.md, and tasks.md after task generation."
|
||||
argument-hint: "Optional focus areas for analysis"
|
||||
compatibility: "Requires spec-kit project structure with .specify/ directory"
|
||||
metadata:
|
||||
author: "github-spec-kit"
|
||||
source: "templates/commands/analyze.md"
|
||||
user-invocable: true
|
||||
disable-model-invocation: false
|
||||
---
|
||||
|
||||
|
||||
## User Input
|
||||
|
||||
```text
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
You **MUST** consider the user input before proceeding (if not empty).
|
||||
|
||||
## Pre-Execution Checks
|
||||
|
||||
**Check for extension hooks (before analysis)**:
|
||||
- Check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it exists, read it and look for entries under the `hooks.before_analyze` key
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Pre-Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
- **Mandatory hook** (`optional: false`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Pre-Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
|
||||
Wait for the result of the hook command before proceeding to the Goal.
|
||||
```
|
||||
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||
|
||||
## Goal
|
||||
|
||||
Identify inconsistencies, duplications, ambiguities, and underspecified items across the three core artifacts (`spec.md`, `plan.md`, `tasks.md`) before implementation. This command MUST run only after `/speckit-tasks` has successfully produced a complete `tasks.md`.
|
||||
|
||||
## Operating Constraints
|
||||
|
||||
**STRICTLY READ-ONLY**: Do **not** modify any files. Output a structured analysis report. Offer an optional remediation plan (user must explicitly approve before any follow-up editing commands would be invoked manually).
|
||||
|
||||
**Constitution Authority**: The project constitution (`.specify/memory/constitution.md`) is **non-negotiable** within this analysis scope. Constitution conflicts are automatically CRITICAL and require adjustment of the spec, plan, or tasks—not dilution, reinterpretation, or silent ignoring of the principle. If a principle itself needs to change, that must occur in a separate, explicit constitution update outside `/speckit-analyze`.
|
||||
|
||||
## Execution Steps
|
||||
|
||||
### 1. Initialize Analysis Context
|
||||
|
||||
Run `.specify/scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks` once from repo root and parse JSON for FEATURE_DIR and AVAILABLE_DOCS. Derive absolute paths:
|
||||
|
||||
- SPEC = FEATURE_DIR/spec.md
|
||||
- PLAN = FEATURE_DIR/plan.md
|
||||
- TASKS = FEATURE_DIR/tasks.md
|
||||
|
||||
Abort with an error message if any required file is missing (instruct the user to run missing prerequisite command).
|
||||
For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot").
|
||||
|
||||
### 2. Load Artifacts (Progressive Disclosure)
|
||||
|
||||
Load only the minimal necessary context from each artifact:
|
||||
|
||||
**From spec.md:**
|
||||
|
||||
- Overview/Context
|
||||
- Functional Requirements
|
||||
- Success Criteria (measurable outcomes — e.g., performance, security, availability, user success, business impact)
|
||||
- User Stories
|
||||
- Edge Cases (if present)
|
||||
|
||||
**From plan.md:**
|
||||
|
||||
- Architecture/stack choices
|
||||
- Data Model references
|
||||
- Phases
|
||||
- Technical constraints
|
||||
|
||||
**From tasks.md:**
|
||||
|
||||
- Task IDs
|
||||
- Descriptions
|
||||
- Phase grouping
|
||||
- Parallel markers [P]
|
||||
- Referenced file paths
|
||||
|
||||
**From constitution:**
|
||||
|
||||
- Load `.specify/memory/constitution.md` for principle validation
|
||||
|
||||
### 3. Build Semantic Models
|
||||
|
||||
Create internal representations (do not include raw artifacts in output):
|
||||
|
||||
- **Requirements inventory**: For each Functional Requirement (FR-###) and Success Criterion (SC-###), record a stable key. Use the explicit FR-/SC- identifier as the primary key when present, and optionally also derive an imperative-phrase slug for readability (e.g., "User can upload file" → `user-can-upload-file`). Include only Success Criteria items that require buildable work (e.g., load-testing infrastructure, security audit tooling), and exclude post-launch outcome metrics and business KPIs (e.g., "Reduce support tickets by 50%").
|
||||
- **User story/action inventory**: Discrete user actions with acceptance criteria
|
||||
- **Task coverage mapping**: Map each task to one or more requirements or stories (inference by keyword / explicit reference patterns like IDs or key phrases)
|
||||
- **Constitution rule set**: Extract principle names and MUST/SHOULD normative statements
|
||||
|
||||
### 4. Detection Passes (Token-Efficient Analysis)
|
||||
|
||||
Focus on high-signal findings. Limit to 50 findings total; aggregate remainder in overflow summary.
|
||||
|
||||
#### A. Duplication Detection
|
||||
|
||||
- Identify near-duplicate requirements
|
||||
- Mark lower-quality phrasing for consolidation
|
||||
|
||||
#### B. Ambiguity Detection
|
||||
|
||||
- Flag vague adjectives (fast, scalable, secure, intuitive, robust) lacking measurable criteria
|
||||
- Flag unresolved placeholders (TODO, TKTK, ???, `<placeholder>`, etc.)
|
||||
|
||||
#### C. Underspecification
|
||||
|
||||
- Requirements with verbs but missing object or measurable outcome
|
||||
- User stories missing acceptance criteria alignment
|
||||
- Tasks referencing files or components not defined in spec/plan
|
||||
|
||||
#### D. Constitution Alignment
|
||||
|
||||
- Any requirement or plan element conflicting with a MUST principle
|
||||
- Missing mandated sections or quality gates from constitution
|
||||
|
||||
#### E. Coverage Gaps
|
||||
|
||||
- Requirements with zero associated tasks
|
||||
- Tasks with no mapped requirement/story
|
||||
- Success Criteria requiring buildable work (performance, security, availability) not reflected in tasks
|
||||
|
||||
#### F. Inconsistency
|
||||
|
||||
- Terminology drift (same concept named differently across files)
|
||||
- Data entities referenced in plan but absent in spec (or vice versa)
|
||||
- Task ordering contradictions (e.g., integration tasks before foundational setup tasks without dependency note)
|
||||
- Conflicting requirements (e.g., one requires Next.js while other specifies Vue)
|
||||
|
||||
### 5. Severity Assignment
|
||||
|
||||
Use this heuristic to prioritize findings:
|
||||
|
||||
- **CRITICAL**: Violates constitution MUST, missing core spec artifact, or requirement with zero coverage that blocks baseline functionality
|
||||
- **HIGH**: Duplicate or conflicting requirement, ambiguous security/performance attribute, untestable acceptance criterion
|
||||
- **MEDIUM**: Terminology drift, missing non-functional task coverage, underspecified edge case
|
||||
- **LOW**: Style/wording improvements, minor redundancy not affecting execution order
|
||||
|
||||
### 6. Produce Compact Analysis Report
|
||||
|
||||
Output a Markdown report (no file writes) with the following structure:
|
||||
|
||||
## Specification Analysis Report
|
||||
|
||||
| ID | Category | Severity | Location(s) | Summary | Recommendation |
|
||||
|----|----------|----------|-------------|---------|----------------|
|
||||
| A1 | Duplication | HIGH | spec.md:L120-134 | Two similar requirements ... | Merge phrasing; keep clearer version |
|
||||
|
||||
(Add one row per finding; generate stable IDs prefixed by category initial.)
|
||||
|
||||
**Coverage Summary Table:**
|
||||
|
||||
| Requirement Key | Has Task? | Task IDs | Notes |
|
||||
|-----------------|-----------|----------|-------|
|
||||
|
||||
**Constitution Alignment Issues:** (if any)
|
||||
|
||||
**Unmapped Tasks:** (if any)
|
||||
|
||||
**Metrics:**
|
||||
|
||||
- Total Requirements
|
||||
- Total Tasks
|
||||
- Coverage % (requirements with >=1 task)
|
||||
- Ambiguity Count
|
||||
- Duplication Count
|
||||
- Critical Issues Count
|
||||
|
||||
### 7. Provide Next Actions
|
||||
|
||||
At end of report, output a concise Next Actions block:
|
||||
|
||||
- If CRITICAL issues exist: Recommend resolving before `/speckit-implement`
|
||||
- If only LOW/MEDIUM: User may proceed, but provide improvement suggestions
|
||||
- Provide explicit command suggestions: e.g., "Run /speckit-specify with refinement", "Run /speckit-plan to adjust architecture", "Manually edit tasks.md to add coverage for 'performance-metrics'"
|
||||
|
||||
### 8. Offer Remediation
|
||||
|
||||
Ask the user: "Would you like me to suggest concrete remediation edits for the top N issues?" (Do NOT apply them automatically.)
|
||||
|
||||
### 9. Check for extension hooks
|
||||
|
||||
After reporting, check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it exists, read it and look for entries under the `hooks.after_analyze` key
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
- **Mandatory hook** (`optional: false`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
```
|
||||
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||
|
||||
## Operating Principles
|
||||
|
||||
### Context Efficiency
|
||||
|
||||
- **Minimal high-signal tokens**: Focus on actionable findings, not exhaustive documentation
|
||||
- **Progressive disclosure**: Load artifacts incrementally; don't dump all content into analysis
|
||||
- **Token-efficient output**: Limit findings table to 50 rows; summarize overflow
|
||||
- **Deterministic results**: Rerunning without changes should produce consistent IDs and counts
|
||||
|
||||
### Analysis Guidelines
|
||||
|
||||
- **NEVER modify files** (this is read-only analysis)
|
||||
- **NEVER hallucinate missing sections** (if absent, report them accurately)
|
||||
- **Prioritize constitution violations** (these are always CRITICAL)
|
||||
- **Use examples over exhaustive rules** (cite specific instances, not generic patterns)
|
||||
- **Report zero issues gracefully** (emit success report with coverage statistics)
|
||||
|
||||
## Context
|
||||
|
||||
$ARGUMENTS
|
||||
@@ -0,0 +1,372 @@
|
||||
---
|
||||
name: "speckit-checklist"
|
||||
description: "Generate a custom checklist for the current feature based on user requirements."
|
||||
argument-hint: "Domain or focus area for the checklist"
|
||||
compatibility: "Requires spec-kit project structure with .specify/ directory"
|
||||
metadata:
|
||||
author: "github-spec-kit"
|
||||
source: "templates/commands/checklist.md"
|
||||
user-invocable: true
|
||||
disable-model-invocation: false
|
||||
---
|
||||
|
||||
|
||||
## Checklist Purpose: "Unit Tests for English"
|
||||
|
||||
**CRITICAL CONCEPT**: Checklists are **UNIT TESTS FOR REQUIREMENTS WRITING** - they validate the quality, clarity, and completeness of requirements in a given domain.
|
||||
|
||||
**NOT for verification/testing**:
|
||||
|
||||
- ❌ NOT "Verify the button clicks correctly"
|
||||
- ❌ NOT "Test error handling works"
|
||||
- ❌ NOT "Confirm the API returns 200"
|
||||
- ❌ NOT checking if code/implementation matches the spec
|
||||
|
||||
**FOR requirements quality validation**:
|
||||
|
||||
- ✅ "Are visual hierarchy requirements defined for all card types?" (completeness)
|
||||
- ✅ "Is 'prominent display' quantified with specific sizing/positioning?" (clarity)
|
||||
- ✅ "Are hover state requirements consistent across all interactive elements?" (consistency)
|
||||
- ✅ "Are accessibility requirements defined for keyboard navigation?" (coverage)
|
||||
- ✅ "Does the spec define what happens when logo image fails to load?" (edge cases)
|
||||
|
||||
**Metaphor**: If your spec is code written in English, the checklist is its unit test suite. You're testing whether the requirements are well-written, complete, unambiguous, and ready for implementation - NOT whether the implementation works.
|
||||
|
||||
## User Input
|
||||
|
||||
```text
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
You **MUST** consider the user input before proceeding (if not empty).
|
||||
|
||||
## Pre-Execution Checks
|
||||
|
||||
**Check for extension hooks (before checklist generation)**:
|
||||
- Check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it exists, read it and look for entries under the `hooks.before_checklist` key
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Pre-Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
- **Mandatory hook** (`optional: false`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Pre-Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
|
||||
Wait for the result of the hook command before proceeding to the Execution Steps.
|
||||
```
|
||||
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||
|
||||
## Execution Steps
|
||||
|
||||
1. **Setup**: Run `.specify/scripts/bash/check-prerequisites.sh --json` from repo root and parse JSON for FEATURE_DIR and AVAILABLE_DOCS list.
|
||||
- All file paths must be absolute.
|
||||
- For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot").
|
||||
|
||||
2. **Clarify intent (dynamic)**: Derive up to THREE initial contextual clarifying questions (no pre-baked catalog). They MUST:
|
||||
- Be generated from the user's phrasing + extracted signals from spec/plan/tasks
|
||||
- Only ask about information that materially changes checklist content
|
||||
- Be skipped individually if already unambiguous in `$ARGUMENTS`
|
||||
- Prefer precision over breadth
|
||||
|
||||
Generation algorithm:
|
||||
1. Extract signals: feature domain keywords (e.g., auth, latency, UX, API), risk indicators ("critical", "must", "compliance"), stakeholder hints ("QA", "review", "security team"), and explicit deliverables ("a11y", "rollback", "contracts").
|
||||
2. Cluster signals into candidate focus areas (max 4) ranked by relevance.
|
||||
3. Identify probable audience & timing (author, reviewer, QA, release) if not explicit.
|
||||
4. Detect missing dimensions: scope breadth, depth/rigor, risk emphasis, exclusion boundaries, measurable acceptance criteria.
|
||||
5. Formulate questions chosen from these archetypes:
|
||||
- Scope refinement (e.g., "Should this include integration touchpoints with X and Y or stay limited to local module correctness?")
|
||||
- Risk prioritization (e.g., "Which of these potential risk areas should receive mandatory gating checks?")
|
||||
- Depth calibration (e.g., "Is this a lightweight pre-commit sanity list or a formal release gate?")
|
||||
- Audience framing (e.g., "Will this be used by the author only or peers during PR review?")
|
||||
- Boundary exclusion (e.g., "Should we explicitly exclude performance tuning items this round?")
|
||||
- Scenario class gap (e.g., "No recovery flows detected—are rollback / partial failure paths in scope?")
|
||||
|
||||
Question formatting rules:
|
||||
- If presenting options, generate a compact table with columns: Option | Candidate | Why It Matters
|
||||
- Limit to A–E options maximum; omit table if a free-form answer is clearer
|
||||
- Never ask the user to restate what they already said
|
||||
- Avoid speculative categories (no hallucination). If uncertain, ask explicitly: "Confirm whether X belongs in scope."
|
||||
|
||||
Defaults when interaction impossible:
|
||||
- Depth: Standard
|
||||
- Audience: Reviewer (PR) if code-related; Author otherwise
|
||||
- Focus: Top 2 relevance clusters
|
||||
|
||||
Output the questions (label Q1/Q2/Q3). After answers: if ≥2 scenario classes (Alternate / Exception / Recovery / Non-Functional domain) remain unclear, you MAY ask up to TWO more targeted follow‑ups (Q4/Q5) with a one-line justification each (e.g., "Unresolved recovery path risk"). Do not exceed five total questions. Skip escalation if user explicitly declines more.
|
||||
|
||||
3. **Understand user request**: Combine `$ARGUMENTS` + clarifying answers:
|
||||
- Derive checklist theme (e.g., security, review, deploy, ux)
|
||||
- Consolidate explicit must-have items mentioned by user
|
||||
- Map focus selections to category scaffolding
|
||||
- Infer any missing context from spec/plan/tasks (do NOT hallucinate)
|
||||
|
||||
4. **Load feature context**: Read from FEATURE_DIR:
|
||||
- spec.md: Feature requirements and scope
|
||||
- plan.md (if exists): Technical details, dependencies
|
||||
- tasks.md (if exists): Implementation tasks
|
||||
|
||||
**Context Loading Strategy**:
|
||||
- Load only necessary portions relevant to active focus areas (avoid full-file dumping)
|
||||
- Prefer summarizing long sections into concise scenario/requirement bullets
|
||||
- Use progressive disclosure: add follow-on retrieval only if gaps detected
|
||||
- If source docs are large, generate interim summary items instead of embedding raw text
|
||||
|
||||
5. **Generate checklist** - Create "Unit Tests for Requirements":
|
||||
- Create `FEATURE_DIR/checklists/` directory if it doesn't exist
|
||||
- Generate unique checklist filename:
|
||||
- Use short, descriptive name based on domain (e.g., `ux.md`, `api.md`, `security.md`)
|
||||
- Format: `[domain].md`
|
||||
- File handling behavior:
|
||||
- If file does NOT exist: Create new file and number items starting from CHK001
|
||||
- If file exists: Append new items to existing file, continuing from the last CHK ID (e.g., if last item is CHK015, start new items at CHK016)
|
||||
- Never delete or replace existing checklist content - always preserve and append
|
||||
|
||||
**CORE PRINCIPLE - Test the Requirements, Not the Implementation**:
|
||||
Every checklist item MUST evaluate the REQUIREMENTS THEMSELVES for:
|
||||
- **Completeness**: Are all necessary requirements present?
|
||||
- **Clarity**: Are requirements unambiguous and specific?
|
||||
- **Consistency**: Do requirements align with each other?
|
||||
- **Measurability**: Can requirements be objectively verified?
|
||||
- **Coverage**: Are all scenarios/edge cases addressed?
|
||||
|
||||
**Category Structure** - Group items by requirement quality dimensions:
|
||||
- **Requirement Completeness** (Are all necessary requirements documented?)
|
||||
- **Requirement Clarity** (Are requirements specific and unambiguous?)
|
||||
- **Requirement Consistency** (Do requirements align without conflicts?)
|
||||
- **Acceptance Criteria Quality** (Are success criteria measurable?)
|
||||
- **Scenario Coverage** (Are all flows/cases addressed?)
|
||||
- **Edge Case Coverage** (Are boundary conditions defined?)
|
||||
- **Non-Functional Requirements** (Performance, Security, Accessibility, etc. - are they specified?)
|
||||
- **Dependencies & Assumptions** (Are they documented and validated?)
|
||||
- **Ambiguities & Conflicts** (What needs clarification?)
|
||||
|
||||
**HOW TO WRITE CHECKLIST ITEMS - "Unit Tests for English"**:
|
||||
|
||||
❌ **WRONG** (Testing implementation):
|
||||
- "Verify landing page displays 3 episode cards"
|
||||
- "Test hover states work on desktop"
|
||||
- "Confirm logo click navigates home"
|
||||
|
||||
✅ **CORRECT** (Testing requirements quality):
|
||||
- "Are the exact number and layout of featured episodes specified?" [Completeness]
|
||||
- "Is 'prominent display' quantified with specific sizing/positioning?" [Clarity]
|
||||
- "Are hover state requirements consistent across all interactive elements?" [Consistency]
|
||||
- "Are keyboard navigation requirements defined for all interactive UI?" [Coverage]
|
||||
- "Is the fallback behavior specified when logo image fails to load?" [Edge Cases]
|
||||
- "Are loading states defined for asynchronous episode data?" [Completeness]
|
||||
- "Does the spec define visual hierarchy for competing UI elements?" [Clarity]
|
||||
|
||||
**ITEM STRUCTURE**:
|
||||
Each item should follow this pattern:
|
||||
- Question format asking about requirement quality
|
||||
- Focus on what's WRITTEN (or not written) in the spec/plan
|
||||
- Include quality dimension in brackets [Completeness/Clarity/Consistency/etc.]
|
||||
- Reference spec section `[Spec §X.Y]` when checking existing requirements
|
||||
- Use `[Gap]` marker when checking for missing requirements
|
||||
|
||||
**EXAMPLES BY QUALITY DIMENSION**:
|
||||
|
||||
Completeness:
|
||||
- "Are error handling requirements defined for all API failure modes? [Gap]"
|
||||
- "Are accessibility requirements specified for all interactive elements? [Completeness]"
|
||||
- "Are mobile breakpoint requirements defined for responsive layouts? [Gap]"
|
||||
|
||||
Clarity:
|
||||
- "Is 'fast loading' quantified with specific timing thresholds? [Clarity, Spec §NFR-2]"
|
||||
- "Are 'related episodes' selection criteria explicitly defined? [Clarity, Spec §FR-5]"
|
||||
- "Is 'prominent' defined with measurable visual properties? [Ambiguity, Spec §FR-4]"
|
||||
|
||||
Consistency:
|
||||
- "Do navigation requirements align across all pages? [Consistency, Spec §FR-10]"
|
||||
- "Are card component requirements consistent between landing and detail pages? [Consistency]"
|
||||
|
||||
Coverage:
|
||||
- "Are requirements defined for zero-state scenarios (no episodes)? [Coverage, Edge Case]"
|
||||
- "Are concurrent user interaction scenarios addressed? [Coverage, Gap]"
|
||||
- "Are requirements specified for partial data loading failures? [Coverage, Exception Flow]"
|
||||
|
||||
Measurability:
|
||||
- "Are visual hierarchy requirements measurable/testable? [Acceptance Criteria, Spec §FR-1]"
|
||||
- "Can 'balanced visual weight' be objectively verified? [Measurability, Spec §FR-2]"
|
||||
|
||||
**Scenario Classification & Coverage** (Requirements Quality Focus):
|
||||
- Check if requirements exist for: Primary, Alternate, Exception/Error, Recovery, Non-Functional scenarios
|
||||
- For each scenario class, ask: "Are [scenario type] requirements complete, clear, and consistent?"
|
||||
- If scenario class missing: "Are [scenario type] requirements intentionally excluded or missing? [Gap]"
|
||||
- Include resilience/rollback when state mutation occurs: "Are rollback requirements defined for migration failures? [Gap]"
|
||||
|
||||
**Traceability Requirements**:
|
||||
- MINIMUM: ≥80% of items MUST include at least one traceability reference
|
||||
- Each item should reference: spec section `[Spec §X.Y]`, or use markers: `[Gap]`, `[Ambiguity]`, `[Conflict]`, `[Assumption]`
|
||||
- If no ID system exists: "Is a requirement & acceptance criteria ID scheme established? [Traceability]"
|
||||
|
||||
**Surface & Resolve Issues** (Requirements Quality Problems):
|
||||
Ask questions about the requirements themselves:
|
||||
- Ambiguities: "Is the term 'fast' quantified with specific metrics? [Ambiguity, Spec §NFR-1]"
|
||||
- Conflicts: "Do navigation requirements conflict between §FR-10 and §FR-10a? [Conflict]"
|
||||
- Assumptions: "Is the assumption of 'always available podcast API' validated? [Assumption]"
|
||||
- Dependencies: "Are external podcast API requirements documented? [Dependency, Gap]"
|
||||
- Missing definitions: "Is 'visual hierarchy' defined with measurable criteria? [Gap]"
|
||||
|
||||
**Content Consolidation**:
|
||||
- Soft cap: If raw candidate items > 40, prioritize by risk/impact
|
||||
- Merge near-duplicates checking the same requirement aspect
|
||||
- If >5 low-impact edge cases, create one item: "Are edge cases X, Y, Z addressed in requirements? [Coverage]"
|
||||
|
||||
**🚫 ABSOLUTELY PROHIBITED** - These make it an implementation test, not a requirements test:
|
||||
- ❌ Any item starting with "Verify", "Test", "Confirm", "Check" + implementation behavior
|
||||
- ❌ References to code execution, user actions, system behavior
|
||||
- ❌ "Displays correctly", "works properly", "functions as expected"
|
||||
- ❌ "Click", "navigate", "render", "load", "execute"
|
||||
- ❌ Test cases, test plans, QA procedures
|
||||
- ❌ Implementation details (frameworks, APIs, algorithms)
|
||||
|
||||
**✅ REQUIRED PATTERNS** - These test requirements quality:
|
||||
- ✅ "Are [requirement type] defined/specified/documented for [scenario]?"
|
||||
- ✅ "Is [vague term] quantified/clarified with specific criteria?"
|
||||
- ✅ "Are requirements consistent between [section A] and [section B]?"
|
||||
- ✅ "Can [requirement] be objectively measured/verified?"
|
||||
- ✅ "Are [edge cases/scenarios] addressed in requirements?"
|
||||
- ✅ "Does the spec define [missing aspect]?"
|
||||
|
||||
6. **Structure Reference**: Generate the checklist following the canonical template in `.specify/templates/checklist-template.md` for title, meta section, category headings, and ID formatting. If template is unavailable, use: H1 title, purpose/created meta lines, `##` category sections containing `- [ ] CHK### <requirement item>` lines with globally incrementing IDs starting at CHK001.
|
||||
|
||||
7. **Report**: Output full path to checklist file, item count, and summarize whether the run created a new file or appended to an existing one. Summarize:
|
||||
- Focus areas selected
|
||||
- Depth level
|
||||
- Actor/timing
|
||||
- Any explicit user-specified must-have items incorporated
|
||||
|
||||
**Important**: Each `/speckit-checklist` command invocation uses a short, descriptive checklist filename and either creates a new file or appends to an existing one. This allows:
|
||||
|
||||
- Multiple checklists of different types (e.g., `ux.md`, `test.md`, `security.md`)
|
||||
- Simple, memorable filenames that indicate checklist purpose
|
||||
- Easy identification and navigation in the `checklists/` folder
|
||||
|
||||
To avoid clutter, use descriptive types and clean up obsolete checklists when done.
|
||||
|
||||
## Example Checklist Types & Sample Items
|
||||
|
||||
**UX Requirements Quality:** `ux.md`
|
||||
|
||||
Sample items (testing the requirements, NOT the implementation):
|
||||
|
||||
- "Are visual hierarchy requirements defined with measurable criteria? [Clarity, Spec §FR-1]"
|
||||
- "Is the number and positioning of UI elements explicitly specified? [Completeness, Spec §FR-1]"
|
||||
- "Are interaction state requirements (hover, focus, active) consistently defined? [Consistency]"
|
||||
- "Are accessibility requirements specified for all interactive elements? [Coverage, Gap]"
|
||||
- "Is fallback behavior defined when images fail to load? [Edge Case, Gap]"
|
||||
- "Can 'prominent display' be objectively measured? [Measurability, Spec §FR-4]"
|
||||
|
||||
**API Requirements Quality:** `api.md`
|
||||
|
||||
Sample items:
|
||||
|
||||
- "Are error response formats specified for all failure scenarios? [Completeness]"
|
||||
- "Are rate limiting requirements quantified with specific thresholds? [Clarity]"
|
||||
- "Are authentication requirements consistent across all endpoints? [Consistency]"
|
||||
- "Are retry/timeout requirements defined for external dependencies? [Coverage, Gap]"
|
||||
- "Is versioning strategy documented in requirements? [Gap]"
|
||||
|
||||
**Performance Requirements Quality:** `performance.md`
|
||||
|
||||
Sample items:
|
||||
|
||||
- "Are performance requirements quantified with specific metrics? [Clarity]"
|
||||
- "Are performance targets defined for all critical user journeys? [Coverage]"
|
||||
- "Are performance requirements under different load conditions specified? [Completeness]"
|
||||
- "Can performance requirements be objectively measured? [Measurability]"
|
||||
- "Are degradation requirements defined for high-load scenarios? [Edge Case, Gap]"
|
||||
|
||||
**Security Requirements Quality:** `security.md`
|
||||
|
||||
Sample items:
|
||||
|
||||
- "Are authentication requirements specified for all protected resources? [Coverage]"
|
||||
- "Are data protection requirements defined for sensitive information? [Completeness]"
|
||||
- "Is the threat model documented and requirements aligned to it? [Traceability]"
|
||||
- "Are security requirements consistent with compliance obligations? [Consistency]"
|
||||
- "Are security failure/breach response requirements defined? [Gap, Exception Flow]"
|
||||
|
||||
## Anti-Examples: What NOT To Do
|
||||
|
||||
**❌ WRONG - These test implementation, not requirements:**
|
||||
|
||||
```markdown
|
||||
- [ ] CHK001 - Verify landing page displays 3 episode cards [Spec §FR-001]
|
||||
- [ ] CHK002 - Test hover states work correctly on desktop [Spec §FR-003]
|
||||
- [ ] CHK003 - Confirm logo click navigates to home page [Spec §FR-010]
|
||||
- [ ] CHK004 - Check that related episodes section shows 3-5 items [Spec §FR-005]
|
||||
```
|
||||
|
||||
**✅ CORRECT - These test requirements quality:**
|
||||
|
||||
```markdown
|
||||
- [ ] CHK001 - Are the number and layout of featured episodes explicitly specified? [Completeness, Spec §FR-001]
|
||||
- [ ] CHK002 - Are hover state requirements consistently defined for all interactive elements? [Consistency, Spec §FR-003]
|
||||
- [ ] CHK003 - Are navigation requirements clear for all clickable brand elements? [Clarity, Spec §FR-010]
|
||||
- [ ] CHK004 - Is the selection criteria for related episodes documented? [Gap, Spec §FR-005]
|
||||
- [ ] CHK005 - Are loading state requirements defined for asynchronous episode data? [Gap]
|
||||
- [ ] CHK006 - Can "visual hierarchy" requirements be objectively measured? [Measurability, Spec §FR-001]
|
||||
```
|
||||
|
||||
**Key Differences:**
|
||||
|
||||
- Wrong: Tests if the system works correctly
|
||||
- Correct: Tests if the requirements are written correctly
|
||||
- Wrong: Verification of behavior
|
||||
- Correct: Validation of requirement quality
|
||||
- Wrong: "Does it do X?"
|
||||
- Correct: "Is X clearly specified?"
|
||||
|
||||
## Post-Execution Checks
|
||||
|
||||
**Check for extension hooks (after checklist generation)**:
|
||||
Check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it exists, read it and look for entries under the `hooks.after_checklist` key
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
- **Mandatory hook** (`optional: false`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
```
|
||||
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||
@@ -0,0 +1,284 @@
|
||||
---
|
||||
name: "speckit-clarify"
|
||||
description: "Identify underspecified areas in the current feature spec by asking up to 5 highly targeted clarification questions and encoding answers back into the spec."
|
||||
argument-hint: "Optional areas to clarify in the spec"
|
||||
compatibility: "Requires spec-kit project structure with .specify/ directory"
|
||||
metadata:
|
||||
author: "github-spec-kit"
|
||||
source: "templates/commands/clarify.md"
|
||||
user-invocable: true
|
||||
disable-model-invocation: false
|
||||
---
|
||||
|
||||
|
||||
## User Input
|
||||
|
||||
```text
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
You **MUST** consider the user input before proceeding (if not empty).
|
||||
|
||||
## Pre-Execution Checks
|
||||
|
||||
**Check for extension hooks (before clarification)**:
|
||||
- Check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it exists, read it and look for entries under the `hooks.before_clarify` key
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Pre-Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
- **Mandatory hook** (`optional: false`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Pre-Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
|
||||
Wait for the result of the hook command before proceeding to the Outline.
|
||||
```
|
||||
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||
|
||||
## Outline
|
||||
|
||||
Goal: Detect and reduce ambiguity or missing decision points in the active feature specification and record the clarifications directly in the spec file.
|
||||
|
||||
Note: This clarification workflow is expected to run (and be completed) BEFORE invoking `/speckit-plan`. If the user explicitly states they are skipping clarification (e.g., exploratory spike), you may proceed, but must warn that downstream rework risk increases.
|
||||
|
||||
Execution steps:
|
||||
|
||||
1. Run `.specify/scripts/bash/check-prerequisites.sh --json --paths-only` from repo root **once** (combined `--json --paths-only` mode / `-Json -PathsOnly`). Parse minimal JSON payload fields:
|
||||
- `FEATURE_DIR`
|
||||
- `FEATURE_SPEC`
|
||||
- (Optionally capture `IMPL_PLAN`, `TASKS` for future chained flows.)
|
||||
- If JSON parsing fails, abort and instruct user to re-run `/speckit-specify` or verify feature branch environment.
|
||||
- For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot").
|
||||
|
||||
2. Load the current spec file. Perform a structured ambiguity & coverage scan using this taxonomy. For each category, mark status: Clear / Partial / Missing. Produce an internal coverage map used for prioritization (do not output raw map unless no questions will be asked).
|
||||
|
||||
Functional Scope & Behavior:
|
||||
- Core user goals & success criteria
|
||||
- Explicit out-of-scope declarations
|
||||
- User roles / personas differentiation
|
||||
|
||||
Domain & Data Model:
|
||||
- Entities, attributes, relationships
|
||||
- Identity & uniqueness rules
|
||||
- Lifecycle/state transitions
|
||||
- Data volume / scale assumptions
|
||||
|
||||
Interaction & UX Flow:
|
||||
- Critical user journeys / sequences
|
||||
- Error/empty/loading states
|
||||
- Accessibility or localization notes
|
||||
|
||||
Non-Functional Quality Attributes:
|
||||
- Performance (latency, throughput targets)
|
||||
- Scalability (horizontal/vertical, limits)
|
||||
- Reliability & availability (uptime, recovery expectations)
|
||||
- Observability (logging, metrics, tracing signals)
|
||||
- Security & privacy (authN/Z, data protection, threat assumptions)
|
||||
- Compliance / regulatory constraints (if any)
|
||||
|
||||
Integration & External Dependencies:
|
||||
- External services/APIs and failure modes
|
||||
- Data import/export formats
|
||||
- Protocol/versioning assumptions
|
||||
|
||||
Edge Cases & Failure Handling:
|
||||
- Negative scenarios
|
||||
- Rate limiting / throttling
|
||||
- Conflict resolution (e.g., concurrent edits)
|
||||
|
||||
Constraints & Tradeoffs:
|
||||
- Technical constraints (language, storage, hosting)
|
||||
- Explicit tradeoffs or rejected alternatives
|
||||
|
||||
Terminology & Consistency:
|
||||
- Canonical glossary terms
|
||||
- Avoided synonyms / deprecated terms
|
||||
|
||||
Completion Signals:
|
||||
- Acceptance criteria testability
|
||||
- Measurable Definition of Done style indicators
|
||||
|
||||
Misc / Placeholders:
|
||||
- TODO markers / unresolved decisions
|
||||
- Ambiguous adjectives ("robust", "intuitive") lacking quantification
|
||||
|
||||
For each category with Partial or Missing status, add a candidate question opportunity unless:
|
||||
- Clarification would not materially change implementation or validation strategy
|
||||
- Information is better deferred to planning phase (note internally)
|
||||
|
||||
3. Generate (internally) a prioritized queue of candidate clarification questions (maximum 5). Do NOT output them all at once. Apply these constraints:
|
||||
- Maximum of 5 total questions across the whole session.
|
||||
- Each question must be answerable with EITHER:
|
||||
- A short multiple‑choice selection (2–5 distinct, mutually exclusive options), OR
|
||||
- A one-word / short‑phrase answer (explicitly constrain: "Answer in <=5 words").
|
||||
- Only include questions whose answers materially impact architecture, data modeling, task decomposition, test design, UX behavior, operational readiness, or compliance validation.
|
||||
- Ensure category coverage balance: attempt to cover the highest impact unresolved categories first; avoid asking two low-impact questions when a single high-impact area (e.g., security posture) is unresolved.
|
||||
- Exclude questions already answered, trivial stylistic preferences, or plan-level execution details (unless blocking correctness).
|
||||
- Favor clarifications that reduce downstream rework risk or prevent misaligned acceptance tests.
|
||||
- If more than 5 categories remain unresolved, select the top 5 by (Impact * Uncertainty) heuristic.
|
||||
|
||||
4. Sequential questioning loop (interactive):
|
||||
- Present EXACTLY ONE question at a time.
|
||||
- For multiple‑choice questions:
|
||||
- **Analyze all options** and determine the **most suitable option** based on:
|
||||
- Best practices for the project type
|
||||
- Common patterns in similar implementations
|
||||
- Risk reduction (security, performance, maintainability)
|
||||
- Alignment with any explicit project goals or constraints visible in the spec
|
||||
- Present your **recommended option prominently** at the top with clear reasoning (1-2 sentences explaining why this is the best choice).
|
||||
- Format as: `**Recommended:** Option [X] - <reasoning>`
|
||||
- Then render all options as a Markdown table:
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| A | <Option A description> |
|
||||
| B | <Option B description> |
|
||||
| C | <Option C description> (add D/E as needed up to 5) |
|
||||
| Short | Provide a different short answer (<=5 words) (Include only if free-form alternative is appropriate) |
|
||||
|
||||
- After the table, add: `You can reply with the option letter (e.g., "A"), accept the recommendation by saying "yes" or "recommended", or provide your own short answer.`
|
||||
- For short‑answer style (no meaningful discrete options):
|
||||
- Provide your **suggested answer** based on best practices and context.
|
||||
- Format as: `**Suggested:** <your proposed answer> - <brief reasoning>`
|
||||
- Then output: `Format: Short answer (<=5 words). You can accept the suggestion by saying "yes" or "suggested", or provide your own answer.`
|
||||
- After the user answers:
|
||||
- If the user replies with "yes", "recommended", or "suggested", use your previously stated recommendation/suggestion as the answer.
|
||||
- Otherwise, validate the answer maps to one option or fits the <=5 word constraint.
|
||||
- If ambiguous, ask for a quick disambiguation (count still belongs to same question; do not advance).
|
||||
- Once satisfactory, record it in working memory (do not yet write to disk) and move to the next queued question.
|
||||
- Stop asking further questions when:
|
||||
- All critical ambiguities resolved early (remaining queued items become unnecessary), OR
|
||||
- User signals completion ("done", "good", "no more"), OR
|
||||
- You reach 5 asked questions.
|
||||
- Never reveal future queued questions in advance.
|
||||
- If no valid questions exist at start, immediately report no critical ambiguities.
|
||||
|
||||
5. Integration after EACH accepted answer (incremental update approach):
|
||||
- Maintain in-memory representation of the spec (loaded once at start) plus the raw file contents.
|
||||
- For the first integrated answer in this session:
|
||||
- Ensure a `## Clarifications` section exists (create it just after the highest-level contextual/overview section per the spec template if missing).
|
||||
- Under it, create (if not present) a `### Session YYYY-MM-DD` subheading for today.
|
||||
- Append a bullet line immediately after acceptance: `- Q: <question> → A: <final answer>`.
|
||||
- Then immediately apply the clarification to the most appropriate section(s):
|
||||
- Functional ambiguity → Update or add a bullet in Functional Requirements.
|
||||
- User interaction / actor distinction → Update User Stories or Actors subsection (if present) with clarified role, constraint, or scenario.
|
||||
- Data shape / entities → Update Data Model (add fields, types, relationships) preserving ordering; note added constraints succinctly.
|
||||
- Non-functional constraint → Add/modify measurable criteria in Success Criteria > Measurable Outcomes (convert vague adjective to metric or explicit target).
|
||||
- Edge case / negative flow → Add a new bullet under Edge Cases / Error Handling (or create such subsection if template provides placeholder for it).
|
||||
- Terminology conflict → Normalize term across spec; retain original only if necessary by adding `(formerly referred to as "X")` once.
|
||||
- If the clarification invalidates an earlier ambiguous statement, replace that statement instead of duplicating; leave no obsolete contradictory text.
|
||||
- Save the spec file AFTER each integration to minimize risk of context loss (atomic overwrite).
|
||||
- Preserve formatting: do not reorder unrelated sections; keep heading hierarchy intact.
|
||||
- Keep each inserted clarification minimal and testable (avoid narrative drift).
|
||||
|
||||
6. Validation (performed after EACH write plus final pass):
|
||||
- Clarifications session contains exactly one bullet per accepted answer (no duplicates).
|
||||
- Total asked (accepted) questions ≤ 5.
|
||||
- Updated sections contain no lingering vague placeholders the new answer was meant to resolve.
|
||||
- No contradictory earlier statement remains (scan for now-invalid alternative choices removed).
|
||||
- Markdown structure valid; only allowed new headings: `## Clarifications`, `### Session YYYY-MM-DD`.
|
||||
- Terminology consistency: same canonical term used across all updated sections.
|
||||
|
||||
7. Write the updated spec back to `FEATURE_SPEC`.
|
||||
|
||||
8. **Re-validate Spec Quality Checklist** (if it exists):
|
||||
- Check if `FEATURE_DIR/checklists/requirements.md` exists.
|
||||
- If it does NOT exist, skip this step silently.
|
||||
- If it exists:
|
||||
1. Read the checklist file.
|
||||
2. Identify all GitHub task-list checkbox lines — lines matching `- [ ]`, `- [x]`, or `- [X]` (case-insensitive, tolerant of leading whitespace for nested items) outside of code fences. Ignore all other content (headings, notes, non-checkbox bullets, metadata).
|
||||
3. For each checkbox line, record its current marker state (checked or unchecked) and item text into a before-snapshot list.
|
||||
4. Re-evaluate each checkbox item against the **updated** spec (the version just saved in step 7).
|
||||
5. For each checkbox item, update only if the checked/unchecked state actually changes:
|
||||
- If the item now passes and was unchecked: change `[ ]` to `[x]`.
|
||||
- If the item now fails and was checked: change `[x]`/`[X]` to `[ ]`.
|
||||
- If the state is unchanged: leave the marker as-is (preserve existing case to avoid cosmetic diffs).
|
||||
6. Save the updated checklist file. **Only toggle the `[ ]`/`[x]` marker portion of checkbox lines whose state changed.** All other file content — headings, metadata, notes, line ordering, whitespace — must remain unchanged to avoid noisy diffs.
|
||||
7. Compare the before-snapshot with the current state to compute three lists for the Completion Report:
|
||||
- **Newly passing**: items that changed from unchecked to checked.
|
||||
- **Regressions**: items that changed from checked to unchecked.
|
||||
- **Still unchecked**: items that remain unchecked.
|
||||
8. Record the before/after pass counts as checked/total checkbox items (e.g., "12/16 → 15/16 items passing").
|
||||
|
||||
Behavior rules:
|
||||
|
||||
- If no meaningful ambiguities found (or all potential questions would be low-impact), respond: "No critical ambiguities detected worth formal clarification." and suggest proceeding.
|
||||
- If spec file missing, instruct user to run `/speckit-specify` first (do not create a new spec here).
|
||||
- Never exceed 5 total asked questions (clarification retries for a single question do not count as new questions).
|
||||
- Avoid speculative tech stack questions unless the absence blocks functional clarity.
|
||||
- Respect user early termination signals ("stop", "done", "proceed").
|
||||
- If no questions asked due to full coverage, output a compact coverage summary (all categories Clear) then suggest advancing.
|
||||
- If quota reached with unresolved high-impact categories remaining, explicitly flag them under Deferred with rationale.
|
||||
|
||||
Context for prioritization: $ARGUMENTS
|
||||
|
||||
## Mandatory Post-Execution Hooks
|
||||
|
||||
**You MUST complete this section before reporting completion to the user.**
|
||||
|
||||
Check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it does not exist, or no hooks are registered under `hooks.after_clarify`, skip to the Completion Report.
|
||||
- If it exists, read it and look for entries under the `hooks.after_clarify` key.
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue to the Completion Report.
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Mandatory hook** (`optional: false`) — **You MUST emit `EXECUTE_COMMAND:` for each mandatory hook**:
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
```
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
|
||||
## Completion Report
|
||||
|
||||
Report completion (after questioning loop ends or early termination):
|
||||
- Number of questions asked & answered.
|
||||
- Path to updated spec.
|
||||
- Sections touched (list names).
|
||||
- Spec quality checklist status (if `FEATURE_DIR/checklists/requirements.md` was re-validated): show before/after pass counts (e.g., "Spec Quality Checklist: 12/16 → 15/16 items passing") and list any items that changed state — both newly checked (unchecked → checked) and any regressions (checked → unchecked). If any items remain unchecked, list them as areas needing attention.
|
||||
- Coverage summary table listing each taxonomy category with Status: Resolved (was Partial/Missing and addressed), Deferred (exceeds question quota or better suited for planning), Clear (already sufficient), Outstanding (still Partial/Missing but low impact).
|
||||
- If any Outstanding or Deferred remain, recommend whether to proceed to `/speckit-plan` or run `/speckit-clarify` again later post-plan.
|
||||
- Suggested next command.
|
||||
|
||||
## Done When
|
||||
|
||||
- [ ] Spec ambiguities identified and clarifications integrated into spec file
|
||||
- [ ] Spec quality checklist re-validated against updated spec (if `FEATURE_DIR/checklists/requirements.md` exists)
|
||||
- [ ] Extension hooks dispatched or skipped according to the rules in Mandatory Post-Execution Hooks above
|
||||
- [ ] Completion reported to user with questions answered, sections touched, checklist status, and coverage summary
|
||||
@@ -0,0 +1,157 @@
|
||||
---
|
||||
name: "speckit-constitution"
|
||||
description: "Create or update the project constitution from interactive or provided principle inputs, ensuring all dependent templates stay in sync."
|
||||
argument-hint: "Principles or values for the project constitution"
|
||||
compatibility: "Requires spec-kit project structure with .specify/ directory"
|
||||
metadata:
|
||||
author: "github-spec-kit"
|
||||
source: "templates/commands/constitution.md"
|
||||
user-invocable: true
|
||||
disable-model-invocation: false
|
||||
---
|
||||
|
||||
|
||||
## User Input
|
||||
|
||||
```text
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
You **MUST** consider the user input before proceeding (if not empty).
|
||||
|
||||
## Pre-Execution Checks
|
||||
|
||||
**Check for extension hooks (before constitution update)**:
|
||||
- Check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it exists, read it and look for entries under the `hooks.before_constitution` key
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Pre-Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
- **Mandatory hook** (`optional: false`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Pre-Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
|
||||
Wait for the result of the hook command before proceeding to the Outline.
|
||||
```
|
||||
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||
|
||||
## Outline
|
||||
|
||||
You are updating the project constitution at `.specify/memory/constitution.md`. This file is a TEMPLATE containing placeholder tokens in square brackets (e.g. `[PROJECT_NAME]`, `[PRINCIPLE_1_NAME]`). Your job is to (a) collect/derive concrete values, (b) fill the template precisely, and (c) propagate any amendments across dependent artifacts.
|
||||
|
||||
**Note**: If `.specify/memory/constitution.md` does not exist yet, it should have been initialized from `.specify/templates/constitution-template.md` during project setup. If it's missing, copy the template first.
|
||||
|
||||
Follow this execution flow:
|
||||
|
||||
1. Load the existing constitution at `.specify/memory/constitution.md`.
|
||||
- Identify every placeholder token of the form `[ALL_CAPS_IDENTIFIER]`.
|
||||
**IMPORTANT**: The user might require less or more principles than the ones used in the template. If a number is specified, respect that - follow the general template. You will update the doc accordingly.
|
||||
|
||||
2. Collect/derive values for placeholders:
|
||||
- If user input (conversation) supplies a value, use it.
|
||||
- Otherwise infer from existing repo context (README, docs, prior constitution versions if embedded).
|
||||
- For governance dates: `RATIFICATION_DATE` is the original adoption date (if unknown ask or mark TODO), `LAST_AMENDED_DATE` is today if changes are made, otherwise keep previous.
|
||||
- `CONSTITUTION_VERSION` must increment according to semantic versioning rules:
|
||||
- MAJOR: Backward incompatible governance/principle removals or redefinitions.
|
||||
- MINOR: New principle/section added or materially expanded guidance.
|
||||
- PATCH: Clarifications, wording, typo fixes, non-semantic refinements.
|
||||
- If version bump type ambiguous, propose reasoning before finalizing.
|
||||
|
||||
3. Draft the updated constitution content:
|
||||
- Replace every placeholder with concrete text (no bracketed tokens left except intentionally retained template slots that the project has chosen not to define yet—explicitly justify any left).
|
||||
- Preserve heading hierarchy and comments can be removed once replaced unless they still add clarifying guidance.
|
||||
- Ensure each Principle section: succinct name line, paragraph (or bullet list) capturing non‑negotiable rules, explicit rationale if not obvious.
|
||||
- Ensure Governance section lists amendment procedure, versioning policy, and compliance review expectations.
|
||||
|
||||
4. Consistency propagation checklist (convert prior checklist into active validations):
|
||||
- Read `.specify/templates/plan-template.md` and ensure any "Constitution Check" or rules align with updated principles.
|
||||
- Read `.specify/templates/spec-template.md` for scope/requirements alignment—update if constitution adds/removes mandatory sections or constraints.
|
||||
- Read `.specify/templates/tasks-template.md` and ensure task categorization reflects new or removed principle-driven task types (e.g., observability, versioning, testing discipline).
|
||||
- Read each command file in `.specify/templates/commands/*.md` (including this one) to verify no outdated references (agent-specific names like CLAUDE only) remain when generic guidance is required.
|
||||
- Read any runtime guidance docs (e.g., `README.md`, `docs/quickstart.md`, or agent-specific guidance files if present). Update references to principles changed.
|
||||
|
||||
5. Produce a Sync Impact Report (prepend as an HTML comment at top of the constitution file after update):
|
||||
- Version change: old → new
|
||||
- List of modified principles (old title → new title if renamed)
|
||||
- Added sections
|
||||
- Removed sections
|
||||
- Templates requiring updates (✅ updated / ⚠ pending) with file paths
|
||||
- Follow-up TODOs if any placeholders intentionally deferred.
|
||||
|
||||
6. Validation before final output:
|
||||
- No remaining unexplained bracket tokens.
|
||||
- Version line matches report.
|
||||
- Dates ISO format YYYY-MM-DD.
|
||||
- Principles are declarative, testable, and free of vague language ("should" → replace with MUST/SHOULD rationale where appropriate).
|
||||
|
||||
7. Write the completed constitution back to `.specify/memory/constitution.md` (overwrite).
|
||||
|
||||
8. Output a final summary to the user with:
|
||||
- New version and bump rationale.
|
||||
- Any files flagged for manual follow-up.
|
||||
- Suggested commit message (e.g., `docs: amend constitution to vX.Y.Z (principle additions + governance update)`).
|
||||
|
||||
Formatting & Style Requirements:
|
||||
|
||||
- Use Markdown headings exactly as in the template (do not demote/promote levels).
|
||||
- Wrap long rationale lines to keep readability (<100 chars ideally) but do not hard enforce with awkward breaks.
|
||||
- Keep a single blank line between sections.
|
||||
- Avoid trailing whitespace.
|
||||
|
||||
If the user supplies partial updates (e.g., only one principle revision), still perform validation and version decision steps.
|
||||
|
||||
If critical info missing (e.g., ratification date truly unknown), insert `TODO(<FIELD_NAME>): explanation` and include in the Sync Impact Report under deferred items.
|
||||
|
||||
Do not create a new template; always operate on the existing `.specify/memory/constitution.md` file.
|
||||
|
||||
## Post-Execution Checks
|
||||
|
||||
**Check for extension hooks (after constitution update)**:
|
||||
Check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it exists, read it and look for entries under the `hooks.after_constitution` key
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
- **Mandatory hook** (`optional: false`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
```
|
||||
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||
@@ -0,0 +1,224 @@
|
||||
---
|
||||
name: "speckit-implement"
|
||||
description: "Execute the implementation plan by processing and executing all tasks defined in tasks.md"
|
||||
argument-hint: "Optional implementation guidance or task filter"
|
||||
compatibility: "Requires spec-kit project structure with .specify/ directory"
|
||||
metadata:
|
||||
author: "github-spec-kit"
|
||||
source: "templates/commands/implement.md"
|
||||
user-invocable: true
|
||||
disable-model-invocation: false
|
||||
---
|
||||
|
||||
|
||||
## User Input
|
||||
|
||||
```text
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
You **MUST** consider the user input before proceeding (if not empty).
|
||||
|
||||
## Pre-Execution Checks
|
||||
|
||||
**Check for extension hooks (before implementation)**:
|
||||
- Check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it exists, read it and look for entries under the `hooks.before_implement` key
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Pre-Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
- **Mandatory hook** (`optional: false`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Pre-Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
|
||||
Wait for the result of the hook command before proceeding to the Outline.
|
||||
```
|
||||
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||
|
||||
## Outline
|
||||
|
||||
1. Run `.specify/scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks` from repo root and parse FEATURE_DIR and AVAILABLE_DOCS list. All paths must be absolute. For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot").
|
||||
|
||||
2. **Check checklists status** (if FEATURE_DIR/checklists/ exists):
|
||||
- Scan all checklist files in the checklists/ directory
|
||||
- For each checklist, count:
|
||||
- Total items: All lines matching `- [ ]` or `- [X]` or `- [x]`
|
||||
- Completed items: Lines matching `- [X]` or `- [x]`
|
||||
- Incomplete items: Lines matching `- [ ]`
|
||||
- Create a status table:
|
||||
|
||||
```text
|
||||
| Checklist | Total | Completed | Incomplete | Status |
|
||||
|-----------|-------|-----------|------------|--------|
|
||||
| ux.md | 12 | 12 | 0 | ✓ PASS |
|
||||
| test.md | 8 | 5 | 3 | ✗ FAIL |
|
||||
| security.md | 6 | 6 | 0 | ✓ PASS |
|
||||
```
|
||||
|
||||
- Calculate overall status:
|
||||
- **PASS**: All checklists have 0 incomplete items
|
||||
- **FAIL**: One or more checklists have incomplete items
|
||||
|
||||
- **If any checklist is incomplete**:
|
||||
- Display the table with incomplete item counts
|
||||
- **STOP** and ask: "Some checklists are incomplete. Do you want to proceed with implementation anyway? (yes/no)"
|
||||
- Wait for user response before continuing
|
||||
- If user says "no" or "wait" or "stop", halt execution
|
||||
- If user says "yes" or "proceed" or "continue", proceed to step 3
|
||||
|
||||
- **If all checklists are complete**:
|
||||
- Display the table showing all checklists passed
|
||||
- Automatically proceed to step 3
|
||||
|
||||
3. Load and analyze the implementation context:
|
||||
- **REQUIRED**: Read tasks.md for the complete task list and execution plan
|
||||
- **REQUIRED**: Read plan.md for tech stack, architecture, and file structure
|
||||
- **IF EXISTS**: Read data-model.md for entities and relationships
|
||||
- **IF EXISTS**: Read contracts/ for API specifications and test requirements
|
||||
- **IF EXISTS**: Read research.md for technical decisions and constraints
|
||||
- **IF EXISTS**: Read .specify/memory/constitution.md for governance constraints
|
||||
- **IF EXISTS**: Read quickstart.md for integration scenarios
|
||||
|
||||
4. **Project Setup Verification**:
|
||||
- **REQUIRED**: Create/verify ignore files based on actual project setup:
|
||||
|
||||
**Detection & Creation Logic**:
|
||||
- Check if the following command succeeds to determine if the repository is a git repo (create/verify .gitignore if so):
|
||||
|
||||
```sh
|
||||
git rev-parse --git-dir 2>/dev/null
|
||||
```
|
||||
|
||||
- Check if Dockerfile* exists or Docker in plan.md → create/verify .dockerignore
|
||||
- Check if .eslintrc* exists → create/verify .eslintignore
|
||||
- Check if eslint.config.* exists → ensure the config's `ignores` entries cover required patterns
|
||||
- Check if .prettierrc* exists → create/verify .prettierignore
|
||||
- Check if .npmrc or package.json exists → create/verify .npmignore (if publishing)
|
||||
- Check if terraform files (*.tf) exist → create/verify .terraformignore
|
||||
- Check if .helmignore needed (helm charts present) → create/verify .helmignore
|
||||
|
||||
**If ignore file already exists**: Verify it contains essential patterns, append missing critical patterns only
|
||||
**If ignore file missing**: Create with full pattern set for detected technology
|
||||
|
||||
**Common Patterns by Technology** (from plan.md tech stack):
|
||||
- **Node.js/JavaScript/TypeScript**: `node_modules/`, `dist/`, `build/`, `*.log`, `.env*`
|
||||
- **Python**: `__pycache__/`, `*.pyc`, `.venv/`, `venv/`, `dist/`, `*.egg-info/`
|
||||
- **Java**: `target/`, `*.class`, `*.jar`, `.gradle/`, `build/`
|
||||
- **C#/.NET**: `bin/`, `obj/`, `*.user`, `*.suo`, `packages/`
|
||||
- **Go**: `*.exe`, `*.test`, `vendor/`, `*.out`
|
||||
- **Ruby**: `.bundle/`, `log/`, `tmp/`, `*.gem`, `vendor/bundle/`
|
||||
- **PHP**: `vendor/`, `*.log`, `*.cache`, `*.env`
|
||||
- **Rust**: `target/`, `debug/`, `release/`, `*.rs.bk`, `*.rlib`, `*.prof*`, `.idea/`, `*.log`, `.env*`
|
||||
- **Kotlin**: `build/`, `out/`, `.gradle/`, `.idea/`, `*.class`, `*.jar`, `*.iml`, `*.log`, `.env*`
|
||||
- **C++**: `build/`, `bin/`, `obj/`, `out/`, `*.o`, `*.so`, `*.a`, `*.exe`, `*.dll`, `.idea/`, `*.log`, `.env*`
|
||||
- **C**: `build/`, `bin/`, `obj/`, `out/`, `*.o`, `*.a`, `*.so`, `*.exe`, `*.dll`, `autom4te.cache/`, `config.status`, `config.log`, `.idea/`, `*.log`, `.env*`
|
||||
- **Swift**: `.build/`, `DerivedData/`, `*.swiftpm/`, `Packages/`
|
||||
- **R**: `.Rproj.user/`, `.Rhistory`, `.RData`, `.Ruserdata`, `*.Rproj`, `packrat/`, `renv/`
|
||||
- **Universal**: `.DS_Store`, `Thumbs.db`, `*.tmp`, `*.swp`, `.vscode/`, `.idea/`
|
||||
|
||||
**Tool-Specific Patterns**:
|
||||
- **Docker**: `node_modules/`, `.git/`, `Dockerfile*`, `.dockerignore`, `*.log*`, `.env*`, `coverage/`
|
||||
- **ESLint**: `node_modules/`, `dist/`, `build/`, `coverage/`, `*.min.js`
|
||||
- **Prettier**: `node_modules/`, `dist/`, `build/`, `coverage/`, `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`
|
||||
- **Terraform**: `.terraform/`, `*.tfstate*`, `*.tfvars`, `.terraform.lock.hcl`
|
||||
- **Kubernetes/k8s**: `*.secret.yaml`, `secrets/`, `.kube/`, `kubeconfig*`, `*.key`, `*.crt`
|
||||
|
||||
5. Parse tasks.md structure and extract:
|
||||
- **Task phases**: Setup, Tests, Core, Integration, Polish
|
||||
- **Task dependencies**: Sequential vs parallel execution rules
|
||||
- **Task details**: ID, description, file paths, parallel markers [P]
|
||||
- **Execution flow**: Order and dependency requirements
|
||||
|
||||
6. Execute implementation following the task plan:
|
||||
- **Phase-by-phase execution**: Complete each phase before moving to the next
|
||||
- **Respect dependencies**: Run sequential tasks in order, parallel tasks [P] can run together
|
||||
- **Follow TDD approach**: Execute test tasks before their corresponding implementation tasks
|
||||
- **File-based coordination**: Tasks affecting the same files must run sequentially
|
||||
- **Validation checkpoints**: Verify each phase completion before proceeding
|
||||
|
||||
7. Implementation execution rules:
|
||||
- **Setup first**: Initialize project structure, dependencies, configuration
|
||||
- **Tests before code**: If you need to write tests for contracts, entities, and integration scenarios
|
||||
- **Core development**: Implement models, services, CLI commands, endpoints
|
||||
- **Integration work**: Database connections, middleware, logging, external services
|
||||
- **Polish and validation**: Unit tests, performance optimization, documentation
|
||||
|
||||
8. Progress tracking and error handling:
|
||||
- Report progress after each completed task
|
||||
- Halt execution if any non-parallel task fails
|
||||
- For parallel tasks [P], continue with successful tasks, report failed ones
|
||||
- Provide clear error messages with context for debugging
|
||||
- Suggest next steps if implementation cannot proceed
|
||||
- **IMPORTANT** For completed tasks, make sure to mark the task off as [X] in the tasks file.
|
||||
|
||||
9. Completion validation:
|
||||
- Verify all required tasks are completed
|
||||
- Check that implemented features match the original specification
|
||||
- Validate that tests pass and coverage meets requirements
|
||||
- Confirm the implementation follows the technical plan
|
||||
|
||||
Note: This command assumes a complete task breakdown exists in tasks.md. If tasks are incomplete or missing, suggest running `/speckit-tasks` first to regenerate the task list.
|
||||
|
||||
## Mandatory Post-Execution Hooks
|
||||
|
||||
**You MUST complete this section before reporting completion to the user.**
|
||||
|
||||
Check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it does not exist, or no hooks are registered under `hooks.after_implement`, skip to the Completion Report.
|
||||
- If it exists, read it and look for entries under the `hooks.after_implement` key.
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue to the Completion Report.
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Mandatory hook** (`optional: false`) — **You MUST emit `EXECUTE_COMMAND:` for each mandatory hook**:
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
```
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
|
||||
## Completion Report
|
||||
|
||||
Report final status with summary of completed work.
|
||||
|
||||
## Done When
|
||||
|
||||
- [ ] All tasks in tasks.md completed and marked `[X]`
|
||||
- [ ] Implementation validated against specification, plan, and test coverage
|
||||
- [ ] Extension hooks dispatched or skipped according to the rules in Mandatory Post-Execution Hooks above
|
||||
- [ ] Completion reported to user with summary of completed work
|
||||
@@ -0,0 +1,164 @@
|
||||
---
|
||||
name: "speckit-plan"
|
||||
description: "Execute the implementation planning workflow using the plan template to generate design artifacts."
|
||||
argument-hint: "Optional guidance for the planning phase"
|
||||
compatibility: "Requires spec-kit project structure with .specify/ directory"
|
||||
metadata:
|
||||
author: "github-spec-kit"
|
||||
source: "templates/commands/plan.md"
|
||||
user-invocable: true
|
||||
disable-model-invocation: false
|
||||
---
|
||||
|
||||
|
||||
## User Input
|
||||
|
||||
```text
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
You **MUST** consider the user input before proceeding (if not empty).
|
||||
|
||||
## Pre-Execution Checks
|
||||
|
||||
**Check for extension hooks (before planning)**:
|
||||
- Check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it exists, read it and look for entries under the `hooks.before_plan` key
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Pre-Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
- **Mandatory hook** (`optional: false`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Pre-Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
|
||||
Wait for the result of the hook command before proceeding to the Outline.
|
||||
```
|
||||
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||
|
||||
## Outline
|
||||
|
||||
1. **Setup**: Run `.specify/scripts/bash/setup-plan.sh --json` from repo root and parse JSON for FEATURE_SPEC, IMPL_PLAN, SPECS_DIR, BRANCH. For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot").
|
||||
|
||||
2. **Load context**: Read FEATURE_SPEC and `.specify/memory/constitution.md`. Load IMPL_PLAN template (already copied).
|
||||
|
||||
3. **Execute plan workflow**: Follow the structure in IMPL_PLAN template to:
|
||||
- Fill Technical Context (mark unknowns as "NEEDS CLARIFICATION")
|
||||
- Fill Constitution Check section from constitution
|
||||
- Evaluate gates (ERROR if violations unjustified)
|
||||
- Phase 0: Generate research.md (resolve all NEEDS CLARIFICATION)
|
||||
- Phase 1: Generate data-model.md, contracts/, quickstart.md
|
||||
- Phase 1: Update agent context by running the agent script
|
||||
- Re-evaluate Constitution Check post-design
|
||||
|
||||
## Mandatory Post-Execution Hooks
|
||||
|
||||
**You MUST complete this section before reporting completion to the user.**
|
||||
|
||||
Check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it does not exist, or no hooks are registered under `hooks.after_plan`, skip to the Completion Report.
|
||||
- If it exists, read it and look for entries under the `hooks.after_plan` key.
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue to the Completion Report.
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Mandatory hook** (`optional: false`) — **You MUST emit `EXECUTE_COMMAND:` for each mandatory hook**:
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
```
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
|
||||
## Completion Report
|
||||
|
||||
Command ends after Phase 2 planning. Report branch, IMPL_PLAN path, and generated artifacts.
|
||||
|
||||
## Phases
|
||||
|
||||
### Phase 0: Outline & Research
|
||||
|
||||
1. **Extract unknowns from Technical Context** above:
|
||||
- For each NEEDS CLARIFICATION → research task
|
||||
- For each dependency → best practices task
|
||||
- For each integration → patterns task
|
||||
|
||||
2. **Generate and dispatch research agents**:
|
||||
|
||||
```text
|
||||
For each unknown in Technical Context:
|
||||
Task: "Research {unknown} for {feature context}"
|
||||
For each technology choice:
|
||||
Task: "Find best practices for {tech} in {domain}"
|
||||
```
|
||||
|
||||
3. **Consolidate findings** in `research.md` using format:
|
||||
- Decision: [what was chosen]
|
||||
- Rationale: [why chosen]
|
||||
- Alternatives considered: [what else evaluated]
|
||||
|
||||
**Output**: research.md with all NEEDS CLARIFICATION resolved
|
||||
|
||||
### Phase 1: Design & Contracts
|
||||
|
||||
**Prerequisites:** `research.md` complete
|
||||
|
||||
1. **Extract entities from feature spec** → `data-model.md`:
|
||||
- Entity name, fields, relationships
|
||||
- Validation rules from requirements
|
||||
- State transitions if applicable
|
||||
|
||||
2. **Define interface contracts** (if project has external interfaces) → `/contracts/`:
|
||||
- Identify what interfaces the project exposes to users or other systems
|
||||
- Document the contract format appropriate for the project type
|
||||
- Examples: public APIs for libraries, command schemas for CLI tools, endpoints for web services, grammars for parsers, UI contracts for applications
|
||||
- Skip if project is purely internal (build scripts, one-off tools, etc.)
|
||||
|
||||
3. **Agent context update**:
|
||||
- Update the plan reference between the `<!-- SPECKIT START -->` and `<!-- SPECKIT END -->` markers in `CLAUDE.md` to point to the plan file created in step 1 (the IMPL_PLAN path)
|
||||
|
||||
**Output**: data-model.md, /contracts/*, quickstart.md, updated agent context file
|
||||
|
||||
## Key rules
|
||||
|
||||
- Use absolute paths for filesystem operations; use project-relative paths for references in documentation and agent context files
|
||||
- ERROR on gate failures or unresolved clarifications
|
||||
|
||||
## Done When
|
||||
|
||||
- [ ] Plan workflow executed and design artifacts generated
|
||||
- [ ] Extension hooks dispatched or skipped according to the rules in Mandatory Post-Execution Hooks above
|
||||
- [ ] Completion reported to user with branch, plan path, and generated artifacts
|
||||
@@ -0,0 +1,342 @@
|
||||
---
|
||||
name: "speckit-specify"
|
||||
description: "Create or update the feature specification from a natural language feature description."
|
||||
argument-hint: "Describe the feature you want to specify"
|
||||
compatibility: "Requires spec-kit project structure with .specify/ directory"
|
||||
metadata:
|
||||
author: "github-spec-kit"
|
||||
source: "templates/commands/specify.md"
|
||||
user-invocable: true
|
||||
disable-model-invocation: false
|
||||
---
|
||||
|
||||
|
||||
## User Input
|
||||
|
||||
```text
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
You **MUST** consider the user input before proceeding (if not empty).
|
||||
|
||||
## Pre-Execution Checks
|
||||
|
||||
**Check for extension hooks (before specification)**:
|
||||
- Check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it exists, read it and look for entries under the `hooks.before_specify` key
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Pre-Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
- **Mandatory hook** (`optional: false`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Pre-Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
|
||||
Wait for the result of the hook command before proceeding to the Outline.
|
||||
```
|
||||
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||
|
||||
## Outline
|
||||
|
||||
The text the user typed after `/speckit-specify` in the triggering message **is** the feature description. Assume you always have it available in this conversation even if `$ARGUMENTS` appears literally below. Do not ask the user to repeat it unless they provided an empty command.
|
||||
|
||||
Given that feature description, do this:
|
||||
|
||||
1. **Generate a concise short name** (2-4 words) for the feature:
|
||||
- Analyze the feature description and extract the most meaningful keywords
|
||||
- Create a 2-4 word short name that captures the essence of the feature
|
||||
- Use action-noun format when possible (e.g., "add-user-auth", "fix-payment-bug")
|
||||
- Preserve technical terms and acronyms (OAuth2, API, JWT, etc.)
|
||||
- Keep it concise but descriptive enough to understand the feature at a glance
|
||||
- Examples:
|
||||
- "I want to add user authentication" → "user-auth"
|
||||
- "Implement OAuth2 integration for the API" → "oauth2-api-integration"
|
||||
- "Create a dashboard for analytics" → "analytics-dashboard"
|
||||
- "Fix payment processing timeout bug" → "fix-payment-timeout"
|
||||
|
||||
2. **Branch creation** (optional, via hook):
|
||||
|
||||
If a `before_specify` hook ran successfully in the Pre-Execution Checks above, it will have created/switched to a git branch and output JSON containing `BRANCH_NAME` and `FEATURE_NUM`. Note these values for reference, but the branch name does **not** dictate the spec directory name.
|
||||
|
||||
If the user explicitly provided `GIT_BRANCH_NAME`, pass it through to the hook so the branch script uses the exact value as the branch name (bypassing all prefix/suffix generation).
|
||||
|
||||
3. **Create the spec feature directory**:
|
||||
|
||||
Specs live under the default `specs/` directory unless the user explicitly provides `SPECIFY_FEATURE_DIRECTORY`.
|
||||
|
||||
**Resolution order for `SPECIFY_FEATURE_DIRECTORY`**:
|
||||
1. If the user explicitly provided `SPECIFY_FEATURE_DIRECTORY` (e.g., via environment variable, argument, or configuration), use it as-is
|
||||
2. Otherwise, auto-generate it under `specs/`:
|
||||
- Check `.specify/init-options.json` for `branch_numbering`
|
||||
- If `"timestamp"`: prefix is `YYYYMMDD-HHMMSS` (current timestamp)
|
||||
- If `"sequential"` or absent: prefix is `NNN` (next available 3-digit number after scanning existing directories in `specs/`)
|
||||
- Construct the directory name: `<prefix>-<short-name>` (e.g., `003-user-auth` or `20260319-143022-user-auth`)
|
||||
- Set `SPECIFY_FEATURE_DIRECTORY` to `specs/<directory-name>`
|
||||
|
||||
**Create the directory and spec file**:
|
||||
- `mkdir -p SPECIFY_FEATURE_DIRECTORY`
|
||||
- Copy `.specify/templates/spec-template.md` to `SPECIFY_FEATURE_DIRECTORY/spec.md` as the starting point
|
||||
- Set `SPEC_FILE` to `SPECIFY_FEATURE_DIRECTORY/spec.md`
|
||||
- Persist the resolved path to `.specify/feature.json`:
|
||||
```json
|
||||
{
|
||||
"feature_directory": "<resolved feature dir>"
|
||||
}
|
||||
```
|
||||
Write the actual resolved directory path value (for example, `specs/003-user-auth`), not the literal string `SPECIFY_FEATURE_DIRECTORY`.
|
||||
This allows downstream commands (`/speckit-plan`, `/speckit-tasks`, etc.) to locate the feature directory without relying on git branch name conventions.
|
||||
|
||||
**IMPORTANT**:
|
||||
- You must only create one feature per `/speckit-specify` invocation
|
||||
- The spec directory name and the git branch name are independent — they may be the same but that is the user's choice
|
||||
- The spec directory and file are always created by this command, never by the hook
|
||||
|
||||
4. Load `.specify/templates/spec-template.md` to understand required sections.
|
||||
|
||||
5. Follow this execution flow:
|
||||
1. Parse user description from arguments
|
||||
If empty: ERROR "No feature description provided"
|
||||
2. Extract key concepts from description
|
||||
Identify: actors, actions, data, constraints
|
||||
3. For unclear aspects:
|
||||
- Make informed guesses based on context and industry standards
|
||||
- Only mark with [NEEDS CLARIFICATION: specific question] if:
|
||||
- The choice significantly impacts feature scope or user experience
|
||||
- Multiple reasonable interpretations exist with different implications
|
||||
- No reasonable default exists
|
||||
- **LIMIT: Maximum 3 [NEEDS CLARIFICATION] markers total**
|
||||
- Prioritize clarifications by impact: scope > security/privacy > user experience > technical details
|
||||
4. Fill User Scenarios & Testing section
|
||||
If no clear user flow: ERROR "Cannot determine user scenarios"
|
||||
5. Generate Functional Requirements
|
||||
Each requirement must be testable
|
||||
Use reasonable defaults for unspecified details (document assumptions in Assumptions section)
|
||||
6. Define Success Criteria
|
||||
Create measurable, technology-agnostic outcomes
|
||||
Include both quantitative metrics (time, performance, volume) and qualitative measures (user satisfaction, task completion)
|
||||
Each criterion must be verifiable without implementation details
|
||||
7. Identify Key Entities (if data involved)
|
||||
8. Return: SUCCESS (spec ready for planning)
|
||||
|
||||
6. Write the specification to SPEC_FILE using the template structure, replacing placeholders with concrete details derived from the feature description (arguments) while preserving section order and headings.
|
||||
|
||||
7. **Specification Quality Validation**: After writing the initial spec, validate it against quality criteria:
|
||||
|
||||
a. **Create Spec Quality Checklist**: Generate a checklist file at `SPECIFY_FEATURE_DIRECTORY/checklists/requirements.md` using the checklist template structure with these validation items:
|
||||
|
||||
```markdown
|
||||
# Specification Quality Checklist: [FEATURE NAME]
|
||||
|
||||
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
||||
**Created**: [DATE]
|
||||
**Feature**: [Link to spec.md]
|
||||
|
||||
## Content Quality
|
||||
|
||||
- [ ] No implementation details (languages, frameworks, APIs)
|
||||
- [ ] Focused on user value and business needs
|
||||
- [ ] Written for non-technical stakeholders
|
||||
- [ ] All mandatory sections completed
|
||||
|
||||
## Requirement Completeness
|
||||
|
||||
- [ ] No [NEEDS CLARIFICATION] markers remain
|
||||
- [ ] Requirements are testable and unambiguous
|
||||
- [ ] Success criteria are measurable
|
||||
- [ ] Success criteria are technology-agnostic (no implementation details)
|
||||
- [ ] All acceptance scenarios are defined
|
||||
- [ ] Edge cases are identified
|
||||
- [ ] Scope is clearly bounded
|
||||
- [ ] Dependencies and assumptions identified
|
||||
|
||||
## Feature Readiness
|
||||
|
||||
- [ ] All functional requirements have clear acceptance criteria
|
||||
- [ ] User scenarios cover primary flows
|
||||
- [ ] Feature meets measurable outcomes defined in Success Criteria
|
||||
- [ ] No implementation details leak into specification
|
||||
|
||||
## Notes
|
||||
|
||||
- Items marked incomplete require spec updates before `/speckit-clarify` or `/speckit-plan`
|
||||
```
|
||||
|
||||
b. **Run Validation Check**: Review the spec against each checklist item:
|
||||
- For each item, determine if it passes or fails
|
||||
- Document specific issues found (quote relevant spec sections)
|
||||
|
||||
c. **Handle Validation Results**:
|
||||
|
||||
- **If all items pass**: Mark checklist complete and proceed to the Mandatory Post-Execution Hooks section
|
||||
|
||||
- **If items fail (excluding [NEEDS CLARIFICATION])**:
|
||||
1. List the failing items and specific issues
|
||||
2. Update the spec to address each issue
|
||||
3. Re-run validation until all items pass (max 3 iterations)
|
||||
4. If still failing after 3 iterations, document remaining issues in checklist notes and warn user
|
||||
|
||||
- **If [NEEDS CLARIFICATION] markers remain**:
|
||||
1. Extract all [NEEDS CLARIFICATION: ...] markers from the spec
|
||||
2. **LIMIT CHECK**: If more than 3 markers exist, keep only the 3 most critical (by scope/security/UX impact) and make informed guesses for the rest
|
||||
3. For each clarification needed (max 3), present options to user in this format:
|
||||
|
||||
```markdown
|
||||
## Question [N]: [Topic]
|
||||
|
||||
**Context**: [Quote relevant spec section]
|
||||
|
||||
**What we need to know**: [Specific question from NEEDS CLARIFICATION marker]
|
||||
|
||||
**Suggested Answers**:
|
||||
|
||||
| Option | Answer | Implications |
|
||||
|--------|--------|--------------|
|
||||
| A | [First suggested answer] | [What this means for the feature] |
|
||||
| B | [Second suggested answer] | [What this means for the feature] |
|
||||
| C | [Third suggested answer] | [What this means for the feature] |
|
||||
| Custom | Provide your own answer | [Explain how to provide custom input] |
|
||||
|
||||
**Your choice**: _[Wait for user response]_
|
||||
```
|
||||
|
||||
4. **CRITICAL - Table Formatting**: Ensure markdown tables are properly formatted:
|
||||
- Use consistent spacing with pipes aligned
|
||||
- Each cell should have spaces around content: `| Content |` not `|Content|`
|
||||
- Header separator must have at least 3 dashes: `|--------|`
|
||||
- Test that the table renders correctly in markdown preview
|
||||
5. Number questions sequentially (Q1, Q2, Q3 - max 3 total)
|
||||
6. Present all questions together before waiting for responses
|
||||
7. Wait for user to respond with their choices for all questions (e.g., "Q1: A, Q2: Custom - [details], Q3: B")
|
||||
8. Update the spec by replacing each [NEEDS CLARIFICATION] marker with the user's selected or provided answer
|
||||
9. Re-run validation after all clarifications are resolved
|
||||
|
||||
d. **Update Checklist**: After each validation iteration, update the checklist file with current pass/fail status
|
||||
|
||||
## Mandatory Post-Execution Hooks
|
||||
|
||||
**You MUST complete this section before reporting completion to the user.**
|
||||
|
||||
Check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it does not exist, or no hooks are registered under `hooks.after_specify`, skip to the Completion Report.
|
||||
- If it exists, read it and look for entries under the `hooks.after_specify` key.
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue to the Completion Report.
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Mandatory hook** (`optional: false`) — **You MUST emit `EXECUTE_COMMAND:` for each mandatory hook**:
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
```
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
|
||||
## Completion Report
|
||||
|
||||
Report completion to the user with:
|
||||
- `SPECIFY_FEATURE_DIRECTORY` — the feature directory path
|
||||
- `SPEC_FILE` — the spec file path
|
||||
- Checklist results summary
|
||||
- Readiness for the next phase (`/speckit-clarify` or `/speckit-plan`)
|
||||
|
||||
**NOTE:** Branch creation is handled by the `before_specify` hook (git extension). Spec directory and file creation are always handled by this core command.
|
||||
|
||||
## Quick Guidelines
|
||||
|
||||
- Focus on **WHAT** users need and **WHY**.
|
||||
- Avoid HOW to implement (no tech stack, APIs, code structure).
|
||||
- Written for business stakeholders, not developers.
|
||||
- DO NOT create any checklists that are embedded in the spec. That will be a separate command.
|
||||
|
||||
### Section Requirements
|
||||
|
||||
- **Mandatory sections**: Must be completed for every feature
|
||||
- **Optional sections**: Include only when relevant to the feature
|
||||
- When a section doesn't apply, remove it entirely (don't leave as "N/A")
|
||||
|
||||
### For AI Generation
|
||||
|
||||
When creating this spec from a user prompt:
|
||||
|
||||
1. **Make informed guesses**: Use context, industry standards, and common patterns to fill gaps
|
||||
2. **Document assumptions**: Record reasonable defaults in the Assumptions section
|
||||
3. **Limit clarifications**: Maximum 3 [NEEDS CLARIFICATION] markers - use only for critical decisions that:
|
||||
- Significantly impact feature scope or user experience
|
||||
- Have multiple reasonable interpretations with different implications
|
||||
- Lack any reasonable default
|
||||
4. **Prioritize clarifications**: scope > security/privacy > user experience > technical details
|
||||
5. **Think like a tester**: Every vague requirement should fail the "testable and unambiguous" checklist item
|
||||
6. **Common areas needing clarification** (only if no reasonable default exists):
|
||||
- Feature scope and boundaries (include/exclude specific use cases)
|
||||
- User types and permissions (if multiple conflicting interpretations possible)
|
||||
- Security/compliance requirements (when legally/financially significant)
|
||||
|
||||
**Examples of reasonable defaults** (don't ask about these):
|
||||
|
||||
- Data retention: Industry-standard practices for the domain
|
||||
- Performance targets: Standard web/mobile app expectations unless specified
|
||||
- Error handling: User-friendly messages with appropriate fallbacks
|
||||
- Authentication method: Standard session-based or OAuth2 for web apps
|
||||
- Integration patterns: Use project-appropriate patterns (REST/GraphQL for web services, function calls for libraries, CLI args for tools, etc.)
|
||||
|
||||
### Success Criteria Guidelines
|
||||
|
||||
Success criteria must be:
|
||||
|
||||
1. **Measurable**: Include specific metrics (time, percentage, count, rate)
|
||||
2. **Technology-agnostic**: No mention of frameworks, languages, databases, or tools
|
||||
3. **User-focused**: Describe outcomes from user/business perspective, not system internals
|
||||
4. **Verifiable**: Can be tested/validated without knowing implementation details
|
||||
|
||||
**Good examples**:
|
||||
|
||||
- "Users can complete checkout in under 3 minutes"
|
||||
- "System supports 10,000 concurrent users"
|
||||
- "95% of searches return results in under 1 second"
|
||||
- "Task completion rate improves by 40%"
|
||||
|
||||
**Bad examples** (implementation-focused):
|
||||
|
||||
- "API response time is under 200ms" (too technical, use "Users see results instantly")
|
||||
- "Database can handle 1000 TPS" (implementation detail, use user-facing metric)
|
||||
- "React components render efficiently" (framework-specific)
|
||||
- "Redis cache hit rate above 80%" (technology-specific)
|
||||
|
||||
## Done When
|
||||
|
||||
- [ ] Specification written to `SPEC_FILE` and validated against quality checklist
|
||||
- [ ] Extension hooks dispatched or skipped according to the rules in Mandatory Post-Execution Hooks above
|
||||
- [ ] Completion reported to user with feature directory, spec file path, and checklist results
|
||||
@@ -0,0 +1,214 @@
|
||||
---
|
||||
name: "speckit-tasks"
|
||||
description: "Generate an actionable, dependency-ordered tasks.md for the feature based on available design artifacts."
|
||||
argument-hint: "Optional task generation constraints"
|
||||
compatibility: "Requires spec-kit project structure with .specify/ directory"
|
||||
metadata:
|
||||
author: "github-spec-kit"
|
||||
source: "templates/commands/tasks.md"
|
||||
user-invocable: true
|
||||
disable-model-invocation: false
|
||||
---
|
||||
|
||||
|
||||
## User Input
|
||||
|
||||
```text
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
You **MUST** consider the user input before proceeding (if not empty).
|
||||
|
||||
## Pre-Execution Checks
|
||||
|
||||
**Check for extension hooks (before tasks generation)**:
|
||||
- Check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it exists, read it and look for entries under the `hooks.before_tasks` key
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Pre-Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
- **Mandatory hook** (`optional: false`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Pre-Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
|
||||
Wait for the result of the hook command before proceeding to the Outline.
|
||||
```
|
||||
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||
|
||||
## Outline
|
||||
|
||||
1. **Setup**: Run `.specify/scripts/bash/setup-tasks.sh --json` from repo root and parse FEATURE_DIR, TASKS_TEMPLATE, and AVAILABLE_DOCS list. `FEATURE_DIR` and `TASKS_TEMPLATE` must be absolute paths when provided. `AVAILABLE_DOCS` is a list of document names/relative paths available under `FEATURE_DIR` (for example `research.md` or `contracts/`). For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot").
|
||||
|
||||
2. **Load design documents**: Read from FEATURE_DIR:
|
||||
- **Required**: plan.md (tech stack, libraries, structure), spec.md (user stories with priorities)
|
||||
- **Optional**: data-model.md (entities), contracts/ (interface contracts), research.md (decisions), quickstart.md (test scenarios)
|
||||
- Note: Not all projects have all documents. Generate tasks based on what's available.
|
||||
|
||||
3. **Execute task generation workflow**:
|
||||
- Load plan.md and extract tech stack, libraries, project structure
|
||||
- Load spec.md and extract user stories with their priorities (P1, P2, P3, etc.)
|
||||
- If data-model.md exists: Extract entities and map to user stories
|
||||
- If contracts/ exists: Map interface contracts to user stories
|
||||
- If research.md exists: Extract decisions for setup tasks
|
||||
- Generate tasks organized by user story (see Task Generation Rules below)
|
||||
- Generate dependency graph showing user story completion order
|
||||
- Create parallel execution examples per user story
|
||||
- Validate task completeness (each user story has all needed tasks, independently testable)
|
||||
|
||||
4. **Generate tasks.md**: Read the tasks template from TASKS_TEMPLATE (from the JSON output above) and use it as structure. If TASKS_TEMPLATE is empty, fall back to `.specify/templates/tasks-template.md`. Fill with:
|
||||
- Correct feature name from plan.md
|
||||
- Phase 1: Setup tasks (project initialization)
|
||||
- Phase 2: Foundational tasks (blocking prerequisites for all user stories)
|
||||
- Phase 3+: One phase per user story (in priority order from spec.md)
|
||||
- Each phase includes: story goal, independent test criteria, tests (if requested), implementation tasks
|
||||
- Final Phase: Polish & cross-cutting concerns
|
||||
- All tasks must follow the strict checklist format (see Task Generation Rules below)
|
||||
- Clear file paths for each task
|
||||
- Dependencies section showing story completion order
|
||||
- Parallel execution examples per story
|
||||
- Implementation strategy section (MVP first, incremental delivery)
|
||||
|
||||
## Mandatory Post-Execution Hooks
|
||||
|
||||
**You MUST complete this section before reporting completion to the user.**
|
||||
|
||||
Check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it does not exist, or no hooks are registered under `hooks.after_tasks`, skip to the Completion Report.
|
||||
- If it exists, read it and look for entries under the `hooks.after_tasks` key.
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue to the Completion Report.
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Mandatory hook** (`optional: false`) — **You MUST emit `EXECUTE_COMMAND:` for each mandatory hook**:
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
```
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
|
||||
## Completion Report
|
||||
|
||||
Output path to generated tasks.md and summary:
|
||||
- Total task count
|
||||
- Task count per user story
|
||||
- Parallel opportunities identified
|
||||
- Independent test criteria for each story
|
||||
- Suggested MVP scope (typically just User Story 1)
|
||||
- Format validation: Confirm ALL tasks follow the checklist format (checkbox, ID, labels, file paths)
|
||||
|
||||
Context for task generation: $ARGUMENTS
|
||||
|
||||
The tasks.md should be immediately executable - each task must be specific enough that an LLM can complete it without additional context.
|
||||
|
||||
## Task Generation Rules
|
||||
|
||||
**CRITICAL**: Tasks MUST be organized by user story to enable independent implementation and testing.
|
||||
|
||||
**Tests are OPTIONAL**: Only generate test tasks if explicitly requested in the feature specification or if user requests TDD approach.
|
||||
|
||||
### Checklist Format (REQUIRED)
|
||||
|
||||
Every task MUST strictly follow this format:
|
||||
|
||||
```text
|
||||
- [ ] [TaskID] [P?] [Story?] Description with file path
|
||||
```
|
||||
|
||||
**Format Components**:
|
||||
|
||||
1. **Checkbox**: ALWAYS start with `- [ ]` (markdown checkbox)
|
||||
2. **Task ID**: Sequential number (T001, T002, T003...) in execution order
|
||||
3. **[P] marker**: Include ONLY if task is parallelizable (different files, no dependencies on incomplete tasks)
|
||||
4. **[Story] label**: REQUIRED for user story phase tasks only
|
||||
- Format: [US1], [US2], [US3], etc. (maps to user stories from spec.md)
|
||||
- Setup phase: NO story label
|
||||
- Foundational phase: NO story label
|
||||
- User Story phases: MUST have story label
|
||||
- Polish phase: NO story label
|
||||
5. **Description**: Clear action with exact file path
|
||||
|
||||
**Examples**:
|
||||
|
||||
- ✅ CORRECT: `- [ ] T001 Create project structure per implementation plan`
|
||||
- ✅ CORRECT: `- [ ] T005 [P] Implement authentication middleware in src/middleware/auth.py`
|
||||
- ✅ CORRECT: `- [ ] T012 [P] [US1] Create User model in src/models/user.py`
|
||||
- ✅ CORRECT: `- [ ] T014 [US1] Implement UserService in src/services/user_service.py`
|
||||
- ❌ WRONG: `- [ ] Create User model` (missing ID and Story label)
|
||||
- ❌ WRONG: `T001 [US1] Create model` (missing checkbox)
|
||||
- ❌ WRONG: `- [ ] [US1] Create User model` (missing Task ID)
|
||||
- ❌ WRONG: `- [ ] T001 [US1] Create model` (missing file path)
|
||||
|
||||
### Task Organization
|
||||
|
||||
1. **From User Stories (spec.md)** - PRIMARY ORGANIZATION:
|
||||
- Each user story (P1, P2, P3...) gets its own phase
|
||||
- Map all related components to their story:
|
||||
- Models needed for that story
|
||||
- Services needed for that story
|
||||
- Interfaces/UI needed for that story
|
||||
- If tests requested: Tests specific to that story
|
||||
- Mark story dependencies (most stories should be independent)
|
||||
|
||||
2. **From Contracts**:
|
||||
- Map each interface contract → to the user story it serves
|
||||
- If tests requested: Each interface contract → contract test task [P] before implementation in that story's phase
|
||||
|
||||
3. **From Data Model**:
|
||||
- Map each entity to the user story(ies) that need it
|
||||
- If entity serves multiple stories: Put in earliest story or Setup phase
|
||||
- Relationships → service layer tasks in appropriate story phase
|
||||
|
||||
4. **From Setup/Infrastructure**:
|
||||
- Shared infrastructure → Setup phase (Phase 1)
|
||||
- Foundational/blocking tasks → Foundational phase (Phase 2)
|
||||
- Story-specific setup → within that story's phase
|
||||
|
||||
### Phase Structure
|
||||
|
||||
- **Phase 1**: Setup (project initialization)
|
||||
- **Phase 2**: Foundational (blocking prerequisites - MUST complete before user stories)
|
||||
- **Phase 3+**: User Stories in priority order (P1, P2, P3...)
|
||||
- Within each story: Tests (if requested) → Models → Services → Endpoints → Integration
|
||||
- Each phase should be a complete, independently testable increment
|
||||
- **Final Phase**: Polish & Cross-Cutting Concerns
|
||||
|
||||
## Done When
|
||||
|
||||
- [ ] tasks.md generated with all phases, task IDs, and file paths
|
||||
- [ ] Extension hooks dispatched or skipped according to the rules in Mandatory Post-Execution Hooks above
|
||||
- [ ] Completion reported to user with task count, story breakdown, and MVP scope
|
||||
@@ -0,0 +1,106 @@
|
||||
---
|
||||
name: "speckit-taskstoissues"
|
||||
description: "Convert existing tasks into actionable, dependency-ordered GitHub issues for the feature based on available design artifacts."
|
||||
argument-hint: "Optional filter or label for GitHub issues"
|
||||
compatibility: "Requires spec-kit project structure with .specify/ directory"
|
||||
metadata:
|
||||
author: "github-spec-kit"
|
||||
source: "templates/commands/taskstoissues.md"
|
||||
user-invocable: true
|
||||
disable-model-invocation: false
|
||||
---
|
||||
|
||||
|
||||
## User Input
|
||||
|
||||
```text
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
You **MUST** consider the user input before proceeding (if not empty).
|
||||
|
||||
## Pre-Execution Checks
|
||||
|
||||
**Check for extension hooks (before tasks-to-issues conversion)**:
|
||||
- Check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it exists, read it and look for entries under the `hooks.before_taskstoissues` key
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Pre-Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
- **Mandatory hook** (`optional: false`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Pre-Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
|
||||
Wait for the result of the hook command before proceeding to the Outline.
|
||||
```
|
||||
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||
|
||||
## Outline
|
||||
|
||||
1. Run `.specify/scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks` from repo root and parse FEATURE_DIR and AVAILABLE_DOCS list. All paths must be absolute. For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot").
|
||||
1. From the executed script, extract the path to **tasks**.
|
||||
1. Get the Git remote by running:
|
||||
|
||||
```bash
|
||||
git config --get remote.origin.url
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
> ONLY PROCEED TO NEXT STEPS IF THE REMOTE IS A GITHUB URL
|
||||
|
||||
1. For each task in the list, use the GitHub MCP server to create a new issue in the repository that is representative of the Git remote.
|
||||
|
||||
> [!CAUTION]
|
||||
> UNDER NO CIRCUMSTANCES EVER CREATE ISSUES IN REPOSITORIES THAT DO NOT MATCH THE REMOTE URL
|
||||
|
||||
## Post-Execution Checks
|
||||
|
||||
**Check for extension hooks (after tasks-to-issues conversion)**:
|
||||
Check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it exists, read it and look for entries under the `hooks.after_taskstoissues` key
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
- **Mandatory hook** (`optional: false`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
```
|
||||
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"feature_directory": "specs/001-standardise-logging"
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"ai": "claude",
|
||||
"ai_skills": true,
|
||||
"branch_numbering": "sequential",
|
||||
"context_file": "CLAUDE.md",
|
||||
"here": true,
|
||||
"integration": "claude",
|
||||
"script": "sh",
|
||||
"speckit_version": "0.8.17"
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"version": "0.8.17",
|
||||
"integration_state_schema": 1,
|
||||
"installed_integrations": [
|
||||
"claude"
|
||||
],
|
||||
"integration_settings": {
|
||||
"claude": {
|
||||
"script": "sh",
|
||||
"invoke_separator": "-"
|
||||
}
|
||||
},
|
||||
"integration": "claude",
|
||||
"default_integration": "claude"
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"integration": "claude",
|
||||
"version": "0.8.17",
|
||||
"installed_at": "2026-06-28T14:03:58.248753+00:00",
|
||||
"files": {
|
||||
".claude/skills/speckit-analyze/SKILL.md": "2eef0fbff6cad15c9d4714d8986192387811c971a82a1135ab0404f3db0c5e90",
|
||||
".claude/skills/speckit-checklist/SKILL.md": "26419fc118dcd9c4e1e977460696a04b7757b8fb0a2d1ff9c64732669deb7977",
|
||||
".claude/skills/speckit-clarify/SKILL.md": "35795a017d6d2ed3ace35a333b22e450788e539e24ed5c756d815aa34cd5b6f5",
|
||||
".claude/skills/speckit-constitution/SKILL.md": "c1a044aba243ca6aff627fb5e4404feb6f1108d4f7dd174631bee3ae477d6c15",
|
||||
".claude/skills/speckit-implement/SKILL.md": "042a0415ce60a5b66adf039da431d9a98cb4897ff330635e0bab0becbbaf3cd2",
|
||||
".claude/skills/speckit-plan/SKILL.md": "bad923f08ffd0e61e37da038626bff374386541fb4c69858a5473d6dc302ee17",
|
||||
".claude/skills/speckit-specify/SKILL.md": "4cb1cb21f598288859e84cbf29052c1c41f213a2ca42b79b3a6d6293dde99d02",
|
||||
".claude/skills/speckit-tasks/SKILL.md": "ca048fadbe29d761c05b497605789656d112a3a9db5f774efcf84503e77a684e",
|
||||
".claude/skills/speckit-taskstoissues/SKILL.md": "99bf5ffd90dcb57b63007c7f659a5160a18ce6feb82889895808e2d277abe83b"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"integration": "speckit",
|
||||
"version": "0.8.17",
|
||||
"installed_at": "2026-06-28T14:03:58.264142+00:00",
|
||||
"files": {
|
||||
".specify/scripts/bash/common.sh": "0ac0b422fecf41a172a21dc163be1558a87e8a8b885cb48eb9307c99427c4102",
|
||||
".specify/scripts/bash/setup-plan.sh": "b23cca3d769a217ab812a6059adb549622471f6893af234cf98ca2019ac4e1a1",
|
||||
".specify/scripts/bash/setup-tasks.sh": "7aeee15192a5ab3ba9ff3c3ae450d9994043bf0493c1eabc840da72a9742fc87",
|
||||
".specify/scripts/bash/check-prerequisites.sh": "f4541a00257f035aa55a9fede6d964e51e6851c3dc2f81d0a6f367db18944765",
|
||||
".specify/scripts/bash/create-new-feature.sh": "bcf4964ca0c6c78717bb42d9e66b8c7e5ee82779cd96afc5aa7b08b75abe5790",
|
||||
".specify/templates/constitution-template.md": "ce7549540fa45543cca797a150201d868e64495fdff39dc38246fb17bd4024b3",
|
||||
".specify/templates/checklist-template.md": "c37695297e5d3153d64f82c21223509940b13932046c7961c42d1d669516130c",
|
||||
".specify/templates/tasks-template.md": "fc29a233f6f5a27ca31f1aa46b596af6500c627441c6e62b2bc4a1d721525842",
|
||||
".specify/templates/spec-template.md": "3945437fc35cd30a5b2bf7beea680337c3516826d3efa5a6b92c4a7eca1ba28e",
|
||||
".specify/templates/plan-template.md": "cc7f7979cf8d8836ec26492785affd80791d3422a2b745062ec695be8c985ef7"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
# comfydv Constitution
|
||||
|
||||
## Core Principles
|
||||
|
||||
### I. ComfyUI Contract First
|
||||
Every node **must** expose `INPUT_TYPES`, `RETURN_TYPES`, `RETURN_NAMES`, `FUNCTION`, and `CATEGORY` to comply with ComfyUI's node registration contract. `NODE_CLASS_MAPPINGS` and `NODE_DISPLAY_NAME_MAPPINGS` in the package `__init__.py` are the only install-time interface — nothing else. Do not require changes to ComfyUI itself.
|
||||
|
||||
### II. Sandbox All User-Supplied Code
|
||||
Any template or expression evaluated at runtime from user input **must** run inside Jinja2's `SandboxedEnvironment` or equivalent. Plain `eval()` or `exec()` on user strings is forbidden. The `additional_context` dict is the only way to expose utilities to templates.
|
||||
|
||||
### III. Test-First (NON-NEGOTIABLE)
|
||||
Write the test before writing the implementation. Tests **must** pass without a live ComfyUI instance — all `comfy.*` and `server.*` imports are runtime-guarded (`if "comfy" in sys.modules`). The test suite runs via `uv run pytest`. Red → Green → Refactor; do not commit red tests.
|
||||
|
||||
### IV. Graceful Degradation Outside ComfyUI
|
||||
Modules imported outside ComfyUI (e.g., in tests or CI) must log a warning and continue loading rather than raising an `ImportError`. The node's core logic (template parsing, key extraction, formatting) must be independently testable without any ComfyUI dependency.
|
||||
|
||||
### V. Simplicity — Function Before Class
|
||||
Prefer module-level functions over class methods where there is no shared state. Prefer a single script over a service. Use classes only when ComfyUI's node registration pattern requires them. No premature abstractions; three similar lines beat a helper no one asked for.
|
||||
|
||||
### VI. Fixed Output Positions
|
||||
Primary outputs (`formatted_string`, `saved_file_path`) **must** always occupy positions 0 and 1 in `RETURN_TYPES`/`RETURN_NAMES`. Variable pass-through outputs follow at positions 2+. This contract, once established for a node, is immutable — changing it breaks existing workflows silently.
|
||||
|
||||
## Quality Gates
|
||||
|
||||
Before any bullet is considered done:
|
||||
|
||||
```bash
|
||||
uv run ruff check --fix && uv run ruff format
|
||||
uv run ty check
|
||||
uv run pytest
|
||||
beacon doctor --strict
|
||||
```
|
||||
|
||||
All four must be green. No exceptions.
|
||||
|
||||
## Governance
|
||||
|
||||
This constitution supersedes style preferences and any CLAUDE.md defaults where they conflict. Amendments require an ADR entry in `project-management/ADRs/` with a rationale and migration note. All PRs are checked against this constitution before merge.
|
||||
|
||||
**Version**: 1.0.0 | **Ratified**: 2026-06-28 | **Last Amended**: 2026-06-28
|
||||
Executable
+192
@@ -0,0 +1,192 @@
|
||||
#!/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
|
||||
_paths_output=$(get_feature_paths) || { echo "ERROR: Failed to resolve feature paths" >&2; exit 1; }
|
||||
eval "$_paths_output"
|
||||
unset _paths_output
|
||||
|
||||
# If paths-only mode, output paths and exit (no validation)
|
||||
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 branch name
|
||||
check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1
|
||||
|
||||
# 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
|
||||
Executable
+644
@@ -0,0 +1,644 @@
|
||||
#!/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
|
||||
}
|
||||
|
||||
# Safely read .specify/feature.json's "feature_directory" value.
|
||||
# Prints the raw value (possibly relative) to stdout, or empty string if the file
|
||||
# is missing, unparseable, or does not contain the key. Always returns 0 so callers
|
||||
# under `set -e` cannot be aborted by parser failure.
|
||||
# Parser order mirrors the historical get_feature_paths behavior: jq -> python3 -> grep/sed.
|
||||
read_feature_json_feature_directory() {
|
||||
local repo_root="$1"
|
||||
local fj="$repo_root/.specify/feature.json"
|
||||
[[ -f "$fj" ]] || { printf '%s' ''; return 0; }
|
||||
|
||||
local _fd=''
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
if ! _fd=$(jq -r '.feature_directory // empty' "$fj" 2>/dev/null); then
|
||||
_fd=''
|
||||
fi
|
||||
elif command -v python3 >/dev/null 2>&1; then
|
||||
# Use Python so pretty-printed/multi-line JSON still parses correctly.
|
||||
if ! _fd=$(python3 -c "import json,sys; d=json.load(open(sys.argv[1])); v=d.get('feature_directory'); print(v if v else '')" "$fj" 2>/dev/null); then
|
||||
_fd=''
|
||||
fi
|
||||
else
|
||||
# Last-resort single-line grep/sed fallback. The `|| true` guards against
|
||||
# grep returning 1 (no match) aborting under `set -e` / `pipefail`.
|
||||
_fd=$( { grep -E '"feature_directory"[[:space:]]*:' "$fj" 2>/dev/null || true; } \
|
||||
| head -n 1 \
|
||||
| sed -E 's/^[^:]*:[[:space:]]*"([^"]*)".*$/\1/' )
|
||||
fi
|
||||
|
||||
printf '%s' "$_fd"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Returns 0 when .specify/feature.json lists feature_directory that exists as a directory
|
||||
# and matches the resolved active FEATURE_DIR (so /speckit-plan can skip git branch pattern checks).
|
||||
# Delegates parsing to read_feature_json_feature_directory, which is safe under `set -e`.
|
||||
feature_json_matches_feature_dir() {
|
||||
local repo_root="$1"
|
||||
local active_feature_dir="$2"
|
||||
|
||||
local _fd
|
||||
_fd=$(read_feature_json_feature_directory "$repo_root")
|
||||
|
||||
[[ -n "$_fd" ]] || return 1
|
||||
[[ "$_fd" != /* ]] && _fd="$repo_root/$_fd"
|
||||
[[ -d "$_fd" ]] || return 1
|
||||
|
||||
local norm_json norm_active
|
||||
norm_json="$(cd -- "$_fd" 2>/dev/null && pwd -P)" || return 1
|
||||
norm_active="$(cd -- "$active_feature_dir" 2>/dev/null && pwd -P)" || return 1
|
||||
|
||||
[[ "$norm_json" == "$norm_active" ]]
|
||||
}
|
||||
|
||||
# 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
|
||||
# Shared, set -e-safe parser: jq -> python3 -> grep/sed. Returns empty on
|
||||
# missing/unparseable/unset so we fall through to the branch-prefix lookup.
|
||||
local _fd
|
||||
_fd=$(read_feature_json_feature_directory "$repo_root")
|
||||
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/<preset-id>/templates/ (sorted by priority from .registry)
|
||||
# 3. .specify/extensions/<ext-id>/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) if isinstance(x[1], dict) else 10):
|
||||
if isinstance(meta, dict) and meta.get('enabled', True) is not False:
|
||||
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
|
||||
}
|
||||
|
||||
# Resolve a template name to composed content using composition strategies.
|
||||
# Reads strategy metadata from preset manifests and composes content
|
||||
# from multiple layers using prepend, append, or wrap strategies.
|
||||
#
|
||||
# Usage: CONTENT=$(resolve_template_content "template-name" "$REPO_ROOT")
|
||||
# Returns composed content string on stdout; exit code 1 if not found.
|
||||
resolve_template_content() {
|
||||
local template_name="$1"
|
||||
local repo_root="$2"
|
||||
local base="$repo_root/.specify/templates"
|
||||
|
||||
# Collect all layers (highest priority first)
|
||||
local -a layer_paths=()
|
||||
local -a layer_strategies=()
|
||||
|
||||
# Priority 1: Project overrides (always "replace")
|
||||
local override="$base/overrides/${template_name}.md"
|
||||
if [ -f "$override" ]; then
|
||||
layer_paths+=("$override")
|
||||
layer_strategies+=("replace")
|
||||
fi
|
||||
|
||||
# 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"
|
||||
local sorted_presets=""
|
||||
if [ -f "$registry_file" ] && command -v python3 >/dev/null 2>&1; then
|
||||
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) if isinstance(x[1], dict) else 10):
|
||||
if isinstance(meta, dict) and meta.get('enabled', True) is not False:
|
||||
print(pid)
|
||||
except Exception:
|
||||
sys.exit(1)
|
||||
" 2>/dev/null); then
|
||||
if [ -n "$sorted_presets" ]; then
|
||||
local yaml_warned=false
|
||||
while IFS= read -r preset_id; do
|
||||
# Read strategy and file path from preset manifest
|
||||
local strategy="replace"
|
||||
local manifest_file=""
|
||||
local manifest="$presets_dir/$preset_id/preset.yml"
|
||||
if [ -f "$manifest" ] && command -v python3 >/dev/null 2>&1; then
|
||||
# Requires PyYAML; falls back to replace/convention if unavailable
|
||||
local result
|
||||
local py_stderr
|
||||
py_stderr=$(mktemp)
|
||||
result=$(SPECKIT_MANIFEST="$manifest" SPECKIT_TMPL="$template_name" python3 -c "
|
||||
import sys, os
|
||||
try:
|
||||
import yaml
|
||||
except ImportError:
|
||||
print('yaml_missing', file=sys.stderr)
|
||||
print('replace\t')
|
||||
sys.exit(0)
|
||||
try:
|
||||
with open(os.environ['SPECKIT_MANIFEST']) as f:
|
||||
data = yaml.safe_load(f)
|
||||
for t in data.get('provides', {}).get('templates', []):
|
||||
if t.get('name') == os.environ['SPECKIT_TMPL'] and t.get('type', 'template') == 'template':
|
||||
print(t.get('strategy', 'replace') + '\t' + t.get('file', ''))
|
||||
sys.exit(0)
|
||||
print('replace\t')
|
||||
except Exception:
|
||||
print('replace\t')
|
||||
" 2>"$py_stderr")
|
||||
local parse_status=$?
|
||||
if [ $parse_status -eq 0 ] && [ -n "$result" ]; then
|
||||
IFS=$'\t' read -r strategy manifest_file <<< "$result"
|
||||
strategy=$(printf '%s' "$strategy" | tr '[:upper:]' '[:lower:]')
|
||||
fi
|
||||
if [ "$yaml_warned" = false ] && grep -q 'yaml_missing' "$py_stderr" 2>/dev/null; then
|
||||
echo "Warning: PyYAML not available; composition strategies may be ignored" >&2
|
||||
yaml_warned=true
|
||||
fi
|
||||
rm -f "$py_stderr"
|
||||
fi
|
||||
# Try manifest file path first, then convention path
|
||||
local candidate=""
|
||||
if [ -n "$manifest_file" ]; then
|
||||
# Reject absolute paths and parent traversal
|
||||
case "$manifest_file" in
|
||||
/*|*../*|../*) manifest_file="" ;;
|
||||
esac
|
||||
fi
|
||||
if [ -n "$manifest_file" ]; then
|
||||
local mf="$presets_dir/$preset_id/$manifest_file"
|
||||
[ -f "$mf" ] && candidate="$mf"
|
||||
fi
|
||||
if [ -z "$candidate" ]; then
|
||||
local cf="$presets_dir/$preset_id/templates/${template_name}.md"
|
||||
[ -f "$cf" ] && candidate="$cf"
|
||||
fi
|
||||
if [ -n "$candidate" ]; then
|
||||
layer_paths+=("$candidate")
|
||||
layer_strategies+=("$strategy")
|
||||
fi
|
||||
done <<< "$sorted_presets"
|
||||
fi
|
||||
else
|
||||
# python3 failed — fall back to unordered directory scan (replace only)
|
||||
for preset in "$presets_dir"/*/; do
|
||||
[ -d "$preset" ] || continue
|
||||
local candidate="$preset/templates/${template_name}.md"
|
||||
if [ -f "$candidate" ]; then
|
||||
layer_paths+=("$candidate")
|
||||
layer_strategies+=("replace")
|
||||
fi
|
||||
done
|
||||
fi
|
||||
else
|
||||
# No python3 or registry — fall back to unordered directory scan (replace only)
|
||||
for preset in "$presets_dir"/*/; do
|
||||
[ -d "$preset" ] || continue
|
||||
local candidate="$preset/templates/${template_name}.md"
|
||||
if [ -f "$candidate" ]; then
|
||||
layer_paths+=("$candidate")
|
||||
layer_strategies+=("replace")
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
# Priority 3: Extension-provided templates (always "replace")
|
||||
local ext_dir="$repo_root/.specify/extensions"
|
||||
if [ -d "$ext_dir" ]; then
|
||||
for ext in "$ext_dir"/*/; do
|
||||
[ -d "$ext" ] || continue
|
||||
case "$(basename "$ext")" in .*) continue;; esac
|
||||
local candidate="$ext/templates/${template_name}.md"
|
||||
if [ -f "$candidate" ]; then
|
||||
layer_paths+=("$candidate")
|
||||
layer_strategies+=("replace")
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Priority 4: Core templates (always "replace")
|
||||
local core="$base/${template_name}.md"
|
||||
if [ -f "$core" ]; then
|
||||
layer_paths+=("$core")
|
||||
layer_strategies+=("replace")
|
||||
fi
|
||||
|
||||
local count=${#layer_paths[@]}
|
||||
[ "$count" -eq 0 ] && return 1
|
||||
|
||||
# Check if any layer uses a non-replace strategy
|
||||
local has_composition=false
|
||||
for s in "${layer_strategies[@]}"; do
|
||||
[ "$s" != "replace" ] && has_composition=true && break
|
||||
done
|
||||
|
||||
# If the top (highest-priority) layer is replace, it wins entirely —
|
||||
# lower layers are irrelevant regardless of their strategies.
|
||||
if [ "${layer_strategies[0]}" = "replace" ]; then
|
||||
cat "${layer_paths[0]}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ "$has_composition" = false ]; then
|
||||
cat "${layer_paths[0]}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Find the effective base: scan from highest priority (index 0) downward
|
||||
# to find the nearest replace layer. Only compose layers above that base.
|
||||
local base_idx=-1
|
||||
local i
|
||||
for (( i=0; i<count; i++ )); do
|
||||
if [ "${layer_strategies[$i]}" = "replace" ]; then
|
||||
base_idx=$i
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $base_idx -lt 0 ]; then
|
||||
return 1 # no base layer found
|
||||
fi
|
||||
|
||||
# Read the base content; compose layers above the base (higher priority)
|
||||
local content
|
||||
content=$(cat "${layer_paths[$base_idx]}"; printf x)
|
||||
content="${content%x}"
|
||||
|
||||
for (( i=base_idx-1; i>=0; i-- )); do
|
||||
local path="${layer_paths[$i]}"
|
||||
local strat="${layer_strategies[$i]}"
|
||||
local layer_content
|
||||
# Preserve trailing newlines
|
||||
layer_content=$(cat "$path"; printf x)
|
||||
layer_content="${layer_content%x}"
|
||||
|
||||
case "$strat" in
|
||||
replace) content="$layer_content" ;;
|
||||
prepend) content="$(printf '%s\n\n%s' "$layer_content" "$content")" ;;
|
||||
append) content="$(printf '%s\n\n%s' "$content" "$layer_content")" ;;
|
||||
wrap)
|
||||
case "$layer_content" in
|
||||
*'{CORE_TEMPLATE}'*) ;;
|
||||
*) echo "Error: wrap strategy missing {CORE_TEMPLATE} placeholder" >&2; return 1 ;;
|
||||
esac
|
||||
while [[ "$layer_content" == *'{CORE_TEMPLATE}'* ]]; do
|
||||
local before="${layer_content%%\{CORE_TEMPLATE\}*}"
|
||||
local after="${layer_content#*\{CORE_TEMPLATE\}}"
|
||||
layer_content="${before}${content}${after}"
|
||||
done
|
||||
content="$layer_content"
|
||||
;;
|
||||
*) echo "Error: unknown strategy '$strat'" >&2; return 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
printf '%s' "$content"
|
||||
return 0
|
||||
}
|
||||
Executable
+413
@@ -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 <name>] [--number N] [--timestamp] <feature_description>"
|
||||
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 <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 <name>] [--number N] [--timestamp] <feature_description>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Trim whitespace and validate description is not empty (e.g., user passed only whitespace)
|
||||
FEATURE_DESCRIPTION=$(echo "$FEATURE_DESCRIPTION" | sed -E 's/^[[:space:]]+|[[:space:]]+$//g')
|
||||
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
|
||||
Executable
+91
@@ -0,0 +1,91 @@
|
||||
#!/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
|
||||
|
||||
# If feature.json pins an existing feature directory, branch naming is not required.
|
||||
if ! feature_json_matches_feature_dir "$REPO_ROOT" "$FEATURE_DIR"; then
|
||||
check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1
|
||||
fi
|
||||
|
||||
# Ensure the feature directory exists
|
||||
mkdir -p "$FEATURE_DIR"
|
||||
|
||||
# Copy plan template if plan doesn't already exist
|
||||
if [[ -f "$IMPL_PLAN" ]]; then
|
||||
if $JSON_MODE; then
|
||||
echo "Plan already exists at $IMPL_PLAN, skipping template copy" >&2
|
||||
else
|
||||
echo "Plan already exists at $IMPL_PLAN, skipping template copy"
|
||||
fi
|
||||
else
|
||||
TEMPLATE=$(resolve_template "plan-template" "$REPO_ROOT") || true
|
||||
if [[ -n "$TEMPLATE" ]] && [[ -f "$TEMPLATE" ]]; then
|
||||
cp "$TEMPLATE" "$IMPL_PLAN"
|
||||
if $JSON_MODE; then
|
||||
echo "Copied plan template to $IMPL_PLAN" >&2
|
||||
else
|
||||
echo "Copied plan template to $IMPL_PLAN"
|
||||
fi
|
||||
else
|
||||
if $JSON_MODE; then
|
||||
echo "Warning: Plan template not found" >&2
|
||||
else
|
||||
echo "Warning: Plan template not found"
|
||||
fi
|
||||
# Create a basic plan file if template doesn't exist
|
||||
touch "$IMPL_PLAN"
|
||||
fi
|
||||
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
|
||||
|
||||
Executable
+96
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
# Parse command line arguments
|
||||
JSON_MODE=false
|
||||
|
||||
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
|
||||
;;
|
||||
*) echo "ERROR: Unknown option '$arg'" >&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
|
||||
_paths_output=$(get_feature_paths) || { echo "ERROR: Failed to resolve feature paths" >&2; exit 1; }
|
||||
eval "$_paths_output"
|
||||
unset _paths_output
|
||||
|
||||
# Validate branch
|
||||
# If feature.json pins an existing feature directory, branch naming is not required.
|
||||
if ! feature_json_matches_feature_dir "$REPO_ROOT" "$FEATURE_DIR"; then
|
||||
check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || 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
|
||||
|
||||
if [[ ! -f "$FEATURE_SPEC" ]]; then
|
||||
echo "ERROR: spec.md not found in $FEATURE_DIR" >&2
|
||||
echo "Run /speckit-specify first to create the feature structure." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build available docs list
|
||||
docs=()
|
||||
[[ -f "$RESEARCH" ]] && docs+=("research.md")
|
||||
[[ -f "$DATA_MODEL" ]] && docs+=("data-model.md")
|
||||
if [[ -d "$CONTRACTS_DIR" ]] && [[ -n "$(ls -A "$CONTRACTS_DIR" 2>/dev/null)" ]]; then
|
||||
docs+=("contracts/")
|
||||
fi
|
||||
[[ -f "$QUICKSTART" ]] && docs+=("quickstart.md")
|
||||
|
||||
# Resolve tasks template through override stack
|
||||
TASKS_TEMPLATE=$(resolve_template "tasks-template" "$REPO_ROOT") || true
|
||||
if [[ -z "$TASKS_TEMPLATE" ]] || [[ ! -f "$TASKS_TEMPLATE" ]]; then
|
||||
echo "ERROR: Could not resolve required tasks-template from the template override stack for $REPO_ROOT" >&2
|
||||
echo "Template 'tasks-template' was not found in any supported location (overrides, presets, extensions, or shared core). Add an override at .specify/templates/overrides/tasks-template.md, or run 'specify init' / reinstall shared infra to restore the core .specify/templates/tasks-template.md template." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Output results
|
||||
if $JSON_MODE; then
|
||||
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" \
|
||||
--arg tasks_template "${TASKS_TEMPLATE:-}" \
|
||||
'{FEATURE_DIR:$feature_dir,AVAILABLE_DOCS:$docs,TASKS_TEMPLATE:$tasks_template}'
|
||||
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,"TASKS_TEMPLATE":"%s"}\n' \
|
||||
"$(json_escape "$FEATURE_DIR")" "$json_docs" "$(json_escape "${TASKS_TEMPLATE:-}")"
|
||||
fi
|
||||
else
|
||||
echo "FEATURE_DIR: $FEATURE_DIR"
|
||||
echo "TASKS_TEMPLATE: ${TASKS_TEMPLATE:-not found}"
|
||||
echo "AVAILABLE_DOCS:"
|
||||
check_file "$RESEARCH" "research.md"
|
||||
check_file "$DATA_MODEL" "data-model.md"
|
||||
check_dir "$CONTRACTS_DIR" "contracts/"
|
||||
check_file "$QUICKSTART" "quickstart.md"
|
||||
fi
|
||||
@@ -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.
|
||||
|
||||
<!--
|
||||
============================================================================
|
||||
IMPORTANT: The checklist items below are SAMPLE ITEMS for illustration only.
|
||||
|
||||
The /speckit-checklist command MUST replace these with actual items based on:
|
||||
- User's specific checklist request
|
||||
- Feature requirements from spec.md
|
||||
- Technical context from plan.md
|
||||
- Implementation details from tasks.md
|
||||
|
||||
DO NOT keep these sample items in the generated checklist file.
|
||||
============================================================================
|
||||
-->
|
||||
|
||||
## [Category 1]
|
||||
|
||||
- [ ] CHK001 First checklist item with clear action
|
||||
- [ ] CHK002 Second checklist item
|
||||
- [ ] CHK003 Third checklist item
|
||||
|
||||
## [Category 2]
|
||||
|
||||
- [ ] CHK004 Another category item
|
||||
- [ ] CHK005 Item with specific criteria
|
||||
- [ ] CHK006 Final item in this category
|
||||
|
||||
## Notes
|
||||
|
||||
- Check items off as completed: `[x]`
|
||||
- Add comments or findings inline
|
||||
- Link to relevant resources or documentation
|
||||
- Items are numbered sequentially for easy reference
|
||||
@@ -0,0 +1,50 @@
|
||||
# [PROJECT_NAME] Constitution
|
||||
<!-- Example: Spec Constitution, TaskFlow Constitution, etc. -->
|
||||
|
||||
## Core Principles
|
||||
|
||||
### [PRINCIPLE_1_NAME]
|
||||
<!-- Example: I. Library-First -->
|
||||
[PRINCIPLE_1_DESCRIPTION]
|
||||
<!-- Example: Every feature starts as a standalone library; Libraries must be self-contained, independently testable, documented; Clear purpose required - no organizational-only libraries -->
|
||||
|
||||
### [PRINCIPLE_2_NAME]
|
||||
<!-- Example: II. CLI Interface -->
|
||||
[PRINCIPLE_2_DESCRIPTION]
|
||||
<!-- Example: Every library exposes functionality via CLI; Text in/out protocol: stdin/args → stdout, errors → stderr; Support JSON + human-readable formats -->
|
||||
|
||||
### [PRINCIPLE_3_NAME]
|
||||
<!-- Example: III. Test-First (NON-NEGOTIABLE) -->
|
||||
[PRINCIPLE_3_DESCRIPTION]
|
||||
<!-- Example: TDD mandatory: Tests written → User approved → Tests fail → Then implement; Red-Green-Refactor cycle strictly enforced -->
|
||||
|
||||
### [PRINCIPLE_4_NAME]
|
||||
<!-- Example: IV. Integration Testing -->
|
||||
[PRINCIPLE_4_DESCRIPTION]
|
||||
<!-- Example: Focus areas requiring integration tests: New library contract tests, Contract changes, Inter-service communication, Shared schemas -->
|
||||
|
||||
### [PRINCIPLE_5_NAME]
|
||||
<!-- Example: V. Observability, VI. Versioning & Breaking Changes, VII. Simplicity -->
|
||||
[PRINCIPLE_5_DESCRIPTION]
|
||||
<!-- Example: Text I/O ensures debuggability; Structured logging required; Or: MAJOR.MINOR.BUILD format; Or: Start simple, YAGNI principles -->
|
||||
|
||||
## [SECTION_2_NAME]
|
||||
<!-- Example: Additional Constraints, Security Requirements, Performance Standards, etc. -->
|
||||
|
||||
[SECTION_2_CONTENT]
|
||||
<!-- Example: Technology stack requirements, compliance standards, deployment policies, etc. -->
|
||||
|
||||
## [SECTION_3_NAME]
|
||||
<!-- Example: Development Workflow, Review Process, Quality Gates, etc. -->
|
||||
|
||||
[SECTION_3_CONTENT]
|
||||
<!-- Example: Code review requirements, testing gates, deployment approval process, etc. -->
|
||||
|
||||
## Governance
|
||||
<!-- Example: Constitution supersedes all other practices; Amendments require documentation, approval, migration plan -->
|
||||
|
||||
[GOVERNANCE_RULES]
|
||||
<!-- Example: All PRs/reviews must verify compliance; Complexity must be justified; Use [GUIDANCE_FILE] for runtime development guidance -->
|
||||
|
||||
**Version**: [CONSTITUTION_VERSION] | **Ratified**: [RATIFICATION_DATE] | **Last Amended**: [LAST_AMENDED_DATE]
|
||||
<!-- Example: Version: 2.1.1 | Ratified: 2025-06-13 | Last Amended: 2025-07-16 -->
|
||||
@@ -0,0 +1,113 @@
|
||||
# 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
|
||||
|
||||
<!--
|
||||
ACTION REQUIRED: Replace the content in this section with the technical details
|
||||
for the project. The structure here is presented in advisory capacity to guide
|
||||
the iteration process.
|
||||
-->
|
||||
|
||||
**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)
|
||||
<!--
|
||||
ACTION REQUIRED: Replace the placeholder tree below with the concrete layout
|
||||
for this feature. Delete unused options and expand the chosen structure with
|
||||
real paths (e.g., apps/admin, packages/something). The delivered plan must
|
||||
not include Option labels.
|
||||
-->
|
||||
|
||||
```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] |
|
||||
@@ -0,0 +1,131 @@
|
||||
# Feature Specification: [FEATURE NAME]
|
||||
|
||||
**Feature Branch**: `[###-feature-name]`
|
||||
|
||||
**Created**: [DATE]
|
||||
|
||||
**Status**: Draft
|
||||
|
||||
**Input**: User description: "$ARGUMENTS"
|
||||
|
||||
## User Scenarios & Testing *(mandatory)*
|
||||
|
||||
<!--
|
||||
IMPORTANT: User stories should be PRIORITIZED as user journeys ordered by importance.
|
||||
Each user story/journey must be INDEPENDENTLY TESTABLE - meaning if you implement just ONE of them,
|
||||
you should still have a viable MVP (Minimum Viable Product) that delivers value.
|
||||
|
||||
Assign priorities (P1, P2, P3, etc.) to each story, where P1 is the most critical.
|
||||
Think of each story as a standalone slice of functionality that can be:
|
||||
- Developed independently
|
||||
- Tested independently
|
||||
- Deployed independently
|
||||
- Demonstrated to users independently
|
||||
-->
|
||||
|
||||
### User Story 1 - [Brief Title] (Priority: P1)
|
||||
|
||||
[Describe this user journey in plain language]
|
||||
|
||||
**Why this priority**: [Explain the value and why it has this priority level]
|
||||
|
||||
**Independent Test**: [Describe how this can be tested independently - e.g., "Can be fully tested by [specific action] and delivers [specific value]"]
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** [initial state], **When** [action], **Then** [expected outcome]
|
||||
2. **Given** [initial state], **When** [action], **Then** [expected outcome]
|
||||
|
||||
---
|
||||
|
||||
### User Story 2 - [Brief Title] (Priority: P2)
|
||||
|
||||
[Describe this user journey in plain language]
|
||||
|
||||
**Why this priority**: [Explain the value and why it has this priority level]
|
||||
|
||||
**Independent Test**: [Describe how this can be tested independently]
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** [initial state], **When** [action], **Then** [expected outcome]
|
||||
|
||||
---
|
||||
|
||||
### User Story 3 - [Brief Title] (Priority: P3)
|
||||
|
||||
[Describe this user journey in plain language]
|
||||
|
||||
**Why this priority**: [Explain the value and why it has this priority level]
|
||||
|
||||
**Independent Test**: [Describe how this can be tested independently]
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** [initial state], **When** [action], **Then** [expected outcome]
|
||||
|
||||
---
|
||||
|
||||
[Add more user stories as needed, each with an assigned priority]
|
||||
|
||||
### Edge Cases
|
||||
|
||||
<!--
|
||||
ACTION REQUIRED: The content in this section represents placeholders.
|
||||
Fill them out with the right edge cases.
|
||||
-->
|
||||
|
||||
- What happens when [boundary condition]?
|
||||
- How does system handle [error scenario]?
|
||||
|
||||
## Requirements *(mandatory)*
|
||||
|
||||
<!--
|
||||
ACTION REQUIRED: The content in this section represents placeholders.
|
||||
Fill them out with the right functional requirements.
|
||||
-->
|
||||
|
||||
### 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)*
|
||||
|
||||
<!--
|
||||
ACTION REQUIRED: Define measurable success criteria.
|
||||
These must be technology-agnostic and measurable.
|
||||
-->
|
||||
|
||||
### 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
|
||||
|
||||
<!--
|
||||
ACTION REQUIRED: The content in this section represents placeholders.
|
||||
Fill them out with the right assumptions based on reasonable defaults
|
||||
chosen when the feature description did not specify certain details.
|
||||
-->
|
||||
|
||||
- [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"]
|
||||
@@ -0,0 +1,252 @@
|
||||
---
|
||||
|
||||
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
|
||||
|
||||
<!--
|
||||
============================================================================
|
||||
IMPORTANT: The tasks below are SAMPLE TASKS for illustration purposes only.
|
||||
|
||||
The /speckit-tasks command MUST replace these with actual tasks based on:
|
||||
- User stories from spec.md (with their priorities P1, P2, P3...)
|
||||
- Feature requirements from plan.md
|
||||
- Entities from data-model.md
|
||||
- Endpoints from contracts/
|
||||
|
||||
Tasks MUST be organized by user story so each story can be:
|
||||
- Implemented independently
|
||||
- Tested independently
|
||||
- Delivered as an MVP increment
|
||||
|
||||
DO NOT keep these sample tasks in the generated tasks.md file.
|
||||
============================================================================
|
||||
-->
|
||||
|
||||
## 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
|
||||
@@ -0,0 +1,77 @@
|
||||
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:
|
||||
# 0.8.5 is the first release with engine-side resolution of the
|
||||
# ``integration: "auto"`` default. Older versions would treat "auto"
|
||||
# as a literal integration key and fail at dispatch.
|
||||
speckit_version: ">=0.8.5"
|
||||
integrations:
|
||||
# The four commands below (specify, plan, tasks, implement) are core
|
||||
# spec-kit commands provided by every integration. The list here is an
|
||||
# advisory, non-exhaustive compatibility hint following the documented
|
||||
# ``any: [...]`` schema -- it is NOT a closed set. The workflow runs
|
||||
# against any integration the project was initialized with, including
|
||||
# ones not listed below, as long as that integration provides the four
|
||||
# core commands referenced in ``steps``.
|
||||
any:
|
||||
- "claude"
|
||||
- "copilot"
|
||||
- "gemini"
|
||||
- "opencode"
|
||||
|
||||
inputs:
|
||||
spec:
|
||||
type: string
|
||||
required: true
|
||||
prompt: "Describe what you want to build"
|
||||
integration:
|
||||
type: string
|
||||
default: "auto"
|
||||
prompt: "Integration to use (e.g. claude, copilot, gemini; 'auto' uses the project's initialized integration)"
|
||||
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 }}"
|
||||
@@ -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-06-28T14:03:58.298046+00:00",
|
||||
"updated_at": "2026-06-28T14:03:58.298057+00:00"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- BEACON framework bootstrap: problem statement, constitution, roadmap, architecture document
|
||||
- `CHANGELOG.md` (this file)
|
||||
- README: What-is-this, Install, and Quickstart sections
|
||||
|
||||
## [0.1.0] — 2026-06-01
|
||||
|
||||
### Added
|
||||
- `FormatString` node: dynamic string formatting via Python f-strings or Jinja2 `SandboxedEnvironment`
|
||||
- Auto-detects template variables and exposes them as typed input sockets
|
||||
- Outputs fixed at positions 0 (`formatted_string`) and 1 (`saved_file_path`); variable pass-through at 2+
|
||||
- Registers aiohttp routes on ComfyUI's `PromptServer` for live widget updates from the JS layer
|
||||
- `RandomChoice` node: seed-controlled selection from an arbitrary number of typed inputs
|
||||
- `CircuitBreaker` node: raises `InterruptProcessingException` to halt a queue run without crashing ComfyUI
|
||||
- Comprehensive pytest suite runnable without a live ComfyUI instance
|
||||
- MkDocs-material documentation site at [darth-veitcher.github.io/comfydv](https://darth-veitcher.github.io/comfydv/stable/)
|
||||
|
||||
### Changed
|
||||
- `FormatString` output order reversed so `formatted_string` and `saved_file_path` are always at fixed positions 0 and 1 (previously variable outputs came first, which broke workflow connections on re-render)
|
||||
@@ -0,0 +1,5 @@
|
||||
<!-- SPECKIT START -->
|
||||
For additional context about technologies to be used, project structure,
|
||||
shell commands, and other important information, read the current plan
|
||||
at specs/001-standardise-logging/plan.md
|
||||
<!-- SPECKIT END -->
|
||||
@@ -1,9 +1,45 @@
|
||||
# comfydv
|
||||
|
||||
A collection of workflow efficiency and quality of life nodes that I've created for personal use out of necessity.
|
||||
A collection of workflow efficiency and quality-of-life nodes built out of necessity for personal ComfyUI use.
|
||||
|
||||
* **String Formatting**: Use either plain python f-strings or more advanced Jinja2 templating to format outputs.
|
||||
* **Random Choice**: Add an abitrary number of inputs and then, with seed control, randomly select one for an output.
|
||||
## What is this?
|
||||
|
||||
`comfydv` fills gaps in ComfyUI's built-in node library: dynamic string formatting, seed-controlled random selection, and graceful workflow interruption. Install it once and connect the nodes like any other — no Python knowledge required.
|
||||
|
||||
| Node | What it does |
|
||||
|------|-------------|
|
||||
| **Format String** | Formats a string from a Python f-string or Jinja2 template. Detects variables in the template and automatically adds/removes input sockets. |
|
||||
| **Random Choice** | Accepts any number of typed inputs and outputs one at random, with a configurable seed for reproducibility. |
|
||||
| **Circuit Breaker** | Halts the current ComfyUI queue run gracefully (raises `InterruptProcessingException`) without crashing the server. |
|
||||
|
||||
## Install
|
||||
|
||||
1. Clone this repo into your ComfyUI `custom_nodes/` directory:
|
||||
|
||||
```bash
|
||||
cd /path/to/ComfyUI/custom_nodes
|
||||
git clone https://github.com/darth-veitcher/comfydv.git
|
||||
```
|
||||
|
||||
2. Restart ComfyUI. The nodes appear under the **dv/** category in the node menu.
|
||||
|
||||
> **Dependencies** (`jinja2`, `rich`, `colorama`, `termcolor`) are listed in `pyproject.toml`. ComfyUI's Python environment must have them installed — run `pip install jinja2 rich colorama termcolor` inside that environment if they are missing.
|
||||
|
||||
## Quickstart
|
||||
|
||||
**Format String — simple f-string:**
|
||||
|
||||
1. Add a **Format String** node to your workflow.
|
||||
2. Set `template_type` to `Simple` and enter `Hello {name}` in the template field.
|
||||
3. A `name` input socket appears automatically — wire it up or type a value.
|
||||
4. Output 0 (`formatted_string`) contains `Hello <your value>`.
|
||||
|
||||
**Random Choice:**
|
||||
|
||||
1. Add a **Random Choice** node.
|
||||
2. Connect any number of inputs (strings, images, conditioning — any type).
|
||||
3. Set `seed` for reproducibility; leave at `0` for a different pick each run.
|
||||
4. Output is whichever input was selected.
|
||||
|
||||
## Documentation
|
||||
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
# comfydv — Roadmap
|
||||
|
||||
<!-- generated by beacon roadmap export — 2026-06-28 -->
|
||||
|
||||
> comfydv is a small, high-quality ComfyUI utility pack that fills the gaps the core node library leaves: composable string formatting, seed-controlled randomisation, and workflow flow-control. Winning looks like: every node is well-tested, installs in one step, produces no surprises in production workflows, and is documented well enough that a non-programmer ComfyUI user can connect it without reading source code.
|
||||
|
||||
## Timeline
|
||||
|
||||
```mermaid
|
||||
gantt
|
||||
title Project Roadmap
|
||||
dateFormat YYYY-MM-DD
|
||||
excludes weekends
|
||||
|
||||
section Planned
|
||||
Logging Modernisation :logging-modernisation, 2026-06-28, 7d
|
||||
|
||||
section Done
|
||||
BEACON Bootstrap :done, beacon-bootstrap, 2026-06-28, 7d
|
||||
|
||||
```
|
||||
|
||||
## Epics
|
||||
|
||||
| Epic | Title | Status | Specs | Fidelity |
|
||||
|---|---|---|---|---|
|
||||
| [logging-modernisation](project-management/Roadmap/epics/logging-modernisation.md) | Logging Modernisation | Planning | — | S? A? T:- |
|
||||
| [beacon-bootstrap](project-management/Roadmap/epics/archive/beacon-bootstrap.md) | BEACON Bootstrap | Done | — | S? A? T:- |
|
||||
|
||||
_Fidelity: `S+/S?` = has specs / none · `A+/A?` = has ADRs / none · `T:N%` = task completion_
|
||||
|
||||
## Active Work
|
||||
|
||||
_No active bullets._
|
||||
@@ -0,0 +1,17 @@
|
||||
# Active bullets
|
||||
|
||||
> "Would I proudly sign my name to this?"
|
||||
|
||||
<!-- BEACON BULLETS START -->
|
||||
<!-- Generated by `beacon bullet list`. Do not edit by hand. -->
|
||||
|
||||
_Run `beacon bullet list` to refresh this section, or `beacon bullet status` for just the current worktree._
|
||||
|
||||
<!-- BEACON BULLETS END -->
|
||||
|
||||
---
|
||||
|
||||
- **Strategy** (quarter-scope): [`project-management/Roadmap/README.md`](project-management/Roadmap/README.md)
|
||||
- **Epics** (weeks-scope): [`project-management/Roadmap/epics/`](project-management/Roadmap/epics/)
|
||||
- **Specs** (days-scope): [`specs/`](specs/) — SpecKit territory
|
||||
- **Live status**: `beacon bullet list` and `beacon epic list --detailed`
|
||||
@@ -0,0 +1,7 @@
|
||||
# BEACON active bullets — committed source of truth (issue #77).
|
||||
#
|
||||
# Maps a non-spec branch to its bullet: branch → {title, owner, started, epic?}.
|
||||
# Maintained by `beacon bullet {start,finish,sweep}`. Committed deliberately so
|
||||
# the bullet survives a fresh clone (`beacon doctor`'s active-bullet check then
|
||||
# works in CI) and no per-branch file is left behind on the trunk after a merge.
|
||||
# `git log -p` on this file is the audit trail of who started which bullet when.
|
||||
@@ -0,0 +1,131 @@
|
||||
# BEACON doctor configuration — example file.
|
||||
#
|
||||
# To activate: rename to `doctor.toml` and edit as needed. While this
|
||||
# file is named `*.toml.example` it has no effect; doctor only reads
|
||||
# `doctor.toml`.
|
||||
#
|
||||
# Every key is optional. Missing keys keep the defaults shown below.
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# disabled_checks — silence specific checks by name.
|
||||
#
|
||||
# Use the kebab-case name printed by `beacon doctor` (e.g. "ghost-epic").
|
||||
# Full list:
|
||||
# manifest, problem-statement, active-bullet, orphan-branch-files,
|
||||
# spec-task-alignment, spec-bdd-coverage, tdd-commit-discipline,
|
||||
# spec-backlink-integrity, epic-spec-listed, epic-coverage, ghost-epic,
|
||||
# epic-spec-lifecycle, epic-adr-coverage, roadmap-staleness,
|
||||
# publishing-mode, release-config-alignment, speckit-integration,
|
||||
# sessions, adr-coverage, readme-completeness, changelog-maintenance,
|
||||
# changelog-vs-commits, public-api-docs, project-urls, docs-freshness,
|
||||
# diataxis-coverage, drift
|
||||
#
|
||||
# Run `beacon doctor --explain <name>` for what each check looks at.
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
# Example: this project doesn't pretend to have epics — silence the
|
||||
# epic-coverage and ghost-epic warnings without removing the discipline
|
||||
# from the framework.
|
||||
# disabled_checks = ["epic-coverage", "ghost-epic"]
|
||||
disabled_checks = []
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# thresholds — tune the numeric defaults.
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
[thresholds]
|
||||
# Sessions older than this trigger the "promote to ADR or delete" WARN.
|
||||
# Default: 30
|
||||
stale_session_age_days = 30
|
||||
|
||||
# Active epic with no in-flight spec and no commit on owned specs in this
|
||||
# window is flagged "ghost". Default: 30
|
||||
ghost_epic_age_days = 30
|
||||
|
||||
# Roadmap/README.md `Last reviewed:` date must be newer than this. Default: 90
|
||||
roadmap_review_age_days = 90
|
||||
|
||||
# At this many commits with zero non-template ADRs, the adr-coverage check
|
||||
# WARNs. Default: 20
|
||||
adr_heuristic_commits = 20
|
||||
|
||||
# An Active epic with zero ADRs is flagged when commit count >= this.
|
||||
# Default: 5
|
||||
epic_adr_min_commits = 5
|
||||
|
||||
# A docs/reference/ page may lag the source it documents by this many
|
||||
# commits before docs-freshness WARNs. Default: 10
|
||||
docs_staleness_commits = 10
|
||||
|
||||
# Fraction of a spec.md `Then` sentence's salient tokens that must appear in
|
||||
# the witness (.feature / test tree) for spec-bdd-coverage to count the
|
||||
# scenario covered. Token-overlap, not literal text — lower this if your
|
||||
# specs are very prose-heavy. Default: 0.5
|
||||
bdd_match_ratio = 0.5
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# quick_fix — branch-name prefixes that mark a bullet as a quick-fix
|
||||
# outside any initiative.
|
||||
#
|
||||
# epic-coverage still WARNs that such a branch has no epic (it genuinely
|
||||
# doesn't), but *holds* the WARN under `--strict` instead of promoting it
|
||||
# to a FAIL — the documented "accept the WARN" escape hatch, made
|
||||
# reachable in CI (issue #94). The other way to declare a quick-fix is per
|
||||
# bullet: `beacon bullet start "<title>" --no-epic`.
|
||||
#
|
||||
# Default prefixes are the conventional non-initiative ones below.
|
||||
# `feat/`/`feature/` are deliberately absent (initiative work should carry
|
||||
# an epic), as is the generic agent prefix `claude/` (it tags *all* agent
|
||||
# branches, so holding on it would gut the gate). Setting this key replaces
|
||||
# the defaults wholesale.
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
# [quick_fix]
|
||||
# branch_prefixes = ["fix/", "chore/", "docs/", "hotfix/", "bugfix/"]
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# docs — user-facing documentation discipline (Diátaxis).
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
# [docs]
|
||||
# diataxis-coverage is opt-in. Set true to require docs/{tutorials,how-to,
|
||||
# reference,explanation}/ each carry a real (non-template) page. Scaffold the
|
||||
# tree with `beacon init --user-facing-docs`. Default: false.
|
||||
# enforce_diataxis = true
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# audit — lifecycle moments at which the `auto-audit` hook nudges toward
|
||||
# `/beacon.audit` (the adversarial epic-completeness auditor).
|
||||
#
|
||||
# design after `beacon epic stub` decomposes an epic — before building
|
||||
# ship before `beacon epic finish` seals it
|
||||
# build after each `beacon spec finish` lands — re-check epic coverage
|
||||
#
|
||||
# `design` + `ship` are on by default. Add `build` to re-audit as specs
|
||||
# land (noisier on fast-moving epics). Setting this key replaces the
|
||||
# default set wholesale. To silence the nudge entirely, disable the
|
||||
# `auto-audit` hook under [hooks] below.
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
# [audit]
|
||||
# moments = ["design", "ship", "build"]
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# hooks — per-hook disable for `.claude/settings.json`.
|
||||
#
|
||||
# Disabled hooks are skipped when BEACON renders the settings.json on
|
||||
# install/upgrade. The drift check honours this list — disabling
|
||||
# auto-lint here won't trip the framework-drift WARN.
|
||||
#
|
||||
# Valid names match the `_name` field in the shipped settings template:
|
||||
# auto-lint PostToolUse Edit|Write under src/ + tests/
|
||||
# auto-doctor PostToolUse Bash matching `beacon bullet finish`
|
||||
# auto-roadmap PostToolUse Bash matching epic/bullet state changes
|
||||
# auto-audit Pre+PostToolUse Bash — /beacon.audit nudges (see [audit])
|
||||
# manifest-write-protect PreToolUse Edit|Write on the BEACON manifest
|
||||
#
|
||||
# For an all-or-nothing toggle, use `beacon init --no-hooks` instead.
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
# [hooks]
|
||||
# disabled = ["auto-lint"]
|
||||
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"ai": [
|
||||
"claude"
|
||||
],
|
||||
"beacon_version": "1.10.0",
|
||||
"framework_files": [
|
||||
".claude/CLAUDE.md",
|
||||
".claude/agents/beacon-auditor.md",
|
||||
".claude/agents/beacon-engineering.md",
|
||||
".claude/agents/beacon-product.md",
|
||||
".claude/agents/beacon-reviewer.md",
|
||||
".claude/commands/beacon/align.md",
|
||||
".claude/commands/beacon/audit.md",
|
||||
".claude/commands/beacon/constitution.md",
|
||||
".claude/commands/beacon/continue.md",
|
||||
".claude/commands/beacon/engineering.md",
|
||||
".claude/commands/beacon/epics.md",
|
||||
".claude/commands/beacon/implement.md",
|
||||
".claude/commands/beacon/plan.md",
|
||||
".claude/commands/beacon/prd.md",
|
||||
".claude/commands/beacon/product.md",
|
||||
".claude/commands/beacon/review.md",
|
||||
".claude/commands/beacon/roadmap.md",
|
||||
".claude/commands/beacon/seed.md",
|
||||
".claude/commands/beacon/specify.md",
|
||||
".claude/commands/beacon/status.md",
|
||||
".claude/commands/beacon/tasks.md",
|
||||
".claude/commands/design/diagram.md",
|
||||
".claude/commands/design/evaluate.md",
|
||||
".claude/commands/design/wardley.md",
|
||||
".claude/commands/git/feature.md",
|
||||
".claude/commands/git/pr.md",
|
||||
".claude/commands/git/release.md",
|
||||
".claude/commands/init.md",
|
||||
".claude/settings.json",
|
||||
".claude/skills/beacon-build/SKILL.md",
|
||||
".claude/skills/beacon-design/SKILL.md",
|
||||
".claude/skills/beacon-seed/SKILL.md",
|
||||
".claude/skills/beacon-ship/SKILL.md",
|
||||
"project-management/.beacon/doctor.toml.example",
|
||||
"project-management/ADRs/ADR-000-template.md",
|
||||
"project-management/Roadmap/archive/.gitkeep",
|
||||
"project-management/Roadmap/epics/.gitkeep",
|
||||
"project-management/Roadmap/epics/EPIC-TEMPLATE.md",
|
||||
"project-management/Roadmap/epics/archive/.gitkeep",
|
||||
"project-management/Work/README.md",
|
||||
"project-management/Work/analysis/.gitkeep",
|
||||
"project-management/Work/branches/.gitkeep",
|
||||
"project-management/Work/planning/.gitkeep",
|
||||
"project-management/Work/sessions/.gitkeep"
|
||||
],
|
||||
"git_flow": "trunk",
|
||||
"hooks_enabled": true,
|
||||
"installed_at": "2026-06-28T14:01:42+00:00",
|
||||
"language": "python",
|
||||
"roadmap_done_limit": 3,
|
||||
"speckit_requested": true,
|
||||
"user_seeded_files": [
|
||||
"beacon.md",
|
||||
"project-management/ADRs/README.md",
|
||||
"project-management/Background/00-problem-statement.md",
|
||||
"project-management/Background/01-final-architecture-document.md",
|
||||
"project-management/Roadmap/README.md"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
# ADR-###: [Short noun-phrase title]
|
||||
|
||||
## Status
|
||||
|
||||
> Proposed | Accepted | Deprecated | Superseded by [ADR-###](ADR-###-name.md)
|
||||
|
||||
_Date:_ YYYY-MM-DD
|
||||
_Deciders:_ [names or roles]
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
<!--
|
||||
What situation, constraint, or requirement forced this decision?
|
||||
What is the problem space? What are the competing concerns?
|
||||
Keep this factual — describe the situation, not the solution.
|
||||
-->
|
||||
|
||||
## Decision
|
||||
|
||||
<!--
|
||||
What did we decide to do, and what is the core rationale?
|
||||
Be specific. Name the tool, pattern, or approach chosen.
|
||||
Explain the "why" — what properties of the chosen option mattered most?
|
||||
-->
|
||||
|
||||
## Consequences
|
||||
|
||||
<!--
|
||||
What becomes easier as a result of this decision?
|
||||
What becomes harder or more constrained?
|
||||
What new problems does this decision introduce?
|
||||
What debt, if any, does this incur?
|
||||
-->
|
||||
|
||||
## Considered Alternatives
|
||||
|
||||
<!--
|
||||
What else did we evaluate? For each alternative:
|
||||
- What is it?
|
||||
- Why didn't we choose it?
|
||||
Be honest about tradeoffs — a decision record that only shows the winner teaches nothing.
|
||||
-->
|
||||
|
||||
### Alternative A: [Name]
|
||||
|
||||
**Why rejected:** …
|
||||
|
||||
### Alternative B: [Name]
|
||||
|
||||
**Why rejected:** …
|
||||
|
||||
---
|
||||
|
||||
## Links
|
||||
|
||||
- Related spec: `specs/[feature-name]/`
|
||||
- Related ADRs: [ADR-###](ADR-###-name.md)
|
||||
- External reference: [link](url)
|
||||
@@ -0,0 +1,69 @@
|
||||
# ADR-001: Use stdlib logging instead of colored console output libraries
|
||||
|
||||
## Status
|
||||
|
||||
> Accepted
|
||||
|
||||
_Date:_ 2026-06-28
|
||||
_Deciders:_ darth-veitcher
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
The initial implementation of comfydv used `colorama`, `rich`, and `termcolor` for
|
||||
colored console output in `format_string.py` and `random_choice.py`. These libraries
|
||||
were listed as runtime dependencies, meaning every ComfyUI installation that uses
|
||||
comfydv would install them even though their sole purpose was developer debugging.
|
||||
|
||||
The visible symptom (GitHub issue #4) was 8 lines of diagnostic output printed to
|
||||
the ComfyUI console on every keystroke in a FormatString node, because:
|
||||
1. `logger.setLevel(logging.DEBUG)` was called unconditionally in the module, and
|
||||
2. Hot-path methods (`IS_CHANGED`, `update_widget`) logged at INFO level.
|
||||
|
||||
ComfyUI is a plugin host. Any output comfydv writes to stdout is mixed with ComfyUI's
|
||||
own output, making the console unusable during normal workflow editing.
|
||||
|
||||
## Decision
|
||||
|
||||
Remove `colorama`, `rich`, and `termcolor` as runtime dependencies. Replace all
|
||||
`print(colored(...))`, `pprint(...)`, and `from rich import print` usage with calls
|
||||
to `logging.getLogger(__name__)`. Debug output that was valuable for development is
|
||||
preserved at `DEBUG` level so it reappears when the host opts in via
|
||||
`logging.basicConfig(level=logging.DEBUG)` or equivalent.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Easier:**
|
||||
- The ComfyUI console is silent during normal workflow use.
|
||||
- comfydv's runtime dependency footprint shrinks from 4 packages to 1 (`jinja2`).
|
||||
- Debug traces are still available to developers who configure the `comfydv` logger.
|
||||
|
||||
**Harder / constrained:**
|
||||
- Colored output is no longer available even in debug mode. Plain log records are
|
||||
less visually distinct than colored terminal output.
|
||||
|
||||
**Debt introduced:**
|
||||
- None. The removed libraries had no purpose beyond developer diagnostics.
|
||||
|
||||
## Considered Alternatives
|
||||
|
||||
### Alternative A: Keep rich/colorama/termcolor but gate them behind a debug flag
|
||||
|
||||
**Why rejected:** Any debug flag adds configuration surface. The right solution for
|
||||
a library is to use the logging system — callers configure it. Adding a bespoke
|
||||
`COMFYDV_DEBUG=1` env var would be an undiscoverable one-off.
|
||||
|
||||
### Alternative B: Move colored output to dev-only dependency group
|
||||
|
||||
**Why rejected:** The `print()` calls write directly to stdout regardless of how the
|
||||
package is installed. Moving the packages to `[dependency-groups].dev` would remove
|
||||
them from the sdist/wheel but the import in the module-level code would then fail at
|
||||
runtime. It does not solve the silent-during-normal-use requirement.
|
||||
|
||||
---
|
||||
|
||||
## Links
|
||||
|
||||
- Related spec: `specs/001-standardise-logging/`
|
||||
- Related ADRs: [ADR-002](ADR-002-nullhandler-pattern-for-library-loggers.md)
|
||||
@@ -0,0 +1,75 @@
|
||||
# ADR-002: NullHandler pattern for the comfydv package root logger
|
||||
|
||||
## Status
|
||||
|
||||
> Accepted
|
||||
|
||||
_Date:_ 2026-06-28
|
||||
_Deciders:_ darth-veitcher
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
comfydv is a ComfyUI plugin — a library loaded into a host application. Python's
|
||||
logging documentation explicitly states that libraries should not configure logging
|
||||
(no `basicConfig`, no `setLevel`, no `StreamHandler` added to the root logger),
|
||||
because doing so hijacks the host application's logging configuration.
|
||||
|
||||
The initial code violated this in two ways:
|
||||
1. `logger.setLevel(logging.DEBUG)` was called unconditionally in `format_string.py`,
|
||||
forcing all debug records through even when the host had not opted in.
|
||||
2. No `NullHandler` was registered, so if the host application had not configured
|
||||
logging, Python's "last resort" handler would write WARNING+ records to stderr.
|
||||
|
||||
## Decision
|
||||
|
||||
Follow PEP 282 and the Python logging HOWTO's recommendation for library code:
|
||||
|
||||
1. Add `logging.getLogger(__name__).addHandler(logging.NullHandler())` to
|
||||
`src/comfydv/__init__.py`. This suppresses all output when the host has not
|
||||
configured logging, preventing spurious stderr noise.
|
||||
2. Remove every `logger.setLevel(...)` call from module-level code. Level is the
|
||||
host's responsibility.
|
||||
3. Use `logging.getLogger(__name__)` in every module (hierarchy rooted at `comfydv`).
|
||||
|
||||
Hosts that want debug output from comfydv add one line:
|
||||
```python
|
||||
logging.getLogger("comfydv").setLevel(logging.DEBUG)
|
||||
```
|
||||
|
||||
## Consequences
|
||||
|
||||
**Easier:**
|
||||
- comfydv is a well-behaved library citizen: zero console output by default.
|
||||
- The host controls verbosity without any comfydv-specific API.
|
||||
- All existing log call sites are preserved — turning on debug gives full traces.
|
||||
|
||||
**Harder / constrained:**
|
||||
- Developers who previously relied on automatic stdout output during ComfyUI sessions
|
||||
must now configure the logger explicitly to see debug traces.
|
||||
|
||||
**Debt introduced:**
|
||||
- None.
|
||||
|
||||
## Considered Alternatives
|
||||
|
||||
### Alternative A: Remove all logging, use only print for visible output
|
||||
|
||||
**Why rejected:** print() cannot be suppressed without patching sys.stdout. A
|
||||
logging call at DEBUG costs essentially nothing when the level gate is not open.
|
||||
Removing logging would destroy the debug-mode capability.
|
||||
|
||||
### Alternative B: Write a comfydv-specific debug flag (env var or config file)
|
||||
|
||||
**Why rejected:** Reinvents the logging system. Python's logging hierarchy already
|
||||
provides per-package level control. A bespoke flag adds undiscoverable configuration
|
||||
surface with the same semantics.
|
||||
|
||||
---
|
||||
|
||||
## Links
|
||||
|
||||
- Related spec: `specs/001-standardise-logging/`
|
||||
- Related ADRs: [ADR-001](ADR-001-stdlib-logging-over-console-libraries.md)
|
||||
- External reference: https://docs.python.org/3/howto/logging.html#configuring-logging-for-a-library
|
||||
@@ -0,0 +1,33 @@
|
||||
# Architectural Decision Records
|
||||
|
||||
Decisions that are **hard to reverse**, involve a **real tradeoff**, or would **confuse a future contributor** without context belong here.
|
||||
|
||||
## Format
|
||||
|
||||
Use [MADR](https://adr.github.io/madr/) — see `ADR-000-template.md`.
|
||||
|
||||
## Numbering
|
||||
|
||||
`ADR-NNN-short-noun-phrase.md` — sequential, never reuse a number.
|
||||
Superseded ADRs keep their file; update their status to `Superseded by ADR-###`.
|
||||
|
||||
## When to Write an ADR
|
||||
|
||||
| Situation | Write ADR? |
|
||||
|-----------|-----------|
|
||||
| Choosing a database or storage layer | ✅ Yes |
|
||||
| Choosing between two library approaches | ✅ Yes |
|
||||
| Defining an API contract or schema | ✅ Yes |
|
||||
| Adding a dependency | ✅ If non-trivial |
|
||||
| Fixing a bug with one obvious fix | ❌ No |
|
||||
| Renaming a variable | ❌ No |
|
||||
| Adding a feature that follows existing patterns | ❌ No — spec is enough |
|
||||
|
||||
## Index
|
||||
|
||||
<!-- Add a row per ADR as you create it: copy ADR-000-template.md to
|
||||
ADR-NNN-short-noun-phrase.md, fill it in, then link it here. -->
|
||||
|
||||
| ADR | Title | Status | Date |
|
||||
|-----|-------|--------|------|
|
||||
| [ADR-000](ADR-000-template.md) | Template | — | — |
|
||||
@@ -0,0 +1,53 @@
|
||||
# Problem Statement
|
||||
|
||||
<!--
|
||||
BEACON SEED phase deliverable. Update this as requirements evolve — it is a living document.
|
||||
Record the date and reason whenever you change it.
|
||||
-->
|
||||
|
||||
## Core Problem
|
||||
|
||||
ComfyUI ships no general-purpose string formatting, random-selection, or workflow-interruption primitives, forcing workflow creators to work around missing utilities with brittle workarounds or custom Python script nodes that can't be shared easily.
|
||||
|
||||
## Target User
|
||||
|
||||
**Who:** ComfyUI power users building automated or parameterised image/video generation pipelines
|
||||
**Context:** Mid-workflow, when they need to compose prompts from variables, randomly sample a style or subject, or halt a misbehaving pipeline without killing the ComfyUI process
|
||||
**Current pain:** They either hard-code values in prompt strings, rely on community nodes with inconsistent APIs, or paste raw Python into script nodes — none of which is reusable, version-controlled, or easy to chain
|
||||
|
||||
## Success Criteria
|
||||
|
||||
How will we know this is working? Make these measurable.
|
||||
|
||||
- [x] `FormatString` node accepts an arbitrary template (Python f-string or Jinja2) and dynamically exposes detected variables as typed inputs, with pass-through outputs for downstream chaining
|
||||
- [x] `RandomChoice` node accepts an arbitrary number of typed inputs and returns one at random with reproducible seed control
|
||||
- [x] `CircuitBreaker` node raises `InterruptProcessingException` to halt a run without crashing ComfyUI when `status=False`
|
||||
- [ ] All nodes install via the standard ComfyUI custom-node mechanism (drop into `custom_nodes/`, restart server)
|
||||
- [ ] Node tests pass outside ComfyUI (no ComfyUI import required in the test suite)
|
||||
- [ ] `ruff`, `ty`, and `beacon doctor` are all clean before any PR merges
|
||||
|
||||
## Non-Goals
|
||||
|
||||
Explicitly naming what we are **not** solving prevents scope creep.
|
||||
|
||||
1. NOT a general-purpose Python scripting node — template execution is sandboxed; arbitrary code is not supported
|
||||
2. NOT a ComfyUI Manager listing or PyPI release at this stage — distribution is manual install or git clone
|
||||
3. NOT cross-platform GPU tooling — nodes are CPU-only utilities; the ComfyUI host handles GPU concerns
|
||||
4. NOT a replacement for ComfyUI's native primitive nodes — these nodes fill the gaps, not the core
|
||||
|
||||
## Why This Matters
|
||||
|
||||
ComfyUI workflows are increasingly used for production pipelines — batch prompt generation, style randomisation, asset naming — but the node library has always under-served text manipulation. A small, well-tested utility pack that installs in seconds and requires zero Python knowledge from the end user removes a class of frustrating workarounds and lets workflow creators focus on composition, not plumbing.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Must load inside ComfyUI's existing import system — top-level `__init__.py` exposes `NODE_CLASS_MAPPINGS` and `NODE_DISPLAY_NAME_MAPPINGS`
|
||||
- Jinja2 rendering **must** use `SandboxedEnvironment` to prevent arbitrary code execution from user-supplied templates
|
||||
- Tests must not require a live ComfyUI process — `comfy.*` imports are guarded and mocked at test time
|
||||
- Python ≥ 3.10; managed via `uv` / `.python-version`
|
||||
|
||||
---
|
||||
|
||||
_Created:_ 2026-06-28
|
||||
_Last updated:_ 2026-06-28 — initial SEED artefact
|
||||
_Status:_ Living document — update when requirements evolve
|
||||
@@ -0,0 +1,153 @@
|
||||
# Architecture Document
|
||||
|
||||
<!--
|
||||
BEACON DESIGN phase deliverable. Update when a new ADR affects the architecture.
|
||||
Always link to the relevant ADR rather than duplicating rationale here.
|
||||
Use /design:diagram <component> to generate or regenerate any diagram below.
|
||||
-->
|
||||
|
||||
## Overview
|
||||
|
||||
`comfydv` is a ComfyUI custom-node package. It is loaded by ComfyUI at startup from the `custom_nodes/` directory. The package exposes three nodes via `NODE_CLASS_MAPPINGS` and a JavaScript web directory via `WEB_DIRECTORY`. All node logic is pure Python; the JS layer handles dynamic UI updates (adding/removing input sockets when a template changes).
|
||||
|
||||
---
|
||||
|
||||
## C4 Context: System in its Environment
|
||||
|
||||
```mermaid
|
||||
C4Context
|
||||
title System Context — comfydv
|
||||
Person(user, "Workflow Creator", "ComfyUI power user building parameterised pipelines")
|
||||
System(comfydv, "comfydv", "Custom node pack: FormatString, RandomChoice, CircuitBreaker")
|
||||
System_Ext(comfyui, "ComfyUI", "Host application — loads custom nodes, executes workflows")
|
||||
System_Ext(jinja2, "Jinja2 (SandboxedEnvironment)", "Template rendering engine")
|
||||
|
||||
Rel(user, comfyui, "Designs and runs workflows", "Browser UI")
|
||||
Rel(comfyui, comfydv, "Loads nodes at startup; executes node functions during queue runs")
|
||||
Rel(comfydv, jinja2, "Renders sandboxed templates")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## C4 Container: Deployable Units
|
||||
|
||||
```mermaid
|
||||
C4Container
|
||||
title Container Diagram — comfydv
|
||||
Person(user, "Workflow Creator", "")
|
||||
|
||||
System_Boundary(comfyui, "ComfyUI Host") {
|
||||
Container(server, "ComfyUI Server", "Python / aiohttp", "Hosts the workflow engine and web API")
|
||||
Container(comfydv, "comfydv package", "Python 3.10+", "FormatString, RandomChoice, CircuitBreaker nodes + JS UI layer")
|
||||
}
|
||||
|
||||
Rel(user, server, "Uses", "Browser / HTTP")
|
||||
Rel(server, comfydv, "Imports at startup; calls node FUNCTION during queue execution")
|
||||
Rel(comfydv, server, "Registers aiohttp routes: /update_format_string_node, /load_format_string_node, /get_format_string_node_config/{id}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## System Components (logical view)
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "comfydv"
|
||||
FS[FormatString]
|
||||
RC[RandomChoice]
|
||||
CB[CircuitBreaker]
|
||||
FS --> JE[(Jinja2 SandboxedEnv)]
|
||||
end
|
||||
subgraph "ComfyUI"
|
||||
Server[PromptServer / aiohttp]
|
||||
NodeReg[NODE_CLASS_MAPPINGS]
|
||||
end
|
||||
FS -->|registers routes on| Server
|
||||
NodeReg -->|maps| FS
|
||||
NodeReg -->|maps| RC
|
||||
NodeReg -->|maps| CB
|
||||
subgraph "src/js"
|
||||
JS[format_string.js / dynamic.js]
|
||||
end
|
||||
Server -->|serves| JS
|
||||
JS -->|POST /update_format_string_node| Server
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Technology Stack
|
||||
|
||||
| Layer | Technology | Rationale | ADR |
|
||||
|-------|-----------|-----------|-----|
|
||||
| Language | Python 3.10+ | ComfyUI minimum | — |
|
||||
| Package manager | uv | Speed, lockfiles | — |
|
||||
| Linter/formatter | Ruff | Single tool, replaces flake8+black+isort | — |
|
||||
| Type checking | ty | Astral native, replaces mypy | — |
|
||||
| Template engine | Jinja2 (SandboxedEnvironment) | Sandboxed evaluation of user templates | — |
|
||||
| Web framework | aiohttp (via ComfyUI's PromptServer) | ComfyUI's built-in; no additional server needed | — |
|
||||
| Testing | pytest + pytest-cov | Standard Python; mocks out ComfyUI imports | — |
|
||||
| Docs | mkdocs-material + mkdocstrings | Generates API docs from docstrings | — |
|
||||
|
||||
---
|
||||
|
||||
## Data Flow: FormatString (primary path)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
actor User
|
||||
participant BrowserJS as Browser JS
|
||||
participant AiohttpRoute as /update_format_string_node
|
||||
participant FormatString
|
||||
participant Jinja2
|
||||
|
||||
User->>BrowserJS: Edits template widget
|
||||
BrowserJS->>AiohttpRoute: POST {nodeId, template_type, template}
|
||||
AiohttpRoute->>FormatString: update_widget(node_id, template_type, template)
|
||||
FormatString-->>AiohttpRoute: updated config (inputs, outputs)
|
||||
AiohttpRoute-->>BrowserJS: JSON config
|
||||
BrowserJS-->>User: Sockets updated on node
|
||||
|
||||
User->>FormatString: Queue prompt (ComfyUI executes format_string())
|
||||
FormatString->>Jinja2: render(template, context) [Jinja2 mode]
|
||||
Jinja2-->>FormatString: rendered string
|
||||
FormatString-->>User: (formatted_string, saved_file_path, var1, var2, ...)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Output Order Contract (immutable)
|
||||
|
||||
FormatString outputs **must** be returned in this order — changing it breaks existing workflow connections silently:
|
||||
|
||||
| Index | Name | Type | Notes |
|
||||
|-------|------|------|-------|
|
||||
| 0 | `formatted_string` | STRING | Primary output |
|
||||
| 1 | `saved_file_path` | STRING | Empty string if `save_path` not set |
|
||||
| 2+ | `<variable_name>` | STRING | One per detected template variable, in order of first appearance |
|
||||
|
||||
---
|
||||
|
||||
## Non-Functional Requirements
|
||||
|
||||
- **Performance:** Template rendering is synchronous and CPU-bound; typical templates render in <1 ms. No caching layer needed at this scale.
|
||||
- **Security:** Jinja2 `SandboxedEnvironment` prevents filesystem/subprocess access from user templates. `additional_context` is the only way to expose utilities.
|
||||
- **Testability:** All node core logic (`_extract_keys`, `format_string`, `update_widget`) must be callable from pytest without a ComfyUI process. ComfyUI-specific imports are guarded by `if "comfy" in sys.modules`.
|
||||
- **Observability:** `logging.getLogger(__name__)` at DEBUG level inside nodes; `rich.print` for user-visible output in the ComfyUI console.
|
||||
|
||||
---
|
||||
|
||||
## Tracer Bullet Decomposition
|
||||
|
||||
| Phase | Bullets | Outcome |
|
||||
|-------|---------|---------|
|
||||
| Bootstrap | 1 | BEACON artefacts populated; quality gates clean |
|
||||
| Test hardening | 2 | Full unit coverage for all three nodes |
|
||||
| Documentation | 3 | mkdocs site reflects current API |
|
||||
| Distribution | 4+ | ComfyUI Manager listing; PyPI release |
|
||||
|
||||
---
|
||||
|
||||
_Created:_ 2026-06-28
|
||||
_Last updated:_ 2026-06-28 — initial DESIGN artefact
|
||||
_Status:_ Living document — update when ADRs change the architecture
|
||||
@@ -0,0 +1,67 @@
|
||||
# Roadmap — Strategy
|
||||
|
||||
**Project:** comfydv
|
||||
**Last reviewed:** 2026-06-28
|
||||
|
||||
---
|
||||
|
||||
> This file is **strategy** (quarter-scope). It is **not** the live status tracker
|
||||
> for active work — that's discovered from git branches and `specs/` by
|
||||
> `beacon bullet list`. It is **not** the per-initiative artifact — those live as
|
||||
> per-file epics under [`epics/`](./epics/).
|
||||
>
|
||||
> What goes here: vision, quarter priorities, sequencing, dependency notes, and
|
||||
> the broader product context the in-flight epics ladder up to.
|
||||
|
||||
---
|
||||
|
||||
## Vision
|
||||
|
||||
`comfydv` is a small, high-quality ComfyUI utility pack that fills the gaps the core node library leaves: composable string formatting, seed-controlled randomisation, and workflow flow-control. Winning looks like: every node is well-tested, installs in one step, produces no surprises in production workflows, and is documented well enough that a non-programmer ComfyUI user can connect it without reading source code.
|
||||
|
||||
---
|
||||
|
||||
## This quarter (Q3 2026)
|
||||
|
||||
- **BEACON bootstrap** — `epics/beacon-bootstrap.md` — BEACON framework wired up; problem statement, constitution, roadmap, and ADR template populated; quality gates clean
|
||||
- **Test hardening** — `epics/test-hardening.md` — full unit coverage for `FormatString._extract_keys`, `format_string`, `update_widget`; circuit breaker and random choice covered; CI green without ComfyUI
|
||||
- **Documentation** — `epics/documentation.md` — mkdocs site rebuilt to reflect current node API, including output-order guarantee and Jinja2 sandbox constraints
|
||||
|
||||
For the live rollup (specs per epic, % tasks complete, last-commit age):
|
||||
|
||||
```
|
||||
beacon epic list --detailed
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Sequencing and dependencies
|
||||
|
||||
- **BEACON bootstrap** is a prerequisite for all other epics (quality gates need to pass before new work merges)
|
||||
- **Test hardening** is independent of documentation and can run in parallel
|
||||
- **Documentation** depends on the final node API (output order, input names) being stable — start after test hardening locks the contracts
|
||||
|
||||
---
|
||||
|
||||
## Out of scope this quarter
|
||||
|
||||
- PyPI / ComfyUI Manager listing — deferred until test coverage is solid and the API is stable
|
||||
- GPU-dependent nodes — out of scope; the utility pack is CPU-only by design
|
||||
- Multi-language template engines (Handlebars, Liquid, etc.) — not planned; Jinja2 covers the use cases
|
||||
- A UI node editor or visual template builder — too complex; out of scope for this stage
|
||||
|
||||
---
|
||||
|
||||
## Where things live
|
||||
|
||||
| Layer | Where | What's in it |
|
||||
|---|---|---|
|
||||
| Strategy | this file | vision, quarter priorities (slow, manual) |
|
||||
| Epic / initiative | `epics/<slug>.md` | scope, ADRs, owned specs (weeks-scope) |
|
||||
| Feature / spec | `specs/<NNN-slug>/` | SpecKit-generated user scenarios, plan, tasks |
|
||||
| Active work | git branches + `specs/<NNN-slug>/tasks.md` + `.beacon/bullets.toml` | discovered live by `beacon bullet list` |
|
||||
| Architectural decisions | `project-management/ADRs/` | epic-level MADRs, linked from the relevant epic |
|
||||
|
||||
---
|
||||
|
||||
*Last reviewed: 2026-06-28 — initial population. `beacon doctor` will warn if it goes stale (>90 days).*
|
||||
@@ -0,0 +1,46 @@
|
||||
# Epic: <Title>
|
||||
|
||||
## Status
|
||||
Planning — started YYYY-MM-DD
|
||||
|
||||
## Why now
|
||||
<Strategic context: why this epic this quarter, not next. One-to-three paragraphs.>
|
||||
|
||||
## Specs
|
||||
_SpecKit specs that contribute to this epic. Linked automatically when
|
||||
`beacon specify --epic <slug>` is used; otherwise add manually after running
|
||||
`/speckit-specify`._
|
||||
|
||||
<!-- - specs/001-example/ — short description -->
|
||||
|
||||
## ADRs
|
||||
_Cross-cutting decisions this epic required. **Creating or editing an epic is a
|
||||
BEACON DESIGN-phase activity** — architectural choices that span specs
|
||||
("OAuth vs own auth", "which database", "build vs buy", "where this sits on the
|
||||
Wardley Map") belong here, not in any single spec.md. SpecKit's spec format
|
||||
can't capture cross-spec decisions; epic-level ADRs are where they live._
|
||||
|
||||
_Create ADRs as `project-management/ADRs/ADR-NNN-name.md` (MADR format) and list
|
||||
them below._
|
||||
|
||||
<!-- - project-management/ADRs/ADR-005-oauth-provider-choice.md -->
|
||||
<!-- - project-management/ADRs/ADR-006-session-storage.md -->
|
||||
|
||||
## Success criteria
|
||||
- <Outcome 1, measurable, attributable to this epic>
|
||||
- <Outcome 2>
|
||||
- <Outcome 3>
|
||||
|
||||
## Non-goals
|
||||
<What this epic is explicitly NOT solving. Prevents scope creep when mid-flight
|
||||
revelations tempt expansion.>
|
||||
|
||||
## Notes
|
||||
<Cross-spec design notes, open questions, dependency tracking that doesn't
|
||||
warrant a full ADR.>
|
||||
|
||||
---
|
||||
|
||||
*This template lives at `Roadmap/epics/EPIC-TEMPLATE.md`. `beacon epic new <slug>
|
||||
--title "<Title>"` creates a new instance from it. Edit the body freely; the
|
||||
structure of the headers is the contract `beacon` reads.*
|
||||
@@ -0,0 +1,33 @@
|
||||
# Epic: BEACON Bootstrap
|
||||
|
||||
## Status
|
||||
Done — completed 2026-06-28
|
||||
|
||||
## Why now
|
||||
No structured planning artefacts exist. Quality gates (`ruff`, `ty`, `beacon doctor`) are not yet enforced, and there is no problem statement, constitution, or roadmap to guide future work. This is the prerequisite for every other epic — nothing else should merge until the framework is clean.
|
||||
|
||||
## Dependencies
|
||||
_None — this is the first epic._
|
||||
|
||||
## Specs
|
||||
_None — bootstrap is a chore; no SpecKit spec needed._
|
||||
|
||||
## ADRs
|
||||
_None required at this stage; architectural decisions will be captured as features are specified._
|
||||
|
||||
## Success criteria
|
||||
- [x] `project-management/Background/00-problem-statement.md` filled in (no placeholder tokens)
|
||||
- [x] `.specify/memory/constitution.md` authored with project-specific principles
|
||||
- [x] `project-management/Roadmap/README.md` populated with vision and Q3 priorities
|
||||
- [x] `project-management/Background/01-final-architecture-document.md` reflects actual system
|
||||
- [ ] `beacon doctor --strict` exits 0 (no warnings, no failures)
|
||||
- [ ] `README.md` has What-is-this, Install, and Quickstart sections
|
||||
- [ ] `CHANGELOG.md` exists in Keep a Changelog format
|
||||
- [ ] `pyproject.toml` has `repository` and `documentation` under `[project.urls]`
|
||||
|
||||
## Non-goals
|
||||
- Not a feature epic — no user-facing code ships here
|
||||
- Not an ADR audit — ADRs will be added as design decisions arise in future epics
|
||||
|
||||
## Notes
|
||||
Bootstrap bullet registered on `feat/beacon`. All artefact writes land in a single PR to `develop`.
|
||||
@@ -0,0 +1,36 @@
|
||||
# Epic: Logging Modernisation
|
||||
|
||||
## Status
|
||||
Active — started 2026-06-28
|
||||
|
||||
## Why now
|
||||
Issue #4 from a community user reports 8 lines of console output per keystroke in the template field. The root cause is a hardcoded `logger.setLevel(logging.DEBUG)` at module level combined with diagnostic `print()` blocks that were never removed after the FormatString output-order fix. Left unaddressed this makes the nodes unusable in production ComfyUI sessions. This is also a correctness issue: a library must never configure its own log level or add handlers — that violates the Python logging best-practice contract.
|
||||
|
||||
## Dependencies
|
||||
_None._
|
||||
|
||||
## Specs
|
||||
<!-- populated by beacon link-spec -->
|
||||
|
||||
- specs/001-standardise-logging/
|
||||
## ADRs
|
||||
|
||||
- [ADR-001](../../ADRs/ADR-001-stdlib-logging-over-console-libraries.md) — stdlib logging over colored console libraries
|
||||
- [ADR-002](../../ADRs/ADR-002-nullhandler-pattern-for-library-loggers.md) — NullHandler pattern for library loggers
|
||||
|
||||
## Success criteria
|
||||
- [x] `logger.setLevel(logging.DEBUG)` removed from all modules; log level controlled entirely by the host (ComfyUI or the user's logging config)
|
||||
- [x] `logging.NullHandler()` added to the package root logger in `__init__.py`
|
||||
- [x] All diagnostic `print()` calls converted to `logger.debug()` / `logger.info()` / `logger.error()` as appropriate
|
||||
- [x] `colorama`, `termcolor`, and `rich` removed from runtime dependencies in `pyproject.toml` (they are only used for the now-deleted console-print logging)
|
||||
- [x] A normal ComfyUI run produces zero output from this package unless the host enables DEBUG
|
||||
- [x] Errors (template render failures, file-save failures, interrupt triggers) still surface at `ERROR` / `INFO` level
|
||||
- [x] All existing tests pass; no new test failures introduced
|
||||
|
||||
## Non-goals
|
||||
- Not adding a user-facing log-level toggle inside ComfyUI's UI
|
||||
- Not changing node behaviour, output structure, or ComfyUI API contracts
|
||||
- Not adding structured/JSON logging
|
||||
|
||||
## Notes
|
||||
The `IS_CHANGED` and `update_widget` methods fire on every keystroke via the JS → aiohttp route. Any log call at INFO or above in these hot paths will be visible to the user. All calls in these hot paths must be DEBUG or removed.
|
||||
@@ -0,0 +1,60 @@
|
||||
# Work/ — Transient Workspace
|
||||
|
||||
This directory is a **scratchpad**. Everything here is temporary by design.
|
||||
|
||||
## Lifecycle
|
||||
|
||||
```
|
||||
During work → Create freely in Work/
|
||||
After commit → Promote important insights to ADRs; delete the rest
|
||||
After merge → Delete sessions/*; prune planning/*; delete analysis/* (if ADR written)
|
||||
```
|
||||
|
||||
**Rule:** If it matters long-term, it belongs in `ADRs/`, `Background/`, or the codebase. If it lives only in `Work/`, it will be lost.
|
||||
|
||||
## Subdirectories
|
||||
|
||||
| Directory | Contents | When to delete |
|
||||
|-----------|----------|----------------|
|
||||
| `sessions/` | Session summaries, daily notes | After merge to develop |
|
||||
| `planning/` | Feature planning docs, future-features.md | After the feature ships |
|
||||
| `analysis/` | Architecture analysis, spike results | After promoted to ADR, or if stale |
|
||||
|
||||
## Cleanup command
|
||||
|
||||
Run after a release PR merges:
|
||||
|
||||
```bash
|
||||
cd project-management/Work
|
||||
rm -rf sessions/*
|
||||
rm -rf planning/* # keep only active WIP
|
||||
rm -rf analysis/* # only if ADRs are written
|
||||
```
|
||||
|
||||
The `post-merge` git hook will remind you if session files are older than 2 weeks.
|
||||
|
||||
## Session file naming
|
||||
|
||||
`sessions/YYYY-MM-DD-[bullet-or-topic].md`
|
||||
|
||||
Example content:
|
||||
```markdown
|
||||
# Session: 2026-01-15 — Bullet #3: Azure DevOps integration
|
||||
|
||||
## Goal
|
||||
Wire up MCP azure-devops server to create work items from Claude Code
|
||||
|
||||
## What I did
|
||||
- Installed @tiberriver256/mcp-server-azure-devops
|
||||
- Configured in .claude/settings.json
|
||||
- Tested with /azure:devops-task create task: test item
|
||||
|
||||
## Decisions made
|
||||
- Using PAT not service principal for now — ADR-002 written
|
||||
|
||||
## Blockers
|
||||
- None
|
||||
|
||||
## Tomorrow
|
||||
- Bullet #4: Add Fabric query support
|
||||
```
|
||||
+4
-3
@@ -7,12 +7,13 @@ authors = [
|
||||
{ name = "darth-veitcher", email = "1722315+darth-veitcher@users.noreply.github.com" }
|
||||
]
|
||||
dependencies = [
|
||||
"colorama>=0.4.6",
|
||||
"jinja2>=3.1.6",
|
||||
"rich>=14.2.0",
|
||||
"termcolor>=2.5.0",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Repository = "https://github.com/darth-veitcher/comfydv"
|
||||
Documentation = "https://darth-veitcher.github.io/comfydv/stable/"
|
||||
|
||||
[project.scripts]
|
||||
comfydv = "comfydv:main"
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
epic = "logging-modernisation"
|
||||
@@ -0,0 +1,34 @@
|
||||
# Specification Quality Checklist: Standardise Node Logging
|
||||
|
||||
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
||||
**Created**: 2026-06-28
|
||||
**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 `/beacon:plan`.
|
||||
@@ -0,0 +1,54 @@
|
||||
# Developer Logging Contract — comfydv
|
||||
|
||||
After this change, the package exposes a standard Python logging hierarchy. ComfyUI users see nothing by default; developers who want traces configure the standard library.
|
||||
|
||||
## Logger hierarchy
|
||||
|
||||
```
|
||||
comfydv ← root package logger (NullHandler attached)
|
||||
├── comfydv.format_string ← FormatString node traces
|
||||
├── comfydv.random_choice ← RandomChoice node traces
|
||||
└── comfydv.circuit_breaker ← CircuitBreaker node traces
|
||||
```
|
||||
|
||||
All loggers are named via `logging.getLogger(__name__)` — standard Python convention.
|
||||
|
||||
## Enabling debug output (developer / ComfyUI startup script)
|
||||
|
||||
```python
|
||||
import logging
|
||||
logging.getLogger("comfydv").setLevel(logging.DEBUG)
|
||||
logging.getLogger("comfydv").addHandler(logging.StreamHandler())
|
||||
```
|
||||
|
||||
Or, to target a single node:
|
||||
|
||||
```python
|
||||
logging.getLogger("comfydv.format_string").setLevel(logging.DEBUG)
|
||||
```
|
||||
|
||||
## Log levels by event
|
||||
|
||||
| Event | Level | Example message |
|
||||
|-------|-------|-----------------|
|
||||
| Template variables extracted | DEBUG | `"FormatString: extracted keys %s"` |
|
||||
| IS_CHANGED / update_widget call | DEBUG | `"IS_CHANGED: template_type=%s"` |
|
||||
| Successful format execution | DEBUG | `"FormatString: rendered %d chars"` |
|
||||
| State saved to disk | INFO | `"FormatString: state saved to %s"` |
|
||||
| Template syntax error | ERROR | `"Jinja2 template error: %s"` |
|
||||
| Missing variable (KeyError) | ERROR | `"Missing variable in template: %s"` |
|
||||
| File save failure | ERROR | `"Failed to save state to %s: %s"` |
|
||||
| RandomChoice selection | DEBUG | `"RandomChoice: selected index %d"` |
|
||||
| CircuitBreaker interrupt | DEBUG | `"CircuitBreaker: interrupt triggered"` |
|
||||
| Module loaded outside ComfyUI | WARNING | `"ComfyUI not detected — node will not function outside ComfyUI"` |
|
||||
|
||||
## What a normal ComfyUI run looks like
|
||||
|
||||
With default ComfyUI logging (INFO handler on root logger, no comfydv-specific config):
|
||||
|
||||
- **Zero output** from comfydv during node execution
|
||||
- Errors (template failures, save failures) **do** appear because they are logged at ERROR
|
||||
|
||||
## Stability guarantee
|
||||
|
||||
Logger names (`comfydv`, `comfydv.format_string`, etc.) are stable across patch releases. The set of events logged at each level may expand (new DEBUG lines) but events currently at WARNING/ERROR will not be downgraded without a minor-version bump.
|
||||
@@ -0,0 +1,21 @@
|
||||
Feature: US1 — Silent Normal Operation
|
||||
|
||||
Scenario: No output during FormatString execution with valid template
|
||||
Given the comfydv package is imported with no logging configuration
|
||||
When FormatString.format_string is called with a valid simple template
|
||||
Then no lines are written to stdout or stderr by the comfydv package
|
||||
|
||||
Scenario: No output when IS_CHANGED is called
|
||||
Given the comfydv package is imported with no logging configuration
|
||||
When FormatString.IS_CHANGED is called with a template string
|
||||
Then no lines are written to stdout or stderr by the comfydv package
|
||||
|
||||
Scenario: No output when update_widget is called
|
||||
Given the comfydv package is imported with no logging configuration
|
||||
When FormatString.update_widget is called with a template string
|
||||
Then no lines are written to stdout or stderr by the comfydv package
|
||||
|
||||
Scenario: No output during RandomChoice execution
|
||||
Given the comfydv package is imported with no logging configuration
|
||||
When RandomChoice.random_choice is called with valid inputs
|
||||
Then no lines are written to stdout or stderr by the comfydv package
|
||||
@@ -0,0 +1,21 @@
|
||||
Feature: US2 — Errors Still Surface
|
||||
|
||||
Scenario: Jinja2 template syntax error produces an ERROR log record
|
||||
Given the comfydv package is imported with a DEBUG-level log handler attached
|
||||
When FormatString.format_string is called with an invalid Jinja2 template
|
||||
Then at least one log record at ERROR level is emitted by the comfydv logger
|
||||
|
||||
Scenario: Missing variable in Simple template produces an ERROR log record
|
||||
Given the comfydv package is imported with a DEBUG-level log handler attached
|
||||
When FormatString.format_string is called with a Simple template referencing an undefined variable
|
||||
Then at least one log record at ERROR level is emitted by the comfydv logger
|
||||
|
||||
Scenario: File save failure produces an ERROR log record
|
||||
Given the comfydv package is imported with a DEBUG-level log handler attached
|
||||
When FormatString.format_string is called with an unwritable save_path
|
||||
Then at least one log record at ERROR level is emitted by the comfydv logger
|
||||
|
||||
Scenario: CircuitBreaker with status False halts the queue run and emits a log record
|
||||
Given the comfydv package is imported with a DEBUG-level log handler attached
|
||||
When CircuitBreaker.doit is called with status set to False
|
||||
Then the queue run halts and a message is visible in the log output
|
||||
@@ -0,0 +1,12 @@
|
||||
Feature: US3 — Developer Debug Mode
|
||||
|
||||
Scenario: DEBUG records appear when comfydv logger is configured at DEBUG
|
||||
Given the comfydv logger is configured with a handler at DEBUG level
|
||||
When FormatString.format_string is called with a valid template
|
||||
Then at least one DEBUG log record is emitted by the comfydv logger
|
||||
|
||||
Scenario: No records appear when no logging is configured (NullHandler default)
|
||||
Given the comfydv package is imported with no logging configuration
|
||||
When FormatString.format_string is called with a valid template
|
||||
Then the comfydv logger has exactly one handler and it is a NullHandler
|
||||
And zero log records reach any external handler
|
||||
@@ -0,0 +1,76 @@
|
||||
# Implementation Plan: Standardise Node Logging
|
||||
|
||||
**Branch**: `001-standardise-logging` | **Date**: 2026-06-28 | **Spec**: [spec.md](spec.md)
|
||||
|
||||
**Input**: Feature specification from `specs/001-standardise-logging/spec.md`
|
||||
|
||||
## Summary
|
||||
|
||||
Remove all hardcoded log-level configuration and diagnostic `print()` calls from the three ComfyUI node modules (`format_string.py`, `random_choice.py`, `circuit_breaker.py`), replace them with correctly-levelled `logger.*` calls, add a `NullHandler` to the package root, and drop the three runtime dependencies (`colorama`, `termcolor`, `rich`) that exist solely to colour those now-deleted prints. The result is a well-behaved Python library that emits nothing by default and is fully controllable by the host logging configuration.
|
||||
|
||||
## Technical Context
|
||||
|
||||
**Language/Version**: Python 3.10+ (constrained by ComfyUI minimum; managed via `uv`)
|
||||
|
||||
**Primary Dependencies**:
|
||||
- `jinja2` — template rendering (retained; unaffected by this change)
|
||||
- `aiohttp` — ComfyUI web routes (retained; unaffected)
|
||||
- `colorama`, `termcolor`, `rich` — **removed** (used only for coloured print() logging)
|
||||
- Standard library: `logging` (already imported in `format_string.py`)
|
||||
|
||||
**Storage**: N/A
|
||||
|
||||
**Testing**: `pytest` via `uv run pytest`; `caplog` fixture for log-record assertions
|
||||
|
||||
**Target Platform**: ComfyUI custom-node environment (Python process); also importable in plain pytest without ComfyUI
|
||||
|
||||
**Project Type**: Python library / ComfyUI plugin
|
||||
|
||||
**Performance Goals**: `IS_CHANGED` and `update_widget` execute on every keystroke — zero stdout/stderr I/O is the target in the hot path
|
||||
|
||||
**Constraints**: Must not change any ComfyUI node API (INPUT_TYPES, RETURN_TYPES, FUNCTION, CATEGORY, NODE_CLASS_MAPPINGS). Must not break existing tests.
|
||||
|
||||
**Scale/Scope**: Three source files + `__init__.py` + `pyproject.toml`; ~30 call-sites changed.
|
||||
|
||||
## Constitution Check
|
||||
|
||||
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
||||
|
||||
| Principle | Check | Status |
|
||||
|-----------|-------|--------|
|
||||
| **ComfyUI Contract First** | No NODE_CLASS_MAPPINGS, RETURN_TYPES, INPUT_TYPES, or FUNCTION names change | ✅ PASS |
|
||||
| **Sandbox All User-Supplied Code** | No change to Jinja2 SandboxedEnvironment usage | ✅ PASS |
|
||||
| **Test-First (NON-NEGOTIABLE)** | New log-assertion tests written before implementation; existing tests confirmed passing | ✅ PASS — enforced in tasks |
|
||||
| **Graceful Degradation Outside ComfyUI** | `comfy.*` guards unchanged; logging works without ComfyUI | ✅ PASS |
|
||||
| **Simplicity — Function Before Class** | Removing code, not adding abstractions | ✅ PASS |
|
||||
| **Fixed Output Positions** | No change to RETURN_TYPES/RETURN_NAMES structure | ✅ PASS |
|
||||
|
||||
No violations. Complexity Tracking section is omitted.
|
||||
|
||||
## Project Structure
|
||||
|
||||
### Documentation (this feature)
|
||||
|
||||
```text
|
||||
specs/001-standardise-logging/
|
||||
├── plan.md ← this file
|
||||
├── research.md ← Phase 0 output
|
||||
├── contracts/
|
||||
│ └── logging-contract.md ← Phase 1 output (developer logging API)
|
||||
└── tasks.md ← Phase 2 output (/beacon:tasks)
|
||||
```
|
||||
|
||||
### Source Code (affected files only)
|
||||
|
||||
```text
|
||||
src/comfydv/
|
||||
├── __init__.py ← add NullHandler
|
||||
├── format_string.py ← remove setLevel; remove print() dump block; remove `from rich import print`; downgrade hot-path INFO→DEBUG
|
||||
├── random_choice.py ← replace print()+colorama/termcolor with logger.*; remove imports
|
||||
└── circuit_breaker.py ← add logger; replace bare print() with logger.debug()
|
||||
|
||||
tests/
|
||||
└── test_logging.py ← new: NullHandler, silence, error visibility, debug opt-in
|
||||
|
||||
pyproject.toml ← remove colorama, termcolor, rich from [project.dependencies]
|
||||
```
|
||||
@@ -0,0 +1,73 @@
|
||||
# Research: Standardise Node Logging
|
||||
|
||||
## Python Library Logging — Best Practices
|
||||
|
||||
**Decision**: Add `logging.NullHandler()` to the package root logger in `__init__.py`; never set level or add handlers inside the library.
|
||||
|
||||
**Rationale**: PEP 282 and the Python logging HOWTO state explicitly that libraries should add only a `NullHandler` to their top-level logger. This means:
|
||||
- A host application with no logging configuration sees nothing (NullHandler swallows all records)
|
||||
- A host that configures a handler + level gets exactly what it asks for
|
||||
- The library never fights the host for control of output
|
||||
|
||||
**Alternatives considered**:
|
||||
- Leaving `logger.setLevel(logging.DEBUG)` — rejected; this is the root cause of issue #4 and violates the library contract
|
||||
- Adding a `StreamHandler` with a flag to disable it — rejected; unnecessary complexity, still breaks the host's control
|
||||
|
||||
---
|
||||
|
||||
## Hot-Path Log Level
|
||||
|
||||
**Decision**: All log calls inside `IS_CHANGED` and `update_widget` (and their aiohttp route handler) are capped at `DEBUG`.
|
||||
|
||||
**Rationale**: Both methods execute synchronously on every keystroke in the ComfyUI UI. Any `INFO` or higher call in these paths goes to whatever handlers the host has configured at INFO+. In a standard ComfyUI session that means console output on every keypress — exactly the reported bug. `DEBUG` is the correct level for trace/diagnostic information a developer explicitly opts into.
|
||||
|
||||
**Alternatives considered**:
|
||||
- Removing all logging from `IS_CHANGED` entirely — acceptable but leaves no debug path; DEBUG is preferable
|
||||
- Adding a module-level flag to suppress — rejected; that's re-inventing the logging framework
|
||||
|
||||
---
|
||||
|
||||
## `print()` in Library Code
|
||||
|
||||
**Decision**: All `print()` calls used for diagnostic output are replaced with `logger.*` at the appropriate level. `from rich import print` override is removed.
|
||||
|
||||
**Rationale**: `print()` writes unconditionally to stdout and cannot be suppressed by the host's logging configuration. Shadowing the built-in `print` via `from rich import print` is additionally dangerous because it silently changes behaviour for every `print()` call in the module, making it harder to audit what's going to stdout.
|
||||
|
||||
Correct mapping:
|
||||
| Original call | Replacement |
|
||||
|---|---|
|
||||
| `print(f"[FormatString Node {id}] Output: ...")` | `logger.debug(...)` |
|
||||
| `print(colored("\nRandom Choice", ...))` | `logger.debug("RandomChoice: executing")` |
|
||||
| `print(colored("Got these inputs:", ...))` | `logger.debug("RandomChoice inputs: %s", ...)` |
|
||||
| `print(colored(f"Chose: {choice}", ...))` | `logger.debug("RandomChoice selected: %s", choice)` |
|
||||
| `print("Circuit Breaker triggered")` | `logger.debug("CircuitBreaker: interrupt triggered")` |
|
||||
| `print(f"Error loading node state: {e}")` | `logger.error("Failed to load node state from %s: %s", file_path, e)` |
|
||||
|
||||
---
|
||||
|
||||
## Removing `colorama`, `termcolor`, `rich`
|
||||
|
||||
**Decision**: All three packages are removed from `[project.dependencies]` in `pyproject.toml`.
|
||||
|
||||
**Rationale**: After removing the `print()`-based logging, grep confirms none of these packages are imported anywhere else in the package. They are dead dependencies that add installation weight and version-pin surface area for no benefit.
|
||||
|
||||
```
|
||||
colorama — used only in random_choice.py: just_fix_windows_console()
|
||||
termcolor — used only in random_choice.py: colored()
|
||||
rich — used in format_string.py: from rich import print
|
||||
used in random_choice.py: from rich.pretty import pprint
|
||||
```
|
||||
|
||||
All uses are exclusively in the logging/print blocks being removed.
|
||||
|
||||
**Alternatives considered**:
|
||||
- Keeping `rich` as a dev-only dependency — rejected; no remaining use case even in dev
|
||||
- Keeping `colorama` for Windows console compatibility — rejected; the underlying `print()` calls that needed it are gone
|
||||
|
||||
---
|
||||
|
||||
## `% formatting` vs f-strings in log calls
|
||||
|
||||
**Decision**: Use `%`-style formatting for log calls (`logger.debug("value: %s", x)`), not f-strings.
|
||||
|
||||
**Rationale**: The logging framework only interpolates the message string if a handler actually processes the record. With f-strings, the string is always constructed even if the log record is discarded (e.g. by NullHandler). For hot-path calls (every keystroke), this matters. `%`-style is the convention recommended in the Python logging docs.
|
||||
@@ -0,0 +1,96 @@
|
||||
# Feature Specification: Standardise Node Logging
|
||||
|
||||
**Feature Branch**: `001-standardise-logging`
|
||||
|
||||
**Created**: 2026-06-28
|
||||
|
||||
**Status**: Draft
|
||||
|
||||
**Input**: Replace ad-hoc print/logging with standard Python library logging across all nodes — remove hardcoded DEBUG level, convert coloured print() calls in RandomChoice and CircuitBreaker to logger calls, strip the diagnostic print dump from FormatString, add NullHandler to package root, and drop colorama/termcolor/rich from runtime dependencies
|
||||
|
||||
## User Scenarios & Testing *(mandatory)*
|
||||
|
||||
### User Story 1 — Silent Normal Operation (Priority: P1)
|
||||
|
||||
A ComfyUI user who has installed comfydv nodes runs a workflow. They expect the ComfyUI console to be quiet — no diagnostic noise from the custom nodes during normal execution.
|
||||
|
||||
**Why this priority**: This directly fixes the reported issue (#4). Every keystroke in the template field currently emits 8 lines to the console, making the plugin actively annoying to use.
|
||||
|
||||
**Independent Test**: Install the nodes, open a workflow with a FormatString node, type into the template field, queue a prompt. The ComfyUI console should show zero output from comfydv modules.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a ComfyUI session with comfydv loaded, **When** a user types into the FormatString template field, **Then** no lines are written to the ComfyUI console from this package.
|
||||
2. **Given** a running queue prompt that includes a FormatString, RandomChoice, or CircuitBreaker node, **When** the node executes successfully, **Then** no output appears in the ComfyUI console from this package.
|
||||
3. **Given** a ComfyUI session, **When** the server starts and loads the comfydv custom nodes, **Then** no startup output (beyond ComfyUI's own "Loaded custom node" line) is emitted from this package.
|
||||
|
||||
---
|
||||
|
||||
### User Story 2 — Errors Still Surface (Priority: P1)
|
||||
|
||||
A workflow creator has a broken Jinja2 template (syntax error). They expect to see a clear error in the ComfyUI console telling them what went wrong, without having to enable debug logging.
|
||||
|
||||
**Why this priority**: Silent failure on errors would be worse than the current verbosity. Error visibility must be preserved.
|
||||
|
||||
**Independent Test**: Create a FormatString node with an invalid Jinja2 template (e.g. `{{ unclosed`). Queue the prompt. An error message must appear in the ComfyUI console.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a FormatString node with an invalid Jinja2 template, **When** the prompt is queued, **Then** an error message describing the template fault appears in the ComfyUI console.
|
||||
2. **Given** a FormatString node with a `save_path` that cannot be written, **When** the prompt executes, **Then** an error message about the failed save appears in the console.
|
||||
3. **Given** a CircuitBreaker node with `status=False`, **When** the prompt executes, **Then** the queue run halts and a message is visible in the console at INFO level or above.
|
||||
|
||||
---
|
||||
|
||||
### User Story 3 — Developer Debug Mode (Priority: P2)
|
||||
|
||||
A developer debugging a custom workflow wants to see verbose output from comfydv nodes. They configure Python's logging system to enable DEBUG for the `comfydv` logger and get detailed traces without any code changes to the package.
|
||||
|
||||
**Why this priority**: Debug visibility must still be available — just opt-in rather than on by default.
|
||||
|
||||
**Independent Test**: In a Python environment with comfydv imported, configure `logging.getLogger("comfydv").setLevel(logging.DEBUG)` with a stream handler. Invoke `FormatString.format_string(...)`. Detailed trace lines should appear.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a Python environment where `logging.getLogger("comfydv").setLevel(logging.DEBUG)` is set, **When** any node function executes, **Then** detailed trace messages (template content, extracted keys, output tuple) appear in the log output.
|
||||
2. **Given** a Python environment with no logging configuration for `comfydv`, **When** any node function executes, **Then** no output appears (NullHandler absorbs everything).
|
||||
|
||||
---
|
||||
|
||||
### Edge Cases
|
||||
|
||||
- What happens when ComfyUI's root logger has no handlers configured? The NullHandler on the package logger prevents a "No handlers could be found" warning.
|
||||
- What happens when a third-party tool sets the `comfydv` logger level before import? The package must not override it — `setLevel` must not be called on import.
|
||||
- What happens if `colorama`/`termcolor`/`rich` are removed but something else in the ComfyUI environment already provides them? No impact — the nodes simply no longer import them.
|
||||
|
||||
## Requirements *(mandatory)*
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
- **FR-001**: The package root logger (`comfydv`) MUST have a `NullHandler` added in `__init__.py` so that a host with no logging configuration does not see "No handlers" warnings.
|
||||
- **FR-002**: No module in the package MUST call `logger.setLevel(...)` at import time or module level.
|
||||
- **FR-003**: No module in the package MUST add handlers to any logger.
|
||||
- **FR-004**: All calls to `print()` used for diagnostic/trace output MUST be replaced with the appropriate `logging` level call (`debug`, `info`, `warning`, `error`).
|
||||
- **FR-005**: Log calls in code paths that execute on every keystroke (`IS_CHANGED`, `update_widget`, the aiohttp route handler for template updates) MUST be at `DEBUG` level only.
|
||||
- **FR-006**: Template render errors (Jinja2 `TemplateSyntaxError`, Python `KeyError`) MUST remain at `ERROR` level.
|
||||
- **FR-007**: File-save failures MUST remain at `ERROR` level.
|
||||
- **FR-008**: The `colorama`, `termcolor`, and `rich` packages MUST be removed from the `[project.dependencies]` list in `pyproject.toml` (they are no longer imported by any node).
|
||||
- **FR-009**: The `from rich import print` override in `format_string.py` MUST be removed; the built-in `print` must not be shadowed.
|
||||
- **FR-010**: All existing tests MUST continue to pass after the changes.
|
||||
|
||||
## Success Criteria *(mandatory)*
|
||||
|
||||
### Measurable Outcomes
|
||||
|
||||
- **SC-001**: Zero lines written to stdout/stderr by comfydv modules during a normal node execution (verified by capturing output in the test suite).
|
||||
- **SC-002**: At least one ERROR-level log record is produced when a FormatString node receives an invalid template (verified by test asserting `caplog` or log record count > 0 at ERROR).
|
||||
- **SC-003**: Setting `logging.getLogger("comfydv").setLevel(logging.DEBUG)` causes DEBUG records to appear; removing that configuration causes zero records — verified by test.
|
||||
- **SC-004**: `pip show comfydv` (or `uv pip show comfydv`) lists no dependency on `colorama`, `termcolor`, or `rich`.
|
||||
- **SC-005**: `uv run pytest` exits 0 with no new failures.
|
||||
|
||||
## Assumptions
|
||||
|
||||
- ComfyUI's own logging configuration is not changed — this package only controls its own logger hierarchy under `comfydv.*`.
|
||||
- The `rich`, `colorama`, and `termcolor` packages are not used anywhere else in the package beyond the logging/print calls being removed (confirmed by grep of the source).
|
||||
- The `aiohttp` dependency remains (it is used for the web routes, not just logging).
|
||||
- No user-facing ComfyUI UI changes are required; this is entirely a server-side/console change.
|
||||
@@ -0,0 +1,112 @@
|
||||
# Tasks: Standardise Node Logging
|
||||
|
||||
**Input**: Design documents from `specs/001-standardise-logging/`
|
||||
|
||||
**Branch**: `001-standardise-logging`
|
||||
|
||||
## Format: `[ID] [P?] [Story] Description`
|
||||
|
||||
- **[P]**: Can run in parallel (different files, no dependencies)
|
||||
- **[US#]**: User story label from spec.md
|
||||
- **-T / -I suffix**: TDD pair — `-T` (write failing test) always committed before its `-I` (implementation)
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Setup — Foundational changes (no user story blocked until these land)
|
||||
|
||||
**Purpose**: Remove the two package-level violations that cause all nodes to misbehave. These must land first; all user-story work builds on them.
|
||||
|
||||
- [x] T001 Add `logging.NullHandler()` to the `comfydv` root logger in `src/comfydv/__init__.py`; remove `from rich import print` import also in `__init__.py` if present
|
||||
- [x] T002 Remove `logger.setLevel(logging.DEBUG)` from `src/comfydv/format_string.py` (line 29); this is the root cause of issue #4
|
||||
|
||||
**Checkpoint**: After T001–T002, importing comfydv no longer forces DEBUG output. User-story work can now begin.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: User Story 1 — Silent Normal Operation (Priority: P1) 🎯 MVP
|
||||
|
||||
**Goal**: Zero lines written to stdout/stderr by comfydv during any successful node execution.
|
||||
|
||||
**Independent Test**: Run `uv run pytest tests/test_logging.py -k us1` — all US1 tests pass and capsys shows no captured output.
|
||||
|
||||
### User Story 1 — TDD pairs
|
||||
|
||||
- [x] T010-T [US1] Write FAILING tests for silent FormatString execution in `tests/test_logging.py` — step defs for US1 scenarios in `specs/001-standardise-logging/features/us1_silent_normal_operation.feature` (red)
|
||||
- [x] T010-I [US1] Remove the 8-line diagnostic `print()` block from `FormatString.format_string` in `src/comfydv/format_string.py` (lines 446–469) so T010-T passes (green)
|
||||
- [x] T011-T [US1] Write FAILING tests asserting `IS_CHANGED` and `update_widget` produce zero stdout in `tests/test_logging.py` (red)
|
||||
- [x] T011-I [US1] Downgrade all `logger.info(...)` calls inside `IS_CHANGED` and `update_widget` to `logger.debug(...)` in `src/comfydv/format_string.py` so T011-T passes (green)
|
||||
- [x] T012-T [P] [US1] Write FAILING test for silent `RandomChoice.random_choice` execution in `tests/test_logging.py` (red)
|
||||
- [x] T012-I [P] [US1] Replace `print(colored(...))` / `pprint(...)` calls in `src/comfydv/random_choice.py` with `logger.debug(...)` calls; add `logger = logging.getLogger(__name__)` and remove `colorama`/`termcolor`/`rich` imports so T012-T passes (green)
|
||||
|
||||
**Checkpoint**: `uv run pytest tests/test_logging.py -k us1` green; capsys shows zero comfydv output for any successful execution path.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: User Story 2 — Errors Still Surface (Priority: P1)
|
||||
|
||||
**Goal**: Template errors, save failures, and CircuitBreaker interrupts emit records at ERROR/INFO — visible in a default ComfyUI session.
|
||||
|
||||
**Independent Test**: Run `uv run pytest tests/test_logging.py -k us2` with `caplog` — at least one ERROR record per failure scenario.
|
||||
|
||||
### User Story 2 — TDD pairs
|
||||
|
||||
- [x] T020-T [US2] Write FAILING tests using `caplog` asserting ERROR records on bad Jinja2 template, missing Simple variable, and failed file save in `tests/test_logging.py` — step defs for `us2_errors_still_surface.feature` (red)
|
||||
- [x] T020-I [US2] Confirm the three error paths in `FormatString.format_string` already use `logger.error(...)` — add or correct any that don't; convert `print(f"Error loading node state: {e}")` in `load_node_state` to `logger.error(...)` in `src/comfydv/format_string.py` so T020-T passes (green)
|
||||
- [x] T021-T [P] [US2] Write FAILING test asserting `CircuitBreaker.doit` with `status=False` produces a log record (DEBUG or above) in `tests/test_logging.py` (red)
|
||||
- [x] T021-I [P] [US2] Add `import logging` and `logger = logging.getLogger(__name__)` to `src/comfydv/circuit_breaker.py`; replace `print("Circuit Breaker triggered")` with `logger.debug("CircuitBreaker: interrupt triggered")` so T021-T passes (green)
|
||||
|
||||
**Checkpoint**: `uv run pytest tests/test_logging.py -k us2` green; error scenarios all produce records.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: User Story 3 — Developer Debug Mode (Priority: P2)
|
||||
|
||||
**Goal**: Setting `logging.getLogger("comfydv").setLevel(logging.DEBUG)` surfaces detailed traces; no config → zero records.
|
||||
|
||||
**Independent Test**: Run `uv run pytest tests/test_logging.py -k us3` — DEBUG opt-in and NullHandler default both verified.
|
||||
|
||||
### User Story 3 — TDD pairs
|
||||
|
||||
- [x] T030-T [US3] Write FAILING tests confirming DEBUG records appear with opt-in config and zero records appear with default NullHandler in `tests/test_logging.py` — step defs for `us3_developer_debug_mode.feature` (red)
|
||||
- [x] T030-I [US3] Verify that after T001/T010-I/T011-I the NullHandler is the only default handler and DEBUG calls exist in `format_string.py` for the trace paths — add `logger.debug()` calls to `update_widget` for key extraction summary and `format_string` for execution trace so T030-T passes (green)
|
||||
|
||||
**Checkpoint**: `uv run pytest tests/test_logging.py -k us3` green; full test suite still passes.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Polish & cross-cutting
|
||||
|
||||
**Purpose**: Remove dead runtime dependencies and close the quality gates.
|
||||
|
||||
- [x] T040 [P] Remove `colorama`, `termcolor`, and `rich` from `[project.dependencies]` in `pyproject.toml`; run `uv sync` to update `uv.lock`
|
||||
- [x] T041 [P] Remove `from colorama import just_fix_windows_console`, `just_fix_windows_console()`, `from termcolor import colored`, `from rich.pretty import pprint` dead imports from `src/comfydv/random_choice.py`
|
||||
- [x] T042 Run `uv run ruff check --fix && uv run ruff format` and fix any remaining lint issues
|
||||
- [x] T043 Run full test suite `uv run pytest` and confirm exit 0 with no new failures
|
||||
- [x] T044 Run `beacon doctor --strict` and confirm 0 failures
|
||||
|
||||
---
|
||||
|
||||
## Dependencies & Execution Order
|
||||
|
||||
- **Phase 1 (T001–T002)**: No dependencies — start here
|
||||
- **Phase 2 (US1)**: Requires Phase 1 complete; T010-T must precede T010-I, T011-T before T011-I, T012-T before T012-I
|
||||
- **Phase 3 (US2)**: Requires Phase 1; T020-T before T020-I, T021-T before T021-I; can run in parallel with Phase 2 on separate files
|
||||
- **Phase 4 (US3)**: Requires Phase 1 + Phase 2 (NullHandler must exist); T030-T before T030-I
|
||||
- **Phase 5 (Polish)**: Requires all prior phases; T040/T041 can run in parallel (different files)
|
||||
|
||||
### TDD rule (enforced by `beacon doctor --strict`)
|
||||
Every `-T` task is committed alone (red) before its `-I` partner. `beacon doctor tdd-commit-discipline` will FAIL if a `-T` and its `-I` appear in the same commit.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Strategy
|
||||
|
||||
### MVP (Phase 1 + Phase 2 only)
|
||||
1. T001 → T002 (setup)
|
||||
2. T010-T → T010-I → T011-T → T011-I → T012-T → T012-I (silence the hot path)
|
||||
3. Validate: `uv run pytest tests/test_logging.py -k us1` green, zero capsys output
|
||||
|
||||
This alone fixes issue #4.
|
||||
|
||||
### Full delivery
|
||||
Continue with Phase 3 → 4 → 5 in order. Full suite green before opening the PR.
|
||||
@@ -1,7 +1,11 @@
|
||||
import logging
|
||||
|
||||
from .circuit_breaker import CircuitBreaker
|
||||
from .format_string import FormatString
|
||||
from .random_choice import RandomChoice
|
||||
|
||||
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
||||
|
||||
# A dictionary that contains all nodes you want to export with their names
|
||||
# NOTE: names should be globally unique
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
This node is designed in a hacky way to allow you to break a render run semi-gracefully.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
if "comfy" in sys.modules:
|
||||
from comfy.model_management import InterruptProcessingException # noqa
|
||||
else:
|
||||
print(
|
||||
logger.debug(
|
||||
"ComfyUI not detected, CircuitBreaker node will not function properly outside of ComfyUI."
|
||||
)
|
||||
|
||||
@@ -47,8 +50,7 @@ class CircuitBreaker:
|
||||
CATEGORY = "dv/utils"
|
||||
|
||||
def doit(self, trigger, **kwargs):
|
||||
if kwargs.get("status"):
|
||||
print("Circuit Breaker triggered")
|
||||
if not kwargs.get("status", True):
|
||||
logger.debug("CircuitBreaker: interrupt triggered")
|
||||
raise InterruptProcessingException()
|
||||
else:
|
||||
return (trigger,)
|
||||
return (trigger,)
|
||||
|
||||
@@ -22,11 +22,9 @@ from typing import Any, Dict, List, Tuple
|
||||
|
||||
from aiohttp import web
|
||||
from jinja2 import exceptions, sandbox
|
||||
from rich import print
|
||||
|
||||
# Set up logger for this module
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
if "comfy" in sys.modules:
|
||||
import folder_paths # from comfyui - gives access to `get_temp_directory()` and `get_output_directory()`
|
||||
@@ -366,33 +364,28 @@ class FormatString:
|
||||
>>> assert result[2] == "Bob"
|
||||
-->
|
||||
"""
|
||||
logger.info(
|
||||
f"Formatting string - type: {template_type}, unique_id: {unique_id}"
|
||||
logger.debug(
|
||||
"Formatting string - type: %s, unique_id: %s", template_type, unique_id
|
||||
)
|
||||
logger.debug(f"Template: {template[:100]}...")
|
||||
logger.debug("Template: %.100s...", template)
|
||||
|
||||
keys = cls._extract_keys(template)
|
||||
logger.debug(f"Extracted variables: {keys}")
|
||||
input_vals = ", ".join(f'{k}={kwargs.get(k, "")}' for k in keys)
|
||||
logger.debug(f"Input values: {input_vals}")
|
||||
logger.debug("Extracted variables: %s", keys)
|
||||
input_vals = ", ".join(f"{k}={kwargs.get(k, '')}" for k in keys)
|
||||
logger.debug("Input values: %s", input_vals)
|
||||
|
||||
# CRITICAL: Update RETURN_TYPES/RETURN_NAMES before execution to ensure they match our return tuple
|
||||
# This is necessary because update_widget might not have been called yet (e.g., on workflow load)
|
||||
if unique_id:
|
||||
cls.update_widget(unique_id, template_type, template)
|
||||
logger.debug(
|
||||
f"Updated RETURN_TYPES for node {unique_id}: {cls.RETURN_TYPES}"
|
||||
"Updated RETURN_TYPES for node %s: %s", unique_id, cls.RETURN_TYPES
|
||||
)
|
||||
|
||||
if template_type == "Simple":
|
||||
try:
|
||||
formatted_string = template.format(**kwargs)
|
||||
if logger.level < logging.DEBUG:
|
||||
logger.info(
|
||||
f"Simple format successful, result length: {len(formatted_string)}"
|
||||
)
|
||||
elif logger.level == logging.DEBUG:
|
||||
logger.debug(f"Simple format successful: {formatted_string}")
|
||||
logger.debug("Simple format successful: %.50s", formatted_string)
|
||||
except KeyError as e:
|
||||
error_msg = f"Missing variable in Simple template: {str(e)}"
|
||||
logger.error(error_msg)
|
||||
@@ -407,8 +400,8 @@ class FormatString:
|
||||
# Combine user-provided kwargs with additional_context
|
||||
context = {**cls.additional_context, **kwargs}
|
||||
formatted_string = jinja_template.render(**context)
|
||||
logger.info(
|
||||
f"Jinja2 format successful, result length: {len(formatted_string)}"
|
||||
logger.debug(
|
||||
"Jinja2 format successful, result length: %d", len(formatted_string)
|
||||
)
|
||||
except exceptions.TemplateSyntaxError as e:
|
||||
error_msg = f"Error in Jinja2 template: {str(e)}"
|
||||
@@ -442,18 +435,6 @@ class FormatString:
|
||||
else:
|
||||
logger.debug("No save_path provided, skipping state save")
|
||||
|
||||
# Log the final formatted string to stdout for visibility
|
||||
print(f"\n[FormatString Node {unique_id}] Output:")
|
||||
print(f" formatted_string: {formatted_string}")
|
||||
print(f" Variables extracted: {keys}")
|
||||
print(f" Variable values: {[kwargs.get(key, '') for key in keys]}")
|
||||
print(f" Class RETURN_TYPES: {cls.RETURN_TYPES}")
|
||||
print(f" Class RETURN_NAMES: {cls.RETURN_NAMES}")
|
||||
print(
|
||||
f" Expected outputs: {len(keys)} vars + formatted_string + saved_file_path = {len(keys) + 2} total"
|
||||
)
|
||||
print()
|
||||
|
||||
# Return formatted_string and saved_file_path FIRST (fixed positions 0,1),
|
||||
# then all input values (for chaining)
|
||||
# The order must match what was set in update_widget's RETURN_TYPES/RETURN_NAMES
|
||||
@@ -462,17 +443,14 @@ class FormatString:
|
||||
actual_save_path,
|
||||
) + tuple(str(kwargs.get(key, "")) for key in keys)
|
||||
|
||||
print(f"[FormatString Node {unique_id}] Actual return tuple:")
|
||||
for i, (name, value) in enumerate(zip(cls.RETURN_NAMES, result)):
|
||||
value_preview = value[:50] if len(value) > 50 else value
|
||||
print(f" Output {i}: {name} = {value_preview}")
|
||||
print()
|
||||
|
||||
logger.debug(
|
||||
f"Returning {len(result)} outputs: keys={keys}, formatted_string={formatted_string[:50]}..., save_path={actual_save_path}"
|
||||
"Returning %d outputs: keys=%s, save_path=%s",
|
||||
len(result),
|
||||
keys,
|
||||
actual_save_path,
|
||||
)
|
||||
logger.debug(
|
||||
f"Full result tuple length: {len(result)}, expected: {len(keys) + 2}"
|
||||
"Full result tuple length: %d, expected: %d", len(result), len(keys) + 2
|
||||
)
|
||||
return result
|
||||
|
||||
@@ -533,13 +511,15 @@ class FormatString:
|
||||
>>> assert FormatString.node_configs["test_node"] == config
|
||||
-->
|
||||
"""
|
||||
logger.info(
|
||||
f"Updating widget config - node_id: {node_id}, template_type: {template_type}"
|
||||
logger.debug(
|
||||
"Updating widget config - node_id: %s, template_type: %s",
|
||||
node_id,
|
||||
template_type,
|
||||
)
|
||||
logger.debug(f"Template: {template[:100]}...")
|
||||
logger.debug("Template: %.100s...", template)
|
||||
|
||||
keys = cls._extract_keys(template)
|
||||
logger.info(f"Extracted {len(keys)} variables from template: {keys}")
|
||||
logger.debug("Extracted %d variables from template: %s", len(keys), keys)
|
||||
|
||||
config: Dict[str, Any] = {
|
||||
"inputs": {
|
||||
@@ -567,12 +547,14 @@ class FormatString:
|
||||
cls.OUTPUT_IS_LIST = (False,) * (len(keys) + 2)
|
||||
|
||||
logger.debug(
|
||||
f"Updated RETURN_TYPES to {len(cls.RETURN_TYPES)} outputs: {cls.RETURN_NAMES}"
|
||||
"Updated RETURN_TYPES to %d outputs: %s",
|
||||
len(cls.RETURN_TYPES),
|
||||
cls.RETURN_NAMES,
|
||||
)
|
||||
|
||||
# Store the configuration for this specific node
|
||||
cls.node_configs[node_id] = config
|
||||
logger.debug(f"Stored config for node {node_id}")
|
||||
logger.debug("Stored config for node %s", node_id)
|
||||
|
||||
return config
|
||||
|
||||
@@ -669,7 +651,7 @@ class FormatString:
|
||||
except FileNotFoundError:
|
||||
return {}
|
||||
except Exception as e:
|
||||
print(f"Error loading node state: {e}")
|
||||
logger.error("Failed to load node state from %s: %s", file_path, e)
|
||||
return {}
|
||||
|
||||
|
||||
@@ -710,16 +692,18 @@ async def update_format_string_node(request):
|
||||
template_type = data.get("template_type", "")
|
||||
template = data.get("template", "")
|
||||
|
||||
logger.info(
|
||||
f"Web API: update_format_string_node - node_id: {node_id}, template_type: {template_type}"
|
||||
logger.debug(
|
||||
"Web API: update_format_string_node - node_id: %s, template_type: %s",
|
||||
node_id,
|
||||
template_type,
|
||||
)
|
||||
|
||||
try:
|
||||
updated_config = FormatString.update_widget(node_id, template_type, template)
|
||||
logger.debug(f"Successfully updated config for node {node_id}")
|
||||
logger.debug("Successfully updated config for node %s", node_id)
|
||||
return web.json_response(updated_config)
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating node config: {str(e)}", exc_info=True)
|
||||
logger.error("Error updating node config: %s", str(e), exc_info=True)
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user