From 6f1e9b4c385f207e2dce791b250b4c9c0e7e85a0 Mon Sep 17 00:00:00 2001 From: Frank Ray <52075808+FrankRay78@users.noreply.github.com> Date: Sat, 16 May 2026 19:42:21 +0100 Subject: [PATCH 1/7] Updated commands --- .claude/commands/capture-learnings.md | 20 +-- .claude/commands/speckit.confirmissue.md | 16 ++- .claude/commands/speckit.draftissue.md | 120 ++++++++++++---- .claude/commands/speckit.reviewissue.md | 62 ++++---- .gitignore | 2 + .../commands/speckit.analyze.testplan.md | 136 ++++++++++-------- .specify/memory/constitution.md | 43 ++++-- 7 files changed, 263 insertions(+), 136 deletions(-) diff --git a/.claude/commands/capture-learnings.md b/.claude/commands/capture-learnings.md index 23064ce..4b4db87 100644 --- a/.claude/commands/capture-learnings.md +++ b/.claude/commands/capture-learnings.md @@ -41,15 +41,9 @@ Run this command at the end of a working session, after completing a feature, or - Renamed or moved files (R status) — especially CLAUDE.md, docs, or config files - Modifications to `CLAUDE.md` or `.specify/memory/constitution.md` - When `has_branch_context` is false (running on the default branch, or feature branch with no commits yet), skip this step — only Confidence levels reachable in step 5 are `Medium (chat only)`. + When `has_branch_context` is false (running on the default branch, or feature branch with no commits yet), skip this step — only Confidence levels reachable in step 4 are `Medium (chat only)`. -4. **Ask discovery questions**: Before synthesising candidates, ask the user: - - > "Before I write up candidates — was there anything surprising, a wrong assumption I made, or something that took longer than expected that didn't show up clearly in our conversation?" - - Wait for a reply. Incorporate any freeform input as additional candidates. - -5. **Synthesise candidates**: Produce at most 5 candidates. Fewer is fine — only include signals that are non-obvious, generalisable, and likely to recur. Skip signals that are clearly one-off, feature-specific, or already documented. +4. **Synthesise candidates**: Produce at most 5 candidates. Fewer is fine — only include signals that are non-obvious, generalisable, and likely to recur. Skip signals that are clearly one-off, feature-specific, or already documented. For each candidate write: @@ -68,15 +62,15 @@ Run this command at the end of a working session, after completing a feature, or - Structural decision → `.claude/memory/project_*.md`, or note a suggested CLAUDE.md edit - Confirmed approach → `.claude/memory/feedback_*.md` -6. **Confirm with the user**: After listing the candidates, ask: +5. **Confirm with the user**: After listing the candidates, ask: > "Which of these are worth keeping? Reply with the numbers (e.g. `1 3`), or `none`." Wait for the reply before writing anything. -7. **Check for duplicates**: For each approved candidate, grep `.claude/memory/` for related terms. If a closely related memory already exists, note whether this should *update* the existing file rather than create a new one. +6. **Check for duplicates**: For each approved candidate, grep `.claude/memory/` for related terms. If a closely related memory already exists, note whether this should *update* the existing file rather than create a new one. -8. **Write approved learnings**: For each approved candidate, write (or update) a memory file in `.claude/memory/` using this format: +7. **Write approved learnings**: For each approved candidate, write (or update) a memory file in `.claude/memory/` using this format: ```markdown --- @@ -85,7 +79,7 @@ Run this command at the end of a working session, after completing a feature, or type: feedback | project --- - [Draft from step 5] + [Draft from step 4] **Why:** [inferred from the evidence — the conversation moment or constraint that caused it] **How to apply:** [when this should influence future behaviour] @@ -95,4 +89,4 @@ Run this command at the end of a working session, after completing a feature, or If the candidate's target was a CLAUDE.md edit rather than a memory file, describe the suggested edit and ask the user whether to apply it. -9. **Output result**: List the files written or updated (or edits proposed). If nothing was approved, output: "No learnings captured." +8. **Output result**: List the files written or updated (or edits proposed). If nothing was approved, output: "No learnings captured." diff --git a/.claude/commands/speckit.confirmissue.md b/.claude/commands/speckit.confirmissue.md index 21298bc..8993ae6 100644 --- a/.claude/commands/speckit.confirmissue.md +++ b/.claude/commands/speckit.confirmissue.md @@ -121,11 +121,25 @@ The new section is structured as: +### Requirements + +- **:** <decision>. +- **<title>:** <decision>. + +### Technical <!-- omit this sub-heading entirely if no technical decisions --> + - **<title>:** <decision>. - **<title>:** <decision>. -- ... ``` +Routing rules: + +- Bullets corresponding to gaps under `### Requirements gaps` in the review comment go under `### Requirements` here. +- Bullets corresponding to gaps under `### Technical gaps` in the review comment go under `### Technical` here. +- If one group has no decisions, **omit its sub-heading entirely** — do not emit an empty `### Technical` section. +- Strip the gap numbers from the review comment (1, 2, 3, …) when emitting confirmed decisions — bullets here are unordered. +- **Backwards compatibility:** if the review comment has no `### Requirements gaps` / `### Technical gaps` sub-headings (i.e. it was authored before this convention), emit all decisions as a flat bullet list with no sub-headings (the pre-split format). + Preserve everything above the section byte-for-byte. Do not "tidy" the original proposal. ### 5. Patch the issue body diff --git a/.claude/commands/speckit.draftissue.md b/.claude/commands/speckit.draftissue.md index 39f9066..1e23c3c 100644 --- a/.claude/commands/speckit.draftissue.md +++ b/.claude/commands/speckit.draftissue.md @@ -12,6 +12,8 @@ $ARGUMENTS `$ARGUMENTS` is expected to be an **unstructured feature brief** — a paragraph or rough bullet list describing capability the user wants, possibly with mixed concerns. +The brief MAY reference one or more existing GitHub issue URLs/numbers as input — e.g. *"I want to use an existing issue as input, that issue will be closed, can we start from there: <url>"*. Treat this as a **migration brief**: ingest the referenced issue(s) in Step 1, classify their content in Step 2.5, and propose closing them in Step 7 (always with explicit user confirmation). + If empty, ask the user for the brief. Do not invent one. --- @@ -30,7 +32,11 @@ Output: a GitHub issue created via `gh`. A transient draft file is used as the e ### 1. Capture the brief -Read `$ARGUMENTS`. If it is empty or a single sentence, ask the user to expand. Do not proceed with a thin brief — the value of this command is in the structured conversation, which needs raw material. +Read `$ARGUMENTS`. If it is empty, ask the user to expand. Do not invent one. + +**Issue-URL ingest (migration mode).** If the brief references one or more GitHub issue URLs or `#N` numbers as input, fetch each with `gh issue view <N> --repo <owner/repo> --json title,body,comments`. From each issue, treat the union of the issue body + any comment marked `<!-- speckit:review -->` + any `## Confirmed decisions` section as additional brief material. Tell the user which issues were ingested before proceeding, and flag this run as a migration so Step 2.5 and Step 7 behave accordingly. A short brief like "*use #44*" is sufficient in migration mode — the source issue body supplies the raw material. + +Otherwise, if the brief is a single sentence with no migration reference, ask the user to expand. The value of this command is in the structured conversation, which needs raw material. ### 2. Ground the draft in the codebase @@ -44,6 +50,17 @@ Before asking the user any questions, explore the codebase so questions are *inf Tell the agent *not* to design the feature, only to surface what exists. Cap report length so it doesn't dominate context. +### 2.5. Classify existing content (migration runs only) + +Skip this step if no existing issue was ingested in Step 1. + +For each ingested issue, sort the content into two buckets and present the classification to the user **before** opening any Q&A: + +- **Requirements candidates** — user/persona, jobs-to-be-done, observable behaviour, scope edges, acceptance criteria phrased as user-visible outcomes. +- **Technical-notes candidates** — file paths under `src/`, ports, library picks, integration shape, visual aesthetic anchors, exact UI copy, polling cadences, project-housekeeping ACs ("project X exists", "solution file includes Y"). + +Show the split as two short lists referencing the original headings/bullets, and ask the user to confirm or redirect items between buckets. Then proceed to Step 3. + ### 3. Surface decision points to the user Identify the ~5–10 decisions the brief leaves open. For each, present: @@ -57,38 +74,55 @@ Each decision should be answerable with a short response. The user can rubber-st **Recommendation quality bar:** identical to `/speckit.reviewissue` — concrete actionable defaults (a value, a library, a field name, a scope call), not "consider X". The reason cites evidence: existing convention, framework behaviour, POC posture, codebase constraint. If you genuinely have no view, say so and list options with trade-offs; don't fake confidence. -Common categories to probe (apply only those that fit the brief): +Common categories to probe (apply only those that fit the brief). Walk **Requirements** categories first — they shape the issue body's main half. Then walk **Technical** categories — suggest the ones Step 2 surfaced from the codebase, and let the author set the depth. -- **Data model & storage location** — where data lives, schema shape, mandatory vs optional fields, mutability. -- **Identity & auth** — who is the caller, how is identity carried/validated, what's enforced now vs later. -- **Read / query design** — what queries the feature must support, indexing strategy, pagination, sort order. -- **Schema / API evolution** — fixed enum vs free-form, versioning, upgrade path. -- **Notification / integration** — push vs pull, webhooks, downstream consumers, event ordering. -- **UI / state** — component placement, routing, state ownership, loading & error states (if the brief touches a frontend). -- **Public API surface** — request/response shape, status codes, error model, idempotency keys. -- **Performance budget** — expected throughput, latency targets, payload size bounds. -- **Scope edges** — what's explicitly *out* (logging? archival? admin UI? migration of existing data?). -- **Failure modes** — unknown inputs, missing references, partial success, retries. +**Requirements categories** (always probe these first): -### 4. Iterate to lock decisions +- **User & persona** — who uses this feature, in what context, on what surface. +- **Job-to-be-done** — the task the user is trying to accomplish; the observable outcome that means "done" from their viewpoint. +- **Scenarios** — the 1–3 concrete flows the feature must support, phrased as user actions and observable system responses. +- **Scope edges** — what's explicitly *out*. Be precise: actual scope decisions (e.g. "no admin UI in v1", "no migration of pre-existing records"), not technical defaults restated negatively. +- **Semantics of user-visible behaviour** — matching rules, comparison scope, case/whitespace handling, what the user sees at boundaries. +- **Acceptance criteria from outside** — what an observer (not the implementer) can see and check. Phrase each AC as a **user-observable outcome**, not the implementation mechanism that delivers it. Multiple reasonable implementations should satisfy the same AC. This is **Principle IX (Behavioural Specification)** in `.specify/memory/constitution.md` — `/speckit.analyze` enforces it. -Capture answers. Where the user delegates back ("you advise", "what's best?"), give a single concrete recommendation with a one-sentence justification — do not re-open the menu. Confirm any small interpretation questions before drafting (e.g. mandatory sub-fields inside an optional struct). + *The independence test*: would this AC still be true under a different reasonable implementation of the same feature? If no, you've described the mechanism — re-phrase at the level the user actually cares about. -### 5. Draft to a transient file + | Avoid (mechanism, prescriptive) | Prefer (outcome, level-up) | + |---|---| + | "Row gains `.flash` class for ~800ms via CSS animation" | "Users can visually distinguish new rows from existing ones" | + | "Sidebar entries appear in DOM order: Identities, Services, Events" | "Sidebar entries follow a fixed, predictable order" | + | "On click, JS toggles `hidden` on screen sections" | "Switching screens shows the chosen content without page reload" | + | "Error toast displays 'Invalid email address' in red" | "User is told the input was rejected and given guidance to correct it" | + | "Endpoint returns `{status: 'ok', data: [...]}` with HTTP 200" | "Caller can retrieve the current list of services in a single request" | + | "Setting is written to the `user_preferences` table and cached in Redis" | "User's setting is remembered across sessions and visible on next sign-in" | + | "Endpoint returns `[]` when no matches found" | "User is shown a clear empty state when no results match" | + + *Do not write into ACs*: CSS class names, DOM IDs or element types, animation specifics, font names/weights/colours, framework or library picks, pixel measurements, timing values (Ns / Nms), polling cadences, specific URLs or ports, exact error message strings, storage technology. These are implementation choices, not acceptance criteria. Project-housekeeping items (project exists, sln updated, test scaffolding created) belong in `/speckit.tasks`, not here. + + *Regression exception*: an AC that pins a specific mechanism is permitted only when it exists to prevent a named, previously-fixed bug — reference the bug. +- **User-visible failure modes** — what the user sees when a dependency is unreachable, slow, or rejects them; what's communicated; what stays available. -Write the draft to `.claude/scratch/gh-issue-body-{slug}.md` using the native `Write` tool, where `{slug}` is a short kebab-case derivation of the title. Run `mkdir -p .claude/scratch` first if the directory does not yet exist. The path is **repo-relative** — `.claude/scratch/` is the project's canonical scratch location (it is git-ignored). Do not use `/tmp`, the system temp dir, or `~/.claude/`. Tell the user the path so they can open the file in their editor for review. +**Technical categories** (suggest from Step 2 findings; the author sets the depth): + +- **Where it lives** — which existing service/module the new work attaches to, or whether it's a new surface. Avoid locking exact paths/ports here unless the brief is explicit. +- **Integration points** — which existing endpoints, schemas, events, or services the work depends on or extends. +- **Data shape** — storage location, schema sketch, mutability — only at the level the brief already implies. +- **Constraints / gotchas** — idempotency, latency budgets, coupling, conventions binding the new work. +- **Open tech questions** — things the spec author will need to resolve in `/speckit.specify` or `/speckit.plan`. + +If a candidate question doesn't fit either group, it probably belongs downstream — leave it for `/speckit.specify` rather than forcing it into the issue. + +### 4. Iterate to lock decisions -The file is the issue body verbatim — what we write is what we post. Do **not** include an H1 title in the file — that goes on the `gh issue create --title` flag, not in the body. +Capture answers. Where the user delegates back ("you advise", "what's best?"), give a single concrete recommendation with a one-sentence justification — do not re-open the menu. Confirm any small interpretation questions before drafting (e.g. mandatory sub-fields inside an optional struct). -**Link rules** — GitHub's relative-link resolution against issue page URLs is unreliable across surfaces and renderers (broken in comments, inconsistent in bodies, ignored by many third-party renderers — mobile clients, RSS, scrapers). Every link to a file, directory, or line range **must** be an absolute GitHub URL: +### 5. Draft to a transient file -- File: `https://github.com/<owner>/<repo>/blob/<default-branch>/<path>` -- File with line: append `#L<line>` or `#L<start>-L<end>` -- Directory: `https://github.com/<owner>/<repo>/tree/<default-branch>/<path>` +Write the draft to `.claude/scratch/gh-issue-body-{slug}.md` using the native `Write` tool, where `{slug}` is a short kebab-case derivation of the title. The directory is git-ignored — run `mkdir -p .claude/scratch` first if you're not certain it exists yet. Tell the user the file path so they can open it in their editor for review. -Resolve `<owner>/<repo>` from `gh repo view --json nameWithOwner` and `<default-branch>` from `gh repo view --json defaultBranchRef --jq .defaultBranchRef.name` once at the start of step 5 — reuse the result for every link in the draft. The link *text* can stay short (e.g. `[handler.ts:42](https://github.com/owner/repo/blob/main/src/api/handler.ts#L42)`) so readability is unaffected. +The file is the issue body verbatim — what we write is what we post. Write all internal links as **paths relative to the repository root** (e.g. `[architecture](docs/ARCHITECTURE.md)`, `[handler](src/api/handler.ts)`), which is how GitHub resolves links in issue bodies. Do **not** include an H1 title in the file — that goes on the `gh issue create --title` flag, not in the body. -Use this template: +Use this template. The body is split by a horizontal rule (`---`) into a **Requirements** half (what we are building, user-observable) and an optional **Technical notes** half (current best thinking about how). Drop the entire `---` block — heading and all — if the author wants no tech-shape commitments in the issue. ```markdown ## Summary @@ -99,18 +133,36 @@ One short paragraph stating what the feature delivers and the user-visible outco Why now, what's missing today, who needs this. Cite concrete evidence from the codebase (file paths + line numbers) where relevant. -## Proposal +## Users & jobs + +Who uses this feature, in what context, and the task they are trying to accomplish. One short paragraph or 2–4 bullets per persona — no more. -### <Component-level sub-sections as the work demands> -e.g. data model, transactions, endpoints, UI flow, auth, schema migration. Use whatever sub-sections the feature needs — there is no fixed list. +## Capability + +What the feature *does* from outside, described as user-observable behaviour and the 1–3 scenarios that exercise it. Use whatever short sub-sections the feature needs. Do **not** specify ports, file paths under `src/`, polling cadences, framework picks, visual aesthetic anchors, or exact UI copy here — those are tech shape, not requirements. Defer them to the Technical notes section below or to `/speckit.specify`. ## Out of scope -Bullet list. Be explicit about what's deliberately deferred — this is the most useful section for `/speckit.reviewissue` to cross-check. +Bullet list of deliberate scope decisions. Phrase as "X is not in this issue", not as technical defaults restated negatively. ## Acceptance criteria -Checklist of testable outcomes. Each item must be observable from outside the implementation. Reference the project's test conventions where relevant. +Checklist of testable outcomes, **all observable from outside the implementation by a user or external test**. Project-housekeeping items (new project exists, sln updated, test scaffolding created) belong in `/speckit.tasks`, not here. Reference the project's test conventions where relevant. + +--- + +## Technical notes + +> Omit this entire `---`-separated block — heading and all — if no Technical notes content was captured. `/speckit.specify` and `/speckit.plan` are the canonical places to fix tech shape if the author wants to defer. + +### Where it lives +### Integration points +### Constraints / assumptions +### Unknowns + +Use the sub-headings that fit; drop the rest. Keep bullets, not paragraphs. + +--- ## Open questions / future work @@ -118,7 +170,7 @@ Bullets. Things that don't need to be answered to ship the feature but should be ## Related -- Links to architecture docs, testing conventions, and the source files most relevant to the work — written as absolute GitHub URLs (see Link rules above). +- Links to architecture docs, testing conventions, and the source files most relevant to the work — paths relative to the repository root. ``` ### 6. User review @@ -129,7 +181,7 @@ The user can also edit the temp file directly in their editor. If they do, `Read ### 7. Raise the issue and clean up -When the user approves, create the issue and delete the temp file in a single step: +When the user approves, create the issue and delete the scratch file in a single step: ```bash gh issue create --title "<title>" --body-file .claude/scratch/gh-issue-body-<slug>.md && rm .claude/scratch/gh-issue-body-<slug>.md @@ -139,6 +191,14 @@ Verify auth and target repo first with `gh auth status` and `gh repo view --json Return the issue URL. +**Migration mode — supersede the source issue(s).** If Step 1 ingested existing issues, after the new issue URL is known, **always confirm with the user before closing any source issue** — even if the brief said "close it". The brief was written before the new issue existed; the user may want to eyeball the new one first. For each source issue the user confirms for closure: + +```bash +gh issue close <N> --comment "Superseded by #<M>" +``` + +Confirm per-issue, not in bulk. + ### 8. Stop Do not move on to `/speckit.specify` or implementation. The next step in the SDD workflow is `/speckit.reviewissue` against the new issue, which the user will trigger separately. diff --git a/.claude/commands/speckit.reviewissue.md b/.claude/commands/speckit.reviewissue.md index 6142948..b495bfe 100644 --- a/.claude/commands/speckit.reviewissue.md +++ b/.claude/commands/speckit.reviewissue.md @@ -71,10 +71,9 @@ that path already exists and what conventions its siblings follow. ### 3. Identify gaps -Group findings into two sections: +Group findings into two gap sections — **Requirements gaps** and **Technical gaps** — plus a **Notes for SDD** section for non-question observations. -**Core gaps / clarifications** — questions the spec author *must* answer before -`/speckit.specify` can produce a sound spec. Each gap must: +Each gap (in either group) must: - be answerable with a short written response (not "go figure it out") - cite concrete evidence from the issue or codebase where relevant @@ -84,26 +83,30 @@ Group findings into two sections: affirmative answer) or redirect it (record their chosen alternative). A gap without a recommendation forces the author to originate the answer from scratch, which is exactly the work this command is meant to front-load. -- cover at least these categories when applicable: - - **Scope & constraints** — contradictions between stated scope and available - test data / reality (e.g. "issue says X-only, but test data is mostly Y") - - **Semantics** — matching rules, comparison scope, case/whitespace handling, - tolerance for errors - - **Thresholds & numeric criteria** — confirm exact values, whether they - reuse existing constants, what happens at boundaries - - **Integration** — which existing endpoint/service/flow is extended vs. - new; who calls whom; which org(s) are involved - - **Data** — seeding strategy, source of truth, storage choice (match - existing patterns unless there is a reason not to) - - **Security boundary** — how access is enforced between services; auth - mechanism (current codebase may have none) - - **Failure modes** — unreachable dependencies, rate limits, retry policy, - fail-open vs fail-closed - - **Operational** — ports, migrations, docker compose entries, deploy scripts - -**Notes for SDD** — things the spec author should *know* but don't need to -answer. These are observations that will shape the spec without being open -questions: + +**Number gaps contiguously across both groups** (1, 2, 3, … not 1a, 1b). This keeps `/speckit.confirmissue` parsing simple and lets the author refer to questions by a single number in chat. + +**Requirements gaps** — probe these before any technical question. Cover at least these categories when applicable: + +- **User & persona** — who uses this, in what context. The issue may name a feature without naming the person. +- **Job-to-be-done** — the user-visible outcome that means "done"; contradictions between the stated outcome and the proposed mechanism. +- **Scenarios** — the 1–3 user-action / system-response flows the feature must support; missing edge scenarios (empty state, error states from the user's POV). +- **Scope & constraints** — contradictions between stated scope and available test data / reality (e.g. "issue says X-only, but test data is mostly Y"); items in Acceptance Criteria that are project-housekeeping rather than user-observable. +- **Semantics of user-visible behaviour** — matching rules, comparison scope, case/whitespace handling, what the user sees at boundaries. +- **Acceptance criteria from outside** — whether existing ACs are observable from outside the implementation by a user or external test; flag project-housekeeping items (project exists, sln updated, test scaffolding) — they belong in `/speckit.tasks`. +- **User-visible failure modes** — what the user sees when a dependency is unreachable, slow, or rejects them; fail-open vs fail-closed *from the user's viewpoint*. + +**Technical gaps** — surface gaps suggested by Step 2 (codebase grounding) plus any tech shape the issue itself already commits to. Let the author set the depth: they may want extensive tech review or none. Items the author wants tracked but not answered now belong in **Notes for SDD**. + +- **Where it lives** — existing endpoint/service/flow extended vs. new; who calls whom; which org(s) are involved. +- **Integration** — interface contracts with existing components, event/data flow, ordering. +- **Data shape** — seeding strategy, source of truth, storage choice (match existing patterns unless there is a reason not to). +- **Thresholds & numeric criteria** — confirm exact values, whether they reuse existing constants, what happens at boundaries. +- **Security boundary** — how access is enforced between services; auth mechanism (current codebase may have none). +- **Operational** — ports, migrations, docker compose entries, deploy scripts. +- **Tech failure modes** — unreachable dependencies, rate limits, retry policy, fail-open vs fail-closed at the system level. + +**Notes for SDD** — things the spec author should *know* but doesn't need to answer here. Two flavours land in this section: passive observations that will shape the spec, and items the author chose to defer to `/speckit.specify` rather than resolve at issue level. Both are written as bullets, not questions: - Port allocation suggestions based on existing assignments - Reuse opportunities (existing classes/modules the new work can share) @@ -131,7 +134,7 @@ Before taking this into SDD, the following points need answers. Please record re > > When all answers are concrete, run `/speckit.confirmissue #N` to fold them into the issue body as **Confirmed decisions**. -### Core gaps / clarifications +### Requirements gaps **1. <short title>** <concrete framing of the gap, including any evidence from issue/codebase> @@ -149,6 +152,17 @@ Before taking this into SDD, the following points need answers. Please record re > _Answer:_ +### Technical gaps + +> Omit this section entirely if no technical gaps were identified — do not emit an empty heading. Gap numbering continues from the Requirements section (3, 4, …), not restarting at 1. + +**N. <short title>** +... + +> _**Recommendation:**_ ... Reason: ... + +> _Answer:_ + ### Notes for SDD - **<category>**: <observation> diff --git a/.gitignore b/.gitignore index 8185607..0acb583 100644 --- a/.gitignore +++ b/.gitignore @@ -412,3 +412,5 @@ FodyWeavers.xsd # Speckit / Claude scratch and staging files (transient drafts, not source) .claude/scratch/ + +*.lscache diff --git a/.specify/extensions/testplan/commands/speckit.analyze.testplan.md b/.specify/extensions/testplan/commands/speckit.analyze.testplan.md index a3e12fe..5f23980 100644 --- a/.specify/extensions/testplan/commands/speckit.analyze.testplan.md +++ b/.specify/extensions/testplan/commands/speckit.analyze.testplan.md @@ -1,24 +1,21 @@ --- -description: "Cross-check test-plan.md against spec.md `**Scenario:**` labels, appending findings to the analyze report" +description: "Cross-check test-plan.md against spec.md and verify each scenario is implementable in the project's test tooling, appending findings to the analyze report" --- # Test Plan Cross-Check -Cross-check `test-plan.md` `#### Scenario:` headings against `spec.md` `**Scenario: [name]**` -labels for the current feature, then append a **Test Plan Cross-Check** findings table to -the analyze report. +Cross-check `test-plan.md` `#### Scenario:` headings against `spec.md` `**Scenario: [name]**` labels for the current feature, verify each scenario is implementable in the project's actual test tooling, then append a **Test Plan Cross-Check** findings table to the analyze report. -This command runs automatically as an `after_analyze` hook. It enforces AC-to-Test -Traceability: every spec.md `**Scenario:**` label MUST appear as a `#### Scenario:` -heading in test-plan.md with an exact name match (case and punctuation included). +This command runs automatically as an `after_analyze` hook. It enforces two contracts: + +1. **AC-to-Test Traceability** — every spec.md `**Scenario:**` label MUST appear as a `#### Scenario:` heading in test-plan.md with an exact name match (case and punctuation included). Checks A–G in Step 4. +2. **Planning-stage test implementability** — every scenario in test-plan.md MUST be implementable as an automated test using the tooling actually present in the repository's manifests. Check H in Step 6. Scenarios that require tooling not in the project (e.g. browser automation when no Playwright is installed) MUST be resolved at the planning stage by editing test-plan.md, not by writing skipped / `NotImplementedException` / `Decision=Pending` stubs in the codebase. --- ## Step 1 — Locate the current feature -Read `.specify/feature.json` and extract `feature_directory`. -All source files are relative to the repo root: `{feature_directory}/spec.md`, -`{feature_directory}/test-plan.md`, `{feature_directory}/tasks.md`. +Read `.specify/feature.json` and extract `feature_directory`. All source files are relative to the repo root: `{feature_directory}/spec.md`, `{feature_directory}/test-plan.md`, `{feature_directory}/tasks.md`. --- @@ -40,12 +37,9 @@ Output this finding and stop: ## Step 3 — Extract artifacts -From `spec.md`: collect every `**Scenario: [name]**` label across all `### User Story N` -sections. The name is the text between `**Scenario: ` and the trailing `**`, trimmed. +From `spec.md`: collect every `**Scenario: [name]**` label across all `### User Story N` sections. The name is the text between `**Scenario: ` and the trailing `**`, trimmed. -From `test-plan.md`: collect every `#### Scenario: [name]` heading. The name is the text -after `#### Scenario: `, trimmed. Also capture the body of each scenario (the lines -between this heading and the next `####`/`###`/EOF) so check F can verify it. +From `test-plan.md`: collect every `#### Scenario: [name]` heading. The name is the text after `#### Scenario: `, trimmed. Also capture the body of each scenario (the lines between this heading and the next `####`/`###`/EOF) so check F can verify it. --- @@ -56,70 +50,100 @@ Perform all of the following checks and collect findings: ### A — Scenario coverage in test-plan.md For each scenario name in `spec.md`: -- If no matching `#### Scenario:` heading appears in `test-plan.md` (exact match, - case-sensitive after trim): - → Finding: **Scenario in spec missing from test-plan** | CRITICAL | Scenario name — - every spec scenario must appear in test-plan.md to maintain traceability +- If no matching `#### Scenario:` heading appears in `test-plan.md` (exact match, case-sensitive after trim): → Finding: **Scenario in spec missing from test-plan** | CRITICAL | Scenario name — every spec scenario must appear in test-plan.md to maintain traceability ### B — Scenario drift in test-plan.md For each `#### Scenario:` name in `test-plan.md`: -- If no matching `**Scenario:**` label appears in `spec.md`: - → Finding: **Scenario in test-plan not found in spec** | WARNING | Scenario name — - possible rename or deletion in spec.md; reconcile by re-running `/speckit.testplan` +- If no matching `**Scenario:**` label appears in `spec.md`: → Finding: **Scenario in test-plan not found in spec** | WARNING | Scenario name — possible rename or deletion in spec.md; reconcile by re-running `/speckit.testplan` ### C — Case/whitespace-only mismatches -For each spec.md scenario name with no exact match in test-plan.md, check whether a -case-insensitive trim-collapsed match exists. If yes: - → Finding: **Scenario name mismatch (case or whitespace)** | CRITICAL | Spec name vs - test-plan name — names must match character-for-character (the traceability key is exact) +For each spec.md scenario name with no exact match in test-plan.md, check whether a case-insensitive trim-collapsed match exists. If yes: → Finding: **Scenario name mismatch (case or whitespace)** | CRITICAL | Spec name vs test-plan name — names must match character-for-character (the traceability key is exact) ### D — Duplicate scenario names in spec.md -If any two `**Scenario:**` labels in `spec.md` are identical (case-insensitive after trim): - → Finding: **Duplicate scenario name in spec** | CRITICAL | Duplicate name — names - must be unique to act as the traceability key +If any two `**Scenario:**` labels in `spec.md` are identical (case-insensitive after trim): → Finding: **Duplicate scenario name in spec** | CRITICAL | Duplicate name — names must be unique to act as the traceability key -Note: the downstream match key (used by checks A–C and by `/speckit.testchecklist`) is -**case-sensitive** after trim. The case-insensitive comparison here is deliberately -broader so near-duplicates (`Login OK` vs `login ok`) are caught as drift even though -they would not collide under exact matching. +Note: the downstream match key (used by checks A–C and by `/speckit.testchecklist`) is **case-sensitive** after trim. The case-insensitive comparison here is deliberately broader so near-duplicates (`Login OK` vs `login ok`) are caught as drift even though they would not collide under exact matching. ### E — Duplicate scenario names in test-plan.md -If any two `#### Scenario:` names in `test-plan.md` are identical (case-insensitive after -trim): - → Finding: **Duplicate scenario name in test-plan** | WARNING | Duplicate name — - `/speckit.testchecklist` cannot distinguish between them (same case-insensitive - rationale as check D) +If any two `#### Scenario:` names in `test-plan.md` are identical (case-insensitive after trim): → Finding: **Duplicate scenario name in test-plan** | WARNING | Duplicate name — `/speckit.testchecklist` cannot distinguish between them (same case-insensitive rationale as check D) ### F — Scenario label and body format in test-plan.md For each scenario in `test-plan.md`: -1. Verify the heading uses exactly `#### Scenario:` (four `#` marks). Any heading that - uses a different depth (`###`, `#####`) or omits the `Scenario:` prefix: - → Finding: **Malformed scenario heading in test-plan** | WARNING | Scenario text +1. Verify the heading uses exactly `#### Scenario:` (four `#` marks). Any heading that uses a different depth (`###`, `#####`) or omits the `Scenario:` prefix: → Finding: **Malformed scenario heading in test-plan** | WARNING | Scenario text -2. Verify the captured body (Step 3) contains at least one `**WHEN**` line and at least - one `**THEN**` line. A scenario with a heading but no `**WHEN**`/`**THEN**` body: - → Finding: **Empty or incomplete scenario body in test-plan** | CRITICAL | Scenario name — - a scenario without WHEN/THEN cannot drive a test +2. Verify the captured body (Step 3) contains at least one `**WHEN**` line and at least one `**THEN**` line. A scenario with a heading but no `**WHEN**`/`**THEN**` body: → Finding: **Empty or incomplete scenario body in test-plan** | CRITICAL | Scenario name — a scenario without WHEN/THEN cannot drive a test ### G — Test plan completeness signal -If `test-plan.md` exists but contains no `#### Scenario:` entries at all: - → Finding: **test-plan.md contains no scenarios** | CRITICAL | File exists but is - empty of scenarios +If `test-plan.md` exists but contains no `#### Scenario:` entries at all: → Finding: **test-plan.md contains no scenarios** | CRITICAL | File exists but is empty of scenarios + +If `spec.md` exists but contains no `**Scenario:**` labels at all: → Finding: **spec.md contains no Scenario labels** | CRITICAL | every Acceptance Scenario must carry a `**Scenario: [name]**` label to be traceable + +--- + +## Step 5 — Detect project test tooling + +Build a **tooling fingerprint** by scanning manifest files for test-related dependencies. This fingerprint feeds the implementability check in Step 6. The detection is best-effort and intentionally repo-agnostic — the fingerprint is just the literal package/dependency names found, passed verbatim into the per-scenario prompt. + +Scan the repo root and immediate subdirectories (e.g. `src/`, `tests/`, `apps/`, `packages/`) for the following manifests. Do NOT descend into `node_modules/`, `bin/`, `obj/`, `target/`, `dist/`, `build/`, `.venv/`, or `vendor/`. + +| Manifest | Extract | +|---|---| +| `*.csproj`, `*.fsproj`, `*.vbproj` | `<PackageReference Include="X" />` values | +| `package.json` | `dependencies` + `devDependencies` keys | +| `pyproject.toml` | `[tool.poetry.dependencies]`, `[project.dependencies]`, `[tool.poetry.group.*.dependencies]` keys | +| `requirements*.txt`, `dev-requirements*.txt` | one package per line, before any version specifier | +| `Cargo.toml` | `[dependencies]`, `[dev-dependencies]` keys | +| `go.mod` | `require` block module paths | +| `Gemfile`, `Gemfile.lock` | `gem` entries | +| `composer.json` | `require` + `require-dev` keys | + +Deduplicate the collected package names. The fingerprint is this deduplicated list rendered as a plain comma-separated string, capped at 8 KB (truncate by dropping the tail if exceeded — common test packages typically appear first because they live in test-project manifests scanned early). + +If no manifests are found, the fingerprint is the literal string `(none)` and Step 6 will treat every scenario as `NOT_IMPLEMENTABLE` with reason "no test tooling detected in any project manifest". That is the correct signal for a repo with no test infrastructure. + +--- + +## Step 6 — Test implementability check (H) + +For each `#### Scenario:` body captured in Step 3, classify whether the scenario can be implemented as an automated test under the tooling fingerprint from Step 5. The classification is model-driven — no hard-coded category map. The vocabulary is exactly two verdicts. + +For each scenario, issue this prompt to the model and capture the response: + +> Project test tooling fingerprint: +> `{fingerprint from Step 5}` +> +> Acceptance scenario (verbatim from test-plan.md): +> ``` +> {scenario heading and full WHEN/THEN/AND body} +> ``` +> +> Can this scenario be implemented as an automated test using only the tooling listed in the fingerprint? Answer with one of: +> +> - `IMPLEMENTABLE` — every assertion in the scenario can be verified deterministically with the listed tooling. +> - `NOT_IMPLEMENTABLE` — at least one assertion requires capability not present in the fingerprint (e.g. browser automation, JS test runner, runtime DOM observation, wall-clock timing of UI animations, cross-screen JS state observation). +> +> Then on a new line, output one sentence stating the specific blocking assertion and the missing capability. No remediation, no recommendations — finding only. + +Parse the response: first non-empty line must start with `IMPLEMENTABLE` or `NOT_IMPLEMENTABLE`; subsequent text is the reason. If parsing fails, treat as `NOT_IMPLEMENTABLE` with reason "audit could not classify (malformed response)". + +Collect findings: + +- For each `NOT_IMPLEMENTABLE` scenario: → Finding: **Scenario not implementable in project tooling** | CRITICAL | `{scenario name} — {one-sentence reason}` + +- `IMPLEMENTABLE` scenarios emit no finding (consistent with Step 4's silent-pass convention). -If `spec.md` exists but contains no `**Scenario:**` labels at all: - → Finding: **spec.md contains no Scenario labels** | CRITICAL | every Acceptance - Scenario must carry a `**Scenario: [name]**` label to be traceable +The intent of this check is to prevent the planning stage from producing scenarios that will become skipped, deferred, or `NotImplementedException` stubs in the codebase. Resolution of a `NOT_IMPLEMENTABLE` finding is to edit `test-plan.md` — either remove the scenario or rewrite it as a testable proxy. Adding stubs to the codebase is not a legal resolution. --- -## Step 5 — Output findings table +## Step 7 — Output findings table Append the following section as the final output of this hook invocation. @@ -153,7 +177,7 @@ Severity legend: **CRITICAL** = blocks implementation, **WARNING** = should be r - Do NOT modify any source files - Do NOT suggest fixes inline — list findings only - Do NOT re-run `/speckit.analyze`; this command produces supplementary output only -- Scenario name matching is **exact** (case-sensitive after trim) — the same rule - `/speckit.testchecklist` applies to test code `// SCENARIO:` comments -- If `spec.md` does not exist, this is a CRITICAL finding (cannot perform the - cross-check at all); skip the dependent steps +- Scenario name matching is **exact** (case-sensitive after trim) — the same rule `/speckit.testchecklist` applies to test code `// SCENARIO:` comments +- If `spec.md` does not exist, this is a CRITICAL finding (cannot perform the cross-check at all); skip the dependent steps +- Step 6 (check H) does NOT recommend tooling additions or scenario rewrites — it reports only. Resolving a `NOT_IMPLEMENTABLE` finding is a planning-stage decision (edit `test-plan.md`), not an implementation-stage workaround (add a stub or skip) +- Step 6's verdict vocabulary is exactly two values (`IMPLEMENTABLE` / `NOT_IMPLEMENTABLE`). Do not introduce additional verdicts; concerns about test *value* or *tautology* belong to code review or `/speckit.testchecklist`, not here diff --git a/.specify/memory/constitution.md b/.specify/memory/constitution.md index 4afdfbc..c938b77 100644 --- a/.specify/memory/constitution.md +++ b/.specify/memory/constitution.md @@ -1,19 +1,21 @@ <!-- Sync Impact Report: -Version: 1.1.0 → 1.2.0 -Bump rationale: MINOR — Amendment Process gains a new procedural clause requiring -amendments that touch principles with downstream references in CLAUDE.md or -docs/conventions/ to note which downstream documents were reviewed. Materially -expands governance guidance; no existing principle redefined. +Version: 1.2.0 → 1.3.0 +Bump rationale: MINOR — adds Principle IX (Behavioural Specification) requiring +ACs and tests to describe outcomes rather than mechanisms. Materially expands +guidance and gives /speckit.analyze a new enforceable rule; no existing +principle redefined. Modified Principles: N/A -Added Sections: N/A (procedural clause added to existing Governance > Amendment Process) +Added Sections: Principle IX — Behavioural Specification (NON-NEGOTIABLE) Removed Sections: N/A -Templates Requiring Updates: - ✅ .specify/templates/plan-template.md — amendment-process clause does not affect plan structure - ✅ .specify/templates/spec-template.md — amendment-process clause does not affect spec structure - ✅ .specify/templates/tasks-template.md — amendment-process clause does not affect task structure - ⚠ No command files found in .specify/templates/commands/ +Downstream documents reviewed (per Amendment Process clause 4): + ✅ .claude/commands/speckit.draftissue.md — updated in lockstep (avoid/prefer table + independence test added to step 5) + ✅ .claude/commands/speckit.testplan.md — updated in lockstep (mechanism-vs-outcome section + table added before Format) + ✅ CLAUDE.md — reviewed; intentionally not duplicated (locality-over-DRY; existing "Don't test" block already covers ad-hoc test writing scope) + ✅ .specify/templates/plan-template.md — Principle IX does not affect plan structure + ✅ .specify/templates/spec-template.md — Principle IX consumed at AC drafting time via /speckit.draftissue, not in spec template + ✅ .specify/templates/tasks-template.md — housekeeping-belongs-in-tasks clarification already implicit Follow-up TODOs: None --> @@ -126,6 +128,23 @@ they are the traceability key linking acceptance criteria → test scenarios → **Rationale**: Consistent labels enable `/speckit.testchecklist` to verify end-to-end coverage automatically. Violations are flagged CRITICAL by `/speckit.analyze`. +### IX. Behavioural Specification (NON-NEGOTIABLE) + +Acceptance criteria and tests MUST describe outcomes an outside observer can verify, not the mechanism that delivers them. Multiple reasonable implementations of the same feature MUST satisfy the same ACs and pass the same tests. + +**The independence test**: would this AC (or test) still hold under a different reasonable implementation of the same feature? If no, it is describing the mechanism, not the outcome. + +**Critical Rules:** + +- ACs MUST be phrased as user-observable outcomes. Mechanism details MUST NOT appear in ACs, including: CSS classes, DOM IDs or element types, animation specifics, font names/weights/colours, framework or library picks, pixel measurements, timing values (Ns / Nms), polling cadences, specific URLs or ports, exact error message strings, and storage technology. +- Tests MUST verify the AC as written, not the chosen implementation. A test that would fail under a different reasonable implementation of the same AC is testing mechanism, not outcome. +- Project housekeeping (project exists, sln updated, scaffolding created) belongs in `tasks.md`, not in ACs. +- **Regression exception**: an AC or test that pins a specific mechanism is permitted only when it exists to prevent a named, previously-fixed bug. Reference the bug in the AC text, scenario name, or a one-line comment in the test so future readers understand why the coupling exists. + +**Rationale**: Mechanism-coupled ACs invite brittle, implementation-mirroring tests that lock the codebase to its current shape and make refactors expensive. Outcome-level ACs preserve the implementer's freedom to choose the simplest mechanism, keep the test suite meaningful through refactors, and give `/speckit.analyze` an enforceable rule rather than style guidance. + +**Downstream references**: detailed avoid/prefer guidance lives in `.claude/commands/speckit.draftissue.md` (AC drafting) and `.claude/commands/speckit.testplan.md` (test scenario authoring). Update those in lockstep with any change to this principle. + ## Development Workflow ### Git Workflow @@ -213,4 +232,4 @@ This constitution supersedes all other development practices and guides. All dev - Complexity MUST be justified against simplicity principles - For runtime development guidance, refer to `CLAUDE.md` -**Version**: 1.2.0 | **Ratified**: 2026-04-10 | **Last Amended**: 2026-05-12 +**Version**: 1.3.0 | **Ratified**: 2026-04-10 | **Last Amended**: 2026-05-16 From f2a4e06d36ff83db5a121040c1430758160c6759 Mon Sep 17 00:00:00 2001 From: Frank Ray <52075808+FrankRay78@users.noreply.github.com> Date: Sat, 16 May 2026 19:54:21 +0100 Subject: [PATCH 2/7] Upgrade speckit to 0.8.12.dev0 --- .claude/skills/speckit-analyze/SKILL.md | 10 +- .claude/skills/speckit-checklist/SKILL.md | 4 +- .claude/skills/speckit-clarify/SKILL.md | 10 +- .claude/skills/speckit-constitution/SKILL.md | 2 + .claude/skills/speckit-implement/SKILL.md | 5 +- .claude/skills/speckit-plan/SKILL.md | 12 +- .claude/skills/speckit-specify/SKILL.md | 14 +- .claude/skills/speckit-tasks/SKILL.md | 6 +- .claude/skills/speckit-taskstoissues/SKILL.md | 2 + .specify/init-options.json | 4 +- .specify/integration.json | 16 +- .specify/integrations/claude.manifest.json | 29 +- .specify/integrations/speckit.manifest.json | 17 +- .specify/scripts/powershell/common.ps1 | 382 +++++++++++++++++- .../scripts/powershell/create-new-feature.ps1 | 5 +- .specify/scripts/powershell/setup-plan.ps1 | 14 +- .specify/scripts/powershell/setup-tasks.ps1 | 74 ++++ .specify/templates/checklist-template.md | 4 +- .specify/templates/plan-template.md | 37 +- .specify/templates/spec-template.md | 13 +- .specify/templates/tasks-template.md | 11 +- .specify/workflows/speckit/workflow.yml | 77 ++++ .specify/workflows/workflow-registry.json | 13 + 23 files changed, 654 insertions(+), 107 deletions(-) create mode 100644 .specify/scripts/powershell/setup-tasks.ps1 create mode 100644 .specify/workflows/speckit/workflow.yml create mode 100644 .specify/workflows/workflow-registry.json diff --git a/.claude/skills/speckit-analyze/SKILL.md b/.claude/skills/speckit-analyze/SKILL.md index 2b75fce..deef419 100644 --- a/.claude/skills/speckit-analyze/SKILL.md +++ b/.claude/skills/speckit-analyze/SKILL.md @@ -29,6 +29,7 @@ You **MUST** consider the user input before proceeding (if not empty). - 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`): ``` @@ -55,13 +56,13 @@ You **MUST** consider the user input before proceeding (if not empty). ## 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`. +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`. +**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 @@ -197,9 +198,9 @@ Output a Markdown report (no file writes) with the following structure: At end of report, output a concise Next Actions block: -- If CRITICAL issues exist: Recommend resolving before `/speckit.implement` +- 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'" +- 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 @@ -214,6 +215,7 @@ After reporting, check if `.specify/extensions.yml` exists in the project root. - 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`): ``` diff --git a/.claude/skills/speckit-checklist/SKILL.md b/.claude/skills/speckit-checklist/SKILL.md index 4d5f87a..11142e3 100644 --- a/.claude/skills/speckit-checklist/SKILL.md +++ b/.claude/skills/speckit-checklist/SKILL.md @@ -50,6 +50,7 @@ You **MUST** consider the user input before proceeding (if not empty). - 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`): ``` @@ -255,7 +256,7 @@ You **MUST** consider the user input before proceeding (if not empty). - 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: +**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 @@ -347,6 +348,7 @@ Check if `.specify/extensions.yml` exists in the project root. - 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`): ``` diff --git a/.claude/skills/speckit-clarify/SKILL.md b/.claude/skills/speckit-clarify/SKILL.md index 9a6a83d..9d57899 100644 --- a/.claude/skills/speckit-clarify/SKILL.md +++ b/.claude/skills/speckit-clarify/SKILL.md @@ -29,6 +29,7 @@ You **MUST** consider the user input before proceeding (if not empty). - 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`): ``` @@ -57,7 +58,7 @@ You **MUST** consider the user input before proceeding (if not empty). 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. +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: @@ -65,7 +66,7 @@ Execution steps: - `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. + - 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). @@ -204,13 +205,13 @@ Execution steps: - Path to updated spec. - Sections touched (list names). - 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. + - If any Outstanding or Deferred remain, recommend whether to proceed to `/speckit-plan` or run `/speckit-clarify` again later post-plan. - Suggested next command. 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). +- 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"). @@ -229,6 +230,7 @@ Check if `.specify/extensions.yml` exists in the project root. - 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`): ``` diff --git a/.claude/skills/speckit-constitution/SKILL.md b/.claude/skills/speckit-constitution/SKILL.md index 4cb36db..ed5b634 100644 --- a/.claude/skills/speckit-constitution/SKILL.md +++ b/.claude/skills/speckit-constitution/SKILL.md @@ -29,6 +29,7 @@ You **MUST** consider the user input before proceeding (if not empty). - 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`): ``` @@ -132,6 +133,7 @@ Check if `.specify/extensions.yml` exists in the project root. - 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`): ``` diff --git a/.claude/skills/speckit-implement/SKILL.md b/.claude/skills/speckit-implement/SKILL.md index 1762ed4..b9a5858 100644 --- a/.claude/skills/speckit-implement/SKILL.md +++ b/.claude/skills/speckit-implement/SKILL.md @@ -29,6 +29,7 @@ You **MUST** consider the user input before proceeding (if not empty). - 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`): ``` @@ -94,6 +95,7 @@ You **MUST** consider the user input before proceeding (if not empty). - **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**: @@ -175,7 +177,7 @@ You **MUST** consider the user input before proceeding (if not empty). - Confirm the implementation follows the technical plan - Report final status with summary of completed work -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. +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. 10. **Check for extension hooks**: After completion validation, check if `.specify/extensions.yml` exists in the project root. - If it exists, read it and look for entries under the `hooks.after_implement` key @@ -184,6 +186,7 @@ Note: This command assumes a complete task breakdown exists in tasks.md. If task - 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`): ``` diff --git a/.claude/skills/speckit-plan/SKILL.md b/.claude/skills/speckit-plan/SKILL.md index 5d0344b..e1e0838 100644 --- a/.claude/skills/speckit-plan/SKILL.md +++ b/.claude/skills/speckit-plan/SKILL.md @@ -29,6 +29,7 @@ You **MUST** consider the user input before proceeding (if not empty). - 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`): ``` @@ -77,6 +78,7 @@ You **MUST** consider the user input before proceeding (if not empty). - 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`): ``` @@ -140,15 +142,11 @@ You **MUST** consider the user input before proceeding (if not empty). - Skip if project is purely internal (build scripts, one-off tools, etc.) 3. **Agent context update**: - - Run `.specify/scripts/powershell/update-agent-context.ps1 -AgentType claude` - - These scripts detect which AI agent is in use - - Update the appropriate agent-specific context file - - Add only new technology from current plan - - Preserve manual additions between markers + - 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, agent-specific file +**Output**: data-model.md, /contracts/*, quickstart.md, updated agent context file ## Key rules -- Use absolute paths +- 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 diff --git a/.claude/skills/speckit-specify/SKILL.md b/.claude/skills/speckit-specify/SKILL.md index 23620aa..5470173 100644 --- a/.claude/skills/speckit-specify/SKILL.md +++ b/.claude/skills/speckit-specify/SKILL.md @@ -29,6 +29,7 @@ You **MUST** consider the user input before proceeding (if not empty). - 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`): ``` @@ -55,7 +56,7 @@ You **MUST** consider the user input before proceeding (if not empty). ## 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. +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: @@ -101,10 +102,10 @@ Given that feature description, do this: } ``` 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. + 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 + - 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 @@ -175,7 +176,7 @@ Given that feature description, do this: ## Notes - - Items marked incomplete require spec updates before `/speckit.clarify` or `/speckit.plan` + - Items marked incomplete require spec updates before `/speckit-clarify` or `/speckit-plan` ``` b. **Run Validation Check**: Review the spec against each checklist item: @@ -184,7 +185,7 @@ Given that feature description, do this: c. **Handle Validation Results**: - - **If all items pass**: Mark checklist complete and proceed to step 7 + - **If all items pass**: Mark checklist complete and proceed to step 8 - **If items fail (excluding [NEEDS CLARIFICATION])**: 1. List the failing items and specific issues @@ -233,7 +234,7 @@ Given that feature description, do this: - `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`) + - Readiness for the next phase (`/speckit-clarify` or `/speckit-plan`) 9. **Check for extension hooks**: After reporting completion, check if `.specify/extensions.yml` exists in the project root. - If it exists, read it and look for entries under the `hooks.after_specify` key @@ -242,6 +243,7 @@ Given that feature description, do this: - 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`): ``` diff --git a/.claude/skills/speckit-tasks/SKILL.md b/.claude/skills/speckit-tasks/SKILL.md index 7d2ee3e..44b9c0c 100644 --- a/.claude/skills/speckit-tasks/SKILL.md +++ b/.claude/skills/speckit-tasks/SKILL.md @@ -29,6 +29,7 @@ You **MUST** consider the user input before proceeding (if not empty). - 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`): ``` @@ -55,7 +56,7 @@ You **MUST** consider the user input before proceeding (if not empty). ## Outline -1. **Setup**: Run `.specify/scripts/powershell/check-prerequisites.ps1 -Json` 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. **Setup**: Run `.specify/scripts/powershell/setup-tasks.ps1 -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) @@ -73,7 +74,7 @@ You **MUST** consider the user input before proceeding (if not empty). - Create parallel execution examples per user story - Validate task completeness (each user story has all needed tasks, independently testable) -4. **Generate tasks.md**: Use `.specify/templates/tasks-template.md` as structure, fill with: +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) @@ -101,6 +102,7 @@ You **MUST** consider the user input before proceeding (if not empty). - 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`): ``` diff --git a/.claude/skills/speckit-taskstoissues/SKILL.md b/.claude/skills/speckit-taskstoissues/SKILL.md index e8d447d..10cf576 100644 --- a/.claude/skills/speckit-taskstoissues/SKILL.md +++ b/.claude/skills/speckit-taskstoissues/SKILL.md @@ -29,6 +29,7 @@ You **MUST** consider the user input before proceeding (if not empty). - 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`): ``` @@ -81,6 +82,7 @@ Check if `.specify/extensions.yml` exists in the project root. - 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`): ``` diff --git a/.specify/init-options.json b/.specify/init-options.json index deffbb2..cb4e7f4 100644 --- a/.specify/init-options.json +++ b/.specify/init-options.json @@ -2,9 +2,9 @@ "ai": "claude", "ai_skills": true, "branch_numbering": "sequential", + "context_file": "CLAUDE.md", "here": true, "integration": "claude", - "preset": null, "script": "ps", - "speckit_version": "0.6.1.dev0" + "speckit_version": "0.8.12.dev0" } \ No newline at end of file diff --git a/.specify/integration.json b/.specify/integration.json index 3d0191d..8be331f 100644 --- a/.specify/integration.json +++ b/.specify/integration.json @@ -1,7 +1,15 @@ { + "version": "0.8.12.dev0", + "integration_state_schema": 1, + "installed_integrations": [ + "claude" + ], + "integration_settings": { + "claude": { + "script": "ps", + "invoke_separator": "-" + } + }, "integration": "claude", - "version": "0.6.1.dev0", - "scripts": { - "update-context": ".specify/integrations/claude/scripts/update-context.ps1" - } + "default_integration": "claude" } diff --git a/.specify/integrations/claude.manifest.json b/.specify/integrations/claude.manifest.json index 9f0d404..5c92687 100644 --- a/.specify/integrations/claude.manifest.json +++ b/.specify/integrations/claude.manifest.json @@ -1,23 +1,16 @@ { "integration": "claude", - "version": "0.6.1.dev0", - "installed_at": "2026-04-10T14:33:29.433243+00:00", + "version": "0.8.12.dev0", + "installed_at": "2026-05-16T18:45:42.840454+00:00", "files": { - ".claude/skills/speckit-analyze/SKILL.md": "d6cf317b6a64ebb5747ee6a4e2a401c3563d6b17a00ba900c1a80eb2ccb2afdd", - ".claude/skills/speckit-checklist/SKILL.md": "22fbfa259032be8dda93fd584a60ac3b58fc8c0561a1946be4160be5d2fcda82", - ".claude/skills/speckit-clarify/SKILL.md": "201ed6e841d604ef06eb13cc38d6c875724b66152e0429916f7d2ceca0fcbb99", - ".claude/skills/speckit-constitution/SKILL.md": "ed6fde3cff810d6540ca2f9347b35a2b60b056e76fc1dfb0871b396bad038285", - ".claude/skills/speckit-git-commit/SKILL.md": "c52d50dbd765eb748f97b91dd8229a1712253e314b5230f84545109afa8c13b0", - ".claude/skills/speckit-git-feature/SKILL.md": "58367e90321bb98eb793841c0c14876cb37ba4935ebe80386b4abe37f7a6235f", - ".claude/skills/speckit-git-initialize/SKILL.md": "da20e8c6933fce96a00ce708aa9c8fa40d510fdfdd0d401bcd99a8d66ed9da2e", - ".claude/skills/speckit-git-remote/SKILL.md": "c51b40b315a620c4422de939a55c98d47200acedf74bdf98083924b88ca21f99", - ".claude/skills/speckit-git-validate/SKILL.md": "441a00edd1b0329c70ee89ddafc0a7dcd29c5105fd1ac0b8b240de34b4171c7d", - ".claude/skills/speckit-implement/SKILL.md": "da049e25017a1e3c8548cbf5809ca5617b48fa66b6af14fa18860a908c1d41c1", - ".claude/skills/speckit-plan/SKILL.md": "9a09a9ba95ed5f571f10f2dda039c10cf3cf539be27f7e3884f0d902be94b71e", - ".claude/skills/speckit-specify/SKILL.md": "2c5770de5de53c6b2a72064a6d59e51aab0e64b7cc5e8dd4dfbc7667afecefe5", - ".claude/skills/speckit-tasks/SKILL.md": "4d28cdf0619b57bdc69f3b67da00cc23a622f63c04ef81cf84c3e84aaa5a24ef", - ".claude/skills/speckit-taskstoissues/SKILL.md": "6dee8e951fb637af7906071bbe0ff3b5f49820d94ae10a3484b8ca6ed2804f77", - ".specify/integrations/claude/scripts/update-context.ps1": "8bce5081fe27ebf414d4eaf127d91b5540b00d24dde4fe1e303e8eb26ad5211a", - ".specify/integrations/claude/scripts/update-context.sh": "21a5aa3fc644f693a29d35975ce21e5a949cdc1d0258b11c21940754c3644fa6" + ".claude/skills/speckit-analyze/SKILL.md": "adb9c4baed1d0e27cebd08e506a7247c1357ee721b6ea340201553d5792e6c8a", + ".claude/skills/speckit-checklist/SKILL.md": "812b2a102310470141b313670a51e7f33fe4f673a3b8c3d97ddff4abce2e5fbd", + ".claude/skills/speckit-clarify/SKILL.md": "0ca2d6dd6eae86d0e02149b1c3df0b7770e5e5dcfc0482f2b7176b12f6f6457d", + ".claude/skills/speckit-constitution/SKILL.md": "c1a044aba243ca6aff627fb5e4404feb6f1108d4f7dd174631bee3ae477d6c15", + ".claude/skills/speckit-implement/SKILL.md": "efeb6dc763bcf6ee5fe6732ce4d4243fe7c9c37f1db10ac39d8b0780ff9eebe0", + ".claude/skills/speckit-plan/SKILL.md": "5dbf517056a7df98de24835cb44c582b8d9b5c95950f917b129a740ca7f6448b", + ".claude/skills/speckit-specify/SKILL.md": "caadc05119eca453709a0425ed88d253883f9c55da4c13a4898367653a859483", + ".claude/skills/speckit-tasks/SKILL.md": "6a8ca4d9d9948e4f50ed7614cd5a0c743b510fd98d6791de554db8f53a0626ed", + ".claude/skills/speckit-taskstoissues/SKILL.md": "ccacc12041d1e27d3afffc7a189ac3d17444836e10eeb4e128b272b5e8ae45cb" } } diff --git a/.specify/integrations/speckit.manifest.json b/.specify/integrations/speckit.manifest.json index dbd33d0..47d13b1 100644 --- a/.specify/integrations/speckit.manifest.json +++ b/.specify/integrations/speckit.manifest.json @@ -1,18 +1,19 @@ { "integration": "speckit", - "version": "0.6.1.dev0", + "version": "0.8.12.dev0", "installed_at": "2026-04-10T14:33:29.472385+00:00", "files": { ".specify/scripts/powershell/check-prerequisites.ps1": "bcb37804b0757c37799b65a9321c1d3fb7b7ddcab6703c55c5b9a142c9166bf1", - ".specify/scripts/powershell/common.ps1": "a221d648081292e5d122c40cfd411fcb6d9b7ffe293d43c148f46228658702e6", - ".specify/scripts/powershell/create-new-feature.ps1": "84227d4cde2af8ae7c854a2cbfb9145325cfce1be67f8018a63febcff8b1a272", - ".specify/scripts/powershell/setup-plan.ps1": "c13ec3f33330e74aa088979c69bf35fe641301aaa50b2c1203c10ce94bcad338", + ".specify/scripts/powershell/common.ps1": "916b3ea1e29ef626a353f0ec011f906c6afff6818a97d560a0321b8557d970ec", + ".specify/scripts/powershell/create-new-feature.ps1": "0a7fc929db81b6318205ab3506187762bae1c4d3b2184f15e903ca54b78511e2", + ".specify/scripts/powershell/setup-plan.ps1": "137af47d79085d2770714ece307ed8f56f0d4cb1e9ec0c17db5c5c0d0b8a8c4c", ".specify/scripts/powershell/update-agent-context.ps1": "eac4ea87703a29835a3f416354bf29232cb60e81f16776669aa4aa28c23d7fc2", ".specify/templates/agent-file-template.md": "55ed438c2e861444ef22f45fe5238f3ebf0dc1cb6e53067d7232fbbf4ce82892", - ".specify/templates/checklist-template.md": "312eee8291dfa984b21f95ddd0ca778e7a1f0b3a64bfc470d79762a3e3f5d7b8", + ".specify/templates/checklist-template.md": "c37695297e5d3153d64f82c21223509940b13932046c7961c42d1d669516130c", ".specify/templates/constitution-template.md": "ce7549540fa45543cca797a150201d868e64495fdff39dc38246fb17bd4024b3", - ".specify/templates/plan-template.md": "873e84b226fe3d24afe28046931b20db9bbb9210366428dc958a515349ed6e68", - ".specify/templates/spec-template.md": "785dc50d856dd92d6515eca0761e16dce0c9ba0a3cd07154fd33eae77932422a", - ".specify/templates/tasks-template.md": "5da92ac1fbf5be2f9018a5064497995bf3592761ccb6b3951503c63d851297e8" + ".specify/templates/plan-template.md": "cc7f7979cf8d8836ec26492785affd80791d3422a2b745062ec695be8c985ef7", + ".specify/templates/spec-template.md": "3945437fc35cd30a5b2bf7beea680337c3516826d3efa5a6b92c4a7eca1ba28e", + ".specify/templates/tasks-template.md": "fc29a233f6f5a27ca31f1aa46b596af6500c627441c6e62b2bc4a1d721525842", + ".specify/scripts/powershell/setup-tasks.ps1": "aa7b19077dc4f6feee1dae47f68dc3e6d8ac75adf572a949443f3322b753cfa2" } } diff --git a/.specify/scripts/powershell/common.ps1 b/.specify/scripts/powershell/common.ps1 index 35ed884..ffc6d73 100644 --- a/.specify/scripts/powershell/common.ps1 +++ b/.specify/scripts/powershell/common.ps1 @@ -127,6 +127,16 @@ function Test-HasGit { } } +# 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. +function Get-SpecKitEffectiveBranchName { + param([string]$Branch) + if ($Branch -match '^([^/]+)/([^/]+)$') { + return $Matches[2] + } + return $Branch +} + function Test-FeatureBranch { param( [string]$Branch, @@ -138,22 +148,137 @@ function Test-FeatureBranch { Write-Warning "[specify] Warning: Git repository not detected; skipped branch validation" return $true } + + $raw = $Branch + $Branch = Get-SpecKitEffectiveBranchName $raw # Accept sequential prefix (3+ digits) but exclude malformed timestamps # Malformed: 7-or-8 digit date + 6-digit time with no trailing slug (e.g. "2026031-143022" or "20260319-143022") $hasMalformedTimestamp = ($Branch -match '^[0-9]{7}-[0-9]{6}-') -or ($Branch -match '^(?:\d{7}|\d{8})-\d{6}$') $isSequential = ($Branch -match '^[0-9]{3,}-') -and (-not $hasMalformedTimestamp) if (-not $isSequential -and $Branch -notmatch '^\d{8}-\d{6}-') { - Write-Output "ERROR: Not on a feature branch. Current branch: $Branch" - Write-Output "Feature branches should be named like: 001-feature-name, 1234-feature-name, or 20260319-143022-feature-name" + [Console]::Error.WriteLine("ERROR: Not on a feature branch. Current branch: $raw") + [Console]::Error.WriteLine("Feature branches should be named like: 001-feature-name, 1234-feature-name, or 20260319-143022-feature-name") return $false } return $true } -function Get-FeatureDir { - param([string]$RepoRoot, [string]$Branch) - Join-Path $RepoRoot "specs/$Branch" +# True when .specify/feature.json pins an existing feature directory that matches the +# active FEATURE_DIR from Get-FeaturePathsEnv (so /speckit.plan can skip git branch pattern checks). +function Test-FeatureJsonMatchesFeatureDir { + param( + [Parameter(Mandatory = $true)][string]$RepoRoot, + [Parameter(Mandatory = $true)][string]$ActiveFeatureDir + ) + + $featureJson = Join-Path (Join-Path $RepoRoot '.specify') 'feature.json' + if (-not (Test-Path -LiteralPath $featureJson -PathType Leaf)) { + return $false + } + + try { + $raw = Get-Content -LiteralPath $featureJson -Raw + $cfg = $raw | ConvertFrom-Json + } catch { + return $false + } + + $fd = $cfg.feature_directory + if ([string]::IsNullOrWhiteSpace([string]$fd)) { + return $false + } + + if (-not [System.IO.Path]::IsPathRooted($fd)) { + $fd = Join-Path $RepoRoot $fd + } + + if (-not (Test-Path -LiteralPath $fd -PathType Container)) { + return $false + } + + # Resolve both paths to canonical absolute form. Prefer Resolve-Path (follows + # symlinks and is the canonical PS way); fall back to [Path]::GetFullPath when + # Resolve-Path can't produce a value. Mirrors the pattern used by Find-SpecifyRoot. + $resolvedJson = Resolve-Path -LiteralPath $fd -ErrorAction SilentlyContinue + if ($resolvedJson) { + $normJson = $resolvedJson.Path + } else { + $normJson = [System.IO.Path]::GetFullPath($fd) + } + + $resolvedActive = Resolve-Path -LiteralPath $ActiveFeatureDir -ErrorAction SilentlyContinue + if ($resolvedActive) { + $normActive = $resolvedActive.Path + } else { + $normActive = [System.IO.Path]::GetFullPath($ActiveFeatureDir) + } + + # Use case-insensitive compare only on Windows; POSIX filesystems are case-sensitive. + # PowerShell 5.1 is Windows-only and does not define $IsWindows, so treat its + # absence as "we're on Windows". + if ($null -ne $IsWindows) { + $onWindows = $IsWindows + } else { + $onWindows = $true + } + + if ($onWindows) { + $comparison = [System.StringComparison]::OrdinalIgnoreCase + } else { + $comparison = [System.StringComparison]::Ordinal + } + + return [string]::Equals($normJson, $normActive, $comparison) +} + +# Resolve specs/<feature-dir> by numeric/timestamp prefix (mirrors scripts/bash/common.sh find_feature_dir_by_prefix). +function Find-FeatureDirByPrefix { + param( + [Parameter(Mandatory = $true)][string]$RepoRoot, + [Parameter(Mandatory = $true)][string]$Branch + ) + $specsDir = Join-Path $RepoRoot 'specs' + $branchName = Get-SpecKitEffectiveBranchName $Branch + + $prefix = $null + if ($branchName -match '^(\d{8}-\d{6})-') { + $prefix = $Matches[1] + } elseif ($branchName -match '^(\d{3,})-') { + $prefix = $Matches[1] + } else { + return (Join-Path $specsDir $branchName) + } + + $dirMatches = @() + if (Test-Path -LiteralPath $specsDir -PathType Container) { + $dirMatches = @(Get-ChildItem -LiteralPath $specsDir -Filter "$prefix-*" -Directory -ErrorAction SilentlyContinue) + } + + if ($dirMatches.Count -eq 0) { + return (Join-Path $specsDir $branchName) + } + if ($dirMatches.Count -eq 1) { + return $dirMatches[0].FullName + } + $names = ($dirMatches | ForEach-Object { $_.Name }) -join ' ' + [Console]::Error.WriteLine("ERROR: Multiple spec directories found with prefix '$prefix': $names") + [Console]::Error.WriteLine('Please ensure only one spec directory exists per prefix.') + return $null +} + +# Branch-based prefix resolution; mirrors bash get_feature_paths failure (stderr + exit 1). +function Get-FeatureDirFromBranchPrefixOrExit { + param( + [Parameter(Mandatory = $true)][string]$RepoRoot, + [Parameter(Mandatory = $true)][string]$CurrentBranch + ) + $resolved = Find-FeatureDirByPrefix -RepoRoot $RepoRoot -Branch $CurrentBranch + if ($null -eq $resolved) { + [Console]::Error.WriteLine('ERROR: Failed to resolve feature directory') + exit 1 + } + return $resolved } function Get-FeaturePathsEnv { @@ -164,7 +289,7 @@ function Get-FeaturePathsEnv { # Resolve feature directory. Priority: # 1. SPECIFY_FEATURE_DIRECTORY env var (explicit override) # 2. .specify/feature.json "feature_directory" key (persisted by /speckit.specify) - # 3. Exact branch-to-directory mapping via Get-FeatureDir (legacy fallback) + # 3. Branch-name-based prefix lookup (same as scripts/bash/common.sh) $featureJson = Join-Path $repoRoot '.specify/feature.json' if ($env:SPECIFY_FEATURE_DIRECTORY) { $featureDir = $env:SPECIFY_FEATURE_DIRECTORY @@ -173,22 +298,24 @@ function Get-FeaturePathsEnv { $featureDir = Join-Path $repoRoot $featureDir } } elseif (Test-Path $featureJson) { + $featureJsonRaw = Get-Content -LiteralPath $featureJson -Raw try { - $featureConfig = Get-Content $featureJson -Raw | ConvertFrom-Json - if ($featureConfig.feature_directory) { - $featureDir = $featureConfig.feature_directory - # Normalize relative paths to absolute under repo root - if (-not [System.IO.Path]::IsPathRooted($featureDir)) { - $featureDir = Join-Path $repoRoot $featureDir - } - } else { - $featureDir = Get-FeatureDir -RepoRoot $repoRoot -Branch $currentBranch - } + $featureConfig = $featureJsonRaw | ConvertFrom-Json } catch { - $featureDir = Get-FeatureDir -RepoRoot $repoRoot -Branch $currentBranch + [Console]::Error.WriteLine("ERROR: Failed to parse .specify/feature.json: $_") + exit 1 + } + if ($featureConfig.feature_directory) { + $featureDir = $featureConfig.feature_directory + # Normalize relative paths to absolute under repo root + if (-not [System.IO.Path]::IsPathRooted($featureDir)) { + $featureDir = Join-Path $repoRoot $featureDir + } + } else { + $featureDir = Get-FeatureDirFromBranchPrefixOrExit -RepoRoot $repoRoot -CurrentBranch $currentBranch } } else { - $featureDir = Get-FeatureDir -RepoRoot $repoRoot -Branch $currentBranch + $featureDir = Get-FeatureDirFromBranchPrefixOrExit -RepoRoot $repoRoot -CurrentBranch $currentBranch } [PSCustomObject]@{ @@ -228,6 +355,21 @@ function Test-DirHasFiles { } } +# Find a usable Python 3 executable (python3, python, or py -3). +# Returns the command/arguments as an array, or $null if none found. +function Get-Python3Command { + if (Get-Command python3 -ErrorAction SilentlyContinue) { return @('python3') } + if (Get-Command python -ErrorAction SilentlyContinue) { + $ver = & python --version 2>&1 + if ($ver -match 'Python 3') { return @('python') } + } + if (Get-Command py -ErrorAction SilentlyContinue) { + $ver = & py -3 --version 2>&1 + if ($ver -match 'Python 3') { return @('py', '-3') } + } + return $null +} + # 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) @@ -256,6 +398,7 @@ function Resolve-Template { $presets = $registryData.presets if ($presets) { $sortedPresets = $presets.PSObject.Properties | + Where-Object { $null -eq $_.Value.enabled -or $_.Value.enabled -ne $false } | Sort-Object { if ($null -ne $_.Value.priority) { $_.Value.priority } else { 10 } } | ForEach-Object { $_.Name } } @@ -295,3 +438,206 @@ function Resolve-Template { return $null } +# 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. +function Resolve-TemplateContent { + param( + [Parameter(Mandatory=$true)][string]$TemplateName, + [Parameter(Mandatory=$true)][string]$RepoRoot + ) + + $base = Join-Path $RepoRoot '.specify/templates' + + # Collect all layers (highest priority first) + $layerPaths = @() + $layerStrategies = @() + + # Priority 1: Project overrides (always "replace") + $override = Join-Path $base "overrides/$TemplateName.md" + if (Test-Path $override) { + $layerPaths += $override + $layerStrategies += 'replace' + } + + # Priority 2: Installed presets (sorted by priority from .registry) + $presetsDir = Join-Path $RepoRoot '.specify/presets' + if (Test-Path $presetsDir) { + $registryFile = Join-Path $presetsDir '.registry' + $sortedPresets = @() + if (Test-Path $registryFile) { + try { + $registryData = Get-Content $registryFile -Raw | ConvertFrom-Json + $presets = $registryData.presets + if ($presets) { + $sortedPresets = $presets.PSObject.Properties | + Where-Object { $null -eq $_.Value.enabled -or $_.Value.enabled -ne $false } | + Sort-Object { if ($null -ne $_.Value.priority) { $_.Value.priority } else { 10 } } | + ForEach-Object { $_.Name } + } + } catch { + $sortedPresets = @() + } + } + + if ($sortedPresets.Count -gt 0) { + $pyCmd = Get-Python3Command + if (-not $pyCmd) { + # Check if any preset has strategy fields that would be ignored + foreach ($pid in $sortedPresets) { + $mf = Join-Path $presetsDir "$pid/preset.yml" + if ((Test-Path $mf) -and (Select-String -Path $mf -Pattern 'strategy:' -Quiet -ErrorAction SilentlyContinue)) { + Write-Warning "No Python 3 found; preset composition strategies will be ignored" + break + } + } + } + $yamlWarned = $false + foreach ($presetId in $sortedPresets) { + # Read strategy and file path from preset manifest + $strategy = 'replace' + $manifestFilePath = '' + $manifest = Join-Path $presetsDir "$presetId/preset.yml" + if ((Test-Path $manifest) -and $pyCmd) { + try { + # Use Python to parse YAML manifest for strategy and file path + $pyArgs = if ($pyCmd.Count -gt 1) { $pyCmd[1..($pyCmd.Count-1)] } else { @() } + $pyStderrFile = [System.IO.Path]::GetTempFileName() + $stratResult = & $pyCmd[0] @pyArgs -c @" +import sys +try: + import yaml +except ImportError: + print('yaml_missing', file=sys.stderr) + print('replace\t') + sys.exit(0) +try: + with open(sys.argv[1]) as f: + data = yaml.safe_load(f) + for t in data.get('provides', {}).get('templates', []): + if t.get('name') == sys.argv[2] 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') +"@ $manifest $TemplateName 2>$pyStderrFile + if ($stratResult) { + $parts = $stratResult.Trim() -split "`t", 2 + $strategy = $parts[0].ToLowerInvariant() + if ($parts.Count -gt 1 -and $parts[1]) { $manifestFilePath = $parts[1] } + } + if (-not $yamlWarned -and (Test-Path $pyStderrFile) -and (Get-Content $pyStderrFile -Raw -ErrorAction SilentlyContinue) -match 'yaml_missing') { + Write-Warning "PyYAML not available; composition strategies may be ignored" + $yamlWarned = $true + } + Remove-Item $pyStderrFile -Force -ErrorAction SilentlyContinue + } catch { + $strategy = 'replace' + if ($pyStderrFile) { Remove-Item $pyStderrFile -Force -ErrorAction SilentlyContinue } + } + } + # Try manifest file path first, then convention path + $candidate = $null + if ($manifestFilePath) { + # Reject absolute paths and parent traversal + if ([System.IO.Path]::IsPathRooted($manifestFilePath) -or $manifestFilePath -match '\.\.[\\/]') { + $manifestFilePath = '' + } + } + if ($manifestFilePath) { + $mf = Join-Path $presetsDir "$presetId/$manifestFilePath" + if (Test-Path $mf) { $candidate = $mf } + } + if (-not $candidate) { + $cf = Join-Path $presetsDir "$presetId/templates/$TemplateName.md" + if (Test-Path $cf) { $candidate = $cf } + } + if ($candidate) { + $layerPaths += $candidate + $layerStrategies += $strategy + } + } + } else { + # Fallback: alphabetical directory order (no registry or parse failure) + foreach ($preset in Get-ChildItem -Path $presetsDir -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -notlike '.*' }) { + $candidate = Join-Path $preset.FullName "templates/$TemplateName.md" + if (Test-Path $candidate) { + $layerPaths += $candidate + $layerStrategies += 'replace' + } + } + } + } + + # Priority 3: Extension-provided templates (always "replace") + $extDir = Join-Path $RepoRoot '.specify/extensions' + if (Test-Path $extDir) { + foreach ($ext in Get-ChildItem -Path $extDir -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -notlike '.*' } | Sort-Object Name) { + $candidate = Join-Path $ext.FullName "templates/$TemplateName.md" + if (Test-Path $candidate) { + $layerPaths += $candidate + $layerStrategies += 'replace' + } + } + } + + # Priority 4: Core templates (always "replace") + $core = Join-Path $base "$TemplateName.md" + if (Test-Path $core) { + $layerPaths += $core + $layerStrategies += 'replace' + } + + if ($layerPaths.Count -eq 0) { return $null } + + # If the top (highest-priority) layer is replace, it wins entirely — + # lower layers are irrelevant regardless of their strategies. + if ($layerStrategies[0] -eq 'replace') { + return (Get-Content $layerPaths[0] -Raw) + } + + # Check if any layer uses a non-replace strategy + $hasComposition = $false + foreach ($s in $layerStrategies) { + if ($s -ne 'replace') { $hasComposition = $true; break } + } + + if (-not $hasComposition) { + return (Get-Content $layerPaths[0] -Raw) + } + + # Find the effective base: scan from highest priority (index 0) downward + # to find the nearest replace layer. Only compose layers above that base. + $baseIdx = -1 + for ($i = 0; $i -lt $layerPaths.Count; $i++) { + if ($layerStrategies[$i] -eq 'replace') { + $baseIdx = $i + break + } + } + if ($baseIdx -lt 0) { return $null } + + $content = Get-Content $layerPaths[$baseIdx] -Raw + + for ($i = $baseIdx - 1; $i -ge 0; $i--) { + $path = $layerPaths[$i] + $strat = $layerStrategies[$i] + $layerContent = Get-Content $path -Raw + + switch ($strat) { + 'replace' { $content = $layerContent } + 'prepend' { $content = "$layerContent`n`n$content" } + 'append' { $content = "$content`n`n$layerContent" } + 'wrap' { + if (-not $layerContent.Contains('{CORE_TEMPLATE}')) { + throw "Wrap strategy missing {CORE_TEMPLATE} placeholder" + } + $content = $layerContent.Replace('{CORE_TEMPLATE}', $content) + } + default { throw "Unknown strategy: $strat" } + } + } + + return $content +} \ No newline at end of file diff --git a/.specify/scripts/powershell/create-new-feature.ps1 b/.specify/scripts/powershell/create-new-feature.ps1 index 2f23283..2835c3e 100644 --- a/.specify/scripts/powershell/create-new-feature.ps1 +++ b/.specify/scripts/powershell/create-new-feature.ps1 @@ -350,7 +350,10 @@ if (-not $DryRun) { if (-not (Test-Path -PathType Leaf $specFile)) { $template = Resolve-Template -TemplateName 'spec-template' -RepoRoot $repoRoot if ($template -and (Test-Path $template)) { - Copy-Item $template $specFile -Force + # Read the template content and write it to the spec file with UTF-8 encoding without BOM + $content = [System.IO.File]::ReadAllText($template) + $utf8NoBom = New-Object System.Text.UTF8Encoding($false) + [System.IO.File]::WriteAllText($specFile, $content, $utf8NoBom) } else { New-Item -ItemType File -Path $specFile -Force | Out-Null } diff --git a/.specify/scripts/powershell/setup-plan.ps1 b/.specify/scripts/powershell/setup-plan.ps1 index ee09094..cd9cf8c 100644 --- a/.specify/scripts/powershell/setup-plan.ps1 +++ b/.specify/scripts/powershell/setup-plan.ps1 @@ -23,9 +23,11 @@ if ($Help) { # Get all paths and variables from common functions $paths = Get-FeaturePathsEnv -# Check if we're on a proper feature branch (only for git repos) -if (-not (Test-FeatureBranch -Branch $paths.CURRENT_BRANCH -HasGit $paths.HAS_GIT)) { - exit 1 +# If feature.json pins an existing feature directory, branch naming is not required. +if (-not (Test-FeatureJsonMatchesFeatureDir -RepoRoot $paths.REPO_ROOT -ActiveFeatureDir $paths.FEATURE_DIR)) { + if (-not (Test-FeatureBranch -Branch $paths.CURRENT_BRANCH -HasGit $paths.HAS_GIT)) { + exit 1 + } } # Ensure the feature directory exists @@ -34,8 +36,10 @@ New-Item -ItemType Directory -Path $paths.FEATURE_DIR -Force | Out-Null # Copy plan template if it exists, otherwise note it or create empty file $template = Resolve-Template -TemplateName 'plan-template' -RepoRoot $paths.REPO_ROOT if ($template -and (Test-Path $template)) { - Copy-Item $template $paths.IMPL_PLAN -Force - Write-Output "Copied plan template to $($paths.IMPL_PLAN)" + # Read the template content and write it to the implementation plan file with UTF-8 encoding without BOM + $content = [System.IO.File]::ReadAllText($template) + $utf8NoBom = New-Object System.Text.UTF8Encoding($false) + [System.IO.File]::WriteAllText($paths.IMPL_PLAN, $content, $utf8NoBom) } else { Write-Warning "Plan template not found" # Create a basic plan file if template doesn't exist diff --git a/.specify/scripts/powershell/setup-tasks.ps1 b/.specify/scripts/powershell/setup-tasks.ps1 new file mode 100644 index 0000000..e00ae7a --- /dev/null +++ b/.specify/scripts/powershell/setup-tasks.ps1 @@ -0,0 +1,74 @@ +#!/usr/bin/env pwsh + +[CmdletBinding()] +param( + [switch]$Json, + [switch]$Help +) + +$ErrorActionPreference = 'Stop' + +if ($Help) { + Write-Output "Usage: setup-tasks.ps1 [-Json] [-Help]" + exit 0 +} + +# Source common functions +. "$PSScriptRoot/common.ps1" + +# Get feature paths and validate branch +$paths = Get-FeaturePathsEnv + +# If feature.json pins an existing feature directory, branch naming is not required. +if (-not (Test-FeatureJsonMatchesFeatureDir -RepoRoot $paths.REPO_ROOT -ActiveFeatureDir $paths.FEATURE_DIR)) { + if (-not (Test-FeatureBranch -Branch $paths.CURRENT_BRANCH -HasGit $paths.HAS_GIT)) { + exit 1 + } +} + +if (-not (Test-Path $paths.IMPL_PLAN -PathType Leaf)) { + [Console]::Error.WriteLine("ERROR: plan.md not found in $($paths.FEATURE_DIR)") + [Console]::Error.WriteLine("Run /speckit.plan first to create the implementation plan.") + exit 1 +} + +if (-not (Test-Path $paths.FEATURE_SPEC -PathType Leaf)) { + [Console]::Error.WriteLine("ERROR: spec.md not found in $($paths.FEATURE_DIR)") + [Console]::Error.WriteLine("Run /speckit.specify first to create the feature structure.") + exit 1 +} + +# Build available docs list +$docs = @() +if (Test-Path $paths.RESEARCH) { $docs += 'research.md' } +if (Test-Path $paths.DATA_MODEL) { $docs += 'data-model.md' } +if ((Test-Path $paths.CONTRACTS_DIR) -and (Get-ChildItem -Path $paths.CONTRACTS_DIR -ErrorAction SilentlyContinue | Select-Object -First 1)) { + $docs += 'contracts/' +} +if (Test-Path $paths.QUICKSTART) { $docs += 'quickstart.md' } + +# Resolve tasks template through override stack +$tasksTemplate = Resolve-Template -TemplateName 'tasks-template' -RepoRoot $paths.REPO_ROOT +if (-not $tasksTemplate -or -not (Test-Path -LiteralPath $tasksTemplate -PathType Leaf)) { + $expectedCoreTemplate = Join-Path $paths.REPO_ROOT '.specify/templates/tasks-template.md' + [Console]::Error.WriteLine("ERROR: Tasks template not found for repository root: $($paths.REPO_ROOT)`nTemplate resolution order: overrides -> presets -> extensions -> core.`nExpected shared/core template location: $expectedCoreTemplate`nTo continue, verify whether 'tasks-template.md' is available in '.specify/templates/overrides/', preset templates, extension templates, or restore the shared/core templates (for example by re-running 'specify init') so that '.specify/templates/tasks-template.md' exists.") + exit 1 +} +$tasksTemplate = (Resolve-Path -LiteralPath $tasksTemplate).Path + +# Output results +if ($Json) { + [PSCustomObject]@{ + FEATURE_DIR = $paths.FEATURE_DIR + AVAILABLE_DOCS = $docs + TASKS_TEMPLATE = $tasksTemplate + } | ConvertTo-Json -Compress +} else { + Write-Output "FEATURE_DIR: $($paths.FEATURE_DIR)" + Write-Output "TASKS_TEMPLATE: $(if ($tasksTemplate) { $tasksTemplate } else { 'not found' })" + Write-Output "AVAILABLE_DOCS:" + Test-FileExists -Path $paths.RESEARCH -Description 'research.md' | Out-Null + Test-FileExists -Path $paths.DATA_MODEL -Description 'data-model.md' | Out-Null + Test-DirHasFiles -Path $paths.CONTRACTS_DIR -Description 'contracts/' | Out-Null + Test-FileExists -Path $paths.QUICKSTART -Description 'quickstart.md' | Out-Null +} diff --git a/.specify/templates/checklist-template.md b/.specify/templates/checklist-template.md index 806657d..c4aa166 100644 --- a/.specify/templates/checklist-template.md +++ b/.specify/templates/checklist-template.md @@ -4,13 +4,13 @@ **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. +**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: + 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 diff --git a/.specify/templates/plan-template.md b/.specify/templates/plan-template.md index 5a2fafe..92b96c7 100644 --- a/.specify/templates/plan-template.md +++ b/.specify/templates/plan-template.md @@ -1,9 +1,10 @@ # 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. +**Note**: This template is filled in by the `/speckit-plan` command. See `.specify/templates/plan-template.md` for the execution workflow. ## Summary @@ -17,14 +18,22 @@ 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] +**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] + +**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 @@ -39,12 +48,12 @@ ```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) +├── 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) diff --git a/.specify/templates/spec-template.md b/.specify/templates/spec-template.md index 4581e40..ceb2877 100644 --- a/.specify/templates/spec-template.md +++ b/.specify/templates/spec-template.md @@ -1,8 +1,11 @@ # Feature Specification: [FEATURE NAME] -**Feature Branch**: `[###-feature-name]` -**Created**: [DATE] -**Status**: Draft +**Feature Branch**: `[###-feature-name]` + +**Created**: [DATE] + +**Status**: Draft + **Input**: User description: "$ARGUMENTS" ## User Scenarios & Testing *(mandatory)* @@ -11,7 +14,7 @@ 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 @@ -85,7 +88,7 @@ ### 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-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"] diff --git a/.specify/templates/tasks-template.md b/.specify/templates/tasks-template.md index 60f9be4..d46a1f1 100644 --- a/.specify/templates/tasks-template.md +++ b/.specify/templates/tasks-template.md @@ -6,6 +6,7 @@ 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. @@ -25,21 +26,21 @@ description: "Task list template for feature implementation" - **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: + + 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. ============================================================================ --> diff --git a/.specify/workflows/speckit/workflow.yml b/.specify/workflows/speckit/workflow.yml new file mode 100644 index 0000000..f69efea --- /dev/null +++ b/.specify/workflows/speckit/workflow.yml @@ -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 }}" diff --git a/.specify/workflows/workflow-registry.json b/.specify/workflows/workflow-registry.json new file mode 100644 index 0000000..f743d88 --- /dev/null +++ b/.specify/workflows/workflow-registry.json @@ -0,0 +1,13 @@ +{ + "schema_version": "1.0", + "workflows": { + "speckit": { + "name": "Full SDD Cycle", + "version": "1.0.0", + "description": "Runs specify \u2192 plan \u2192 tasks \u2192 implement with review gates", + "source": "bundled", + "installed_at": "2026-05-16T18:45:43.234673+00:00", + "updated_at": "2026-05-16T18:45:43.234688+00:00" + } + } +} \ No newline at end of file From 09afa6a7bb2f9d31db4da76071e191660b46f1cd Mon Sep 17 00:00:00 2001 From: Frank Ray <52075808+FrankRay78@users.noreply.github.com> Date: Sat, 16 May 2026 21:06:34 +0100 Subject: [PATCH 3/7] spec-template.md overrride --- .specify/templates/overrides/README.md | 21 +++ .specify/templates/overrides/spec-template.md | 144 ++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 .specify/templates/overrides/README.md create mode 100644 .specify/templates/overrides/spec-template.md diff --git a/.specify/templates/overrides/README.md b/.specify/templates/overrides/README.md new file mode 100644 index 0000000..0ed3033 --- /dev/null +++ b/.specify/templates/overrides/README.md @@ -0,0 +1,21 @@ +# Template Overrides + +Files in this directory replace the equivalent files in `.specify/templates/` +when speckit reads templates. They sit at the **top** of the resolution stack +and are never touched by `specify init --here --force` upgrades. + +Resolution order (highest → lowest precedence): + +1. `.specify/templates/overrides/` ← here +2. `.specify/presets/<id>/` +3. `.specify/extensions/<id>/` +4. `.specify/templates/` ← core (overwritten by `--force`) + +## Current overrides + +| File | Why IMS overrides it | +|------|----------------------| +| `spec-template.md` | Bakes in the `**Scenario: [name]**` label convention required by [Constitution principle VIII](../../memory/constitution.md) for AC-to-test traceability. | + +To add a new override: drop a file with the same name as the core template +into this directory. To stop overriding: delete the file (core wins again). diff --git a/.specify/templates/overrides/spec-template.md b/.specify/templates/overrides/spec-template.md new file mode 100644 index 0000000..2f60e2b --- /dev/null +++ b/.specify/templates/overrides/spec-template.md @@ -0,0 +1,144 @@ +# 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**: + +<!-- + CONVENTION (Constitution principle VIII — AC-to-test traceability): + Every scenario MUST carry a `**Scenario: [Descriptive name]**` label. + The label MUST match the `#### Scenario: [Descriptive name]` header in test-plan.md + exactly — this is the traceability key linking AC → test scenario → test code. + `/speckit-analyze` will flag mismatches as CRITICAL. +--> + +**Scenario: [Descriptive name]** +Given [initial state], When [action], Then [expected outcome] + +**Scenario: [Another descriptive name]** +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**: + +**Scenario: [Descriptive name]** +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**: + +**Scenario: [Descriptive name]** +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"] From a89a7db8852e8a4754d515a8de085e5b2f47445b Mon Sep 17 00:00:00 2001 From: Frank Ray <52075808+FrankRay78@users.noreply.github.com> Date: Sat, 16 May 2026 21:12:29 +0100 Subject: [PATCH 4/7] Add wsl isolated sandbox setup notes --- docs/wsl-claude-sandbox.md | 278 +++++++++++++++++++++++++++++++++++++ 1 file changed, 278 insertions(+) create mode 100644 docs/wsl-claude-sandbox.md diff --git a/docs/wsl-claude-sandbox.md b/docs/wsl-claude-sandbox.md new file mode 100644 index 0000000..58bf8f2 --- /dev/null +++ b/docs/wsl-claude-sandbox.md @@ -0,0 +1,278 @@ +# Isolated WSL Sandbox for Claude Code + +A WSL2 Ubuntu instance configured for running `claude --dangerously-skip-permissions` with meaningful isolation from the Windows host: separate filesystem, no Windows PATH inheritance, read-only access to Windows files, no LAN reachability, sandbox-only SSH credentials. + +This manual covers the happy path only. Adapt commands to your own usernames, paths, and project repos. + +--- + +## Prerequisites + +- Windows 10 (version 2004+) or Windows 11 with WSL2 installed and updated (`wsl --update`). +- VS Code installed on Windows with the **WSL** extension (publisher: Microsoft). +- A GitHub account if cloning private repos or pushing. + +--- + +## Step 1: Create a dedicated Ubuntu instance + +Why: keeps the sandbox separate from your main dev environment, so a misbehaving Claude session can't trash your real work or credentials. + +1. Download the Ubuntu 24.04 WSL image from <https://releases.ubuntu.com/noble/> — pick the file ending in `.wsl`. + +2. From **PowerShell** (any window, not WSL): + ```powershell + mkdir C:\WSL\Ubuntu-Claude + wsl --install --from-file C:\path\to\downloaded.wsl --name Ubuntu-Claude + ``` + +3. Launch and create your user account when prompted: + ```powershell + wsl -d Ubuntu-Claude + ``` + +--- + +## Step 2: Configure isolation in `/etc/wsl.conf` + +Why: turns off Windows PATH inheritance (so the sandbox can't run `notepad.exe` etc. by name), mounts Windows drives read-only (so Claude can't write to or delete Windows files), and gives the instance a distinct hostname. + +Inside the sandbox: + +```bash +sudo nano /etc/wsl.conf +``` + +Replace the contents with (substitute your username for `frankray`): + +```ini +[boot] +systemd=true + +[user] +default=frankray + +[automount] +enabled = true +mountFsTab = false +options = "metadata,uid=1000,gid=1000,umask=022,fmask=033,ro" + +[interop] +enabled = true +appendWindowsPath = false + +[network] +hostname = claude-sandbox +generateHosts = true +generateResolvConf = true +``` + +Note: `interop.enabled = true` is required for VS Code's WSL extension to install its server. `appendWindowsPath = false` still prevents Claude from running Windows binaries by name. + +Apply by exiting and running from PowerShell: + +```powershell +wsl --shutdown +``` + +Wait 30 seconds, then `wsl -d Ubuntu-Claude`. + +--- + +## Step 3: Unmount unwanted drives via systemd + +Why: `automount.enabled = true` mounts all Windows drives. If you have drives with sensitive data (Dropbox folders, source repos with secrets, personal documents), unmount them so Claude can't read them. + +Create a systemd unit (substitute your drive letter for `d`): + +```bash +sudo nano /etc/systemd/system/unmount-mnt-d.service +``` + +Paste: + +```ini +[Unit] +Description=Unmount /mnt/d to keep it invisible to the sandbox +After=local-fs.target +ConditionPathIsMountPoint=/mnt/d + +[Service] +Type=oneshot +ExecStart=/bin/umount /mnt/d +RemainAfterExit=no + +[Install] +WantedBy=multi-user.target +``` + +Enable: + +```bash +sudo systemctl daemon-reload +sudo systemctl enable unmount-mnt-d.service +``` + +From PowerShell, `wsl --shutdown` and reopen. Verify with `ls /mnt/` — only desired drives should appear with content. + +Repeat the unit for each additional drive you want unmounted. + +--- + +## Step 4: Network firewall — block LAN access + +Why: by default the sandbox can reach your home router, NAS, other machines on your LAN, and other WSL2 instances. The firewall restricts it to the public internet only. + +First, find your WSL gateway IP (changes per-machine): + +```bash +ip route | awk '/default/ {print $3}' +``` + +Substitute the result wherever you see `172.26.80.1` below. + +Install and configure UFW: + +```bash +sudo apt update +sudo apt install -y ufw + +sudo ufw default allow outgoing +sudo ufw default deny incoming + +# Allow only the WSL gateway (DNS + outbound routing). Narrower than allowing the +# whole /20 subnet — this also blocks sandbox-to-sandbox traffic between WSL2 instances. +sudo ufw allow out to 172.26.80.1 + +# Block private network ranges +sudo ufw deny out to 10.0.0.0/8 +sudo ufw deny out to 172.16.0.0/12 +sudo ufw deny out to 192.168.0.0/16 +sudo ufw deny out to 169.254.0.0/16 + +sudo ufw enable +``` + +Order matters: the allow rule must come before the broader denies it overrides. Confirm with `sudo ufw status numbered`. + +Note: if WSL renumbers its virtual network (rare, but happens on some Windows updates), the gateway IP can change, and the firewall will silently block all outbound traffic. Re-check `ip route` and update the allow rule if internet stops working. + +--- + +## Step 5: Install development tooling + +Why: minimal stack for .NET development plus Claude Code. + +```bash +sudo apt update +sudo apt install -y curl git build-essential ca-certificates + +# .NET SDK (Ubuntu 24.04 ships .NET in its own repos — no Microsoft repo needed) +sudo apt install -y dotnet-sdk-8.0 +dotnet --version + +# Claude Code (Anthropic's official installer) +curl -fsSL https://claude.ai/install.sh | bash +claude --version +``` + +First run of `claude` will walk you through authentication via browser. + +--- + +## Step 6: Generate a sandbox-only SSH key and clone + +Why: never copy your main SSH keys into the sandbox. Generate a fresh key here; if it's ever compromised, you revoke only the sandbox's access. + +```bash +ssh-keygen -t ed25519 -C "claude-sandbox" -f ~/.ssh/id_ed25519 +cat ~/.ssh/id_ed25519.pub +``` + +Copy the output line. On GitHub: **Settings → SSH and GPG keys → New SSH key** → paste → save. + +Test: + +```bash +ssh -T git@github.com +``` + +Configure git identity (use your GitHub noreply email to keep your real email private): + +```bash +git config --global user.name "yourusername" +git config --global user.email "<id>+yourusername@users.noreply.github.com" +``` + +Find your noreply address at <https://github.com/settings/emails>. + +Clone your project: + +```bash +mkdir -p ~/projects +cd ~/projects +git clone git@github.com:youruser/yourrepo.git +``` + +--- + +## Step 7: Connect VS Code + +Why: VS Code's WSL extension installs a server inside the sandbox and connects to it. Editor, terminal, debugger all run with the sandbox as backend. + +1. On Windows, open VS Code. +2. `Ctrl+Shift+P` → **WSL: Connect to WSL using Distro** → `Ubuntu-Claude`. +3. A new window opens; bottom-left shows `WSL: Ubuntu-Claude` in green. +4. `File → Open Folder` → `/home/<username>/projects/<repo>` → Open. +5. When prompted, install the **C# Dev Kit** (Microsoft) **into WSL: Ubuntu-Claude**, not locally. + +The integrated terminal (`Ctrl+\``) runs inside the sandbox. Confirm by checking the prompt shows your sandbox hostname. + +--- + +## Step 8: Take snapshots + +Why: rollback insurance. If Claude trashes the instance later, restore from a snapshot in one command. + +From PowerShell: + +```powershell +mkdir C:\WSL\backups -ErrorAction SilentlyContinue +wsl --export Ubuntu-Claude C:\WSL\backups\ubuntu-claude-final.tar +``` + +Recommended snapshots to keep: +- **Baseline** — after Step 4 (isolation done, no tooling yet). Useful for rebuilds with different tooling. +- **Working** — after Step 5 (tooling installed). Useful if a later config change breaks things. +- **Final** — after Step 7 (fully configured). Your day-to-day rollback point. + +Restore any snapshot with: + +```powershell +wsl --shutdown +wsl --unregister Ubuntu-Claude +wsl --import Ubuntu-Claude C:\WSL\Ubuntu-Claude C:\WSL\backups\<snapshot>.tar +``` + +--- + +## Step 9: Run Claude + +```bash +cd ~/projects/<repo> +claude --dangerously-skip-permissions +``` + +--- + +## Operational notes + +**Don't put real credentials in this sandbox.** No AWS/Azure/GCP CLI logins, no production API keys, no SSH keys from your main account. The SSH key created here should be sandbox-only. + +**Don't `cd` into mounted Windows directories for project work.** Files at `/mnt/c/...` are read-only and slow over 9P. Keep work in `~/projects/`. + +**The Windows host can still read sandbox files** via `\\wsl.localhost\Ubuntu-Claude\home\<user>\`. The sandbox can't reach out to Windows, but Windows can reach in — usually what you want (so you can pull files out), but worth knowing. + +**The isolation has limits.** WSL2 distros share a kernel and VM. A kernel-level exploit could cross between them. This sandbox protects against the realistic threat (an agent doing something dumb or getting prompt-injected); it's not a defence against targeted kernel attacks. + +**Two failed cosmetic services are normal.** `console-getty.service` and `getty@tty1.service` will show as failed in `systemctl --failed`. WSL has no physical consoles for them to attach to. Mask them with `sudo systemctl mask console-getty.service getty@tty1.service` if the `degraded` system state bothers you. From 2b963fa7acf1972a44c9f6b32df44dd54ccaad45 Mon Sep 17 00:00:00 2001 From: Frank Ray <52075808+FrankRay78@users.noreply.github.com> Date: Sat, 16 May 2026 21:17:28 +0100 Subject: [PATCH 5/7] Updated --- .claude/commands/speckit.draftissue.md | 2 +- .claude/commands/speckit.testplan.md | 37 ++++++++++++++++++++++++++ .specify/memory/constitution.md | 2 +- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/.claude/commands/speckit.draftissue.md b/.claude/commands/speckit.draftissue.md index 1e23c3c..73e6bc3 100644 --- a/.claude/commands/speckit.draftissue.md +++ b/.claude/commands/speckit.draftissue.md @@ -97,7 +97,7 @@ Common categories to probe (apply only those that fit the brief). Walk **Require | "Setting is written to the `user_preferences` table and cached in Redis" | "User's setting is remembered across sessions and visible on next sign-in" | | "Endpoint returns `[]` when no matches found" | "User is shown a clear empty state when no results match" | - *Do not write into ACs*: CSS class names, DOM IDs or element types, animation specifics, font names/weights/colours, framework or library picks, pixel measurements, timing values (Ns / Nms), polling cadences, specific URLs or ports, exact error message strings, storage technology. These are implementation choices, not acceptance criteria. Project-housekeeping items (project exists, sln updated, test scaffolding created) belong in `/speckit.tasks`, not here. + *Do not write into ACs*: CSS classes, DOM IDs or element types, animation specifics, font names/weights/colours, and pixel measurements; HTTP methods, endpoint paths, and status codes; response/payload schemas (JSON keys, field names); database tables, collections, columns, or indexes; algorithm or protocol choices (hash functions, signature schemes, encryption modes); framework or library picks; storage technology; specific URLs or ports; timing values (Ns / Nms) and polling cadences; exact error message strings; and log line formats or log levels. These are implementation choices, not acceptance criteria. Project-housekeeping items (project exists, sln updated, test scaffolding created) belong in `/speckit.tasks`, not here. *Regression exception*: an AC that pins a specific mechanism is permitted only when it exists to prevent a named, previously-fixed bug — reference the bug. - **User-visible failure modes** — what the user sees when a dependency is unreachable, slow, or rejects them; what's communicated; what stays available. diff --git a/.claude/commands/speckit.testplan.md b/.claude/commands/speckit.testplan.md index 95b86df..0dc5e24 100644 --- a/.claude/commands/speckit.testplan.md +++ b/.claude/commands/speckit.testplan.md @@ -162,6 +162,43 @@ testable without reading implementation code. --- +## Tests verify outcome, not mechanism + +A scenario's THEN clauses must describe an outcome an outside observer can verify, +not the specific mechanism the implementation chose to deliver it. This is the +test-plan-layer expression of Principle IX (Behavioural Specification) in +`.specify/memory/constitution.md`. + +**The independence test**: would this scenario still hold under a different +reasonable implementation of the same AC? If no, the scenario is testing +mechanism, not outcome — rewrite it before generating test code from it. + +| Avoid (mechanism, brittle) | Prefer (outcome, durable) | +|---|---| +| THEN: response body contains the literal string `"Invalid email address"` | THEN: caller is told the input was rejected and given an error code identifying the validation failure | +| THEN: response body is `{"status":"ok","data":[...]}` with HTTP 200 | THEN: caller can retrieve the current list of services in a single successful response | +| THEN: setting is written to the `user_preferences` table and cached in Redis | THEN: setting is remembered across sessions and visible on the next sign-in | +| THEN: endpoint returns `[]` when no records match | THEN: caller is shown a clear empty-result state when no records match | +| THEN: stdout contains the literal string `"Mbps"` | THEN: output presents the measured throughput in a unit the caller can interpret | +| THEN: result row is rendered via `Spectre.Console.Table` with column widths 12/8/8 | THEN: results are presented as a tabular summary readable in a terminal | + +**Do not write into THEN clauses**: CSS classes, DOM IDs or element types, +animation specifics, font names/weights/colours, and pixel measurements; +HTTP methods, endpoint paths, and status codes; response/payload schemas +(JSON keys, field names); database tables, collections, columns, or indexes; +algorithm or protocol choices (hash functions, signature schemes, encryption +modes); framework or library picks; storage technology; specific URLs or +ports; timing values (Ns / Nms) and polling cadences; exact error message +strings; and log line formats or log levels. These are implementation choices +— they belong in implementation notes, not in the test plan. + +**Regression exception**: a scenario that pins a specific mechanism is permitted +only when it exists to prevent a named, previously-fixed bug. Reference the bug +in the scenario name (e.g. `Scenario: GH#176 — release archive omits .pdb after +AOT publish`). + +--- + ## Format Output to `specs/$ARGUMENTS/test-plan.md`. diff --git a/.specify/memory/constitution.md b/.specify/memory/constitution.md index c938b77..eaec5fd 100644 --- a/.specify/memory/constitution.md +++ b/.specify/memory/constitution.md @@ -136,7 +136,7 @@ Acceptance criteria and tests MUST describe outcomes an outside observer can ver **Critical Rules:** -- ACs MUST be phrased as user-observable outcomes. Mechanism details MUST NOT appear in ACs, including: CSS classes, DOM IDs or element types, animation specifics, font names/weights/colours, framework or library picks, pixel measurements, timing values (Ns / Nms), polling cadences, specific URLs or ports, exact error message strings, and storage technology. +- ACs MUST be phrased as user-observable outcomes. Mechanism details MUST NOT appear in ACs, including: CSS classes, DOM IDs or element types, animation specifics, font names/weights/colours, and pixel measurements; HTTP methods, endpoint paths, and status codes; response/payload schemas (JSON keys, field names); database tables, collections, columns, or indexes; algorithm or protocol choices (hash functions, signature schemes, encryption modes); framework or library picks; storage technology; specific URLs or ports; timing values (Ns / Nms) and polling cadences; exact error message strings; and log line formats or log levels. - Tests MUST verify the AC as written, not the chosen implementation. A test that would fail under a different reasonable implementation of the same AC is testing mechanism, not outcome. - Project housekeeping (project exists, sln updated, scaffolding created) belongs in `tasks.md`, not in ACs. - **Regression exception**: an AC or test that pins a specific mechanism is permitted only when it exists to prevent a named, previously-fixed bug. Reference the bug in the AC text, scenario name, or a one-line comment in the test so future readers understand why the coupling exists. From 03f7641cf1ba976a6e585eb58dd31d0a62131e6d Mon Sep 17 00:00:00 2001 From: Frank Ray <52075808+FrankRay78@users.noreply.github.com> Date: Mon, 18 May 2026 14:44:08 +0100 Subject: [PATCH 6/7] Much improved raise-pr.md --- .claude/commands/raise-pr.md | 67 ++++++++++++++++++++++++-------- .github/pull_request_template.md | 26 ------------- .github/workflows/claude.yml | 37 +----------------- 3 files changed, 52 insertions(+), 78 deletions(-) delete mode 100644 .github/pull_request_template.md diff --git a/.claude/commands/raise-pr.md b/.claude/commands/raise-pr.md index f4dd36d..fdca703 100644 --- a/.claude/commands/raise-pr.md +++ b/.claude/commands/raise-pr.md @@ -1,31 +1,66 @@ --- -description: Raise a PR for the current feature branch with auto-detected spec and CIR links. +description: Raise a PR for the current feature branch — cleans up the spec folder, composes a PR body sized to the change, and requests an @claude review. --- -Read `CLAUDE.md` and `docs/conventions/*.md` for project context before proceeding. - ## Steps 1. **Get branch name**: Run `git rev-parse --abbrev-ref HEAD`. If the result is `main`, stop immediately and output: "Run /raise-pr from a feature branch, not main." 2. **Check for commits**: Run `git log main..HEAD --oneline`. If the output is empty, stop immediately and output: "No commits found on this branch compared to main — nothing to raise a PR for." -3. **Get all changed files**: Run `git diff main...HEAD --name-only` to list every file changed on this branch. +3. **Detect spec folder for this branch**: Run whichever spec-kit prerequisites helper is present in the repo: + - POSIX: `bash .specify/scripts/bash/check-prerequisites.sh --json --paths-only` + - Windows / pwsh: `pwsh .specify/scripts/powershell/check-prerequisites.ps1 -Json -PathsOnly` + + If neither script exists, or the command exits non-zero (non-feature branch), skip to step 5. + + Otherwise parse `FEATURE_DIR` from the JSON output. If `FEATURE_DIR` does not exist on disk, skip to step 5. Otherwise keep it for step 4. + +4. **Confirm and delete spec folder**: Use `AskUserQuestion` to ask: + - Question: "Delete the spec folder `<FEATURE_DIR>` and commit the removal? Specs are typically deleted before merge so they don't accumulate on main." + - Options: "Delete and commit" / "Skip" + + If "Delete and commit": + - Run `git rm -r <FEATURE_DIR>` + - Run `git commit -m "chore: remove <FEATURE_DIR>"` + + If "Skip", proceed without changes. + +5. **Infer PR title**: Take the branch name from step 1, strip any leading path segment (`feat/`, `fix/`, `chore/`, `docs/`, etc.), strip any leading `NNN-` numeric prefix on the remaining segment, replace hyphens with spaces, and apply title case. Examples: `004-raise-pr-command` → `Raise PR Command`; `chore/speckit-docs-tidy` → `Speckit Docs Tidy`. + +6. **Compose PR body**: Write a description sized to the change. **Do not follow a fixed template.** Decide which sections earn their place based on what's actually in this PR. + + **Guiding principle — don't duplicate things a reviewer can get elsewhere.** GitHub already shows: + - the list of changed files and their diffs + - the commit list and commit messages + - line-count statistics, additions/deletions + - the author, base/head branches, and CI status + + Don't repeat any of that in the body. Use the body for things a reviewer *can't* easily get from the code or git history. + + **Sections to consider** (include each only if it adds value for this PR): + + - **Why** — motivation or context that isn't already obvious from commit messages. Skip for trivial PRs where the title is self-explanatory. + - **What changes** — a short summary of observable changes. For single-theme PRs, 1–3 bullets. For multi-theme PRs, group by theme rather than by file. Describe *what* changed at a behaviour level, not which files were touched. + - **Non-obvious things a reviewer should know** — caveats, deliberate trade-offs, hidden invariants, anything in the diff that looks weird but is intentional. Omit if there are none. + - **How to verify** — a markdown checklist of concrete actions a reviewer can perform. Derive items from what actually changed. Omit for pure-documentation PRs where there's nothing functional to exercise. + - **Related** — links to issues, prior PRs, or specs that give context. Omit if none. -4. **Get added files**: Run `git diff main...HEAD --diff-filter=A --name-only` to list only files new to this branch. + A tiny PR may collapse to two sections. A sprawling PR may use all five. Match the size of the body to the size of the change. -5. **Extract artifacts** from the added files (step 4): - - Spec folders: find any matching `specs/*/spec.md` and extract the parent folder (e.g. `specs/004-raise-pr-command`) - - CIR files: find any matching `docs/change-intent-records/*.md` and use the full path (e.g. `docs/change-intent-records/006-slug.md`) +7. **Push branch**: Run `git push -u origin <branch>`. Ensures the spec-deletion commit (if any) is included in the PR. -6. **Infer PR title**: Take the branch name from step 1, strip any leading `NNN-` numeric prefix, replace hyphens with spaces, and apply title case. Examples: `004-raise-pr-command` → `Raise PR Command`; `add-glider-pattern` → `Add Glider Pattern`. +8. **Create PR**: Run `gh pr create --title "<inferred title>" --body "<pr body>"` with the title from step 5 and the body from step 6. Capture the PR URL from the output. Use whatever multi-line string quoting your shell needs (bash heredoc, PowerShell here-string, etc.). -7. **Build PR body**: Read `.github/pull_request_template.md` to get the section structure. Pre-fill as follows: - - **Summary**: Write 2–3 sentences covering what changed, why it was needed, and the approach taken — enough context for an independent reviewer without them needing to read the code first. Draw from the changed files (step 3) and commit list (step 2). - - **Spec**: Markdown link to detected spec folder(s), or `N/A` if none detected - - **Changed Files**: All files from step 3, one per line - - **New Artifacts**: For each detected spec folder or CIR file, one markdown link per line (e.g. `- Spec: [specs/004-raise-pr-command](specs/004-raise-pr-command)`). Omit the section entirely if no artifacts were detected. +9. **Request @claude review**: Run `gh pr comment <PR URL> --body "<review prompt>"` with this body (same shell-quoting rules as step 8): -8. **Create PR**: Run `gh pr create --title "<inferred title>" --body "<pr body>"` using the values from steps 6 and 7. + ``` + @claude Review this pull request. Analyse the code changes and provide feedback covering: + - Bugs or correctness issues + - Security concerns (including any flagged by static analysis) + - Adherence to the project conventions in CLAUDE.md + - Test coverage — do the tests adequately cover the new behaviour? + - Any spec/test-plan mismatches + ``` -9. **Output result**: Print the PR URL returned by `gh pr create`. If any spec folders or CIR files were detected in step 5, also print a single line: `Detected: <comma-separated list>`. Omit the `Detected:` line entirely if no spec folders or CIR files were found. +10. **Output result**: Print the PR URL. If step 4 deleted a spec folder, also print `Deleted spec folder: <FEATURE_DIR>`. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md deleted file mode 100644 index 8e98963..0000000 --- a/.github/pull_request_template.md +++ /dev/null @@ -1,26 +0,0 @@ -<!-- - Summary: What changed, why it was needed, and the approach taken. - Give an independent reviewer enough context to understand the PR without reading the spec first. ---> -## Summary - - - -<!-- - Spec: Link to the spec folder for this feature, or "N/A" if no spec exists. - Example: [specs/004-raise-pr-command](specs/004-raise-pr-command) ---> -## Spec - - - -## Changed Files - - - -## New Artifacts - -<!-- - Link any new spec folders or CIR files introduced on this branch. - Remove this section if none. ---> diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 986a7b7..844e5b1 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -1,45 +1,10 @@ name: Claude Code on: - pull_request: - types: [opened] issue_comment: types: [created] jobs: - auto-review: - if: | - github.event_name == 'pull_request' && - github.event.pull_request.user.login == 'FrankRay78' - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - issues: write - id-token: write - actions: read - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - name: Run Claude Code - id: claude - uses: anthropics/claude-code-action@v1 - with: - claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - additional_permissions: | - actions: read - prompt: | - Review this pull request. Analyse the code changes and provide feedback covering: - - Bugs or correctness issues - - Security concerns (including any flagged by static analysis) - - Adherence to the project conventions in CLAUDE.md and the C# style guide - - Test coverage — do the tests adequately cover the new behaviour? - - Any spec/test-plan mismatches - Post your review as a comment on the PR. - respond: if: | github.event_name == 'issue_comment' && @@ -56,7 +21,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 with: - fetch-depth: 1 + fetch-depth: 0 - name: Run Claude Code id: claude From a7ff812a96f0e6b30b061fbcf20fa6f60bb070a1 Mon Sep 17 00:00:00 2001 From: Frank Ray <52075808+FrankRay78@users.noreply.github.com> Date: Mon, 18 May 2026 15:08:21 +0100 Subject: [PATCH 7/7] Read `CLAUDE.md` for project context before proceeding. --- .claude/commands/raise-pr.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.claude/commands/raise-pr.md b/.claude/commands/raise-pr.md index fdca703..c9ab703 100644 --- a/.claude/commands/raise-pr.md +++ b/.claude/commands/raise-pr.md @@ -2,6 +2,8 @@ description: Raise a PR for the current feature branch — cleans up the spec folder, composes a PR body sized to the change, and requests an @claude review. --- +Read `CLAUDE.md` for project context before proceeding. + ## Steps 1. **Get branch name**: Run `git rev-parse --abbrev-ref HEAD`. If the result is `main`, stop immediately and output: "Run /raise-pr from a feature branch, not main."