qmj 12 часов назад
Родитель
Сommit
f9598929ed

+ 2 - 0
.claude/skills/speckit-analyze/SKILL.md

@@ -8,6 +8,8 @@ metadata:
   source: "templates/commands/analyze.md"
 user-invocable: true
 disable-model-invocation: false
+context: fork
+agent: general-purpose
 ---
 
 

+ 8 - 6
.claude/skills/speckit-checklist/SKILL.md

@@ -81,7 +81,9 @@ You **MUST** consider the user input before proceeding (if not empty).
    - All file paths must be absolute.
    - For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot").
 
-2. **Clarify intent (dynamic)**: Derive up to THREE initial contextual clarifying questions (no pre-baked catalog). They MUST:
+2. **IF EXISTS**: Load `.specify/memory/constitution.md` for project principles and governance constraints.
+
+3. **Clarify intent (dynamic)**: Derive up to THREE initial contextual clarifying questions (no pre-baked catalog). They MUST:
    - Be generated from the user's phrasing + extracted signals from spec/plan/tasks
    - Only ask about information that materially changes checklist content
    - Be skipped individually if already unambiguous in `$ARGUMENTS`
@@ -113,13 +115,13 @@ You **MUST** consider the user input before proceeding (if not empty).
 
    Output the questions (label Q1/Q2/Q3). After answers: if ≥2 scenario classes (Alternate / Exception / Recovery / Non-Functional domain) remain unclear, you MAY ask up to TWO more targeted follow‑ups (Q4/Q5) with a one-line justification each (e.g., "Unresolved recovery path risk"). Do not exceed five total questions. Skip escalation if user explicitly declines more.
 
-3. **Understand user request**: Combine `$ARGUMENTS` + clarifying answers:
+4. **Understand user request**: Combine `$ARGUMENTS` + clarifying answers:
    - Derive checklist theme (e.g., security, review, deploy, ux)
    - Consolidate explicit must-have items mentioned by user
    - Map focus selections to category scaffolding
    - Infer any missing context from spec/plan/tasks (do NOT hallucinate)
 
-4. **Load feature context**: Read from FEATURE_DIR:
+5. **Load feature context**: Read from FEATURE_DIR:
    - spec.md: Feature requirements and scope
    - plan.md (if exists): Technical details, dependencies
    - tasks.md (if exists): Implementation tasks
@@ -130,7 +132,7 @@ You **MUST** consider the user input before proceeding (if not empty).
    - Use progressive disclosure: add follow-on retrieval only if gaps detected
    - If source docs are large, generate interim summary items instead of embedding raw text
 
-5. **Generate checklist** - Create "Unit Tests for Requirements":
+6. **Generate checklist** - Create "Unit Tests for Requirements":
    - Create `FEATURE_DIR/checklists/` directory if it doesn't exist
    - Generate unique checklist filename:
      - Use short, descriptive name based on domain (e.g., `ux.md`, `api.md`, `security.md`)
@@ -248,9 +250,9 @@ You **MUST** consider the user input before proceeding (if not empty).
    - ✅ "Are [edge cases/scenarios] addressed in requirements?"
    - ✅ "Does the spec define [missing aspect]?"
 
-6. **Structure Reference**: Generate the checklist following the canonical template in `.specify/templates/checklist-template.md` for title, meta section, category headings, and ID formatting. If template is unavailable, use: H1 title, purpose/created meta lines, `##` category sections containing `- [ ] CHK### <requirement item>` lines with globally incrementing IDs starting at CHK001.
+7. **Structure Reference**: Generate the checklist following the canonical template in `.specify/templates/checklist-template.md` for title, meta section, category headings, and ID formatting. If template is unavailable, use: H1 title, purpose/created meta lines, `##` category sections containing `- [ ] CHK### <requirement item>` lines with globally incrementing IDs starting at CHK001.
 
-7. **Report**: Output full path to checklist file, item count, and summarize whether the run created a new file or appended to an existing one. Summarize:
+8. **Report**: Output full path to checklist file, item count, and summarize whether the run created a new file or appended to an existing one. Summarize:
    - Focus areas selected
    - Depth level
    - Actor/timing

+ 58 - 26
.claude/skills/speckit-clarify/SKILL.md

@@ -69,7 +69,9 @@ Execution steps:
    - 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).
+2. **IF EXISTS**: Load `.specify/memory/constitution.md` for project principles and governance constraints.
+
+3. Load the current spec file. Perform a structured ambiguity & coverage scan using this taxonomy. For each category, mark status: Clear / Partial / Missing. Produce an internal coverage map used for prioritization (do not output raw map unless no questions will be asked).
 
    Functional Scope & Behavior:
    - Core user goals & success criteria
@@ -125,7 +127,7 @@ Execution steps:
    - Clarification would not materially change implementation or validation strategy
    - Information is better deferred to planning phase (note internally)
 
-3. Generate (internally) a prioritized queue of candidate clarification questions (maximum 5). Do NOT output them all at once. Apply these constraints:
+4. Generate (internally) a prioritized queue of candidate clarification questions (maximum 5). Do NOT output them all at once. Apply these constraints:
     - Maximum of 5 total questions across the whole session.
     - Each question must be answerable with EITHER:
        - A short multiple‑choice selection (2–5 distinct, mutually exclusive options), OR
@@ -136,7 +138,7 @@ Execution steps:
     - Favor clarifications that reduce downstream rework risk or prevent misaligned acceptance tests.
     - If more than 5 categories remain unresolved, select the top 5 by (Impact * Uncertainty) heuristic.
 
-4. Sequential questioning loop (interactive):
+5. Sequential questioning loop (interactive):
     - Present EXACTLY ONE question at a time.
     - For multiple‑choice questions:
        - **Analyze all options** and determine the **most suitable option** based on:
@@ -172,7 +174,7 @@ Execution steps:
     - Never reveal future queued questions in advance.
     - If no valid questions exist at start, immediately report no critical ambiguities.
 
-5. Integration after EACH accepted answer (incremental update approach):
+6. Integration after EACH accepted answer (incremental update approach):
     - Maintain in-memory representation of the spec (loaded once at start) plus the raw file contents.
     - For the first integrated answer in this session:
        - Ensure a `## Clarifications` section exists (create it just after the highest-level contextual/overview section per the spec template if missing).
@@ -190,7 +192,7 @@ Execution steps:
     - Preserve formatting: do not reorder unrelated sections; keep heading hierarchy intact.
     - Keep each inserted clarification minimal and testable (avoid narrative drift).
 
-6. Validation (performed after EACH write plus final pass):
+7. Validation (performed after EACH write plus final pass):
    - Clarifications session contains exactly one bullet per accepted answer (no duplicates).
    - Total asked (accepted) questions ≤ 5.
    - Updated sections contain no lingering vague placeholders the new answer was meant to resolve.
@@ -198,15 +200,26 @@ Execution steps:
    - Markdown structure valid; only allowed new headings: `## Clarifications`, `### Session YYYY-MM-DD`.
    - Terminology consistency: same canonical term used across all updated sections.
 
-7. Write the updated spec back to `FEATURE_SPEC`.
-
-8. Report completion (after questioning loop ends or early termination):
-   - Number of questions asked & answered.
-   - 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.
-   - Suggested next command.
+8. Write the updated spec back to `FEATURE_SPEC`.
+
+9. **Re-validate Spec Quality Checklist** (if it exists):
+   - Check if `FEATURE_DIR/checklists/requirements.md` exists.
+   - If it does NOT exist, skip this step silently.
+   - If it exists:
+     1. Read the checklist file.
+     2. Identify all GitHub task-list checkbox lines — lines matching `- [ ]`, `- [x]`, or `- [X]` (case-insensitive, tolerant of leading whitespace for nested items) outside of code fences. Ignore all other content (headings, notes, non-checkbox bullets, metadata).
+     3. For each checkbox line, record its current marker state (checked or unchecked) and item text into a before-snapshot list.
+     4. Re-evaluate each checkbox item against the **updated** spec (the version just saved in step 7).
+     5. For each checkbox item, update only if the checked/unchecked state actually changes:
+        - If the item now passes and was unchecked: change `[ ]` to `[x]`.
+        - If the item now fails and was checked: change `[x]`/`[X]` to `[ ]`.
+        - If the state is unchanged: leave the marker as-is (preserve existing case to avoid cosmetic diffs).
+     6. Save the updated checklist file. **Only toggle the `[ ]`/`[x]` marker portion of checkbox lines whose state changed.** All other file content — headings, metadata, notes, line ordering, whitespace — must remain unchanged to avoid noisy diffs.
+     7. Compare the before-snapshot with the current state to compute three lists for the Completion Report:
+        - **Newly passing**: items that changed from unchecked to checked.
+        - **Regressions**: items that changed from checked to unchecked.
+        - **Still unchecked**: items that remain unchecked.
+     8. Record the before/after pass counts as checked/total checkbox items (e.g., "12/16 → 15/16 items passing").
 
 Behavior rules:
 
@@ -220,18 +233,28 @@ Behavior rules:
 
 Context for prioritization: $ARGUMENTS
 
-## Post-Execution Checks
+## Mandatory Post-Execution Hooks
+
+**You MUST complete this section before reporting completion to the user.**
 
-**Check for extension hooks (after clarification)**:
 Check if `.specify/extensions.yml` exists in the project root.
-- If it exists, read it and look for entries under the `hooks.after_clarify` key
-- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
+- If it does not exist, or no hooks are registered under `hooks.after_clarify`, skip to the Completion Report.
+- If it exists, read it and look for entries under the `hooks.after_clarify` key.
+- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue to the Completion Report.
 - Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
 - For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
   - If the hook has no `condition` field, or it is null/empty, treat the hook as executable
   - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
 - When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
 - For each executable hook, output the following based on its `optional` flag:
+  - **Mandatory hook** (`optional: false`) — **You MUST emit `EXECUTE_COMMAND:` for each mandatory hook**:
+    ```
+    ## Extension Hooks
+
+    **Automatic Hook**: {extension}
+    Executing: `/{command}`
+    EXECUTE_COMMAND: {command}
+    ```
   - **Optional hook** (`optional: true`):
     ```
     ## Extension Hooks
@@ -243,12 +266,21 @@ Check if `.specify/extensions.yml` exists in the project root.
     Prompt: {prompt}
     To execute: `/{command}`
     ```
