Feature File Management Manage - a waterfall-style planning document combining structured requirements tracking with incremental development. Quick Reference See for complete field documentation. Proactive Usage This skill should be used automatically when features.yml exists. Before Starting Implementation 1. Check if exists in project root 2. If missing: do not use this skill proactively (stop here) 3. Plan the work in features.yml before writing code: - Add/update the feature with all requirements extracted from the user's request - Add anticipated test cases to (with ) - Document design d…

, line)\n if match:\n current_feature = match.group(1).strip()\n else:\n current_feature = line.split(\":\", 1)[1].strip().strip(\"\\\"'\")\n elif line.startswith(\"feature:\") and current_feature is None:\n # Extract feature name\n match = re.match(r'feature:\\s*[\"\\']?(.+?)[\"\\']?\\s*

Feature File Management Manage - a waterfall-style planning document combining structured requirements tracking with incremental development. Quick Reference See for complete field documentation. Proactive Usage This skill should be used automatically when features.yml exists. Before Starting Implementation 1. Check if exists in project root 2. If missing: do not use this skill proactively (stop here) 3. Plan the work in features.yml before writing code: - Add/update the feature with all requirements extracted from the user's request - Add anticipated test cases to (with ) - Document design d…

, line)\n if match:\n current_feature = match.group(1).strip()\n else:\n # Handle multiline feature names\n current_feature = line.split(\":\", 1)[1].strip().strip(\"\\\"'|\")\n if not current_feature:\n # Multiline - look at next non-empty line\n for j in range(i, min(i + 3, len(lines) + 1)):\n if (\n j \u003c len(lines)\n and lines[j - 1].strip()\n and not lines[j - 1].startswith(\" \")\n ):\n break\n if j \u003c len(lines) and lines[j - 1].strip():\n current_feature = lines[j - 1].strip()\n break\n\n # Don't forget the last feature\n if current_feature is not None:\n features.append((current_feature, start_line, len(lines)))\n\n return features\n\n\ndef get_last_modification(start_line: int, end_line: int) -> tuple[str, str] | None:\n \"\"\"\n Get the most recent commit that modified lines in the given range.\n Returns (commit_hash, date) or None.\n \"\"\"\n result = subprocess.run(\n [\n \"git\",\n \"blame\",\n \"-l\",\n f\"-L{start_line},{end_line}\",\n \"--date=short\",\n FEATURES_FILE,\n ],\n capture_output=True,\n text=True,\n )\n\n if result.returncode != 0:\n return None\n\n # Parse blame output to collect unique commits\n commits = set()\n for line in result.stdout.strip().split(\"\\n\"):\n if not line:\n continue\n # Format: ^?hash (author date line_no) content\n # Note: ^ prefix appears on boundary commits\n match = re.match(r\"^\\^?([a-f0-9]+)\", line)\n if match:\n commits.add(match.group(1))\n\n if not commits:\n return None\n\n # Use git log to find most recent commit among those that touched these lines\n # This properly handles commit ordering when dates are the same\n commit_list = list(commits)\n result = subprocess.run(\n [\"git\", \"log\", \"--format=%H %cs\", \"-1\", \"--\"] + commit_list,\n capture_output=True,\n text=True,\n )\n\n # Fallback: use git rev-list to find most recent among our commits\n result = subprocess.run(\n [\"git\", \"rev-list\", \"--no-walk\", \"--date-order\"] + commit_list,\n capture_output=True,\n text=True,\n )\n\n if result.returncode != 0 or not result.stdout.strip():\n return None\n\n # First line is most recent commit\n most_recent_hash = result.stdout.strip().split(\"\\n\")[0][:8]\n\n # Get its date\n result = subprocess.run(\n [\"git\", \"log\", \"-1\", \"--format=%cs\", most_recent_hash],\n capture_output=True,\n text=True,\n )\n\n if result.returncode != 0:\n return None\n\n date = result.stdout.strip()\n return (most_recent_hash, date)\n\n\ndef get_version_last_set(start_line: int, end_line: int) -> tuple[str, str, int] | None:\n \"\"\"\n Find when the version field was last changed for this feature.\n Returns (commit_hash, date, version) or None.\n \"\"\"\n # First, find the version line within this range\n with open(FEATURES_FILE) as f:\n lines = f.readlines()\n\n version_line = None\n version_value = None\n for i in range(start_line - 1, min(end_line, len(lines))):\n if lines[i].startswith(\"version:\"):\n version_line = i + 1 # 1-indexed\n match = re.match(r\"version:\\s*(\\d+)\", lines[i])\n if match:\n version_value = int(match.group(1))\n break\n\n if version_line is None:\n return None\n\n # Get blame for just the version line\n result = subprocess.run(\n [\n \"git\",\n \"blame\",\n \"-l\",\n f\"-L{version_line},{version_line}\",\n \"--date=short\",\n FEATURES_FILE,\n ],\n capture_output=True,\n text=True,\n )\n\n if result.returncode != 0:\n return None\n\n line = result.stdout.strip()\n # Note: ^ prefix appears on boundary commits\n match = re.match(r\"^\\^?([a-f0-9]+)\\s+.*?(\\d{4}-\\d{2}-\\d{2})\", line)\n if match:\n return (match.group(1)[:8], match.group(2), version_value or 1)\n\n return None\n\n\ndef format_plain(results: list[dict]) -> str:\n \"\"\"Format output as plain text.\"\"\"\n lines = []\n\n for r in results:\n lines.append(f\"{r['name']} (v{r['version']})\")\n if r.get(\"error\"):\n lines.append(f\" {r['error']}\")\n else:\n lines.append(\n f\" Last modified: {r['last_modified_date']} (commit {r['last_modified_commit']})\"\n )\n lines.append(\n f\" Version set: {r['version_set_date']} (commit {r['version_set_commit']})\"\n )\n lines.append(f\" Recommendation: {r['recommendation']}\")\n lines.append(\"\")\n\n return \"\\n\".join(lines).rstrip()\n\n\ndef format_markdown(results: list[dict]) -> str:\n \"\"\"Format output as markdown.\"\"\"\n lines = []\n lines.append(\"# Version Check\")\n\n for r in results:\n lines.append(\"\")\n lines.append(f\"## {r['name']} (v{r['version']})\")\n lines.append(\"\")\n if r.get(\"error\"):\n lines.append(f\"- {r['error']}\")\n else:\n lines.append(\n f\"- **Last modified**: {r['last_modified_date']} (commit `{r['last_modified_commit']}`)\"\n )\n lines.append(\n f\"- **Version set**: {r['version_set_date']} (commit `{r['version_set_commit']}`)\"\n )\n rec = r[\"recommendation\"]\n if \"BUMP\" in rec:\n lines.append(f\"- **Recommendation**: {rec}\")\n else:\n lines.append(f\"- Recommendation: {rec}\")\n\n return \"\\n\".join(lines)\n\n\ndef format_json(results: list[dict]) -> str:\n \"\"\"Format output as JSON.\"\"\"\n return json.dumps({\"features\": results}, indent=2)\n\n\ndef main():\n parser = argparse.ArgumentParser(\n description=\"Check if feature versions need bumping\"\n )\n parser.add_argument(\n \"--format\",\n choices=[\"plain\", \"markdown\", \"json\"],\n default=\"plain\",\n help=\"Output format (default: plain)\",\n )\n args = parser.parse_args()\n\n path = Path(FEATURES_FILE)\n features = load_features(path)\n\n if not features:\n print(\"No features found in features.yml\", file=sys.stderr)\n sys.exit(1)\n\n if not is_git_repo():\n print(\"Error: Not a git repository\", file=sys.stderr)\n sys.exit(1)\n\n if not get_file_in_git():\n print(f\"Error: {FEATURES_FILE} is not tracked by git\", file=sys.stderr)\n sys.exit(1)\n\n # Get line ranges for each feature\n line_ranges = get_feature_line_ranges(path)\n\n results = []\n for feature_name, start_line, end_line in line_ranges:\n version = 1\n # Find matching feature to get version\n for f in features:\n fname = f.get(\"feature\", \"\")\n if fname.strip().startswith(feature_name[:20]) or feature_name.startswith(\n fname[:20]\n ):\n version = f.get(\"version\", 1)\n break\n\n result = {\n \"name\": feature_name[:50] + (\"...\" if len(feature_name) > 50 else \"\"),\n \"version\": version,\n }\n\n last_mod = get_last_modification(start_line, end_line)\n version_info = get_version_last_set(start_line, end_line)\n\n if last_mod is None:\n result[\"error\"] = (\n \"Could not determine last modification (uncommitted changes?)\"\n )\n elif version_info is None:\n result[\"error\"] = \"Could not determine when version was set\"\n else:\n result[\"last_modified_commit\"] = last_mod[0]\n result[\"last_modified_date\"] = last_mod[1]\n result[\"version_set_commit\"] = version_info[0]\n result[\"version_set_date\"] = version_info[1]\n\n if last_mod[1] > version_info[1]:\n result[\"recommendation\"] = (\n \"BUMP VERSION - feature modified since version was set\"\n )\n result[\"needs_bump\"] = True\n elif last_mod[0] != version_info[0] and last_mod[1] == version_info[1]:\n result[\"recommendation\"] = (\n \"BUMP VERSION - feature modified since version was set\"\n )\n result[\"needs_bump\"] = True\n else:\n result[\"recommendation\"] = \"Up to date\"\n result[\"needs_bump\"] = False\n\n results.append(result)\n\n if args.format == \"plain\":\n print(format_plain(results))\n elif args.format == \"markdown\":\n print(format_markdown(results))\n else:\n print(format_json(results))\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":11501,"content_sha256":"aad31256ea31bd70bd8ec3dcd308e18738763275833537ffbcd523715ca43c4d"},{"filename":"scripts/extract-issues.py","content":"#!/usr/bin/env -S uv run --script\n# /// script\n# requires-python = \">=3.10\"\n# dependencies = [\"pyyaml\"]\n# ///\n\"\"\"\nExtract known issues across all features.\n\nUsage:\n ./extract-issues.py [--format plain|markdown|json]\n\"\"\"\n\nimport argparse\nimport json\nimport sys\nfrom pathlib import Path\n\nimport yaml\n\n\ndef load_features(path: Path) -> list[dict]:\n \"\"\"Load all feature documents from features.yml.\"\"\"\n if not path.exists():\n print(f\"Error: {path} not found\", file=sys.stderr)\n sys.exit(1)\n\n with open(path) as f:\n docs = list(yaml.safe_load_all(f))\n\n return [doc for doc in docs if doc is not None]\n\n\ndef format_plain(features: list[dict]) -> str:\n \"\"\"Format output as plain text.\"\"\"\n lines = []\n found_any = False\n\n for f in features:\n name = f.get(\"feature\", \"Unnamed\")\n version = f.get(\"version\", 1)\n phase = f.get(\"phase\", \"Unknown\")\n issues = f.get(\"known-issues\", [])\n\n if not issues:\n continue\n\n found_any = True\n lines.append(f\"{name} (v{version}, {phase})\")\n for issue in issues:\n if issue: # Skip empty strings\n lines.append(f\" - {issue}\")\n lines.append(\"\")\n\n if not found_any:\n return \"No known issues found.\"\n\n return \"\\n\".join(lines).rstrip()\n\n\ndef format_markdown(features: list[dict]) -> str:\n \"\"\"Format output as markdown.\"\"\"\n lines = []\n lines.append(\"# Known Issues\")\n\n found_any = False\n for f in features:\n name = f.get(\"feature\", \"Unnamed\")\n version = f.get(\"version\", 1)\n phase = f.get(\"phase\", \"Unknown\")\n issues = f.get(\"known-issues\", [])\n\n # Filter out empty issues\n issues = [i for i in issues if i]\n\n if not issues:\n continue\n\n found_any = True\n lines.append(\"\")\n lines.append(f\"## {name} (v{version}, {phase})\")\n lines.append(\"\")\n for issue in issues:\n lines.append(f\"- {issue}\")\n\n if not found_any:\n lines.append(\"\")\n lines.append(\"No known issues found.\")\n\n return \"\\n\".join(lines)\n\n\ndef format_json(features: list[dict]) -> str:\n \"\"\"Format output as JSON.\"\"\"\n output = {\"features\": []}\n\n for f in features:\n name = f.get(\"feature\", \"Unnamed\")\n version = f.get(\"version\", 1)\n phase = f.get(\"phase\", \"Unknown\")\n issues = f.get(\"known-issues\", [])\n\n # Filter out empty issues\n issues = [i for i in issues if i]\n\n if not issues:\n continue\n\n output[\"features\"].append(\n {\"name\": name, \"version\": version, \"phase\": phase, \"issues\": issues}\n )\n\n return json.dumps(output, indent=2)\n\n\ndef main():\n parser = argparse.ArgumentParser(\n description=\"Extract known issues across all features\"\n )\n parser.add_argument(\n \"--format\",\n choices=[\"plain\", \"markdown\", \"json\"],\n default=\"plain\",\n help=\"Output format (default: plain)\",\n )\n args = parser.parse_args()\n\n features = load_features(Path(\"features.yml\"))\n\n if not features:\n print(\"No features found in features.yml\", file=sys.stderr)\n sys.exit(1)\n\n if args.format == \"plain\":\n print(format_plain(features))\n elif args.format == \"markdown\":\n print(format_markdown(features))\n else:\n print(format_json(features))\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":3423,"content_sha256":"a0339c104921a52c8e7c8207ebf52f43f276e0771c25d2fc4326522238565be8"},{"filename":"scripts/extract-work.py","content":"#!/usr/bin/env -S uv run --script\n# /// script\n# requires-python = \">=3.10\"\n# dependencies = [\"pyyaml\"]\n# ///\n\"\"\"\nExtract requirements that need work (status != Complete).\n\nUsage:\n ./extract-work.py [--format plain|markdown|json] [--phase PHASE] [--status STATUS]\n\"\"\"\n\nimport argparse\nimport json\nimport sys\nfrom pathlib import Path\n\nimport yaml\n\n\nPHASES = [\"Requirements\", \"Design\", \"Implementation\", \"Testing\", \"Complete\"]\nSTATUSES = [\"Not-Started\", \"In-Progress\", \"Needs-Work\", \"Complete\"]\n\n\ndef load_features(path: Path) -> list[dict]:\n \"\"\"Load all feature documents from features.yml.\"\"\"\n if not path.exists():\n print(f\"Error: {path} not found\", file=sys.stderr)\n sys.exit(1)\n\n with open(path) as f:\n docs = list(yaml.safe_load_all(f))\n\n return [doc for doc in docs if doc is not None]\n\n\ndef get_incomplete_requirements(\n feature: dict, phase_filter: str | None, status_filter: str | None\n) -> list[dict]:\n \"\"\"Get requirements that are not Complete, with optional filters.\"\"\"\n requirements = feature.get(\"requirements\", {})\n feature_phase = feature.get(\"phase\", \"Requirements\")\n\n # Apply phase filter\n if phase_filter and feature_phase != phase_filter:\n return []\n\n incomplete = []\n for req_id, req in requirements.items():\n status = req.get(\"status\", \"Not-Started\")\n\n # Skip Complete unless explicitly filtered\n if status_filter:\n if status != status_filter:\n continue\n else:\n if status == \"Complete\":\n continue\n\n incomplete.append(\n {\n \"id\": req_id,\n \"description\": req.get(\"description\", \"\"),\n \"status\": status,\n \"tested_by\": req.get(\"tested-by\", []),\n }\n )\n\n return incomplete\n\n\ndef format_plain(\n features: list[dict], phase_filter: str | None, status_filter: str | None\n) -> str:\n \"\"\"Format output as plain text.\"\"\"\n lines = []\n\n for f in features:\n name = f.get(\"feature\", \"Unnamed\")\n phase = f.get(\"phase\", \"Unknown\")\n incomplete = get_incomplete_requirements(f, phase_filter, status_filter)\n\n if not incomplete:\n continue\n\n lines.append(f\"{name} ({phase})\")\n for req in incomplete:\n desc = req[\"description\"]\n if len(desc) > 60:\n desc = desc[:57] + \"...\"\n lines.append(f\" - {req['id']} [{req['status']}]: {desc}\")\n lines.append(\"\")\n\n if not lines:\n return \"No incomplete requirements found.\"\n\n return \"\\n\".join(lines).rstrip()\n\n\ndef format_markdown(\n features: list[dict], phase_filter: str | None, status_filter: str | None\n) -> str:\n \"\"\"Format output as markdown.\"\"\"\n lines = []\n lines.append(\"# Work Remaining\")\n\n found_any = False\n for f in features:\n name = f.get(\"feature\", \"Unnamed\")\n phase = f.get(\"phase\", \"Unknown\")\n incomplete = get_incomplete_requirements(f, phase_filter, status_filter)\n\n if not incomplete:\n continue\n\n found_any = True\n lines.append(\"\")\n lines.append(f\"## {name} ({phase})\")\n lines.append(\"\")\n for req in incomplete:\n status_badge = f\"`{req['status']}`\"\n lines.append(f\"- [ ] **{req['id']}** {status_badge}\")\n lines.append(f\" - {req['description']}\")\n if req[\"tested_by\"]:\n lines.append(f\" - Tests: {', '.join(req['tested_by'])}\")\n\n if not found_any:\n lines.append(\"\")\n lines.append(\"No incomplete requirements found.\")\n\n return \"\\n\".join(lines)\n\n\ndef format_json(\n features: list[dict], phase_filter: str | None, status_filter: str | None\n) -> str:\n \"\"\"Format output as JSON.\"\"\"\n output = {\"features\": []}\n\n for f in features:\n name = f.get(\"feature\", \"Unnamed\")\n phase = f.get(\"phase\", \"Unknown\")\n incomplete = get_incomplete_requirements(f, phase_filter, status_filter)\n\n if not incomplete:\n continue\n\n output[\"features\"].append(\n {\"name\": name, \"phase\": phase, \"incomplete_requirements\": incomplete}\n )\n\n return json.dumps(output, indent=2)\n\n\ndef main():\n parser = argparse.ArgumentParser(description=\"Extract requirements needing work\")\n parser.add_argument(\n \"--format\",\n choices=[\"plain\", \"markdown\", \"json\"],\n default=\"plain\",\n help=\"Output format (default: plain)\",\n )\n parser.add_argument(\n \"--phase\", choices=PHASES, default=None, help=\"Filter to specific phase\"\n )\n parser.add_argument(\n \"--status\",\n choices=STATUSES,\n default=None,\n help=\"Filter to specific status (default: all except Complete)\",\n )\n args = parser.parse_args()\n\n features = load_features(Path(\"features.yml\"))\n\n if not features:\n print(\"No features found in features.yml\", file=sys.stderr)\n sys.exit(1)\n\n if args.format == \"plain\":\n print(format_plain(features, args.phase, args.status))\n elif args.format == \"markdown\":\n print(format_markdown(features, args.phase, args.status))\n else:\n print(format_json(features, args.phase, args.status))\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":5278,"content_sha256":"e46101b6a34c58c1882e77b0a7cbcf24eef961f1a0f9f09bf337627aaf0d8e85"},{"filename":"scripts/feature-status.py","content":"#!/usr/bin/env -S uv run --script\n# /// script\n# requires-python = \">=3.10\"\n# dependencies = [\"pyyaml\"]\n# ///\n\"\"\"\nFeature status overview with validation and test breakdown.\n\nUsage:\n ./feature-status.py [--format plain|markdown|json] [--validate]\n\"\"\"\n\nimport argparse\nimport json\nimport sys\nfrom pathlib import Path\n\nimport yaml\n\n\nPHASES = [\"Requirements\", \"Design\", \"Implementation\", \"Testing\", \"Complete\"]\nSTATUSES = [\"Not-Started\", \"In-Progress\", \"Needs-Work\", \"Complete\"]\n\n\ndef load_features(path: Path) -> list[dict]:\n \"\"\"Load all feature documents from features.yml.\"\"\"\n if not path.exists():\n print(f\"Error: {path} not found\", file=sys.stderr)\n sys.exit(1)\n\n with open(path) as f:\n docs = list(yaml.safe_load_all(f))\n\n return [doc for doc in docs if doc is not None]\n\n\ndef validate_feature(feature: dict) -> list[str]:\n \"\"\"Validate phase transition rules for a feature. Returns list of errors.\"\"\"\n errors = []\n phase = feature.get(\"phase\", \"Requirements\")\n requirements = feature.get(\"requirements\", {})\n decisions = feature.get(\"decisions\")\n test_cases = feature.get(\"test-cases\", {})\n\n phase_idx = PHASES.index(phase) if phase in PHASES else 0\n\n # Requirements -> Design: all requirements need descriptions\n if phase_idx >= PHASES.index(\"Design\"):\n for req_id, req in requirements.items():\n desc = req.get(\"description\", \"\")\n if not desc or not desc.strip():\n errors.append(\n f\"{req_id} missing description (required for Design phase)\"\n )\n\n # Design -> Implementation: need at least one decision or explicit empty list\n if phase_idx >= PHASES.index(\"Implementation\"):\n if decisions is None:\n errors.append(\n \"No decisions field (required for Implementation phase, use [] if none needed)\"\n )\n\n # Implementation -> Testing: all requirements must be In-Progress or Complete\n if phase_idx >= PHASES.index(\"Testing\"):\n for req_id, req in requirements.items():\n status = req.get(\"status\", \"Not-Started\")\n if status == \"Not-Started\":\n errors.append(\n f\"{req_id} is Not-Started (must be In-Progress or Complete for Testing phase)\"\n )\n\n # Testing -> Complete: all requirements Complete and all tests passing\n if phase_idx >= PHASES.index(\"Complete\"):\n for req_id, req in requirements.items():\n status = req.get(\"status\", \"Not-Started\")\n if status != \"Complete\":\n errors.append(\n f\"{req_id} is {status} (must be Complete for Complete phase)\"\n )\n\n for test_id, test in test_cases.items():\n if not test.get(\"passing\", False):\n errors.append(\n f\"{test_id} is failing (all tests must pass for Complete phase)\"\n )\n\n return errors\n\n\ndef get_failing_tests(feature: dict) -> list[dict]:\n \"\"\"Get list of failing tests with their details.\"\"\"\n failing = []\n test_cases = feature.get(\"test-cases\", {})\n requirements = feature.get(\"requirements\", {})\n\n for test_id, test in test_cases.items():\n if not test.get(\"passing\", False):\n # Find which requirements this test covers\n covers = []\n for req_id, req in requirements.items():\n if test_id in req.get(\"tested-by\", []):\n covers.append(req_id)\n\n # Get test types\n raw_types = test.get(\"type\")\n if raw_types is None:\n types = None\n elif isinstance(raw_types, str):\n types = [raw_types] if raw_types else None\n elif isinstance(raw_types, list):\n types = raw_types if raw_types else None\n else:\n types = None\n\n failing.append(\n {\n \"id\": test_id,\n \"name\": test.get(\"name\", \"\"),\n \"file\": test.get(\"file\", \"\"),\n \"covers\": covers,\n \"type\": types,\n }\n )\n\n return failing\n\n\ndef get_test_stats(feature: dict) -> dict:\n \"\"\"\n Get test statistics with optional breakdown by type.\n\n Returns {\n \"passing\": int,\n \"total\": int,\n \"by_type\": {type_name: {\"passing\": int, \"total\": int}, ...} | None\n }\n\n by_type is None if no tests have a type field.\n Types are normalized to lowercase, with \"Uncategorized\" for tests without types.\n \"\"\"\n test_cases = feature.get(\"test-cases\", {})\n\n total = len(test_cases)\n passing = sum(1 for t in test_cases.values() if t.get(\"passing\", False))\n\n # Check if any test has a type\n any_typed = any(\"type\" in t for t in test_cases.values())\n\n if not any_typed:\n return {\"passing\": passing, \"total\": total, \"by_type\": None}\n\n by_type = {}\n for test in test_cases.values():\n test_passing = test.get(\"passing\", False)\n raw_types = test.get(\"type\")\n\n # Normalize: string -> list, empty/missing -> [\"Uncategorized\"]\n if raw_types is None:\n types = [\"Uncategorized\"]\n elif isinstance(raw_types, str):\n types = [raw_types.lower()] if raw_types else [\"Uncategorized\"]\n elif isinstance(raw_types, list):\n types = (\n [t.lower() for t in raw_types if t] if raw_types else [\"Uncategorized\"]\n )\n if not types:\n types = [\"Uncategorized\"]\n else:\n types = [\"Uncategorized\"]\n\n for t in types:\n # Normalize \"uncategorized\" to title case for display\n display_type = \"Uncategorized\" if t.lower() == \"uncategorized\" else t\n if display_type not in by_type:\n by_type[display_type] = {\"passing\": 0, \"total\": 0}\n by_type[display_type][\"total\"] += 1\n if test_passing:\n by_type[display_type][\"passing\"] += 1\n\n return {\"passing\": passing, \"total\": total, \"by_type\": by_type}\n\n\ndef format_tests_column(stats: dict) -> str:\n \"\"\"Format the Tests column value with optional type breakdown.\"\"\"\n base = f\"{stats['passing']}/{stats['total']}\"\n\n if stats[\"by_type\"] is None:\n return base\n\n # Sort alphabetically, \"Uncategorized\" last\n types = sorted(\n stats[\"by_type\"].keys(), key=lambda t: (t == \"Uncategorized\", t.lower())\n )\n parts = [\n f\"{t}: {stats['by_type'][t]['passing']}/{stats['by_type'][t]['total']}\"\n for t in types\n ]\n\n return f\"{base} ({', '.join(parts)})\"\n\n\ndef get_progress(feature: dict) -> tuple[int, int]:\n \"\"\"Get (complete_count, total_count) for requirements.\"\"\"\n requirements = feature.get(\"requirements\", {})\n total = len(requirements)\n complete = sum(1 for r in requirements.values() if r.get(\"status\") == \"Complete\")\n return complete, total\n\n\ndef format_plain(\n features: list[dict], all_errors: dict, all_failing: dict, all_test_stats: dict\n) -> str:\n \"\"\"Format output as plain text.\"\"\"\n lines = []\n\n # Header\n lines.append(\n \"Feature Phase Progress Tests\"\n )\n lines.append(\"-\" * 78)\n\n # Feature rows\n for f in features:\n name = f.get(\"feature\", \"Unnamed\")\n display_name = name[:40]\n phase = f.get(\"phase\", \"Unknown\")\n complete, total = get_progress(f)\n tests_col = format_tests_column(\n all_test_stats.get(name, {\"passing\": 0, \"total\": 0, \"by_type\": None})\n )\n\n lines.append(f\"{display_name:\u003c42}{phase:\u003c16}{complete}/{total:\u003c10}{tests_col}\")\n\n # Failing tests section\n has_failing = any(all_failing.values())\n if has_failing:\n lines.append(\"\")\n lines.append(\"Failing Tests:\")\n for feature_name, tests in all_failing.items():\n if tests:\n lines.append(f\" {feature_name}:\")\n for t in tests:\n type_str = \"\"\n if t.get(\"type\"):\n type_str = f\" [{', '.join(t['type'])}]\"\n lines.append(\n f\" - {t['id']}{type_str} ({t['file']}::{t['name']})\"\n )\n if t[\"covers\"]:\n lines.append(f\" Covers: {', '.join(t['covers'])}\")\n\n # Validation errors section\n has_errors = any(all_errors.values())\n if has_errors:\n lines.append(\"\")\n lines.append(\"Validation Errors:\")\n for feature_name, errors in all_errors.items():\n if errors:\n lines.append(f\" {feature_name}:\")\n for e in errors:\n lines.append(f\" - {e}\")\n\n return \"\\n\".join(lines)\n\n\ndef format_markdown(\n features: list[dict], all_errors: dict, all_failing: dict, all_test_stats: dict\n) -> str:\n \"\"\"Format output as markdown.\"\"\"\n lines = []\n\n lines.append(\"# Feature Status\")\n lines.append(\"\")\n lines.append(\"| Feature | Phase | Progress | Tests |\")\n lines.append(\"|---------|-------|----------|-------|\")\n\n for f in features:\n name = f.get(\"feature\", \"Unnamed\")\n phase = f.get(\"phase\", \"Unknown\")\n complete, total = get_progress(f)\n tests_col = format_tests_column(\n all_test_stats.get(name, {\"passing\": 0, \"total\": 0, \"by_type\": None})\n )\n\n lines.append(f\"| {name} | {phase} | {complete}/{total} | {tests_col} |\")\n\n # Failing tests section\n has_failing = any(all_failing.values())\n if has_failing:\n lines.append(\"\")\n lines.append(\"## Failing Tests\")\n for feature_name, tests in all_failing.items():\n if tests:\n lines.append(\"\")\n lines.append(f\"### {feature_name}\")\n for t in tests:\n type_str = \"\"\n if t.get(\"type\"):\n type_str = f\" [{', '.join(t['type'])}]\"\n lines.append(\n f\"- **{t['id']}**{type_str} (`{t['file']}::{t['name']}`)\"\n )\n if t[\"covers\"]:\n lines.append(f\" - Covers: {', '.join(t['covers'])}\")\n\n # Validation errors section\n has_errors = any(all_errors.values())\n if has_errors:\n lines.append(\"\")\n lines.append(\"## Validation Errors\")\n for feature_name, errors in all_errors.items():\n if errors:\n lines.append(\"\")\n lines.append(f\"### {feature_name}\")\n for e in errors:\n lines.append(f\"- {e}\")\n\n return \"\\n\".join(lines)\n\n\ndef format_json(\n features: list[dict], all_errors: dict, all_failing: dict, all_test_stats: dict\n) -> str:\n \"\"\"Format output as JSON.\"\"\"\n output = {\n \"features\": [],\n \"failing_tests\": all_failing,\n \"validation_errors\": all_errors,\n }\n\n for f in features:\n name = f.get(\"feature\", \"Unnamed\")\n complete, total = get_progress(f)\n stats = all_test_stats.get(name, {\"passing\": 0, \"total\": 0, \"by_type\": None})\n\n output[\"features\"].append(\n {\n \"name\": name,\n \"phase\": f.get(\"phase\", \"Unknown\"),\n \"version\": f.get(\"version\", 1),\n \"requirements_complete\": complete,\n \"requirements_total\": total,\n \"tests_passing\": stats[\"passing\"],\n \"tests_total\": stats[\"total\"],\n \"tests_by_type\": stats[\"by_type\"],\n }\n )\n\n return json.dumps(output, indent=2)\n\n\ndef main():\n parser = argparse.ArgumentParser(description=\"Show feature status overview\")\n parser.add_argument(\n \"--format\",\n choices=[\"plain\", \"markdown\", \"json\"],\n default=\"plain\",\n help=\"Output format (default: plain)\",\n )\n parser.add_argument(\n \"--validate\",\n action=\"store_true\",\n help=\"Exit with code 1 if validation errors exist\",\n )\n args = parser.parse_args()\n\n features = load_features(Path(\"features.yml\"))\n\n if not features:\n print(\"No features found in features.yml\", file=sys.stderr)\n sys.exit(1)\n\n # Collect errors, failing tests, and test stats per feature\n all_errors = {}\n all_failing = {}\n all_test_stats = {}\n\n for f in features:\n name = f.get(\"feature\", \"Unnamed\")\n all_errors[name] = validate_feature(f)\n all_failing[name] = get_failing_tests(f)\n all_test_stats[name] = get_test_stats(f)\n\n # Format and output\n if args.format == \"plain\":\n print(format_plain(features, all_errors, all_failing, all_test_stats))\n elif args.format == \"markdown\":\n print(format_markdown(features, all_errors, all_failing, all_test_stats))\n else:\n print(format_json(features, all_errors, all_failing, all_test_stats))\n\n # Exit with error if validation requested and errors found\n if args.validate and any(all_errors.values()):\n sys.exit(1)\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":13104,"content_sha256":"c12b1e0dd62a9ff5b0bcfdaf6eaf420639ba74a0b4f0b1e8824f936c44ebcf14"},{"filename":"skill-report.json","content":"{\n \"schema_version\": \"2.0\",\n \"meta\": {\n \"generated_at\": \"2026-01-16T19:40:59.475Z\",\n \"slug\": \"chemiseblanc-feature-file\",\n \"source_url\": \"https://github.com/Chemiseblanc/ai/tree/main/skill/feature-file\",\n \"source_ref\": \"main\",\n \"model\": \"claude\",\n \"analysis_version\": \"3.0.0\",\n \"source_type\": \"community\",\n \"content_hash\": \"073a4a6eeee5df38af4792115601ccba8e9bf47042d353f8deb8a6bc320d64cc\",\n \"tree_hash\": \"88358655dfba3cebfc9a60d1df3456013cd94f04d85015db7e3c2d9e9dddf0b7\"\n },\n \"skill\": {\n \"name\": \"feature-file\",\n \"description\": \"Manage features.yml for tracking requirements and progress; use proactively ONLY when features.yml already exists, or invoke manually to create one; complements TodoWrite for persistent project state.\",\n \"summary\": \"Manage features.yml for tracking requirements and progress; use proactively ONLY when features.yml a...\",\n \"icon\": \"📋\",\n \"version\": \"1.0.0\",\n \"author\": \"Chemiseblanc\",\n \"license\": \"MIT\",\n \"category\": \"coding\",\n \"tags\": [\n \"project-management\",\n \"requirements\",\n \"testing\",\n \"feature-tracking\"\n ],\n \"supported_tools\": [\n \"claude\",\n \"codex\",\n \"claude-code\"\n ],\n \"risk_factors\": [\n \"external_commands\",\n \"network\"\n ]\n },\n \"security_audit\": {\n \"risk_level\": \"safe\",\n \"is_blocked\": false,\n \"safe_to_publish\": true,\n \"summary\": \"This skill is a legitimate project management tool for tracking feature requirements and progress. All 252 static findings are FALSE POSITIVES. The external command detections are markdown documentation examples (not code execution). Weak cryptographic algorithm flags are YAML documentation examples showing recommended practices like bcrypt/JWT. System reconnaissance flags are standard git operations for version control. No network calls, credential access, or malicious code execution exists. All scripts use safe YAML parsing and subprocess with hardcoded git commands.\",\n \"risk_factor_evidence\": [\n {\n \"factor\": \"external_commands\",\n \"evidence\": [\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 3,\n \"line_end\": 3\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 7,\n \"line_end\": 7\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 9,\n \"line_end\": 24\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 24,\n \"line_end\": 28\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 28,\n \"line_end\": 31\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 31,\n \"line_end\": 31\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 31,\n \"line_end\": 33\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 33,\n \"line_end\": 35\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 35,\n \"line_end\": 35\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 35,\n \"line_end\": 35\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 35,\n \"line_end\": 35\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 35,\n \"line_end\": 35\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 35,\n \"line_end\": 38\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 38,\n \"line_end\": 41\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 41,\n \"line_end\": 41\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 41,\n \"line_end\": 41\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 41,\n \"line_end\": 43\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 43,\n \"line_end\": 47\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 47,\n \"line_end\": 62\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 62,\n \"line_end\": 64\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 64,\n \"line_end\": 68\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 68,\n \"line_end\": 72\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 72,\n \"line_end\": 74\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 74,\n \"line_end\": 78\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 78,\n \"line_end\": 82\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 82,\n \"line_end\": 84\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 84,\n \"line_end\": 87\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 87,\n \"line_end\": 87\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 87,\n \"line_end\": 89\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 89,\n \"line_end\": 92\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 92,\n \"line_end\": 92\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 92,\n \"line_end\": 96\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 96,\n \"line_end\": 98\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 98,\n \"line_end\": 106\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 106,\n \"line_end\": 108\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 108,\n \"line_end\": 110\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 110,\n \"line_end\": 110\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 110,\n \"line_end\": 110\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 110,\n \"line_end\": 110\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 110,\n \"line_end\": 113\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 113,\n \"line_end\": 116\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 116,\n \"line_end\": 120\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 120,\n \"line_end\": 122\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 122,\n \"line_end\": 125\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 125,\n \"line_end\": 127\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 127,\n \"line_end\": 130\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 130,\n \"line_end\": 132\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 132,\n \"line_end\": 135\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 135,\n \"line_end\": 137\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 137,\n \"line_end\": 139\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 139,\n \"line_end\": 139\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 139,\n \"line_end\": 142\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 142,\n \"line_end\": 146\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 146,\n \"line_end\": 147\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 147,\n \"line_end\": 148\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 148,\n \"line_end\": 158\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 158,\n \"line_end\": 159\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 159,\n \"line_end\": 159\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 159,\n \"line_end\": 160\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 160,\n \"line_end\": 160\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 160,\n \"line_end\": 161\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 161,\n \"line_end\": 161\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 161,\n \"line_end\": 167\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 167,\n \"line_end\": 168\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 168,\n \"line_end\": 169\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 169,\n \"line_end\": 170\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 170,\n \"line_end\": 174\n },\n {\n \"file\": \"references/schema.md\",\n \"line_start\": 174,\n \"line_end\": 268\n },\n {\n \"file\": \"scripts/check-version.py\",\n \"line_start\": 42,\n \"line_end\": 42\n },\n {\n \"file\": \"scripts/check-version.py\",\n \"line_start\": 50,\n \"line_end\": 50\n },\n {\n \"file\": \"scripts/check-version.py\",\n \"line_start\": 120,\n \"line_end\": 120\n },\n {\n \"file\": \"scripts/check-version.py\",\n \"line_start\": 153,\n \"line_end\": 153\n },\n {\n \"file\": \"scripts/check-version.py\",\n \"line_start\": 160,\n \"line_end\": 160\n },\n {\n \"file\": \"scripts/check-version.py\",\n \"line_start\": 173,\n \"line_end\": 173\n },\n {\n \"file\": \"scripts/check-version.py\",\n \"line_start\": 209,\n \"line_end\": 209\n },\n {\n \"file\": \"scripts/check-version.py\",\n \"line_start\": 268,\n \"line_end\": 268\n },\n {\n \"file\": \"scripts/check-version.py\",\n \"line_start\": 271,\n \"line_end\": 271\n },\n {\n \"file\": \"scripts/extract-work.py\",\n \"line_start\": 121,\n \"line_end\": 121\n },\n {\n \"file\": \"scripts/feature-status.py\",\n \"line_start\": 301,\n \"line_end\": 301\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 8,\n \"line_end\": 8\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 12,\n \"line_end\": 38\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 38,\n \"line_end\": 40\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 40,\n \"line_end\": 48\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 48,\n \"line_end\": 52\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 52,\n \"line_end\": 52\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 52,\n \"line_end\": 53\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 53,\n \"line_end\": 54\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 54,\n \"line_end\": 58\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 58,\n \"line_end\": 58\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 58,\n \"line_end\": 59\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 59,\n \"line_end\": 60\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 60,\n \"line_end\": 61\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 61,\n \"line_end\": 65\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 65,\n \"line_end\": 66\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 66,\n \"line_end\": 83\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 83,\n \"line_end\": 83\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 83,\n \"line_end\": 84\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 84,\n \"line_end\": 84\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 84,\n \"line_end\": 85\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 85,\n \"line_end\": 85\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 85,\n \"line_end\": 93\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 93,\n \"line_end\": 95\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 95,\n \"line_end\": 96\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 96,\n \"line_end\": 97\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 97,\n \"line_end\": 98\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 98,\n \"line_end\": 103\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 103,\n \"line_end\": 104\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 104,\n \"line_end\": 116\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 116,\n \"line_end\": 117\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 117,\n \"line_end\": 121\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 121,\n \"line_end\": 133\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 133,\n \"line_end\": 139\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 139,\n \"line_end\": 141\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 141,\n \"line_end\": 142\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 142,\n \"line_end\": 143\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 143,\n \"line_end\": 144\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 144,\n \"line_end\": 145\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 145,\n \"line_end\": 151\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 151,\n \"line_end\": 152\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 152,\n \"line_end\": 154\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 154,\n \"line_end\": 154\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 154,\n \"line_end\": 155\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 155,\n \"line_end\": 156\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 156,\n \"line_end\": 157\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 157,\n \"line_end\": 162\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 162,\n \"line_end\": 164\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 164,\n \"line_end\": 166\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 166,\n \"line_end\": 174\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 174,\n \"line_end\": 179\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 179,\n \"line_end\": 179\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 179,\n \"line_end\": 185\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 185,\n \"line_end\": 189\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 189,\n \"line_end\": 195\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 195,\n \"line_end\": 200\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 200,\n \"line_end\": 206\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 206,\n \"line_end\": 209\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 209,\n \"line_end\": 215\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 215,\n \"line_end\": 217\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 217,\n \"line_end\": 219\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 219,\n \"line_end\": 225\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 225,\n \"line_end\": 226\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 226,\n \"line_end\": 237\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 237,\n \"line_end\": 238\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 238,\n \"line_end\": 239\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 239,\n \"line_end\": 245\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 245,\n \"line_end\": 246\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 246,\n \"line_end\": 247\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 247,\n \"line_end\": 247\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 247,\n \"line_end\": 249\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 249,\n \"line_end\": 250\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 250,\n \"line_end\": 251\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 251,\n \"line_end\": 258\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 258,\n \"line_end\": 260\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 260,\n \"line_end\": 261\n },\n {\n \"file\": \"SKILL.md\",\n \"line_start\": 261,\n \"line_end\": 269\n }\n ]\n },\n {\n \"factor\": \"network\",\n \"evidence\": [\n {\n \"file\": \"skill-report.json\",\n \"line_start\": 6,\n \"line_end\": 6\n }\n ]\n }\n ],\n \"critical_findings\": [],\n \"high_findings\": [],\n \"medium_findings\": [],\n \"low_findings\": [],\n \"dangerous_patterns\": [],\n \"files_scanned\": 7,\n \"total_lines\": 1868,\n \"audit_model\": \"claude\",\n \"audited_at\": \"2026-01-16T19:40:59.475Z\"\n },\n \"content\": {\n \"user_title\": \"Track Software Features with YAML Requirements Management\",\n \"value_statement\": \"Software projects need structured requirement tracking beyond simple todo lists. This skill provides waterfall-style feature management with requirements, test cases, and progress tracking in a persistent YAML format stored in your repository.\",\n \"seo_keywords\": [\n \"feature tracking\",\n \"requirements management\",\n \"test case tracking\",\n \"Claude Code\",\n \"YAML project management\",\n \"software development\",\n \"project planning\",\n \"feature status\",\n \"Claude\",\n \"Codex\"\n ],\n \"actual_capabilities\": [\n \"Create and manage features.yml files with structured requirements\",\n \"Track feature phases from Requirements to Complete\",\n \"Link test cases to requirements with passing/failing status\",\n \"Generate reports on work remaining, issues, and feature status\",\n \"Validate phase transitions and requirement completeness\",\n \"Version features with changelog tracking\"\n ],\n \"limitations\": [\n \"Requires features.yml file to exist for proactive usage\",\n \"Waterfall methodology may not suit all project types\",\n \"No integration with external project management tools\",\n \"Manual status updates required during development\"\n ],\n \"use_cases\": [\n {\n \"target_user\": \"Software developers\",\n \"title\": \"Track implementation progress\",\n \"description\": \"Manage feature requirements and mark them complete as you implement functionality, with linked test cases for verification.\"\n },\n {\n \"target_user\": \"Project managers\",\n \"title\": \"Monitor feature development\",\n \"description\": \"Get overview of all features, their phases, progress, and failing tests to track project status.\"\n },\n {\n \"target_user\": \"QA engineers\",\n \"title\": \"Manage test coverage\",\n \"description\": \"Link test cases to requirements and track which tests are passing or failing for each feature.\"\n }\n ],\n \"prompt_templates\": [\n {\n \"title\": \"Create new feature\",\n \"scenario\": \"Starting a new feature implementation\",\n \"prompt\": \"Create a features.yml file for implementing user authentication with requirements for login, logout, and password reset functionality.\"\n },\n {\n \"title\": \"Update status\",\n \"scenario\": \"After completing a requirement\",\n \"prompt\": \"Update the user authentication feature in features.yml to mark the login requirement as Complete and add a test case for it.\"\n },\n {\n \"title\": \"View work remaining\",\n \"scenario\": \"Planning next development tasks\",\n \"prompt\": \"Show me all incomplete requirements across all features in features.yml, filtered to Implementation phase.\"\n },\n {\n \"title\": \"Check feature status\",\n \"scenario\": \"Weekly project review\",\n \"prompt\": \"Generate a markdown report of all features showing their phases, progress, test status, and any validation errors.\"\n }\n ],\n \"output_examples\": [\n {\n \"input\": \"Show the status of all features\",\n \"output\": [\n \"Feature Status Overview:\",\n \"\",\n \"User Authentication System (Implementation) - 3/5 requirements complete\",\n \"Tests: 8/10 passing (unit: 6/7, integration: 2/3)\",\n \"\",\n \"API Rate Limiting (Design) - 0/2 requirements complete\",\n \"Tests: 0/0 passing\",\n \"\",\n \"Validation Errors:\",\n \" User Authentication System:\",\n \" - req-auth-oauth is In-Progress (must be Complete for Complete phase)\"\n ]\n },\n {\n \"input\": \"What work is remaining?\",\n \"output\": [\n \"Work Remaining:\",\n \"\",\n \"User Authentication System (Implementation)\",\n \" - req-auth-oauth [In-Progress]: The system SHALL support OAuth2 authentication\",\n \" - req-auth-logout [Needs-Work]: When the user clicks logout, the system SHALL invalidate\",\n \"\",\n \"Payment Processing (Requirements)\",\n \" - req-pay-submit [Not-Started]: When user submits payment, system SHALL validate\"\n ]\n }\n ],\n \"best_practices\": [\n \"Use EARS syntax for requirements: When X, the system SHALL Y\",\n \"Update status immediately after completing work to keep file in sync\",\n \"Document design decisions in the decisions field before implementation\",\n \"Link every requirement to at least one test case for traceability\"\n ],\n \"anti_patterns\": [\n \"Creating features.yml after starting implementation instead of planning first\",\n \"Leaving requirements in Not-Started status when actively working on them\",\n \"Forgetting to bump version when adding or modifying requirements\",\n \"Not documenting known issues that could affect other developers\"\n ],\n \"faq\": [\n {\n \"question\": \"When should I use this skill versus TodoWrite?\",\n \"answer\": \"Use TodoWrite for immediate session tasks and features.yml for persistent project requirements that span multiple sessions.\"\n },\n {\n \"question\": \"What happens if features.yml does not exist?\",\n \"answer\": \"The skill will not activate proactively. You must manually create features.yml or request the skill to create one.\"\n },\n {\n \"question\": \"Can I integrate this with external tools?\",\n \"answer\": \"The skill works independently with YAML files. You can manually sync data to other tools as needed.\"\n },\n {\n \"question\": \"How do I handle requirement changes?\",\n \"answer\": \"Document change rationale, update requirement, increment version, and add changelog entry under appropriate section.\"\n },\n {\n \"question\": \"What if my project uses agile instead of waterfall?\",\n \"answer\": \"Adapt phases to your workflow. The tool is flexible - use phases that match your development process.\"\n },\n {\n \"question\": \"How do I track bugs versus requirements?\",\n \"answer\": \"Add bugs to the known-issues field. Requirements represent planned features while known-issues track defects.\"\n }\n ]\n },\n \"file_structure\": [\n {\n \"name\": \"references\",\n \"type\": \"dir\",\n \"path\": \"references\",\n \"children\": [\n {\n \"name\": \"schema.md\",\n \"type\": \"file\",\n \"path\": \"references/schema.md\",\n \"lines\": 269\n }\n ]\n },\n {\n \"name\": \"scripts\",\n \"type\": \"dir\",\n \"path\": \"scripts\",\n \"children\": [\n {\n \"name\": \"check-version.py\",\n \"type\": \"file\",\n \"path\": \"scripts/check-version.py\",\n \"lines\": 375\n },\n {\n \"name\": \"extract-issues.py\",\n \"type\": \"file\",\n \"path\": \"scripts/extract-issues.py\",\n \"lines\": 143\n },\n {\n \"name\": \"extract-work.py\",\n \"type\": \"file\",\n \"path\": \"scripts/extract-work.py\",\n \"lines\": 190\n },\n {\n \"name\": \"feature-status.py\",\n \"type\": \"file\",\n \"path\": \"scripts/feature-status.py\",\n \"lines\": 399\n }\n ]\n },\n {\n \"name\": \"SKILL.md\",\n \"type\": \"file\",\n \"path\": \"SKILL.md\",\n \"lines\": 270\n }\n ]\n}\n","content_type":"application/json; charset=utf-8","language":"json","size":28797,"content_sha256":"bbe1bdaf1371764bb9c7f967337b724efc7ce5a21a0190ab4f1c72e5956355e7"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Feature File Management","type":"text"}]},{"type":"paragraph","content":[{"text":"Manage ","type":"text"},{"text":"features.yml","type":"text","marks":[{"type":"code_inline"}]},{"text":" - a waterfall-style planning document combining structured requirements tracking with incremental development.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Quick Reference","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"yaml"},"content":[{"text":"feature: \"Feature Name\"\nphase: Requirements | Design | Implementation | Testing | Complete\nversion: 1\nchangelog: |\n ## [1]\n ### Added\n - Initial feature\ndecisions:\n - Decision rationale\nknown-issues:\n - Known bug or limitation\nrequirements:\n req-id:\n description: \"When X, the system SHALL Y\"\n status: Not-Started | In-Progress | Needs-Work | Complete\n tested-by: [test-id]\ntest-cases:\n test-id:\n name: \"test_function_name\"\n file: \"tests/test_file.py\"\n description: \"Given X, when Y, then Z\"\n passing: true | false\n type: unit | [integration, rainy-day] # optional, string or list\n---\n# Next feature...","type":"text"}]},{"type":"paragraph","content":[{"text":"See ","type":"text"},{"text":"references/schema.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for complete field documentation.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Proactive Usage","type":"text"}]},{"type":"paragraph","content":[{"text":"This skill should be used automatically when features.yml exists.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Before Starting Implementation","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check if ","type":"text"},{"text":"features.yml","type":"text","marks":[{"type":"code_inline"}]},{"text":" exists in project root","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If missing: do not use this skill proactively (stop here)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Plan the work in features.yml before writing code:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Add/update the feature with all requirements extracted from the user's request","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Add anticipated test cases to ","type":"text"},{"text":"test-cases","type":"text","marks":[{"type":"code_inline"}]},{"text":" (with ","type":"text"},{"text":"passing: false","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Document design decisions in ","type":"text"},{"text":"decisions","type":"text","marks":[{"type":"code_inline"}]},{"text":" if non-trivial choices are involved","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Set the first requirement to ","type":"text"},{"text":"status: In-Progress","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"During Implementation","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Update ","type":"text"},{"text":"status","type":"text","marks":[{"type":"code_inline"}]},{"text":" to ","type":"text"},{"text":"Complete","type":"text","marks":[{"type":"code_inline"}]},{"text":" as requirements are finished","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Add test cases to ","type":"text"},{"text":"test-cases","type":"text","marks":[{"type":"code_inline"}]},{"text":" when writing tests","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Update ","type":"text"},{"text":"passing","type":"text","marks":[{"type":"code_inline"}]},{"text":" field after running tests","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Add discovered issues to ","type":"text"},{"text":"known-issues","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"After Completing Work","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Verify all implemented requirements are marked ","type":"text"},{"text":"Complete","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Run ","type":"text"},{"text":"./scripts/feature-status.py --validate","type":"text","marks":[{"type":"code_inline"}]},{"text":" to check consistency","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Commit features.yml changes with the implementation","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Relationship with TodoWrite","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Tool","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Purpose","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Persistence","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"TodoWrite","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Immediate session actions","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Ephemeral","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"features.yml","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Requirements and progress","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Persistent (in repo)","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"Use both: TodoWrite for what to do now, features.yml for durable project state.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Phase Transitions","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"From","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"To","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Conditions","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Requirements","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Design","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"All requirements have descriptions","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Design","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Implementation","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"decisions","type":"text","marks":[{"type":"code_inline"}]},{"text":" field exists (use ","type":"text"},{"text":"[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" if none needed)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Implementation","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Testing","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"All requirements ","type":"text"},{"text":"In-Progress","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"Complete","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Testing","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Complete","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"All requirements ","type":"text"},{"text":"Complete","type":"text","marks":[{"type":"code_inline"}]},{"text":" AND all tests ","type":"text"},{"text":"passing: true","type":"text","marks":[{"type":"code_inline"}]}]}]}]}]},{"type":"paragraph","content":[{"text":"Scripts validate these rules and report errors.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Workflows","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Agent Workflow (Condensed)","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"ls features.yml","type":"text","marks":[{"type":"code_inline"}]},{"text":" → exists? Read it : Create it","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Plan first","type":"text","marks":[{"type":"strong"}]},{"text":": Add feature, requirements, test-cases, decisions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Set first requirement ","type":"text"},{"text":"status: In-Progress","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Implement, then set ","type":"text"},{"text":"status: Complete","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Run tests, update ","type":"text"},{"text":"passing","type":"text","marks":[{"type":"code_inline"}]},{"text":" status","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"./scripts/feature-status.py --validate","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Commit with implementation changes","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Creating a New Feature File","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Create ","type":"text"},{"text":"features.yml","type":"text","marks":[{"type":"code_inline"}]},{"text":" with first feature:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"yaml"},"content":[{"text":"feature: \"Feature Name\"\nphase: Requirements\nversion: 1\nchangelog: |\n ## [1]\n ### Added\n - Initial planning\nrequirements:\n req-1:\n description: \"Requirement description using EARS syntax\"\n status: Not-Started","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Run ","type":"text"},{"text":"./scripts/feature-status.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" to validate structure","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Minimal start","type":"text","marks":[{"type":"strong"}]},{"text":" (for quick bootstrapping during implementation):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"yaml"},"content":[{"text":"feature: \"Feature Name\"\nphase: Implementation\nversion: 1\nchangelog: |\n ## [1]\n ### Added\n - Initial implementation\nrequirements:\n req-1:\n description: \"Requirement from user request\"\n status: In-Progress","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Building from Existing Codebase","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Identify logical feature boundaries in the code","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"For each feature:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Create feature document, set ","type":"text"},{"text":"phase","type":"text","marks":[{"type":"code_inline"}]},{"text":" based on current maturity","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Extract requirements from code behavior, comments, documentation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Discover existing tests and add to ","type":"text"},{"text":"test-cases","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Link tests to requirements via ","type":"text"},{"text":"tested-by","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Set ","type":"text"},{"text":"status","type":"text","marks":[{"type":"code_inline"}]},{"text":" based on implementation state and test coverage","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Run ","type":"text"},{"text":"./scripts/feature-status.py --validate","type":"text","marks":[{"type":"code_inline"}]},{"text":" to check consistency","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Run ","type":"text"},{"text":"./scripts/extract-work.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" to see gaps","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Development Workflow (Incremental Progress)","type":"text"}]},{"type":"paragraph","content":[{"text":"Work on ONE requirement at a time:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Run ","type":"text"},{"text":"./scripts/extract-work.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" to see incomplete requirements","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Pick highest priority requirement, update ","type":"text"},{"text":"status: In-Progress","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Implement the requirement","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Write/update tests, add to ","type":"text"},{"text":"test-cases","type":"text","marks":[{"type":"code_inline"}]},{"text":" with ","type":"text"},{"text":"passing: false","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Run tests, update ","type":"text"},{"text":"passing: true","type":"text","marks":[{"type":"code_inline"}]},{"text":" when passing","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Update requirement ","type":"text"},{"text":"status: Complete","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Run ","type":"text"},{"text":"./scripts/feature-status.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" to check phase advancement eligibility","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Repeat","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Version Management","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Run ","type":"text"},{"text":"./scripts/check-version.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" to check for needed bumps","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If bump recommended:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Increment ","type":"text"},{"text":"version","type":"text","marks":[{"type":"code_inline"}]},{"text":" field","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Add new changelog section:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"yaml"},"content":[{"text":"changelog: |\n ## [2]\n ### Added\n - New capability\n \n ## [1]\n ...","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Commit the update","type":"text"}]}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Scripts","type":"text"}]},{"type":"paragraph","content":[{"text":"All scripts read from ","type":"text"},{"text":"features.yml","type":"text","marks":[{"type":"code_inline"}]},{"text":" in current directory. Scripts are executable and use inline dependencies via ","type":"text"},{"text":"uv run --script","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"feature-status.py","type":"text"}]},{"type":"paragraph","content":[{"text":"Overview of all features with progress and test status (with breakdown by type if defined).","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"./scripts/feature-status.py # Plain text output\n./scripts/feature-status.py --format markdown # Markdown table\n./scripts/feature-status.py --validate # Exit 1 if validation errors","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"extract-work.py","type":"text"}]},{"type":"paragraph","content":[{"text":"List requirements needing work (status != Complete).","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"./scripts/extract-work.py # All incomplete\n./scripts/extract-work.py --phase Implementation\n./scripts/extract-work.py --status In-Progress\n./scripts/extract-work.py --format markdown","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"extract-issues.py","type":"text"}]},{"type":"paragraph","content":[{"text":"List known issues across all features.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"./scripts/extract-issues.py\n./scripts/extract-issues.py --format json","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"check-version.py","type":"text"}]},{"type":"paragraph","content":[{"text":"Check git history to see if versions need bumping.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"./scripts/check-version.py","type":"text"}]},{"type":"paragraph","content":[{"text":"Compares when feature sections were last modified vs when ","type":"text"},{"text":"version","type":"text","marks":[{"type":"code_inline"}]},{"text":" was last set.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Best Practices","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"One requirement at a time","type":"text","marks":[{"type":"strong"}]},{"text":": Complete and verify before starting next","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Update status immediately","type":"text","marks":[{"type":"strong"}]},{"text":": Keep file in sync with actual state","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Document decisions","type":"text","marks":[{"type":"strong"}]},{"text":": Capture rationale in ","type":"text"},{"text":"decisions","type":"text","marks":[{"type":"code_inline"}]},{"text":" before implementation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Track known issues","type":"text","marks":[{"type":"strong"}]},{"text":": Document bugs and limitations in ","type":"text"},{"text":"known-issues","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Bump version on requirement changes","type":"text","marks":[{"type":"strong"}]},{"text":": Any add, modify, or remove","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use EARS syntax","type":"text","marks":[{"type":"strong"}]},{"text":" for requirements: \"When X, the system SHALL Y\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use Given/When/Then","type":"text","marks":[{"type":"strong"}]},{"text":" for test descriptions","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Change Management","type":"text"}]},{"type":"paragraph","content":[{"text":"All requirement changes require a version bump. This ensures traceability and clear history.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Adding Requirements to Existing Feature","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Add requirement with ","type":"text"},{"text":"status: Not-Started","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Increment ","type":"text"},{"text":"version","type":"text","marks":[{"type":"code_inline"}]},{"text":" field","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Add changelog entry under ","type":"text"},{"text":"### Added","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If feature is past Design phase, consider whether new requirement needs design review","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Modifying Existing Requirements","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Document rationale for the change","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Update requirement ","type":"text"},{"text":"description","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Update ","type":"text"},{"text":"status","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If was ","type":"text"},{"text":"Complete","type":"text","marks":[{"type":"code_inline"}]},{"text":": set to ","type":"text"},{"text":"Needs-Work","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Otherwise: keep current status","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Review affected test cases in ","type":"text"},{"text":"tested-by","type":"text","marks":[{"type":"code_inline"}]},{"text":" - update or mark as needing revision","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Increment ","type":"text"},{"text":"version","type":"text","marks":[{"type":"code_inline"}]},{"text":" field","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Add changelog entry under ","type":"text"},{"text":"### Changed","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Deprecating/Removing Requirements","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Document rationale","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Either:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Remove requirement entirely, OR","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Move to ","type":"text"},{"text":"known-issues","type":"text","marks":[{"type":"code_inline"}]},{"text":" as historical note (e.g., \"req-x removed: no longer needed\")","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Remove or update associated test cases","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Increment ","type":"text"},{"text":"version","type":"text","marks":[{"type":"code_inline"}]},{"text":" field","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Add changelog entry under ","type":"text"},{"text":"### Removed","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Version Bump Triggers","type":"text"}]},{"type":"paragraph","content":[{"text":"Always bump version when:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Adding, modifying, or removing requirements","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Shipping a milestone","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Significant scope changes","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Phase transitions to ","type":"text"},{"text":"Complete","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"feature-file","author":"@skillopedia","source":{"stars":336,"repo_name":"marketplace","origin_url":"https://github.com/aiskillstore/marketplace/blob/HEAD/skills/chemiseblanc/feature-file/SKILL.md","repo_owner":"aiskillstore","body_sha256":"0d40914675ce15508789e0dfedb7d1f493456f53d167f6f2b2088936c7b1a00f","cluster_key":"f7da453c8d348a61cc96f510898b3cec764338985ca69b1acb6e139947f63351","clean_bundle":{"format":"clean-skill-bundle-v1","source":"aiskillstore/marketplace/skills/chemiseblanc/feature-file/SKILL.md","attachments":[{"id":"7f1140bc-ffcc-56d5-b34e-ba8a1114f22b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/7f1140bc-ffcc-56d5-b34e-ba8a1114f22b/attachment.md","path":"references/schema.md","size":8048,"sha256":"f97821b08169f6c6ec42c3b9bfa47861ccc6ea84700f29305cc14df4b0f305da","contentType":"text/markdown; charset=utf-8"},{"id":"4117228c-2460-5fe7-9356-0c2e8828ab14","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/4117228c-2460-5fe7-9356-0c2e8828ab14/attachment.py","path":"scripts/check-version.py","size":11501,"sha256":"aad31256ea31bd70bd8ec3dcd308e18738763275833537ffbcd523715ca43c4d","contentType":"text/x-python; charset=utf-8"},{"id":"c734677b-e97a-5326-ba8a-53af5bfec342","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c734677b-e97a-5326-ba8a-53af5bfec342/attachment.py","path":"scripts/extract-issues.py","size":3423,"sha256":"a0339c104921a52c8e7c8207ebf52f43f276e0771c25d2fc4326522238565be8","contentType":"text/x-python; charset=utf-8"},{"id":"2796cc0b-c478-53e2-9336-2528d98e0a32","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/2796cc0b-c478-53e2-9336-2528d98e0a32/attachment.py","path":"scripts/extract-work.py","size":5278,"sha256":"e46101b6a34c58c1882e77b0a7cbcf24eef961f1a0f9f09bf337627aaf0d8e85","contentType":"text/x-python; charset=utf-8"},{"id":"7de9e535-9c98-55f1-8f40-81bc3398b20e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/7de9e535-9c98-55f1-8f40-81bc3398b20e/attachment.py","path":"scripts/feature-status.py","size":13104,"sha256":"c12b1e0dd62a9ff5b0bcfdaf6eaf420639ba74a0b4f0b1e8824f936c44ebcf14","contentType":"text/x-python; charset=utf-8"},{"id":"34c5ca16-6208-5b47-9736-a68ec42760c0","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/34c5ca16-6208-5b47-9736-a68ec42760c0/attachment.json","path":"skill-report.json","size":28797,"sha256":"bbe1bdaf1371764bb9c7f967337b724efc7ce5a21a0190ab4f1c72e5956355e7","contentType":"application/json; charset=utf-8"}],"bundle_sha256":"f0cc7facdd775f361e9c492c4692183c9ca15abbc4df87940c7bf1ad014950e0","attachment_count":6,"text_attachments":6,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"skills/chemiseblanc/feature-file/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"web-development","category_label":"Web"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"web-development","import_tag":"clean-skills-v1","description":"Manage features.yml for tracking requirements and progress; use proactively ONLY when features.yml already exists, or invoke manually to create one; complements TodoWrite for persistent project state."}},"renderedAt":1782980156148}

Feature File Management Manage - a waterfall-style planning document combining structured requirements tracking with incremental development. Quick Reference See for complete field documentation. Proactive Usage This skill should be used automatically when features.yml exists. Before Starting Implementation 1. Check if exists in project root 2. If missing: do not use this skill proactively (stop here) 3. Plan the work in features.yml before writing code: - Add/update the feature with all requirements extracted from the user's request - Add anticipated test cases to (with ) - Document design d…