
Requirements Analysis
- 146 installs
- 144 repo stars
- Updated August 5, 2026
- naodeng/awesome-qa-skills
requirements-analysis is a Claude agent QA skill that analyzes requirement documents for test points, boundaries, dependencies, and risks for developers and QA engineers who must assess testability before writing test ca
About
requirements-analysis is a QA skill from naodeng/awesome-qa-skills that helps developers and QA engineers analyze requirements before test design when documents are incomplete, ambiguous, or changing frequently. The skill loads prompts/requirements-analysis.md as the main prompt, accepts project context including scope, environment, constraints, risks, and dependencies, and outputs a risk-driven analysis usable for execution and review. The workflow decomposes requirements into business rules and state transitions, marks ambiguity such as missing inputs or conflicting logic, maps test dimensions across functional, data, interface, workflow, and non-functional areas, prioritizes by user impact and change volatility, forms clarification questions with decision owners, and delivers analysis conclusions with recommended next QA actions. Default output is Markdown with optional Excel, CSV, JSON, or Word formats documented in output-formats.md. Reach for requirements-analysis when the team needs to know what can be tested now versus what requires PM or dev clarification before case implementation.
- Loads prompts/requirements-analysis.md as the primary analysis prompt template
- Six-step flow: decompose rules, mark ambiguity, map test dimensions, prioritize, clarify, conclude
- Maps functional, data, interface, workflow, and non-functional test dimensions
- Outputs ambiguity list, clarification questions, risk ranking, and testability conclusion
- Supports Markdown default plus optional Excel, CSV, JSON, Word via output-formats.md
Requirements Analysis by the numbers
- 146 all-time installs (skills.sh)
- +8 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #892 of 2,153 Testing & QA skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/naodeng/awesome-qa-skills --skill requirements-analysisAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 146 |
|---|---|
| repo stars | ★ 144 |
| Last updated | August 5, 2026 |
| Repository | naodeng/awesome-qa-skills ↗ |
How do you assess requirement testability before test design?
Analyze a PRD with requirements-analysis to produce ambiguity lists, risk rankings, clarification questions, and testability conclusions before writing test cases.
Who is it for?
QA engineers and developers analyzing PRDs or specs with ambiguous, incomplete, or fast-changing requirements before investing in test case authoring.
Skip if: Teams with fully specified acceptance criteria and stable requirements who are ready to jump directly into automated test implementation.
When should I use this skill?
User requests requirements analysis, test point identification, ambiguity review, or risk assessment on a PRD before test case writing.
What you get
Requirements analysis report with ambiguity list, risk ranking, clarification questions, dependency map, and testability conclusion.
- requirements analysis report
- ambiguity list
- clarification questions
By the numbers
- Six-step analysis workflow from decomposition through clarification to conclusions
- Maps five test dimensions: functional, data, interface, workflow, non-functional
- Bundled prompt at prompts/requirements-analysis.md with optional output-formats.md
Files
Requirements Analysis (English)
中文版: 见对应中文技能。
When to Use
- Need help with requirements analysis in a real project context.
- Need an output that can be used directly for execution, review, or follow-up.
Output Format Options
Markdown by default. If you need Excel, CSV, JSON, Word, or other supported formats, append the format request at the end and check output-formats.md.
How to Use
1. Open prompts/requirements-analysis.md and use it as the main prompt. 2. Add the real project context: scope, environment, constraints, risks, dependencies, and expected deliverable. 3. If the input is incomplete, return a usable first version and mark missing information and assumptions.
Reference Files
prompts/requirements-analysis.md: main prompt for this skill.output-formats.md: optional output format instructions.references/: supporting notes loaded only when needed.scripts/: helper scripts or converters for this skill.
Common Pitfalls
- Do not use it with vague scope and no context.
- Do not treat every area as equally important.
- Do not skip assumptions and missing information.
Best Practices
- Start from the prompt file, then add only the context that matters.
- Keep the output risk-driven and executable.
- If the request is incomplete, return a usable first version and mark gaps.
version: 1
metadata:
key: "requirements-analysis"
interface:
display_name: "Requirements Analysis (English)"
short_description: "Use this skill when you need to analyze requirements, identify test points, boundaries, dependencies, and risks before test design; trigg..."
default_prompt: "Use the requirements-analysis skill to complete this testing task with structured outputs and practical next steps."
policy:
allow_implicit_invocation: true
Output Format Options
This skill defaults to Markdown. To get Excel, CSV, or JSON, add at the end of your request:
- Excel: "Please output as tab-separated table for pasting into Excel."
- CSV: "Please output as CSV (comma-separated, header row first)."
- JSON: "Please output as JSON."
See repo skills/testing-types/_output-formats-template-en.md for examples.
section,item,value
meta,skill,
meta,scope,
meta,environment,
meta,priority,
inputs,requirement,
results,status,
results,evidence,
results,defects,
next_actions,owner,
next_actions,eta,
next_actions,action,
Section Field Value Notes
Meta Skill
Meta Scope
Meta Environment
Meta Priority
Inputs Requirement
Inputs Constraints Use comma-separated values
Inputs Risks Use comma-separated values
Results Status Pass/Fail/Blocked
Results Evidence Links or IDs
Results Defects Bug IDs
Next Actions Owner
Next Actions ETA YYYY-MM-DD
Next Actions Action
{
"meta": {
"skill": "",
"scope": "",
"environment": "",
"priority": ""
},
"inputs": {
"requirement": "",
"constraints": [],
"risks": []
},
"execution": [
{ "step": 1, "action": "", "expected": "" }
],
"results": {
"status": "",
"evidence": [],
"defects": []
},
"next_actions": [
{ "owner": "", "eta": "", "action": "" }
]
}
QA Output Template
Summary
- Skill:
- Scope:
- Environment:
- Priority:
Inputs
- Requirement:
- Constraints:
- Risks:
Execution
1. Step 1 2. Step 2 3. Step 3
Results
- Status:
- Evidence:
- Defects:
Next Actions
- Owner:
- ETA:
QA Report =========
1. Basic Information
- Skill:
- Scope:
- Environment:
- Priority:
2. Requirement and Constraints
- Requirement:
- Constraints:
- Risks:
3. Test/Review Process
- Step 1:
- Step 2:
- Step 3:
4. Outcome
- Status:
- Evidence:
- Defects:
5. Follow-up Plan
- Owner:
- ETA:
- Action:
QA Output Mindmap
- QA Output
- Meta
- Skill
- Scope
- Environment
- Priority
- Inputs
- Requirement
- Constraints
- Risks
- Execution
- Step 1
- Step 2
- Step 3
- Results
- Status
- Evidence
- Defects
- Next Actions
- Owner
- ETA
- Action
Requirements Analysis Prompt
Analyze requirements from a QA perspective and turn them into clear risks, gaps, and testable next actions.
Role
- Act as a senior QA analyst who turns requirements into clear risks, gaps, and testable next actions.
Input
- requirement docs, stories, acceptance criteria, prototypes, or change requests
- business context, release scope, dependencies, and constraints
- known issues, open questions, and related technical notes
What to do
1. Understand the intended behavior, boundaries, and business value. 2. Find ambiguity, inconsistency, missing rules, and weak acceptance criteria. 3. Turn the analysis into practical follow-up actions for QA and the team.
Execution Rules
- Separate confirmed requirements from inferred expectations.
- Focus on issues that affect delivery, quality, or testability.
- Do not restate the whole document without adding value.
Minimum Coverage Checklist
Unless the user explicitly narrows the scope, make sure the result addresses these items:
- scope summary
- business objective
- clear and unclear requirements
- missing rules
- edge or exception conditions
- testability gaps
- dependencies and impacts
- risk priority
- questions to resolve
- assumptions
Output
Return the result in this order:
1. Requirement Understanding
2. Gaps and Ambiguities
3. Risk Assessment
4. Testability Impact
5. Questions to Resolve
6. Recommended Next Steps
Quality Bar
- Focus on the gaps that matter most.
- Do not copy large parts of the source.
- Make the output actionable.
Requirements Analysis
Skill Overview
Need help with requirements analysis in a real project context; Need an output that can be used directly for execution, review, or follow-up.
How to Use
1. Open SKILL.md in this folder and confirm this skill fits your task. 2. In your AI tool, call @skill requirements-analysis, then add your real project context and goal. 3. If you need a specific output format (table, checklist, report), include it directly in your request.
One-Click Install Script
Run from the repository root:
macOS / Linux
bash ./scripts/install-skills-mac.sh --tool codex --lang en --skill requirements-analysisWindows PowerShell
powershell -ExecutionPolicy Bypass -File .\scripts\install-skills-windows.ps1 -Tool codex -Lang en -Skill requirements-analysisArchived Local Reference
This file is a lightweight legacy note. The previous long snapshot was removed to avoid duplicate and outdated guidance.
Use Instead
- Main prompt:
prompts/requirements-analysis.md - Main entry:
SKILL.md - Output format guide:
output-formats.md
Notes
- Keep using the current prompt and
SKILL.mdas the source of truth. - Load
references/,examples/, orscripts/only when the task really needs extra detail. - Do not rely on this file for the latest prompt wording or workflow rules.
Troubleshooting
Issue 1: Unclear Requirements
Symptoms: Requirements are ambiguous, incomplete, or contradictory
Solution: 1. List unclear points with specific questions:
- What is the expected behavior when...?
- What are the valid input ranges?
- What should happen in error cases?
2. Communicate with product manager/stakeholders 3. Document clarification results 4. Update requirement documents 5. Get written confirmation of changes
Prevention:
- Use structured requirement templates
- Include acceptance criteria
- Add concrete examples
- Define edge cases explicitly
Issue 2: Missing Test Points
Symptoms: Test coverage is incomplete, important scenarios overlooked
Solution: Use comprehensive checklist:
- [ ] Normal scenarios (happy path)
- [ ] Exception scenarios (error handling)
- [ ] Boundary values (min/max/edge cases)
- [ ] Permission validation (role-based access)
- [ ] Data validation (format, type, range)
- [ ] Performance requirements (response time, throughput)
- [ ] Security requirements (authentication, authorization, encryption)
- [ ] Compatibility (browsers, devices, OS)
- [ ] Usability (accessibility, user experience)
- [ ] Integration points (APIs, external services)
Techniques:
- Mind mapping for visual coverage
- Traceability matrix (requirements → test points)
- Peer review of analysis
- Use test design techniques systematically
Issue 3: Conflicting Requirements
Symptoms: Requirements contradict each other or are mutually exclusive
Solution: 1. Document all conflicts clearly 2. Analyze impact of each option 3. Present trade-offs to stakeholders 4. Facilitate decision-making meeting 5. Update requirements with final decision 6. Communicate changes to all parties
Issue 4: Non-Functional Requirements Overlooked
Symptoms: Only functional requirements analyzed, missing performance/security/usability
Solution: 1. Use NFR checklist:
- Performance (response time, throughput, scalability)
- Security (authentication, authorization, data protection)
- Usability (accessibility, user experience, learnability)
- Reliability (availability, fault tolerance, recovery)
- Maintainability (code quality, documentation, testability)
- Compatibility (browsers, devices, platforms)
2. Ask specific NFR questions for each feature 3. Define measurable NFR criteria 4. Include NFR in test planning
Issue 5: Inadequate Stakeholder Input
Symptoms: Analysis based on assumptions, lacking validation from stakeholders
Solution: 1. Schedule requirements review sessions 2. Prepare specific questions and scenarios 3. Use prototypes or mockups for validation 4. Document stakeholder feedback 5. Iterate analysis based on input 6. Get sign-off on final analysis
Issue 6: Analysis Paralysis
Symptoms: Spending too much time on analysis, delaying testing
Solution: 1. Set time limits for analysis phase 2. Focus on high-priority requirements first 3. Use iterative approach (analyze → test → refine) 4. Accept that some details will emerge during testing 5. Document assumptions and move forward 6. Schedule follow-up reviews
Get More Help
If the issue persists: 1. Check FAQ.md 2. Review example templates in examples/ directory 3. Search GitHub Issues 4. Submit a new Issue with detailed information
Related skills: test-case-writing, test-strategy, functional-testing.
#!/usr/bin/env python3
import argparse
import subprocess
import sys
from pathlib import Path
def detect_from(file: Path) -> str:
ext = file.suffix.lower()
if file.name.endswith('.word.md'):
return 'markdown'
return {
'.md': 'markdown',
'.markdown': 'markdown',
'.json': 'json',
'.csv': 'csv',
'.tsv': 'excel',
'.docx': 'word',
'.xlsx': 'excel',
'.xmind': 'xmind',
}.get(ext, 'markdown')
def run_convert(convert_script: Path, src: Path, to_fmt: str, out: Path) -> int:
cmd = [sys.executable, str(convert_script), str(src), '--from', detect_from(src), '--to', to_fmt, '--output', str(out)]
return subprocess.call(cmd)
def main() -> None:
parser = argparse.ArgumentParser(description='Batch convert all template files into target formats.')
parser.add_argument('--templates-dir', type=Path, default=Path('output-templates'))
parser.add_argument('--artifacts-dir', type=Path, default=Path('artifacts'))
parser.add_argument('--targets', default='word,excel,xmind,json,csv,markdown', help='comma-separated target formats')
parser.add_argument('--skip-same', action='store_true', help='skip conversion when source format equals target format')
args = parser.parse_args()
cwd = Path.cwd()
templates_dir = (cwd / args.templates_dir).resolve()
artifacts_dir = (cwd / args.artifacts_dir).resolve()
artifacts_dir.mkdir(parents=True, exist_ok=True)
local_convert = (Path(__file__).resolve().parent / 'convert_formats.py').resolve()
targets = [t.strip() for t in args.targets.split(',') if t.strip()]
if not templates_dir.exists():
raise SystemExit(f'templates directory not found: {templates_dir}')
files = [p for p in sorted(templates_dir.iterdir()) if p.is_file()]
total = 0
failed = 0
for src in files:
src_fmt = detect_from(src)
for to_fmt in targets:
if args.skip_same and src_fmt == to_fmt:
continue
out_ext = {
'json': '.json',
'csv': '.csv',
'excel': '.tsv',
'markdown': '.md',
'word': '.word.md',
'xmind': '.xmind.md',
}[to_fmt]
out = artifacts_dir / f"{src.stem}.to-{to_fmt}{out_ext}"
total += 1
rc = run_convert(local_convert, src, to_fmt, out)
if rc != 0:
failed += 1
print(f'[FAILED] {src.name} -> {to_fmt}')
else:
print(f'[OK] {src.name} -> {out.name}')
print(f'\nDone. total={total}, failed={failed}, artifacts={artifacts_dir}')
if failed:
raise SystemExit(1)
if __name__ == '__main__':
main()
#!/usr/bin/env python3
import argparse
import csv
import json
import re
import zipfile
from pathlib import Path
from typing import Any
from xml.etree import ElementTree as ET
# ---- parsing ----
def parse_markdown(path: Path) -> dict[str, Any]:
text = path.read_text(encoding="utf-8", errors="ignore")
lines = text.splitlines()
headings: list[dict[str, Any]] = []
for line in lines:
m = re.match(r"^(#{1,6})\s+(.*)$", line.strip())
if m:
headings.append({"level": len(m.group(1)), "title": m.group(2).strip()})
return {"title": headings[0]["title"] if headings else path.stem, "headings": headings, "text": text}
def parse_json(path: Path) -> dict[str, Any]:
data = json.loads(path.read_text(encoding="utf-8", errors="ignore"))
return {"title": path.stem, "data": data}
def parse_csv_file(path: Path) -> dict[str, Any]:
with path.open("r", encoding="utf-8", errors="ignore", newline="") as f:
reader = csv.DictReader(f)
rows = list(reader)
return {"title": path.stem, "columns": reader.fieldnames or [], "rows": rows}
def parse_docx(path: Path) -> dict[str, Any]:
paragraphs: list[str] = []
with zipfile.ZipFile(path) as zf:
with zf.open("word/document.xml") as f:
root = ET.fromstring(f.read())
ns = {"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"}
for p in root.findall(".//w:p", ns):
texts = [t.text for t in p.findall(".//w:t", ns) if t.text]
s = "".join(texts).strip()
if s:
paragraphs.append(s)
return {"title": path.stem, "paragraphs": paragraphs}
def _shared_strings(zf: zipfile.ZipFile) -> list[str]:
out: list[str] = []
try:
with zf.open("xl/sharedStrings.xml") as f:
root = ET.fromstring(f.read())
ns = {"a": "http://schemas.openxmlformats.org/spreadsheetml/2006/main"}
for si in root.findall(".//a:si", ns):
out.append("".join((t.text or "") for t in si.findall(".//a:t", ns)))
except KeyError:
pass
return out
def parse_xlsx(path: Path) -> dict[str, Any]:
rows: list[list[str]] = []
with zipfile.ZipFile(path) as zf:
shared = _shared_strings(zf)
with zf.open("xl/worksheets/sheet1.xml") as f:
root = ET.fromstring(f.read())
ns = {"a": "http://schemas.openxmlformats.org/spreadsheetml/2006/main"}
for row in root.findall(".//a:sheetData/a:row", ns):
vals = []
for c in row.findall("a:c", ns):
t = c.attrib.get("t")
v = c.find("a:v", ns)
if v is None or v.text is None:
vals.append("")
elif t == "s":
idx = int(v.text)
vals.append(shared[idx] if 0 <= idx < len(shared) else "")
else:
vals.append(v.text)
rows.append(vals)
return {"title": path.stem, "rows": rows}
def parse_xmind(path: Path) -> dict[str, Any]:
with zipfile.ZipFile(path) as zf:
names = set(zf.namelist())
topics: list[str] = []
if "content.json" in names:
data = json.loads(zf.read("content.json").decode("utf-8", errors="ignore"))
def walk(node: Any):
if isinstance(node, dict):
t = node.get("title")
if isinstance(t, str) and t.strip():
topics.append(t.strip())
for k in ("children", "topics", "rootTopic", "attached"):
walk(node.get(k))
elif isinstance(node, list):
for i in node:
walk(i)
walk(data)
elif "content.xml" in names:
root = ET.fromstring(zf.read("content.xml"))
topics = [e.text.strip() for e in root.findall(".//title") if e.text and e.text.strip()]
else:
raise ValueError("Unsupported XMind package structure")
return {"title": topics[0] if topics else path.stem, "topics": topics}
def detect_in_format(path: Path, forced: str | None) -> str:
if forced and forced != "auto":
return forced
return {
".md": "markdown",
".markdown": "markdown",
".json": "json",
".csv": "csv",
".docx": "word",
".xlsx": "excel",
".xmind": "xmind",
".tsv": "excel",
}.get(path.suffix.lower(), "markdown")
def normalize(parsed: dict[str, Any]) -> dict[str, Any]:
title = parsed.get("title") or "QA Output"
sections: list[dict[str, Any]] = []
if "text" in parsed:
sections.append({"name": "content", "items": [{"key": "text", "value": parsed["text"]}]})
if "headings" in parsed:
sections.append({"name": "headings", "items": [{"key": "heading", "value": h.get("title", "")} for h in parsed["headings"]]})
if "data" in parsed:
data = parsed["data"]
if isinstance(data, dict):
items = [{"key": k, "value": v} for k, v in list(data.items())[:100]]
sections.append({"name": "json_object", "items": items})
elif isinstance(data, list):
sections.append({"name": "json_array", "items": [{"key": "row", "value": v} for v in data[:200]]})
else:
sections.append({"name": "json_value", "items": [{"key": "value", "value": data}]})
if "rows" in parsed:
rows = parsed["rows"]
sections.append({"name": "rows", "items": [{"key": f"row_{i+1}", "value": r} for i, r in enumerate(rows[:200])]})
if "columns" in parsed:
sections.append({"name": "columns", "items": [{"key": "column", "value": c} for c in parsed["columns"]]})
if "paragraphs" in parsed:
sections.append({"name": "paragraphs", "items": [{"key": f"p{i+1}", "value": p} for i, p in enumerate(parsed["paragraphs"][:200])]})
if "topics" in parsed:
sections.append({"name": "topics", "items": [{"key": "topic", "value": t} for t in parsed["topics"][:300]]})
return {"title": title, "sections": sections}
# ---- writers ----
def write_json(model: dict[str, Any], output: Path) -> None:
output.write_text(json.dumps(model, ensure_ascii=False, indent=2), encoding="utf-8")
def _scalar(v: Any) -> str:
if isinstance(v, (dict, list)):
return json.dumps(v, ensure_ascii=False)
return str(v)
def write_csv(model: dict[str, Any], output: Path) -> None:
with output.open("w", encoding="utf-8", newline="") as f:
writer = csv.writer(f)
writer.writerow(["section", "key", "value"])
for s in model.get("sections", []):
for item in s.get("items", []):
writer.writerow([s.get("name", ""), item.get("key", ""), _scalar(item.get("value", ""))])
def write_excel_tsv(model: dict[str, Any], output: Path) -> None:
lines = ["Section\tKey\tValue"]
for s in model.get("sections", []):
for item in s.get("items", []):
lines.append(f"{s.get('name','')}\t{item.get('key','')}\t{_scalar(item.get('value','')).replace(chr(9), ' ')}")
output.write_text("\n".join(lines) + "\n", encoding="utf-8")
def write_markdown(model: dict[str, Any], output: Path) -> None:
lines = [f"# {model.get('title', 'QA Output')}", ""]
for s in model.get("sections", []):
lines.append(f"## {s.get('name', 'section')}")
for item in s.get("items", []):
lines.append(f"- **{item.get('key','key')}**: {_scalar(item.get('value',''))}")
lines.append("")
output.write_text("\n".join(lines).rstrip() + "\n", encoding="utf-8")
def write_word_md(model: dict[str, Any], output: Path) -> None:
lines = [model.get("title", "QA Output"), "=" * len(model.get("title", "QA Output")), ""]
idx = 1
for s in model.get("sections", []):
lines.append(f"{idx}. {s.get('name', 'section').replace('_', ' ').title()}")
for item in s.get("items", []):
lines.append(f"- {item.get('key','key')}: {_scalar(item.get('value',''))}")
lines.append("")
idx += 1
output.write_text("\n".join(lines).rstrip() + "\n", encoding="utf-8")
def write_xmind_md(model: dict[str, Any], output: Path) -> None:
lines = [f"# {model.get('title', 'QA Output')}", "", f"- {model.get('title', 'QA Output')}"]
for s in model.get("sections", []):
lines.append(f" - {s.get('name', 'section')}")
for item in s.get("items", []):
lines.append(f" - {item.get('key','key')}: {_scalar(item.get('value',''))}")
output.write_text("\n".join(lines).rstrip() + "\n", encoding="utf-8")
def default_output(input_path: Path, to_fmt: str) -> Path:
ext = {
"json": ".json",
"csv": ".csv",
"excel": ".tsv",
"markdown": ".md",
"word": ".word.md",
"xmind": ".xmind.md",
}[to_fmt]
return input_path.with_name(input_path.stem + ".converted" + ext)
def main() -> None:
parser = argparse.ArgumentParser(description="Convert QA output files between common formats")
parser.add_argument("input", type=Path, help="Input file path")
parser.add_argument("--from", dest="from_fmt", default="auto", choices=["auto", "word", "excel", "xmind", "json", "csv", "markdown"])
parser.add_argument("--to", required=True, choices=["word", "excel", "xmind", "json", "csv", "markdown"])
parser.add_argument("--output", type=Path, help="Output file path")
args = parser.parse_args()
in_fmt = detect_in_format(args.input, args.from_fmt)
if in_fmt == "word":
parsed = parse_docx(args.input)
elif in_fmt == "excel":
if args.input.suffix.lower() == ".tsv":
rows = [line.rstrip("\n").split("\t") for line in args.input.read_text(encoding="utf-8", errors="ignore").splitlines() if line]
parsed = {"title": args.input.stem, "rows": rows}
else:
parsed = parse_xlsx(args.input)
elif in_fmt == "xmind":
parsed = parse_xmind(args.input)
elif in_fmt == "json":
parsed = parse_json(args.input)
elif in_fmt == "csv":
parsed = parse_csv_file(args.input)
else:
parsed = parse_markdown(args.input)
model = normalize(parsed)
output = args.output or default_output(args.input, args.to)
if args.to == "json":
write_json(model, output)
elif args.to == "csv":
write_csv(model, output)
elif args.to == "excel":
write_excel_tsv(model, output)
elif args.to == "markdown":
write_markdown(model, output)
elif args.to == "word":
write_word_md(model, output)
else:
write_xmind_md(model, output)
print(str(output))
if __name__ == "__main__":
main()
#!/usr/bin/env python3
import os
import subprocess
import sys
LOCAL = os.path.normpath(os.path.join(os.path.dirname(__file__), 'convert_formats.py'))
if __name__ == '__main__':
raise SystemExit(subprocess.call([sys.executable, LOCAL] + sys.argv[1:]))
#!/usr/bin/env python3
import os
import subprocess
import sys
LOCAL = os.path.normpath(os.path.join(os.path.dirname(__file__), 'convert_formats.py'))
if __name__ == '__main__':
raise SystemExit(subprocess.call([sys.executable, LOCAL, '--to', 'csv'] + sys.argv[1:]))
#!/usr/bin/env python3
import os
import subprocess
import sys
LOCAL = os.path.normpath(os.path.join(os.path.dirname(__file__), 'convert_formats.py'))
if __name__ == '__main__':
raise SystemExit(subprocess.call([sys.executable, LOCAL, '--to', 'excel'] + sys.argv[1:]))
#!/usr/bin/env python3
import os
import subprocess
import sys
LOCAL = os.path.normpath(os.path.join(os.path.dirname(__file__), 'convert_formats.py'))
if __name__ == '__main__':
raise SystemExit(subprocess.call([sys.executable, LOCAL, '--to', 'json'] + sys.argv[1:]))
#!/usr/bin/env python3
import os
import subprocess
import sys
LOCAL = os.path.normpath(os.path.join(os.path.dirname(__file__), 'convert_formats.py'))
if __name__ == '__main__':
raise SystemExit(subprocess.call([sys.executable, LOCAL, '--to', 'markdown'] + sys.argv[1:]))
#!/usr/bin/env python3
import os
import subprocess
import sys
LOCAL = os.path.normpath(os.path.join(os.path.dirname(__file__), 'convert_formats.py'))
if __name__ == '__main__':
raise SystemExit(subprocess.call([sys.executable, LOCAL, '--to', 'word'] + sys.argv[1:]))
#!/usr/bin/env python3
import os
import subprocess
import sys
LOCAL = os.path.normpath(os.path.join(os.path.dirname(__file__), 'convert_formats.py'))
if __name__ == '__main__':
raise SystemExit(subprocess.call([sys.executable, LOCAL, '--to', 'xmind'] + sys.argv[1:]))
#!/usr/bin/env python3
import os
import subprocess
import sys
LOCAL = os.path.normpath(os.path.join(os.path.dirname(__file__), 'parse_formats.py'))
if __name__ == '__main__':
raise SystemExit(subprocess.call([sys.executable, LOCAL, '--format', 'csv'] + sys.argv[1:]))
#!/usr/bin/env python3
import os
import subprocess
import sys
LOCAL = os.path.normpath(os.path.join(os.path.dirname(__file__), 'parse_formats.py'))
if __name__ == '__main__':
raise SystemExit(subprocess.call([sys.executable, LOCAL, '--format', 'excel'] + sys.argv[1:]))
#!/usr/bin/env python3
import argparse
import csv
import json
import re
import zipfile
from pathlib import Path
from typing import Any
from xml.etree import ElementTree as ET
def parse_markdown(path: Path) -> dict[str, Any]:
text = path.read_text(encoding="utf-8", errors="ignore")
headings = []
for line in text.splitlines():
m = re.match(r"^(#{1,6})\s+(.*)$", line.strip())
if m:
headings.append({"level": len(m.group(1)), "title": m.group(2).strip()})
return {"format": "markdown", "headings": headings, "preview": text[:500]}
def parse_json(path: Path) -> dict[str, Any]:
data = json.loads(path.read_text(encoding="utf-8", errors="ignore"))
if isinstance(data, dict):
shape = {"type": "object", "keys": list(data.keys())[:50]}
elif isinstance(data, list):
shape = {"type": "array", "size": len(data)}
else:
shape = {"type": type(data).__name__}
return {"format": "json", "shape": shape, "data": data}
def parse_csv_file(path: Path) -> dict[str, Any]:
with path.open("r", encoding="utf-8", errors="ignore", newline="") as f:
reader = csv.DictReader(f)
rows = list(reader)
return {
"format": "csv",
"columns": reader.fieldnames or [],
"row_count": len(rows),
"sample_rows": rows[:10],
}
def parse_docx(path: Path) -> dict[str, Any]:
paragraphs = []
with zipfile.ZipFile(path) as zf:
with zf.open("word/document.xml") as f:
root = ET.fromstring(f.read())
ns = {"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"}
for p in root.findall(".//w:p", ns):
texts = [t.text for t in p.findall(".//w:t", ns) if t.text]
joined = "".join(texts).strip()
if joined:
paragraphs.append(joined)
return {"format": "word", "paragraph_count": len(paragraphs), "paragraphs": paragraphs[:100]}
def _read_shared_strings(zf: zipfile.ZipFile) -> list[str]:
strings = []
try:
with zf.open("xl/sharedStrings.xml") as f:
root = ET.fromstring(f.read())
ns = {"a": "http://schemas.openxmlformats.org/spreadsheetml/2006/main"}
for si in root.findall(".//a:si", ns):
parts = [t.text or "" for t in si.findall(".//a:t", ns)]
strings.append("".join(parts))
except KeyError:
pass
return strings
def parse_xlsx(path: Path) -> dict[str, Any]:
rows_out: list[list[str]] = []
with zipfile.ZipFile(path) as zf:
shared = _read_shared_strings(zf)
with zf.open("xl/worksheets/sheet1.xml") as f:
root = ET.fromstring(f.read())
ns = {"a": "http://schemas.openxmlformats.org/spreadsheetml/2006/main"}
for row in root.findall(".//a:sheetData/a:row", ns):
vals = []
for c in row.findall("a:c", ns):
cell_type = c.attrib.get("t")
v = c.find("a:v", ns)
if v is None or v.text is None:
vals.append("")
continue
if cell_type == "s":
idx = int(v.text)
vals.append(shared[idx] if 0 <= idx < len(shared) else "")
else:
vals.append(v.text)
rows_out.append(vals)
return {"format": "excel", "row_count": len(rows_out), "sample_rows": rows_out[:20]}
def parse_xmind(path: Path) -> dict[str, Any]:
with zipfile.ZipFile(path) as zf:
names = set(zf.namelist())
if "content.json" in names:
data = json.loads(zf.read("content.json").decode("utf-8", errors="ignore"))
titles: list[str] = []
def walk(node: Any):
if isinstance(node, dict):
title = node.get("title")
if isinstance(title, str) and title.strip():
titles.append(title.strip())
for k in ("children", "topics", "rootTopic", "attached"):
walk(node.get(k))
elif isinstance(node, list):
for i in node:
walk(i)
walk(data)
return {"format": "xmind", "topic_count": len(titles), "topics": titles[:200]}
if "content.xml" in names:
root = ET.fromstring(zf.read("content.xml"))
titles = [el.text.strip() for el in root.findall(".//title") if el.text and el.text.strip()]
return {"format": "xmind", "topic_count": len(titles), "topics": titles[:200]}
raise ValueError("Unsupported XMind package structure")
def detect_format(path: Path, forced: str | None) -> str:
if forced and forced != "auto":
return forced
ext = path.suffix.lower()
return {
".md": "markdown",
".markdown": "markdown",
".json": "json",
".csv": "csv",
".docx": "word",
".xlsx": "excel",
".xmind": "xmind",
}.get(ext, "markdown")
def main() -> None:
parser = argparse.ArgumentParser(description="Parse common QA output formats into normalized JSON")
parser.add_argument("input", type=Path, help="Input file path")
parser.add_argument("--format", default="auto", choices=["auto", "word", "excel", "xmind", "json", "csv", "markdown"])
parser.add_argument("--output", type=Path, help="Output JSON path (default: stdout)")
args = parser.parse_args()
fmt = detect_format(args.input, args.format)
if fmt == "word":
result = parse_docx(args.input)
elif fmt == "excel":
result = parse_xlsx(args.input)
elif fmt == "xmind":
result = parse_xmind(args.input)
elif fmt == "json":
result = parse_json(args.input)
elif fmt == "csv":
result = parse_csv_file(args.input)
else:
result = parse_markdown(args.input)
result["source"] = str(args.input)
out = json.dumps(result, ensure_ascii=False, indent=2)
if args.output:
args.output.write_text(out, encoding="utf-8")
else:
print(out)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
import os
import subprocess
import sys
LOCAL = os.path.normpath(os.path.join(os.path.dirname(__file__), 'parse_formats.py'))
if __name__ == '__main__':
raise SystemExit(subprocess.call([sys.executable, LOCAL, '--format', 'json'] + sys.argv[1:]))
#!/usr/bin/env python3
import os
import subprocess
import sys
LOCAL = os.path.normpath(os.path.join(os.path.dirname(__file__), 'parse_formats.py'))
if __name__ == '__main__':
raise SystemExit(subprocess.call([sys.executable, LOCAL, '--format', 'markdown'] + sys.argv[1:]))
#!/usr/bin/env python3
import os
import subprocess
import sys
LOCAL = os.path.normpath(os.path.join(os.path.dirname(__file__), 'parse_formats.py'))
if __name__ == '__main__':
raise SystemExit(subprocess.call([sys.executable, LOCAL] + sys.argv[1:]))
#!/usr/bin/env python3
import os
import subprocess
import sys
LOCAL = os.path.normpath(os.path.join(os.path.dirname(__file__), 'parse_formats.py'))
if __name__ == '__main__':
raise SystemExit(subprocess.call([sys.executable, LOCAL, '--format', 'word'] + sys.argv[1:]))
#!/usr/bin/env python3
import os
import subprocess
import sys
LOCAL = os.path.normpath(os.path.join(os.path.dirname(__file__), 'parse_formats.py'))
if __name__ == '__main__':
raise SystemExit(subprocess.call([sys.executable, LOCAL, '--format', 'xmind'] + sys.argv[1:]))
Related skills
How it compares
Use requirements-analysis for pre-test requirement risk assessment; use requirements-analysis-plus when parsing multi-format source documents with deeper conflict checks.
FAQ
What prompt file drives requirements-analysis?
requirements-analysis uses prompts/requirements-analysis.md as its main prompt; developers add real project context—scope, environment, constraints, risks, and expected deliverable—before triggering analysis on requirement input.
What outputs should requirements-analysis produce?
requirements-analysis should deliver an ambiguity list, clarification questions with decision owners, risk ranking, testability conclusion, dependency list, and recommended next QA actions in Markdown or another requested format.
When should QA rerun requirements-analysis?
requirements-analysis should be rerun after PM or development clarifications arrive so the final analysis reflects resolved ambiguities and updated scope before test case implementation begins.