fix(taskstoissues): scope issue dedup to the feature the tasks belong to (#4271) - #4314
Open
ntdatt812 wants to merge 1 commit into
Open
fix(taskstoissues): scope issue dedup to the feature the tasks belong to (#4271)#4314ntdatt812 wants to merge 1 commit into
ntdatt812 wants to merge 1 commit into
Conversation
Task IDs are local to a feature -- every tasks.md restarts at T001 -- but the
dedup matched existing issues on the bare ID. So once feature 001-auth had an
issue titled T001, running the command for 002-billing saw "T001 exists" and
skipped it. The task was never created and nothing said so, which is a silent
gap in exactly the multi-feature repos this command targets.
The canonical title now carries the feature directory basename, and a task is
skipped only when an existing issue matches both that identifier and the ID.
The ID keeps its own word boundaries inside the prefixed title, so the
\bT\d{3,}\b matching from github#2968 is unchanged.
Issues filed before the prefix existed carry a bare `T001: ...`; those are
still recognised for their own feature, so upgrading does not re-create work
that is already tracked.
Closes github#4271
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Legacy-title handling still causes cross-feature skips, and the template contains malformed regex control characters.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Scopes task-to-issue deduplication by feature to prevent cross-feature task suppression.
Changes:
- Adds feature-qualified issue titles and deduplication rules.
- Adds regression tests for template instructions.
File summaries
| File | Description |
|---|---|
templates/commands/taskstoissues.md |
Defines feature-scoped titles and matching. |
tests/unit/test_taskstoissues_feature_scope.py |
Verifies feature-scoping instructions. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 2
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| 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 **only if the title also carries this feature's identifier** (see below). Task IDs restart at `T001` in every feature's `tasks.md`, so an unscoped match means the first feature to reach the tracker permanently suppresses `T001` for every later feature -- a silent gap in exactly the multi-feature repos this command is for. 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 `[<feature>] T001: <description>`, where `<feature>` is the basename of FEATURE_DIR parsed in step 1 (the `NNN-name` spec directory, e.g. `002-billing`), followed by the ID written once and then the task description (for example, the line `- [ ] T001 Create project structure` in feature `002-billing` becomes the title `[002-billing] T001: Create project structure`). The ID keeps its own word boundaries, so the `T\d{3,}` matching above is unchanged by the prefix. | ||
| - **Skip** a task only when an existing issue matches **both** this feature's identifier and the task ID, and report it (for example, `[002-billing] T001 already has an issue, skipping`). A `T001` belonging to another feature is a different task and must not suppress this one. | ||
| - Issues created before this scoping exists carry a bare `T001: ...` title. Treat those as matching only when no `[<feature>]` prefix is present anywhere in the fetched set for that ID, so an upgrade does not re-create issues that are already tracked. |
| 1. For each task in the list, use the GitHub MCP server to create a new issue in the repository that is representative of the Git remote. Task lines in `tasks.md` start with a markdown checkbox, so first strip the leading `- [ ]` (and any `[P]` / `[US#]` markers) to recover the task ID and its description. Create the issue with a single canonical title of the form `T001: <description>`, with the ID written once followed by the task description (for example, the line `- [ ] T001 Create project structure` becomes the title `T001: Create project structure`). | ||
| - **Skip** any task whose ID is already present in the set of existing issues from the previous step, and report it (for example, `T001 already has an issue, skipping`). | ||
| 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 **only if the title also carries this feature's identifier** (see below). Task IDs restart at `T001` in every feature's `tasks.md`, so an unscoped match means the first feature to reach the tracker permanently suppresses `T001` for every later feature -- a silent gap in exactly the multi-feature repos this command is for. 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 `[<feature>] T001: <description>`, where `<feature>` is the basename of FEATURE_DIR parsed in step 1 (the `NNN-name` spec directory, e.g. `002-billing`), followed by the ID written once and then the task description (for example, the line `- [ ] T001 Create project structure` in feature `002-billing` becomes the title `[002-billing] T001: Create project structure`). The ID keeps its own word boundaries, so the `T\d{3,}` matching above is unchanged by the prefix. |
mnriem
requested changes
Sep 1, 2026
mnriem
left a comment
Collaborator
There was a problem hiding this comment.
Please address Copilot feedback. Note that the change should backwards compatible as it will impact anyone using this feature already
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #4271.
Task IDs are local to a feature — every
tasks.mdrestarts atT001— but the dedup step matched existing issues on the bare ID:So once
001-authhad an issue titledT001: …, running the command for002-billingsaw "T001 exists" and skipped it. The issue was never created and the report saidT001 already has an issue, skipping, which reads like success. Every feature after the first loses its low-numbered tasks, and it degrades with repo age rather than showing up on the first run.This is the inverse of #2968: tightening the matching to stop duplicates made it aggressive enough to suppress valid tasks.
What changed
The canonical title now carries the feature.
FEATURE_DIRis already parsed in step 1, so its basename — theNNN-namespec directory — goes in front:The ID keeps its own word boundaries inside that prefix, so the
\bT\d{3,}\bmatching #2968 established still works unchanged, including the four-digit case.A task is skipped only when both match. Feature identity and task ID. A
T001belonging to another feature is a different task and no longer suppresses this one.Existing issues still count. Anything filed before the prefix existed carries a bare
T001: …. Those are treated as matching only when no[<feature>]-prefixed issue for that ID is present in the fetched set, so upgrading does not re-create work that is already tracked — which would have been the obvious way to turn this fix into a fresh duplicate problem.On the choice of title over label
The issue offers "title or a label". Title, for two reasons: the dedup already reads titles from
list_issues, so nothing new is fetched and no extra call is added to a step that is explicitly written to bound its pagination; and a label needs create-label permission and a repo-side convention, which is a heavier requirement for a command whose whole contract is "create issues on the remote". A label would be the better choice if the identifier had to be filterable in the GitHub UI — happy to switch if you would rather have that.I did not implement the second bullet of the issue (re-checking immediately before creation so two concurrent invocations cannot race). It is a real gap, but it is a different change — the fetch-then-create window — and it is worth its own PR rather than being folded in here where it would be untested.
Tests
tests/unit/test_taskstoissues_feature_scope.py, three cases over the template text:[<feature>] T001:and says where<feature>comes fromEach assertion fails with the offending instruction line quoted, and
_line_containingraises if the instruction disappears entirely — so deleting the step cannot make the suite pass vacuously.Mutation-checked — dropping
[<feature>]from the canonical title fails exactly one case:The two failures are
test_add_source_refuses_symlinked_specify_escapeandtest_save_records_refuses_symlinked_specify_escape. Both fail identically onmainon this machine — creating a symlink on Windows needs elevation — measured on a clean checkout before this branch, not assumed.