@jakkrichm/create-nexus-devflow 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/bin/create-nexus-devflow.js +28 -4
  2. package/dist/bin/create-nexus-devflow.js.map +1 -1
  3. package/dist/lib/project-metadata.d.ts +1 -1
  4. package/dist/lib/project-metadata.js +3 -2
  5. package/dist/lib/project-metadata.js.map +1 -1
  6. package/dist/lib/update.js +7 -3
  7. package/dist/lib/update.js.map +1 -1
  8. package/package.json +1 -1
  9. package/template/.agents/skills/adopt/SKILL.md +58 -5
  10. package/template/.agents/skills/doctor/SKILL.md +23 -16
  11. package/template/.agents/skills/implement/SKILL.md +2 -2
  12. package/template/.agents/skills/onboard/SKILL.md +18 -17
  13. package/template/.agents/skills/rollback/SKILL.md +1 -1
  14. package/template/.claude/skills/00-explore/SKILL.md +2 -2
  15. package/template/.claude/skills/10-define/SKILL.md +2 -2
  16. package/template/.claude/skills/20-spec/SKILL.md +1 -2
  17. package/template/.claude/skills/30-plan/SKILL.md +1 -2
  18. package/template/.claude/skills/40-execute/SKILL.md +2 -2
  19. package/template/.claude/skills/50-verify/SKILL.md +2 -2
  20. package/template/.claude/skills/60-report/SKILL.md +2 -2
  21. package/template/.claude/skills/70-deliver/SKILL.md +2 -2
  22. package/template/.claude/skills/adopt/SKILL.md +191 -75
  23. package/template/.claude/skills/audit/SKILL.md +267 -133
  24. package/template/.claude/skills/autopilot/SKILL.md +226 -167
  25. package/template/.claude/skills/brainstorm/SKILL.md +62 -0
  26. package/template/.claude/skills/brief/SKILL.md +93 -92
  27. package/template/.claude/skills/check/SKILL.md +96 -76
  28. package/template/.claude/skills/ci/SKILL.md +140 -61
  29. package/template/.claude/skills/complete/SKILL.md +156 -101
  30. package/template/.claude/skills/convert-any-to-md/SKILL.md +2 -2
  31. package/template/.claude/skills/convert-any-to-md/references/setup.md +29 -0
  32. package/template/.claude/skills/convert-any-to-md/scripts/convert_any_to_md.py +487 -0
  33. package/template/.claude/skills/convert-any-to-md/scripts/requirements.txt +3 -0
  34. package/template/.claude/skills/debug/SKILL.md +124 -49
  35. package/template/.claude/skills/devflow/SKILL.md +9 -3
  36. package/template/.claude/skills/discovery/SKILL.md +150 -129
  37. package/template/.claude/skills/doctor/SKILL.md +195 -72
  38. package/template/.claude/skills/feature/SKILL.md +195 -151
  39. package/template/.claude/skills/fix/SKILL.md +41 -90
  40. package/template/.claude/skills/idea/SKILL.md +2 -2
  41. package/template/.claude/skills/implement/SKILL.md +189 -46
  42. package/template/.claude/skills/onboard/SKILL.md +216 -85
  43. package/template/.claude/skills/overview/SKILL.md +44 -29
  44. package/template/.claude/skills/prototype/SKILL.md +82 -27
  45. package/template/.claude/skills/release/SKILL.md +119 -130
  46. package/template/.claude/skills/report-html/SKILL.md +2 -2
  47. package/template/.claude/skills/rollback/SKILL.md +123 -77
  48. package/template/.claude/skills/status/SKILL.md +109 -0
  49. package/template/.claude/skills/test/SKILL.md +2 -2
  50. package/template/.claude/skills/tests/SKILL.md +126 -0
  51. package/template/.claude/skills/try/SKILL.md +77 -65
  52. package/template/AGENTS.md +2 -1
  53. package/template/devflow/build-plan.md +8 -0
  54. package/template/devflow/history/features/README.md +5 -0
  55. package/template/devflow/history/fixes/README.md +5 -0
  56. package/template/devflow/history/rollbacks/README.md +5 -0
