Conversation
…rnal task tracking Define mandatory Issue Body Composition template and GitHub Label Protocol in the Issue Creation Protocol, replacing bare one-liner issue descriptions with structured bodies that pull context from spec artifacts.
📝 WalkthroughWalkthroughThis PR adds a new "rich-issue-bodies" feature: mandatory Issue Body Composition templates and an automated GitHub Label Protocol, updates platform/tracking docs to use composed issue bodies and labels, and enforces presence of ISSUE_BODY_MARKERS via generator validation and platform-consistency tests. Changes
Sequence Diagram(s)mermaid Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 12
🧹 Nitpick comments (1)
core/workflow.md (1)
133-133: Generalize provider wording in core workflow text.Line [133] hard-codes GitHub in a core module. Consider phrasing this as provider-specific metadata to keep core guidance platform-agnostic.
As per coding guidelines: `core/` must remain platform-agnostic — use abstract operations and never platform-specific tool names.💡 Proposed wording tweak
-6. **External issue creation (mandatory when taskTracking configured)**: If `config.team.taskTracking` is not `"none"`, create external issues following the Task Tracking Integration protocol in the Configuration Handling module. READ_FILE `tasks.md`, identify all tasks with `**Priority:** High` or `**Priority:** Medium`. For each eligible task, compose the issue body using the Issue Body Composition template (reading spec artifacts for context), create issues via the Issue Creation Protocol (with labels for GitHub), and write IssueIDs back to `tasks.md`. If issue creation is skipped or all IssueIDs remain `None`, the Phase 3 task tracking gate will catch the omission — the spec artifact linter validates IssueIDs on completed specs and fails CI when they are missing. +6. **External issue creation (mandatory when taskTracking configured)**: If `config.team.taskTracking` is not `"none"`, create external issues following the Task Tracking Integration protocol in the Configuration Handling module. READ_FILE `tasks.md`, identify all tasks with `**Priority:** High` or `**Priority:** Medium`. For each eligible task, compose the issue body using the Issue Body Composition template (reading spec artifacts for context), create issues via the Issue Creation Protocol (including provider-specific metadata, e.g., labels when using GitHub), and write IssueIDs back to `tasks.md`. If issue creation is skipped or all IssueIDs remain `None`, the Phase 3 task tracking gate will catch the omission — the spec artifact linter validates IssueIDs on completed specs and fails CI when they are missing.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@core/workflow.md` at line 133, Update the hard-coded provider wording in the External issue creation step: replace the phrase "create issues via the Issue Creation Protocol (with labels for GitHub)" with a platform-agnostic description that uses provider-specific metadata (e.g., "create issues via the Issue Creation Protocol, attaching provider-specific metadata such as labels/tags/fields as required by the configured taskTracking provider"). Ensure references to config.team.taskTracking, Issue Creation Protocol, Issue Body Composition, and writing IssueIDs back to tasks.md remain unchanged so behavior is unchanged but wording is provider-neutral.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.specops/rich-issue-bodies/implementation.md:
- Line 7: The docs line containing the config summary currently uses a lowercase
platform name; update the phrase "taskTracking: github" in the string "Config:
loaded from `.specops.json` — vertical: builder, specsDir: .specops,
taskTracking: github" to use the proper capitalized form "GitHub" so it reads
"taskTracking: GitHub".
- Around line 18-28: The markdown tables under the headings "## Decision Log",
"## Deviations from Design", and "## Blockers Encountered" need blank lines
before and after each table to satisfy markdownlint MD058; edit the
implementation.md content to insert an empty line above and below each table
block (i.e., surround the three pipe-delimited tables following those headings)
so each table is separated from surrounding text by a blank line.
In @.specops/rich-issue-bodies/requirements.md:
- Line 21: Update the canonical section name in the requirements extraction
rule: replace the phrase "bugfix.md Bug Description" with "bugfix.md Problem
Statement" so the rule reads that the Context section should extract 1–3
sentences from Overview (or bugfix.md Problem Statement / refactor.md
Motivation); update the string literal in the requirements.md rule to use
"Problem Statement" to remove ambiguity.
In `@core/config-handling.md`:
- Line 106: The fenced code block opening currently uses a bare ``` fence which
triggers MD040; update the opening fence to include the language identifier by
changing the leading ``` to ```markdown so the block becomes a markdown fenced
block containing the "## Context" section and the priority/effort/dependencies
line, ensuring markdownlint no longer flags it.
- Line 171: The examples and guidance use unescaped <TaskTitle> in shell
single-quoted arguments; update the three tracker command templates (the GitHub,
Jira, and Linear examples) to use the explicit <EscapedTaskTitle> placeholder
instead of <TaskTitle> (i.e., change --title '<taskPrefix><TaskTitle>' and
--summary='<taskPrefix><TaskTitle>' to use <EscapedTaskTitle>) and update the
Shell safety guidance text to mention <EscapedTaskTitle> explicitly (e.g., "use
an explicitly escaped value (<EscapedTaskTitle>) where internal single quotes
are escaped or prefer file-based args"). Ensure all three command examples and
the guidance consistently reference <EscapedTaskTitle> so implementers know to
escape internal single quotes before interpolation.
In `@platforms/codex/SKILL.md`:
- Around line 477-479: Replace the unsafe single-quoted title interpolation in
the gh issue create command (the fragment using '--title
'<taskPrefix><TaskTitle>'') so that titles containing apostrophes do not break:
construct the title via a safe shell variable or printf expansion (for example
use --title "$(printf '%s' "$taskPrefix$TaskTitle")" or --title
"$taskPrefix$TaskTitle") in core/config-handling.md where the gh issue create
command is composed, then regenerate all platform files with python3
generator/generate.py --all.
In `@platforms/copilot/specops.instructions.md`:
- Around line 457-460: The label creation path interpolates raw spec IDs into
shell commands (e.g., the gh label create "<label>" --force --description
"<description>" invocation) without sanitization—add a validation step that
enforces a strict allowlist regex (e.g., /^[a-z0-9:-]+$/) for spec-id and any
derived label names, and reject or normalize inputs that don't match;
additionally, stop constructing commands via raw string interpolation by using a
safe command runner that accepts arguments as an array (or the GitHub CLI SDK)
so labels are passed as argv rather than embedded into a shell string; update
the label-generation helper to apply the regex, return sanitized labels, and
fail early with a clear error if validation fails before calling the gh label
create invocation.
In `@platforms/cursor/specops.mdc`:
- Around line 473-474: Update the "Jira and Linear" guidance so the concrete
command examples actually attempt to apply labels when the `--label` flag is
available, and fall back to silently skipping labels on failure; specifically,
modify the command/example blocks under the "Jira and Linear" section to include
the `--label` flag usage and error-tolerant behavior described in the prose,
ensuring the examples show how to pass labels and note the silent skip behavior
when the flag is unsupported or fails (keep label application non-blocking for
issue creation).
- Around line 419-421: Replace the hardcoded artifact link that points to
"<specsDir>/<spec-name>/requirements.md" with the resolved spec artifact file
path so non-feature specs (bugfix/refactor) link correctly; locate the three
markdown link entries in platforms/cursor/specops.mdc (the lines containing
"[Requirements](<specsDir>/<spec-name>/requirements.md)" and the similar
Design/Tasks links) and substitute the Requirements link to use the same
resolved artifact variable or helper used for the other artifacts (e.g.,
specArtifactPath or resolvedSpecArtifact) so it points to the actual artifact
filename instead of always "requirements.md".
- Line 480: The gh issue creation commands use --title '<taskPrefix><TaskTitle>'
(and similar occurrences) without escaping single quotes, violating the
shell-safety guidance; fix by either writing the interpolated title to a
temporary file and passing it via a file-based argument (use the CLI/file flag
variant for title instead of --title inline) or by escaping single quotes in the
title before interpolation using the safe shell escape pattern (replace each '
with '\'' ), and update every occurrence of the command pattern (--title
'<taskPrefix><TaskTitle>') to use one of these safe approaches.
In `@skills/specops/SKILL.md`:
- Line 410: The fenced code block in SKILL.md currently starts with triple
backticks (```) with no language tag which triggers markdownlint MD040; update
that opening fence to include the correct language identifier (for example
```bash, ```json, or ```js depending on the block contents) so the code block is
language-marked and MD040 is satisfied.
- Around line 419-421: The three hard-coded links in SKILL.md always point to
requirements.md and break for bugfix/refactor specs; update the template to
choose the correct artifact filename based on the spec type (requirements.md for
"feature"/"spec", bugfix.md for "bugfix", refactor.md for "refactor") instead of
always linking to requirements.md. Locate the block that renders
"[Requirements](<specsDir>/<spec-name>/requirements.md)" and replace it with a
conditional/lookup that selects the filename using the spec type variable (e.g.,
spec.type or similar) so the link becomes
"<specsDir>/<spec-name>/<chosen-filename>.md", ensuring the three items
(Requirements/Design/Tasks) resolve to the correct artifact names for bugfix and
refactor specs.
---
Nitpick comments:
In `@core/workflow.md`:
- Line 133: Update the hard-coded provider wording in the External issue
creation step: replace the phrase "create issues via the Issue Creation Protocol
(with labels for GitHub)" with a platform-agnostic description that uses
provider-specific metadata (e.g., "create issues via the Issue Creation
Protocol, attaching provider-specific metadata such as labels/tags/fields as
required by the configured taskTracking provider"). Ensure references to
config.team.taskTracking, Issue Creation Protocol, Issue Body Composition, and
writing IssueIDs back to tasks.md remain unchanged so behavior is unchanged but
wording is provider-neutral.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 1ee21248-9796-469e-9146-7a766b132661
📒 Files selected for processing (18)
.specops/index.json.specops/memory/context.md.specops/rich-issue-bodies/design.md.specops/rich-issue-bodies/implementation.md.specops/rich-issue-bodies/requirements.md.specops/rich-issue-bodies/spec.json.specops/rich-issue-bodies/tasks.mdCHECKSUMS.sha256core/config-handling.mdcore/task-tracking.mdcore/workflow.mdgenerator/validate.pyplatforms/claude/SKILL.mdplatforms/codex/SKILL.mdplatforms/copilot/specops.instructions.mdplatforms/cursor/specops.mdcskills/specops/SKILL.mdtests/test_platform_consistency.py
| 6 tasks completed, 0 deviations from design, 0 blockers. Added Issue Body Composition template and GitHub Label Protocol to core/config-handling.md — defining a mandatory issue body structure that pulls context from spec artifacts (requirements overview, spec links, task details) and auto-applies GitHub labels (priority, spec name, type). Cross-references added to task-tracking.md and workflow.md. ISSUE_BODY_MARKERS added to both validate.py and test_platform_consistency.py (Gap 31 enforced). All 4 platform outputs regenerated, validator passes all checks including new markers, all 8 tests pass. | ||
|
|
||
| ## Phase 1 Context Summary | ||
| - Config: loaded from `.specops.json` — vertical: builder, specsDir: .specops, taskTracking: github |
There was a problem hiding this comment.
Capitalize the platform name consistently.
Line [7] should use GitHub (not github) for consistency and correctness in docs.
🧰 Tools
🪛 LanguageTool
[uncategorized] ~7-~7: The official name of this software platform is spelled with a capital “H”.
Context: ...lder, specsDir: .specops, taskTracking: github - Context recovery: none (new spec) - S...
(GITHUB)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.specops/rich-issue-bodies/implementation.md at line 7, The docs line
containing the config summary currently uses a lowercase platform name; update
the phrase "taskTracking: github" in the string "Config: loaded from
`.specops.json` — vertical: builder, specsDir: .specops, taskTracking: github"
to use the proper capitalized form "GitHub" so it reads "taskTracking: GitHub".
| ## Decision Log | ||
| | # | Decision | Rationale | Task | Timestamp | | ||
| |---|----------|-----------|------|-----------| | ||
|
|
||
| ## Deviations from Design | ||
| | Planned | Actual | Reason | Task | | ||
| |---------|--------|--------|------| | ||
|
|
||
| ## Blockers Encountered | ||
| | Blocker | Resolution | Impact | Task | | ||
| |---------|------------|--------|------| |
There was a problem hiding this comment.
Add blank lines around tables to satisfy markdownlint (MD058).
Line [19], Line [23], and Line [27] tables should be surrounded by blank lines.
🧹 Proposed markdownlint-compliant spacing
## Decision Log
+
| # | Decision | Rationale | Task | Timestamp |
|---|----------|-----------|------|-----------|
## Deviations from Design
+
| Planned | Actual | Reason | Task |
|---------|--------|--------|------|
## Blockers Encountered
+
| Blocker | Resolution | Impact | Task |
|---------|------------|--------|------|📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ## Decision Log | |
| | # | Decision | Rationale | Task | Timestamp | | |
| |---|----------|-----------|------|-----------| | |
| ## Deviations from Design | |
| | Planned | Actual | Reason | Task | | |
| |---------|--------|--------|------| | |
| ## Blockers Encountered | |
| | Blocker | Resolution | Impact | Task | | |
| |---------|------------|--------|------| | |
| ## Decision Log | |
| | # | Decision | Rationale | Task | Timestamp | | |
| |---|----------|-----------|------|-----------| | |
| ## Deviations from Design | |
| | Planned | Actual | Reason | Task | | |
| |---------|--------|--------|------| | |
| ## Blockers Encountered | |
| | Blocker | Resolution | Impact | Task | | |
| |---------|------------|--------|------| |
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)
[warning] 19-19: Tables should be surrounded by blank lines
(MD058, blanks-around-tables)
[warning] 23-23: Tables should be surrounded by blank lines
(MD058, blanks-around-tables)
[warning] 27-27: Tables should be surrounded by blank lines
(MD058, blanks-around-tables)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.specops/rich-issue-bodies/implementation.md around lines 18 - 28, The
markdown tables under the headings "## Decision Log", "## Deviations from
Design", and "## Blockers Encountered" need blank lines before and after each
table to satisfy markdownlint MD058; edit the implementation.md content to
insert an empty line above and below each table block (i.e., surround the three
pipe-delimited tables following those headings) so each table is separated from
surrounding text by a blank line.
| Unwanted: IF [unwanted condition] THEN THE SYSTEM SHALL [response] | ||
| --> | ||
| - WHEN creating an external issue for a High or Medium priority task THE SYSTEM SHALL compose the issue body using the Issue Body Composition template with sections: Context, Spec Artifacts, Description, Implementation Steps, Acceptance Criteria, Files to Modify, Tests Required (optional), and metadata footer | ||
| - WHEN composing the Context section THE SYSTEM SHALL extract 1-3 sentences from the requirements.md Overview (or bugfix.md Bug Description / refactor.md Motivation) explaining why the work exists |
There was a problem hiding this comment.
Use canonical bugfix section name to avoid extraction ambiguity
Line 21 says bugfix.md Bug Description, but the standard bugfix template section is Problem Statement. Please align wording so context extraction rules are unambiguous.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.specops/rich-issue-bodies/requirements.md at line 21, Update the canonical
section name in the requirements extraction rule: replace the phrase "bugfix.md
Bug Description" with "bugfix.md Problem Statement" so the rule reads that the
Context section should extract 1–3 sentences from Overview (or bugfix.md Problem
Statement / refactor.md Motivation); update the string literal in the
requirements.md rule to use "Problem Statement" to remove ambiguity.
|
|
||
| Compose `<IssueBody>` using this template: | ||
|
|
||
| ``` |
There was a problem hiding this comment.
Add a language identifier to the fenced template block.
This block is flagged by markdownlint (MD040). Add markdown after the opening fence.
Proposed doc fix
-```
+```markdown
## Context
...
**Priority:** <task priority> | **Effort:** <task effort> | **Dependencies:** <task dependencies></details>
<details>
<summary>🧰 Tools</summary>
<details>
<summary>🪛 markdownlint-cli2 (0.21.0)</summary>
[warning] 106-106: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
</details>
</details>
<details>
<summary>🤖 Prompt for AI Agents</summary>
Verify each finding against the current code and only fix it if needed.
In @core/config-handling.md at line 106, The fenced code block opening currently
uses a bare fence which triggers MD040; update the opening fence to include the language identifier by changing the leading to ```markdown so the block
becomes a markdown fenced block containing the "## Context" section and the
priority/effort/dependencies line, ensuring markdownlint no longer flags it.
</details>
<!-- fingerprinting:phantom:triton:hawk -->
<!-- This is an auto-generated comment by CodeRabbit -->
|
|
||
| Compose `<IssueBody>` using this template: | ||
|
|
||
| ``` |
There was a problem hiding this comment.
Add a language identifier to the fenced code block
Line 410 starts a fenced block without a language, which fails markdownlint MD040.
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)
[warning] 410-410: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@skills/specops/SKILL.md` at line 410, The fenced code block in SKILL.md
currently starts with triple backticks (```) with no language tag which triggers
markdownlint MD040; update that opening fence to include the correct language
identifier (for example ```bash, ```json, or ```js depending on the block
contents) so the code block is language-marked and MD040 is satisfied.
.specops/rich-issue-bodies/spec.json
Outdated
| "reviewRounds": 0, | ||
| "approvals": 0, | ||
| "requiredApprovals": 0 | ||
| } No newline at end of file |
There was a problem hiding this comment.
Missing newline at end of file
The file is missing a trailing newline (the diff shows \ No newline at end of file). This is inconsistent with POSIX conventions and may trigger linter warnings. All other spec JSON files in the repo should have a trailing newline for consistency.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
Pull request overview
This PR formalizes how SpecOps composes external tracker issue bodies (pulling structured content from spec artifacts) and standardizes GitHub auto-labeling, then enforces presence of these instructions across generated platform outputs via validation and tests.
Changes:
- Added a mandatory “Issue Body Composition” template and a “GitHub Label Protocol” to the task-tracking integration docs, including protocol-breach language for freeform bodies.
- Extended CI/validator enforcement with new
ISSUE_BODY_MARKERSand corresponding cross-platform consistency checks. - Regenerated platform outputs and introduced a full spec (
.specops/rich-issue-bodies/) capturing requirements/design/tasks for this change.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
core/config-handling.md |
Defines the issue body composition template + GitHub label protocol; updates issue creation steps to use <IssueBody> and --label. |
core/task-tracking.md |
Adds a protocol-breach cross-reference reinforcing mandatory issue-body composition. |
core/workflow.md |
Updates Phase 2 step 6 to reference the new issue body composition + labels behavior. |
generator/validate.py |
Adds ISSUE_BODY_MARKERS, validates them per-platform, and includes them in cross-platform consistency checks. |
tests/test_platform_consistency.py |
Adds issue_body markers to ensure platform outputs contain the required new content. |
platforms/claude/SKILL.md |
Regenerated platform output containing the new issue body + label protocol instructions. |
platforms/cursor/specops.mdc |
Regenerated platform output containing the new issue body + label protocol instructions. |
platforms/codex/SKILL.md |
Regenerated platform output containing the new issue body + label protocol instructions. |
platforms/copilot/specops.instructions.md |
Regenerated platform output containing the new issue body + label protocol instructions. |
skills/specops/SKILL.md |
Synced “legacy skills” output (generated from Claude output) to include the new instructions. |
CHECKSUMS.sha256 |
Updates checksums to match regenerated outputs / updated docs. |
.specops/rich-issue-bodies/requirements.md |
Captures product requirements for rich issue bodies + GitHub auto-labeling + CI enforcement. |
.specops/rich-issue-bodies/design.md |
Documents design decisions and enforcement approach for the new protocol. |
.specops/rich-issue-bodies/tasks.md |
Task breakdown and acceptance criteria for implementing the feature end-to-end. |
.specops/rich-issue-bodies/spec.json |
Spec metadata for the new “rich-issue-bodies” feature. |
.specops/rich-issue-bodies/implementation.md |
Implementation journal summarizing work completed and validations run. |
.specops/memory/context.md |
Appends the completed spec summary to project memory. |
.specops/index.json |
Adds the completed spec entry to the spec index. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
core/config-handling.md
Outdated
| **Label set per issue:** | ||
| - **Priority label**: `P-high` or `P-medium` (matching the task's `**Priority:**` field; Low tasks are not created as issues) | ||
| - **Spec label**: `spec:<spec-id>` where `<spec-id>` is the `id` from `spec.json` (e.g., `spec:proxy-metrics`) | ||
| - **Type label**: `<type>` where `<type>` is the `type` from `spec.json` — one of `feat`, `fix`, or `refactor` (map `feature` to `feat`, `bugfix` to `fix`, `refactor` stays `refactor`) |
There was a problem hiding this comment.
The Type label bullet is internally inconsistent with the spec.json schema: spec.json.type is feature|bugfix|refactor (see spec-schema.json), but this text says it is already feat|fix|refactor while also describing a mapping. Please reword to make it explicit that the label value is derived from spec.json.type via the mapping (and consider naming it <typeLabel> to avoid confusion with the raw type).
| - **Type label**: `<type>` where `<type>` is the `type` from `spec.json` — one of `feat`, `fix`, or `refactor` (map `feature` to `feat`, `bugfix` to `fix`, `refactor` stays `refactor`) | |
| - **Type label**: `<typeLabel>` where `<typeLabel>` is derived from the `type` field in `spec.json` using this mapping: `feature` → `feat`, `bugfix` → `fix`, `refactor` → `refactor` |
core/config-handling.md
Outdated
|
|
||
| RUN_COMMAND(`gh label create "<label>" --force --description "<description>"`) | ||
|
|
||
| The `--force` flag is idempotent — it creates the label if missing, does nothing if it already exists. Run this once per unique label, not once per issue. |
There was a problem hiding this comment.
This description of gh label create --force is inaccurate: --force will update/overwrite an existing label’s metadata (name/description/color) rather than “do nothing”. Please adjust the wording to reflect the actual behavior (e.g., idempotent only when the same arguments are used) so readers don’t assume it’s a no-op on existing labels.
| The `--force` flag is idempotent — it creates the label if missing, does nothing if it already exists. Run this once per unique label, not once per issue. | |
| The `--force` flag creates the label if it is missing and updates/overwrites its metadata (name/description/color) if it already exists. It is effectively idempotent only when you re-run it with the same arguments for a given label. Run this once per unique label definition, not once per issue. |
core/config-handling.md
Outdated
| - **Priority label**: `P-high` or `P-medium` (matching the task's `**Priority:**` field; Low tasks are not created as issues) | ||
| - **Spec label**: `spec:<spec-id>` where `<spec-id>` is the `id` from `spec.json` (e.g., `spec:proxy-metrics`) | ||
| - **Type label**: `<type>` where `<type>` is the `type` from `spec.json` — one of `feat`, `fix`, or `refactor` (map `feature` to `feat`, `bugfix` to `fix`, `refactor` stays `refactor`) |
There was a problem hiding this comment.
Placeholder conventions are inconsistent in this protocol: the spec label is written as spec:<spec-id> here, but the later gh issue create example uses spec:<specId>. Please standardize on a single placeholder style/value source and use it consistently across the label bullets and the command examples to avoid agents substituting the wrong value.
skills/specops/SKILL.md
Outdated
| **Label set per issue:** | ||
| - **Priority label**: `P-high` or `P-medium` (matching the task's `**Priority:**` field; Low tasks are not created as issues) | ||
| - **Spec label**: `spec:<spec-id>` where `<spec-id>` is the `id` from `spec.json` (e.g., `spec:proxy-metrics`) | ||
| - **Type label**: `<type>` where `<type>` is the `type` from `spec.json` — one of `feat`, `fix`, or `refactor` (map `feature` to `feat`, `bugfix` to `fix`, `refactor` stays `refactor`) |
There was a problem hiding this comment.
The Type label bullet is internally inconsistent with the spec.json schema: spec.json.type is feature|bugfix|refactor, but this text says it is already feat|fix|refactor while also describing a mapping. Please reword to make it explicit that the label value is derived from spec.json.type via the mapping (and consider naming it <typeLabel> vs the raw type).
| - **Type label**: `<type>` where `<type>` is the `type` from `spec.json` — one of `feat`, `fix`, or `refactor` (map `feature` to `feat`, `bugfix` to `fix`, `refactor` stays `refactor`) | |
| - **Type label**: `<typeLabel>` where `<typeLabel>` is derived from the `type` field in `spec.json` (one of `feature`, `bugfix`, `refactor`) by mapping `feature` → `feat`, `bugfix` → `fix`, and `refactor` → `refactor` |
skills/specops/SKILL.md
Outdated
|
|
||
| Use the Bash tool to run(`gh label create "<label>" --force --description "<description>"`) | ||
|
|
||
| The `--force` flag is idempotent — it creates the label if missing, does nothing if it already exists. Run this once per unique label, not once per issue. |
There was a problem hiding this comment.
This description of gh label create --force is inaccurate: --force will update/overwrite an existing label’s metadata rather than “do nothing”. Please adjust the wording to reflect the actual behavior so readers don’t assume it’s a no-op on existing labels.
| The `--force` flag is idempotent — it creates the label if missing, does nothing if it already exists. Run this once per unique label, not once per issue. | |
| The `--force` flag is safe to re-run — it creates the label if missing, and updates/overwrites the existing label’s metadata (such as description or color) if it already exists. Run this once per unique label name, not once per issue. |
skills/specops/SKILL.md
Outdated
| **Label set per issue:** | ||
| - **Priority label**: `P-high` or `P-medium` (matching the task's `**Priority:**` field; Low tasks are not created as issues) | ||
| - **Spec label**: `spec:<spec-id>` where `<spec-id>` is the `id` from `spec.json` (e.g., `spec:proxy-metrics`) | ||
| - **Type label**: `<type>` where `<type>` is the `type` from `spec.json` — one of `feat`, `fix`, or `refactor` (map `feature` to `feat`, `bugfix` to `fix`, `refactor` stays `refactor`) |
There was a problem hiding this comment.
Placeholder conventions are inconsistent: the spec label is described as spec:<spec-id> here, but the later gh issue create example uses spec:<specId>. Please standardize on a single placeholder style/value source and use it consistently across the section to avoid agents substituting the wrong value.
| **Label set per issue:** | ||
| - **Priority label**: `P-high` or `P-medium` (matching the task's `**Priority:**` field; Low tasks are not created as issues) | ||
| - **Spec label**: `spec:<spec-id>` where `<spec-id>` is the `id` from `spec.json` (e.g., `spec:proxy-metrics`) | ||
| - **Type label**: `<type>` where `<type>` is the `type` from `spec.json` — one of `feat`, `fix`, or `refactor` (map `feature` to `feat`, `bugfix` to `fix`, `refactor` stays `refactor`) |
There was a problem hiding this comment.
The Type label bullet is internally inconsistent with the spec.json schema: spec.json.type is feature|bugfix|refactor, but this text says it is already feat|fix|refactor while also describing a mapping. Please reword to make it explicit that the label value is derived from spec.json.type via the mapping.
| - **Type label**: `<type>` where `<type>` is the `type` from `spec.json` — one of `feat`, `fix`, or `refactor` (map `feature` to `feat`, `bugfix` to `fix`, `refactor` stays `refactor`) | |
| - **Type label**: `<type-label>` where `<type-label>` is derived from the `type` field in `spec.json` by mapping `feature` → `feat`, `bugfix` → `fix`, and `refactor` → `refactor` |
|
|
||
| Run the terminal command(`gh label create "<label>" --force --description "<description>"`) | ||
|
|
||
| The `--force` flag is idempotent — it creates the label if missing, does nothing if it already exists. Run this once per unique label, not once per issue. |
There was a problem hiding this comment.
This description of gh label create --force is inaccurate: --force will update/overwrite an existing label’s metadata rather than “do nothing”. Please adjust the wording to reflect the actual behavior so users don’t assume it’s a no-op on existing labels.
| The `--force` flag is idempotent — it creates the label if missing, does nothing if it already exists. Run this once per unique label, not once per issue. | |
| The `--force` flag is safe to run repeatedly — it creates the label if missing and updates an existing label’s metadata (such as its description) to match the arguments you pass. Run this once per unique label definition, not once per issue. |
| - **Priority label**: `P-high` or `P-medium` (matching the task's `**Priority:**` field; Low tasks are not created as issues) | ||
| - **Spec label**: `spec:<spec-id>` where `<spec-id>` is the `id` from `spec.json` (e.g., `spec:proxy-metrics`) | ||
| - **Type label**: `<type>` where `<type>` is the `type` from `spec.json` — one of `feat`, `fix`, or `refactor` (map `feature` to `feat`, `bugfix` to `fix`, `refactor` stays `refactor`) | ||
|
|
There was a problem hiding this comment.
Placeholder conventions are inconsistent: the spec label is described as spec:<spec-id> here, but the later gh issue create example uses spec:<specId>. Please standardize on a single placeholder style/value source and regenerate outputs so agents apply the intended label reliably.
…ng hierarchy, and cross-platform consistency - Use <EscapedTaskTitle> placeholder in all issue creation command templates (CodeRabbit critical) - Fix inaccurate --force flag description: creates/updates, not "does nothing" (Greptile + Copilot) - Reword type label bullet to show explicit spec.json mapping (Copilot) - Standardize placeholder to <spec-id> across label definitions and commands (Copilot) - Demote Issue Body Composition / GitHub Label Protocol to h4 sub-sections (Greptile) - Add spec-id sanitization rule before shell interpolation (CodeRabbit critical) - Fix hardcoded requirements.md link — use <specArtifact> for bugfix/refactor specs (CodeRabbit) - Add --label flags to Jira/Linear command examples (CodeRabbit) - Add trailing newline to spec.json (Greptile) - Regenerate all platform outputs and checksums
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (2)
core/config-handling.md (1)
106-106:⚠️ Potential issue | 🟡 MinorAdd a language tag to the fenced template block.
The opening fence should include a language (e.g.,
```markdown) to satisfy markdownlint MD040.Proposed fix
-``` +```markdown ## Context ... **Priority:** <task priority> | **Effort:** <task effort> | **Dependencies:** <task dependencies></details> <details> <summary>🤖 Prompt for AI Agents</summary>Verify each finding against the current code and only fix it if needed.
In
@core/config-handling.mdat line 106, Add a language tag to the fenced
template block that begins with the opening triple backticks and contains the
"## Context" heading: change the opening fence fromtomarkdown so the
block is explicitly marked as Markdown (resolving markdownlint MD040); update
the fenced block that includes the lines starting with "## Context" and the task
metadata line to use the ```markdown tag.</details> </blockquote></details> <details> <summary>platforms/cursor/specops.mdc (1)</summary><blockquote> `475-476`: _⚠️ Potential issue_ | _🟠 Major_ **Jira/Linear examples still contradict the non-blocking label policy.** Line 475 says label failures must be skipped silently, but Lines 489 and 496 use required `--label` flags with no fallback path, so issue creation can fail instead of degrading gracefully. <details> <summary>Suggested patch</summary> ```diff -3. Run the terminal command(`jira issue create --type=Task --summary='<taskPrefix><EscapedTaskTitle>' --description-file <tempFile> --label '<priorityLabel>' --label 'spec:<spec-id>' --label '<typeLabel>'`) +3. Run the terminal command(`jira issue create --type=Task --summary '<taskPrefix><EscapedTaskTitle>' --description-file <tempFile> --label '<priorityLabel>' --label 'spec:<spec-id>' --label '<typeLabel>' || jira issue create --type=Task --summary '<taskPrefix><EscapedTaskTitle>' --description-file <tempFile>`) -3. Run the terminal command(`linear issue create --title '<taskPrefix><EscapedTaskTitle>' --description-file <tempFile> --label '<priorityLabel>' --label 'spec:<spec-id>' --label '<typeLabel>'`) +3. Run the terminal command(`linear issue create --title '<taskPrefix><EscapedTaskTitle>' --description-file <tempFile> --label '<priorityLabel>' --label 'spec:<spec-id>' --label '<typeLabel>' || linear issue create --title '<taskPrefix><EscapedTaskTitle>' --description-file <tempFile>`) ``` </details> Also applies to: 489-490, 496-497 <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against the current code and only fix it if needed. In `@platforms/cursor/specops.mdc` around lines 475 - 476, The Jira/Linear examples currently treat `--label` as required (contradicting the non-blocking label policy); update the examples and implementation so label usage is optional: change example CLI usage to show `--label` as optional, and in the code paths that apply labels (e.g., functions named createJiraIssue, createLinearIssue, or applyLabels) wrap label application in a try/catch (or check feature-flag/flag-presence) so any failure to add labels only logs a non-fatal warning and does not abort or return an error for issue creation; ensure any place that previously assumed `--label` was mandatory is updated to degrade gracefully and reflect the spec language that labels are enhancements, not requirements. ``` </details> </blockquote></details> </blockquote></details> <details> <summary>🤖 Prompt for all review comments with AI agents</summary>Verify each finding against the current code and only fix it if needed.
Inline comments:
In@core/config-handling.md:
- Around line 171-193: The command templates for Jira and Linear still always
pass --label and can fail on older CLIs; update the flow around RUN_COMMAND for
"jira issue create" and "linear issue create" so label failures do not abort
creation: after WRITE_FILE of , attempt RUN_COMMAND with labels
first, detect a non-fatal label-related error from stdout/stderr, and on such
failure retry RUN_COMMAND without any --label flags (or with only guaranteed
flags); ensure the parsed issue key/identifier handling (the parse step that
feeds EDIT_FILE tasks.md and the EDIT_FILE step that sets IssueID:) runs on
success of either attempt; keep EscapedTaskTitle and --description-file/--title
usage unchanged.In
@platforms/claude/SKILL.md:
- Line 410: The fenced code block containing the "## Context" snippet in
SKILL.md is missing a language identifier (triggering markdownlint MD040);
update the opening fence to include the markdown language (e.g., replace the
openingwithmarkdown) so the block is explicitly marked as markdown,
ensuring the "## Context" section and its contents are linted correctly.In
@platforms/codex/SKILL.md:
- Around line 397-498: The fenced code block for the "Issue Body Composition"
template is missing a language tag; edit the template in core/config-handling.md
(the "Issue Body Composition" section that contains the triple-backtick block
showing the IssueBody template) and change the opening fence from ``` toby running python3 generator/generate.py --all so platforms/codex/SKILL.md is updated with the language-annotated code fence. In `@platforms/cursor/specops.mdc`: - Line 477: The composed command uses config.team.taskPrefix + EscapedTaskTitle which escapes only the title and leaves the prefix unescaped, enabling shell injection; fix by creating a single escaped final title (e.g., compute EscapedIssueTitle = shellEscape(config.team.taskPrefix + TaskTitle) where shellEscape replaces '\'' with '\''\\\'\'' or, preferably, write the full composed title and IssueBody to temporary files and pass via file-based args (e.g., --title-file/--body-file) to the tracker CLI; replace all uses of the current EscapedTaskTitle concatenation in the command templates with EscapedIssueTitle (or the file-argument) and ensure IssueBody is similarly passed via file or fully escaped. --- Duplicate comments: In `@core/config-handling.md`: - Line 106: Add a language tag to the fenced template block that begins with the opening triple backticks and contains the "## Context" heading: change the opening fence from ``` to ```markdown so the block is explicitly marked as Markdown (resolving markdownlint MD040); update the fenced block that includes the lines starting with "## Context" and the task metadata line to use the ```markdown tag. In `@platforms/cursor/specops.mdc`: - Around line 475-476: The Jira/Linear examples currently treat `--label` as required (contradicting the non-blocking label policy); update the examples and implementation so label usage is optional: change example CLI usage to show `--label` as optional, and in the code paths that apply labels (e.g., functions named createJiraIssue, createLinearIssue, or applyLabels) wrap label application in a try/catch (or check feature-flag/flag-presence) so any failure to add labels only logs a non-fatal warning and does not abort or return an error for issue creation; ensure any place that previously assumed `--label` was mandatory is updated to degrade gracefully and reflect the spec language that labels are enhancements, not requirements.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID:
1a39210c-a2ea-4ce5-a193-f03f4632bf4e📒 Files selected for processing (8)
.specops/rich-issue-bodies/spec.jsonCHECKSUMS.sha256core/config-handling.mdplatforms/claude/SKILL.mdplatforms/codex/SKILL.mdplatforms/copilot/specops.instructions.mdplatforms/cursor/specops.mdcskills/specops/SKILL.md✅ Files skipped from review due to trivial changes (2)
- CHECKSUMS.sha256
- .specops/rich-issue-bodies/spec.json
🚧 Files skipped from review as they are similar to previous changes (1)
- skills/specops/SKILL.md
| **Jira and Linear**: Label/tag support varies. For Jira, use `--label` flag if available in the CLI version. For Linear, use `--label` flag. If the flag is unavailable or fails, skip labels silently — labels are enhancement, not requirement. Do not block issue creation on label failure. | ||
|
|
||
| **Shell safety**: `<TaskTitle>` and `<IssueBody>` contain user-controlled text. Before interpolating into shell commands, write the title and body to temporary files and pass via file-based arguments (e.g., `--body-file`). If file-based arguments are unavailable for the tracker CLI, single-quote the values with internal single-quotes escaped (`'` → `'\''`). Never pass unescaped user text directly in shell command strings. In command templates below, `<EscapedTaskTitle>` denotes the title after applying this escaping. | ||
|
|
||
| **GitHub** (`taskTracking: "github"`): | ||
| 1. WRITE_FILE a temp file with `<TaskDescription>` as content | ||
| 2. RUN_COMMAND(`gh issue create --title '<taskPrefix><TaskTitle>' --body-file <tempFile>`) | ||
| 3. Parse the issue URL/number from stdout | ||
| 4. EDIT_FILE `tasks.md` — set the task's `**IssueID:**` to the returned issue identifier (e.g., `#42`) | ||
| 1. Compose `<IssueBody>` following the Issue Body Composition template above | ||
| 2. WRITE_FILE a temp file with `<IssueBody>` as content | ||
| 3. RUN_COMMAND(`gh issue create --title '<taskPrefix><EscapedTaskTitle>' --body-file <tempFile> --label '<priorityLabel>' --label 'spec:<spec-id>' --label '<typeLabel>'`) | ||
| 4. Parse the issue URL/number from stdout | ||
| 5. EDIT_FILE `tasks.md` — set the task's `**IssueID:**` to the returned issue identifier (e.g., `#42`) | ||
|
|
||
| **Jira** (`taskTracking: "jira"`): | ||
| 1. WRITE_FILE a temp file with `<TaskDescription>` as content | ||
| 2. RUN_COMMAND(`jira issue create --type=Task --summary='<taskPrefix><TaskTitle>' --description-file <tempFile>`) | ||
| 3. Parse the issue key from stdout (e.g., `PROJ-123`) | ||
| 4. EDIT_FILE `tasks.md` — set the task's `**IssueID:**` to the returned key | ||
| 1. Compose `<IssueBody>` following the Issue Body Composition template above | ||
| 2. WRITE_FILE a temp file with `<IssueBody>` as content | ||
| 3. RUN_COMMAND(`jira issue create --type=Task --summary='<taskPrefix><EscapedTaskTitle>' --description-file <tempFile> --label '<priorityLabel>' --label 'spec:<spec-id>' --label '<typeLabel>'`) | ||
| 4. Parse the issue key from stdout (e.g., `PROJ-123`) | ||
| 5. EDIT_FILE `tasks.md` — set the task's `**IssueID:**` to the returned key | ||
|
|
||
| **Linear** (`taskTracking: "linear"`): | ||
| 1. WRITE_FILE a temp file with `<TaskDescription>` as content | ||
| 2. RUN_COMMAND(`linear issue create --title '<taskPrefix><TaskTitle>' --description-file <tempFile>`) | ||
| 3. Parse the issue identifier from stdout | ||
| 4. EDIT_FILE `tasks.md` — set the task's `**IssueID:**` to the returned identifier | ||
| 1. Compose `<IssueBody>` following the Issue Body Composition template above | ||
| 2. WRITE_FILE a temp file with `<IssueBody>` as content | ||
| 3. RUN_COMMAND(`linear issue create --title '<taskPrefix><EscapedTaskTitle>' --description-file <tempFile> --label '<priorityLabel>' --label 'spec:<spec-id>' --label '<typeLabel>'`) | ||
| 4. Parse the issue identifier from stdout |
There was a problem hiding this comment.
Make Jira/Linear label fallback explicit in the command flow.
This section says label failures must not block issue creation, but the command templates always pass --label and don’t define a retry path without labels. That can still fail hard on unsupported/older CLIs.
Proposed protocol patch
**Jira** (`taskTracking: "jira"`):
1. Compose `<IssueBody>` following the Issue Body Composition template above
2. WRITE_FILE a temp file with `<IssueBody>` as content
-3. RUN_COMMAND(`jira issue create --type=Task --summary='<taskPrefix><EscapedTaskTitle>' --description-file <tempFile> --label '<priorityLabel>' --label 'spec:<spec-id>' --label '<typeLabel>'`)
+3. Try with labels:
+ RUN_COMMAND(`jira issue create --type=Task --summary='<taskPrefix><EscapedTaskTitle>' --description-file <tempFile> --label '<priorityLabel>' --label 'spec:<spec-id>' --label '<typeLabel>'`)
+ If label flags are unsupported/fail, retry once without labels:
+ RUN_COMMAND(`jira issue create --type=Task --summary='<taskPrefix><EscapedTaskTitle>' --description-file <tempFile>`)
4. Parse the issue key from stdout (e.g., `PROJ-123`)
5. EDIT_FILE `tasks.md` — set the task's `**IssueID:**` to the returned key
**Linear** (`taskTracking: "linear"`):
1. Compose `<IssueBody>` following the Issue Body Composition template above
2. WRITE_FILE a temp file with `<IssueBody>` as content
-3. RUN_COMMAND(`linear issue create --title '<taskPrefix><EscapedTaskTitle>' --description-file <tempFile> --label '<priorityLabel>' --label 'spec:<spec-id>' --label '<typeLabel>'`)
+3. Try with labels:
+ RUN_COMMAND(`linear issue create --title '<taskPrefix><EscapedTaskTitle>' --description-file <tempFile> --label '<priorityLabel>' --label 'spec:<spec-id>' --label '<typeLabel>'`)
+ If label flags are unsupported/fail, retry once without labels:
+ RUN_COMMAND(`linear issue create --title '<taskPrefix><EscapedTaskTitle>' --description-file <tempFile>`)
4. Parse the issue identifier from stdout
5. EDIT_FILE `tasks.md` — set the task's `**IssueID:**` to the returned identifier🧰 Tools
🪛 LanguageTool
[uncategorized] ~175-~175: The official name of this software platform is spelled with a capital “H”.
Context: ...er applying this escaping. GitHub (taskTracking: "github"): 1. Compose <IssueBody> following ...
(GITHUB)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@core/config-handling.md` around lines 171 - 193, The command templates for
Jira and Linear still always pass --label and can fail on older CLIs; update the
flow around RUN_COMMAND for "jira issue create" and "linear issue create" so
label failures do not abort creation: after WRITE_FILE of <IssueBody>, attempt
RUN_COMMAND with labels first, detect a non-fatal label-related error from
stdout/stderr, and on such failure retry RUN_COMMAND without any --label flags
(or with only guaranteed flags); ensure the parsed issue key/identifier handling
(the parse step that feeds EDIT_FILE tasks.md and the EDIT_FILE step that sets
**IssueID:**) runs on success of either attempt; keep EscapedTaskTitle and
--description-file/--title usage unchanged.
|
|
||
| Compose `<IssueBody>` using this template: | ||
|
|
||
| ``` |
There was a problem hiding this comment.
Add a language identifier to the fenced code block.
This fence triggers markdownlint MD040 and will keep lint noisy/failing.
Suggested fix
-```
+```markdown
## Context
<1-3 sentence summary from requirements.md/bugfix.md/refactor.md Overview explaining why this work exists>
@@
**Priority:** <task priority> | **Effort:** <task effort> | **Dependencies:** <task dependencies></details>
<!-- suggestion_start -->
<details>
<summary>📝 Committable suggestion</summary>
> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
```suggestion
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)
[warning] 410-410: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@platforms/claude/SKILL.md` at line 410, The fenced code block containing the
"## Context" snippet in SKILL.md is missing a language identifier (triggering
markdownlint MD040); update the opening fence to include the markdown language
(e.g., replace the opening ``` with ```markdown) so the block is explicitly
marked as markdown, ensuring the "## Context" section and its contents are
linted correctly.
| #### Issue Body Composition | ||
|
|
||
| Before creating each issue, compose `<IssueBody>` by extracting content from spec artifacts. This composition is mandatory — writing a freeform description instead of following this template is a protocol breach. | ||
|
|
||
| For each eligible task, Read the file at `<specsDir>/<spec-name>/requirements.md` (or `bugfix.md`/`refactor.md`), Read the file at `<specsDir>/<spec-name>/spec.json`, and extract: | ||
|
|
||
| 1. **Context**: The spec's Overview/Product Requirements first paragraph (1-3 sentences explaining "why") | ||
| 2. **Spec type**: From `spec.json` `type` field | ||
| 3. **Spec name**: From `spec.json` `id` field | ||
|
|
||
| Compose `<IssueBody>` using this template: | ||
|
|
||
| ``` | ||
| ## Context | ||
|
|
||
| <1-3 sentence summary from requirements.md/bugfix.md/refactor.md Overview explaining why this work exists> | ||
|
|
||
| **Spec:** `<spec-id>` | **Type:** <spec-type> | ||
|
|
||
| ## Spec Artifacts | ||
|
|
||
| - [Requirements](<specsDir>/<spec-name>/<specArtifact>) where <specArtifact> is `requirements.md` for features, `bugfix.md` for bugfixes, or `refactor.md` for refactors | ||
| - [Design](<specsDir>/<spec-name>/design.md) | ||
| - [Tasks](<specsDir>/<spec-name>/tasks.md) | ||
|
|
||
| ## Description | ||
|
|
||
| <Full text from the task's **Description:** section in tasks.md> | ||
|
|
||
| ## Implementation Steps | ||
|
|
||
| <Numbered list from the task's **Implementation Steps:** section in tasks.md> | ||
|
|
||
| ## Acceptance Criteria | ||
|
|
||
| <Checkbox items from the task's **Acceptance Criteria:** section in tasks.md> | ||
|
|
||
| ## Files to Modify | ||
|
|
||
| <Bulleted list from the task's **Files to Modify:** section in tasks.md> | ||
|
|
||
| ## Tests Required | ||
|
|
||
| <Checkbox items from the task's **Tests Required:** section in tasks.md. If the task has no Tests Required section, omit this entire section.> | ||
|
|
||
| --- | ||
|
|
||
| **Priority:** <task priority> | **Effort:** <task effort> | **Dependencies:** <task dependencies> | ||
| ``` | ||
|
|
||
| Every section above (except Tests Required) is mandatory. If a section's source data is empty in `tasks.md`, write "None specified" rather than omitting the section. | ||
|
|
||
| #### GitHub Label Protocol | ||
|
|
||
| When `taskTracking` is `"github"`, apply labels to each created issue. Labels make issues searchable and categorizable. | ||
|
|
||
| **Label set per issue:** | ||
| - **Priority label**: `P-high` or `P-medium` (matching the task's `**Priority:**` field; Low tasks are not created as issues) | ||
| - **Spec label**: `spec:<spec-id>` where `<spec-id>` is the `id` from `spec.json` (e.g., `spec:proxy-metrics`) | ||
| - **Type label**: `<typeLabel>` where `<typeLabel>` is derived from the `type` field in `spec.json` using this mapping: `feature` → `feat`, `bugfix` → `fix`, `refactor` → `refactor` | ||
|
|
||
| **Label safety**: Before interpolating `<spec-id>` or `<typeLabel>` into label commands, validate that each value matches `^[a-z0-9][a-z0-9:_-]*$` (lowercase alphanumeric, hyphens, underscores, colons). Reject or normalize any value that doesn't match — this prevents shell injection via malformed spec IDs. | ||
|
|
||
| **Label creation**: Before creating the first issue for a spec, ensure all required labels exist. For each label in the set, run: | ||
|
|
||
| Execute the command(`gh label create "<label>" --force --description "<description>"`) | ||
|
|
||
| The `--force` flag creates the label if it is missing and updates/overwrites its metadata (name/description/color) if it already exists. It is effectively idempotent only when you re-run it with the same arguments. Run this once per unique label definition, not once per issue. | ||
|
|
||
| Label descriptions: | ||
| - `P-high`: "High priority task" | ||
| - `P-medium`: "Medium priority task" | ||
| - `spec:<spec-id>`: "SpecOps spec: <spec-id>" | ||
| - `feat`: "Feature implementation" | ||
| - `fix`: "Bug fix" | ||
| - `refactor`: "Code refactoring" | ||
|
|
||
| **Jira and Linear**: Label/tag support varies. For Jira, use `--label` flag if available in the CLI version. For Linear, use `--label` flag. If the flag is unavailable or fails, skip labels silently — labels are enhancement, not requirement. Do not block issue creation on label failure. | ||
|
|
||
| **Shell safety**: `<TaskTitle>` and `<IssueBody>` contain user-controlled text. Before interpolating into shell commands, write the title and body to temporary files and pass via file-based arguments (e.g., `--body-file`). If file-based arguments are unavailable for the tracker CLI, single-quote the values with internal single-quotes escaped (`'` → `'\''`). Never pass unescaped user text directly in shell command strings. In command templates below, `<EscapedTaskTitle>` denotes the title after applying this escaping. | ||
|
|
||
| **GitHub** (`taskTracking: "github"`): | ||
| 1. Write the file at a temp file with `<TaskDescription>` as content | ||
| 2. Execute the command(`gh issue create --title '<taskPrefix><TaskTitle>' --body-file <tempFile>`) | ||
| 3. Parse the issue URL/number from stdout | ||
| 4. Edit the file at `tasks.md` — set the task's `**IssueID:**` to the returned issue identifier (e.g., `#42`) | ||
| 1. Compose `<IssueBody>` following the Issue Body Composition template above | ||
| 2. Write the file at a temp file with `<IssueBody>` as content | ||
| 3. Execute the command(`gh issue create --title '<taskPrefix><EscapedTaskTitle>' --body-file <tempFile> --label '<priorityLabel>' --label 'spec:<spec-id>' --label '<typeLabel>'`) | ||
| 4. Parse the issue URL/number from stdout | ||
| 5. Edit the file at `tasks.md` — set the task's `**IssueID:**` to the returned issue identifier (e.g., `#42`) | ||
|
|
||
| **Jira** (`taskTracking: "jira"`): | ||
| 1. Write the file at a temp file with `<TaskDescription>` as content | ||
| 2. Execute the command(`jira issue create --type=Task --summary='<taskPrefix><TaskTitle>' --description-file <tempFile>`) | ||
| 3. Parse the issue key from stdout (e.g., `PROJ-123`) | ||
| 4. Edit the file at `tasks.md` — set the task's `**IssueID:**` to the returned key | ||
| 1. Compose `<IssueBody>` following the Issue Body Composition template above | ||
| 2. Write the file at a temp file with `<IssueBody>` as content | ||
| 3. Execute the command(`jira issue create --type=Task --summary='<taskPrefix><EscapedTaskTitle>' --description-file <tempFile> --label '<priorityLabel>' --label 'spec:<spec-id>' --label '<typeLabel>'`) | ||
| 4. Parse the issue key from stdout (e.g., `PROJ-123`) | ||
| 5. Edit the file at `tasks.md` — set the task's `**IssueID:**` to the returned key | ||
|
|
||
| **Linear** (`taskTracking: "linear"`): | ||
| 1. Write the file at a temp file with `<TaskDescription>` as content | ||
| 2. Execute the command(`linear issue create --title '<taskPrefix><TaskTitle>' --description-file <tempFile>`) | ||
| 3. Parse the issue identifier from stdout | ||
| 4. Edit the file at `tasks.md` — set the task's `**IssueID:**` to the returned identifier | ||
| 1. Compose `<IssueBody>` following the Issue Body Composition template above | ||
| 2. Write the file at a temp file with `<IssueBody>` as content | ||
| 3. Execute the command(`linear issue create --title '<taskPrefix><EscapedTaskTitle>' --description-file <tempFile> --label '<priorityLabel>' --label 'spec:<spec-id>' --label '<typeLabel>'`) | ||
| 4. Parse the issue identifier from stdout | ||
| 5. Edit the file at `tasks.md` — set the task's `**IssueID:**` to the returned identifier | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Confirm source-of-truth files contain the new protocol markers
rg -n -C2 "Issue Body Composition|GitHub Label Protocol|freeform issue bodies are a protocol breach|<EscapedTaskTitle>|spec:<spec-id>" core generator/templates || true
# 2) Confirm regenerated outputs contain the same markers across platform files
rg -n -C1 "Issue Body Composition|GitHub Label Protocol|freeform issue bodies are a protocol breach" \
platforms/claude/SKILL.md \
platforms/cursor/specops.mdc \
platforms/codex/SKILL.md \
platforms/copilot/specops.instructions.md || true
# 3) Show whether this PR touched likely source files for this generated change
git diff --name-only HEAD~1..HEAD | rg -n "^(core/|generator/templates/|platforms/)" || trueRepository: sanmak/specops
Length of output: 19822
The generated file is correctly produced from source — no manual edits detected. Ensure the fenced code block in the Issue Body Composition template has a language identifier.
The Issue Body Composition protocol text is properly synchronized between core/config-handling.md (source) and all platform outputs. However, the fenced code block showing the template (around line 410 in platforms/codex/SKILL.md) is missing a language identifier on the opening fence. Update the source template in core/config-handling.md to add a language tag (e.g., ```markdown), then regenerate with python3 generator/generate.py --all.
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)
[warning] 410-410: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@platforms/codex/SKILL.md` around lines 397 - 498, The fenced code block for
the "Issue Body Composition" template is missing a language tag; edit the
template in core/config-handling.md (the "Issue Body Composition" section that
contains the triple-backtick block showing the IssueBody template) and change
the opening fence from ``` to ```markdown (or another appropriate language),
then regenerate the platform docs by running python3 generator/generate.py --all
so platforms/codex/SKILL.md is updated with the language-annotated code fence.
|
|
||
| **Jira and Linear**: Label/tag support varies. For Jira, use `--label` flag if available in the CLI version. For Linear, use `--label` flag. If the flag is unavailable or fails, skip labels silently — labels are enhancement, not requirement. Do not block issue creation on label failure. | ||
|
|
||
| **Shell safety**: `<TaskTitle>` and `<IssueBody>` contain user-controlled text. Before interpolating into shell commands, write the title and body to temporary files and pass via file-based arguments (e.g., `--body-file`). If file-based arguments are unavailable for the tracker CLI, single-quote the values with internal single-quotes escaped (`'` → `'\''`). Never pass unescaped user text directly in shell command strings. In command templates below, `<EscapedTaskTitle>` denotes the title after applying this escaping. |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify where title composition currently relies on escaping only TaskTitle
rg -n -C 2 "EscapedTaskTitle|taskPrefix|--title|--summary" platforms/cursor/specops.mdc
# Confirm there is no dedicated escaped placeholder for the full composed title
rg -n "EscapedIssueTitle|EscapedFullTitle|EscapedTitle" platforms/cursor/specops.mdcRepository: sanmak/specops
Length of output: 3862
🏁 Script executed:
#!/bin/bash
# Check if taskPrefix is sanitized or escaped anywhere
rg -n "taskPrefix|config.team.taskPrefix" platforms/cursor/specops.mdc | head -20
# Look for any configuration validation or escaping of taskPrefix
rg -n -B 2 -A 2 "config.team" platforms/cursor/specops.mdc | grep -A 5 -B 5 "taskPrefix"Repository: sanmak/specops
Length of output: 1003
🏁 Script executed:
#!/bin/bash
# Look for config schema or validation
rg -n "config\.team\.|schema|validation" platforms/cursor/specops.mdc | head -15
# Find all shell command patterns that might use taskPrefix or similar prefix patterns
rg -n "create.*--title|create.*--summary" platforms/cursor/specops.mdc | grep -v "EscapedTaskTitle"Repository: sanmak/specops
Length of output: 3605
Escape the complete composed title, not only <EscapedTaskTitle>.
Line 477 defines <EscapedTaskTitle>, but commands at lines 482, 489, and 496 compose <taskPrefix><EscapedTaskTitle>. Since config.team.taskPrefix is user-controlled, if it contains ' or shell metacharacters, escaping breaks and allows injection. Escape the final composed title as one unit (e.g., <EscapedIssueTitle>) encompassing both the prefix and title together.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@platforms/cursor/specops.mdc` at line 477, The composed command uses
config.team.taskPrefix + EscapedTaskTitle which escapes only the title and
leaves the prefix unescaped, enabling shell injection; fix by creating a single
escaped final title (e.g., compute EscapedIssueTitle =
shellEscape(config.team.taskPrefix + TaskTitle) where shellEscape replaces '\''
with '\''\\\'\'' or, preferably, write the full composed title and IssueBody to
temporary files and pass via file-based args (e.g., --title-file/--body-file) to
the tracker CLI; replace all uses of the current EscapedTaskTitle concatenation
in the command templates with EscapedIssueTitle (or the file-argument) and
ensure IssueBody is similarly passed via file or fully escaped.
| **Jira** (`taskTracking: "jira"`): | ||
| 1. WRITE_FILE a temp file with `<TaskDescription>` as content | ||
| 2. RUN_COMMAND(`jira issue create --type=Task --summary='<taskPrefix><TaskTitle>' --description-file <tempFile>`) | ||
| 3. Parse the issue key from stdout (e.g., `PROJ-123`) | ||
| 4. EDIT_FILE `tasks.md` — set the task's `**IssueID:**` to the returned key | ||
| 1. Compose `<IssueBody>` following the Issue Body Composition template above | ||
| 2. WRITE_FILE a temp file with `<IssueBody>` as content | ||
| 3. RUN_COMMAND(`jira issue create --type=Task --summary='<taskPrefix><EscapedTaskTitle>' --description-file <tempFile> --label '<priorityLabel>' --label 'spec:<spec-id>' --label '<typeLabel>'`) | ||
| 4. Parse the issue key from stdout (e.g., `PROJ-123`) | ||
| 5. EDIT_FILE `tasks.md` — set the task's `**IssueID:**` to the returned key | ||
|
|
||
| **Linear** (`taskTracking: "linear"`): | ||
| 1. WRITE_FILE a temp file with `<TaskDescription>` as content | ||
| 2. RUN_COMMAND(`linear issue create --title '<taskPrefix><TaskTitle>' --description-file <tempFile>`) | ||
| 3. Parse the issue identifier from stdout | ||
| 4. EDIT_FILE `tasks.md` — set the task's `**IssueID:**` to the returned identifier | ||
| 1. Compose `<IssueBody>` following the Issue Body Composition template above | ||
| 2. WRITE_FILE a temp file with `<IssueBody>` as content | ||
| 3. RUN_COMMAND(`linear issue create --title '<taskPrefix><EscapedTaskTitle>' --description-file <tempFile> --label '<priorityLabel>' --label 'spec:<spec-id>' --label '<typeLabel>'`) | ||
| 4. Parse the issue identifier from stdout | ||
| 5. EDIT_FILE `tasks.md` — set the task's `**IssueID:**` to the returned identifier |
There was a problem hiding this comment.
Jira/Linear label flags block issue creation on failure
The graceful degradation section states "Do not block issue creation on label failure," but the command templates for Jira and Linear embed --label flags inline in the same command as issue creation:
jira issue create ... --label '<priorityLabel>' --label 'spec:<spec-id>' --label '<typeLabel>'
linear issue create ... --label '<priorityLabel>' --label 'spec:<spec-id>' --label '<typeLabel>'
If the CLI version doesn't support --label (e.g., older Jira CLI), the entire command fails — not just the label application — and issue creation is blocked entirely. This directly contradicts the stated intent of "If the flag is unavailable or fails, skip labels silently."
A retry-without-labels fallback step needs to be described explicitly, e.g.:
- Attempt issue creation with
--labelflags - If the command fails due to an unrecognised flag, retry using the command without any
--labelflags - Only then apply the Graceful Degradation rule
This problem also appears identically in all four generated platform files:
platforms/claude/SKILL.md(Jira/Linear steps)platforms/codex/SKILL.md(Jira/Linear steps)platforms/copilot/specops.instructions.md(Jira/Linear steps)platforms/cursor/specops.mdc(Jira/Linear steps)
Summary
P-high/P-medium,spec:<id>,feat/fix/refactor) using idempotentgh label create --forceISSUE_BODY_MARKERSadded to validate.py and test_platform_consistency.py for all 4 platformsChanges
core/config-handling.md<TaskDescription>→<IssueBody>,--labelflags)core/task-tracking.mdcore/workflow.mdgenerator/validate.pyISSUE_BODY_MARKERSconstant, per-platform check, cross-platform consistencytests/test_platform_consistency.pyissue_bodytoREQUIRED_MARKERSplatforms/*/,skills/specops/.specops/rich-issue-bodies/Test Plan
python3 generator/generate.py --all— 4 platforms generatedpython3 generator/validate.py— all checks pass including new issue-body markersbash scripts/run-tests.sh— 8/8 tests passshasum -a 256 -c CHECKSUMS.sha256— all 17 checksums verifiedSummary by CodeRabbit
New Features
Documentation
Tests
Greptile Summary
This PR enriches external issue creation in SpecOps by adding a mandatory structured Issue Body Composition template (Context, Spec Artifacts, Description, Implementation Steps, Acceptance Criteria, Files to Modify, Tests Required) and a GitHub Label Protocol that auto-applies three label categories (
P-high/P-medium,spec:<id>,feat/fix/refactor) using idempotentgh label create --force. The changes are wired tocore/config-handling.md, propagated to all four generated platform outputs, cross-referenced intask-tracking.mdandworkflow.md, and enforced in CI viaISSUE_BODY_MARKERSin bothvalidate.pyandtest_platform_consistency.py.Key findings:
--labelflags are embedded inline in the singlejira issue create/linear issue createcommand. If the CLI version doesn't support--label, the entire command fails, blocking issue creation — contrary to the stated "Do not block issue creation on label failure" requirement. The protocol needs an explicit retry-without-labels fallback for these trackers.^[a-z0-9][a-z0-9:_-]*$allows colons inside the spec-id value, which is unintentional (spec IDs are hyphenated slugs) and could allow ambiguous label names likespec:foo:bar.ISSUE_BODY_MARKERSis correctly added to both the per-platform validation and the cross-platform consistency loop, and the test file is updated in parallel — consistent with the pattern used by all prior marker sets.--forcesemantics: The documentation now accurately describes--forceas an overwrite/upsert operation (creating if absent, updating if present), which is correct — an improvement over the wording flagged in the previous review round.Confidence Score: 3/5
Important Files Changed
Sequence Diagram
sequenceDiagram participant Agent participant TasksMd as tasks.md participant SpecArtifacts as Spec Artifacts participant GitHub Agent->>TasksMd: Read - identify High/Medium priority tasks loop For each eligible task Agent->>SpecArtifacts: Read requirements.md or bugfix.md or refactor.md Agent->>SpecArtifacts: Read spec.json for id and type fields Agent->>Agent: Compose IssueBody from template note over Agent,GitHub: Label creation runs once per spec Agent->>GitHub: gh label create P-high or P-medium --force Agent->>GitHub: gh label create spec id --force Agent->>GitHub: gh label create type label --force Agent->>Agent: Write IssueBody to temp file Agent->>GitHub: gh issue create with title, body-file, and labels GitHub-->>Agent: Return issue URL or number Agent->>TasksMd: Write IssueID back to task entry endLast reviewed commit: "fix: address PR revi..."