diff --git a/docs/extensions/api.md b/docs/extensions/api.md index 3ba1641839..682221c4f4 100644 --- a/docs/extensions/api.md +++ b/docs/extensions/api.md @@ -632,8 +632,6 @@ Standard events (defined by core): - `after_clarify` - After spec clarification - `before_constitution` - Before constitution update - `after_constitution` - After constitution update -- `before_taskstoissues` - Before tasks-to-issues conversion -- `after_taskstoissues` - After tasks-to-issues conversion #### Bundled `bug` extension events (available when the extension is installed) diff --git a/docs/extensions/developing.md b/docs/extensions/developing.md index e1babe7778..ed95e2c2c3 100644 --- a/docs/extensions/developing.md +++ b/docs/extensions/developing.md @@ -237,7 +237,6 @@ Available hook points: - `before_checklist` / `after_checklist`: Before/after checklist generation - `before_clarify` / `after_clarify`: Before/after spec clarification - `before_constitution` / `after_constitution`: Before/after constitution update -- `before_taskstoissues` / `after_taskstoissues`: Before/after tasks-to-issues conversion - `before_bug_assess` / `after_bug_assess`: Before/after bug assessment (bundled `bug` extension) - `before_bug_fix` / `after_bug_fix`: Before/after bug remediation (bundled `bug` extension) - `before_bug_test` / `after_bug_test`: Before/after bug verification (bundled `bug` extension) diff --git a/docs/extensions/overview.md b/docs/extensions/overview.md index 39ad4d4050..033e5c2f4b 100644 --- a/docs/extensions/overview.md +++ b/docs/extensions/overview.md @@ -388,7 +388,6 @@ settings: # before_tasks, after_tasks, before_implement, after_implement, # before_analyze, after_analyze, before_checklist, after_checklist, # before_clarify, after_clarify, before_constitution, after_constitution, -# before_taskstoissues, after_taskstoissues # Bundled bug extension events (when installed): # before_bug_assess, after_bug_assess, before_bug_fix, after_bug_fix, # before_bug_test, after_bug_test diff --git a/docs/installation.md b/docs/installation.md index 1226e01dc6..54f5c024cc 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -155,16 +155,6 @@ After initialization, you should see the following commands available in your co - `/speckit.checklist` - Generate quality checklists - `/speckit.constitution` - Create or update project principles - `/speckit.converge` - Assess codebase against artifacts and append remaining tasks -- `/speckit.taskstoissues` - Convert tasks to issues (deprecated; warns on invocation; replaced by the bundled `github` extension's - `/speckit.github.taskstoissues`; install it with `specify extension add github`) - -The `generic` integration also registers extension commands in its configured -`--commands-dir`. Installing `github` makes `/speckit.github.taskstoissues` -available there as a command file, or as `/speckit-github-taskstoissues` when -`--skills` is enabled. The core `/speckit.taskstoissues` (or -`/speckit-taskstoissues` with `--skills`) remains available during migration, warns on invocation, -and will be removed in a future minor release. -See the [GitHub extension's installation notes](https://github.com/github/spec-kit/blob/main/extensions/github/README.md#installation). Scripts are installed into a variant subdirectory matching the chosen script type: diff --git a/docs/integrations/cli.md b/docs/integrations/cli.md index c320a0f5e6..8605e691f8 100644 --- a/docs/integrations/cli.md +++ b/docs/integrations/cli.md @@ -486,15 +486,6 @@ Docker Agent currently exposes structured agent/safety configuration. Headless approval flags, model forwarding, and structured-output support are also adapter-specific, not universal guarantees. -## GitHub Issue Creation - -The deprecated `speckit.taskstoissues` command still runs with a warning but -will be removed in a future minor release. Install the opt-in GitHub extension -with `specify extension add github` and use `speckit.github.taskstoissues` -instead. See the [extension's migration guide](https://github.com/github/spec-kit/blob/main/extensions/github/README.md#migrating-from-the-core-taskstoissues-command) -for invocation syntax and migration details; the core command does not install -the extension automatically. - ## FAQ ### Can I install multiple integrations in the same project? diff --git a/docs/reference/agentic-sdd.md b/docs/reference/agentic-sdd.md index bef8b1c09d..8d2ed3f39d 100644 --- a/docs/reference/agentic-sdd.md +++ b/docs/reference/agentic-sdd.md @@ -30,7 +30,6 @@ skills by default. | `/speckit.tasks` | `speckit-tasks` | Break the plan into actionable tasks | | `/speckit.implement` | `speckit-implement` | Execute the tasks | | `/speckit.converge` | `speckit-converge` | Assess implementation against the artifacts and append remaining work | -| `/speckit.taskstoissues` | `speckit-taskstoissues` | **Deprecated core command**; use the `github` extension for GitHub issues | | `/speckit.clarify` | `speckit-clarify` | Resolve ambiguity before planning (optional quality gate; formerly `/quizme`) | | `/speckit.analyze` | `speckit-analyze` | Check artifact consistency after tasks and before implementation (optional quality gate) | | `/speckit.checklist` | `speckit-checklist` | Generate requirements-quality checklists (optional quality gate) | @@ -157,31 +156,3 @@ It first prints a severity-graded findings summary, then resolves to one of two - **Converged** — no gaps found. `tasks.md` is left byte-for-byte unchanged and you'll see a clean result like `✅ Converged — the implementation satisfies the spec, plan, and tasks.` You're done; proceed to review or open a PR. - **Tasks appended** — gaps found. Converge appends them as new tasks under a Convergence section in `tasks.md` and tells you how many. Run `/speckit.implement` again to complete them, then `/speckit.converge` once more. Each pass finds fewer items; repeat until it reports converged. - -## `/speckit.taskstoissues` (deprecated core command) - -The core command is deprecated and will be removed in a future minor release. -It warns when invoked, then continues its existing workflow without installing -or enabling the extension automatically. - -The recommended extension command optionally converts an existing `tasks.md` -into actionable GitHub issues. Install the extension from your project root: - -```bash -specify extension add github -``` - -Then invoke it in your agent after generating tasks when you want to track -execution in GitHub: - -```text -/speckit.github.taskstoissues -``` - -This command requires a GitHub `origin` remote and access to the GitHub MCP tools -for listing and creating issues. It creates issues only in the repository -identified by that remote and checks existing task IDs to avoid duplicates. -It is not required to implement tasks or converge on a feature. - -> [!NOTE] -> GitHub issue tracking uses the bundled, opt-in [`github` extension](https://github.com/github/spec-kit/blob/main/extensions/github/README.md). Its recommended command is `/speckit.github.taskstoissues` (slash-hyphen integrations: `/speckit-github-taskstoissues`; Codex, ZCode, and Command Code skills: `$speckit-github-taskstoissues`; Kimi: `/skill:speckit-github-taskstoissues`); the deprecated core `/speckit.taskstoissues` remains available during migration. The `generic` integration registers the extension under its configured command directory in commands or skills mode. See the [installation and migration notes](https://github.com/github/spec-kit/blob/main/extensions/github/README.md#installation) for all integration-specific invocation syntaxes. diff --git a/docs/reference/extensions.md b/docs/reference/extensions.md index 4c93f94dbe..72ca64d3dc 100644 --- a/docs/reference/extensions.md +++ b/docs/reference/extensions.md @@ -37,7 +37,7 @@ specify extension add | `--force` | Overwrite if the extension is already installed | | `--priority `| Resolution priority (default: 10; lower = higher precedence) | -Installs an extension from the catalog, a URL, or a local directory. Extension commands are registered with the active AI coding agent integration. For `generic`, invocations use the configured `--commands-dir`: flat command files by default, or `speckit-/SKILL.md` with `--skills`. The deprecated core `speckit.taskstoissues` command remains available during migration and will be removed in a future minor release. It warns on invocation and continues its existing workflow without installing or enabling the extension automatically. For GitHub issue tracking, run `specify extension add github` and use the recommended `/speckit.github.taskstoissues` command (slash-hyphen integrations: `/speckit-github-taskstoissues`; Codex, ZCode, and Command Code skills: `$speckit-github-taskstoissues`; Kimi: `/skill:speckit-github-taskstoissues`). +Installs an extension from the catalog, a URL, or a local directory. Extension commands are registered with the active AI coding agent integration. For `generic`, invocations use the configured `--commands-dir`: flat command files by default, or `speckit-/SKILL.md` with `--skills`. If a generic integration refresh cannot produce every extension invocation (for example, because a command or skill is user-modified or its source is missing), it warns and restores that extension's prior registered artifacts. Other extensions can still refresh. diff --git a/extensions/catalog.json b/extensions/catalog.json index fed3f08842..f7bbe0a302 100644 --- a/extensions/catalog.json +++ b/extensions/catalog.json @@ -66,7 +66,7 @@ "github": { "name": "GitHub Integration", "id": "github", - "version": "1.0.4", + "version": "1.0.5", "description": "GitHub platform integration for Spec Kit - create GitHub issues from a feature's task list", "author": "spec-kit-core", "repository": "https://github.com/github/spec-kit", diff --git a/extensions/github/README.md b/extensions/github/README.md index 95e239618f..e2a6cd0f71 100644 --- a/extensions/github/README.md +++ b/extensions/github/README.md @@ -23,10 +23,8 @@ specify extension add github The `generic` (bring your own agent) integration registers this command under its configured `--commands-dir` in both commands and skills layouts. Installing `github` creates `speckit.github.taskstoissues.md` in commands mode or -`speckit-github-taskstoissues/SKILL.md` in skills mode. The core -`speckit.taskstoissues` command is deprecated but remains available during migration. -Use this extension's recommended command instead. See -[integration-specific options](../../docs/integrations/cli.md#integration-specific-options). +`speckit-github-taskstoissues/SKILL.md` in skills mode. See +[integration-specific options](https://github.github.io/spec-kit/integrations/cli.html#integration-specific-options). ## Removal @@ -74,56 +72,66 @@ The extension ships its own feature-resolution script in all three supported run | PowerShell | `scripts/powershell/resolve-tasks.ps1` | | Python | `scripts/python/resolve_tasks.py` | -Once installed they live under `.specify/extensions/github/scripts/`. The script is a trimmed twin of core's `check-prerequisites`: it resolves the project root and active feature directory, requires `plan.md` and `tasks.md`, and reports the design docs alongside them. It matches the invocation the core command makes (`--require-tasks --include-tasks`), so `spec.md` stays optional. By default, a `SPECIFY_FEATURE_DIRECTORY` override is persisted to `.specify/feature.json` so a later run without the variable resolves to the same feature. When `SPECIFY_FEATURE_NO_PERSIST=1` or `true`, the override still selects the feature for this run, but the script does not create or modify `feature.json`. +Once installed they live under `.specify/extensions/github/scripts/`. The script is a trimmed twin of core's `check-prerequisites`: it resolves the project root and active feature directory, requires `plan.md` and `tasks.md`, and reports the design docs alongside them. `spec.md` stays optional. By default, a `SPECIFY_FEATURE_DIRECTORY` override is persisted to `.specify/feature.json` so a later run without the variable resolves to the same feature. When `SPECIFY_FEATURE_NO_PERSIST=1` or `true`, the override still selects the feature for this run, but the script does not create or modify `feature.json`. Prerequisite errors name the Spec Kit command without assuming an integration's invocation syntax. Use the syntax described under [Commands](#commands) for your integration. ## Migrating from the core `taskstoissues` command -Spec Kit is moving GitHub issue tracking out of core in three stages: - -1. **Initial stage (completed)** — this extension shipped with the core `/speckit.taskstoissues` command still available. -2. **Next (current stage)** — the replacement has been available for at least one release, so the core command is now deprecated. It displays a migration warning and continues its existing workflow. It does not install or enable the extension automatically or run the replacement command. -3. **Later** — the core command will be removed in a future minor release. +**Breaking change:** the core command has been removed. Integration upgrades +delete old manifest-tracked `taskstoissues` command and skill files, rather than +replacing them with an updated prompt. Back up customized legacy and extension +command or skill files outside the generated directories **before** upgrading; +forcing the upgrade with `--force` also authorizes deletion of customized legacy +files. Reapply customizations only after the migration steps below, because the +upgrade can overwrite generated extension files. Installing this extension does +not restore the old invocation or provide a compatibility alias. For existing projects, upgrading the CLI alone does not refresh the generated core command files. After upgrading the CLI, run this from the project root: ```bash -specify integration upgrade +specify integration upgrade ``` -Replace `` with the installed integration key, or omit it to upgrade the -default integration. This refreshes the core command with the deprecation -warning. If locally modified files block the upgrade, inspect those changes -before deciding whether to overwrite them. See [Upgrade an Integration](../../docs/integrations/cli.md#upgrade-an-integration). +Replace `` with the installed integration key, or omit it to upgrade +the default integration. Repeat for each installed integration. If locally +modified files block the upgrade, back them up before deciding whether to +delete the legacy files with `--force`. See +[Upgrade an Integration](https://github.github.io/spec-kit/integrations/cli.html#upgrade-an-integration). -To migrate, install the extension and use the namespaced command instead: +Then install the extension, **skipping this step if `github` is already +installed**, and use the namespaced command instead: ```bash specify extension add github ``` +An already-installed, enabled extension is re-registered when upgrading the +default integration only. Before using the replacement in a non-default +integration, run `specify integration use ` to select it and register +the extension there. After upgrading, installing if needed, and selecting the +integration, reapply customizations from your separate backup to the namespaced +extension command. + The `generic` integration supports this migration in both commands and skills -layouts under its configured `--commands-dir`. The core command remains -available during this deprecation period until the removal stage. +layouts under its configured `--commands-dir`. -| Deprecated core command | Recommended extension command | +| Core command | Recommended extension command | | ------------------------- | --------------------------------- | | `/speckit.taskstoissues` | `/speckit.github.taskstoissues` | -Apart from the core command's deprecation warning, conversion behavior is unchanged: the same feature resolution, the same `plan.md` and `tasks.md` prerequisites, the same remote validation, the same deduplication across open and closed issues, the same issue titles, and the same hook contract. The extension command and deprecated core command coexist without shadowing each other while the core command still exists. +Conversion behavior is unchanged: the same feature resolution, the same `plan.md` and `tasks.md` prerequisites, the same remote validation, the same deduplication across open and closed issues, the same issue titles, and the same hook contract. This extension does **not** register `speckit.taskstoissues` as an alias. ### Presets and dependent extensions -Before the core command is removed, audit references to `speckit.taskstoissues` +Audit references to `speckit.taskstoissues` in presets, extension prompts, workflow steps, handoffs, and project automation. For GitHub issue conversion, use the canonical `speckit.github.taskstoissues` command ID and render it with the agent-specific syntax under [Commands](#commands). Replace `__SPECKIT_COMMAND_TASKSTOISSUES__` cross-command references with `__SPECKIT_COMMAND_GITHUB_TASKSTOISSUES__` where template rendering is supported. -Document `specify extension add github` as a setup prerequisite; the deprecation -warning does not install this dependency. +Document `specify extension add github` as a setup prerequisite. If an extension proposal or external dependency declaration uses `requires.commands: [speckit.taskstoissues]`, update its target to @@ -137,11 +145,4 @@ Preset command overrides are separate from command references: an override of command. Review and port any needed customization, then verify the installed command in a sample project before retiring the old override. A preset that uses another issue tracker should retain its own provider-specific workflow -rather than redirecting it to the GitHub command. Existing overrides may also -hide the core deprecation warning, so communicate the migration to their users. - -Keep the `before_taskstoissues` and `after_taskstoissues` hook keys unchanged; -both commands consume them. Only update a hook's command reference if it invokes -the deprecated command. Do not invoke both core and replacement commands for -the same conversion, since that also runs their hooks twice. Publish and test -updated preset/extension versions before the later core-removal release. +rather than redirecting it to the GitHub command. diff --git a/extensions/github/extension.yml b/extensions/github/extension.yml index e8305c39b2..70a171f5f0 100644 --- a/extensions/github/extension.yml +++ b/extensions/github/extension.yml @@ -3,7 +3,7 @@ schema_version: "1.0" extension: id: github name: "GitHub Integration" - version: "1.0.4" + version: "1.0.5" description: "GitHub platform integration for Spec Kit - create GitHub issues from a feature's task list" author: spec-kit-core repository: https://github.com/github/spec-kit diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index 181b0feec1..402a7fe564 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -384,7 +384,6 @@ def _print_cli_warning( "clarify": "Structured clarification workflow for underspecified requirements.", "constitution": "Create or update project governing principles and development guidelines.", "checklist": "Generate custom quality checklists for validating requirements completeness and clarity.", - "taskstoissues": "Deprecated: Convert tasks from tasks.md into GitHub issues.", } diff --git a/src/specify_cli/extensions/__init__.py b/src/specify_cli/extensions/__init__.py index c5f47bf9b2..b8065ca274 100644 --- a/src/specify_cli/extensions/__init__.py +++ b/src/specify_cli/extensions/__init__.py @@ -67,7 +67,6 @@ "plan", "specify", "tasks", - "taskstoissues", } ) EXTENSION_COMMAND_NAME_PATTERN = re.compile(r"^speckit\.([a-z0-9-]+)\.([a-z0-9-]+)$") @@ -1209,10 +1208,8 @@ def _normalize_shadow_name(name: str) -> str: Agent integrations (Cline, Forge, Junie) and the SKILL.md output-name computation (``CommandRegistrar._compute_output_name``) all collapse - dots to hyphens and prefix a bare name with ``speckit-``, so - ``speckit.taskstoissues``, ``taskstoissues``, and - ``speckit-taskstoissues`` are distinct alias spellings that land on - the same on-disk command name. Normalize before comparing so all of + dots to hyphens and prefix a bare name with ``speckit-``. + Normalize before comparing so all of them are caught, not just the exact dotted spelling. """ hyphenated = name.replace(".", "-") diff --git a/src/specify_cli/integrations/alquimia/__init__.py b/src/specify_cli/integrations/alquimia/__init__.py index 54ea47f486..271451749d 100644 --- a/src/specify_cli/integrations/alquimia/__init__.py +++ b/src/specify_cli/integrations/alquimia/__init__.py @@ -18,7 +18,6 @@ "clarify": "Optional areas to clarify in the spec", "constitution": "Principles or values for the project constitution", "checklist": "Domain or focus area for the checklist", - "taskstoissues": "Optional filter or label for GitHub issues", } diff --git a/src/specify_cli/integrations/base.py b/src/specify_cli/integrations/base.py index 95545b67a7..2dfc9e15c9 100644 --- a/src/specify_cli/integrations/base.py +++ b/src/specify_cli/integrations/base.py @@ -53,7 +53,6 @@ "checklist", "specify", "tasks", - "taskstoissues", ) _CORE_COMMAND_TEMPLATE_RANK = { command: index for index, command in enumerate(_CORE_COMMAND_TEMPLATE_ORDER) diff --git a/src/specify_cli/integrations/claude/__init__.py b/src/specify_cli/integrations/claude/__init__.py index 03d428bc51..e2de919b8c 100644 --- a/src/specify_cli/integrations/claude/__init__.py +++ b/src/specify_cli/integrations/claude/__init__.py @@ -20,7 +20,6 @@ "clarify": "Optional areas to clarify in the spec", "constitution": "Principles or values for the project constitution", "checklist": "Domain or focus area for the checklist", - "taskstoissues": "Optional filter or label for GitHub issues", } # Per-command frontmatter overrides for skills that should run in a forked diff --git a/src/specify_cli/integrations/copilot/__init__.py b/src/specify_cli/integrations/copilot/__init__.py index a283dc3df2..9a72be3a14 100644 --- a/src/specify_cli/integrations/copilot/__init__.py +++ b/src/specify_cli/integrations/copilot/__init__.py @@ -43,7 +43,6 @@ "plan", "specify", "tasks", - "taskstoissues", } diff --git a/templates/commands/taskstoissues.md b/templates/commands/taskstoissues.md deleted file mode 100644 index dea7e7cfe5..0000000000 --- a/templates/commands/taskstoissues.md +++ /dev/null @@ -1,114 +0,0 @@ ---- -description: "Deprecated: Convert existing tasks into actionable, dependency-ordered GitHub issues for the feature based on available design artifacts." -tools: ['github/github-mcp-server/list_issues', 'github/github-mcp-server/issue_write'] -scripts: - sh: scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks - ps: scripts/powershell/check-prerequisites.ps1 -Json -RequireTasks -IncludeTasks - py: scripts/python/check_prerequisites.py --json --require-tasks --include-tasks ---- - -## Deprecation Notice - -You **MUST display the following concise warning first**, as the first step of this command and before the User Input and Pre-Execution Checks sections below: - -> ⚠️ `__SPECKIT_COMMAND_TASKSTOISSUES__` is deprecated and will be removed in a future minor release. Use the bundled `github` extension instead: run `specify extension add github`, then invoke `__SPECKIT_COMMAND_GITHUB_TASKSTOISSUES__`. - -Continue with the existing workflow unchanged. Do not install or enable the extension automatically, do not stop, and do not run the replacement command. - -## User Input - -```text -$ARGUMENTS -``` - -You **MUST** consider the user input before proceeding (if not empty). - -## Pre-Execution Checks - -**Check for extension hooks (before tasks-to-issues conversion)**: -- Check if `.specify/extensions.yml` exists in the project root. -- If it exists, read it and look for entries under the `hooks.before_taskstoissues` key -- If the YAML cannot be parsed or is invalid, do not skip silently: tell the user that `.specify/extensions.yml` could not be read (include the parser error) and that no hooks were checked, including any mandatory (`optional: false`) hooks registered there, then continue normally -- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default. -- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions: - - If the hook has no `condition` field, or it is null/empty, treat the hook as executable - - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation -- For each executable hook, output the following based on its `optional` flag: - - **Optional hook** (`optional: true`): - ``` - ## Extension Hooks - - **Optional Pre-Hook**: {extension} - Command: `/{command}` - Description: {description} - - Prompt: {prompt} - To execute: `/{command}` - ``` - - **Mandatory hook** (`optional: false`): - ``` - ## Extension Hooks - - **Automatic Pre-Hook**: {extension} - Executing: `/{command}` - EXECUTE_COMMAND: {command} - - Wait for the result of the hook command before proceeding to the Outline. - ``` - After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook. -- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently - -## Outline - -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"). -1. **IF EXISTS**: Load `/memory/constitution.md` for project principles and governance constraints. -1. From the executed script, extract the path to **tasks**. -1. Get the Git remote by running: - -```bash -git config --get remote.origin.url -``` - -> [!CAUTION] -> ONLY PROCEED TO NEXT STEPS IF THE REMOTE IS A GITHUB URL - -1. **Fetch existing issues for deduplication**: Before creating anything, build the set of task IDs you are about to process from `tasks.md` (each is a `T` followed by **at least** three digits, e.g. `T001` — `__SPECKIT_COMMAND_CONVERGE__` assigns new IDs with `T{M+1:03d}`, which is a floor rather than a cap, so once a file has more than 999 tasks the IDs are four digits or longer). Then use the GitHub MCP server's `list_issues` tool to look for issues that already cover those IDs. Do not pass a `state` value, since omitting it makes the tool return both open and closed issues. Request `perPage: 100` to keep the number of calls down, and since the tool uses cursor-based pagination, request pages with the `after` parameter (using the `endCursor` from the previous response). For each issue title, match it against the task ID pattern `\bT\d{3,}\b` (the `{3,}` accepts four-digit and longer IDs — with `\d{3}` a title containing `T1000` would not match at all, because the trailing `\b` cannot fall between two digits, so that task would be silently neither deduplicated nor created; word boundaries still stop a token like `ST001` from matching, and force the whole digit run to be consumed so `T100` can never match inside `T1000`; this also recognises titles written as `T001 ...`, `T001: ...` or `[T001] ...`) and, when it matches one of your task IDs, mark that ID as already having an issue. Stop paginating as soon as every task ID has been matched, or when there are no more pages, so you do not keep fetching the whole repository's issue history once all task IDs are accounted for. This bounds the number of calls on repos with large issue histories and still prevents duplicates when the command is re-run after `tasks.md` is regenerated or the skill is re-invoked. -1. For each task in the list, use the GitHub MCP server to create a new issue in the repository that is representative of the Git remote. Task lines in `tasks.md` start with a markdown checkbox, so first strip the leading `- [ ]` (and any `[P]` / `[US#]` markers) to recover the task ID and its description. Create the issue with a single canonical title of the form `T001: `, with the ID written once followed by the task description (for example, the line `- [ ] T001 Create project structure` becomes the title `T001: Create project structure`). - - **Skip** any task whose ID is already present in the set of existing issues from the previous step, and report it (for example, `T001 already has an issue, skipping`). - - Only create issues for tasks that do not yet have a matching issue. - -> [!CAUTION] -> UNDER NO CIRCUMSTANCES EVER CREATE ISSUES IN REPOSITORIES THAT DO NOT MATCH THE REMOTE URL - -## Post-Execution Checks - -**Check for extension hooks (after tasks-to-issues conversion)**: -Check if `.specify/extensions.yml` exists in the project root. -- If it exists, read it and look for entries under the `hooks.after_taskstoissues` key -- If the YAML cannot be parsed or is invalid, do not skip silently: tell the user that `.specify/extensions.yml` could not be read (include the parser error) and that no hooks were checked, including any mandatory (`optional: false`) hooks registered there, then continue normally -- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default. -- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions: - - If the hook has no `condition` field, or it is null/empty, treat the hook as executable - - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation -- For each executable hook, output the following based on its `optional` flag: - - **Optional hook** (`optional: true`): - ``` - ## Extension Hooks - - **Optional Hook**: {extension} - Command: `/{command}` - Description: {description} - - Prompt: {prompt} - To execute: `/{command}` - ``` - - **Mandatory hook** (`optional: false`): - ``` - ## Extension Hooks - - **Automatic Hook**: {extension} - Executing: `/{command}` - EXECUTE_COMMAND: {command} - ``` - After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook. -- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently diff --git a/tests/extensions/github/test_github_extension.py b/tests/extensions/github/test_github_extension.py index 8de187999d..aa6bbd0ae8 100644 --- a/tests/extensions/github/test_github_extension.py +++ b/tests/extensions/github/test_github_extension.py @@ -3,7 +3,7 @@ Validates: - Bundled layout (manifest, README, command file, script twins) - Catalog registration and wheel/source resolution via ``_locate_bundled_extension`` -- Manifest validation, including that no alias claims the core command name +- Manifest validation, including no legacy compatibility aliases - Install/uninstall through ``ExtensionManager`` - Rendered command artifacts across command mode and skills mode, and in particular that ``{SCRIPT}`` resolves to a script the extension actually @@ -36,7 +36,6 @@ PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent.parent EXT_DIR = PROJECT_ROOT / "extensions" / "github" -CORE_COMMAND = PROJECT_ROOT / "templates" / "commands" / "taskstoissues.md" # A released Spec Kit version that satisfies the manifest floor. Installs are # refused below it, so this cannot be an arbitrary synthetic value. @@ -158,28 +157,23 @@ def test_manifest_validates(self): m = ExtensionManifest(EXT_DIR / "extension.yml") assert m.id == "github" - assert m.version == "1.0.4" + assert m.version == "1.0.5" assert [c["name"] for c in m.commands] == [COMMAND_NAME] - def test_manifest_command_files_exist(self): + def test_manifest_does_not_restore_legacy_alias(self): from specify_cli.extensions import ExtensionManifest - m = ExtensionManifest(EXT_DIR / "extension.yml") - for cmd in m.commands: - assert (EXT_DIR / cmd["file"]).is_file() - - def test_no_alias_claims_the_core_command(self): - """Stage 1 keeps the core command; nothing here may shadow it. + manifest = ExtensionManifest(EXT_DIR / "extension.yml") + for command in manifest.commands: + assert command["name"] != "speckit.taskstoissues" + assert not command.get("aliases") - Alias names are not pattern-checked and the core-namespace guard - applies to primary names only, so this is author discipline that a - test has to hold in place. - """ + def test_manifest_command_files_exist(self): from specify_cli.extensions import ExtensionManifest m = ExtensionManifest(EXT_DIR / "extension.yml") - aliases = [a for cmd in m.commands for a in cmd.get("aliases", []) or []] - assert aliases == [] + for cmd in m.commands: + assert (EXT_DIR / cmd["file"]).is_file() def test_declares_its_mandatory_external_tools(self): """The command runs ``git config`` and needs the GitHub MCP tools.""" @@ -205,11 +199,6 @@ def test_version_floor_covers_all_rendering_prerequisites(self): # And the version the install tests use must satisfy it. assert INSTALL_SPECKIT_VERSION in spec, floor - def test_core_command_remains_unchanged(self): - """Stage 1 is additive: the core command still ships.""" - assert CORE_COMMAND.is_file() - - # -- Install / uninstall ------------------------------------------------------ @@ -249,8 +238,8 @@ def test_generic_registers_and_removes_artifact_in_configured_directory( commands_dir = project / ".myagent" / "commands" commands_dir.mkdir(parents=True) core = commands_dir / ( - "speckit-taskstoissues/SKILL.md" - if skills_mode else "speckit.taskstoissues.md" + "speckit-tasks/SKILL.md" + if skills_mode else "speckit.tasks.md" ) core.parent.mkdir(parents=True, exist_ok=True) core.write_text("core command remains unchanged\n", encoding="utf-8") @@ -319,12 +308,12 @@ def test_remove_uninstalls_cleanly(self, tmp_path: Path): f".github/agents/{COMMAND_NAME}.agent.md", f".github/prompts/{COMMAND_NAME}.prompt.md", ], - ".github/agents/speckit.taskstoissues.agent.md", + ".github/agents/speckit.tasks.agent.md", ), ( "claude", [".claude/skills/speckit-github-taskstoissues/SKILL.md"], - ".claude/skills/speckit-taskstoissues/SKILL.md", + ".claude/skills/speckit-tasks/SKILL.md", ), ], ids=["command-mode", "skills-mode"], @@ -344,7 +333,7 @@ def test_remove_deletes_registered_artifacts_and_keeps_core( ) core = tmp_path / core_artifact core.parent.mkdir(parents=True) - core.write_text("core taskstoissues\n", encoding="utf-8") + core.write_text("core tasks\n", encoding="utf-8") manager = ExtensionManager(tmp_path) manager.install_from_directory( @@ -357,7 +346,7 @@ def test_remove_deletes_registered_artifacts_and_keeps_core( for rel_path in extension_artifacts: assert not (tmp_path / rel_path).exists(), f"Left behind: {rel_path}" - assert core.read_text(encoding="utf-8") == "core taskstoissues\n" + assert core.read_text(encoding="utf-8") == "core tasks\n" # -- Rendered command artifacts ----------------------------------------------- @@ -524,7 +513,7 @@ def test_rendered_skill_points_at_a_script_that_ships(self, tmp_path: Path): assert (project / expected).is_file() -# -- Behaviour parity with the core command ----------------------------------- +# -- Preserved conversion contracts ------------------------------------------ class TestCommandBody: @@ -598,45 +587,6 @@ def test_preserves_deduplication_and_pagination(self): # Four-digit and longer task IDs must still match. assert r"\bT\d{3,}\b" in body - def test_body_differs_from_core_only_in_deprecation_and_script_invocation(self): - """Behaviour parity, enforced as a diff rather than as spot checks. - - Apart from core-only deprecation metadata and notice, everything except - the ``scripts:`` frontmatter and the two lines that read the new ``TASKS`` - value must match verbatim, so the two cannot silently drift while both exist. - """ - import difflib - - core_text = CORE_COMMAND.read_text(encoding="utf-8") - before_notice, notice_and_workflow = core_text.split("## Deprecation Notice\n", 1) - _, workflow = notice_and_workflow.split("## User Input", 1) - core = (before_notice + "## User Input" + workflow).splitlines() - ext = COMMAND_FILE.read_text(encoding="utf-8").splitlines() - core_description = yaml.safe_load(core[1])["description"] - ext_description = yaml.safe_load(ext[1])["description"] - assert core_description == "Deprecated: " + ext_description - core[1] = ext[1] - changed = [ - line - for line in difflib.unified_diff(core, ext, n=0) - if line.startswith(("+", "-")) and not line.startswith(("+++", "---")) - ] - - # 3 script lines + 2 outline lines, each as one removal and one addition. - assert len(changed) == 10, "\n".join(changed) - - markers = ( - "check-prerequisites", - "check_prerequisites", - "resolve-tasks", - "resolve_tasks", - "AVAILABLE_DOCS", - "path to **tasks**", - ) - assert all( - any(marker in line for marker in markers) for line in changed - ), "\n".join(changed) - def test_uses_the_portable_command_reference_token(self): """A literal invocation would be correct for exactly one agent.""" body = COMMAND_FILE.read_text(encoding="utf-8") diff --git a/tests/extensions/github/test_taskstoissues_deprecation.py b/tests/extensions/github/test_taskstoissues_deprecation.py deleted file mode 100644 index b18324863b..0000000000 --- a/tests/extensions/github/test_taskstoissues_deprecation.py +++ /dev/null @@ -1,99 +0,0 @@ -"""Text contracts for the core command's deprecation transition.""" - -from pathlib import Path - -import pytest -import yaml - - -ROOT = Path(__file__).resolve().parents[3] -CORE = ROOT / "templates/commands/taskstoissues.md" -EXTENSION = ROOT / "extensions/github/commands/speckit.github.taskstoissues.md" - - -def test_core_deprecation_warning_and_continuation(): - text = CORE.read_text(encoding="utf-8") - assert "## Deprecation Notice" in text - assert "## Pre-Execution Checks" in text - notice = text.split("## Deprecation Notice\n", 1)[1].split("## User Input", 1)[0] - assert text.index("## Deprecation Notice") < text.index("## User Input") < text.index("## Pre-Execution Checks") - for phrase in ( - "MUST display the following concise warning first", - "`__SPECKIT_COMMAND_TASKSTOISSUES__` is deprecated", - "will be removed in a future minor release", - "bundled `github` extension", - "specify extension add github", - "__SPECKIT_COMMAND_GITHUB_TASKSTOISSUES__", - "Continue with the existing workflow unchanged", - "Do not install or enable the extension automatically", - "do not stop", - "do not run the replacement command", - ): - assert phrase in notice - - -def test_core_frontmatter_preserves_execution_metadata(): - metadata = yaml.safe_load(CORE.read_text(encoding="utf-8").split("---", 2)[1]) - assert metadata["description"].startswith("Deprecated: ") - assert metadata["tools"] == [ - "github/github-mcp-server/list_issues", - "github/github-mcp-server/issue_write", - ] - assert metadata["scripts"] == { - "sh": "scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks", - "ps": "scripts/powershell/check-prerequisites.ps1 -Json -RequireTasks -IncludeTasks", - "py": "scripts/python/check_prerequisites.py --json --require-tasks --include-tasks", - } - - -def test_recommended_extension_is_not_deprecated(): - text = EXTENSION.read_text(encoding="utf-8") - assert "## Deprecation Notice" not in text - assert "future minor release" not in text - assert not yaml.safe_load(text.split("---", 2)[1])["description"].startswith("Deprecated:") - - -@pytest.mark.parametrize("command", [CORE, EXTENSION], ids=["core", "github"]) -def test_taskstoissues_hooks_and_safety_contracts_remain(command): - text = command.read_text(encoding="utf-8") - assert "hooks.before_taskstoissues" in text - assert "hooks.after_taskstoissues" in text - assert "ONLY PROCEED TO NEXT STEPS IF THE REMOTE IS A GITHUB URL" in text - assert "**Fetch existing issues for deduplication**" in text - assert "**Skip** any task whose ID is already present" in text - assert "Only create issues for tasks that do not yet have a matching issue." in text - assert "UNDER NO CIRCUMSTANCES EVER CREATE ISSUES IN REPOSITORIES THAT DO NOT MATCH THE REMOTE URL" in text - - -@pytest.mark.parametrize( - ("integration_key", "core_invocation", "replacement_invocation"), - [ - ("opencode", "/speckit.taskstoissues", "/speckit.github.taskstoissues"), - ("agy", "/speckit-taskstoissues", "/speckit-github-taskstoissues"), - ], -) -def test_rendered_core_warning_uses_integration_invocations( - tmp_path, integration_key, core_invocation, replacement_invocation -): - from specify_cli.integrations import get_integration - from specify_cli.integrations.manifest import IntegrationManifest - - integration = get_integration(integration_key) - created = integration.setup( - tmp_path, IntegrationManifest(integration_key, tmp_path), script_type="sh" - ) - command = next( - path for path in created - if path.name == "speckit.taskstoissues.md" - or path.parent.name == "speckit-taskstoissues" - ) - text = command.read_text(encoding="utf-8") - assert "## Deprecation Notice" in text - assert "## Pre-Execution Checks" in text - notice = text.split("## Deprecation Notice", 1)[1].split("## User Input", 1)[0] - assert f"`{core_invocation}` is deprecated" in notice - assert f"invoke `{replacement_invocation}`" in notice - assert "__SPECKIT_COMMAND_" not in notice - assert "specify extension add github" in notice - assert "Continue with the existing workflow unchanged" in notice - assert "Do not install or enable the extension automatically" in notice diff --git a/tests/integrations/test_integration_base_markdown.py b/tests/integrations/test_integration_base_markdown.py index e94f58c6ff..16ee0bd49d 100644 --- a/tests/integrations/test_integration_base_markdown.py +++ b/tests/integrations/test_integration_base_markdown.py @@ -220,7 +220,7 @@ def test_integration_flag_creates_files(self, tmp_path): COMMAND_STEMS = [ "analyze", "clarify", "constitution", "converge", "implement", - "plan", "checklist", "specify", "tasks", "taskstoissues", + "plan", "checklist", "specify", "tasks", ] def _expected_files(self, script_variant: str) -> list[str]: diff --git a/tests/integrations/test_integration_base_skills.py b/tests/integrations/test_integration_base_skills.py index 015152be01..f190508382 100644 --- a/tests/integrations/test_integration_base_skills.py +++ b/tests/integrations/test_integration_base_skills.py @@ -95,7 +95,7 @@ def test_skill_directory_structure(self, tmp_path): expected_commands = { "analyze", "clarify", "constitution", "converge", "implement", - "plan", "checklist", "specify", "tasks", "taskstoissues", + "plan", "checklist", "specify", "tasks", } # Derive command names from the skill directory names @@ -466,7 +466,7 @@ def test_options_include_skills_flag(self): _SKILL_COMMANDS = [ "analyze", "clarify", "constitution", "converge", "implement", - "plan", "checklist", "specify", "tasks", "taskstoissues", + "plan", "checklist", "specify", "tasks", ] def _expected_files(self, script_variant: str) -> list[str]: diff --git a/tests/integrations/test_integration_base_toml.py b/tests/integrations/test_integration_base_toml.py index 1b1bb18807..c7950b85fb 100644 --- a/tests/integrations/test_integration_base_toml.py +++ b/tests/integrations/test_integration_base_toml.py @@ -470,7 +470,6 @@ def test_integration_flag_creates_files(self, tmp_path): "checklist", "specify", "tasks", - "taskstoissues", ] def _expected_files(self, script_variant: str) -> list[str]: diff --git a/tests/integrations/test_integration_base_yaml.py b/tests/integrations/test_integration_base_yaml.py index 01914c5988..68c921ed23 100644 --- a/tests/integrations/test_integration_base_yaml.py +++ b/tests/integrations/test_integration_base_yaml.py @@ -384,7 +384,6 @@ def test_integration_flag_creates_files(self, tmp_path): "checklist", "specify", "tasks", - "taskstoissues", ] def _expected_files(self, script_variant: str) -> list[str]: diff --git a/tests/integrations/test_integration_bob.py b/tests/integrations/test_integration_bob.py index 752d7a42bc..8e85c66559 100644 --- a/tests/integrations/test_integration_bob.py +++ b/tests/integrations/test_integration_bob.py @@ -246,7 +246,7 @@ def test_skill_directory_structure(self, tmp_path): expected_commands = { "analyze", "clarify", "constitution", "converge", "implement", - "plan", "checklist", "specify", "tasks", "taskstoissues", + "plan", "checklist", "specify", "tasks", } actual_commands = {f.parent.name.removeprefix("speckit-") for f in created} assert actual_commands == expected_commands @@ -411,7 +411,7 @@ def test_init_default_complete_file_inventory_sh(self, tmp_path): commands = [ "analyze", "clarify", "constitution", "converge", "implement", - "plan", "checklist", "specify", "tasks", "taskstoissues", + "plan", "checklist", "specify", "tasks", ] for cmd in commands: assert (project / ".bob" / "skills" / f"speckit-{cmd}" / "SKILL.md").exists(), ( diff --git a/tests/integrations/test_integration_copilot.py b/tests/integrations/test_integration_copilot.py index 04098650e5..230fd3f2b8 100644 --- a/tests/integrations/test_integration_copilot.py +++ b/tests/integrations/test_integration_copilot.py @@ -176,10 +176,10 @@ def test_directory_structure(self, tmp_path): agents_dir = tmp_path / ".github" / "agents" assert agents_dir.is_dir() agent_files = sorted(agents_dir.glob("speckit.*.agent.md")) - assert len(agent_files) == 10 + assert len(agent_files) == 9 expected_commands = { "analyze", "clarify", "constitution", "converge", "implement", - "plan", "checklist", "specify", "tasks", "taskstoissues", + "plan", "checklist", "specify", "tasks", } actual_commands = {f.name.removeprefix("speckit.").removesuffix(".agent.md") for f in agent_files} assert actual_commands == expected_commands @@ -292,7 +292,6 @@ def test_complete_file_inventory_sh(self, tmp_path): ".github/agents/speckit.plan.agent.md", ".github/agents/speckit.specify.agent.md", ".github/agents/speckit.tasks.agent.md", - ".github/agents/speckit.taskstoissues.agent.md", ".github/prompts/speckit.analyze.prompt.md", ".github/prompts/speckit.checklist.prompt.md", ".github/prompts/speckit.clarify.prompt.md", @@ -302,7 +301,6 @@ def test_complete_file_inventory_sh(self, tmp_path): ".github/prompts/speckit.plan.prompt.md", ".github/prompts/speckit.specify.prompt.md", ".github/prompts/speckit.tasks.prompt.md", - ".github/prompts/speckit.taskstoissues.prompt.md", ".vscode/settings.json", ".specify/integration.json", ".specify/init-options.json", @@ -357,7 +355,6 @@ def test_complete_file_inventory_ps(self, tmp_path): ".github/agents/speckit.plan.agent.md", ".github/agents/speckit.specify.agent.md", ".github/agents/speckit.tasks.agent.md", - ".github/agents/speckit.taskstoissues.agent.md", ".github/prompts/speckit.analyze.prompt.md", ".github/prompts/speckit.checklist.prompt.md", ".github/prompts/speckit.clarify.prompt.md", @@ -367,7 +364,6 @@ def test_complete_file_inventory_ps(self, tmp_path): ".github/prompts/speckit.plan.prompt.md", ".github/prompts/speckit.specify.prompt.md", ".github/prompts/speckit.tasks.prompt.md", - ".github/prompts/speckit.taskstoissues.prompt.md", ".vscode/settings.json", ".specify/integration.json", ".specify/init-options.json", @@ -400,7 +396,7 @@ class TestCopilotSkillsMode: _SKILL_COMMANDS = [ "analyze", "clarify", "constitution", "converge", "implement", - "plan", "checklist", "specify", "tasks", "taskstoissues", + "plan", "checklist", "specify", "tasks", ] def _make_copilot(self): diff --git a/tests/integrations/test_integration_generic.py b/tests/integrations/test_integration_generic.py index c2eabfcaa7..3ea10f7b11 100644 --- a/tests/integrations/test_integration_generic.py +++ b/tests/integrations/test_integration_generic.py @@ -71,7 +71,7 @@ def test_generic_extension_lifecycle(tmp_path, generic_extension, skills): commands_dir=".custom/skills" if skills else ".custom/commands", ) output_dir = project / ".custom" / ("skills" if skills else "commands") - core = output_dir / ("speckit-taskstoissues/SKILL.md" if skills else "speckit.taskstoissues.md") + core = output_dir / ("speckit-tasks/SKILL.md" if skills else "speckit.tasks.md") core_content = core.read_bytes() unrelated = output_dir / ("user-skill/SKILL.md" if skills else "user.md") unrelated.parent.mkdir(parents=True, exist_ok=True) @@ -2535,7 +2535,6 @@ def test_implement_loads_constitution_context(self, tmp_path): "checklist", "specify", "tasks", - "taskstoissues", ], ) def test_command_loads_constitution_context(self, tmp_path, command_stem): @@ -2604,7 +2603,6 @@ def test_complete_file_inventory_sh(self, tmp_path): ".myagent/commands/speckit.plan.md", ".myagent/commands/speckit.specify.md", ".myagent/commands/speckit.tasks.md", - ".myagent/commands/speckit.taskstoissues.md", ".specify/init-options.json", ".specify/integration.json", ".specify/integrations/generic.manifest.json", @@ -2752,7 +2750,6 @@ def test_complete_file_inventory_ps(self, tmp_path): ".myagent/commands/speckit.plan.md", ".myagent/commands/speckit.specify.md", ".myagent/commands/speckit.tasks.md", - ".myagent/commands/speckit.taskstoissues.md", ".specify/init-options.json", ".specify/integration.json", ".specify/integrations/generic.manifest.json", diff --git a/tests/specify_cli/presets/test_manager_skills.py b/tests/specify_cli/presets/test_manager_skills.py index 7ae833bfb9..a0176936d2 100644 --- a/tests/specify_cli/presets/test_manager_skills.py +++ b/tests/specify_cli/presets/test_manager_skills.py @@ -461,12 +461,12 @@ def test_core_command_override_skill_uses_preset_command_description(self, proje """Preset skill overrides for core commands should keep preset frontmatter descriptions.""" self._write_init_options(project_dir, ai="claude") skills_dir = project_dir / ".claude" / "skills" - self._create_skill(skills_dir, "speckit-taskstoissues") + self._create_skill(skills_dir, "speckit-tasks") - preset_dir = temp_dir / "taskstoissues-description" + preset_dir = temp_dir / "tasks-description" preset_dir.mkdir() (preset_dir / "commands").mkdir() - (preset_dir / "commands" / "speckit.repro.taskstoissues.md").write_text( + (preset_dir / "commands" / "speckit.repro.tasks.md").write_text( "---\n" "description: COMMAND-FRONTMATTER-DESCRIPTION\n" "---\n\n" @@ -475,8 +475,8 @@ def test_core_command_override_skill_uses_preset_command_description(self, proje manifest_data = { "schema_version": "1.0", "preset": { - "id": "taskstoissues-description", - "name": "Taskstoissues Description", + "id": "tasks-description", + "name": "Tasks Description", "version": "1.0.0", "description": "Test", }, @@ -485,10 +485,10 @@ def test_core_command_override_skill_uses_preset_command_description(self, proje "templates": [ { "type": "command", - "name": "speckit.taskstoissues", - "file": "commands/speckit.repro.taskstoissues.md", + "name": "speckit.tasks", + "file": "commands/speckit.repro.tasks.md", "description": "MANIFEST-DESCRIPTION", - "replaces": "speckit.taskstoissues", + "replaces": "speckit.tasks", "strategy": "replace", } ] @@ -500,76 +500,76 @@ def test_core_command_override_skill_uses_preset_command_description(self, proje manager = PresetManager(project_dir) manager.install_from_directory(preset_dir, "0.1.5") - skill_file = skills_dir / "speckit-taskstoissues" / "SKILL.md" + skill_file = skills_dir / "speckit-tasks" / "SKILL.md" content = skill_file.read_text() assert "description: COMMAND-FRONTMATTER-DESCRIPTION" in content - assert "Convert tasks from tasks.md into GitHub issues." not in content - assert "source: preset:taskstoissues-description" in content + assert "Break down implementation plans into actionable task lists." not in content + assert "source: preset:tasks-description" in content def test_core_skill_restore_uses_core_command_description(self, project_dir, temp_dir): """Core skill restore should keep core command frontmatter descriptions.""" self._write_init_options(project_dir, ai="claude") skills_dir = project_dir / ".claude" / "skills" - self._create_skill(skills_dir, "speckit-taskstoissues") + self._create_skill(skills_dir, "speckit-tasks") core_cmds = project_dir / ".specify" / "templates" / "commands" core_cmds.mkdir(parents=True, exist_ok=True) - (core_cmds / "taskstoissues.md").write_text( + (core_cmds / "tasks.md").write_text( "---\n" "description: CORE-FRONTMATTER-DESCRIPTION\n" "---\n\n" - "core taskstoissues body\n" + "core tasks body\n" ) preset_dir = self._create_command_preset( temp_dir, - "taskstoissues-restore", - "speckit.taskstoissues", + "tasks-restore", + "speckit.tasks", "PRESET-FRONTMATTER-DESCRIPTION", - "preset taskstoissues body\n", + "preset tasks body\n", ) manager = PresetManager(project_dir) manager.install_from_directory(preset_dir, "0.1.5") - manager.remove("taskstoissues-restore") + manager.remove("tasks-restore") - skill_file = skills_dir / "speckit-taskstoissues" / "SKILL.md" + skill_file = skills_dir / "speckit-tasks" / "SKILL.md" content = skill_file.read_text() assert "description: CORE-FRONTMATTER-DESCRIPTION" in content - assert "Convert tasks from tasks.md into GitHub issues." not in content - assert "source: templates/commands/taskstoissues.md" in content - assert "core taskstoissues body" in content + assert "Break down implementation plans into actionable task lists." not in content + assert "source: templates/commands/tasks.md" in content + assert "core tasks body" in content def test_override_skill_reconcile_uses_override_command_description(self, project_dir, temp_dir): """Override skill reconciliation should keep override frontmatter descriptions.""" self._write_init_options(project_dir, ai="claude") skills_dir = project_dir / ".claude" / "skills" - self._create_skill(skills_dir, "speckit-taskstoissues") + self._create_skill(skills_dir, "speckit-tasks") overrides_dir = project_dir / ".specify" / "templates" / "overrides" overrides_dir.mkdir(parents=True) - (overrides_dir / "speckit.taskstoissues.md").write_text( + (overrides_dir / "speckit.tasks.md").write_text( "---\n" "description: OVERRIDE-FRONTMATTER-DESCRIPTION\n" "---\n\n" - "override taskstoissues body\n" + "override tasks body\n" ) preset_dir = self._create_command_preset( temp_dir, - "taskstoissues-reconcile", - "speckit.taskstoissues", + "tasks-reconcile", + "speckit.tasks", "PRESET-FRONTMATTER-DESCRIPTION", - "preset taskstoissues body\n", + "preset tasks body\n", ) manager = PresetManager(project_dir) manager.install_from_directory(preset_dir, "0.1.5") - skill_file = skills_dir / "speckit-taskstoissues" / "SKILL.md" + skill_file = skills_dir / "speckit-tasks" / "SKILL.md" content = skill_file.read_text() assert "description: OVERRIDE-FRONTMATTER-DESCRIPTION" in content - assert "Convert tasks from tasks.md into GitHub issues." not in content - assert "source: override:speckit.taskstoissues" in content - assert "override taskstoissues body" in content + assert "Break down implementation plans into actionable task lists." not in content + assert "source: override:speckit.tasks" in content + assert "override tasks body" in content def test_skill_not_updated_when_ai_skills_disabled(self, project_dir, temp_dir): """When skills mode was NOT used, preset install should not touch skills.""" diff --git a/tests/test_extensions.py b/tests/test_extensions.py index 5c56341281..f40a7d4370 100644 --- a/tests/test_extensions.py +++ b/tests/test_extensions.py @@ -3165,7 +3165,7 @@ def test_install_rejects_alias_shadowing_core_command(self, temp_dir, project_di Regression test for #4555: a primary name is namespace-checked against CORE_COMMAND_NAMES, but aliases are intentionally free-form and previously went unchecked against core commands entirely, so an - extension could claim e.g. 'speckit.taskstoissues' as an alias and + extension could claim e.g. 'speckit.tasks' as an alias and shadow the core command of the same name. """ import yaml @@ -3186,9 +3186,9 @@ def test_install_rejects_alias_shadowing_core_command(self, temp_dir, project_di "provides": { "commands": [ { - "name": "speckit.probe.taskstoissues", + "name": "speckit.probe.tasks", "file": "commands/cmd.md", - "aliases": ["speckit.taskstoissues"], + "aliases": ["speckit.tasks"], } ] }, @@ -3201,7 +3201,7 @@ def test_install_rejects_alias_shadowing_core_command(self, temp_dir, project_di with pytest.raises(ValidationError, match="conflicts with core command"): manager.install_from_directory(ext_dir, "0.1.0", register_commands=False) - @pytest.mark.parametrize("alias", ["taskstoissues", "speckit-taskstoissues"]) + @pytest.mark.parametrize("alias", ["tasks", "speckit-tasks"]) def test_install_rejects_equivalent_alias_shadowing_core_command( self, temp_dir, project_dir, alias ): @@ -3209,8 +3209,8 @@ def test_install_rejects_equivalent_alias_shadowing_core_command( Regression test for the reviewer follow-up on #4555: agent-specific name transformation (``CommandRegistrar._compute_output_name`` and the - Cline/Forge/Junie formatters) collapses ``speckit.taskstoissues``, - ``taskstoissues``, and ``speckit-taskstoissues`` to the same on-disk + Cline/Forge/Junie formatters) collapses ``speckit.tasks``, + ``tasks``, and ``speckit-tasks`` to the same on-disk command name, so all three spellings must be rejected, not just the exact dotted one. """ @@ -3232,7 +3232,7 @@ def test_install_rejects_equivalent_alias_shadowing_core_command( "provides": { "commands": [ { - "name": "speckit.probe.taskstoissues", + "name": "speckit.probe.tasks", "file": "commands/cmd.md", "aliases": [alias], }