@@ -0,0 +1,487 @@
1
+ #!/usr/bin/env python3
2
+ """Convert any document (.xlsx, .pdf, .docx, .txt, .csv, .log, .json, .yaml, etc.)
3
+ or a directory of mixed files into clean Markdown, outputting by default into `devflow/reference/`.
4
+
5
+ Usage:
6
+ python convert_any_to_md.py <input> [-o OUTPUT] [--recursive]
7
+
8
+ Arguments:
9
+ <input> Path to a single document or a directory.
10
+ -o, --output Target output directory (default: devflow/reference).
11
+ --recursive Recursively process subdirectories when input is a directory.
12
+
13
+ Exit codes:
14
+ 0 - All requested conversions succeeded
15
+ 1 - One or more conversions failed (partial success in batch mode)
16
+ 2 - Required dependency missing
17
+ 3 - Invalid input path
18
+ """
19
+
20
+ import argparse
21
+ import hashlib
22
+ import posixpath
23
+ import re
24
+ import shutil
25
+ import sys
26
+ import zipfile
27
+ from pathlib import Path
28
+ from xml.etree import ElementTree as ET
29
+
30
+ EXIT_OK = 0
31
+ EXIT_CONVERSION_FAILED = 1
32
+ EXIT_MISSING_DEPENDENCY = 2
33
+ EXIT_INVALID_INPUT = 3
34
+
35
+ # Namespaces for OOXML
36
+ _REL_NS = "http://schemas.openxmlformats.org/package/2006/relationships"
37
+ _MAIN_NS = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
38
+ _R_NS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
39
+ _A_NS = "http://schemas.openxmlformats.org/drawingml/2006/main"
40
+
41
+ # MarkItDown placeholder pattern for docx
42
+ _PLACEHOLDER_IMAGE_RE = re.compile(
43
+ r'!\[([^\]]*)\]\(data:image/[a-zA-Z0-9.+-]+;base64[^)]*\)'
44
+ )
45
+
46
+ # Sheet header pattern for xlsx
47
+ _SHEET_HEADER_RE = re.compile(r"^## (.+)$", re.MULTILINE)
48
+
49
+ SUPPORTED_TEXT_EXTS = {
50
+ ".txt", ".log", ".csv", ".tsv", ".json", ".yaml", ".yml",
51
+ ".md", ".markdown", ".py", ".js", ".ts", ".html", ".xml", ".css"
52
+ }
53
+
54
+ IGNORE_EXTS = {
55
+ ".exe", ".dll", ".so", ".dylib", ".bin", ".zip", ".tar", ".gz",
56
+ ".7z", ".rar", ".iso", ".pyc", ".class", ".o", ".obj"
57
+ }
58
+
59
+ def get_markitdown():
60
+ """Lazy import MarkItDown if available."""
61
+ try:
62
+ from markitdown import MarkItDown
63
+ return MarkItDown()
64
+ except ImportError:
65
+ return None
66
+
67
+ # ============================================================================
68
+ # Excel Handler
69
+ # ============================================================================
70
+
71
+ def _normalize_rel_path(base_dir: str, target: str) -> str:
72
+ if target.startswith("/"):
73
+ return target.lstrip("/")
74
+ return posixpath.normpath(posixpath.join(base_dir, target))
75
+
76
+ def _xlsx_sheet_media(xlsx_path: Path):
77
+ try:
78
+ with zipfile.ZipFile(xlsx_path) as z:
79
+ names = set(z.namelist())
80
+ if "xl/workbook.xml" not in names or "xl/_rels/workbook.xml.rels" not in names:
81
+ return {}
82
+ workbook_xml = z.read("xl/workbook.xml")
83
+ workbook_rels_xml = z.read("xl/_rels/workbook.xml.rels")
84
+
85
+ sheet_rid = {}
86
+ for sheet_el in ET.fromstring(workbook_xml).iter(f"{{{_MAIN_NS}}}sheet"):
87
+ name = sheet_el.get("name")
88
+ rid = sheet_el.get(f"{{{_R_NS}}}id")
89
+ if name and rid:
90
+ sheet_rid[name] = rid
91
+
92
+ rid_target = {}
93
+ for rel in ET.fromstring(workbook_rels_xml).findall(f"{{{_REL_NS}}}Relationship"):
94
+ rid_target[rel.get("Id")] = rel.get("Target")
95
+
96
+ result = {}
97
+ for sheet_name, rid in sheet_rid.items():
98
+ target = rid_target.get(rid)
99
+ if not target:
100
+ continue
101
+ sheet_path = _normalize_rel_path("xl", target)
102
+ if sheet_path not in names or "/" not in sheet_path:
103
+ continue
104
+ sheet_dir, sheet_file = sheet_path.rsplit("/", 1)
105
+ sheet_rels_path = f"{sheet_dir}/_rels/{sheet_file}.rels"
106
+ if sheet_rels_path not in names:
107
+ continue
108
+
109
+ drawing_rid = None
110
+ for d in ET.fromstring(z.read(sheet_path)).iter(f"{{{_MAIN_NS}}}drawing"):
111
+ drawing_rid = d.get(f"{{{_R_NS}}}id")
112
+ break
113
+ if not drawing_rid:
114
+ continue
115
+
116
+ drawing_target = None
117
+ for rel in ET.fromstring(z.read(sheet_rels_path)).findall(f"{{{_REL_NS}}}Relationship"):
118
+ if rel.get("Id") == drawing_rid:
119
+ drawing_target = rel.get("Target")
120
+ break
121
+ if not drawing_target:
122
+ continue
123
+ drawing_path = _normalize_rel_path(sheet_dir, drawing_target)
124
+ if drawing_path not in names or "/" not in drawing_path:
125
+ continue
126
+ drawing_dir, drawing_file = drawing_path.rsplit("/", 1)
127
+ drawing_rels_path = f"{drawing_dir}/_rels/{drawing_file}.rels"
128
+ if drawing_rels_path not in names:
129
+ continue
130
+
131
+ drawing_rel_map = {}
132
+ for rel in ET.fromstring(z.read(drawing_rels_path)).findall(f"{{{_REL_NS}}}Relationship"):
133
+ drawing_rel_map[rel.get("Id")] = rel.get("Target")
134
+
135
+ media_paths = []
136
+ for blip in ET.fromstring(z.read(drawing_path)).iter(f"{{{_A_NS}}}blip"):
137
+ embed_rid = blip.get(f"{{{_R_NS}}}embed")
138
+ if not embed_rid:
139
+ continue
140
+ rel_target = drawing_rel_map.get(embed_rid)
141
+ if not rel_target:
142
+ continue
143
+ media_path = _normalize_rel_path(drawing_dir, rel_target)
144
+ if media_path in names:
145
+ media_paths.append(media_path)
146
+
147
+ if media_paths:
148
+ result[sheet_name] = media_paths
149
+ return result
150
+ except Exception:
151
+ return {}
152
+
153
+ def extract_xlsx_images(xlsx_path: Path, img_dir: Path):
154
+ sheet_media = _xlsx_sheet_media(xlsx_path)
155
+ if not sheet_media:
156
+ return {}
157
+
158
+ written = {}
159
+ with zipfile.ZipFile(xlsx_path) as z:
160
+ names_in_zip = set(z.namelist())
161
+ for sheet_idx, (sheet_name, media_paths) in enumerate(sheet_media.items(), start=1):
162
+ safe_name = re.sub(r"[^A-Za-z0-9_.-]+", "_", sheet_name).strip("_") or "sheet"
163
+ safe_prefix = f"sheet{sheet_idx:03d}_{safe_name}"
164
+ files = []
165
+ for idx, media_path in enumerate(media_paths, start=1):
166
+ if media_path not in names_in_zip:
167
+ continue
168
+ ext = Path(media_path).suffix.lstrip(".").lower() or "bin"
169
+ if ext == "jpg":
170
+ ext = "jpeg"
171
+ fname = f"{safe_prefix}_img{idx:03d}.{ext}"
172
+ dest = img_dir / fname
173
+ img_dir.mkdir(parents=True, exist_ok=True)
174
+ with z.open(media_path) as src, open(dest, "wb") as dst:
175
+ shutil.copyfileobj(src, dst)
176
+ files.append(fname)
177
+ if files:
178
+ written[sheet_name] = files
179
+ return written
180
+
181
+ def convert_excel(file_path: Path, out_dir: Path, md_engine) -> bool:
182
+ img_dir = out_dir / "img"
183
+ sheet_images = extract_xlsx_images(file_path, img_dir)
184
+
185
+ if md_engine:
186
+ res = md_engine.convert(str(file_path))
187
+ text = res.text_content
188
+ else:
189
+ text = f"# {file_path.stem}\n\n*(MarkItDown engine not available. Basic conversion applied)*\n"
190
+
191
+ if sheet_images:
192
+ matches = list(_SHEET_HEADER_RE.finditer(text))
193
+ if matches:
194
+ parts = []
195
+ last_end = 0
196
+ for i, m in enumerate(matches):
197
+ sheet_name = m.group(1).strip()
198
+ next_start = matches[i + 1].start() if i + 1 < len(matches) else len(text)
199
+ section = text[last_end:next_start]
200
+ last_end = next_start
201
+
202
+ if sheet_name in sheet_images:
203
+ img_lines = ["\n\n#### Images in this sheet\n"]
204
+ for fname in sheet_images[sheet_name]:
205
+ img_lines.append(f"![{sheet_name} image](img/{fname})\n")
206
+ section = section.rstrip() + "\n" + "".join(img_lines)
207
+ parts.append(section)
208
+ text = "".join(parts)
209
+
210
+ out_md = out_dir / f"{file_path.stem}.md"
211
+ out_dir.mkdir(parents=True, exist_ok=True)
212
+ out_md.write_text(text, encoding="utf-8")
213
+ return True
214
+
215
+ # ============================================================================
216
+ # PDF Handler
217
+ # ============================================================================
218
+
219
+ def extract_pdf_images(pdf_path: Path, img_dir: Path):
220
+ try:
221
+ import fitz
222
+ except ImportError:
223
+ return {}
224
+
225
+ written_by_page = {}
226
+ try:
227
+ doc = fitz.open(str(pdf_path))
228
+ except Exception:
229
+ return written_by_page
230
+
231
+ try:
232
+ for page_index in range(len(doc)):
233
+ page = doc[page_index]
234
+ page_label = page_index + 1
235
+ raw_images = []
236
+ try:
237
+ xobjects = page.get_images(full=True)
238
+ except Exception:
239
+ xobjects = []
240
+
241
+ for img in xobjects:
242
+ xref = img[0]
243
+ try:
244
+ base_image = doc.extract_image(xref)
245
+ except Exception:
246
+ continue
247
+ img_bytes = base_image.get("image") or b""
248
+ if not img_bytes:
249
+ continue
250
+ ext = (base_image.get("ext") or "png").lower()
251
+ raw_images.append((img_bytes, ext))
252
+
253
+ if not raw_images:
254
+ continue
255
+
256
+ saved_files = []
257
+ img_dir.mkdir(parents=True, exist_ok=True)
258
+ for idx, (img_bytes, ext) in enumerate(raw_images, start=1):
259
+ fname = f"page{page_label:03d}_img{idx:03d}.{ext}"
260
+ dest = img_dir / fname
261
+ dest.write_bytes(img_bytes)
262
+ saved_files.append(fname)
263
+
264
+ if saved_files:
265
+ written_by_page[page_label] = saved_files
266
+ finally:
267
+ doc.close()
268
+ return written_by_page
269
+
270
+ def convert_pdf(file_path: Path, out_dir: Path, md_engine) -> bool:
271
+ img_dir = out_dir / "img"
272
+ page_images = extract_pdf_images(file_path, img_dir)
273
+
274
+ if md_engine:
275
+ res = md_engine.convert(str(file_path))
276
+ text = res.text_content
277
+ else:
278
+ text = f"# {file_path.stem}\n\n*(MarkItDown engine not available)*\n"
279
+
280
+ if page_images:
281
+ appendix = ["\n\n## Extracted Images\n"]
282
+ for page_num in sorted(page_images.keys()):
283
+ appendix.append(f"\n### Page {page_num}\n")
284
+ for fname in page_images[page_num]:
285
+ appendix.append(f"![Page {page_num} image](img/{fname})\n")
286
+ text = text.rstrip() + "".join(appendix)
287
+
288
+ out_md = out_dir / f"{file_path.stem}.md"
289
+ out_dir.mkdir(parents=True, exist_ok=True)
290
+ out_md.write_text(text, encoding="utf-8")
291
+ return True
292
+
293
+ # ============================================================================
294
+ # Word Handler
295
+ # ============================================================================
296
+
297
+ def _word_ordered_media(docx_path: Path):
298
+ try:
299
+ with zipfile.ZipFile(docx_path) as z:
300
+ if "word/document.xml" not in z.namelist() or "word/_rels/document.xml.rels" not in z.namelist():
301
+ return []
302
+ rels_xml = z.read("word/_rels/document.xml.rels")
303
+ doc_xml = z.read("word/document.xml")
304
+ except Exception:
305
+ return []
306
+
307
+ try:
308
+ rels_root = ET.fromstring(rels_xml)
309
+ doc_root = ET.fromstring(doc_xml)
310
+ except Exception:
311
+ return []
312
+
313
+ rel_map = {rel.get("Id"): rel.get("Target") for rel in rels_root.findall(f"{{{_REL_NS}}}Relationship")}
314
+ ordered_rel_ids = []
315
+ for elem in doc_root.iter():
316
+ tag = elem.tag.rsplit("}", 1)[-1]
317
+ rid = elem.get(f"{{{_R_NS}}}embed") if tag == "blip" else (elem.get(f"{{{_R_NS}}}id") if tag == "imagedata" else None)
318
+ if rid:
319
+ ordered_rel_ids.append(rid)
320
+
321
+ ordered_media = []
322
+ for rid in ordered_rel_ids:
323
+ target = rel_map.get(rid)
324
+ if not target or "media/" not in target:
325
+ continue
326
+ media_path = target.lstrip("/") if target.startswith("/") else posixpath.normpath(target if target.startswith("word/") else posixpath.join("word", target))
327
+ ordered_media.append((rid, media_path))
328
+ return ordered_media
329
+
330
+ def extract_word_images(docx_path: Path, img_dir: Path):
331
+ ordered_media = _word_ordered_media(docx_path)
332
+ if not ordered_media:
333
+ return []
334
+
335
+ written = []
336
+ with zipfile.ZipFile(docx_path) as z:
337
+ names_in_zip = set(z.namelist())
338
+ for idx, (rid, media_path) in enumerate(ordered_media, start=1):
339
+ if media_path not in names_in_zip:
340
+ continue
341
+ ext = Path(media_path).suffix.lstrip(".").lower() or "bin"
342
+ if ext == "jpg":
343
+ ext = "jpeg"
344
+ fname = f"img{idx:03d}.{ext}"
345
+ dest = img_dir / fname
346
+ img_dir.mkdir(parents=True, exist_ok=True)
347
+ with z.open(media_path) as src, open(dest, "wb") as dst:
348
+ shutil.copyfileobj(src, dst)
349
+ written.append(fname)
350
+ return written
351
+
352
+ def convert_word(file_path: Path, out_dir: Path, md_engine) -> bool:
353
+ img_dir = out_dir / "img"
354
+ images = extract_word_images(file_path, img_dir)
355
+
356
+ if md_engine:
357
+ res = md_engine.convert(str(file_path))
358
+ text = res.text_content
359
+ else:
360
+ text = f"# {file_path.stem}\n\n*(MarkItDown engine not available)*\n"
361
+
362
+ if images:
363
+ img_idx = 0
364
+ def replace_img(match):
365
+ nonlocal img_idx
366
+ alt_text = match.group(1)
367
+ if img_idx < len(images):
368
+ fname = images[img_idx]
369
+ img_idx += 1
370
+ return f"![{alt_text}](img/{fname})"
371
+ return match.group(0)
372
+
373
+ text = _PLACEHOLDER_IMAGE_RE.sub(replace_img, text)
374
+
375
+ out_md = out_dir / f"{file_path.stem}.md"
376
+ out_dir.mkdir(parents=True, exist_ok=True)
377
+ out_md.write_text(text, encoding="utf-8")
378
+ return True
379
+
380
+ # ============================================================================
381
+ # Plaintext Handler
382
+ # ============================================================================
383
+
384
+ def convert_text(file_path: Path, out_dir: Path) -> bool:
385
+ try:
386
+ content = file_path.read_text(encoding="utf-8", errors="replace")
387
+ except Exception as exc:
388
+ print(f"ERROR reading {file_path}: {exc}", file=sys.stderr)
389
+ return False
390
+
391
+ ext = file_path.suffix.lower()
392
+ if ext in {".md", ".markdown"}:
393
+ md_text = content
394
+ else:
395
+ lang_map = {
396
+ ".py": "python", ".js": "javascript", ".ts": "typescript",
397
+ ".json": "json", ".yaml": "yaml", ".yml": "yaml",
398
+ ".html": "html", ".xml": "xml", ".css": "css", ".csv": "csv"
399
+ }
400
+ lang = lang_map.get(ext, "")
401
+ if lang:
402
+ md_text = f"# {file_path.name}\n\n```{lang}\n{content}\n```\n"
403
+ else:
404
+ md_text = f"# {file_path.name}\n\n{content}\n"
405
+
406
+ out_md = out_dir / f"{file_path.stem}.md"
407
+ out_dir.mkdir(parents=True, exist_ok=True)
408
+ out_md.write_text(md_text, encoding="utf-8")
409
+ return True
410
+
411
+ # ============================================================================
412
+ # Main Router
413
+ # ============================================================================
414
+
415
+ def convert_single_file(file_path: Path, base_out_dir: Path, md_engine) -> bool:
416
+ ext = file_path.suffix.lower()
417
+ out_dir = base_out_dir / file_path.stem
418
+
419
+ if ext in IGNORE_EXTS:
420
+ return True
421
+
422
+ print(f"Converting [{ext or 'text'}] {file_path.name} -> {out_dir}")
423
+
424
+ if ext == ".xlsx":
425
+ return convert_excel(file_path, out_dir, md_engine)
426
+ elif ext == ".pdf":
427
+ return convert_pdf(file_path, out_dir, md_engine)
428
+ elif ext == ".docx":
429
+ return convert_word(file_path, out_dir, md_engine)
430
+ elif ext in SUPPORTED_TEXT_EXTS or ext == "":
431
+ return convert_text(file_path, out_dir)
432
+ else:
433
+ # Fallback to text conversion for unknown extensions
434
+ return convert_text(file_path, out_dir)
435
+
436
+ def main():
437
+ parser = argparse.ArgumentParser(
438
+ description="Convert documents to Markdown and save to devflow/reference/"
439
+ )
440
+ parser.add_argument("input", help="Path to a file or directory to convert")
441
+ parser.add_argument(
442
+ "-o", "--output",
443
+ default="devflow/reference",
444
+ help="Target output directory (default: devflow/reference)"
445
+ )
446
+ parser.add_argument(
447
+ "--recursive",
448
+ action="store_true",
449
+ help="Recursively process subdirectories"
450
+ )
451
+
452
+ args = parser.parse_args()
453
+ input_path = Path(args.input).resolve()
454
+ base_out_dir = Path(args.output).resolve()
455
+
456
+ if not input_path.exists():
457
+ print(f"ERROR: Input path not found: {input_path}", file=sys.stderr)
458
+ sys.exit(EXIT_INVALID_INPUT)
459
+
460
+ md_engine = get_markitdown()
461
+
462
+ if input_path.is_file():
463
+ success = convert_single_file(input_path, base_out_dir, md_engine)
464
+ sys.exit(EXIT_OK if success else EXIT_CONVERSION_FAILED)
465
+ elif input_path.is_dir():
466
+ pattern = "**/*" if args.recursive else "*"
467
+ files = [p for p in input_path.glob(pattern) if p.is_file()]
468
+
469
+ if not files:
470
+ print(f"No files found in {input_path}")
471
+ sys.exit(EXIT_OK)
472
+
473
+ success_count = 0
474
+ fail_count = 0
475
+ for f in files:
476
+ if f.suffix.lower() in IGNORE_EXTS:
477
+ continue
478
+ if convert_single_file(f, base_out_dir, md_engine):
479
+ success_count += 1
480
+ else:
481
+ fail_count += 1
482
+
483
+ print(f"\nBatch summary: {success_count} succeeded, {fail_count} failed.")
484
+ sys.exit(EXIT_OK if fail_count == 0 else EXIT_CONVERSION_FAILED)
485
+
486
+ if __name__ == "__main__":
487
+ main()
@@ -0,0 +1,3 @@
1
+ markitdown[xlsx]
2
+ pymupdf
3
+ openpyxl
@@ -1,70 +1,145 @@
1
1
  ---
