Compare commits

..
Author SHA1 Message Date
copilot-swe-agent[bot]andGitHub f20678dfd2 Initial plan 2026-08-12 22:48:42 +00:00
197dde6253 fix(bundler): treat a blank active integration as indeterminate in FR-019 (#3886)
`resolve_install_plan`'s two FR-019 guards are a truthiness test and an
`is None` test:

    if active_integration and required != active_integration:   # clash
    if active_integration is None and not integration_explicit: # indeterminate

An empty string satisfies neither, so it falls through to
`effective_integration = required` and the bundle's pinned integration is
silently adopted — the exact outcome the docstring says the guard prevents
("resolution fails instead of silently adopting the bundle's required
integration").

  active=None        -> BundlerError: ... could not be determined
  active='' (blank)  -> effective_integration='copilot'   <-- silent adopt
  active='claude'    -> BundlerError: ... targets integration 'copilot'

Normalise a blank value to None before the guards, and strip first to
match the writer (`integration_state.clean_integration_key`, which returns
`None` for empty/whitespace and strips otherwise) so a padded value is not
reported as clashing with its own unpadded form.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 11:52:34 -05:00
Luca BottiandGitHub f2583e675c Integrate Junie with dot-to-hyphen behavior and command formatting (#4073)
* Add Junie integration with dot-to-hyphen behavior, command formatting, and file transformations. Based on Cline Integration.

* Fix references to Cline in Junie integration and update class/test names for consistency.

* Fix references to Cline in Junie integration and update class/test names for consistency.

* Modified to generate correct formatting in junie
2026-08-12 11:36:12 -05:00
github-actions[bot]GitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Copilot
996b24c561 Update A11Y Governance preset to v0.4.3 (#4074)
Update a11y-governance preset submitted by @hindermath to:
- presets/catalog.community.json (version, download_url, documentation, description, templates count, tags)
- docs/community/presets.md community presets table

Closes #4064


Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-12 11:34:40 -05:00
b77ca572ca Fix Alquimia argument hints after folded descriptions (#4063)
Co-authored-by: root <kinsonnee@gmail.com>
2026-08-12 11:13:02 -05:00
Quratulain-bilalandGitHub c1bceb625c fix: use bounded read for bundle download HTTP responses (#3764)
* fix: use bounded read for bundle download HTTP responses

The bundle download used unbounded resp.read() to read HTTP responses
into memory. A malicious or misconfigured catalog server could return
an arbitrarily large payload causing OOM.

Replace with read_response_limited() capped at MAX_DOWNLOAD_BYTES
(50 MiB), consistent with how other download paths in the codebase
enforce bounded reads.

Add regression test that monkeypatches MAX_DOWNLOAD_BYTES to 100 bytes
and verifies oversized responses are rejected.

* fix: remove duplicate import of MAX_DOWNLOAD_BYTES and read_response_limited
2026-08-12 08:29:36 -05:00
github-actions[bot]GitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Copilot
7dd706880e Update iSAQB Architecture Governance preset to v0.2.2 (#4056)
Update isaqb-architecture-governance preset submitted by @hindermath:
- presets/catalog.community.json (version, download_url, documentation, description, templates count, tags, updated_at)
- docs/community/presets.md community presets table

Closes #4055

Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-11 16:58:14 -05:00
chelsealongandGitHub bd595cf838 fix(claude): make argument-hint injection fold-aware for long descriptions (#4045)
* fix(claude): make argument-hint injection fold-aware for long descriptions

ClaudeIntegration.inject_argument_hint spliced argument-hint: "..." as a
raw text line right after the first line starting with "description:".
When a description is long enough for the YAML dumper to fold it across
indented continuation lines, that splice landed inside the scalar,
producing invalid YAML (plain scalar) or silently absorbing the hint
into the description string (quoted scalar). This reproduces #3991 for
the case #3996 didn't cover: bundled core commands have no argument-hint
in their source frontmatter, so the structural apply_argument_hint path
is a no-op and this raw-text fallback is what actually runs.

Skip every continuation line of the description scalar (anything more
indented than the key itself) before inserting, so the new key always
lands after the whole scalar ends rather than in the middle of it.

Fixes #4044

* fix(claude): also skip unindented blank lines in description scalar

PyYAML serializes an embedded paragraph break ("\n\n") inside a quoted
description as unindented blank lines, not indented continuation
lines. inject_argument_hint only skipped indented lines, so it still
inserted argument-hint mid-scalar for multi-paragraph descriptions,
reproducing the #4044 failure modes. Skip blank lines too, and add a
regression test for the multi-paragraph case.
2026-08-11 13:14:19 -05:00
github-actions[bot]GitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Copilot
85d3ed289d Add SpecKit Grill Me extension to community catalog (#4052)
Add grill extension submitted by @yoshi1220 to:
- extensions/catalog.community.json (alphabetical order)
- docs/community/extensions.md community extensions table

Closes #4047

Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-11 13:12:48 -05:00
github-actions[bot]GitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Copilot
5ec937e07a Update Architecture Governance preset to v0.5.2 (#4050)
Update architecture-governance preset submitted by @hindermath to:
- presets/catalog.community.json (version, download_url, documentation, description, provides, tags, updated_at)
- docs/community/presets.md community presets table

Closes #4042

Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-11 11:17:33 -05:00
6461f09483 Remove auto-assign from catalog submission workflow (#4054)
Catalog submission issues no longer auto-assign mnriem. The workflow now
only posts the team notification comment (cc @github/spec-kit-maintainers),
since GitHub issues cannot be assigned to a team. Renamed the workflow and
job to reflect its notification-only purpose.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, supervised)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 63ae7e23-13e3-4a83-96b3-1178422aaa6e
2026-08-11 11:16:53 -05:00
fb427e47ef docs: clarify example spec guidance (#4048)
Co-authored-by: root <kinsonnee@gmail.com>
2026-08-11 11:11:30 -05:00
bd04776dcc Clarify custom checklist ownership and lifecycle (#4028)
* docs: clarify custom checklist lifecycle

* docs: clarify implement checklist marker ownership

---------

Co-authored-by: root <kinsonnee@gmail.com>
2026-08-11 11:09:56 -05:00
github-actions[bot]GitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Copilot
c5258e680b Update Archive Extension to v1.2.2 (#4053)
Update archive extension submitted by @stn1slv:
- extensions/catalog.community.json (version, download_url, updated_at)
- docs/community/extensions.md community extensions table

Closes #4049

Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-11 10:29:58 -05:00
github-actions[bot]GitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Copilot
9d15554c08 Update Security Governance preset to v0.6.2 (#4040)
Update security-governance preset submitted by @hindermath to:
- presets/catalog.community.json (version, download_url, description, documentation, provides, tags, updated_at)
- docs/community/presets.md community presets table

Closes #4039

Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-10 16:22:36 -05:00
c3bbcc40a0 docs: clarify maintainer applies submission label during triage (#4041)
Community extension, preset, and bundle submissions are validated by
label-triggered agentic workflows that only run once the corresponding
`*-submission` label is applied. On this public repo contributors cannot
apply that label themselves, so a maintainer applies it during issue
triage. Document this in the three submission issue templates, the preset
publishing guide, and correct the extension guide's inaccurate claim that
issues are "automatically labeled and assigned".

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 3b6f6854-9be6-4b67-b5b9-34b19ededcb8
2026-08-10 15:37:51 -05:00
Manfred RiemGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
39b56626c9 chore: release 0.16.2, begin 0.16.3.dev0 development (#4038)
* chore: bump version to 0.16.2

* chore: begin 0.16.3.dev0 development

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-10 14:46:12 -05:00
29 changed files with 827 additions and 131 deletions
@@ -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:
+2 -18
View File
@@ -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,
+4 -2
View File
@@ -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
+2 -1
View File
@@ -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) |
+4 -4
View File
@@ -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) |
+1 -1
View File
@@ -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
View File
@@ -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)
+9 -1
View File
@@ -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
+1 -1
View File
@@ -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`
+39 -4
View File
@@ -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",
+6
View File
@@ -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
+31 -72
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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,
+5
View File
@@ -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
+7 -2
View File
@@ -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
+11 -1
View File
@@ -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
+16 -13
View File
@@ -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
+30
View File
@@ -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)
+26
View File
@@ -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(