mirror of
https://github.com/github/spec-kit.git
synced 2026-08-15 14:02:04 +02:00
Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f20678dfd2 | ||
|
|
197dde6253 | ||
|
|
f2583e675c | ||
|
|
996b24c561 | ||
|
|
b77ca572ca | ||
|
|
c1bceb625c | ||
|
|
7dd706880e | ||
|
|
bd595cf838 | ||
|
|
85d3ed289d | ||
|
|
5ec937e07a | ||
|
|
6461f09483 | ||
|
|
fb427e47ef | ||
|
|
bd04776dcc | ||
|
|
c5258e680b | ||
|
|
9d15554c08 | ||
|
|
c3bbcc40a0 | ||
|
|
39b56626c9 |
@@ -16,6 +16,8 @@ body:
|
||||
- If you host a bundle catalog, test catalog installation with `specify bundle catalog add <catalog-url> --id <catalog-id> --policy install-allowed` and `specify bundle install <bundle-id>`
|
||||
- If your bundle depends on components from non-default catalogs, document those catalog URLs and test installation from a clean project
|
||||
|
||||
**After submitting:** a maintainer applies the `bundle-submission` label during issue triage, which starts the automated catalog validation. You don't need to apply any label or ask for one.
|
||||
|
||||
- type: input
|
||||
id: bundle-id
|
||||
attributes:
|
||||
|
||||
@@ -14,6 +14,8 @@ body:
|
||||
- Create a GitHub release with a version tag (e.g., v1.0.0)
|
||||
- Test installation: `specify extension add <extension-name> --from <your-release-url>`
|
||||
|
||||
**After submitting:** a maintainer applies the `extension-submission` label during issue triage, which starts the automated catalog validation. You don't need to apply any label or ask for one.
|
||||
|
||||
- type: input
|
||||
id: extension-id
|
||||
attributes:
|
||||
|
||||
@@ -14,6 +14,8 @@ body:
|
||||
- Create a GitHub release with a version tag (e.g., v1.0.0)
|
||||
- Test installation from the release archive: `specify preset add --from <download-url>`
|
||||
|
||||
**After submitting:** a maintainer applies the `preset-submission` label during issue triage, which starts the automated catalog validation. You don't need to apply any label or ask for one.
|
||||
|
||||
- type: input
|
||||
id: preset-id
|
||||
attributes:
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
name: "Catalog: Auto-assign submission"
|
||||
name: "Catalog: Notify submission"
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, labeled]
|
||||
|
||||
jobs:
|
||||
assign:
|
||||
notify:
|
||||
if: >
|
||||
(github.event.action == 'opened' && (
|
||||
contains(github.event.issue.labels.*.name, 'extension-submission') ||
|
||||
@@ -24,24 +24,8 @@ jobs:
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
|
||||
with:
|
||||
script: |
|
||||
const issue = context.payload.issue;
|
||||
const assigned = (issue.assignees || []).map(a => a.login);
|
||||
const marker = '<!-- catalog-assign-bot -->';
|
||||
|
||||
// Assign mnriem if not already assigned
|
||||
if (!assigned.includes('mnriem')) {
|
||||
try {
|
||||
await github.rest.issues.addAssignees({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
assignees: ['mnriem'],
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(`Warning: could not assign mnriem: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Post team notification if not already posted
|
||||
const comments = await github.paginate(
|
||||
github.rest.issues.listComments,
|
||||
|
||||
@@ -283,9 +283,11 @@ specify bundle validate --path ./my-bundle # structural + reference checks
|
||||
specify bundle build --path ./my-bundle # produce a versioned .zip artifact
|
||||
```
|
||||
|
||||
Four ready-to-read example manifests live under
|
||||
Four ready-to-read example bundle manifests live under
|
||||
[`examples/bundles/`](examples/bundles/) (product manager, business analyst,
|
||||
security researcher, developer).
|
||||
security researcher, developer). These are bundle packaging examples, not
|
||||
filled generated feature specs; for end-to-end community examples, see the
|
||||
[community walkthroughs](https://github.github.io/spec-kit/community/walkthroughs.html).
|
||||
|
||||
Key guarantees: `info` shows exactly what `install` adds (transparency);
|
||||
installs are idempotent and confined to the project root; `remove` never touches
|
||||
|
||||
@@ -34,7 +34,7 @@ The following community-contributed extensions are available in [`catalog.commun
|
||||
| Architect Impact Previewer | Predicts architectural impact, complexity, and risks of proposed changes before implementation. | `visibility` | Read-only | [spec-kit-architect-preview](https://github.com/UmmeHabiba1312/spec-kit-architect-preview) |
|
||||
| Architecture Guard | Framework-agnostic architecture review extension for validating implementation against governance and architecture constitutions, detecting architectural drift, and generating non-blocking refactor tasks | `process` | Read+Write | [spec-kit-architecture-guard](https://github.com/DyanGalih/spec-kit-architecture-guard) |
|
||||
| Architecture Workflow | Generate or reverse project-level 4+1 architecture views with per-view and full-workflow commands | `docs` | Read+Write | [spec-kit-arch](https://github.com/bigsmartben/spec-kit-arch) |
|
||||
| Archive Extension | Archive merged features into main project memory. | `docs` | Read+Write | [spec-kit-archive](https://github.com/stn1slv/spec-kit-archive) |
|
||||
| Archive Extension | Archive merged features into main project memory, resolving gaps and conflicts. | `docs` | Read+Write | [spec-kit-archive](https://github.com/stn1slv/spec-kit-archive) |
|
||||
| Azure DevOps Integration | Sync user stories and tasks to Azure DevOps work items using OAuth authentication | `integration` | Read+Write | [spec-kit-azure-devops](https://github.com/pragya247/spec-kit-azure-devops) |
|
||||
| Blueprint | Stay code-literate in AI-driven development: review a complete code blueprint for every task from spec artifacts before /speckit.implement runs | `docs` | Read+Write | [spec-kit-blueprint](https://github.com/chordpli/spec-kit-blueprint) |
|
||||
| Blueprint Index — Living Architecture Map | A living architecture map for spec-driven projects, kept honest by a deterministic, low-friction, machine-first CI gate (JSON, self-healable) that blocks only when the map contradicts the specs or code. Brownfield or greenfield. | `process` | Read+Write | [spec-kit-blueprint](https://github.com/ogil109/spec-kit-blueprint) |
|
||||
@@ -146,6 +146,7 @@ The following community-contributed extensions are available in [`catalog.commun
|
||||
| Spec-Kit BDD | ATDD/BDD extension: convert specs to Gherkin scenarios, scaffold step definitions, and verify acceptance test coverage | `process` | Read+Write | [spec-kit-bdd](https://github.com/RSginer/spec-kit-bdd) |
|
||||
| Spec2Cloud | Spec-driven workflow tuned for shipping to Azure | `process` | Read+Write | [spec2cloud](https://github.com/Azure-Samples/Spec2Cloud) |
|
||||
| SpecKit Companion | Live spec-driven progress — lifecycle capture, status, resume, and a turbo pipeline profile | `visibility` | Read+Write | [speckit-companion](https://github.com/alfredoperez/speckit-companion) |
|
||||
| SpecKit Grill Me | Exhaustively resolve specification ambiguities and decisions before planning | `process` | Read+Write | [speckit-grill-me](https://github.com/yoshi1220/speckit-grill-me) |
|
||||
| SpecTest | Auto-generate test scaffolds from spec criteria, map coverage, and find untested requirements | `code` | Read+Write | [spec-kit-spectest](https://github.com/Quratulain-bilal/spec-kit-spectest) |
|
||||
| Squad Bridge | Bootstrap and synchronize a Squad agent team from your Speckit spec and tasks. | `process` | Read+Write | [spec-kit-squad](https://github.com/jwill824/spec-kit-squad) |
|
||||
| Staff Review Extension | Staff-engineer-level code review that validates implementation against spec, checks security, performance, and test coverage | `code` | Read-only | [spec-kit-staff-review](https://github.com/arunt14/spec-kit-staff-review) |
|
||||
|
||||
@@ -7,10 +7,10 @@ The following community-contributed presets customize how Spec Kit behaves — o
|
||||
|
||||
| Preset | Purpose | Provides | Requires | URL |
|
||||
|--------|---------|----------|----------|-----|
|
||||
| A11Y Governance | Adds accessibility (WCAG 2.2 AA), accessible text and JSON status parity, bilingual DE/EN delivery, CEFR-B2 readability, inclusive-content governance, didactic inline-code-comment review, and audit-ready Spec-Kit run evidence to Spec Kit | 10 templates, 3 commands | — | [spec-kit-preset-a11y-governance](https://github.com/hindermath/spec-kit-preset-a11y-governance) |
|
||||
| A11Y Governance | Adds WCAG 2.2 AA, accessible status output, bilingual CEFR-B2 delivery, inclusive-content and didactic-comment governance, and provider-neutral model routing. | 11 templates, 3 commands | — | [spec-kit-preset-a11y-governance](https://github.com/hindermath/spec-kit-preset-a11y-governance) |
|
||||
| Agent Parity Governance | Adds shared-guidance and generated-command parity, repository-fleet completion evidence, secret-free runner/status metadata, audit-ready Spec-Kit run evidence, and agent-neutral model-routing guidance across declared AI-agent surfaces. | 6 templates, 3 commands | — | [spec-kit-preset-agent-parity-governance](https://github.com/hindermath/spec-kit-preset-agent-parity-governance) |
|
||||
| AIDE In-Place Migration | Adapts the AIDE extension workflow for in-place technology migrations (X → Y pattern) — adds migration objectives, verification gates, knowledge documents, and behavioral equivalence criteria | 2 templates, 8 commands | AIDE extension | [spec-kit-presets](https://github.com/mnriem/spec-kit-presets) |
|
||||
| Architecture Governance | Adds secure software architecture, resumable remote-transaction boundaries, STRIDE+CAPEC threat modeling, arc42 security cross-cutting concepts, S-ADRs, Zero Trust applicability, OWASP SAMM governance, BSI C3A cloud autonomy, BSI C5 cloud compliance assurance, and audit-ready Spec Kit run evidence | 13 templates, 3 commands | — | [spec-kit-preset-architecture-governance](https://github.com/hindermath/spec-kit-preset-architecture-governance) |
|
||||
| Architecture Governance | Adds secure architecture, STRIDE/CAPEC threat modeling, arc42/S-ADR guidance, Zero Trust, SAMM, BSI cloud assurance, audit evidence, and provider-neutral model routing. | 14 templates, 3 commands | — | [spec-kit-preset-architecture-governance](https://github.com/hindermath/spec-kit-preset-architecture-governance) |
|
||||
| Autonomous Run Governance | Adds permission-bounded autonomous delivery, an optional intake-review gate, and preservation of the project's learner and accessibility contract. | 13 templates, 5 commands, 4 scripts | — | [spec-kit-preset-autonomous-run-governance](https://github.com/hindermath/spec-kit-preset-autonomous-run-governance) |
|
||||
| Canon Core | Adapts original Spec Kit workflow to work together with Canon extension | 2 templates, 8 commands | — | [spec-kit-canon](https://github.com/maximiliamus/spec-kit-canon) |
|
||||
| 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) |
|
||||
@@ -22,7 +22,7 @@ The following community-contributed presets customize how Spec Kit behaves — o
|
||||
| Intake Authoring Governance | Governs traceable intake CRUD and language-aware requirements collections with atomic migrations, rollback evidence, and safe series authoring. | 12 templates, 5 commands, 7 scripts | — | [spec-kit-preset-intake-authoring-governance](https://github.com/hindermath/spec-kit-preset-intake-authoring-governance) |
|
||||
| Intake Review Governance | Reviews single, series, campaign, and language-aware requirements collections before Spec Kit execution. | 8 templates, 3 commands, 4 scripts | — | [spec-kit-preset-intake-review-governance](https://github.com/hindermath/spec-kit-preset-intake-review-governance) |
|
||||
| Intake Sequencing Governance | Manages language-aware intake-series order, typed dependencies, lifecycle, and authority-neutral next-candidate selection. | 11 templates, 6 commands, 8 scripts | — | [spec-kit-preset-intake-sequencing-governance](https://github.com/hindermath/spec-kit-preset-intake-sequencing-governance) |
|
||||
| iSAQB Architecture Governance | Adds iSAQB/CPSA-F and arc42 architecture governance with audit-ready evidence for goals, views, resumability, partial-failure 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) |
|
||||
| iSAQB Architecture Governance | Adds iSAQB/CPSA-F and arc42 architecture governance, architecture views, quality scenarios, ADRs, risks, technical-debt evidence, and provider-neutral model routing. | 14 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) |
|
||||
| Model Driven Engineering | Focuses on streamlined commands, app repository support, cross-spec support, and capability-aware project memory for model-driven engineering workflows | 6 templates, 11 commands | MDE extension | [spec-kit-preset-mde](https://github.com/AI-MDE/spec-kit-preset-mde) |
|
||||
| Model Routing Governance | Maps provider-neutral Spec Kit roles to validated harness-local runner profiles without storing model availability, credentials, or machine-specific selections in Git. | 4 templates, 2 commands, 2 scripts | — | [spec-kit-preset-model-routing-governance](https://github.com/hindermath/spec-kit-preset-model-routing-governance) |
|
||||
@@ -30,7 +30,7 @@ The following community-contributed presets customize how Spec Kit behaves — o
|
||||
| Parallel Autonomous Run Governance | Coordinates permission-bounded autonomous campaigns while preserving the project's learner and accessibility contract across workers and consolidation. | 9 templates, 5 commands, 2 scripts | autonomous-run-governance >=0.2.2; optional: intake-review-governance >=0.1.0 | [spec-kit-preset-parallel-autonomous-run-governance](https://github.com/hindermath/spec-kit-preset-parallel-autonomous-run-governance) |
|
||||
| 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 memory-safe-language and secure-coding governance, exact-head and security-gate evidence, provider-failure classification, ASVS, supply-chain transparency, and EU regulatory screening. | 14 templates, 3 commands | — | [spec-kit-preset-security-governance](https://github.com/hindermath/spec-kit-preset-security-governance) |
|
||||
| Security Governance | Adds memory-safe-language and secure-coding governance, exact-head security evidence, ASVS, supply-chain transparency, EU regulatory screening, and provider-neutral model routing. | 15 templates, 3 commands | — | [spec-kit-preset-security-governance](https://github.com/hindermath/spec-kit-preset-security-governance) |
|
||||
| SicarioSpec Core | Baseline secure-by-default Spec Kit governance profile. | 5 templates | — | [sicario-spec](https://github.com/dfirs1car1o/sicario-spec) |
|
||||
| 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) |
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
> [!NOTE]
|
||||
> Community walkthroughs are independently created and maintained by their respective authors. They are **not reviewed, nor endorsed, nor supported by GitHub**. Review their content before following along and use at your own discretion.
|
||||
|
||||
See Spec-Driven Development in action across different scenarios with these community-contributed walkthroughs:
|
||||
See Spec-Driven Development in action across different scenarios with these community-contributed walkthroughs. They are useful read-only examples of completed flows, but they are not official golden outputs for generated specs, plans, or tasks.
|
||||
|
||||
- **[Greenfield .NET CLI tool](https://github.com/mnriem/spec-kit-dotnet-cli-demo)** — Builds a Timezone Utility as a .NET single-binary CLI tool from a blank directory, covering the full spec-kit workflow: constitution, specify, plan, tasks, and multi-pass implement using GitHub Copilot agents.
|
||||
|
||||
|
||||
+5
-5
@@ -5,8 +5,7 @@ This guide will help you get started with Spec-Driven Development using Spec Kit
|
||||
> [!NOTE]
|
||||
> Automation scripts are provided as Bash (`.sh`), PowerShell (`.ps1`), and Python (`.py`) variants. Interactive `specify init` prompts you to choose one; non-interactive runs default to a shell variant for your OS. Pass `--script sh|ps|py` to select explicitly.
|
||||
|
||||
> [!NOTE]
|
||||
> Commands are shown here in `/speckit.*` form, but the exact invocation depends on your agent. Some skills-based agents use `$speckit-*` (e.g. Codex, ZCode) or `/skill:speckit-*` (e.g. Kimi). Use whichever form your agent exposes — the steps are otherwise identical.
|
||||
Commands are shown here in `/speckit.*` form, but the exact invocation depends on your agent. Some skills-based agents use `$speckit-*` (e.g. Codex, ZCode) or `/skill:speckit-*` (e.g. Kimi). Use whichever form your agent exposes — the steps are otherwise identical.
|
||||
|
||||
## Recommended Process
|
||||
|
||||
@@ -83,7 +82,7 @@ Generates the design artifacts from the spec. This is where implementation detai
|
||||
|
||||
### Step 5: `/speckit.checklist` — validate the spec
|
||||
|
||||
Generates a quality checklist — "unit tests for your requirements" — to confirm the spec is complete, clear, and consistent before you break the work down.
|
||||
Generates a custom quality checklist — "unit tests for your requirements" — to confirm the spec is complete, clear, and consistent before you break the work down. These custom checklists are reviewer-owned requirements-quality review artifacts: mark an item `[x]` only when the reviewer determines that requirement-quality criterion is satisfied. Checked custom items do not mean implementation work is complete.
|
||||
|
||||
```text
|
||||
/speckit.checklist
|
||||
@@ -107,7 +106,7 @@ Reports conflicts, gaps, and ambiguities across `spec.md`, `plan.md`, and `tasks
|
||||
|
||||
### Step 8: `/speckit.implement` — build it
|
||||
|
||||
Executes the tasks in `tasks.md` in dependency order. Run it once to build everything, or scope it to one phase at a time for large features.
|
||||
Executes the tasks in `tasks.md` in dependency order. Before implementation, it reads checklist checkbox state as a gate and asks before proceeding if any checklist items are unchecked; it does not change any checklist files or markers. The built-in `checklists/requirements.md` checklist is maintained by `/speckit.specify` and `/speckit.clarify`, while custom checklists remain reviewer-owned. Run it once to build everything, or scope it to one phase at a time for large features.
|
||||
|
||||
```text
|
||||
/speckit.implement
|
||||
@@ -136,5 +135,6 @@ Checks the codebase against the spec, plan, and tasks. If it finds gaps, it appe
|
||||
|
||||
- See the [Agentic SDD](reference/agentic-sdd.md) reference for full detail on every command
|
||||
- Read the [complete methodology](https://github.com/github/spec-kit/blob/main/spec-driven.md) for in-depth guidance
|
||||
- Check out [more examples](https://github.com/github/spec-kit/tree/main/templates) in the repository
|
||||
- Compare the [core templates](https://github.com/github/spec-kit/tree/main/templates) with
|
||||
[community walkthroughs](community/walkthroughs.md) to see how Spec-Driven Development is used in real projects
|
||||
- Explore the [source code on GitHub](https://github.com/github/spec-kit)
|
||||
|
||||
@@ -23,6 +23,8 @@ Creates or updates the project **constitution** — the guiding principles that
|
||||
|
||||
Creates or updates the feature **specification** from a natural-language description. Focus on the **what** and **why** — the user-facing behavior and goals — not the tech stack, which belongs in `/speckit.plan`.
|
||||
|
||||
This workflow may also maintain `checklists/requirements.md`, the built-in spec-quality checklist that `/speckit.specify` creates and `/speckit.clarify` re-evaluates. That lifecycle is separate from custom checklists generated by `/speckit.checklist`.
|
||||
|
||||
```text
|
||||
/speckit.specify Build an application that helps me organize photos into albums grouped by date, re-orderable by drag-and-drop on the main page, with a tile preview inside each album.
|
||||
```
|
||||
@@ -37,6 +39,8 @@ Asks up to five targeted questions about underspecified areas of the current spe
|
||||
|
||||
Clarifying before planning keeps you from designing on top of ambiguity. If `/speckit.analyze` later surfaces requirement gaps, come back and run `/speckit.clarify` (or `/speckit.specify`) again.
|
||||
|
||||
When `checklists/requirements.md` exists, `/speckit.clarify` may update its evaluated state as part of tightening the spec. This exception applies only to the built-in requirements checklist, not to custom review checklists.
|
||||
|
||||
## `/speckit.plan`
|
||||
|
||||
Runs the planning process to generate design artifacts from the spec. This is where implementation detail belongs — provide your tech stack, architecture, and technical constraints as arguments.
|
||||
@@ -49,6 +53,8 @@ Runs the planning process to generate design artifacts from the spec. This is wh
|
||||
|
||||
Generates a quality checklist for the feature — think of it as **"unit tests for your requirements."** Rather than testing code, it checks whether the spec itself is complete, clear, unambiguous, and consistent (for example: "Are the drag-and-drop rules defined for every column?", "Is behavior specified for a deleted assigned user?").
|
||||
|
||||
Custom checklists generated by this command are reviewer-owned requirements-quality review artifacts. An agent may help evaluate them when explicitly asked, but implementation must not silently self-approve them. In a custom checklist, `[x]` means the reviewer determined the requirements-quality criterion is satisfied; it does not mean implementation work is complete.
|
||||
|
||||
Run it with no arguments for a broad pass, or pass a focus area to target one aspect:
|
||||
|
||||
```text
|
||||
@@ -59,7 +65,7 @@ Run it with no arguments for a broad pass, or pass a focus area to target one as
|
||||
/speckit.checklist Focus on the Kanban board interactions and comment permissions.
|
||||
```
|
||||
|
||||
Review the generated checklist. If it surfaces gaps, loop back to `/speckit.clarify` or `/speckit.specify` to tighten the spec before breaking the work down.
|
||||
Review the generated checklist. If it surfaces gaps, loop back to `/speckit.clarify` or `/speckit.specify` to tighten the spec before breaking the work down, then mark each custom checklist item `[x]` only after the requirements-quality criterion has been reviewed and satisfied.
|
||||
|
||||
## `/speckit.tasks`
|
||||
|
||||
@@ -83,6 +89,8 @@ Run it before implementing, while the artifacts can still be adjusted cheaply. I
|
||||
|
||||
Executes the tasks in `tasks.md`, running each phase in dependency order and respecting parallel markers.
|
||||
|
||||
Before executing tasks, it reads checklist checkbox state as a gate. Checklist markers are read-only for this command: `/speckit.implement` counts checked and unchecked items and asks before proceeding when any are unchecked, but it must not change checklist markers. For custom checklists, checked items mean reviewer approval of requirements quality, not completed implementation work.
|
||||
|
||||
For a small feature, run it once to build everything:
|
||||
|
||||
```text
|
||||
|
||||
@@ -151,7 +151,7 @@ To submit your extension to the community catalog, file a new issue using the **
|
||||
|
||||
### What Happens After You Submit
|
||||
|
||||
1. Your issue is automatically labeled and assigned to a maintainer for review
|
||||
1. A maintainer reviews the issue during issue triage and applies the `extension-submission` label, which starts the automated catalog validation. On this public repository, contributors cannot apply that label themselves, so there is nothing to label or re-request — the issue simply waits in triage.
|
||||
2. A maintainer verifies that the catalog entry is complete and correctly formatted
|
||||
3. Once approved, the maintainer adds your extension to `extensions/catalog.community.json` and the Community Extensions table in the README
|
||||
4. Your extension becomes discoverable via `specify extension search`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"updated_at": "2026-08-10T00:00:00Z",
|
||||
"updated_at": "2026-08-11T00:00:00Z",
|
||||
"catalog_url": "https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.community.json",
|
||||
"extensions": {
|
||||
"adrkit": {
|
||||
@@ -362,8 +362,8 @@
|
||||
"id": "archive",
|
||||
"description": "Archive merged features into main project memory, resolving gaps and conflicts.",
|
||||
"author": "Stanislav Deviatov",
|
||||
"version": "1.1.0",
|
||||
"download_url": "https://github.com/stn1slv/spec-kit-archive/archive/refs/tags/v1.1.0.zip",
|
||||
"version": "1.2.2",
|
||||
"download_url": "https://github.com/stn1slv/spec-kit-archive/archive/refs/tags/v1.2.2.zip",
|
||||
"repository": "https://github.com/stn1slv/spec-kit-archive",
|
||||
"homepage": "https://github.com/stn1slv/spec-kit-archive",
|
||||
"documentation": "https://github.com/stn1slv/spec-kit-archive/blob/main/README.md",
|
||||
@@ -388,7 +388,7 @@
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-03-14T00:00:00Z",
|
||||
"updated_at": "2026-08-04T00:00:00Z"
|
||||
"updated_at": "2026-08-11T00:00:00Z"
|
||||
},
|
||||
"azure-devops": {
|
||||
"name": "Azure DevOps Integration",
|
||||
@@ -1815,6 +1815,41 @@
|
||||
"created_at": "2026-06-24T00:00:00Z",
|
||||
"updated_at": "2026-07-07T00:00:00Z"
|
||||
},
|
||||
"grill": {
|
||||
"name": "SpecKit Grill Me",
|
||||
"id": "grill",
|
||||
"description": "Exhaustively resolve specification ambiguities and decisions before planning.",
|
||||
"author": "yoshi1220",
|
||||
"version": "1.0.0",
|
||||
"download_url": "https://github.com/yoshi1220/speckit-grill-me/releases/download/v1.0.0/speckit-grill-me-extension-v1.0.0.zip",
|
||||
"repository": "https://github.com/yoshi1220/speckit-grill-me",
|
||||
"homepage": "https://github.com/yoshi1220/speckit-grill-me/tree/main/spec-kit-extension",
|
||||
"documentation": "https://github.com/yoshi1220/speckit-grill-me/blob/main/spec-kit-extension/README.md",
|
||||
"changelog": "https://github.com/yoshi1220/speckit-grill-me/blob/main/spec-kit-extension/CHANGELOG.md",
|
||||
"license": "MIT",
|
||||
"category": "process",
|
||||
"effect": "read-write",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.16.2",
|
||||
"tools": [{ "name": "bash", "required": true }]
|
||||
},
|
||||
"provides": {
|
||||
"commands": 1,
|
||||
"hooks": 0
|
||||
},
|
||||
"tags": [
|
||||
"clarification",
|
||||
"requirements",
|
||||
"specification",
|
||||
"elicitation",
|
||||
"workflow"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-08-11T00:00:00Z",
|
||||
"updated_at": "2026-08-11T00:00:00Z"
|
||||
},
|
||||
"harness": {
|
||||
"name": "Research Harness",
|
||||
"id": "harness",
|
||||
|
||||
@@ -300,6 +300,12 @@ git push origin add-your-preset
|
||||
|
||||
## Verification Process
|
||||
|
||||
> **How submissions get picked up:** the automated catalog-validation workflow only runs
|
||||
> once the `preset-submission` label is on the issue. On this public repository, contributors
|
||||
> cannot apply that label themselves — a maintainer applies it during issue triage. Until then
|
||||
> the issue simply waits in triage; there is no action required from you, and there is no need to
|
||||
> re-request the label in a comment.
|
||||
|
||||
After submission, maintainers will review:
|
||||
|
||||
1. **Manifest validation** — valid `preset.yml`, all files exist
|
||||
|
||||
@@ -1,40 +1,35 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"updated_at": "2026-08-10T00:00:00Z",
|
||||
|
||||
"updated_at": "2026-08-12T00:00:00Z",
|
||||
"catalog_url": "https://raw.githubusercontent.com/github/spec-kit/main/presets/catalog.community.json",
|
||||
"presets": {
|
||||
"a11y-governance": {
|
||||
"name": "A11Y Governance",
|
||||
"id": "a11y-governance",
|
||||
"version": "0.4.2",
|
||||
"description": "Adds accessibility (WCAG 2.2 AA), accessible text and JSON status parity, bilingual DE/EN delivery, CEFR-B2 readability, inclusive-content governance, didactic inline-code-comment review, and audit-ready Spec-Kit run evidence to Spec Kit.",
|
||||
"version": "0.4.3",
|
||||
"description": "Adds WCAG 2.2 AA, accessible status output, bilingual CEFR-B2 delivery, inclusive-content and didactic-comment governance, and provider-neutral model routing.",
|
||||
"author": "Thorsten Hindermann",
|
||||
"repository": "https://github.com/hindermath/spec-kit-preset-a11y-governance",
|
||||
"download_url": "https://github.com/hindermath/spec-kit-preset-a11y-governance/archive/refs/tags/v0.4.2.zip",
|
||||
"download_url": "https://github.com/hindermath/spec-kit-preset-a11y-governance/archive/refs/tags/v0.4.3.zip",
|
||||
"homepage": "https://github.com/hindermath/spec-kit-preset-a11y-governance",
|
||||
"documentation": "https://github.com/hindermath/spec-kit-preset-a11y-governance/blob/v0.4.2/README.md",
|
||||
"documentation": "https://github.com/hindermath/spec-kit-preset-a11y-governance/blob/v0.4.3/README.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.8.0"
|
||||
},
|
||||
"provides": {
|
||||
"templates": 10,
|
||||
"templates": 11,
|
||||
"commands": 3
|
||||
},
|
||||
"tags": [
|
||||
"a11y",
|
||||
"accessibility",
|
||||
"bilingual",
|
||||
"wcag",
|
||||
"wcag-2-2",
|
||||
"cefr-b2",
|
||||
"inclusion",
|
||||
"include-everyone",
|
||||
"didactic-comments"
|
||||
"model-routing"
|
||||
],
|
||||
"created_at": "2026-04-27T00:00:00Z",
|
||||
"updated_at": "2026-07-28T00:00:00Z"
|
||||
"updated_at": "2026-08-12T00:00:00Z"
|
||||
},
|
||||
"agent-parity-governance": {
|
||||
"name": "Agent Parity Governance",
|
||||
@@ -97,40 +92,30 @@
|
||||
"architecture-governance": {
|
||||
"name": "Architecture Governance",
|
||||
"id": "architecture-governance",
|
||||
"version": "0.5.1",
|
||||
"description": "Adds secure software architecture, resumable remote-transaction boundaries, STRIDE+CAPEC threat modeling, arc42 security cross-cutting concepts, S-ADRs, Zero Trust applicability, OWASP SAMM governance, BSI C3A cloud autonomy, BSI C5 cloud compliance assurance, and audit-ready Spec Kit run evidence.",
|
||||
"version": "0.5.2",
|
||||
"description": "Adds secure architecture, STRIDE/CAPEC threat modeling, arc42/S-ADR guidance, Zero Trust, SAMM, BSI cloud assurance, audit evidence, and provider-neutral model routing.",
|
||||
"author": "Thorsten Hindermann",
|
||||
"repository": "https://github.com/hindermath/spec-kit-preset-architecture-governance",
|
||||
"download_url": "https://github.com/hindermath/spec-kit-preset-architecture-governance/archive/refs/tags/v0.5.1.zip",
|
||||
"download_url": "https://github.com/hindermath/spec-kit-preset-architecture-governance/archive/refs/tags/v0.5.2.zip",
|
||||
"homepage": "https://github.com/hindermath/spec-kit-preset-architecture-governance",
|
||||
"documentation": "https://github.com/hindermath/spec-kit-preset-architecture-governance/blob/v0.5.1/README.md",
|
||||
"documentation": "https://github.com/hindermath/spec-kit-preset-architecture-governance/blob/v0.5.2/README.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.8.0"
|
||||
},
|
||||
"provides": {
|
||||
"templates": 13,
|
||||
"templates": 14,
|
||||
"commands": 3
|
||||
},
|
||||
"tags": [
|
||||
"architecture",
|
||||
"governance",
|
||||
"threat-modeling",
|
||||
"stride",
|
||||
"capec",
|
||||
"arc42",
|
||||
"adr",
|
||||
"zero-trust",
|
||||
"samm",
|
||||
"isaqb",
|
||||
"cloud",
|
||||
"sovereignty",
|
||||
"c3a",
|
||||
"c5",
|
||||
"assurance"
|
||||
"model-routing"
|
||||
],
|
||||
"created_at": "2026-04-27T00:00:00Z",
|
||||
"updated_at": "2026-07-23T00:00:00Z"
|
||||
"updated_at": "2026-08-11T00:00:00Z"
|
||||
},
|
||||
"autonomous-run-governance": {
|
||||
"name": "Autonomous Run Governance",
|
||||
@@ -454,34 +439,30 @@
|
||||
"isaqb-architecture-governance": {
|
||||
"name": "iSAQB Architecture Governance",
|
||||
"id": "isaqb-architecture-governance",
|
||||
"version": "0.2.1",
|
||||
"description": "Adds iSAQB/CPSA-F and arc42 architecture governance with audit-ready evidence for goals, views, resumability, partial-failure scenarios, ADRs, risks, and technical debt.",
|
||||
"version": "0.2.2",
|
||||
"description": "Adds iSAQB/CPSA-F and arc42 architecture governance, architecture views, quality scenarios, ADRs, risks, technical-debt evidence, and provider-neutral model routing.",
|
||||
"author": "Thorsten Hindermann",
|
||||
"repository": "https://github.com/hindermath/spec-kit-preset-isaqb-architecture-governance",
|
||||
"download_url": "https://github.com/hindermath/spec-kit-preset-isaqb-architecture-governance/archive/refs/tags/v0.2.1.zip",
|
||||
"download_url": "https://github.com/hindermath/spec-kit-preset-isaqb-architecture-governance/archive/refs/tags/v0.2.2.zip",
|
||||
"homepage": "https://github.com/hindermath/spec-kit-preset-isaqb-architecture-governance",
|
||||
"documentation": "https://github.com/hindermath/spec-kit-preset-isaqb-architecture-governance/blob/v0.2.1/README.md",
|
||||
"documentation": "https://github.com/hindermath/spec-kit-preset-isaqb-architecture-governance/blob/v0.2.2/README.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.8.0"
|
||||
},
|
||||
"provides": {
|
||||
"templates": 13,
|
||||
"templates": 14,
|
||||
"commands": 3
|
||||
},
|
||||
"tags": [
|
||||
"architecture",
|
||||
"governance",
|
||||
"isaqb",
|
||||
"cpsa-f",
|
||||
"arc42",
|
||||
"adr",
|
||||
"quality-attributes",
|
||||
"architecture-views",
|
||||
"technical-debt"
|
||||
"model-routing"
|
||||
],
|
||||
"created_at": "2026-04-27T00:00:00Z",
|
||||
"updated_at": "2026-07-23T00:00:00Z"
|
||||
"updated_at": "2026-08-11T00:00:00Z"
|
||||
},
|
||||
"jira": {
|
||||
"name": "Jira Issue Tracking",
|
||||
@@ -609,7 +590,7 @@
|
||||
"documentation": "https://github.com/hindermath/spec-kit-preset-parallel-autonomous-run-governance/blob/v0.2.4/README.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.8.3"
|
||||
"speckit_version": ">=0.8.3"
|
||||
},
|
||||
"provides": {
|
||||
"templates": 9,
|
||||
@@ -692,52 +673,30 @@
|
||||
"security-governance": {
|
||||
"name": "Security Governance",
|
||||
"id": "security-governance",
|
||||
"version": "0.6.1",
|
||||
"description": "Adds memory-safe-language and secure-coding governance, exact-head and security-gate evidence, provider-failure classification, ASVS, supply-chain transparency, and EU regulatory screening.",
|
||||
"version": "0.6.2",
|
||||
"description": "Adds memory-safe-language and secure-coding governance, exact-head security evidence, ASVS, supply-chain transparency, EU regulatory screening, and provider-neutral model routing.",
|
||||
"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.6.1.zip",
|
||||
"download_url": "https://github.com/hindermath/spec-kit-preset-security-governance/archive/refs/tags/v0.6.2.zip",
|
||||
"homepage": "https://github.com/hindermath/spec-kit-preset-security-governance",
|
||||
"documentation": "https://github.com/hindermath/spec-kit-preset-security-governance/blob/v0.6.1/README.md",
|
||||
"documentation": "https://github.com/hindermath/spec-kit-preset-security-governance/blob/v0.6.2/README.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.8.0"
|
||||
},
|
||||
"provides": {
|
||||
"templates": 14,
|
||||
"templates": 15,
|
||||
"commands": 3
|
||||
},
|
||||
"tags": [
|
||||
"security",
|
||||
"governance",
|
||||
"msl",
|
||||
"ssdf",
|
||||
"asvs",
|
||||
"supply-chain",
|
||||
"sbom",
|
||||
"ai-sbom",
|
||||
"vex",
|
||||
"slsa",
|
||||
"cwe-top-25",
|
||||
"secure-coding",
|
||||
"rust",
|
||||
"go",
|
||||
"swift",
|
||||
"java",
|
||||
"kotlin",
|
||||
"python",
|
||||
"typescript",
|
||||
"g7",
|
||||
"bsi",
|
||||
"cra",
|
||||
"cyber-resilience-act",
|
||||
"nis2",
|
||||
"ai-act",
|
||||
"dora",
|
||||
"regulatory"
|
||||
"supply-chain",
|
||||
"model-routing"
|
||||
],
|
||||
"created_at": "2026-04-27T00:00:00Z",
|
||||
"updated_at": "2026-07-23T00:00:00Z"
|
||||
"updated_at": "2026-08-10T00:00:00Z"
|
||||
},
|
||||
"sicario-core": {
|
||||
"name": "SicarioSpec Core",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "specify-cli"
|
||||
version = "0.16.2"
|
||||
version = "0.16.3.dev0"
|
||||
description = "Specify CLI, part of GitHub Spec Kit. A tool to bootstrap your projects for Spec-Driven Development (SDD)."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11"
|
||||
|
||||
@@ -77,6 +77,16 @@ def resolve_install_plan(
|
||||
|
||||
# FR-019: integration-compatibility — a bundle that pins a different
|
||||
# integration than the project's active one halts (no silent change).
|
||||
#
|
||||
# A blank integration arrives as ``""``, not ``None`` — which is not a usable
|
||||
# integration id but satisfied NEITHER guard below (the first is a truthiness
|
||||
# test, the second an ``is None`` test), so a pinned bundle was silently
|
||||
# adopted: precisely the outcome this guard exists to prevent. Treat blank as
|
||||
# indeterminate, and strip first like the writer
|
||||
# (``integration_state.clean_integration_key``) so a padded value is not
|
||||
# reported as clashing with itself.
|
||||
if active_integration is not None:
|
||||
active_integration = active_integration.strip() or None
|
||||
effective_integration = active_integration
|
||||
if manifest.integration is not None:
|
||||
required = manifest.integration.id
|
||||
|
||||
@@ -14,8 +14,8 @@ from pathlib import Path
|
||||
import typer
|
||||
from rich.markup import escape as _escape_markup
|
||||
|
||||
from ..._console import console, err_console
|
||||
from ..._download_security import MAX_DOWNLOAD_BYTES, read_response_limited
|
||||
from ..._console import console, err_console
|
||||
from ...bundler import BundlerError
|
||||
from ...bundler.lib.project import (
|
||||
active_integration,
|
||||
|
||||
@@ -4712,6 +4712,7 @@ class HookExecutor:
|
||||
kimi_skill_mode = selected_ai == "kimi"
|
||||
cline_mode = selected_ai == "cline"
|
||||
forge_mode = selected_ai == "forge"
|
||||
junie_mode = selected_ai == "junie"
|
||||
|
||||
skill_name = self._skill_name_from_command(command_id)
|
||||
if dollar_skill_mode and skill_name:
|
||||
@@ -4726,6 +4727,10 @@ class HookExecutor:
|
||||
from ..integrations.forge import format_forge_command_name
|
||||
|
||||
return f"/{format_forge_command_name(command_id)}"
|
||||
if junie_mode:
|
||||
from ..integrations.junie import format_junie_command_name
|
||||
|
||||
return f"/{format_junie_command_name(command_id)}"
|
||||
|
||||
use_slash = is_slash_skills_agent(selected_ai, ai_skills_enabled)
|
||||
|
||||
|
||||
@@ -65,7 +65,16 @@ class AlquimiaAIIntegration(SkillsIntegration):
|
||||
|
||||
@staticmethod
|
||||
def inject_argument_hint(content: str, hint: str) -> str:
|
||||
"""Insert ``argument-hint`` after the first ``description:`` in YAML frontmatter.
|
||||
"""Insert ``argument-hint`` after the ``description:`` scalar in YAML frontmatter.
|
||||
|
||||
A long ``description`` gets folded by the YAML dumper across
|
||||
indented continuation lines (plain or quoted), and an embedded
|
||||
paragraph break can add unindented blank lines inside a quoted
|
||||
scalar. Inserting the new line right after the *first* line of
|
||||
that scalar — instead of after the whole scalar — either produces
|
||||
invalid YAML or gets silently absorbed into the description
|
||||
string (#4044), so every continuation line (indented, or blank)
|
||||
is skipped first.
|
||||
|
||||
Skips injection if ``argument-hint:`` already exists in the
|
||||
frontmatter to avoid duplicate keys.
|
||||
@@ -88,15 +97,29 @@ class AlquimiaAIIntegration(SkillsIntegration):
|
||||
in_fm = False
|
||||
dash_count = 0
|
||||
injected = False
|
||||
for line in lines:
|
||||
i = 0
|
||||
n = len(lines)
|
||||
while i < n:
|
||||
line = lines[i]
|
||||
stripped = line.rstrip("\n\r")
|
||||
if stripped == "---":
|
||||
dash_count += 1
|
||||
in_fm = dash_count == 1
|
||||
out.append(line)
|
||||
i += 1
|
||||
continue
|
||||
if in_fm and not injected and stripped.startswith("description:"):
|
||||
out.append(line)
|
||||
i += 1
|
||||
# Skip folded/quoted continuation lines before inserting
|
||||
# so the new key lands after the description scalar ends.
|
||||
# Blank lines count too: PyYAML emits unindented blank
|
||||
# lines for embedded "\n\n" inside a quoted scalar.
|
||||
while i < n and (
|
||||
lines[i][:1] in (" ", "\t") or lines[i].rstrip("\r\n") == ""
|
||||
):
|
||||
out.append(lines[i])
|
||||
i += 1
|
||||
# Preserve the exact line-ending style (\r\n vs \n)
|
||||
if line.endswith("\r\n"):
|
||||
eol = "\r\n"
|
||||
@@ -109,6 +132,7 @@ class AlquimiaAIIntegration(SkillsIntegration):
|
||||
injected = True
|
||||
continue
|
||||
out.append(line)
|
||||
i += 1
|
||||
return "".join(out)
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -67,7 +67,16 @@ class ClaudeIntegration(SkillsIntegration):
|
||||
|
||||
@staticmethod
|
||||
def inject_argument_hint(content: str, hint: str) -> str:
|
||||
"""Insert ``argument-hint`` after the first ``description:`` in YAML frontmatter.
|
||||
"""Insert ``argument-hint`` after the ``description:`` scalar in YAML frontmatter.
|
||||
|
||||
A long ``description`` gets folded by the YAML dumper across
|
||||
indented continuation lines (plain or quoted), and an embedded
|
||||
paragraph break can add unindented blank lines inside a quoted
|
||||
scalar. Inserting the new line right after the *first* line of
|
||||
that scalar — instead of after the whole scalar — either produces
|
||||
invalid YAML or gets silently absorbed into the description
|
||||
string (#4044), so every continuation line (indented, or blank)
|
||||
is skipped first.
|
||||
|
||||
Skips injection if ``argument-hint:`` already exists in the
|
||||
frontmatter to avoid duplicate keys.
|
||||
@@ -90,15 +99,29 @@ class ClaudeIntegration(SkillsIntegration):
|
||||
in_fm = False
|
||||
dash_count = 0
|
||||
injected = False
|
||||
for line in lines:
|
||||
i = 0
|
||||
n = len(lines)
|
||||
while i < n:
|
||||
line = lines[i]
|
||||
stripped = line.rstrip("\n\r")
|
||||
if stripped == "---":
|
||||
dash_count += 1
|
||||
in_fm = dash_count == 1
|
||||
out.append(line)
|
||||
i += 1
|
||||
continue
|
||||
if in_fm and not injected and stripped.startswith("description:"):
|
||||
out.append(line)
|
||||
i += 1
|
||||
# Skip past folded/quoted continuation lines of the scalar
|
||||
# before inserting, so the new key lands after it ends.
|
||||
# Blank lines count too: PyYAML emits unindented blank
|
||||
# lines for embedded "\n\n" inside a quoted scalar.
|
||||
while i < n and (
|
||||
lines[i][:1] in (" ", "\t") or lines[i].rstrip("\r\n") == ""
|
||||
):
|
||||
out.append(lines[i])
|
||||
i += 1
|
||||
# Preserve the exact line-ending style (\r\n vs \n)
|
||||
if line.endswith("\r\n"):
|
||||
eol = "\r\n"
|
||||
@@ -111,6 +134,7 @@ class ClaudeIntegration(SkillsIntegration):
|
||||
injected = True
|
||||
continue
|
||||
out.append(line)
|
||||
i += 1
|
||||
return "".join(out)
|
||||
|
||||
def _render_skill(self, template_name: str, frontmatter: dict[str, Any], body: str) -> str:
|
||||
|
||||
@@ -1,6 +1,51 @@
|
||||
"""Junie integration (JetBrains)."""
|
||||
|
||||
from ..base import MarkdownIntegration
|
||||
from ..manifest import IntegrationManifest
|
||||
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
# Note injected into hook sections so Junie maps dot-notation command
|
||||
# names (from extensions.yml) to the hyphenated slash commands it uses.
|
||||
_HOOK_COMMAND_NOTE = (
|
||||
"- When constructing slash commands from hook command names, "
|
||||
"replace dots (`.`) with hyphens (`-`). "
|
||||
"For example, `speckit.git.commit` → `/speckit-git-commit`.\n"
|
||||
)
|
||||
|
||||
|
||||
def format_junie_command_name(cmd_name: str) -> str:
|
||||
"""Convert command name to Junie-compatible hyphenated format.
|
||||
|
||||
Junie does not allow dots inside of slash-commands.
|
||||
This function converts dot-notation command names to hyphenated format.
|
||||
|
||||
The function is idempotent: already-formatted names are returned unchanged.
|
||||
|
||||
Examples:
|
||||
>>> format_junie_command_name("plan")
|
||||
'speckit-plan'
|
||||
>>> format_junie_command_name("speckit.plan")
|
||||
'speckit-plan'
|
||||
>>> format_junie_command_name("speckit.git.commit")
|
||||
'speckit-git-commit'
|
||||
|
||||
Args:
|
||||
cmd_name: Command name in dot notation (speckit.foo.bar),
|
||||
hyphenated format (speckit-foo-bar), or plain name (foo)
|
||||
|
||||
Returns:
|
||||
Hyphenated command name with 'speckit-' prefix
|
||||
"""
|
||||
cmd_name = cmd_name.replace(".", "-")
|
||||
|
||||
if not cmd_name.startswith("speckit-"):
|
||||
cmd_name = f"speckit-{cmd_name}"
|
||||
|
||||
return cmd_name
|
||||
|
||||
|
||||
class JunieIntegration(MarkdownIntegration):
|
||||
@@ -17,5 +62,117 @@ class JunieIntegration(MarkdownIntegration):
|
||||
"format": "markdown",
|
||||
"args": "$ARGUMENTS",
|
||||
"extension": ".md",
|
||||
"inject_name": True,
|
||||
"format_name": format_junie_command_name,
|
||||
"invoke_separator": "-",
|
||||
}
|
||||
multi_install_safe = True
|
||||
invoke_separator = "-"
|
||||
|
||||
def command_filename(self, template_name: str) -> str:
|
||||
return format_junie_command_name(template_name) + ".md"
|
||||
|
||||
def build_command_invocation(self, command_name: str, args: str = "") -> str:
|
||||
"""Junie installs hyphenated slash-commands (``/speckit-<name>``), so the
|
||||
dispatch invocation must match. The inherited MarkdownIntegration default
|
||||
builds the dotted ``/speckit.<name>``, which references a command Junie
|
||||
never registered. Reuse the same hyphenation as command_filename /
|
||||
the injected frontmatter name (see ``format_junie_command_name``),
|
||||
mirroring the forge integration.
|
||||
"""
|
||||
invocation = "/" + format_junie_command_name(command_name)
|
||||
if args:
|
||||
invocation = f"{invocation} {args}"
|
||||
return invocation
|
||||
|
||||
def process_template(self, *args, **kwargs):
|
||||
"""Ensure shared templates render Junie command references with hyphens."""
|
||||
kwargs.setdefault("invoke_separator", self.invoke_separator)
|
||||
return super().process_template(*args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def _inject_hook_command_note(content: str) -> str:
|
||||
"""Insert a dot-to-hyphen note before each hook output instruction.
|
||||
|
||||
Targets the line ``- For each executable hook, output the following``
|
||||
and inserts the note on the line before it, matching its indentation.
|
||||
Skips if the note is already present.
|
||||
"""
|
||||
if "replace dots" in content:
|
||||
return content
|
||||
|
||||
def repl(m: re.Match[str]) -> str:
|
||||
indent = m.group(1)
|
||||
instruction = m.group(2)
|
||||
# ``eol`` is empty when the regex matched via ``$`` because the
|
||||
# instruction was the final line of a file with no trailing
|
||||
# newline. Default to ``\n`` so the note never collapses onto
|
||||
# the same line as the instruction.
|
||||
eol = m.group(3) or "\n"
|
||||
return (
|
||||
indent
|
||||
+ _HOOK_COMMAND_NOTE.rstrip("\n")
|
||||
+ eol
|
||||
+ indent
|
||||
+ instruction
|
||||
+ eol
|
||||
)
|
||||
|
||||
return re.sub(
|
||||
r"(?m)^(\s*)(- For each executable hook, output the following[^\r\n]*)(\r\n|\n|$)",
|
||||
repl,
|
||||
content,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _rewrite_handoff_references(content: str) -> str:
|
||||
"""Replace dot-notation agent references in handoffs with hyphens."""
|
||||
return re.sub(
|
||||
r"(?m)^(\s*agent:\s*)(speckit\.[A-Za-z0-9-_]+(?:\.[A-Za-z0-9-_]+)*)",
|
||||
lambda m: f"{m.group(1)}{format_junie_command_name(m.group(2))}",
|
||||
content,
|
||||
)
|
||||
def post_process_command_content(self, content: str) -> str:
|
||||
"""Apply Junie-specific transformations to command content.
|
||||
|
||||
Overrides the ``IntegrationBase`` hook of the same name so that
|
||||
``CommandRegistrar.register_commands()`` (which dispatches to
|
||||
``post_process_command_content``) applies these transforms to
|
||||
extension/preset command files too, not just core commands.
|
||||
"""
|
||||
updated = self._inject_hook_command_note(content)
|
||||
updated = self._rewrite_handoff_references(updated)
|
||||
return updated
|
||||
|
||||
def setup(
|
||||
self,
|
||||
project_root: Path,
|
||||
manifest: IntegrationManifest,
|
||||
parsed_options: dict[str, Any] | None = None,
|
||||
**opts: Any,
|
||||
) -> list[Path]:
|
||||
"""Install Junie commands and apply post-processing transformations."""
|
||||
created = super().setup(project_root, manifest, parsed_options, **opts)
|
||||
|
||||
# Post-process generated command files
|
||||
dest_dir = self.commands_dest(project_root).resolve()
|
||||
|
||||
for path in created:
|
||||
# Only touch .md files under the commands directory
|
||||
try:
|
||||
path.resolve().relative_to(dest_dir)
|
||||
except ValueError:
|
||||
continue
|
||||
if path.suffix != ".md":
|
||||
continue
|
||||
|
||||
content_bytes = path.read_bytes()
|
||||
content = content_bytes.decode("utf-8")
|
||||
|
||||
updated = self.post_process_command_content(content)
|
||||
|
||||
if updated != content:
|
||||
path.write_bytes(updated.encode("utf-8"))
|
||||
self.record_file_in_manifest(path, project_root, manifest)
|
||||
|
||||
return created
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
**Created**: [DATE]
|
||||
**Feature**: [Link to spec.md or relevant documentation]
|
||||
|
||||
**Note**: This checklist is generated by the `__SPECKIT_COMMAND_CHECKLIST__` command based on feature context and requirements.
|
||||
**Note**: This custom checklist is generated by the `__SPECKIT_COMMAND_CHECKLIST__` command based on feature context and requirements.
|
||||
**Review Ownership**: This checklist is a reviewer-owned requirements-quality review artifact. Mark an item `[x]` only when the reviewer determines the requirements-quality criterion is satisfied.
|
||||
**Marker Semantics**: `[x]` means the criterion has been reviewed and satisfied for requirements quality. It does not mean implementation work is complete.
|
||||
|
||||
<!--
|
||||
============================================================================
|
||||
@@ -34,7 +36,10 @@
|
||||
|
||||
## Notes
|
||||
|
||||
- Check items off as completed: `[x]`
|
||||
- Mark items `[x]` only after review confirms the requirement-quality criterion is satisfied
|
||||
- Leave items unchecked when they still require clarification, correction, or reviewer evaluation
|
||||
- `__SPECKIT_COMMAND_IMPLEMENT__` reads checklist checkbox state as a gate and must not modify markers
|
||||
- `checklists/requirements.md` has a separate built-in lifecycle maintained by `__SPECKIT_COMMAND_SPECIFY__` and `__SPECKIT_COMMAND_CLARIFY__`
|
||||
- Add comments or findings inline
|
||||
- Link to relevant resources or documentation
|
||||
- Items are numbered sequentially for easy reference
|
||||
|
||||
@@ -27,6 +27,15 @@ scripts:
|
||||
|
||||
**Metaphor**: If your spec is code written in English, the checklist is its unit test suite. You're testing whether the requirements are well-written, complete, unambiguous, and ready for implementation - NOT whether the implementation works.
|
||||
|
||||
**Ownership and checkbox lifecycle**:
|
||||
|
||||
- Custom checklists generated by this command are reviewer-owned requirements-quality review artifacts.
|
||||
- `[x]` means the reviewer determined the requirements-quality criterion is satisfied.
|
||||
- `[x]` does NOT mean implementation work is complete.
|
||||
- This command generates or appends checklist items; it MUST NOT mark generated items `[x]`.
|
||||
- An agent may assist with evaluating items only when explicitly asked by the reviewer.
|
||||
- `checklists/requirements.md` is a separate built-in spec-quality checklist maintained by `__SPECKIT_COMMAND_SPECIFY__` and `__SPECKIT_COMMAND_CLARIFY__`; do not treat that exception as applying to custom checklists generated here.
|
||||
|
||||
## User Input
|
||||
|
||||
```text
|
||||
@@ -136,6 +145,7 @@ You **MUST** consider the user input before proceeding (if not empty).
|
||||
- If file does NOT exist: Create new file and number items starting from CHK001
|
||||
- If file exists: Append new items to existing file, continuing from the last CHK ID (e.g., if last item is CHK015, start new items at CHK016)
|
||||
- Never delete or replace existing checklist content - always preserve and append
|
||||
- Leave every newly generated item unchecked (`[ ]`); checkbox state belongs to the reviewer
|
||||
|
||||
**CORE PRINCIPLE - Test the Requirements, Not the Implementation**:
|
||||
Every checklist item MUST evaluate the REQUIREMENTS THEMSELVES for:
|
||||
@@ -245,7 +255,7 @@ You **MUST** consider the user input before proceeding (if not empty).
|
||||
- ✅ "Are [edge cases/scenarios] addressed in requirements?"
|
||||
- ✅ "Does the spec define [missing aspect]?"
|
||||
|
||||
7. **Structure Reference**: Generate the checklist following the canonical template in `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 `templates/checklist-template.md` for title, meta section, category headings, ownership note, notes section, and ID formatting. If template is unavailable, use: H1 title, purpose/created meta lines, an ownership note explaining that `[x]` means reviewer approval of requirements quality, `##` category sections containing `- [ ] CHK### <requirement item>` lines with globally incrementing IDs starting at CHK001, and notes that `__SPECKIT_COMMAND_IMPLEMENT__` reads checklist state but does not modify markers.
|
||||
|
||||
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
|
||||
|
||||
@@ -54,33 +54,36 @@ You **MUST** consider the user input before proceeding (if not empty).
|
||||
1. Run `{SCRIPT}` from repo root and parse FEATURE_DIR and AVAILABLE_DOCS list. All paths must be absolute. For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot").
|
||||
|
||||
2. **Check checklists status** (if FEATURE_DIR/checklists/ exists):
|
||||
- Treat checklist markers as a read-only gate: scan checkbox state, report status, and ask before proceeding when needed; do NOT modify checklist files or markers
|
||||
- `checklists/requirements.md` is the built-in spec-quality checklist maintained by `__SPECKIT_COMMAND_SPECIFY__` and `__SPECKIT_COMMAND_CLARIFY__`; custom checklists generated by `__SPECKIT_COMMAND_CHECKLIST__` are reviewer-owned requirements-quality review artifacts
|
||||
- For custom checklists, `[x]` means the reviewer determined the requirements-quality criterion is satisfied; it does NOT mean implementation work is complete
|
||||
- Scan all checklist files in the checklists/ directory
|
||||
- For each checklist, count:
|
||||
- Total items: All lines matching `- [ ]` or `- [X]` or `- [x]`
|
||||
- Completed items: Lines matching `- [X]` or `- [x]`
|
||||
- Incomplete items: Lines matching `- [ ]`
|
||||
- Checked items: Lines matching `- [X]` or `- [x]`
|
||||
- Unchecked items: Lines matching `- [ ]`
|
||||
- Create a status table:
|
||||
|
||||
```text
|
||||
| Checklist | Total | Completed | Incomplete | Status |
|
||||
|-----------|-------|-----------|------------|--------|
|
||||
| ux.md | 12 | 12 | 0 | ✓ PASS |
|
||||
| test.md | 8 | 5 | 3 | ✗ FAIL |
|
||||
| security.md | 6 | 6 | 0 | ✓ PASS |
|
||||
| Checklist | Total | Checked | Unchecked | Status |
|
||||
|-----------|-------|---------|-----------|--------|
|
||||
| ux.md | 12 | 12 | 0 | ✓ PASS |
|
||||
| test.md | 8 | 5 | 3 | ✗ FAIL |
|
||||
| security.md | 6 | 6 | 0 | ✓ PASS |
|
||||
```
|
||||
|
||||
- Calculate overall status:
|
||||
- **PASS**: All checklists have 0 incomplete items
|
||||
- **FAIL**: One or more checklists have incomplete items
|
||||
- **PASS**: All checklists have 0 unchecked items
|
||||
- **FAIL**: One or more checklists have unchecked items
|
||||
|
||||
- **If any checklist is incomplete**:
|
||||
- Display the table with incomplete item counts
|
||||
- **STOP** and ask: "Some checklists are incomplete. Do you want to proceed with implementation anyway? (yes/no)"
|
||||
- **If any checklist has unchecked items**:
|
||||
- Display the table with unchecked item counts
|
||||
- **STOP** and ask: "Some checklists have unchecked items. Do you want to proceed with implementation anyway? (yes/no)"
|
||||
- Wait for user response before continuing
|
||||
- If user says "no" or "wait" or "stop", halt execution
|
||||
- If user says "yes" or "proceed" or "continue", proceed to step 3
|
||||
|
||||
- **If all checklists are complete**:
|
||||
- **If all checklists are checked**:
|
||||
- Display the table showing all checklists passed
|
||||
- Automatically proceed to step 3
|
||||
|
||||
|
||||
@@ -1010,3 +1010,33 @@ def test_bundle_info_resolves_ghes_browser_release_url(project: Path):
|
||||
|
||||
payload = json.loads(result.output)
|
||||
assert payload["id"] == "demo-bundle"
|
||||
|
||||
|
||||
def test_bundle_download_rejects_oversized_response(project: Path, monkeypatch):
|
||||
"""Bundle download rejects responses exceeding MAX_DOWNLOAD_BYTES."""
|
||||
# Monkeypatch to a small limit so the test is fast and low-memory.
|
||||
monkeypatch.setattr(
|
||||
"specify_cli.commands.bundle.MAX_DOWNLOAD_BYTES", 100
|
||||
)
|
||||
|
||||
api_asset_url = "https://api.github.com/repos/org/repo/releases/assets/99"
|
||||
|
||||
def fake_open_url(url, timeout=None, extra_headers=None, redirect_validator=None):
|
||||
# Return a response that exceeds 100 bytes.
|
||||
return FakeBundleResponse(b"x" * 200, url=api_asset_url)
|
||||
|
||||
catalog = project / "catalog.json"
|
||||
write_catalog_file(
|
||||
catalog,
|
||||
{"demo-bundle": catalog_entry_dict("demo-bundle", download_url=api_asset_url)},
|
||||
)
|
||||
_make_catalog_config(catalog, project)
|
||||
|
||||
with patch("specify_cli.authentication.http.open_url", side_effect=fake_open_url):
|
||||
result = runner.invoke(app, ["bundle", "info", "demo-bundle", "--json"])
|
||||
|
||||
# Must fail with a size-limit error, not an unhandled traceback.
|
||||
assert result.exit_code == 1
|
||||
# Rich may wrap the message across lines; normalise whitespace before checking.
|
||||
output_flat = " ".join(result.output.split())
|
||||
assert "exceeds maximum size of 100 bytes" in output_flat
|
||||
|
||||
@@ -471,6 +471,101 @@ class TestAlquimiaArgumentHints:
|
||||
hint_count = sum(1 for ln in lines if ln.startswith("argument-hint:"))
|
||||
assert hint_count == 1
|
||||
|
||||
def test_inject_argument_hint_survives_folded_description(self):
|
||||
"""A long description folded across lines must not corrupt the YAML (#4044).
|
||||
|
||||
A description long enough for the YAML dumper to fold it into a
|
||||
multi-line plain scalar previously had ``argument-hint:`` spliced
|
||||
into the *middle* of that scalar, producing invalid YAML.
|
||||
"""
|
||||
from specify_cli.integrations.alquimia import AlquimiaAIIntegration
|
||||
|
||||
frontmatter = {
|
||||
"name": "speckit-specify",
|
||||
"description": (
|
||||
"Create or update the feature specification from a natural "
|
||||
"language feature description. Also accepts an issue URL "
|
||||
"resolved via gh CLI (demo customization)."
|
||||
),
|
||||
"compatibility": "Requires spec-kit project structure with .specify/ directory",
|
||||
}
|
||||
frontmatter_text = yaml.safe_dump(
|
||||
frontmatter, sort_keys=False, allow_unicode=True
|
||||
).strip()
|
||||
content = f"---\n{frontmatter_text}\n---\n\nBody text\n"
|
||||
assert "\n " in content, "fixture description must actually fold across lines"
|
||||
|
||||
result = AlquimiaAIIntegration.inject_argument_hint(
|
||||
content, "Describe the feature"
|
||||
)
|
||||
|
||||
parsed = yaml.safe_load(result.split("---")[1])
|
||||
assert parsed["argument-hint"] == "Describe the feature"
|
||||
assert parsed["description"] == frontmatter["description"]
|
||||
|
||||
def test_inject_argument_hint_survives_quoted_folded_description(self):
|
||||
"""A folded description forced into quotes must not absorb the hint (#4044)."""
|
||||
from specify_cli.integrations.alquimia import AlquimiaAIIntegration
|
||||
|
||||
frontmatter = {
|
||||
"name": "speckit-specify",
|
||||
"description": (
|
||||
"Create or update the feature specification from a natural "
|
||||
"language feature description. Also accepts a GitHub "
|
||||
"issue/PR URL or #N reference resolved via gh CLI (demo)."
|
||||
),
|
||||
"compatibility": "Requires spec-kit project structure with .specify/ directory",
|
||||
}
|
||||
frontmatter_text = yaml.safe_dump(
|
||||
frontmatter, sort_keys=False, allow_unicode=True
|
||||
).strip()
|
||||
content = f"---\n{frontmatter_text}\n---\n\nBody text\n"
|
||||
assert "\n " in content, "fixture description must actually fold across lines"
|
||||
|
||||
result = AlquimiaAIIntegration.inject_argument_hint(
|
||||
content, "Describe the feature"
|
||||
)
|
||||
|
||||
parsed = yaml.safe_load(result.split("---")[1])
|
||||
assert parsed["argument-hint"] == "Describe the feature"
|
||||
assert parsed["description"] == frontmatter["description"]
|
||||
|
||||
def test_inject_argument_hint_survives_multi_paragraph_description(self):
|
||||
"""A description with an embedded blank line must not absorb the hint.
|
||||
|
||||
PyYAML serializes an embedded ``\\n\\n`` inside a quoted scalar as
|
||||
unindented blank lines, not indented ones, so a fix that only skips
|
||||
indented continuation lines still fails on this case.
|
||||
"""
|
||||
from specify_cli.integrations.alquimia import AlquimiaAIIntegration
|
||||
|
||||
frontmatter = {
|
||||
"name": "speckit-specify",
|
||||
"description": (
|
||||
"First paragraph of a fairly long description that will "
|
||||
"need to wrap across multiple lines when dumped by PyYAML."
|
||||
"\n\n"
|
||||
"Second paragraph continues the description after a blank "
|
||||
"line separator to force embedded newlines in the scalar."
|
||||
),
|
||||
"compatibility": "Requires spec-kit project structure with .specify/ directory",
|
||||
}
|
||||
frontmatter_text = yaml.safe_dump(
|
||||
frontmatter, sort_keys=False, allow_unicode=True
|
||||
).strip()
|
||||
content = f"---\n{frontmatter_text}\n---\n\nBody text\n"
|
||||
assert "\n\n" in frontmatter_text, (
|
||||
"fixture must produce a blank continuation line"
|
||||
)
|
||||
|
||||
result = AlquimiaAIIntegration.inject_argument_hint(
|
||||
content, "Describe the feature"
|
||||
)
|
||||
|
||||
parsed = yaml.safe_load(result.split("---")[1])
|
||||
assert parsed["argument-hint"] == "Describe the feature"
|
||||
assert parsed["description"] == frontmatter["description"]
|
||||
|
||||
|
||||
class TestAlquimiaDisableModelInvocation:
|
||||
"""Verify disable-model-invocation is false for Alquimia skills."""
|
||||
|
||||
@@ -451,6 +451,93 @@ class TestClaudeArgumentHints:
|
||||
hint_count = sum(1 for ln in lines if ln.startswith("argument-hint:"))
|
||||
assert hint_count == 1
|
||||
|
||||
def test_inject_argument_hint_survives_folded_description(self):
|
||||
"""A long description folded across lines must not corrupt the YAML (#4044).
|
||||
|
||||
A description long enough for the YAML dumper to fold it into a
|
||||
multi-line plain scalar previously had ``argument-hint:`` spliced
|
||||
into the *middle* of that scalar, producing invalid YAML.
|
||||
"""
|
||||
from specify_cli.integrations.claude import ClaudeIntegration
|
||||
|
||||
frontmatter = {
|
||||
"name": "speckit-specify",
|
||||
"description": (
|
||||
"Create or update the feature specification from a natural "
|
||||
"language feature description. Also accepts an issue URL "
|
||||
"resolved via gh CLI (demo customization)."
|
||||
),
|
||||
"compatibility": "Requires spec-kit project structure with .specify/ directory",
|
||||
}
|
||||
frontmatter_text = yaml.safe_dump(
|
||||
frontmatter, sort_keys=False, allow_unicode=True
|
||||
).strip()
|
||||
content = f"---\n{frontmatter_text}\n---\n\nBody text\n"
|
||||
assert "\n " in content, "fixture description must actually fold across lines"
|
||||
|
||||
result = ClaudeIntegration.inject_argument_hint(content, "Describe the feature")
|
||||
|
||||
parsed = yaml.safe_load(result.split("---")[1])
|
||||
assert parsed["argument-hint"] == "Describe the feature"
|
||||
assert parsed["description"] == frontmatter["description"]
|
||||
|
||||
def test_inject_argument_hint_survives_quoted_folded_description(self):
|
||||
"""A folded description forced into quotes must not absorb the hint (#4044)."""
|
||||
from specify_cli.integrations.claude import ClaudeIntegration
|
||||
|
||||
frontmatter = {
|
||||
"name": "speckit-specify",
|
||||
"description": (
|
||||
"Create or update the feature specification from a natural "
|
||||
"language feature description. Also accepts a GitHub "
|
||||
"issue/PR URL or #N reference resolved via gh CLI (demo)."
|
||||
),
|
||||
"compatibility": "Requires spec-kit project structure with .specify/ directory",
|
||||
}
|
||||
frontmatter_text = yaml.safe_dump(
|
||||
frontmatter, sort_keys=False, allow_unicode=True
|
||||
).strip()
|
||||
content = f"---\n{frontmatter_text}\n---\n\nBody text\n"
|
||||
assert "\n " in content, "fixture description must actually fold across lines"
|
||||
|
||||
result = ClaudeIntegration.inject_argument_hint(content, "Describe the feature")
|
||||
|
||||
parsed = yaml.safe_load(result.split("---")[1])
|
||||
assert parsed["argument-hint"] == "Describe the feature"
|
||||
assert parsed["description"] == frontmatter["description"]
|
||||
|
||||
def test_inject_argument_hint_survives_multi_paragraph_description(self):
|
||||
"""A description with an embedded blank line must not absorb the hint.
|
||||
|
||||
PyYAML serializes an embedded ``\\n\\n`` inside a quoted scalar as
|
||||
unindented blank lines, not indented ones, so a fix that only skips
|
||||
indented continuation lines still fails on this case.
|
||||
"""
|
||||
from specify_cli.integrations.claude import ClaudeIntegration
|
||||
|
||||
frontmatter = {
|
||||
"name": "speckit-specify",
|
||||
"description": (
|
||||
"First paragraph of a fairly long description that will "
|
||||
"need to wrap across multiple lines when dumped by PyYAML."
|
||||
"\n\n"
|
||||
"Second paragraph continues the description after a blank "
|
||||
"line separator to force embedded newlines in the scalar."
|
||||
),
|
||||
"compatibility": "Requires spec-kit project structure with .specify/ directory",
|
||||
}
|
||||
frontmatter_text = yaml.safe_dump(
|
||||
frontmatter, sort_keys=False, allow_unicode=True
|
||||
).strip()
|
||||
content = f"---\n{frontmatter_text}\n---\n\nBody text\n"
|
||||
assert "\n\n" in frontmatter_text, "fixture must produce a blank continuation line"
|
||||
|
||||
result = ClaudeIntegration.inject_argument_hint(content, "Describe the feature")
|
||||
|
||||
parsed = yaml.safe_load(result.split("---")[1])
|
||||
assert parsed["argument-hint"] == "Describe the feature"
|
||||
assert parsed["description"] == frontmatter["description"]
|
||||
|
||||
|
||||
class TestClaudeDisableModelInvocation:
|
||||
"""Verify disable-model-invocation is false for Claude skills."""
|
||||
|
||||
@@ -1,10 +1,229 @@
|
||||
"""Tests for JunieIntegration."""
|
||||
|
||||
import os
|
||||
import pytest
|
||||
|
||||
from specify_cli.integrations import get_integration
|
||||
from specify_cli.integrations.junie import format_junie_command_name
|
||||
from .test_integration_base_markdown import MarkdownIntegrationTests
|
||||
|
||||
class TestJunieCommandNameFormatter:
|
||||
"""Test the junie command name formatter."""
|
||||
|
||||
def test_simple_name_without_prefix(self):
|
||||
"""Test formatting a simple name without 'speckit.' prefix."""
|
||||
assert format_junie_command_name("plan") == "speckit-plan"
|
||||
assert format_junie_command_name("tasks") == "speckit-tasks"
|
||||
assert format_junie_command_name("specify") == "speckit-specify"
|
||||
|
||||
def test_name_with_speckit_prefix(self):
|
||||
"""Test formatting a name that already has 'speckit.' prefix."""
|
||||
assert format_junie_command_name("speckit.plan") == "speckit-plan"
|
||||
assert format_junie_command_name("speckit.tasks") == "speckit-tasks"
|
||||
|
||||
def test_extension_command_name(self):
|
||||
"""Test formatting extension command names with dots."""
|
||||
assert (
|
||||
format_junie_command_name("speckit.my-extension.example")
|
||||
== "speckit-my-extension-example"
|
||||
)
|
||||
assert (
|
||||
format_junie_command_name("my-extension.example")
|
||||
== "speckit-my-extension-example"
|
||||
)
|
||||
|
||||
def test_idempotent_already_hyphenated(self):
|
||||
"""Test that already-hyphenated names are returned unchanged (idempotent)."""
|
||||
assert format_junie_command_name("speckit-plan") == "speckit-plan"
|
||||
assert (
|
||||
format_junie_command_name("speckit-my-extension-example")
|
||||
== "speckit-my-extension-example"
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
class TestJunieIntegration(MarkdownIntegrationTests):
|
||||
KEY = "junie"
|
||||
FOLDER = ".junie/"
|
||||
COMMANDS_SUBDIR = "commands"
|
||||
REGISTRAR_DIR = ".junie/commands"
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"cmd_name, expected_filename",
|
||||
[
|
||||
("plan", "speckit-plan.md"),
|
||||
("speckit.plan", "speckit-plan.md"),
|
||||
("speckit.git.commit", "speckit-git-commit.md"),
|
||||
("speckit", "speckit-speckit.md"),
|
||||
("speckitfoo", "speckit-speckitfoo.md"),
|
||||
],
|
||||
)
|
||||
|
||||
def test_junie_command_filename(self, cmd_name, expected_filename):
|
||||
"""Verify junie uses hyphenated filenames."""
|
||||
junie = get_integration("junie")
|
||||
assert junie.command_filename(cmd_name) == expected_filename
|
||||
|
||||
def test_junie_invoke_separator(self):
|
||||
"""Verify junie uses hyphen as invoke separator."""
|
||||
junie = get_integration("junie")
|
||||
assert junie.invoke_separator == "-"
|
||||
assert junie.registrar_config["invoke_separator"] == "-"
|
||||
|
||||
def test_junie_name_injection_and_formatting(self):
|
||||
"""Verify junie has inject_name and format_name configured."""
|
||||
junie = get_integration("junie")
|
||||
assert junie.registrar_config["inject_name"] is True
|
||||
assert junie.registrar_config[
|
||||
"format_name"] == format_junie_command_name
|
||||
|
||||
def test_junie_handoff_rewrite(self):
|
||||
"""Verify junie rewrites agent: speckit.foo to agent: speckit-foo."""
|
||||
junie = get_integration("junie")
|
||||
content = "---\nagent: speckit.plan\n---\n"
|
||||
rewritten = junie._rewrite_handoff_references(content)
|
||||
assert rewritten == "---\nagent: speckit-plan\n---\n"
|
||||
|
||||
def test_junie_hook_instruction_injection(self):
|
||||
"""Verify junie injects the dot-to-hyphen note for hooks."""
|
||||
junie = get_integration("junie")
|
||||
content = "- For each executable hook, output the following:\n"
|
||||
injected = junie._inject_hook_command_note(content)
|
||||
assert "replace dots (`.`) with hyphens (`-`)" in injected
|
||||
assert "- For each executable hook, output the following:" in injected
|
||||
|
||||
def test_junie_hook_instruction_injection_no_trailing_newline(self):
|
||||
"""Note must not collapse onto the instruction line when the
|
||||
instruction is the final line with no trailing newline.
|
||||
|
||||
The injection regex matches the end-of-line via ``(\\r\\n|\\n|$)``, so
|
||||
the captured ``eol`` is empty on a file's last line that lacks a
|
||||
trailing newline. Without an ``or "\\n"`` fallback the note text and
|
||||
the instruction are emitted on the same line.
|
||||
"""
|
||||
junie = get_integration("junie")
|
||||
content = "- For each executable hook, output the following:" # no trailing \n
|
||||
injected = junie._inject_hook_command_note(content)
|
||||
assert "replace dots (`.`) with hyphens (`-`)" in injected
|
||||
# Instruction stays on its own line rather than being mashed onto the note.
|
||||
assert "\n- For each executable hook, output the following:" in injected
|
||||
|
||||
# -- Overrides for MarkdownIntegrationTests ---------------------------
|
||||
|
||||
def test_setup_creates_files(self, tmp_path):
|
||||
from specify_cli.integrations.manifest import IntegrationManifest
|
||||
|
||||
i = get_integration(self.KEY)
|
||||
m = IntegrationManifest(self.KEY, tmp_path)
|
||||
created = i.setup(tmp_path, m)
|
||||
assert len(created) > 0
|
||||
cmd_files = [
|
||||
f
|
||||
for f in created
|
||||
if "scripts" not in f.parts
|
||||
and f.suffix == ".md"
|
||||
]
|
||||
for f in cmd_files:
|
||||
assert f.exists()
|
||||
assert f.name.startswith("speckit-")
|
||||
assert f.name.endswith(".md")
|
||||
|
||||
specify_file = next(
|
||||
(f for f in cmd_files if f.name == "speckit-specify.md"), None
|
||||
)
|
||||
assert specify_file is not None
|
||||
specify_contents = specify_file.read_text(encoding="utf-8")
|
||||
assert "/speckit-plan" in specify_contents
|
||||
assert "/speckit.plan" not in specify_contents
|
||||
|
||||
def test_integration_flag_creates_files(self, tmp_path):
|
||||
from typer.testing import CliRunner
|
||||
from specify_cli import app
|
||||
|
||||
project = tmp_path / f"int-{self.KEY}"
|
||||
project.mkdir()
|
||||
old_cwd = os.getcwd()
|
||||
try:
|
||||
os.chdir(project)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
app,
|
||||
[
|
||||
"init",
|
||||
"--here",
|
||||
"--integration",
|
||||
self.KEY,
|
||||
"--script",
|
||||
"sh",
|
||||
"--ignore-agent-tools",
|
||||
],
|
||||
catch_exceptions=False,
|
||||
)
|
||||
finally:
|
||||
os.chdir(old_cwd)
|
||||
assert result.exit_code == 0
|
||||
i = get_integration(self.KEY)
|
||||
cmd_dir = i.commands_dest(project)
|
||||
assert cmd_dir.is_dir()
|
||||
commands = sorted(cmd_dir.glob("speckit-*"))
|
||||
assert len(commands) > 0
|
||||
|
||||
def _expected_files(self, script_variant: str) -> list[str]:
|
||||
"""Override to expect hyphenated speckit- prefix."""
|
||||
i = get_integration(self.KEY)
|
||||
cmd_dir = i.registrar_config["dir"]
|
||||
files = []
|
||||
|
||||
# Command files
|
||||
for stem in (
|
||||
self.COMMANDS_SUBDIR_STEMS
|
||||
if hasattr(self, "COMMANDS_SUBDIR_STEMS")
|
||||
else self.COMMAND_STEMS
|
||||
):
|
||||
files.append(f"{cmd_dir}/speckit-{stem.replace('.', '-')}.md")
|
||||
|
||||
# Framework files
|
||||
files.append(".specify/integration.json")
|
||||
files.append(".specify/init-options.json")
|
||||
files.append(f".specify/integrations/{self.KEY}.manifest.json")
|
||||
files.append(".specify/integrations/speckit.manifest.json")
|
||||
files.append(".specify/.gitignore")
|
||||
|
||||
if script_variant == "sh":
|
||||
for name in [
|
||||
"check-prerequisites.sh",
|
||||
"common.sh",
|
||||
"create-new-feature.sh",
|
||||
"resolve-template.sh",
|
||||
"setup-plan.sh",
|
||||
"setup-tasks.sh",
|
||||
]:
|
||||
files.append(f".specify/scripts/bash/{name}")
|
||||
else:
|
||||
for name in [
|
||||
"check-prerequisites.ps1",
|
||||
"common.ps1",
|
||||
"create-new-feature.ps1",
|
||||
"resolve-template.ps1",
|
||||
"setup-plan.ps1",
|
||||
"setup-tasks.ps1",
|
||||
]:
|
||||
files.append(f".specify/scripts/powershell/{name}")
|
||||
|
||||
for name in [
|
||||
"checklist-template.md",
|
||||
"constitution-template.md",
|
||||
"plan-template.md",
|
||||
"spec-template.md",
|
||||
"tasks-template.md",
|
||||
]:
|
||||
files.append(f".specify/templates/{name}")
|
||||
|
||||
files.append(".specify/memory/.constitution-template.json")
|
||||
files.append(".specify/memory/constitution.md")
|
||||
# Bundled workflow
|
||||
files.append(".specify/workflows/speckit/workflow.yml")
|
||||
files.append(".specify/workflows/workflow-registry.json")
|
||||
|
||||
return sorted(files)
|
||||
|
||||
@@ -62,6 +62,32 @@ def test_pinned_integration_with_indeterminate_active_fails():
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("blank", ["", " ", "\t"])
|
||||
def test_pinned_integration_with_blank_active_fails(blank):
|
||||
"""A blank active integration is indeterminate, not a match.
|
||||
|
||||
The clash guard is a truthiness test and the indeterminate guard is an
|
||||
`is None` test, so `""` satisfied neither and fell through to
|
||||
`effective_integration = required` — silently adopting the bundle's pinned
|
||||
integration, the exact outcome the docstring says the guard prevents.
|
||||
"""
|
||||
manifest = _manifest(integration={"id": "claude"})
|
||||
with pytest.raises(BundlerError, match="could not be determined"):
|
||||
resolve_install_plan(
|
||||
manifest, speckit_version="0.11.2", active_integration=blank
|
||||
)
|
||||
|
||||
|
||||
def test_padded_active_integration_is_not_a_clash_with_itself():
|
||||
"""A padded value must strip, like the writer's clean_integration_key,
|
||||
rather than be reported as clashing with its own unpadded form."""
|
||||
manifest = _manifest(integration={"id": "claude"})
|
||||
plan = resolve_install_plan(
|
||||
manifest, speckit_version="0.11.2", active_integration=" claude "
|
||||
)
|
||||
assert plan.effective_integration == "claude"
|
||||
|
||||
|
||||
def test_pinned_integration_with_indeterminate_active_allows_explicit_override():
|
||||
manifest = _manifest(integration={"id": "claude"})
|
||||
plan = resolve_install_plan(
|
||||
|
||||
Reference in New Issue
Block a user