2
2
  name: debug
3
- description: "[Devflow] Root cause investigation and diagnostic loop before or during implementation without editing code. Use when encountering broken behavior, test failures, or bugs."
3
+ description: "[devflow][B] Diagnose a failing test, broken build, crash, error, regression, or unexpected behavior without editing source or Blueprint state. Reproduces the symptom with the smallest safe command, localizes the failing path, tests competing hypotheses, identifies the root cause when evidence supports one, and reports a repair handoff to /fix or /implement. Use when the user runs /debug, invokes $debug, asks why something is failing or broken, wants a root-cause investigation, or asks to diagnose before fixing."
4
4
  ---
5
5
 
6
- # Debug & Root Cause Analysis (RCA)
6
+ # debug - find the cause before changing the code
7
7
 
8
- ## Overview
8
+ Where this sits in the workflow:
9
9
 
10
- This is the comprehensive debugging master skill for Nexus-DevFlow. It guides systematic root-cause investigation without blindly editing code. The goal is to find the actual origin of an issue, not merely suppress visible symptoms.
10
+ reported failure -> [debug] -> /fix or /implement
11
+ (test, build, (reproduce, (spec a new fix, or
12
+ crash, behavior) isolate, repair active work)
13
+ explain)
11
14
 
12
- **The Debug Mantra (9arm Pattern)**:
13
- ```text
14
- Reproduce Trace Fail Path Falsify Hypotheses ➔ Cross-reference Breadcrumbs ➔ RCA Proof
15
- ```
15
+ `/debug` separates diagnosis from repair. It gathers evidence, narrows the
16
+ failure to a specific cause when possible, and stops with a useful handoff. It
17
+ does not make the code "temporarily work" while investigating.
16
18
 
