Compare commits

..

10 Commits

Author SHA1 Message Date
github-actions[bot]
cee80f7841 chore: bump version to 0.8.15 2026-05-27 11:28:33 +00:00
Manfred Riem
7556fc7fe7 Update Fiction Book Writing preset to v1.8.1 (#2714)
Update fiction-book-writing preset submitted by @adaumann to:
- presets/catalog.community.json (version, download_url, provides, updated_at)
- docs/community/presets.md community presets table

Changes from v1.7.0:
- 25 templates (was 22), 33 commands (was 27), 2 scripts
- RAG search stability update

Closes #2691
2026-05-26 17:21:10 -05:00
Hamilton Snow
98b8bb6eb7 chore: update memorylint and superb to 1.4.0 (#2690)
* chore(catalog): update memorylint and superb to 1.4.0

* chore(catalog): keep existing extension descriptions
2026-05-26 17:05:17 -05:00
Manfred Riem
7a7843b68b fix: promote post-execution hook dispatch to H2 with directive language (#2713)
* fix: promote post-execution hook dispatch to H2 with directive language

Restructure the after_* hook dispatch in all five core command templates
(specify, clarify, implement, plan, tasks) to make hook execution
structurally unmissable by LLMs during interactive slash-command flows.

Changes applied consistently across all five templates:

1. Promote hook block from a final numbered step to a top-level
   '## Mandatory Post-Execution Hooks' H2, placed before the completion
   report. An H2 boundary is salient in a way that a numbered sub-step
   buried after 'Report completion' is not.

2. Use directive language for mandatory hooks: 'You MUST emit
   EXECUTE_COMMAND: for each mandatory hook' and 'You MUST complete this
   section before reporting completion to the user.' The previous
   conditional framing ('check if', 'based on its optional flag') buried
   the mandatory cases.

3. List mandatory hooks before optional hooks in the dispatch block, so
   the required action appears first.

4. Add a terminal '## Done When' verification checklist at the end of
   each template, including 'Extension hooks dispatched' as an explicit
   completion criterion. This gives the model a structured opportunity to
   verify completion before exiting.

5. Extract the completion report into its own '## Completion Report' H2
   section, clearly separated from the hook dispatch.

These changes preserve the interactive-vs-workflow distinction and do not
introduce auto-run. They raise the reliability of the existing
best-effort dispatch mechanism.

Fixes #2688

* fix: address review — narrow Done When wording and move to end of templates

- Narrow the hooks checklist item from a broad condition to 'dispatched
  or skipped according to the rules in Mandatory Post-Execution Hooks
  above' so it does not contradict the filtering rules for disabled
  hooks or hooks with non-empty conditions.

- Move the Done When section to the actual end of specify.md, plan.md,
  and tasks.md so it does not signal premature completion before the
  agent reads required guidance sections (Quick Guidelines, Phases/Key
  rules, Task Generation Rules).

* fix: update stale step 8 reference in specify.md validation flow

The old 'proceed to step 8' pointed to the completion report step that
was renumbered when hook dispatch was promoted to its own H2 section.
Update the reference to point to 'Mandatory Post-Execution Hooks'.
2026-05-26 16:57:03 -05:00
Manfred Riem
7e9d470144 Add Token Budget extension to community catalog (#2712)
* Add Token Budget extension to community catalog

Add token-budget extension submitted by @tinesoft to:
- extensions/catalog.community.json (alphabetical order)
- docs/community/extensions.md community extensions table

Closes #2687

* Fix alphabetical order: Token Budget before Token Consumption Analyzer

* Fix tools entry: use 'python3' instead of 'python3 + tiktoken'
2026-05-26 15:38:56 -05:00
Manfred Riem
e54653efcc fix: create skills directory on demand during extension/preset install (#2711)
* fix: create skills directory on demand during extension/preset install

_get_skills_dir() in both extensions.py and presets.py returned None
when the skills directory did not yet exist on disk, even though skills
were enabled in init-options. This caused extension skill registration
to silently produce an empty registered_skills list and skip writing
SKILL.md files.

Replace the is_dir() bail-out with mkdir(parents=True, exist_ok=True)
so the directory is created on demand when ai_skills is enabled.

Update the existing test expectation and add a parametrized regression
test (claude + codex) that installs an extension before the skills
directory exists and asserts SKILL.md files and registry entries are
created.

Fixes #2682

* test: assert skills dir is NOT created when skills are disabled

Strengthen negative tests to verify _get_skills_dir does not create the
directory on disk when ai_skills is false or init-options.json is absent.

* fix: add symlink/containment check and preserve Kimi existence gate

Address PR review feedback:

- Use _ensure_safe_shared_directory() instead of raw mkdir() to prevent
  symlink-following writes outside the project root.
- Restore the is_dir() existence gate for the Kimi native-skills
  fallback (ai_skills=false): only create the directory on demand when
  ai_skills is explicitly enabled.
- Update docstrings to reflect the on-demand vs existence-gate behavior.
- Reuse resolve_skills_dir helper in tests instead of manually
  reconstructing paths from AGENT_CONFIG.

* refactor: extract resolve_active_skills_dir shared helper

Deduplicate the _get_skills_dir logic that was nearly identical in
ExtensionManager and PresetManager into a single module-level
resolve_active_skills_dir() function in __init__.py.

The shared helper wraps _ensure_safe_shared_directory errors with
skills-specific messages so users see 'agent skills directory' instead
of 'shared infrastructure directory' in error output.

Both class methods now delegate to the shared helper.

* fix: preserve original error reason in skills dir safety check

Include the original exception message from _ensure_safe_shared_directory
in the re-raised ValueError so the user sees the specific reason (symlink,
not-a-directory, path escape, etc.) instead of a generic message.

* fix: handle skills dir safety errors gracefully during install

Catch ValueError/OSError from _get_skills_dir() inside
_register_extension_skills() so a symlink or permission error logs a
warning and returns [] instead of aborting mid-install and leaving a
partially-installed extension without a registry entry.

Also document OSError in resolve_active_skills_dir() docstring.

* fix: catch errors in _get_skills_dir and use _print_cli_warning

Move the ValueError/OSError catch from _register_extension_skills into
_get_skills_dir itself so all callers (install, uninstall, reconcile)
are protected from unsafe-path exceptions.

Replace logging.getLogger().warning with _print_cli_warning for
consistent Rich-formatted user output.

* fix: use context-aware error messages for skills directory safety

Add a 'context' parameter to _ensure_safe_shared_directory (defaults to
'shared infrastructure directory' for backward compat). The skills dir
caller passes context='agent skills directory' so error messages say
e.g. 'Refusing to use symlinked agent skills directory' instead of
'Refusing to use symlinked shared infrastructure directory'.

Simplify resolve_active_skills_dir by removing the now-unnecessary
try/except wrapper.

* fix: validate Kimi native-skills directory for symlink/containment

The Kimi fallback path (ai_skills=false) used is_dir() which follows
symlinks, so a symlinked .kimi/skills could cause writes outside the
project root. Now validates with _ensure_safe_shared_directory(create=
False) before returning the directory.
2026-05-26 15:10:59 -05:00
Copilot
c7e0cacaff fix: PS 5.1 compat — replace non-ASCII chars in shipped PowerShell scripts (#2709)
* Initial plan

* fix: replace non-ASCII chars in PS1 files, add encoding regression tests, fix ANSI stripping in tests, update docs

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-05-26 13:18:34 -05:00
Thorsten Hindermann
0f9beabca7 docs: update security-governance preset to v0.3.0 (#2676) 2026-05-26 12:40:15 -05:00
Davit Mnatobishvili
69b9348776 Update README.md (#2675) 2026-05-26 11:04:22 -05:00
Manfred Riem
c47f334629 chore: release 0.8.14, begin 0.8.15.dev0 development (#2706)
* chore: bump version to 0.8.14

* chore: begin 0.8.15.dev0 development

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-26 09:19:56 -05:00
26 changed files with 507 additions and 252 deletions

View File

@@ -2,6 +2,20 @@
<!-- insert new changelog below this comment -->
## [0.8.15] - 2026-05-27
### Changed
- Update Fiction Book Writing preset to v1.8.1 (#2714)
- chore: update memorylint and superb to 1.4.0 (#2690)
- fix: promote post-execution hook dispatch to H2 with directive language (#2713)
- Add Token Budget extension to community catalog (#2712)
- fix: create skills directory on demand during extension/preset install (#2711)
- fix: PS 5.1 compat — replace non-ASCII chars in shipped PowerShell scripts (#2709)
- docs: update security-governance preset to v0.3.0 (#2676)
- Update README.md (#2675)
- chore: release 0.8.14, begin 0.8.15.dev0 development (#2706)
## [0.8.14] - 2026-05-26
### Changed

View File

@@ -35,7 +35,7 @@
- [🔧 Prerequisites](#-prerequisites)
- [📖 Learn More](#-learn-more)
- [📋 Detailed Process](#-detailed-process)
- [ Support](#-support)
- [💬 Support](#-support)
- [🙏 Acknowledgements](#-acknowledgements)
- [📄 License](#-license)
@@ -581,7 +581,7 @@ Once the implementation is complete, test the application and resolve any runtim
---
## Support
## 💬 Support
For support, please open a [GitHub issue](https://github.com/github/spec-kit/issues/new). We welcome bug reports, feature requests, and questions about using Spec-Driven Development.

View File

@@ -116,6 +116,7 @@ The following community-contributed extensions are available in [`catalog.commun
| Team Assign | Assign tasks.md items to human engineers, split into subtasks, and generate a per-engineer workboard | `process` | Read+Write | [spec-kit-team-assign](https://github.com/tarunkumarbhati/spec-kit-team-assign) |
| Time Machine | Retroactively apply the full SDD workflow to existing codebases — analyse, spec, and ship feature-by-feature | `process` | Read+Write | [spec-kit-time-machine](https://github.com/teeyo/spec-kit-time-machine) |
| TinySpec | Lightweight single-file workflow for small tasks — skip the heavy multi-step SDD process | `process` | Read+Write | [spec-kit-tinyspec](https://github.com/Quratulain-bilal/spec-kit-tinyspec) |
| Token Budget | Reduces LLM token consumption in Spec Kit workflows: compact artifacts in-place, scope per-phase reading, suppress prose padding, and report token usage | `process` | Read+Write | [spec-kit-token-budget](https://github.com/tinesoft/spec-kit-token-budget) |
| Token Consumption Analyzer | Captures, analyzes, and compares token consumption across SDD workflows | `visibility` | Read-only | [spec-kit-token-analyzer](https://github.com/coderandhiker/spec-kit-token-analyzer) |
| V-Model Extension Pack | Enforces V-Model paired generation of development specs and test specs with full traceability | `docs` | Read+Write | [spec-kit-v-model](https://github.com/leocamello/spec-kit-v-model) |
| Verify Extension | Post-implementation quality gate that validates implemented code against specification artifacts | `code` | Read-only | [spec-kit-verify](https://github.com/ismaelJimenez/spec-kit-verify) |

View File

@@ -15,7 +15,7 @@ The following community-contributed presets customize how Spec Kit behaves — o
| Claude AskUserQuestion | Upgrades `/speckit.clarify` and `/speckit.checklist` on Claude Code from Markdown-table prompts to the native AskUserQuestion picker, with a recommended option and reasoning on every question | 2 commands | — | [spec-kit-preset-claude-ask-questions](https://github.com/0xrafasec/spec-kit-preset-claude-ask-questions) |
| Cross-Platform Governance | Adds Bash/PowerShell parity, dry-run/WhatIf parity, Unix man-page expectations, PowerShell comment-based help, and Verb-Noun Cmdlet discipline | 8 templates, 3 commands | — | [spec-kit-preset-cross-platform-governance](https://github.com/hindermath/spec-kit-preset-cross-platform-governance) |
| Explicit Task Dependencies | Adds explicit `(depends on T###)` dependency declarations and an Execution Wave DAG to tasks.md for parallel scheduling | 1 template, 1 command | — | [spec-kit-preset-explicit-task-dependencies](https://github.com/Quratulain-bilal/spec-kit-preset-explicit-task-dependencies) |
| Fiction Book Writing | It adapts the Spec-Driven Development workflow for storytelling to create books or audiobooks (with annotations) in 12 languages: features become story elements, specs become story briefs, plans become story structures, and tasks become scene-by-scene writing tasks. Supports single and multi-POV, all major plot structure frameworks, and two style modes: an author voice sample or humanized AI prose. Supports interactive elements like brainstorming, interview, roleplay and extras like statistics, cover builder and bio command. Export with templates for KDP, D2D etc. | 22 templates, 27 commands, 2 scripts | — | [speckit-preset-fiction-book-writing](https://github.com/adaumann/speckit-preset-fiction-book-writing) |
| Fiction Book Writing | It adapts the Spec-Driven Development workflow for storytelling to create books or audiobooks (with annotations) in 12 languages: features become story elements, specs become story briefs, plans become story structures, and tasks become scene-by-scene writing tasks. Supports single and multi-POV, all major plot structure frameworks, and two style modes: an author voice sample or humanized AI prose principles. Supports interactive elements like brainstorming, interview, roleplay and extras like statistics, cover builder and bio command. Export with templates for KDP, D2D etc. | 25 templates, 33 commands, 2 scripts | — | [speckit-preset-fiction-book-writing](https://github.com/adaumann/speckit-preset-fiction-book-writing) |
| Game Narrative Writing | Spec-Driven Development for interactive game narrative pre-production for video games. Authors write in a portable generic format, Twine/Sugarcube (.twee) or Ink (.ink). Covers choice-IF, visual novels, and branching dialogue. Supports Tier 1 mechanic hooks (flag, counter, inventory, timer, trust, currency, npc_state, ending_condition), multi-ending design, series carry-over variable registry, and NPC-focused character architecture. | 22 templates, 36 commands, 2 scripts | — | [speckit-preset-game-narrative-writing](https://github.com/adaumann/speckit-preset-game-narrative-writing) |
| iSAQB Architecture Governance | Adds general iSAQB/CPSA-F and arc42 architecture governance: goals, context, building blocks, runtime and deployment views, quality scenarios, ADRs, risks, and technical debt | 13 templates, 3 commands | — | [spec-kit-preset-isaqb-architecture-governance](https://github.com/hindermath/spec-kit-preset-isaqb-architecture-governance) |
| Jira Issue Tracking | Overrides `speckit.taskstoissues` to create Jira epics, stories, and tasks instead of GitHub Issues via Atlassian MCP tools | 1 command | — | [spec-kit-preset-jira](https://github.com/luno/spec-kit-preset-jira) |
@@ -23,7 +23,7 @@ The following community-contributed presets customize how Spec Kit behaves — o
| Multi-Repo Branching | Coordinates feature branch creation across multiple git repositories (independent repos and submodules) during plan and tasks phases | 2 commands | — | [spec-kit-preset-multi-repo-branching](https://github.com/sakitA/spec-kit-preset-multi-repo-branching) |
| Pirate Speak (Full) | Transforms all Spec Kit output into pirate speak — specs become "Voyage Manifests", plans become "Battle Plans", tasks become "Crew Assignments" | 6 templates, 9 commands | — | [spec-kit-presets](https://github.com/mnriem/spec-kit-presets) |
| Screenwriting | Spec-Driven Development for screenwriting/scriptwriting/tutorials: feature films, television (pilot, episode, limited series), and stage plays. Adapts the Spec Kit workflow to screenplay craft — slug lines, action lines, act breaks, beat sheets, and industry-standard pitch documents. Supports three-act, Save the Cat, TV pilot, network episode, cable/streaming episode, and stage-play structural frameworks. Export to Fountain, FTX, PDF | 26 templates, 32 commands, 1 script | — | [speckit-preset-screenwriting](https://github.com/adaumann/speckit-preset-screenwriting) |
| Security Governance | Adds secure development governance: memory-safe-language preference, secure code generation, NIST SSDF, CWE Top 25, OWASP ASVS, SBOM/VEX/SLSA, OpenSSF Scorecard, and EU CRA applicability | 12 templates, 3 commands | — | [spec-kit-preset-security-governance](https://github.com/hindermath/spec-kit-preset-security-governance) |
| Security Governance | Adds secure development governance: memory-safe-language preference, secure code generation, NIST SSDF, CWE Top 25, OWASP ASVS, SBOM/AI-SBOM, VEX/SLSA, OpenSSF Scorecard, G7/BSI AI-SBOM target evidence, and EU CRA applicability | 12 templates, 3 commands | — | [spec-kit-preset-security-governance](https://github.com/hindermath/spec-kit-preset-security-governance) |
| Spec2Cloud | Spec-driven workflow tuned for shipping to Azure: spec → plan → tasks → implement → deploy | 5 templates, 8 commands | — | [spec2cloud](https://github.com/Azure-Samples/Spec2Cloud) |
| Table of Contents Navigation | Adds a navigable Table of Contents to generated spec.md, plan.md, and tasks.md documents | 3 templates, 3 commands | — | [spec-kit-preset-toc-navigation](https://github.com/Quratulain-bilal/spec-kit-preset-toc-navigation) |
| VS Code Ask Questions | Enhances the clarify command to use `vscode/askQuestions` for batched interactive questioning. | 1 command | — | [spec-kit-presets](https://github.com/fdcastel/spec-kit-presets) |

View File

@@ -1646,8 +1646,8 @@
"id": "memorylint",
"description": "Agent memory governance tool: Automatically audits and fixes boundary conflicts between AGENTS.md and the constitution.",
"author": "RbBtSn0w",
"version": "1.3.0",
"download_url": "https://github.com/RbBtSn0w/spec-kit-extensions/releases/download/memorylint-v1.3.0/memorylint.zip",
"version": "1.4.0",
"download_url": "https://github.com/RbBtSn0w/spec-kit-extensions/releases/download/memorylint-v1.4.0/memorylint.zip",
"repository": "https://github.com/RbBtSn0w/spec-kit-extensions",
"homepage": "https://github.com/RbBtSn0w/spec-kit-extensions/tree/main/memorylint",
"documentation": "https://github.com/RbBtSn0w/spec-kit-extensions/blob/main/memorylint/README.md",
@@ -1657,8 +1657,8 @@
"speckit_version": ">=0.5.1"
},
"provides": {
"commands": 1,
"hooks": 1
"commands": 2,
"hooks": 3
},
"tags": [
"memory",
@@ -1671,7 +1671,7 @@
"downloads": 0,
"stars": 0,
"created_at": "2026-04-09T00:00:00Z",
"updated_at": "2026-04-16T13:10:26Z"
"updated_at": "2026-05-24T01:06:49Z"
},
"multi-model-review": {
"name": "Multi-Model Review",
@@ -2894,8 +2894,8 @@
"id": "superb",
"description": "Orchestrates obra/superpowers skills within the spec-kit SDD workflow. Thin bridge commands delegate to superpowers' authoritative SKILL.md files at runtime (with graceful fallback), while bridge-original commands provide spec-kit-native value. Eight commands cover the full lifecycle: intent clarification, TDD enforcement, task review, verification, critique, systematic debugging, branch completion, and review response. Hook-bound commands fire automatically; standalone commands are invoked when needed.",
"author": "rbbtsn0w",
"version": "1.3.0",
"download_url": "https://github.com/RbBtSn0w/spec-kit-extensions/releases/download/superpowers-bridge-v1.3.0/superpowers-bridge.zip",
"version": "1.4.0",
"download_url": "https://github.com/RbBtSn0w/spec-kit-extensions/releases/download/superpowers-bridge-v1.4.0/superpowers-bridge.zip",
"repository": "https://github.com/RbBtSn0w/spec-kit-extensions",
"homepage": "https://github.com/RbBtSn0w/spec-kit-extensions",
"documentation": "https://github.com/RbBtSn0w/spec-kit-extensions/blob/main/superpowers-bridge/README.md",
@@ -2913,7 +2913,7 @@
},
"provides": {
"commands": 8,
"hooks": 4
"hooks": 3
},
"tags": [
"methodology",
@@ -2930,7 +2930,7 @@
"downloads": 0,
"stars": 0,
"created_at": "2026-03-30T00:00:00Z",
"updated_at": "2026-04-16T14:08:23Z"
"updated_at": "2026-05-24T01:07:34Z"
},
"superpowers-bridge": {
"name": "Superpowers Bridge",
@@ -3160,6 +3160,48 @@
"created_at": "2026-05-01T00:00:00Z",
"updated_at": "2026-05-01T00:00:00Z"
},
"token-budget": {
"name": "Token Budget",
"id": "token-budget",
"description": "Reduces LLM token consumption in Spec Kit workflows: compact artifacts in-place, scope per-phase reading, suppress prose padding, and report token usage.",
"author": "Tine Kondo",
"version": "1.0.1",
"download_url": "https://github.com/tinesoft/spec-kit-token-budget/archive/refs/tags/v1.0.1.zip",
"repository": "https://github.com/tinesoft/spec-kit-token-budget",
"homepage": "https://github.com/tinesoft/spec-kit-token-budget",
"documentation": "https://github.com/tinesoft/spec-kit-token-budget/blob/main/README.md",
"changelog": "https://github.com/tinesoft/spec-kit-token-budget/blob/main/CHANGELOG.md",
"license": "MIT",
"requires": {
"speckit_version": ">=0.1.0",
"tools": [
{
"name": "python3",
"required": false
},
{
"name": "rtk",
"required": false
}
]
},
"provides": {
"commands": 4,
"hooks": 6
},
"tags": [
"tokens",
"budget",
"context",
"efficiency",
"cost-optimization"
],
"verified": false,
"downloads": 0,
"stars": 0,
"created_at": "2026-05-26T00:00:00Z",
"updated_at": "2026-05-26T00:00:00Z"
},
"v-model": {
"name": "V-Model Extension Pack",
"id": "v-model",

View File

@@ -35,7 +35,7 @@ Replace the script to add project-specific Git initialization steps:
## Output
On success:
- ` Git repository initialized`
- `[OK] Git repository initialized`
## Graceful Degradation

View File

@@ -115,7 +115,7 @@ if (Test-Path $configFile) {
}
}
} else {
# No config file auto-commit disabled by default
# No config file -- auto-commit disabled by default
exit 0
}

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env pwsh
# Git-specific common functions for the git extension.
# Extracted from scripts/powershell/common.ps1 contains only git-specific
# Extracted from scripts/powershell/common.ps1 -- contains only git-specific
# branch validation and detection logic.
function Test-HasGit {

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env pwsh
# Git extension: initialize-repo.ps1
# Initialize a Git repository with an initial commit.
# Customizable replace this script to add .gitignore templates,
# Customizable -- replace this script to add .gitignore templates,
# default branch config, git-flow, LFS, signing, etc.
$ErrorActionPreference = 'Stop'
@@ -66,4 +66,4 @@ try {
exit 1
}
Write-Host " Git repository initialized"
Write-Host "[OK] Git repository initialized"

View File

@@ -1,6 +1,6 @@
{
"schema_version": "1.0",
"updated_at": "2026-05-05T10:00:00Z",
"updated_at": "2026-05-26T00:00:00Z",
"catalog_url": "https://raw.githubusercontent.com/github/spec-kit/main/presets/catalog.community.json",
"presets": {
"a11y-governance": {
@@ -222,11 +222,11 @@
"fiction-book-writing": {
"name": "Fiction Book Writing",
"id": "fiction-book-writing",
"version": "1.7.0",
"description": "Spec-Driven Development for novel and long-form fiction. 27 AI commands from idea to submission: story bible governance, 9 POV modes, all major plot structure frameworks, scene-by-scene drafting with quality gates, audiobook pipeline (SSML/ElevenLabs), cover design, sensitivity review, pacing and prose statistics, and pandoc-based export to DOCX/EPUB/LaTeX. Two style modes: author voice sample extraction or humanized-AI prose with 5 craft profiles. 12 languages supported. Support for offline semantic search.",
"version": "1.8.1",
"description": "Spec-Driven Development for novel and long-form fiction. 33 AI commands from idea to submission: story bible governance, 9 POV modes, all major plot structure frameworks, scene-by-scene drafting with quality gates, audiobook pipeline (SSML/ElevenLabs), cover design, sensitivity review, pacing and prose statistics, and pandoc-based export to DOCX/EPUB/LaTeX. Two style modes: author voice sample extraction or humanized-AI prose with 5 craft profiles. 12 languages supported. Support for offline semantic search.",
"author": "Andreas Daumann",
"repository": "https://github.com/adaumann/speckit-preset-fiction-book-writing",
"download_url": "https://github.com/adaumann/speckit-preset-fiction-book-writing/archive/refs/tags/v1.7.0.zip",
"download_url": "https://github.com/adaumann/speckit-preset-fiction-book-writing/archive/refs/tags/v1.8.1.zip",
"homepage": "https://github.com/adaumann/speckit-preset-fiction-book-writing",
"documentation": "https://github.com/adaumann/speckit-preset-fiction-book-writing/blob/main/fiction-book-writing/README.md",
"license": "MIT",
@@ -234,8 +234,8 @@
"speckit_version": ">=0.5.0"
},
"provides": {
"templates": 22,
"commands": 27,
"templates": 25,
"commands": 33,
"scripts": 2
},
"tags": [
@@ -254,7 +254,7 @@
"language-support"
],
"created_at": "2026-04-09T08:00:00Z",
"updated_at": "2026-04-27T08:00:00Z"
"updated_at": "2026-05-24T08:00:00Z"
},
"game-narrative-writing": {
"name": "Game Narrative Writing",
@@ -472,11 +472,11 @@
"security-governance": {
"name": "Security Governance",
"id": "security-governance",
"version": "0.2.0",
"description": "Adds secure development governance, MSL preference, ASVS verification, supply-chain transparency, and EU CRA awareness.",
"version": "0.3.0",
"description": "Adds memory-safe-language preference, secure code generation, ASVS verification, SBOM/AI-SBOM supply-chain transparency, and EU Cyber Resilience Act awareness.",
"author": "Thorsten Hindermann",
"repository": "https://github.com/hindermath/spec-kit-preset-security-governance",
"download_url": "https://github.com/hindermath/spec-kit-preset-security-governance/archive/refs/tags/v0.2.0.zip",
"download_url": "https://github.com/hindermath/spec-kit-preset-security-governance/archive/refs/tags/v0.3.0.zip",
"homepage": "https://github.com/hindermath/spec-kit-preset-security-governance",
"documentation": "https://github.com/hindermath/spec-kit-preset-security-governance/blob/main/README.md",
"license": "MIT",
@@ -491,11 +491,20 @@
"security",
"governance",
"msl",
"ssdf",
"asvs",
"supply-chain"
"supply-chain",
"sbom",
"ai-sbom",
"vex",
"slsa",
"cwe-top-25",
"g7",
"bsi",
"cra"
],
"created_at": "2026-04-27T00:00:00Z",
"updated_at": "2026-04-27T00:00:00Z"
"updated_at": "2026-05-22T00:00:00Z"
},
"spec2cloud": {
"name": "Spec2Cloud",

View File

@@ -1,6 +1,6 @@
[project]
name = "specify-cli"
version = "0.8.14"
version = "0.8.15"
description = "Specify CLI, part of GitHub Spec Kit. A tool to bootstrap your projects for Spec-Driven Development (SDD)."
requires-python = ">=3.11"
dependencies = [

View File

@@ -336,10 +336,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,10 +347,10 @@ 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
}
}
@@ -591,7 +591,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)

View File

@@ -312,7 +312,7 @@ if (-not $DryRun) {
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
# 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

View File

@@ -317,6 +317,57 @@ def _get_skills_dir(project_path: Path, selected_ai: str) -> Path:
return project_path / ".agents" / "skills"
def resolve_active_skills_dir(project_root: Path) -> Path | None:
"""Return the active skills directory, creating it on demand when enabled.
Reads ``.specify/init-options.json`` to determine whether skills are
enabled and which agent was selected. When ``ai_skills`` is true the
directory is created safely (symlink/containment checks); when false
only Kimi's native-skills fallback is honoured (directory must already
exist).
Returns:
The skills directory ``Path``, or ``None`` if skills are not active.
Raises:
ValueError: If the resolved skills path escapes the project root,
a parent component is a symlink, or a path component exists
but is not a directory.
OSError: If the directory cannot be created (e.g. permission denied).
"""
from .shared_infra import _ensure_safe_shared_directory
opts = load_init_options(project_root)
if not isinstance(opts, dict):
opts = {}
agent = opts.get("ai")
if not isinstance(agent, str) or not agent:
return None
ai_skills_enabled = bool(opts.get("ai_skills"))
if not ai_skills_enabled and agent != "kimi":
return None
skills_dir = _get_skills_dir(project_root, agent)
if not ai_skills_enabled:
# Kimi native-skills fallback: use the directory only if it exists.
if not skills_dir.is_dir():
return None
_ensure_safe_shared_directory(
project_root, skills_dir,
create=False, context="agent skills directory",
)
return skills_dir
# ai_skills is explicitly enabled — create the directory safely.
_ensure_safe_shared_directory(
project_root, skills_dir, context="agent skills directory",
)
return skills_dir
def _cli_error_detail(exc: BaseException) -> str:
"""Return a compact one-line exception detail for CLI output."""
detail = str(exc).replace("\n", " ").strip()

View File

@@ -801,38 +801,24 @@ class ExtensionManager:
def _get_skills_dir(self) -> Optional[Path]:
"""Return the active skills directory for extension skill registration.
Reads ``.specify/init-options.json`` to determine whether skills
are enabled and which agent was selected, then delegates to
the module-level ``_get_skills_dir()`` helper for the concrete path.
Delegates to :func:`resolve_active_skills_dir` which reads
init-options, applies the Kimi native-skills fallback, and
safely creates the directory when ``ai_skills`` is enabled.
Kimi is treated as a native-skills agent: if ``ai == "kimi"`` and
``.kimi/skills`` exists, extension installs should still propagate
command skills even when ``ai_skills`` is false.
Returns:
The skills directory ``Path``, or ``None`` if skills were not
enabled and no native-skills fallback applies.
Returns ``None`` (instead of raising) when the directory cannot
be created due to symlink, containment, or permission issues so
that callers can fall back gracefully.
"""
from . import load_init_options, _get_skills_dir as resolve_skills_dir
opts = load_init_options(self.project_root)
if not isinstance(opts, dict):
opts = {}
agent = opts.get("ai")
if not isinstance(agent, str) or not agent:
from . import resolve_active_skills_dir, _print_cli_warning
try:
return resolve_active_skills_dir(self.project_root)
except (ValueError, OSError) as exc:
_print_cli_warning(
"resolve", "skills directory", None, exc,
continuing="Continuing without skill registration.",
)
return None
ai_skills_enabled = bool(opts.get("ai_skills"))
if not ai_skills_enabled and agent != "kimi":
return None
skills_dir = resolve_skills_dir(self.project_root, agent)
if not skills_dir.is_dir():
return None
return skills_dir
def _register_extension_skills(
self,
manifest: ExtensionManifest,

View File

@@ -1097,37 +1097,24 @@ class PresetManager:
def _get_skills_dir(self) -> Optional[Path]:
"""Return the active skills directory for preset skill overrides.
Reads ``.specify/init-options.json`` to determine whether skills
are enabled and which agent was selected, then delegates to
the module-level ``_get_skills_dir()`` helper for the concrete path.
Delegates to :func:`resolve_active_skills_dir` which reads
init-options, applies the Kimi native-skills fallback, and
safely creates the directory when ``ai_skills`` is enabled.
Kimi is treated as a native-skills agent: if ``ai == "kimi"`` and
``.kimi/skills`` exists, presets should still propagate command
overrides to skills even when ``ai_skills`` is false.
Returns:
The skills directory ``Path``, or ``None`` if skills were not
enabled and no native-skills fallback applies.
Returns ``None`` (instead of raising) when the directory cannot
be created due to symlink, containment, or permission issues so
that callers can fall back gracefully.
"""
from . import load_init_options, _get_skills_dir
opts = load_init_options(self.project_root)
if not isinstance(opts, dict):
opts = {}
agent = opts.get("ai")
if not isinstance(agent, str) or not agent:
from . import resolve_active_skills_dir, _print_cli_warning
try:
return resolve_active_skills_dir(self.project_root)
except (ValueError, OSError) as exc:
_print_cli_warning(
"resolve", "skills directory", None, exc,
continuing="Continuing without skill registration.",
)
return None
ai_skills_enabled = bool(opts.get("ai_skills"))
if not ai_skills_enabled and agent != "kimi":
return None
skills_dir = _get_skills_dir(self.project_root, agent)
if not skills_dir.is_dir():
return None
return skills_dir
@staticmethod
def _skill_names_for_command(cmd_name: str) -> tuple[str, str]:
"""Return the modern and legacy skill directory names for a command."""

View File

@@ -88,7 +88,13 @@ def _shared_relative_path(project_path: Path, dest: Path) -> Path:
return rel
def _ensure_safe_shared_directory(project_path: Path, directory: Path, *, create: bool = True) -> None:
def _ensure_safe_shared_directory(
project_path: Path,
directory: Path,
*,
create: bool = True,
context: str = "shared infrastructure directory",
) -> None:
"""Create a shared infra directory without following symlinked parents."""
root = project_path.resolve()
rel = _shared_relative_path(project_path, directory)
@@ -98,24 +104,24 @@ def _ensure_safe_shared_directory(project_path: Path, directory: Path, *, create
current = current / part
label = _shared_destination_label(project_path, current)
if current.is_symlink():
raise SymlinkedSharedPathError(f"Refusing to use symlinked shared infrastructure directory: {label}")
raise SymlinkedSharedPathError(f"Refusing to use symlinked {context}: {label}")
if current.exists():
if not current.is_dir():
raise ValueError(f"Shared infrastructure directory path is not a directory: {label}")
raise ValueError(f"{context.capitalize()} path is not a directory: {label}")
try:
current.resolve().relative_to(root)
except (OSError, ValueError):
raise ValueError(f"Shared infrastructure directory escapes project root: {label}") from None
raise ValueError(f"{context.capitalize()} escapes project root: {label}") from None
continue
if not create:
raise ValueError(f"Shared infrastructure directory does not exist: {label}")
raise ValueError(f"{context.capitalize()} does not exist: {label}")
current.mkdir()
if current.is_symlink():
raise SymlinkedSharedPathError(f"Refusing to use symlinked shared infrastructure directory: {label}")
raise SymlinkedSharedPathError(f"Refusing to use symlinked {context}: {label}")
try:
current.resolve().relative_to(root)
except (OSError, ValueError):
raise ValueError(f"Shared infrastructure directory escapes project root: {label}") from None
raise ValueError(f"{context.capitalize()} escapes project root: {label}") from None
def _validate_safe_shared_directory(project_path: Path, directory: Path) -> None:

View File

@@ -197,14 +197,6 @@ Execution steps:
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_COMMAND_PLAN__` or run `__SPECKIT_COMMAND_CLARIFY__` again later post-plan.
- Suggested next command.
Behavior rules:
- If no meaningful ambiguities found (or all potential questions would be low-impact), respond: "No critical ambiguities detected worth formal clarification." and suggest proceeding.
@@ -217,17 +209,27 @@ Behavior rules:
Context for prioritization: {ARGS}
## 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
- 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
@@ -239,12 +241,19 @@ 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).
- 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_COMMAND_PLAN__` or run `__SPECKIT_COMMAND_CLARIFY__` again later post-plan.
- Suggested next command.
## Done When
- [ ] Spec ambiguities identified and clarifications integrated into spec file
- [ ] Extension hooks dispatched or skipped according to the rules in Mandatory Post-Execution Hooks above
- [ ] Completion reported to user with questions answered, sections touched, and coverage summary

View File

@@ -168,35 +168,49 @@ 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_COMMAND_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
- For each executable hook, output the following based on its `optional` flag:
- **Optional hook** (`optional: true`):
```
## Extension Hooks
## Mandatory Post-Execution Hooks
**Optional Hook**: {extension}
Command: `/{command}`
Description: {description}
**You MUST complete this section before reporting completion to the user.**
Prompt: {prompt}
To execute: `/{command}`
```
- **Mandatory hook** (`optional: false`):
```
## Extension Hooks
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
- 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}
```
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
**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

View File

@@ -70,36 +70,42 @@ 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.
## Mandatory Post-Execution Hooks
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
- For each executable hook, output the following based on its `optional` flag:
- **Optional hook** (`optional: true`):
```
## Extension Hooks
**You MUST complete this section before reporting completion to the user.**
**Optional Hook**: {extension}
Command: `/{command}`
Description: {description}
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
- 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
Prompt: {prompt}
To execute: `/{command}`
```
- **Mandatory hook** (`optional: false`):
```
## Extension Hooks
**Automatic Hook**: {extension}
Executing: `/{command}`
EXECUTE_COMMAND: {command}
```
- **Optional hook** (`optional: true`):
```
## Extension Hooks
**Automatic Hook**: {extension}
Executing: `/{command}`
EXECUTE_COMMAND: {command}
```
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
**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
@@ -150,3 +156,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

View File

@@ -183,7 +183,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
@@ -228,40 +228,46 @@ 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_COMMAND_CLARIFY__` or `__SPECKIT_COMMAND_PLAN__`)
## Mandatory Post-Execution Hooks
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
- For each executable hook, output the following based on its `optional` flag:
- **Optional hook** (`optional: true`):
```
## Extension Hooks
**You MUST complete this section before reporting completion to the user.**
**Optional Hook**: {extension}
Command: `/{command}`
Description: {description}
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
- 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
Prompt: {prompt}
To execute: `/{command}`
```
- **Mandatory hook** (`optional: false`):
```
## Extension Hooks
**Automatic Hook**: {extension}
Executing: `/{command}`
EXECUTE_COMMAND: {command}
```
- **Optional hook** (`optional: true`):
```
## Extension Hooks
**Automatic Hook**: {extension}
Executing: `/{command}`
EXECUTE_COMMAND: {command}
```
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
**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_COMMAND_CLARIFY__` or `__SPECKIT_COMMAND_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.
@@ -325,3 +331,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

View File

@@ -89,42 +89,48 @@ 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)
## Mandatory Post-Execution Hooks
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
- For each executable hook, output the following based on its `optional` flag:
- **Optional hook** (`optional: true`):
```
## Extension Hooks
**You MUST complete this section before reporting completion to the user.**
**Optional Hook**: {extension}
Command: `/{command}`
Description: {description}
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
- 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
Prompt: {prompt}
To execute: `/{command}`
```
- **Mandatory hook** (`optional: false`):
```
## Extension Hooks
**Automatic Hook**: {extension}
Executing: `/{command}`
EXECUTE_COMMAND: {command}
```
- **Optional hook** (`optional: true`):
```
## Extension Hooks
**Automatic Hook**: {extension}
Executing: `/{command}`
EXECUTE_COMMAND: {command}
```
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
**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: {ARGS}
@@ -201,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

View File

@@ -335,10 +335,11 @@ class TestInitIntegrationFlag:
_install_shared_infra(project, "sh", force=False)
captured = capsys.readouterr()
assert "already exist and were not updated" in captured.out
assert "specify init --here --force" in captured.out
plain = strip_ansi(captured.out)
assert "already exist and were not updated" in plain
assert "specify init --here --force" in plain
# Rich may wrap long lines; normalize whitespace for the second command
normalized = " ".join(captured.out.split())
normalized = " ".join(plain.split())
assert "specify integration upgrade --force" in normalized
def test_shared_infra_warns_when_manifest_cannot_be_loaded(self, tmp_path, capsys):

View File

@@ -7,6 +7,7 @@ import pytest
from typer.testing import CliRunner
from specify_cli import app
from tests.conftest import strip_ansi
runner = CliRunner()
@@ -49,7 +50,8 @@ def _write_invalid_manifest(project, key):
def _integration_list_row_cells(output: str, key: str) -> list[str]:
row = next(line for line in output.splitlines() if line.startswith(f"{key}"))
plain = strip_ansi(output)
row = next(line for line in plain.splitlines() if line.startswith(f"{key}"))
return [cell.strip() for cell in row.split("")[1:-1]]
@@ -160,8 +162,9 @@ class TestIntegrationInstall:
finally:
os.chdir(old_cwd)
assert result.exit_code == 0
assert "already installed" in result.output
normalized = " ".join(result.output.split())
plain = strip_ansi(result.output)
assert "already installed" in plain
normalized = " ".join(plain.split())
assert "specify integration upgrade copilot" in normalized
assert "already the default integration" in normalized
assert "No files were changed" in normalized
@@ -197,9 +200,10 @@ class TestIntegrationInstall:
finally:
os.chdir(old_cwd)
assert result.exit_code != 0
assert "Installed integrations: copilot" in result.output
assert "Default integration: copilot" in result.output
normalized = " ".join(result.output.split())
plain = strip_ansi(result.output)
assert "Installed integrations: copilot" in plain
assert "Default integration: copilot" in plain
normalized = " ".join(plain.split())
assert "To replace the default integration" in normalized
assert "specify integration switch claude" in normalized
assert "To install 'claude' alongside" in normalized
@@ -309,9 +313,10 @@ class TestIntegrationInstall:
finally:
os.chdir(old_cwd)
assert result.exit_code != 0
assert "Installed integrations: copilot" in result.output
assert "multi-install safe" in result.output
normalized = " ".join(result.output.split())
plain = strip_ansi(result.output)
assert "Installed integrations: copilot" in plain
assert "multi-install safe" in plain
normalized = " ".join(plain.split())
assert "To replace the default integration" in normalized
assert "specify integration switch claude" in normalized
assert "To install 'claude' alongside" in normalized

View File

@@ -173,24 +173,32 @@ class TestExtensionManagerGetSkillsDir:
assert result == skills_dir
def test_returns_none_when_no_ai_skills(self, no_skills_project):
"""Should return None when ai_skills is false."""
"""Should return None when ai_skills is false and not create the dir."""
manager = ExtensionManager(no_skills_project)
result = manager._get_skills_dir()
assert result is None
# Ensure the directory was NOT created on disk
from specify_cli import _get_skills_dir as resolve_skills_dir
skills_path = resolve_skills_dir(no_skills_project, "claude")
assert not skills_path.exists()
def test_returns_none_when_no_init_options(self, project_dir):
"""Should return None when init-options.json is missing."""
"""Should return None when init-options.json is missing and not create any dir."""
manager = ExtensionManager(project_dir)
result = manager._get_skills_dir()
assert result is None
# No agent skills directory should have been created
assert not (project_dir / ".claude" / "skills").exists()
assert not (project_dir / ".agents" / "skills").exists()
def test_returns_none_when_skills_dir_missing(self, project_dir):
"""Should return None when skills dir doesn't exist on disk."""
def test_creates_skills_dir_on_demand(self, project_dir):
"""Should create skills dir when ai_skills is enabled but dir is missing."""
_create_init_options(project_dir, ai="claude", ai_skills=True)
# Don't create the skills directory
# Don't create the skills directory — _get_skills_dir should do it
manager = ExtensionManager(project_dir)
result = manager._get_skills_dir()
assert result is None
assert result is not None
assert result.is_dir()
def test_returns_kimi_skills_dir_when_ai_skills_disabled(self, project_dir):
"""Kimi should still use its native skills dir when ai_skills is false."""
@@ -460,6 +468,38 @@ class TestExtensionSkillRegistration:
assert "speckit-missing-cmd-ext-exists" in metadata["registered_skills"]
assert "speckit-missing-cmd-ext-ghost" not in metadata["registered_skills"]
@pytest.mark.parametrize("ai", ["claude", "codex"])
def test_skills_registered_when_dir_missing(self, project_dir, temp_dir, ai):
"""Extension add should create skills dir on demand and register skills.
Regression test for https://github.com/github/spec-kit/issues/2682:
when an extension is installed before the agent skills directory exists,
skills must still be materialized (the directory is created on demand).
"""
_create_init_options(project_dir, ai=ai, ai_skills=True)
# Deliberately do NOT create the skills directory
ext_dir = _create_extension_dir(temp_dir, ext_id="early-ext")
manager = ExtensionManager(project_dir)
manifest = manager.install_from_directory(
ext_dir, "0.1.0", register_commands=False
)
# Skills dir should have been created automatically
from specify_cli import _get_skills_dir as resolve_skills_dir
skills_dir = resolve_skills_dir(project_dir, ai)
assert skills_dir.is_dir()
# SKILL.md files should exist
assert (skills_dir / "speckit-early-ext-hello" / "SKILL.md").exists()
assert (skills_dir / "speckit-early-ext-world" / "SKILL.md").exists()
# Registry should record them
metadata = manager.registry.get(manifest.id)
assert len(metadata["registered_skills"]) == 2
assert "speckit-early-ext-hello" in metadata["registered_skills"]
assert "speckit-early-ext-world" in metadata["registered_skills"]
# ===== Extension Skill Unregistration Tests =====

View File

@@ -0,0 +1,54 @@
"""Regression tests for PowerShell 5.1 compatibility (GitHub issue #2680).
PowerShell 5.1 (built-in on Windows) defaults to the system's legacy encoding
when reading .ps1 files. Non-ASCII characters in UTF-8-encoded scripts cause
parse errors because multi-byte sequences are misinterpreted as individual bytes.
These tests ensure that all shipped .ps1 files remain ASCII-only so they work
on both PowerShell 5.1 and 7+.
"""
from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parent.parent
# All directories that contain shipped PowerShell scripts.
_PS1_DIRS = [
REPO_ROOT / "scripts" / "powershell",
REPO_ROOT / "extensions" / "git" / "scripts" / "powershell",
]
def _collect_ps1_files():
"""Yield all .ps1 files under the known script directories."""
for d in _PS1_DIRS:
if d.is_dir():
yield from sorted(d.rglob("*.ps1"))
_PS1_FILES = list(_collect_ps1_files())
@pytest.mark.parametrize("ps1_file", _PS1_FILES, ids=lambda p: str(p.relative_to(REPO_ROOT)))
def test_ps1_file_is_ascii_only(ps1_file: Path):
"""Every .ps1 file must contain only ASCII characters (PS 5.1 compat)."""
content = ps1_file.read_bytes()
non_ascii = [
(i + 1, byte)
for i, byte in enumerate(content)
if byte > 127
]
assert not non_ascii, (
f"{ps1_file.relative_to(REPO_ROOT)} contains non-ASCII bytes "
f"(PowerShell 5.1 incompatible): "
f"first at byte offset {non_ascii[0][0]} (0x{non_ascii[0][1]:02x})"
)
def test_ps1_files_discovered():
"""Sanity check: at least the known script files are found."""
names = {p.name for p in _PS1_FILES}
assert "common.ps1" in names
assert "initialize-repo.ps1" in names