OpenSpec Feature List (Instruction-Only)
Follow these instructions when this skill is invoked to generate or update openspec/changes/<change-id>/feature_list.json by parsing openspec/changes/<change-id>/tasks.md.
Do not add scripts or code files. Do not modify openspec/changes/<change-id>/tasks.md. Do not run any git commands.
Invocation
- Preferred:
$openspec-feature-list <change-id>
- If
<change-id> is missing, ask the user for it once, then proceed.
Behavior (step-by-step)
- Inputs and paths
change-id = user-provided argument.
tasks.md = openspec/changes/<change-id>/tasks.md
- Output JSON =
openspec/changes/<change-id>/feature_list.json
- Read
tasks.md
- If
tasks.md does not exist, stop and report an error. Do not create output.
- Parse
tasks.md into task blocks (checkbox-only)
- Consider ONLY GitHub-flavored checkbox task lines:
- [ ] ...
- [x] ... (case-insensitive x is allowed)
- A task block is: the checkbox line plus all following lines until the next checkbox line or EOF.
- Hard validation (fail fast; do not write/overwrite output on failure)
- Every checkbox task line MUST contain EXACTLY ONE ref token of the form
[#R<n>] (example: [#R12]).
- If missing or more than one, error and point to the task line.
- Each
[#R<n>] MUST be unique across the entire tasks.md.
- If duplicates exist, error and list the duplicate refs.
- In each task block, BOTH fields must exist (case-insensitive; allow optional leading
- bullet and indentation):
ACCEPT: ...
TEST: ...
- If either is missing, error and include the ref key and the checkbox line number.
- Derive per-feature fields (one feature per ref)
ref key: "R<n>" from [#R<n>] (example: [#R12] => key "R12").
task_id:
- If the first token after the checkbox marker looks like dot-numbered (example:
1.1), store it.
- Otherwise use
"?".
description:
- Start from the checkbox line text.
- Remove the leading checkbox marker and
task_id (if present).
- Strip trailing bracket tokens (examples:
[#R1], [@F_login], [#REQ-...], [#SCN-...], and any other trailing [...] tokens).
- Keep a short, verb-led summary.
accept: the text after ACCEPT: in the block (trim).
test: the text after TEST: in the block (trim).
steps:
- If
TEST starts with Manual: (case-insensitive), treat it as manual steps:
- Split the remainder on newlines and/or semicolons.
- Trim each step and remove leading numbering like
1) or 1..
- Otherwise treat
TEST as runnable command(s):
- Split on newlines.
- Also split simple
&& chains into separate commands.
- Produce steps like:
Run: <command>.
category (simple heuristic from description, case-insensitive):
docs if contains: doc, docs, readme, changelog
maintenance if contains: refactor, cleanup, lint, reformat
performance if contains: perf, performance, benchmark
testing if contains: test, pytest, jest, unit, integration
- else:
functional
passes: default false (see preservation rule below).
- Preserve pass state if output exists
- If
feature_list.json already exists, preserve passes for matching ref keys.
- Do not reset
passes unless the user explicitly asks to reset (example: "reset passes").
- New refs start with
passes=false.
- Write output JSON (pretty, deterministic)
- Write to:
openspec/changes/<change-id>/feature_list.json.
- Do not write anything if hard validation fails.
- Ensure determinism:
- Sort features by ref number (R1, R2, R10, ...).
- Pretty-print with
indent=2 and a trailing newline.
- Set:
change_id = <change-id>
generated_at = UTC ISO-8601 timestamp with Z suffix (example: 2026-01-06T02:30:00Z)
- Output format MUST be a ref-keyed map (not a list), exactly:
{
"change_id": "<change-id>",
"generated_at": "<UTC ISO-8601 Z timestamp>",
"features": {
"R1": {
"category": "functional",
"description": "New chat button creates a fresh conversation",
"task_id": "1.1",
"accept": "<ACCEPT value>",
"test": "<TEST value>",
"steps": [
"Navigate to main interface",
"Click the 'New Chat' button",
"Verify a new conversation is created",
"Check that chat area shows welcome state",
"Verify conversation appears in sidebar"
],
"passes": false
}
}
}
- After writing, print a short summary
- Print:
change-id, number of features generated, and output path.
1---2name: openspec-feature-list3description: Generate openspec/changes/<id>/feature_list.json by parsing openspec/changes/<id>/tasks.md (one feature per unique [#R<n>] checkbox task).4---5
6# OpenSpec Feature List (Instruction-Only)
7
8Follow these instructions when this skill is invoked to generate or update `openspec/changes/<change-id>/feature_list.json` by parsing `openspec/changes/<change-id>/tasks.md`.
9
10Do not add scripts or code files. Do not modify `openspec/changes/<change-id>/tasks.md`. Do not run any git commands.
11
12## Invocation
13
14- Preferred: `$openspec-feature-list <change-id>`
15- If `<change-id>` is missing, ask the user for it once, then proceed.
16
17## Behavior (step-by-step)
18
191) Inputs and paths
20- `change-id` = user-provided argument.
21- `tasks.md` = `openspec/changes/<change-id>/tasks.md`
22- Output JSON = `openspec/changes/<change-id>/feature_list.json`
23
242) Read `tasks.md`
25- If `tasks.md` does not exist, stop and report an error. Do not create output.
26
273) Parse `tasks.md` into task blocks (checkbox-only)
28- Consider ONLY GitHub-flavored checkbox task lines:
29 - `- [ ] ...`
30 - `- [x] ...` (case-insensitive `x` is allowed)
31- A task block is: the checkbox line plus all following lines until the next checkbox line or EOF.
32
334) Hard validation (fail fast; do not write/overwrite output on failure)
34- Every checkbox task line MUST contain EXACTLY ONE ref token of the form `[#R<n>]` (example: `[#R12]`).
35 - If missing or more than one, error and point to the task line.
36- Each `[#R<n>]` MUST be unique across the entire `tasks.md`.
37 - If duplicates exist, error and list the duplicate refs.
38- In each task block, BOTH fields must exist (case-insensitive; allow optional leading `-` bullet and indentation):
39 - `ACCEPT: ...`
40 - `TEST: ...`
41 - If either is missing, error and include the ref key and the checkbox line number.
42
435) Derive per-feature fields (one feature per ref)
44- `ref` key: `"R<n>"` from `[#R<n>]` (example: `[#R12]` => key `"R12"`).
45- `task_id`:
46 - If the first token after the checkbox marker looks like dot-numbered (example: `1.1`), store it.
47 - Otherwise use `"?"`.
48- `description`:
49 - Start from the checkbox line text.
50 - Remove the leading checkbox marker and `task_id` (if present).
51 - Strip trailing bracket tokens (examples: `[#R1]`, `[@F_login]`, `[#REQ-...]`, `[#SCN-...]`, and any other trailing `[...]` tokens).
52 - Keep a short, verb-led summary.
53- `accept`: the text after `ACCEPT:` in the block (trim).
54- `test`: the text after `TEST:` in the block (trim).
55- `steps`:
56 - If `TEST` starts with `Manual:` (case-insensitive), treat it as manual steps:
57 - Split the remainder on newlines and/or semicolons.
58 - Trim each step and remove leading numbering like `1)` or `1.`.
59 - Otherwise treat `TEST` as runnable command(s):
60 - Split on newlines.
61 - Also split simple `&&` chains into separate commands.
62 - Produce steps like: `Run: <command>`.
63- `category` (simple heuristic from `description`, case-insensitive):
64 - `docs` if contains: `doc`, `docs`, `readme`, `changelog`
65 - `maintenance` if contains: `refactor`, `cleanup`, `lint`, `reformat`
66 - `performance` if contains: `perf`, `performance`, `benchmark`
67 - `testing` if contains: `test`, `pytest`, `jest`, `unit`, `integration`
68 - else: `functional`
69- `passes`: default `false` (see preservation rule below).
70
716) Preserve pass state if output exists
72- If `feature_list.json` already exists, preserve `passes` for matching ref keys.
73- Do not reset `passes` unless the user explicitly asks to reset (example: "reset passes").
74- New refs start with `passes=false`.
75
767) Write output JSON (pretty, deterministic)
77- Write to: `openspec/changes/<change-id>/feature_list.json`.
78- Do not write anything if hard validation fails.
79- Ensure determinism:
80 - Sort features by ref number (R1, R2, R10, ...).
81 - Pretty-print with `indent=2` and a trailing newline.
82- Set:
83 - `change_id` = `<change-id>`
84 - `generated_at` = UTC ISO-8601 timestamp with `Z` suffix (example: `2026-01-06T02:30:00Z`)
85- Output format MUST be a ref-keyed map (not a list), exactly:
86```json
87{
88 "change_id": "<change-id>",
89 "generated_at": "<UTC ISO-8601 Z timestamp>",
90 "features": {
91 "R1": {
92 "category": "functional",
93 "description": "New chat button creates a fresh conversation",
94 "task_id": "1.1",
95 "accept": "<ACCEPT value>",
96 "test": "<TEST value>",
97 "steps": [
98 "Navigate to main interface",
99 "Click the 'New Chat' button",
100 "Verify a new conversation is created",
101 "Check that chat area shows welcome state",
102 "Verify conversation appears in sidebar"
103 ],
104 "passes": false
105 }
106 }
107}
108```
109
1108) After writing, print a short summary
111- Print: `change-id`, number of features generated, and output path.