17
- ---
19
+ ## Input
18
20
 
19
- ## 1. The 4-Phase Diagnostic Loop
21
+ Accept a symptom, failing command, error message, or unexpected behavior. Examples:
20
22
 
21
- ### Phase 1: Reproduce & Classify
22
- - Restate the observed symptom vs. expected behavior with exact steps.
23
- - Create a minimal reproduction script, test case, or curl command.
24
- - Rule: **Do not propose a code fix before the reproduction story is verified.**
23
+ /debug npm test fails in cart-total.test.js
24
+ /debug the upload route returns 500 for PNG files
25
+ /debug why does the production build fail?
25
26
 
26
- ### Phase 2: Isolate & Hypothesize
27
- - Generate 2–4 distinct hypotheses ranked by probability.
28
- - Formulate specific criteria and evidence that would *falsify* each hypothesis.
27
+ With no useful symptom, ask for the expected behavior, actual behavior, and
28
+ smallest known reproduction. Do not guess which problem the user means.
29
29
 
30
- ### Phase 3: Non-Destructive Investigation
31
- - Trace code execution paths end-to-end (stack traces, logs, variable states, async boundaries).
32
- - Inspect recent commits or configuration changes that touch the affected boundary.
33
- - Test hypotheses methodically using tests and logging without altering business logic.
30
+ ## Step 1 - establish the boundary
34
31
 