-  - **Mandatory hook** (`optional: false`):
-    ```
-    ## Extension Hooks
 
-    **Automatic Hook**: {extension}
-    Executing: `/{command}`
-    EXECUTE_COMMAND: {command}
-    ```
-- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
+## Completion Report
+
+Report completion (after questioning loop ends or early termination):
+- Number of questions asked & answered.
+- Path to updated spec.
+- Sections touched (list names).
+- Spec quality checklist status (if `FEATURE_DIR/checklists/requirements.md` was re-validated): show before/after pass counts (e.g., "Spec Quality Checklist: 12/16 → 15/16 items passing") and list any items that changed state — both newly checked (unchecked → checked) and any regressions (checked → unchecked). If any items remain unchecked, list them as areas needing attention.
+- Coverage summary table listing each taxonomy category with Status: Resolved (was Partial/Missing and addressed), Deferred (exceeds question quota or better suited for planning), Clear (already sufficient), Outstanding (still Partial/Missing but low impact).
+- If any Outstanding or Deferred remain, recommend whether to proceed to `/speckit-plan` or run `/speckit-clarify` again later post-plan.
+- Suggested next command.
+
+## Done When
+
+- [ ] Spec ambiguities identified and clarifications integrated into spec file
+- [ ] Spec quality checklist re-validated against updated spec (if `FEATURE_DIR/checklists/requirements.md` exists)
+- [ ] Extension hooks dispatched or skipped according to the rules in Mandatory Post-Execution Hooks above
+- [ ] Completion reported to user with questions answered, sections touched, checklist status, and coverage summary

+ 277 - 0
.claude/skills/speckit-converge/SKILL.md