35
- ### Phase 4: Root Cause Conclusion (RCA)
36
- - State precisely *why* the bug occurred (underlying invariant violation).
37
- - Define the minimal, robust architectural fix direction.
38
- - Propose regression prevention measures (unit test, type guard, linter rule).
32
+ Read the project instructions and the context relevant to the failure:
39
33
 
40
- ---
34
+ - `AGENTS.md` and its real commands
35
+ - `devflow/context/project-overview.md`
36
+ - `devflow/context/coding-standards.md`
37
+ - `devflow/context/current-feature.md`
38
+ - the reported error, failing output, and affected files
39
+ - git status, diff, and recent log when a regression is possible
41
40
 
42
- ## 2. Output Format (RCA Report)
41
+ State the symptom and what would count as reproducing it. Note whether the
42
+ failure belongs to an active feature or is an unplanned bug.
43
43
 
44
- Save substantial RCA investigations under:
45
- ```text
46
- devflow/debug/rca-{slug}.md
47
- ```
44
+ Do not treat a dirty working tree as permission to discard or rewrite anything.
45
+ Use the diff as evidence and preserve it.
48
46
 
49
- Structure:
50
- ```markdown
51
- ## Debug Summary
47
+ ## Step 2 - reproduce safely
52
48
 
53
- 1. **Symptom**: [What is happening vs expected]
54
- 2. **Evidence**: [Error logs, stack trace, file:line references]
55
- 3. **Investigation Path**: [Hypotheses tested and falsification proof]
56
- 4. **Root Cause**: [The exact mechanism causing the failure]
57
- 5. **Fix Direction**: [Recommended scoped change]
58
- 6. **Regression Guard**: [Reproduction test to add before fixing]
59
- ```
49
+ Run the smallest existing command or interaction that can reproduce the symptom.
60
50
 
61
- ---
51
+ - Prefer one focused test, request, CLI command, or input over the entire suite.
52
+ - Capture the exact exit code, error, stack trace, output, response, console
53
+ error, or failed request.
54
+ - Reuse an already-running local app when available. If reproduction requires a
55
+ long-running server that is not running, ask the user to start it and provide
56
+ the documented command.
57
+ - Do not install dependencies, change configuration, run migrations, mutate
58
+ production data, contact external users, or use destructive commands to force
59
+ a reproduction.
60
+ - Do not edit code to add logs or probes. Use existing logs, debuggers,
61
+ read-only inspection, or one-off commands that do not change project files.
62
+ - Compare git status after diagnostic commands. If one changes tracked or
63
+ untracked project files, stop and report those paths. Do not clean, restore,
64
+ or hide the changes.
65
+
66
+ If the symptom cannot be reproduced, say what was attempted and what evidence is
67
+ missing. Continue with static investigation only when it can produce a clearly
68
+ labeled hypothesis, not a claimed root cause.
69
+
70
+ ## Step 3 - localize the failure
71
+
72
+ Trace from the observed failure toward the smallest responsible area.
73
+
74
+ Use the evidence that fits the project:
75
+
76
+ - the first relevant application frame in a stack trace
77
+ - the smallest failing test and its inputs
78
+ - request and response data at the failing boundary
79
+ - console and network errors
80
+ - callers, imports, data flow, and configuration reads
81
+ - `git diff`, `git log`, and `git blame` for a suspected regression
82
+ - comparison with a nearby working path or input
83
+
84
+ Separate facts from hypotheses. Test the cheapest safe competing explanations
85
+ first. Do not stop at the first plausible line, blame a dependency without
86
+ evidence, or confuse the place an error surfaced with the place it originated.
87
+
88
+ ## Step 4 - confirm or narrow
89
+
90
+ A root cause is confirmed only when the evidence connects all three:
91
+
92
+ 1. the triggering input or state
93
+ 2. the responsible code, configuration, or contract
94
+ 3. the observed failure
95
+
96
+ When safe and read-only, vary one input or run a smaller focused command to
97
+ confirm the connection. Do not change implementation or tests to prove the fix.
98
+
99
+ Use one of these verdicts:
100
+
101
+ - **Confirmed** - evidence identifies the cause and explains the failure.
102
+ - **Likely** - evidence narrows the cause, but one specific proof is unavailable.
103
+ - **Blocked** - the failure cannot be reproduced or required evidence is
104
+ inaccessible.
105
+
106
+ ## Step 5 - report and hand off
107
+
108
+ Give a concise debug report:
109
+
110
+ - symptom and reproduction
111
+ - verdict
112
+ - root cause or leading hypothesis
113
+ - evidence, including commands and relevant paths
114
+ - affected behavior and likely repair boundary
115
+ - what was not verified
116
+ - exact next action
117
+
118
+ Choose the next action without writing files:
119
+
120
+ - Active feature or fix caused the failure -> return the diagnosis to
121
+ `/implement`.
122
+ - No active work item and the bug is confirmed -> recommend
123
+ `/fix "<concise bug and confirmed cause>"`.
124
+ - Cause is only likely or blocked -> recommend the next diagnostic evidence, not
125
+ a speculative repair.
126
+ - The issue is planned product work rather than a defect -> point to
127
+ `/feature`.
62
128
 
63
- ## Relationship To DevFlow 2.0
129
+ ## Rules
130
+
131
+ - Diagnose, do not repair. Never edit source, tests, configuration, lockfiles, or
132
+ Blueprint files.
133
+ - Never create, switch, merge, or delete branches. Never commit or push.
134
+ - Do not update the findings ledger. `/audit` owns recorded code-quality
135
+ findings; `/debug` reports one investigated failure in chat.
136
+ - Evidence outranks confidence. Label uncertainty and failed reproduction
137
+ honestly.
138
+ - Preserve the user's working tree and running processes.
139
+ - Do not broaden one failure into a general audit or refactor.
64
140
 
65
- - **Classification**: Companion command & Investigation lane
66
- - **Mainline integration**:
67
- - During `00-explore`: Unclear failure intake before allocation
68
- - During `40-execute`: Hard test failure or unexpected runtime exception
69
- - During `50-verify`: Defect found during QA inspection
70
- - **Handoff**: `test` (write repro test), `40-execute` (execute fix), `50-verify` (re-check)
141
+ ## Formatting
142
+
143
+ Format the output to match the project's conventions in
144
+ `devflow/context/ai-interaction.md`: concise, scannable markdown with a short
145
+ evidence list and a clear next action.