@@ -0,0 +1,277 @@
+---
+name: "speckit-converge"
+description: "Assess the current codebase against the feature's spec, plan, and tasks, then append any remaining unbuilt work as new tasks to tasks.md so implement can complete it."
+compatibility: "Requires spec-kit project structure with .specify/ directory"
+metadata:
+  author: "github-spec-kit"
+  source: "templates/commands/converge.md"
+user-invocable: true
+disable-model-invocation: false
+---
+
+
+## User Input
+
+```text
+$ARGUMENTS
+```
+
+You **MUST** consider the user input before proceeding (if not empty).
+
+## Pre-Execution Checks
+
+**Check for extension hooks (before convergence)**:
+
+- Check if `.specify/extensions.yml` exists in the project root.
+- If it exists, read it and look for entries under the `hooks.before_converge` key
+- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
+- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
+- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
+  - If the hook has no `condition` field, or it is null/empty, treat the hook as executable
+  - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
+- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
+- For each executable hook, output the following based on its `optional` flag:
+  - **Optional hook** (`optional: true`):
+
+    ```text
+    ## Extension Hooks
+
+    **Optional Pre-Hook**: {extension}
+    Command: `/{command}`
+    Description: {description}
+
+    Prompt: {prompt}
+    To execute: `/{command}`
+    ```
+
+  - **Mandatory hook** (`optional: false`):
+
+    ```text
+    ## Extension Hooks
+
+    **Automatic Pre-Hook**: {extension}
+    Executing: `/{command}`
+    EXECUTE_COMMAND: {command}
+
+    Wait for the result of the hook command before proceeding to the Goal.
+    ```
+
+- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
+
+## Goal
+
+Close the gap between what a feature's specification, plan, and tasks call for and what the
+codebase currently implements. Read `spec.md`, `plan.md`, and `tasks.md` as the **sole
+source of intent** (with the constitution as governing constraints), assess the current
+state of the code, determine which requirements, acceptance criteria, plan decisions, and
+existing tasks are unmet, incomplete, or only partially satisfied, and **append each piece
+of remaining work as a new, traceable task** at the bottom of `tasks.md` so that
+`/speckit-implement` can complete it. This command MUST run only after
+`/speckit-implement` has run on the current `tasks.md`, and after `/speckit-tasks` has produced a complete `tasks.md`.
+
+This is **not** a diff tool and does **not** track changes. It assesses the present state
+of the code relative to the feature's artifacts — no git, no branch comparison, no history.
+
+## Operating Constraints
+
+**APPEND-ONLY, NEVER REWRITE**: The command's **only** write is appending a new
+`## Phase N: Convergence` section to `tasks.md`. It MUST NOT:
+
+- modify `spec.md` or `plan.md` in any way;
+- rewrite, renumber, reorder, or delete any existing task (including tasks from a prior
+  Convergence phase);
+- modify, create, or delete any application code — completing the appended tasks is the
+  job of `/speckit-implement`.
+
+When the codebase already satisfies everything, the command MUST leave `tasks.md`
+**byte-for-byte unchanged** (no empty Convergence header) and report a clean result.
+
+**Constitution Authority**: The project constitution (`.specify/memory/constitution.md`) is
+**non-negotiable**. Code that violates a MUST principle is the highest-severity finding and
+produces a corresponding remediation task. If the constitution is an unfilled template,
+skip constitution checks gracefully rather than failing.
+
+## Execution Steps
+
+### 1. Initialize Convergence Context
+
+Run `.specify/scripts/powershell/check-prerequisites.ps1 -Json -RequireTasks -IncludeTasks` once from repo root and parse JSON for FEATURE_DIR and AVAILABLE_DOCS. Derive absolute paths:
+
+- SPEC = FEATURE_DIR/spec.md
+- PLAN = FEATURE_DIR/plan.md
+- TASKS = FEATURE_DIR/tasks.md
+- CONSTITUTION = `.specify/memory/constitution.md` (if present)
+If `spec.md`, `plan.md`, or `tasks.md` is missing, STOP with a clear, actionable message naming the
+prerequisite command to run (`/speckit-specify` for a missing spec, `/speckit-plan` for a missing plan,
+`/speckit-tasks` for missing tasks). Do not produce partial output.
+For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot").
+
+### 2. Load Artifacts (Progressive Disclosure)
+
+Load only the minimal necessary context from each artifact:
+
+**From spec.md:**
+
+- Functional Requirements (FR-###)
+- Success Criteria (SC-###) — include only items requiring buildable work; exclude
+  post-launch outcome metrics and business KPIs
+- User Stories and their Acceptance Scenarios
+- Edge Cases (if present)
+
+**From plan.md:**
+
+- Architecture/stack choices and technical decisions
+- Data Model references
+- Phases and named touch-points (files/components the plan says will be created or edited)
+- Technical constraints
+
+**From tasks.md:**
+
+- Task IDs (to compute the next ID and next phase number)
+- Descriptions, phase grouping, and referenced file paths
+
+**From constitution (if not an unfilled template):**
+
+- Principle names and MUST/SHOULD normative statements
+
+### 3. Build the Intent Inventory
+
+Create an internal model (do not echo raw artifacts):
+
+- **Requirements inventory**: one stable key per FR-### / SC-### / user-story acceptance
+  scenario (e.g. `US1/AC2`), plus the plan decisions and constitution principles that
+  impose buildable obligations.
+- **Code-scope map**: from the file paths named in `plan.md` and `tasks.md`, plus a keyword
+  search for the concepts each requirement describes, derive the set of source files and
+  components in scope for assessment. Bound the assessment to these — do **not** infer
+  scope beyond what the artifacts define.
+
+### 4. Assess the Codebase and Classify Findings
+
+For each item in the intent inventory, inspect the current code in scope and produce a
+`Finding` only where there is a gap. Classify every finding by **gap type**:
+
+- **`missing`**: the required work is absent from the code entirely.
+- **`partial`**: the work exists but does not yet fully satisfy the requirement /
+  acceptance criterion / plan decision.
+- **`contradicts`**: the code does something that conflicts with stated intent or a
+  constitution MUST principle.
+- **`unrequested`**: the code contains work not called for by the spec, plan, or tasks
+  (surfaced for awareness — converge does **not** delete code, it only appends a task to
+  review/justify or remove it).
+
+Each `Finding` records: a stable id, the `source-ref` it traces to, the `gap-type`, a
+severity, and a short human-readable description with the evidence (the file/area observed).
+
+**Edge cases:**
+
+- **Little or no code yet**: treat the entire specified scope as `missing` remaining work
+  rather than failing.
+- **Nothing remains**: produce zero findings and follow the converged branch in Step 7.
+
+### 5. Assign Severity
+
+- **CRITICAL**: violates a constitution MUST principle, or a `missing`/`contradicts` gap
+  that blocks baseline functionality of a P1 user story.
+- **HIGH**: a `missing` or `partial` gap on a core functional requirement or acceptance
+  criterion.
+- **MEDIUM**: a `partial` gap on a secondary requirement, or an `unrequested` addition with
+  unclear justification.
+- **LOW**: minor partial gaps, polish, or low-risk `unrequested` additions.
+
+### 6. Present the In-Session Findings Summary
+
+Before appending anything, output a compact, severity-graded summary (no file writes yet):
+
+## Convergence Findings
+
+| ID | Gap Type | Severity | Source | Evidence | Remaining Work |
+|----|----------|----------|--------|----------|----------------|
+| F1 | missing  | HIGH     | FR-008 | Example: no append-only guard detected in path/to/module.py when writing tasks.md | Add append-only enforcement |
+
+**Summary metrics:**
+
+- Requirements / acceptance criteria checked
+- Plan decisions checked
+- Constitution principles checked (or "skipped — template")
+- Findings by gap type (missing / partial / contradicts / unrequested)
+- Findings by severity
+
+### 7. Append Convergence Tasks (or report converged)
+
+**If there are one or more actionable findings** (`tasks_appended` outcome):
+
+Append to the **end** of `tasks.md`, per the append contract:
+
+1. Scan all existing task IDs; let `M` be the maximum. Determine the next phase number `N`
+   (highest existing phase + 1).
+2. Write a single new section header `## Phase N: Convergence`.
+3. Emit one checklist item per actionable finding, ordered CRITICAL/HIGH first, assigning
+   zero-padded IDs `T{M+1:03d}, T{M+2:03d}, …`:
+
+   ```markdown
+   - [ ] T042 <imperative description> per <source-ref> (<gap-type>)
+   ```
+
+   `<source-ref>` traces the task to its origin: e.g. `FR-003`, `SC-002`,
+   `US1/AC2`, `plan: storage decision`, `Constitution II`.
+
+   `<gap-type>` is one of `missing`, `partial`, `contradicts`, `unrequested`.
+
+   Constitution-violation tasks MUST be emitted first and described as
+   `CRITICAL`.
+4. Never reuse or renumber existing IDs. If a prior Convergence phase exists, add a new,
+   separately-numbered one below it — do not touch the old one.
+
+**If there are no actionable findings** (`converged` outcome):
+
+- Do **not** modify `tasks.md` at all — no empty phase header.
+- Report: **"✅ Converged — the implementation satisfies the spec, plan, and tasks."**
+- Include the summary counts of what was checked.
+
+### 8. Provide Next Actions (Handoff)
+
+- On `tasks_appended`: state how many tasks were appended under which phase, and recommend
+  running `/speckit-implement` to complete them; note that a follow-up converge
+  run will find fewer or no remaining items.
+- On `converged`: recommend proceeding to review / opening a PR. No further implement pass
+  is needed for this feature's specified scope.
+
+### 9. Check for extension hooks
+
+After producing the result, check if `.specify/extensions.yml` exists in the project root.
+
+- If it exists, read it and look for entries under the `hooks.after_converge` key
+- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
+- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
+- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
+  - If the hook has no `condition` field, or it is null/empty, treat the hook as executable
+  - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
+- Report the convergence outcome (`converged` or `tasks_appended`) in-session before listing
+  any hooks, so users can decide whether to run optional follow-up commands.
+- 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`):
+
+    ```text
+    ## Extension Hooks
+
+    **Optional Hook**: {extension}
+    Command: `/{command}`
+    Description: {description}
+
+    Prompt: {prompt}
+    To execute: `/{command}`
+    ```
+
+  - **Mandatory hook** (`optional: false`):
+
+    ```text
+    ## Extension Hooks
+
+    **Automatic Hook**: {extension}
+    Executing: `/{command}`
+    EXECUTE_COMMAND: {command}
+    ```
+
+- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently

+ 44 - 30
.claude/skills/speckit-implement/SKILL.md

@@ -175,36 +175,50 @@ You **MUST** consider the user input before proceeding (if not empty).
    - Check that implemented features match the original specification
    - Validate that tests pass and coverage meets requirements
    - 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.
 
-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
-    - If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
-    - Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
-    - For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
-      - If the hook has no `condition` field, or it is null/empty, treat the hook as executable
-      - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
-    - When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
-    - For each executable hook, output the following based on its `optional` flag:
-      - **Optional hook** (`optional: true`):
-        ```
-        ## Extension Hooks
-
-        **Optional Hook**: {extension}
-        Command: `/{command}`
-        Description: {description}
-
-        Prompt: {prompt}
-        To execute: `/{command}`
-        ```
-      - **Mandatory hook** (`optional: false`):
-        ```
-        ## Extension Hooks
-
-        **Automatic Hook**: {extension}
-        Executing: `/{command}`
-        EXECUTE_COMMAND: {command}
-        ```
-    - If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
+## Mandatory Post-Execution Hooks
+
+**You MUST complete this section before reporting completion to the user.**
+
+Check if `.specify/extensions.yml` exists in the project root.
+- If it does not exist, or no hooks are registered under `hooks.after_implement`, skip to the Completion Report.
+- If it exists, read it and look for entries under the `hooks.after_implement` key.
+- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue to the Completion Report.
+- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
+- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
+  - If the hook has no `condition` field, or it is null/empty, treat the hook as executable
+  - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
+- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
+- For each executable hook, output the following based on its `optional` flag:
+  - **Mandatory hook** (`optional: false`) — **You MUST emit `EXECUTE_COMMAND:` for each mandatory hook**:
+    ```
+    ## Extension Hooks
+
+    **Automatic Hook**: {extension}
+    Executing: `/{command}`
+    EXECUTE_COMMAND: {command}
+    ```
+  - **Optional hook** (`optional: true`):
+    ```
+    ## Extension Hooks
+
+    **Optional Hook**: {extension}
+    Command: `/{command}`
+    Description: {description}
+
+    Prompt: {prompt}
+    To execute: `/{command}`
+    ```
+
+## Completion Report
+
+Report final status with summary of completed work.
+
+## Done When
+
+- [ ] All tasks in tasks.md completed and marked `[X]`
+- [ ] Implementation validated against specification, plan, and test coverage
+- [ ] Extension hooks dispatched or skipped according to the rules in Mandatory Post-Execution Hooks above
+- [ ] Completion reported to user with summary of completed work

+ 51 - 32
.claude/skills/speckit-plan/SKILL.md

@@ -69,37 +69,43 @@ You **MUST** consider the user input before proceeding (if not empty).
    - Phase 1: Update agent context by running the agent script
    - Re-evaluate Constitution Check post-design
 
-4. **Stop and report**: Command ends after Phase 2 planning. Report branch, IMPL_PLAN path, and generated artifacts.
-
-5. **Check for extension hooks**: After reporting, check if `.specify/extensions.yml` exists in the project root.
-   - If it exists, read it and look for entries under the `hooks.after_plan` key
-   - If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
-   - Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
-   - For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
-     - If the hook has no `condition` field, or it is null/empty, treat the hook as executable
-     - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
-   - When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
-   - For each executable hook, output the following based on its `optional` flag:
-     - **Optional hook** (`optional: true`):
-       ```
-       ## Extension Hooks
-
-       **Optional Hook**: {extension}
-       Command: `/{command}`
-       Description: {description}
-
-       Prompt: {prompt}
-       To execute: `/{command}`
-       ```
-     - **Mandatory hook** (`optional: false`):
-       ```
-       ## Extension Hooks
-
-       **Automatic Hook**: {extension}
-       Executing: `/{command}`
-       EXECUTE_COMMAND: {command}
-       ```
-   - If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
+## Mandatory Post-Execution Hooks
+
+**You MUST complete this section before reporting completion to the user.**
+
+Check if `.specify/extensions.yml` exists in the project root.
+- If it does not exist, or no hooks are registered under `hooks.after_plan`, skip to the Completion Report.
+- If it exists, read it and look for entries under the `hooks.after_plan` key.
+- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue to the Completion Report.
+- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
+- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
+  - If the hook has no `condition` field, or it is null/empty, treat the hook as executable
+  - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
+- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
+- For each executable hook, output the following based on its `optional` flag:
+  - **Mandatory hook** (`optional: false`) — **You MUST emit `EXECUTE_COMMAND:` for each mandatory hook**:
+    ```
+    ## Extension Hooks
+
+    **Automatic Hook**: {extension}
+    Executing: `/{command}`
+    EXECUTE_COMMAND: {command}
+    ```
+  - **Optional hook** (`optional: true`):
+    ```
+    ## Extension Hooks
+
+    **Optional Hook**: {extension}
+    Command: `/{command}`
+    Description: {description}
+
+    Prompt: {prompt}
+    To execute: `/{command}`
+    ```
+
+## Completion Report
+
+Command ends after Phase 2 planning. Report branch, IMPL_PLAN path, and generated artifacts.
 
 ## Phases
 
@@ -141,7 +147,14 @@ You **MUST** consider the user input before proceeding (if not empty).
    - Examples: public APIs for libraries, command schemas for CLI tools, endpoints for web services, grammars for parsers, UI contracts for applications
    - Skip if project is purely internal (build scripts, one-off tools, etc.)
 
-3. **Agent context update**:
+3. **Create quickstart validation guide** → `quickstart.md`:
+   - Document runnable validation scenarios that prove the feature works end-to-end
+   - Include prerequisites, setup commands, test/run commands, and expected outcomes
+   - Use links or references to contracts and data model details instead of duplicating them
+   - Do not include full implementation code, model/service/controller bodies, migrations, or complete test suites
+   - Keep this artifact as a validation/run guide; implementation details belong in `tasks.md` and the implementation phase
+
+4. **Agent context update**:
    - Update the plan reference between the `<!-- SPECKIT START -->` and `<!-- SPECKIT END -->` markers in `CLAUDE.md` to point to the plan file created in step 1 (the IMPL_PLAN path)
 
 **Output**: data-model.md, /contracts/*, quickstart.md, updated agent context file
@@ -150,3 +163,9 @@ You **MUST** consider the user input before proceeding (if not empty).
 
 - Use absolute paths for filesystem operations; use project-relative paths for references in documentation and agent context files
 - ERROR on gate failures or unresolved clarifications
+
+## Done When
+
+- [ ] Plan workflow executed and design artifacts generated
+- [ ] Extension hooks dispatched or skipped according to the rules in Mandatory Post-Execution Hooks above
+- [ ] Completion reported to user with branch, plan path, and generated artifacts

+ 56 - 40
.claude/skills/speckit-specify/SKILL.md

@@ -85,15 +85,17 @@ Given that feature description, do this:
    **Resolution order for `SPECIFY_FEATURE_DIRECTORY`**:
    1. If the user explicitly provided `SPECIFY_FEATURE_DIRECTORY` (e.g., via environment variable, argument, or configuration), use it as-is
    2. Otherwise, auto-generate it under `specs/`:
-      - Check `.specify/init-options.json` for `branch_numbering`
+      - Check `.specify/init-options.json` for `feature_numbering` (preferred) or `branch_numbering` (deprecated, migration only — will be removed in a future release)
       - If `"timestamp"`: prefix is `YYYYMMDD-HHMMSS` (current timestamp)
       - If `"sequential"` or absent: prefix is `NNN` (next available 3-digit number after scanning existing directories in `specs/`)
       - Construct the directory name: `<prefix>-<short-name>` (e.g., `003-user-auth` or `20260319-143022-user-auth`)
       - Set `SPECIFY_FEATURE_DIRECTORY` to `specs/<directory-name>`
+      - If `branch_numbering` was used (and `feature_numbering` was absent), emit a one-line warning: "⚠️ `branch_numbering` in init-options.json is deprecated. Rename to `feature_numbering`."
 
    **Create the directory and spec file**:
    - `mkdir -p SPECIFY_FEATURE_DIRECTORY`
-   - Copy `.specify/templates/spec-template.md` to `SPECIFY_FEATURE_DIRECTORY/spec.md` as the starting point
+   - Resolve the active `spec-template` through the Spec Kit preset/template resolution stack (equivalent to `specify preset resolve spec-template`)
+   - Copy the resolved `spec-template` file to `SPECIFY_FEATURE_DIRECTORY/spec.md` as the starting point
    - Set `SPEC_FILE` to `SPECIFY_FEATURE_DIRECTORY/spec.md`
    - Persist the resolved path to `.specify/feature.json`:
      ```json
@@ -109,9 +111,11 @@ Given that feature description, do this:
    - The spec directory name and the git branch name are independent — they may be the same but that is the user's choice
    - The spec directory and file are always created by this command, never by the hook
 
-4. Load `.specify/templates/spec-template.md` to understand required sections.
+4. Load the resolved active `spec-template` file to understand required sections.
 
-5. Follow this execution flow:
+5. **IF EXISTS**: Load `.specify/memory/constitution.md` for project principles and governance constraints.
+
+6. Follow this execution flow:
     1. Parse user description from arguments
        If empty: ERROR "No feature description provided"
     2. Extract key concepts from description
@@ -185,7 +189,7 @@ Given that feature description, do this:
 
    c. **Handle Validation Results**:
 
-      - **If all items pass**: Mark checklist complete and proceed to step 8
+      - **If all items pass**: Mark checklist complete and proceed to the Mandatory Post-Execution Hooks section
 
       - **If items fail (excluding [NEEDS CLARIFICATION])**:
         1. List the failing items and specific issues
@@ -230,41 +234,47 @@ Given that feature description, do this:
 
    d. **Update Checklist**: After each validation iteration, update the checklist file with current pass/fail status
 
-8. **Report completion** to the user with:
-   - `SPECIFY_FEATURE_DIRECTORY` — the feature directory path
-   - `SPEC_FILE` — the spec file path
-   - Checklist results summary
-   - Readiness for the next phase (`/speckit-clarify` or `/speckit-plan`)
-
-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
-   - If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
-   - Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
-   - For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
-     - If the hook has no `condition` field, or it is null/empty, treat the hook as executable
-     - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
-   - When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
-   - For each executable hook, output the following based on its `optional` flag:
-     - **Optional hook** (`optional: true`):
-       ```
-       ## Extension Hooks
-
-       **Optional Hook**: {extension}
-       Command: `/{command}`
-       Description: {description}
-
-       Prompt: {prompt}
-       To execute: `/{command}`
-       ```
-     - **Mandatory hook** (`optional: false`):
-       ```
-       ## Extension Hooks
-
-       **Automatic Hook**: {extension}
-       Executing: `/{command}`
-       EXECUTE_COMMAND: {command}
-       ```
-   - If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
+## Mandatory Post-Execution Hooks
+
+**You MUST complete this section before reporting completion to the user.**
+
+Check if `.specify/extensions.yml` exists in the project root.
+- If it does not exist, or no hooks are registered under `hooks.after_specify`, skip to the Completion Report.
+- If it exists, read it and look for entries under the `hooks.after_specify` key.
+- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue to the Completion Report.
+- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
+- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
+  - If the hook has no `condition` field, or it is null/empty, treat the hook as executable
+  - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
+- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
+- For each executable hook, output the following based on its `optional` flag:
+  - **Mandatory hook** (`optional: false`) — **You MUST emit `EXECUTE_COMMAND:` for each mandatory hook**:
+    ```
+    ## Extension Hooks
+
+    **Automatic Hook**: {extension}
+    Executing: `/{command}`
+    EXECUTE_COMMAND: {command}
+    ```
+  - **Optional hook** (`optional: true`):
+    ```
+    ## Extension Hooks
+
+    **Optional Hook**: {extension}
+    Command: `/{command}`
+    Description: {description}
+
+    Prompt: {prompt}
+    To execute: `/{command}`
+    ```
+
+## Completion Report
+
+Report completion to the user with:
+- `SPECIFY_FEATURE_DIRECTORY` — the feature directory path
+- `SPEC_FILE` — the spec file path
+- Checklist results summary
+- Readiness for the next phase (`/speckit-clarify` or `/speckit-plan`)
 
 **NOTE:** Branch creation is handled by the `before_specify` hook (git extension). Spec directory and file creation are always handled by this core command.
 
@@ -328,3 +338,9 @@ Success criteria must be:
 - "Database can handle 1000 TPS" (implementation detail, use user-facing metric)
 - "React components render efficiently" (framework-specific)
 - "Redis cache hit rate above 80%" (technology-specific)
+
+## Done When
+
+- [ ] Specification written to `SPEC_FILE` and validated against quality checklist
+- [ ] Extension hooks dispatched or skipped according to the rules in Mandatory Post-Execution Hooks above
+- [ ] Completion reported to user with feature directory, spec file path, and checklist results

+ 50 - 37
.claude/skills/speckit-tasks/SKILL.md

@@ -61,6 +61,7 @@ You **MUST** consider the user input before proceeding (if not empty).
 2. **Load design documents**: Read from FEATURE_DIR:
    - **Required**: plan.md (tech stack, libraries, structure), spec.md (user stories with priorities)
    - **Optional**: data-model.md (entities), contracts/ (interface contracts), research.md (decisions), quickstart.md (test scenarios)
+   - **IF EXISTS**: Load `.specify/memory/constitution.md` for project principles and governance constraints
    - Note: Not all projects have all documents. Generate tasks based on what's available.
 
 3. **Execute task generation workflow**:
@@ -87,43 +88,49 @@ You **MUST** consider the user input before proceeding (if not empty).
    - Parallel execution examples per story
    - Implementation strategy section (MVP first, incremental delivery)
 
-5. **Report**: Output path to generated tasks.md and summary:
-   - Total task count
-   - Task count per user story
-   - Parallel opportunities identified
-   - Independent test criteria for each story
-   - Suggested MVP scope (typically just User Story 1)
-   - Format validation: Confirm ALL tasks follow the checklist format (checkbox, ID, labels, file paths)
-
-6. **Check for extension hooks**: After tasks.md is generated, check if `.specify/extensions.yml` exists in the project root.
-   - If it exists, read it and look for entries under the `hooks.after_tasks` key
-   - If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
-   - Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
-   - For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
-     - If the hook has no `condition` field, or it is null/empty, treat the hook as executable
-     - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
-   - When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
-   - For each executable hook, output the following based on its `optional` flag:
-     - **Optional hook** (`optional: true`):
-       ```
-       ## Extension Hooks
-
-       **Optional Hook**: {extension}
-       Command: `/{command}`
-       Description: {description}
-
-       Prompt: {prompt}
-       To execute: `/{command}`
-       ```
-     - **Mandatory hook** (`optional: false`):
-       ```
-       ## Extension Hooks
-
-       **Automatic Hook**: {extension}
-       Executing: `/{command}`
-       EXECUTE_COMMAND: {command}
-       ```
-   - If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
+## Mandatory Post-Execution Hooks
+
+**You MUST complete this section before reporting completion to the user.**
+
+Check if `.specify/extensions.yml` exists in the project root.
+- If it does not exist, or no hooks are registered under `hooks.after_tasks`, skip to the Completion Report.
+- If it exists, read it and look for entries under the `hooks.after_tasks` key.
+- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue to the Completion Report.
+- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
+- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
+  - If the hook has no `condition` field, or it is null/empty, treat the hook as executable
+  - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
+- When constructing slash commands from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/speckit-git-commit`.
+- For each executable hook, output the following based on its `optional` flag:
+  - **Mandatory hook** (`optional: false`) — **You MUST emit `EXECUTE_COMMAND:` for each mandatory hook**:
+    ```
+    ## Extension Hooks
+
+    **Automatic Hook**: {extension}
+    Executing: `/{command}`
+    EXECUTE_COMMAND: {command}
+    ```
+  - **Optional hook** (`optional: true`):
+    ```
+    ## Extension Hooks
+
+    **Optional Hook**: {extension}
+    Command: `/{command}`
+    Description: {description}
+
+    Prompt: {prompt}
+    To execute: `/{command}`
+    ```
+
+## Completion Report
+
+Output path to generated tasks.md and summary:
+- Total task count
+- Task count per user story
+- Parallel opportunities identified
+- Independent test criteria for each story
+- Suggested MVP scope (typically just User Story 1)
+- Format validation: Confirm ALL tasks follow the checklist format (checkbox, ID, labels, file paths)
 
 Context for task generation: $ARGUMENTS
 
@@ -200,3 +207,9 @@ Every task MUST strictly follow this format:
   - Within each story: Tests (if requested) → Models → Services → Endpoints → Integration
   - Each phase should be a complete, independently testable increment
 - **Final Phase**: Polish & Cross-Cutting Concerns
+
+## Done When
+
+- [ ] tasks.md generated with all phases, task IDs, and file paths
+- [ ] Extension hooks dispatched or skipped according to the rules in Mandatory Post-Execution Hooks above
+- [ ] Completion reported to user with task count, story breakdown, and MVP scope

+ 5 - 1
.claude/skills/speckit-taskstoissues/SKILL.md

@@ -57,6 +57,7 @@ You **MUST** consider the user input before proceeding (if not empty).
 ## Outline
 
 1. Run `.specify/scripts/powershell/check-prerequisites.ps1 -Json -RequireTasks -IncludeTasks` 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. **IF EXISTS**: Load `.specify/memory/constitution.md` for project principles and governance constraints.
 1. From the executed script, extract the path to **tasks**.
 1. Get the Git remote by running:
 
@@ -67,7 +68,10 @@ git config --get remote.origin.url
 > [!CAUTION]
 > ONLY PROCEED TO NEXT STEPS IF THE REMOTE IS A GITHUB URL
 
-1. For each task in the list, use the GitHub MCP server to create a new issue in the repository that is representative of the Git remote.
+1. **Fetch existing issues for deduplication**: Before creating anything, build the set of task IDs you are about to process from `tasks.md` (each is a `T` followed by three digits, e.g. `T001`). Then use the GitHub MCP server's `list_issues` tool to look for issues that already cover those IDs. Do not pass a `state` value, since omitting it makes the tool return both open and closed issues. Request `perPage: 100` to keep the number of calls down, and since the tool uses cursor-based pagination, request pages with the `after` parameter (using the `endCursor` from the previous response). For each issue title, match it against the task ID pattern `\bT\d{3}\b` (word boundaries so tokens like `ST001` or `T0010` are not matched by mistake; this also recognises titles written as `T001 ...`, `T001: ...` or `[T001] ...`) and, when it matches one of your task IDs, mark that ID as already having an issue. Stop paginating as soon as every task ID has been matched, or when there are no more pages, so you do not keep fetching the whole repository's issue history once all task IDs are accounted for. This bounds the number of calls on repos with large issue histories and still prevents duplicates when the command is re-run after `tasks.md` is regenerated or the skill is re-invoked.
+1. For each task in the list, use the GitHub MCP server to create a new issue in the repository that is representative of the Git remote. Task lines in `tasks.md` start with a markdown checkbox, so first strip the leading `- [ ]` (and any `[P]` / `[US#]` markers) to recover the task ID and its description. Create the issue with a single canonical title of the form `T001: <description>`, with the ID written once followed by the task description (for example, the line `- [ ] T001 Create project structure` becomes the title `T001: Create project structure`).
+   - **Skip** any task whose ID is already present in the set of existing issues from the previous step, and report it (for example, `T001 already has an issue, skipping`).
+   - Only create issues for tasks that do not yet have a matching issue.
 
 > [!CAUTION]
 > UNDER NO CIRCUMSTANCES EVER CREATE ISSUES IN REPOSITORIES THAT DO NOT MATCH THE REMOTE URL

+ 4 - 0
.specify/extensions/agent-context/agent-context-config.yml

@@ -0,0 +1,4 @@
+context_file: CLAUDE.md
+context_markers:
+  start: <!-- SPECKIT START -->
+  end: <!-- SPECKIT END -->

+ 1 - 2
.specify/init-options.json

@@ -2,9 +2,8 @@
   "ai": "claude",
   "ai_skills": true,
   "branch_numbering": "sequential",
-  "context_file": "CLAUDE.md",
   "here": true,
   "integration": "claude",
   "script": "ps",
-  "speckit_version": "0.8.12"
+  "speckit_version": "0.11.3"
 }

+ 1 - 1
.specify/integration.json

@@ -1,5 +1,5 @@
 {
-  "version": "0.8.12",
+  "version": "0.11.3",
   "integration_state_schema": 1,
   "installed_integrations": [
     "claude"

+ 11 - 10
.specify/integrations/claude.manifest.json

@@ -1,16 +1,17 @@
 {
   "integration": "claude",
-  "version": "0.8.12",
-  "installed_at": "2026-06-01T02:01:40.494115+00:00",
+  "version": "0.11.3",
+  "installed_at": "2026-06-22T01:09:51.726390+00:00",
   "files": {
-    ".claude/skills/speckit-analyze/SKILL.md": "adb9c4baed1d0e27cebd08e506a7247c1357ee721b6ea340201553d5792e6c8a",
-    ".claude/skills/speckit-checklist/SKILL.md": "812b2a102310470141b313670a51e7f33fe4f673a3b8c3d97ddff4abce2e5fbd",
-    ".claude/skills/speckit-clarify/SKILL.md": "0ca2d6dd6eae86d0e02149b1c3df0b7770e5e5dcfc0482f2b7176b12f6f6457d",
+    ".claude/skills/speckit-analyze/SKILL.md": "a7e8dfbe83462514464e35e16d23b55ebd96c2eed0abe4dcee15cb0fc6993847",
+    ".claude/skills/speckit-clarify/SKILL.md": "fd720c2b468f10f89cf4b6dfcff876e735e557cde5f9bcb2ec1fc7079c19051c",
     ".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"
+    ".claude/skills/speckit-implement/SKILL.md": "97a339450a2a81cb63d17cac84e8fdca6be07f0e66c37bcdca6ee87cf1f8d9f2",
+    ".claude/skills/speckit-converge/SKILL.md": "0c16a3220a920425f346d6a5d92c1b9bee10fea4a475b6f49c8864f65c35628f",
+    ".claude/skills/speckit-plan/SKILL.md": "08e693951151193ea45fe4a42079c520e223999eb803e10a89190e070f089949",
+    ".claude/skills/speckit-checklist/SKILL.md": "82a54d9ce969ae87c0034ffce77539bc708290efc26a6160efe928b248c712c4",
+    ".claude/skills/speckit-specify/SKILL.md": "ebe19db388407058a6b078b3a66ea7198cb89795dc75680ba3c457f11bc8bf7a",
+    ".claude/skills/speckit-tasks/SKILL.md": "84c276f7037693ebc69eef74dce0af127280eda1d8e6af18d29ff8440b028d3f",
+    ".claude/skills/speckit-taskstoissues/SKILL.md": "74a522b0a1c3beeeaa18e7cd1fde3ecdc42a05954ad89ad993b62a32ddb826ce"
   }
 }

+ 6 - 6
.specify/integrations/speckit.manifest.json

@@ -1,12 +1,12 @@
 {
   "integration": "speckit",
-  "version": "0.8.12",
+  "version": "0.11.3",
   "installed_at": "2026-04-28T01:07:03.130714+00:00",
   "files": {
-    ".specify/scripts/powershell/check-prerequisites.ps1": "bcb37804b0757c37799b65a9321c1d3fb7b7ddcab6703c55c5b9a142c9166bf1",
-    ".specify/scripts/powershell/common.ps1": "916b3ea1e29ef626a353f0ec011f906c6afff6818a97d560a0321b8557d970ec",
-    ".specify/scripts/powershell/create-new-feature.ps1": "0a7fc929db81b6318205ab3506187762bae1c4d3b2184f15e903ca54b78511e2",
-    ".specify/scripts/powershell/setup-plan.ps1": "137af47d79085d2770714ece307ed8f56f0d4cb1e9ec0c17db5c5c0d0b8a8c4c",
+    ".specify/scripts/powershell/check-prerequisites.ps1": "7302acc26ed7920ed6e8c627be322cd8ae9a46482ead5930f36f5e38aff1f563",
+    ".specify/scripts/powershell/common.ps1": "24b5c490efb01f364971b3aac6618873fb56352de2b58815c95bc8e855afab1e",
+    ".specify/scripts/powershell/create-new-feature.ps1": "1cd5b716e56c1df148cc1c690bff862a2576a42269380c0c61446b0f1dd5172d",
+    ".specify/scripts/powershell/setup-plan.ps1": "d55b161c13b1a83d10035d2601255698aed6c3a629a44bf0bfa3299c7b42729d",
     ".specify/scripts/powershell/update-agent-context.ps1": "42db3401c2869d8c6e4a77bf2e4d744afca9aff472cc2ae5b360aeaccc3c4e07",
     ".specify/templates/agent-file-template.md": "55ed438c2e861444ef22f45fe5238f3ebf0dc1cb6e53067d7232fbbf4ce82892",
     ".specify/templates/checklist-template.md": "c37695297e5d3153d64f82c21223509940b13932046c7961c42d1d669516130c",
@@ -14,6 +14,6 @@
     ".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"
+    ".specify/scripts/powershell/setup-tasks.ps1": "441f3a6cfa1da236411ed6a68ebf754a391515662df0bb74dd6916106d8caf52"
   }
 }

+ 8 - 9
.specify/scripts/powershell/check-prerequisites.ps1

@@ -56,14 +56,10 @@ EXAMPLES:
 # Source common functions
 . "$PSScriptRoot/common.ps1"
 
-# Get feature paths and validate branch
+# Get feature paths
 $paths = Get-FeaturePathsEnv
 
-if (-not (Test-FeatureBranch -Branch $paths.CURRENT_BRANCH -HasGit:$paths.HAS_GIT)) { 
-    exit 1 
-}
-
-# If paths-only mode, output paths and exit (support combined -Json -PathsOnly)
+# If paths-only mode, output paths and exit (no validation)
 if ($PathsOnly) {
     if ($Json) {
         [PSCustomObject]@{
@@ -88,20 +84,23 @@ if ($PathsOnly) {
 # Validate required directories and files
 if (-not (Test-Path $paths.FEATURE_DIR -PathType Container)) {
     Write-Output "ERROR: Feature directory not found: $($paths.FEATURE_DIR)"
-    Write-Output "Run /speckit.specify first to create the feature structure."
+    $specifyCommand = '/speckit-specify'
+    Write-Output "Run $specifyCommand first to create the feature structure."
     exit 1
 }
 
 if (-not (Test-Path $paths.IMPL_PLAN -PathType Leaf)) {
     Write-Output "ERROR: plan.md not found in $($paths.FEATURE_DIR)"
-    Write-Output "Run /speckit.plan first to create the implementation plan."
+    $planCommand = '/speckit-plan'
+    Write-Output "Run $planCommand first to create the implementation plan."
     exit 1
 }
 
 # Check for tasks.md if required
 if ($RequireTasks -and -not (Test-Path $paths.TASKS -PathType Leaf)) {
     Write-Output "ERROR: tasks.md not found in $($paths.FEATURE_DIR)"
-    Write-Output "Run /speckit.tasks first to create the task list."
+    $tasksCommand = '/speckit-tasks'
+    Write-Output "Run $tasksCommand first to create the task list."
     exit 1
 }
 

+ 149 - 234
.specify/scripts/powershell/common.ps1

@@ -24,272 +24,132 @@ function Find-SpecifyRoot {
     }
 }
 
-# Get repository root, prioritizing .specify directory over git
-# This prevents using a parent git repo when spec-kit is initialized in a subdirectory
+# Resolve an explicit SPECIFY_INIT_DIR project override (the directory that
+# *contains* .specify/), for non-interactive / CI use -- e.g. running a Spec Kit
+# command against a member project from a monorepo root without cd.
+#
+# Precondition: $env:SPECIFY_INIT_DIR is set. Returns the validated project root,
+# or writes an error and exits 1. Strict by design: the path must exist and
+# contain .specify/, with no silent fallback. (An empty string is falsy, so the
+# caller's `if ($env:SPECIFY_INIT_DIR)` guard treats empty as unset.)
+#
+# This is the single resolver: bundled extensions inherit it by sourcing core
+# (e.g. the git extension's create-new-feature-branch) rather than duplicating it.
+function Resolve-SpecifyInitDir {
+    $initDir = $env:SPECIFY_INIT_DIR
+    # Normalize: relative paths resolve against the current directory.
+    if (-not [System.IO.Path]::IsPathRooted($initDir)) {
+        $initDir = Join-Path (Get-Location).Path $initDir
+    }
+    $resolved = Resolve-Path -LiteralPath $initDir -ErrorAction SilentlyContinue
+    # Resolve-Path also succeeds for files, so check the resolved path is a
+    # directory; otherwise a file value would slip through to the less accurate
+    # "not a Spec Kit project" error below.
+    if (-not $resolved -or -not (Test-Path -LiteralPath $resolved.Path -PathType Container)) {
+        [Console]::Error.WriteLine("ERROR: SPECIFY_INIT_DIR does not point to an existing directory: $($env:SPECIFY_INIT_DIR)")
+        exit 1
+    }
+    # Resolve-Path echoes back any trailing separator from the input; trim it so
+    # the returned root matches the bash resolver, whose `cd && pwd` never yields
+    # one. TrimEndingDirectorySeparator is a no-op on a bare root and on a path
+    # that already has no trailing separator.
+    $initRoot = [System.IO.Path]::TrimEndingDirectorySeparator($resolved.Path)
+    if (-not (Test-Path -LiteralPath (Join-Path $initRoot '.specify') -PathType Container)) {
+        [Console]::Error.WriteLine("ERROR: SPECIFY_INIT_DIR is not a Spec Kit project (no .specify/ directory): $initRoot")
+        exit 1
+    }
+    return $initRoot
+}
+
+# Get repository root, prioritizing .specify directory
+# This prevents using a parent repository when spec-kit is initialized in a subdirectory
 function Get-RepoRoot {
+    # Explicit project override wins (see Resolve-SpecifyInitDir).
+    if ($env:SPECIFY_INIT_DIR) {
+        return (Resolve-SpecifyInitDir)
+    }
+
     # First, look for .specify directory (spec-kit's own marker)
     $specifyRoot = Find-SpecifyRoot
     if ($specifyRoot) {
         return $specifyRoot
     }
 
-    # Fallback to git if no .specify found
-    try {
-        $result = git rev-parse --show-toplevel 2>$null
-        if ($LASTEXITCODE -eq 0) {
-            return $result
-        }
-    } catch {
-        # Git command failed
-    }
-
-    # Final fallback to script location for non-git repos
+    # Final fallback to script location
     # Use -LiteralPath to handle paths with wildcard characters
     return (Resolve-Path -LiteralPath (Join-Path $PSScriptRoot "../../..")).Path
 }
 
 function Get-CurrentBranch {
-    # First check if SPECIFY_FEATURE environment variable is set
+    # Return feature name from explicit state only.
+    # Feature state is set by SPECIFY_FEATURE (from create-new-feature or
+    # the git extension) or implicitly via .specify/feature.json.
     if ($env:SPECIFY_FEATURE) {
         return $env:SPECIFY_FEATURE
     }
 
-    # Then check git if available at the spec-kit root (not parent)
-    $repoRoot = Get-RepoRoot
-    if (Test-HasGit) {
-        try {
-            $result = git -C $repoRoot rev-parse --abbrev-ref HEAD 2>$null
-            if ($LASTEXITCODE -eq 0) {
-                return $result
-            }
-        } catch {
-            # Git command failed
-        }
-    }
-
-    # For non-git repos, try to find the latest feature directory
-    $specsDir = Join-Path $repoRoot "specs"
-    
-    if (Test-Path $specsDir) {
-        $latestFeature = ""
-        $highest = 0
-        $latestTimestamp = ""
-
-        Get-ChildItem -Path $specsDir -Directory | ForEach-Object {
-            if ($_.Name -match '^(\d{8}-\d{6})-') {
-                # Timestamp-based branch: compare lexicographically
-                $ts = $matches[1]
-                if ($ts -gt $latestTimestamp) {
-                    $latestTimestamp = $ts
-                    $latestFeature = $_.Name
-                }
-            } elseif ($_.Name -match '^(\d{3,})-') {
-                $num = [long]$matches[1]
-                if ($num -gt $highest) {
-                    $highest = $num
-                    # Only update if no timestamp branch found yet
-                    if (-not $latestTimestamp) {
-                        $latestFeature = $_.Name
-                    }
-                }
-            }
-        }
-
-        if ($latestFeature) {
-            return $latestFeature
-        }
-    }
-    
-    # Final fallback
-    return "main"
-}
-
-# Check if we have git available at the spec-kit root level
-# Returns true only if git is installed and the repo root is inside a git work tree
-# Handles both regular repos (.git directory) and worktrees/submodules (.git file)
-function Test-HasGit {
-    # First check if git command is available (before calling Get-RepoRoot which may use git)
-    if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
-        return $false
-    }
-    $repoRoot = Get-RepoRoot
-    # Check if .git exists (directory or file for worktrees/submodules)
-    # Use -LiteralPath to handle paths with wildcard characters
-    if (-not (Test-Path -LiteralPath (Join-Path $repoRoot ".git"))) {
-        return $false
-    }
-    # Verify it's actually a valid git work tree
-    try {
-        $null = git -C $repoRoot rev-parse --is-inside-work-tree 2>$null
-        return ($LASTEXITCODE -eq 0)
-    } catch {
-        return $false
-    }
-}
-
-# 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
+    # No explicit feature set - return empty to signal "unknown".
+    return ""
 }
 
-function Test-FeatureBranch {
-    param(
-        [string]$Branch,
-        [bool]$HasGit = $true
-    )
-    
-    # For non-git repos, we can't enforce branch naming but still provide output
-    if (-not $HasGit) {
-        Write-Warning "[specify] Warning: Git repository not detected; skipped branch validation"
-        return $true
-    }
 
-    $raw = $Branch
-    $Branch = Get-SpecKitEffectiveBranchName $raw
-    
-    # Accept sequential prefix (3+ digits) but exclude malformed timestamps
-    # Malformed: 7-or-8 digit date + 6-digit time with no trailing slug (e.g. "2026031-143022" or "20260319-143022")
-    $hasMalformedTimestamp = ($Branch -match '^[0-9]{7}-[0-9]{6}-') -or ($Branch -match '^(?:\d{7}|\d{8})-\d{6}$')
-    $isSequential = ($Branch -match '^[0-9]{3,}-') -and (-not $hasMalformedTimestamp)
-    if (-not $isSequential -and $Branch -notmatch '^\d{8}-\d{6}-') {
-        [Console]::Error.WriteLine("ERROR: Not on a feature branch. Current branch: $raw")
-        [Console]::Error.WriteLine("Feature branches should be named like: 001-feature-name, 1234-feature-name, or 20260319-143022-feature-name")
-        return $false
-    }
-    return $true
-}
 
-# 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 {
+# Persist a feature_directory value to .specify/feature.json.
+# Writes only when the file is missing or the value differs from what's stored.
+function Save-FeatureJson {
     param(
         [Parameter(Mandatory = $true)][string]$RepoRoot,
-        [Parameter(Mandatory = $true)][string]$ActiveFeatureDir
+        [Parameter(Mandatory = $true)][string]$FeatureDirectory
     )
 
-    $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
+    # Strip repo root prefix if the value is absolute and under repo root.
+    # Use case-insensitive comparison on Windows only (case-sensitive filesystems elsewhere).
+    $prefix = $RepoRoot + [System.IO.Path]::DirectorySeparatorChar
+    if ($null -ne $IsWindows) { $onWin = $IsWindows } else { $onWin = $true }
+    if ($onWin) {
+        $cmp = [System.StringComparison]::OrdinalIgnoreCase
     } else {
-        $normJson = [System.IO.Path]::GetFullPath($fd)
+        $cmp = [System.StringComparison]::Ordinal
     }
-
-    $resolvedActive = Resolve-Path -LiteralPath $ActiveFeatureDir -ErrorAction SilentlyContinue
-    if ($resolvedActive) {
-        $normActive = $resolvedActive.Path
-    } else {
-        $normActive = [System.IO.Path]::GetFullPath($ActiveFeatureDir)
+    if ($FeatureDirectory.StartsWith($prefix, $cmp)) {
+        $FeatureDirectory = $FeatureDirectory.Substring($prefix.Length)
     }
 
-    # 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)
-}
+    $fjPath = Join-Path (Join-Path $RepoRoot '.specify') 'feature.json'
 
-# 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)
+    # Read current value and skip write when unchanged
+    if (Test-Path -LiteralPath $fjPath -PathType Leaf) {
+        try {
+            $raw = Get-Content -LiteralPath $fjPath -Raw
+            $cfg = $raw | ConvertFrom-Json
+            if ($cfg.feature_directory -eq $FeatureDirectory) {
+                return
+            }
+        } catch {
+            # File is corrupt or unreadable - overwrite it
+        }
     }
 
-    $dirMatches = @()
-    if (Test-Path -LiteralPath $specsDir -PathType Container) {
-        $dirMatches = @(Get-ChildItem -LiteralPath $specsDir -Filter "$prefix-*" -Directory -ErrorAction SilentlyContinue)
+    # Ensure .specify/ directory exists
+    $specifyDir = Join-Path $RepoRoot '.specify'
+    if (-not (Test-Path -LiteralPath $specifyDir -PathType Container)) {
+        New-Item -ItemType Directory -Path $specifyDir -Force | Out-Null
     }
 
-    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
+    # Write feature.json
+    $json = @{ feature_directory = $FeatureDirectory } | ConvertTo-Json -Compress
+    $utf8NoBom = New-Object System.Text.UTF8Encoding($false)
+    [System.IO.File]::WriteAllText($fjPath, $json, $utf8NoBom)
 }
 
 function Get-FeaturePathsEnv {
     $repoRoot = Get-RepoRoot
     $currentBranch = Get-CurrentBranch
-    $hasGit = Test-HasGit
 
     # Resolve feature directory.  Priority:
     #   1. SPECIFY_FEATURE_DIRECTORY env var (explicit override)
-    #   2. .specify/feature.json "feature_directory" key (persisted by /speckit.specify)
-    #   3. Branch-name-based prefix lookup (same as scripts/bash/common.sh)
+    #   2. .specify/feature.json "feature_directory" key (persisted by specify command)
+    #   3. Error - no feature context available
     $featureJson = Join-Path $repoRoot '.specify/feature.json'
     if ($env:SPECIFY_FEATURE_DIRECTORY) {
         $featureDir = $env:SPECIFY_FEATURE_DIRECTORY
@@ -297,6 +157,8 @@ function Get-FeaturePathsEnv {
         if (-not [System.IO.Path]::IsPathRooted($featureDir)) {
             $featureDir = Join-Path $repoRoot $featureDir
         }
+        # Persist to feature.json so future sessions without the env var still work
+        Save-FeatureJson -RepoRoot $repoRoot -FeatureDirectory $env:SPECIFY_FEATURE_DIRECTORY
     } elseif (Test-Path $featureJson) {
         $featureJsonRaw = Get-Content -LiteralPath $featureJson -Raw
         try {
@@ -312,16 +174,17 @@ function Get-FeaturePathsEnv {
                 $featureDir = Join-Path $repoRoot $featureDir
             }
         } else {
-            $featureDir = Get-FeatureDirFromBranchPrefixOrExit -RepoRoot $repoRoot -CurrentBranch $currentBranch
+            [Console]::Error.WriteLine("ERROR: Feature directory not found. Set SPECIFY_FEATURE_DIRECTORY or ensure .specify/feature.json contains feature_directory.")
+            exit 1
         }
     } else {
-        $featureDir = Get-FeatureDirFromBranchPrefixOrExit -RepoRoot $repoRoot -CurrentBranch $currentBranch
+        [Console]::Error.WriteLine("ERROR: Feature directory not found. Set SPECIFY_FEATURE_DIRECTORY or run the specify command to create .specify/feature.json.")
+        exit 1
     }
     
     [PSCustomObject]@{
         REPO_ROOT     = $repoRoot
         CURRENT_BRANCH = $currentBranch
-        HAS_GIT       = $hasGit
         FEATURE_DIR   = $featureDir
         FEATURE_SPEC  = Join-Path $featureDir 'spec.md'
         IMPL_PLAN     = Join-Path $featureDir 'plan.md'
@@ -336,10 +199,10 @@ function Get-FeaturePathsEnv {
 function Test-FileExists {
     param([string]$Path, [string]$Description)
     if (Test-Path -Path $Path -PathType Leaf) {
-        Write-Output "   $Description"
+        Write-Output "  [OK] $Description"
         return $true
     } else {
-        Write-Output "   $Description"
+        Write-Output "  [FAIL] $Description"
         return $false
     }
 }
@@ -347,14 +210,66 @@ function Test-FileExists {
 function Test-DirHasFiles {
     param([string]$Path, [string]$Description)
     if ((Test-Path -Path $Path -PathType Container) -and (Get-ChildItem -Path $Path -ErrorAction SilentlyContinue | Where-Object { -not $_.PSIsContainer } | Select-Object -First 1)) {
-        Write-Output "   $Description"
+        Write-Output "  [OK] $Description"
         return $true
     } else {
-        Write-Output "   $Description"
+        Write-Output "  [FAIL] $Description"
         return $false
     }
 }
 
+function Get-InvokeSeparator {
+    param([string]$RepoRoot = (Get-RepoRoot))
+
+    if ($null -eq $script:SpecKitInvokeSeparatorCache) {
+        $script:SpecKitInvokeSeparatorCache = @{}
+    }
+    if ($script:SpecKitInvokeSeparatorCache.ContainsKey($RepoRoot)) {
+        return $script:SpecKitInvokeSeparatorCache[$RepoRoot]
+    }
+
+    $separator = '.'
+    $integrationJson = Join-Path $RepoRoot '.specify/integration.json'
+    if (Test-Path -LiteralPath $integrationJson -PathType Leaf) {
+        try {
+            $state = Get-Content -LiteralPath $integrationJson -Raw | ConvertFrom-Json
+            $key = if ($state.default_integration) { [string]$state.default_integration } elseif ($state.integration) { [string]$state.integration } else { '' }
+            if ($key -and $state.integration_settings) {
+                $settingProperty = $state.integration_settings.PSObject.Properties[$key]
+                if ($settingProperty) {
+                    $setting = $settingProperty.Value
+                    if ($setting -and ($setting.invoke_separator -eq '.' -or $setting.invoke_separator -eq '-')) {
+                        $separator = [string]$setting.invoke_separator
+                    }
+                }
+            }
+        } catch {
+            $separator = '.'
+        }
+    }
+
+    $script:SpecKitInvokeSeparatorCache[$RepoRoot] = $separator
+    return $separator
+}
+
+function Format-SpecKitCommand {
+    param(
+        [Parameter(Mandatory = $true)][string]$CommandName,
+        [string]$RepoRoot = (Get-RepoRoot)
+    )
+
+    $separator = Get-InvokeSeparator -RepoRoot $RepoRoot
+    $name = $CommandName.TrimStart('/')
+    if ($name.StartsWith('speckit.')) {
+        $name = $name.Substring(8)
+    } elseif ($name.StartsWith('speckit-')) {
+        $name = $name.Substring(8)
+    }
+    $name = $name -replace '\.', $separator
+
+    return "/speckit$separator$name"
+}
+
 # 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 {
@@ -591,7 +506,7 @@ except Exception:
 
     if ($layerPaths.Count -eq 0) { return $null }
 
-    # If the top (highest-priority) layer is replace, it wins entirely 
+    # 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)
@@ -640,4 +555,4 @@ except Exception:
     }
 
     return $content
-}
+}

+ 20 - 168
.specify/scripts/powershell/create-new-feature.ps1

@@ -21,9 +21,9 @@ if ($Help) {
     Write-Host ""
     Write-Host "Options:"
     Write-Host "  -Json               Output in JSON format"
-    Write-Host "  -DryRun             Compute branch name and paths without creating branches, directories, or files"
-    Write-Host "  -AllowExistingBranch  Switch to branch if it already exists instead of failing"
-    Write-Host "  -ShortName <name>   Provide a custom short name (2-4 words) for the branch"
+    Write-Host "  -DryRun             Compute feature name and paths without creating directories or files"
+    Write-Host "  -AllowExistingBranch  Reuse an existing feature directory if it already exists"
+    Write-Host "  -ShortName <name>   Provide a custom short name (2-4 words) for the feature"
     Write-Host "  -Number N           Specify branch number manually (overrides auto-detection)"
     Write-Host "  -Timestamp          Use timestamp prefix (YYYYMMDD-HHMMSS) instead of sequential numbering"
     Write-Host "  -Help               Show this help message"
@@ -67,111 +67,17 @@ function Get-HighestNumberFromSpecs {
     return $highest
 }
 
-# Extract the highest sequential feature number from a list of branch/ref names.
-# Shared by Get-HighestNumberFromBranches and Get-HighestNumberFromRemoteRefs.
-function Get-HighestNumberFromNames {
-    param([string[]]$Names)
-
-    [long]$highest = 0
-    foreach ($name in $Names) {
-        if ($name -match '^(\d{3,})-' -and $name -notmatch '^\d{8}-\d{6}-') {
-            [long]$num = 0
-            if ([long]::TryParse($matches[1], [ref]$num) -and $num -gt $highest) {
-                $highest = $num
-            }
-        }
-    }
-    return $highest
-}
-
-function Get-HighestNumberFromBranches {
-    param()
-
-    try {
-        $branches = git branch -a 2>$null
-        if ($LASTEXITCODE -eq 0 -and $branches) {
-            $cleanNames = $branches | ForEach-Object {
-                $_.Trim() -replace '^\*?\s+', '' -replace '^remotes/[^/]+/', ''
-            }
-            return Get-HighestNumberFromNames -Names $cleanNames
-        }
-    } catch {
-        Write-Verbose "Could not check Git branches: $_"
-    }
-    return 0
-}
-
-function Get-HighestNumberFromRemoteRefs {
-    [long]$highest = 0
-    try {
-        $remotes = git remote 2>$null
-        if ($remotes) {
-            foreach ($remote in $remotes) {
-                $env:GIT_TERMINAL_PROMPT = '0'
-                $refs = git ls-remote --heads $remote 2>$null
-                $env:GIT_TERMINAL_PROMPT = $null
-                if ($LASTEXITCODE -eq 0 -and $refs) {
-                    $refNames = $refs | ForEach-Object {
-                        if ($_ -match 'refs/heads/(.+)$') { $matches[1] }
-                    } | Where-Object { $_ }
-                    $remoteHighest = Get-HighestNumberFromNames -Names $refNames
-                    if ($remoteHighest -gt $highest) { $highest = $remoteHighest }
-                }
-            }
-        }
-    } catch {
-        Write-Verbose "Could not query remote refs: $_"
-    }
-    return $highest
-}
-
-# Return next available branch number. When SkipFetch is true, queries remotes
-# via ls-remote (read-only) instead of fetching.
-function Get-NextBranchNumber {
-    param(
-        [string]$SpecsDir,
-        [switch]$SkipFetch
-    )
-
-    if ($SkipFetch) {
-        # Side-effect-free: query remotes via ls-remote
-        $highestBranch = Get-HighestNumberFromBranches
-        $highestRemote = Get-HighestNumberFromRemoteRefs
-        $highestBranch = [Math]::Max($highestBranch, $highestRemote)
-    } else {
-        # Fetch all remotes to get latest branch info (suppress errors if no remotes)
-        try {
-            git fetch --all --prune 2>$null | Out-Null
-        } catch {
-            # Ignore fetch errors
-        }
-        $highestBranch = Get-HighestNumberFromBranches
-    }
-
-    # Get highest number from ALL specs (not just matching short name)
-    $highestSpec = Get-HighestNumberFromSpecs -SpecsDir $SpecsDir
-
-    # Take the maximum of both
-    $maxNum = [Math]::Max($highestBranch, $highestSpec)
-
-    # Return next number
-    return $maxNum + 1
-}
-
 function ConvertTo-CleanBranchName {
     param([string]$Name)
 
     return $Name.ToLower() -replace '[^a-z0-9]', '-' -replace '-{2,}', '-' -replace '^-', '' -replace '-$', ''
 }
-# Load common functions (includes Get-RepoRoot, Test-HasGit, Resolve-Template)
+# Load common functions (includes Get-RepoRoot and Resolve-Template)
 . "$PSScriptRoot/common.ps1"
 
-# Use common.ps1 functions which prioritize .specify over git
+# Use common.ps1 functions which prioritize .specify
 $repoRoot = Get-RepoRoot
 
-# Check if git is available at this repo root (not a parent)
-$hasGit = Test-HasGit
-
 Set-Location $repoRoot
 
 $specsDir = Join-Path $repoRoot 'specs'
@@ -244,21 +150,9 @@ if ($Timestamp) {
     $featureNum = Get-Date -Format 'yyyyMMdd-HHmmss'
     $branchName = "$featureNum-$branchSuffix"
 } else {
-    # Determine branch number
+    # Determine branch number from existing feature directories
     if ($Number -eq 0) {
-        if ($DryRun -and $hasGit) {
-            # Dry-run: query remotes via ls-remote (side-effect-free, no fetch)
-            $Number = Get-NextBranchNumber -SpecsDir $specsDir -SkipFetch
-        } elseif ($DryRun) {
-            # Dry-run without git: local spec dirs only
-            $Number = (Get-HighestNumberFromSpecs -SpecsDir $specsDir) + 1
-        } elseif ($hasGit) {
-            # Check existing branches on remotes
-            $Number = Get-NextBranchNumber -SpecsDir $specsDir
-        } else {
-            # Fall back to local directory check
-            $Number = (Get-HighestNumberFromSpecs -SpecsDir $specsDir) + 1
-        }
+        $Number = (Get-HighestNumberFromSpecs -SpecsDir $specsDir) + 1
     }
 
     $featureNum = ('{0:000}' -f $Number)
@@ -291,58 +185,13 @@ $featureDir = Join-Path $specsDir $branchName
 $specFile = Join-Path $featureDir 'spec.md'
 
 if (-not $DryRun) {
-    if ($hasGit) {
-        $branchCreated = $false
-        $branchCreateError = ''
-        try {
-            $branchCreateError = git checkout -q -b $branchName 2>&1 | Out-String
-            if ($LASTEXITCODE -eq 0) {
-                $branchCreated = $true
-            }
-        } catch {
-            $branchCreateError = $_.Exception.Message
-        }
-
-        if (-not $branchCreated) {
-            $currentBranch = ''
-            try { $currentBranch = (git rev-parse --abbrev-ref HEAD 2>$null).Trim() } catch {}
-            # Check if branch already exists
-            $existingBranch = git branch --list $branchName 2>$null
-            if ($existingBranch) {
-                if ($AllowExistingBranch) {
-                    # If we're already on the branch, continue without another checkout.
-                    if ($currentBranch -eq $branchName) {
-                        # Already on the target branch — nothing to do
-                    } else {
-                        # Otherwise switch to the existing branch instead of failing.
-                        $switchBranchError = git checkout -q $branchName 2>&1 | Out-String
-                        if ($LASTEXITCODE -ne 0) {
-                            if ($switchBranchError) {
-                                Write-Error "Error: Branch '$branchName' exists but could not be checked out.`n$($switchBranchError.Trim())"
-                            } else {
-                                Write-Error "Error: Branch '$branchName' exists but could not be checked out. Resolve any uncommitted changes or conflicts and try again."
-                            }
-                            exit 1
-                        }
-                    }
-                } elseif ($Timestamp) {
-                    Write-Error "Error: Branch '$branchName' already exists. Rerun to get a new timestamp or use a different -ShortName."
-                    exit 1
-                } else {
-                    Write-Error "Error: Branch '$branchName' already exists. Please use a different feature name or specify a different number with -Number."
-                    exit 1
-                }
-            } else {
-                if ($branchCreateError) {
-                    Write-Error "Error: Failed to create git branch '$branchName'.`n$($branchCreateError.Trim())"
-                } else {
-                    Write-Error "Error: Failed to create git branch '$branchName'. Please check your git configuration and try again."
-                }
-                exit 1
-            }
+    if ((Test-Path -LiteralPath $featureDir -PathType Container) -and -not $AllowExistingBranch) {
+        if ($Timestamp) {
+            Write-Error "Error: Feature directory '$featureDir' already exists. Rerun to get a new timestamp or use a different -ShortName."
+        } else {
+            Write-Error "Error: Feature directory '$featureDir' already exists. Please use a different feature name or specify a different number with -Number."
         }
-    } else {
-        Write-Warning "[specify] Warning: Git repository not detected; skipped branch creation for $branchName"
+        exit 1
     }
 
     New-Item -ItemType Directory -Path $featureDir -Force | Out-Null
@@ -359,8 +208,12 @@ if (-not $DryRun) {
         }
     }
 
-    # Set the SPECIFY_FEATURE environment variable for the current session
+    # Persist to .specify/feature.json so downstream commands can find the feature
+    Save-FeatureJson -RepoRoot $repoRoot -FeatureDirectory $featureDir
+
+    # Set environment variables for the current session
     $env:SPECIFY_FEATURE = $branchName
+    $env:SPECIFY_FEATURE_DIRECTORY = $featureDir
 }
 
 if ($Json) {
@@ -368,7 +221,6 @@ if ($Json) {
         BRANCH_NAME = $branchName
         SPEC_FILE = $specFile
         FEATURE_NUM = $featureNum
-        HAS_GIT = $hasGit
     }
     if ($DryRun) {
         $obj | Add-Member -NotePropertyName 'DRY_RUN' -NotePropertyValue $true
@@ -378,8 +230,8 @@ if ($Json) {
     Write-Output "BRANCH_NAME: $branchName"
     Write-Output "SPEC_FILE: $specFile"
     Write-Output "FEATURE_NUM: $featureNum"
-    Write-Output "HAS_GIT: $hasGit"
     if (-not $DryRun) {
-        Write-Output "SPECIFY_FEATURE environment variable set to: $branchName"
+        Write-Output "SPECIFY_FEATURE set to: $branchName"
+        Write-Output "SPECIFY_FEATURE_DIRECTORY set to: $featureDir"
     }
 }

+ 18 - 19
.specify/scripts/powershell/setup-plan.ps1

@@ -23,27 +23,28 @@ if ($Help) {
 # Get all paths and variables from common functions
 $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
-    }
-}
-
 # Ensure the feature directory exists
 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)) { 
-    # 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)
+# Copy plan template if plan doesn't already exist
+if (Test-Path $paths.IMPL_PLAN -PathType Leaf) {
+    if ($Json) {
+        [Console]::Error.WriteLine("Plan already exists at $($paths.IMPL_PLAN), skipping template copy")
+    } else {
+        Write-Output "Plan already exists at $($paths.IMPL_PLAN), skipping template copy"
+    }
 } else {
-    Write-Warning "Plan template not found"
-    # Create a basic plan file if template doesn't exist
-    New-Item -ItemType File -Path $paths.IMPL_PLAN -Force | Out-Null
+    $template = Resolve-Template -TemplateName 'plan-template' -RepoRoot $paths.REPO_ROOT
+    if ($template -and (Test-Path $template)) {
+        # 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
+        New-Item -ItemType File -Path $paths.IMPL_PLAN -Force | Out-Null
+    }
 }
 
 # Output results
@@ -53,7 +54,6 @@ if ($Json) {
         IMPL_PLAN = $paths.IMPL_PLAN
         SPECS_DIR = $paths.FEATURE_DIR
         BRANCH = $paths.CURRENT_BRANCH
-        HAS_GIT = $paths.HAS_GIT
     }
     $result | ConvertTo-Json -Compress
 } else {
@@ -61,5 +61,4 @@ if ($Json) {
     Write-Output "IMPL_PLAN: $($paths.IMPL_PLAN)"
     Write-Output "SPECS_DIR: $($paths.FEATURE_DIR)"
     Write-Output "BRANCH: $($paths.CURRENT_BRANCH)"
-    Write-Output "HAS_GIT: $($paths.HAS_GIT)"
 }

+ 5 - 10
.specify/scripts/powershell/setup-tasks.ps1

@@ -16,25 +16,20 @@ if ($Help) {
 # Source common functions
 . "$PSScriptRoot/common.ps1"
 
-# Get feature paths and validate branch
+# Get feature paths
 $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.")
+    $planCommand = '/speckit-plan'
+    [Console]::Error.WriteLine("Run $planCommand 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.")
+    $specifyCommand = '/speckit-specify'
+    [Console]::Error.WriteLine("Run $specifyCommand first to create the feature structure.")
     exit 1
 }
 

+ 1 - 2
CLAUDE.md

@@ -152,6 +152,5 @@ Strong success criteria let you loop independently. Weak criteria ("make it work
 
 <!-- SPECKIT START -->
 For additional context about technologies to be used, project structure,
-shell commands, and other important information, read the current plan:
-`specs/010-order-invoice/plan.md` (订单 ezPay 电子发票开立)
+shell commands, and other important information, read the current plan
 <!-- SPECKIT END -->

+ 4 - 0
ruoyi-admin/src/main/java/com/ruoyi/app/order/PosOrderShOprateController.java

@@ -63,6 +63,8 @@ public class PosOrderShOprateController extends BaseController {
     /**
      * 商家端查询订单发票(状态/发票号/凭证)
      */
+    @Auth
+    @Anonymous
     @GetMapping("/getInvoice")
     public AjaxResult getInvoice(@RequestHeader String token, @RequestParam Long orderId) {
         Long userId = Long.valueOf(new JwtUtil().getusid(token));
@@ -72,6 +74,8 @@ public class PosOrderShOprateController extends BaseController {
     /**
      * 商家端作废订单发票(仅已开,调 ezPay invoice_invalid)。入参可选 {invalidReason}
      */
+    @Auth
+    @Anonymous
     @PutMapping("/invalidInvoice/{orderId}")
     public AjaxResult invalidInvoice(@RequestHeader String token, @PathVariable Long orderId,
                                      @RequestBody(required = false) InvalidInvoiceDto dto) {

+ 4 - 0
ruoyi-admin/src/main/java/com/ruoyi/app/order/UserOrderController.java

@@ -77,6 +77,8 @@ public class UserOrderController extends BaseController {
     /**
      * 申请电子发票(订单完成后,客户主动申请:B2C 邮箱 / B2B 统编 / 载具)
      */
+    @Auth
+    @Anonymous
     @PostMapping("/applyInvoice")
     public AjaxResult applyInvoice(@RequestHeader String token, @RequestBody @Valid ApplyInvoiceDto dto) {
         Long userId = Long.valueOf(new JwtUtil().getusid(token));
@@ -86,6 +88,8 @@ public class UserOrderController extends BaseController {
     /**
      * 查询订单电子发票(状态 / 发票号 / 凭证);门店不可开票时 invoiceStatus=null
      */
+    @Auth
+    @Anonymous
     @GetMapping("/getInvoice")
     public AjaxResult getInvoice(@RequestHeader String token, @RequestParam Long orderId) {
         Long userId = Long.valueOf(new JwtUtil().getusid(token));