mindsystem-cc 4.4.2 → 4.5.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.
- package/README.md +17 -8
- package/agents/ms-designer.md +25 -47
- package/agents/ms-executor.md +1 -1
- package/agents/ms-mockup-designer.md +7 -4
- package/agents/ms-plan-checker.md +32 -27
- package/agents/ms-plan-writer.md +12 -8
- package/commands/ms/adhoc.md +11 -1
- package/commands/ms/config.md +47 -9
- package/commands/ms/design-phase.md +83 -63
- package/commands/ms/discuss-phase.md +1 -0
- package/commands/ms/doctor.md +7 -3
- package/commands/ms/execute-phase.md +1 -5
- package/commands/ms/help.md +6 -5
- package/commands/ms/remove-phase.md +7 -25
- package/commands/ms/research-phase.md +13 -0
- package/commands/ms/review-design.md +1 -1
- package/commands/ms/verify-work.md +1 -3
- package/mindsystem/references/design-directions.md +2 -2
- package/mindsystem/references/plan-format.md +2 -13
- package/mindsystem/references/prework-status.md +6 -32
- package/mindsystem/references/routing/next-phase-routing.md +7 -41
- package/mindsystem/references/scope-estimation.md +8 -4
- package/mindsystem/templates/config.json +6 -0
- package/mindsystem/templates/design.md +1 -1
- package/mindsystem/workflows/adhoc.md +63 -0
- package/mindsystem/workflows/discuss-phase.md +12 -0
- package/mindsystem/workflows/doctor-fixes.md +71 -0
- package/mindsystem/workflows/execute-phase.md +19 -6
- package/mindsystem/workflows/execute-plan.md +1 -7
- package/mindsystem/workflows/mockup-generation.md +1 -1
- package/mindsystem/workflows/plan-phase.md +41 -77
- package/mindsystem/workflows/verify-work.md +8 -77
- package/package.json +1 -1
- package/scripts/ms-tools.py +481 -0
- package/agents/ms-verify-fixer.md +0 -125
package/scripts/ms-tools.py
CHANGED
|
@@ -168,6 +168,14 @@ def parse_roadmap_phases(roadmap_path: Path) -> list[tuple[str, str]]:
|
|
|
168
168
|
return results
|
|
169
169
|
|
|
170
170
|
|
|
171
|
+
def _phase_sort_key(phase_str: str) -> float:
|
|
172
|
+
"""Convert phase string to sortable float: '17' -> 17.0, '17.1' -> 17.1."""
|
|
173
|
+
try:
|
|
174
|
+
return float(phase_str)
|
|
175
|
+
except ValueError:
|
|
176
|
+
return float("inf")
|
|
177
|
+
|
|
178
|
+
|
|
171
179
|
def run_git(*args: str) -> str:
|
|
172
180
|
"""Run a git command and return stdout. Raise on failure."""
|
|
173
181
|
result = subprocess.run(
|
|
@@ -1413,6 +1421,121 @@ def cmd_doctor_scan(args: argparse.Namespace) -> None:
|
|
|
1413
1421
|
record("WARN", "Screenshot Optimization")
|
|
1414
1422
|
print()
|
|
1415
1423
|
|
|
1424
|
+
# ---- CHECK 13: Roadmap Format ----
|
|
1425
|
+
print("=== Roadmap Format ===")
|
|
1426
|
+
roadmap_path = planning / "ROADMAP.md"
|
|
1427
|
+
if not roadmap_path.is_file():
|
|
1428
|
+
print("Status: SKIP")
|
|
1429
|
+
print("No ROADMAP.md found")
|
|
1430
|
+
record("SKIP", "Roadmap Format")
|
|
1431
|
+
else:
|
|
1432
|
+
roadmap_text = roadmap_path.read_text(encoding="utf-8")
|
|
1433
|
+
all_phases = parse_roadmap_phases(roadmap_path)
|
|
1434
|
+
|
|
1435
|
+
if not all_phases:
|
|
1436
|
+
print("Status: SKIP")
|
|
1437
|
+
print("No phases found in ROADMAP.md")
|
|
1438
|
+
record("SKIP", "Roadmap Format")
|
|
1439
|
+
else:
|
|
1440
|
+
# Find incomplete phases — check overview checklist
|
|
1441
|
+
completed_phases: set[str] = set()
|
|
1442
|
+
for line in roadmap_text.splitlines():
|
|
1443
|
+
done_match = re.match(
|
|
1444
|
+
r"^-\s*\[x\]\s*\*\*Phase\s+(\d+(?:\.\d+)?)", line
|
|
1445
|
+
)
|
|
1446
|
+
if done_match:
|
|
1447
|
+
completed_phases.add(done_match.group(1))
|
|
1448
|
+
|
|
1449
|
+
phases_to_check = [
|
|
1450
|
+
(num, name)
|
|
1451
|
+
for num, name in all_phases
|
|
1452
|
+
if num not in completed_phases
|
|
1453
|
+
]
|
|
1454
|
+
|
|
1455
|
+
if not phases_to_check:
|
|
1456
|
+
print("Status: PASS")
|
|
1457
|
+
print("All phases completed — no pre-work flags to validate")
|
|
1458
|
+
record("PASS", "Roadmap Format")
|
|
1459
|
+
else:
|
|
1460
|
+
issues: list[str] = []
|
|
1461
|
+
for num, name in phases_to_check:
|
|
1462
|
+
padded = normalize_phase(num)
|
|
1463
|
+
info = _parse_phase_section(roadmap_text, padded)
|
|
1464
|
+
if info is None:
|
|
1465
|
+
issues.append(f"Phase {num}: no detail section found")
|
|
1466
|
+
continue
|
|
1467
|
+
for flag in ("discuss", "design", "research"):
|
|
1468
|
+
pw = info["prework"][flag]
|
|
1469
|
+
if pw.get("status") == "parse_error":
|
|
1470
|
+
issues.append(
|
|
1471
|
+
f"Phase {num} ({name}): {flag.capitalize()} flag missing or malformed"
|
|
1472
|
+
)
|
|
1473
|
+
|
|
1474
|
+
if issues:
|
|
1475
|
+
print("Status: FAIL")
|
|
1476
|
+
print(f"{len(issues)} pre-work flag issue(s):")
|
|
1477
|
+
for issue in issues:
|
|
1478
|
+
print(f" - {issue}")
|
|
1479
|
+
record("FAIL", "Roadmap Format")
|
|
1480
|
+
else:
|
|
1481
|
+
print("Status: PASS")
|
|
1482
|
+
print(
|
|
1483
|
+
f"All {len(phases_to_check)} incomplete phase(s) have valid pre-work flags"
|
|
1484
|
+
)
|
|
1485
|
+
record("PASS", "Roadmap Format")
|
|
1486
|
+
print()
|
|
1487
|
+
|
|
1488
|
+
# ---- CHECK 14: Phase Skills ----
|
|
1489
|
+
print("=== Phase Skills ===")
|
|
1490
|
+
skills_config = config.get("skills", {})
|
|
1491
|
+
plan_skills = skills_config.get("plan", []) if isinstance(skills_config, dict) else []
|
|
1492
|
+
design_skills = skills_config.get("design", []) if isinstance(skills_config, dict) else []
|
|
1493
|
+
skill_warnings: list[str] = []
|
|
1494
|
+
|
|
1495
|
+
if not plan_skills:
|
|
1496
|
+
skill_warnings.append("plan")
|
|
1497
|
+
print("skills.plan: not configured")
|
|
1498
|
+
print(" Impact: Plan-phase code quality — the highest-leverage skill slot")
|
|
1499
|
+
print(" What to add: A code quality skill encoding your project's framework")
|
|
1500
|
+
print(" best practices. Include rules for common pitfalls, idiomatic patterns,")
|
|
1501
|
+
print(" performance gotchas, and structural conventions specific to your stack.")
|
|
1502
|
+
print(" The executor runs a multi-pass review after implementation, catching")
|
|
1503
|
+
print(" framework misuse and structural problems before they reach verification.")
|
|
1504
|
+
print(" Ideal structure: A SKILL.md with categorized rules (reactivity, typing,")
|
|
1505
|
+
print(" performance, composition) plus reference files with bad/good code examples")
|
|
1506
|
+
print(" for each category. The agent selectively reads only rules relevant to the")
|
|
1507
|
+
print(" changed code, keeping context usage efficient.")
|
|
1508
|
+
print(" Set up: Create a skill with framework-specific rules and reference files,")
|
|
1509
|
+
print(" then run /ms:config to add it to skills.plan")
|
|
1510
|
+
else:
|
|
1511
|
+
print(f"skills.plan: {', '.join(plan_skills)}")
|
|
1512
|
+
|
|
1513
|
+
if not design_skills:
|
|
1514
|
+
skill_warnings.append("design")
|
|
1515
|
+
print("skills.design: not configured")
|
|
1516
|
+
print(" Impact: Design-phase quality — ensures generated designs match your")
|
|
1517
|
+
print(" existing design system instead of generic AI output")
|
|
1518
|
+
print(" What to add: A skill describing your project's design system — color")
|
|
1519
|
+
print(" palette, typography, spacing scale, reusable components, and layout")
|
|
1520
|
+
print(" conventions. The designer agent uses this to produce designs that feel")
|
|
1521
|
+
print(" native to your product rather than starting from scratch.")
|
|
1522
|
+
print(" Ideal structure: Document your design tokens (colors, fonts, sizes),")
|
|
1523
|
+
print(" component inventory (buttons, cards, inputs with their variants), and")
|
|
1524
|
+
print(" brand guidelines (visual tone, density preference, platform conventions).")
|
|
1525
|
+
print(" Set up: Create a skill with your design tokens and component inventory,")
|
|
1526
|
+
print(" then run /ms:config to add it to skills.design")
|
|
1527
|
+
else:
|
|
1528
|
+
print(f"skills.design: {', '.join(design_skills)}")
|
|
1529
|
+
|
|
1530
|
+
if skill_warnings:
|
|
1531
|
+
print(f"Status: WARN")
|
|
1532
|
+
record("WARN", "Phase Skills")
|
|
1533
|
+
else:
|
|
1534
|
+
print("Status: PASS")
|
|
1535
|
+
print("Plan and design phase skills configured")
|
|
1536
|
+
record("PASS", "Phase Skills")
|
|
1537
|
+
print()
|
|
1538
|
+
|
|
1416
1539
|
# ---- SUMMARY ----
|
|
1417
1540
|
total = pass_count + warn_count + fail_count + skip_count
|
|
1418
1541
|
print("=== Summary ===")
|
|
@@ -1464,6 +1587,140 @@ def cmd_create_phase_dirs(args: argparse.Namespace) -> None:
|
|
|
1464
1587
|
print(f"\n{created} created, {skipped} skipped (already exist)")
|
|
1465
1588
|
|
|
1466
1589
|
|
|
1590
|
+
# ===================================================================
|
|
1591
|
+
# Subcommand: phase-renumber
|
|
1592
|
+
# ===================================================================
|
|
1593
|
+
|
|
1594
|
+
|
|
1595
|
+
def cmd_phase_renumber(args: argparse.Namespace) -> None:
|
|
1596
|
+
"""Renumber phase directories and files after phase removal.
|
|
1597
|
+
|
|
1598
|
+
Contract:
|
|
1599
|
+
Args: phase (str, the removed phase number), --dry-run (bool)
|
|
1600
|
+
Output: JSON report of all renames
|
|
1601
|
+
Exit codes: 0 = success, 1 = error
|
|
1602
|
+
Side effects: renames directories and files (unless --dry-run)
|
|
1603
|
+
"""
|
|
1604
|
+
removed = normalize_phase(args.phase)
|
|
1605
|
+
is_decimal = "." in removed
|
|
1606
|
+
dry_run = args.dry_run
|
|
1607
|
+
|
|
1608
|
+
git_root = find_git_root()
|
|
1609
|
+
planning = git_root / ".planning"
|
|
1610
|
+
phases_dir = planning / "phases"
|
|
1611
|
+
|
|
1612
|
+
if not phases_dir.is_dir():
|
|
1613
|
+
print("Error: .planning/phases/ directory not found", file=sys.stderr)
|
|
1614
|
+
sys.exit(1)
|
|
1615
|
+
|
|
1616
|
+
# Precondition: removed phase dir must not exist
|
|
1617
|
+
if find_phase_dir(planning, removed) is not None:
|
|
1618
|
+
print(
|
|
1619
|
+
f"Error: Directory for phase {removed} still exists. "
|
|
1620
|
+
"Delete it before renumbering.",
|
|
1621
|
+
file=sys.stderr,
|
|
1622
|
+
)
|
|
1623
|
+
sys.exit(1)
|
|
1624
|
+
|
|
1625
|
+
# Parse removed phase components
|
|
1626
|
+
removed_float = _phase_sort_key(removed)
|
|
1627
|
+
if is_decimal:
|
|
1628
|
+
removed_parent = int(removed_float) # e.g., 17.1 -> 17
|
|
1629
|
+
else:
|
|
1630
|
+
removed_int = int(removed_float) # e.g., 17
|
|
1631
|
+
|
|
1632
|
+
# Scan all phase dirs and compute renames
|
|
1633
|
+
renames: list[tuple[str, str, Path]] = [] # (old_phase, new_phase, dir_path)
|
|
1634
|
+
for d in sorted(phases_dir.iterdir()):
|
|
1635
|
+
if not d.is_dir():
|
|
1636
|
+
continue
|
|
1637
|
+
parts = d.name.split("-", 1)
|
|
1638
|
+
phase_prefix = parts[0]
|
|
1639
|
+
phase_float = _phase_sort_key(phase_prefix)
|
|
1640
|
+
if phase_float == float("inf"):
|
|
1641
|
+
continue # not a phase dir
|
|
1642
|
+
|
|
1643
|
+
is_phase_decimal = "." in phase_prefix
|
|
1644
|
+
|
|
1645
|
+
if is_decimal:
|
|
1646
|
+
# Decimal removal: only subsequent decimals in same series
|
|
1647
|
+
if not is_phase_decimal:
|
|
1648
|
+
continue
|
|
1649
|
+
phase_parent = int(phase_float)
|
|
1650
|
+
if phase_parent != removed_parent:
|
|
1651
|
+
continue
|
|
1652
|
+
if phase_float <= removed_float:
|
|
1653
|
+
continue
|
|
1654
|
+
# Decrement decimal: 17.2 -> 17.1, 17.3 -> 17.2
|
|
1655
|
+
old_decimal = int(phase_prefix.split(".")[1])
|
|
1656
|
+
new_decimal = old_decimal - 1
|
|
1657
|
+
new_phase = normalize_phase(f"{phase_parent}.{new_decimal}")
|
|
1658
|
+
renames.append((phase_prefix, new_phase, d))
|
|
1659
|
+
else:
|
|
1660
|
+
# Integer removal: all dirs with phase > removed decrement by 1
|
|
1661
|
+
if phase_float <= removed_float:
|
|
1662
|
+
continue
|
|
1663
|
+
if is_phase_decimal:
|
|
1664
|
+
# Decimal under higher integer: 18.1 -> 17.1
|
|
1665
|
+
parent = int(phase_float)
|
|
1666
|
+
dec = phase_prefix.split(".")[1]
|
|
1667
|
+
new_parent = parent - 1
|
|
1668
|
+
new_phase = normalize_phase(f"{new_parent}.{dec}")
|
|
1669
|
+
else:
|
|
1670
|
+
# Integer: 18 -> 17
|
|
1671
|
+
new_phase = normalize_phase(str(int(phase_float) - 1))
|
|
1672
|
+
renames.append((phase_prefix, new_phase, d))
|
|
1673
|
+
|
|
1674
|
+
# Sort ascending by phase key — ascending is correct because the removed
|
|
1675
|
+
# phase slot is free, so each rename fills the slot vacated by the previous
|
|
1676
|
+
renames.sort(key=lambda r: _phase_sort_key(r[0]))
|
|
1677
|
+
|
|
1678
|
+
# Collision detection: check every target path before renaming
|
|
1679
|
+
source_paths = {r[2] for r in renames}
|
|
1680
|
+
for old_phase, new_phase, dir_path in renames:
|
|
1681
|
+
suffix = dir_path.name.split("-", 1)[1] if "-" in dir_path.name else ""
|
|
1682
|
+
target_name = f"{new_phase}-{suffix}" if suffix else new_phase
|
|
1683
|
+
target_path = phases_dir / target_name
|
|
1684
|
+
if target_path.exists() and target_path not in source_paths:
|
|
1685
|
+
print(
|
|
1686
|
+
f"Error: Collision — renaming {dir_path.name} to {target_name} "
|
|
1687
|
+
f"would overwrite existing directory",
|
|
1688
|
+
file=sys.stderr,
|
|
1689
|
+
)
|
|
1690
|
+
sys.exit(1)
|
|
1691
|
+
|
|
1692
|
+
# Execute renames
|
|
1693
|
+
dir_renames = []
|
|
1694
|
+
file_renames = []
|
|
1695
|
+
|
|
1696
|
+
for old_phase, new_phase, dir_path in renames:
|
|
1697
|
+
suffix = dir_path.name.split("-", 1)[1] if "-" in dir_path.name else ""
|
|
1698
|
+
new_dir_name = f"{new_phase}-{suffix}" if suffix else new_phase
|
|
1699
|
+
new_dir_path = phases_dir / new_dir_name
|
|
1700
|
+
|
|
1701
|
+
dir_renames.append({"old": dir_path.name, "new": new_dir_name})
|
|
1702
|
+
|
|
1703
|
+
if not dry_run:
|
|
1704
|
+
dir_path.rename(new_dir_path)
|
|
1705
|
+
|
|
1706
|
+
# Rename files matching {old_phase}-* inside the directory
|
|
1707
|
+
scan_dir = new_dir_path if not dry_run else dir_path
|
|
1708
|
+
for f in sorted(scan_dir.iterdir()):
|
|
1709
|
+
if f.is_file() and f.name.startswith(f"{old_phase}-"):
|
|
1710
|
+
new_file_name = f"{new_phase}-{f.name[len(old_phase) + 1:]}"
|
|
1711
|
+
file_renames.append({"directory": new_dir_name, "old": f.name, "new": new_file_name})
|
|
1712
|
+
if not dry_run:
|
|
1713
|
+
f.rename(scan_dir / new_file_name)
|
|
1714
|
+
|
|
1715
|
+
report = {
|
|
1716
|
+
"removed_phase": removed,
|
|
1717
|
+
"dry_run": dry_run,
|
|
1718
|
+
"directory_renames": dir_renames,
|
|
1719
|
+
"file_renames": file_renames,
|
|
1720
|
+
}
|
|
1721
|
+
print(json.dumps(report, indent=2))
|
|
1722
|
+
|
|
1723
|
+
|
|
1467
1724
|
# ===================================================================
|
|
1468
1725
|
# Subcommand: gather-milestone-stats
|
|
1469
1726
|
# ===================================================================
|
|
@@ -2217,6 +2474,219 @@ def cmd_list_artifacts(args: argparse.Namespace) -> None:
|
|
|
2217
2474
|
sys.stdout.write("\n")
|
|
2218
2475
|
|
|
2219
2476
|
|
|
2477
|
+
# ===================================================================
|
|
2478
|
+
# Subcommand: prework-status
|
|
2479
|
+
# ===================================================================
|
|
2480
|
+
|
|
2481
|
+
|
|
2482
|
+
def _parse_phase_section(roadmap_text: str, phase: str) -> dict[str, Any] | None:
|
|
2483
|
+
"""Parse a phase section from ROADMAP.md for pre-work flags.
|
|
2484
|
+
|
|
2485
|
+
Returns dict with name, goal, and prework flags, or None if phase not found.
|
|
2486
|
+
"""
|
|
2487
|
+
# Try both padded ("08") and unpadded ("8") forms
|
|
2488
|
+
raw_match = re.match(r"^0*(\d.*)", phase)
|
|
2489
|
+
raw = raw_match.group(1) if raw_match else phase
|
|
2490
|
+
candidates = [phase] if raw == phase else [phase, raw]
|
|
2491
|
+
|
|
2492
|
+
match = None
|
|
2493
|
+
for candidate in candidates:
|
|
2494
|
+
pattern = rf"### Phase\s+{re.escape(candidate)}:\s*(.+)"
|
|
2495
|
+
match = re.search(pattern, roadmap_text)
|
|
2496
|
+
if match:
|
|
2497
|
+
break
|
|
2498
|
+
|
|
2499
|
+
if not match:
|
|
2500
|
+
return None
|
|
2501
|
+
|
|
2502
|
+
phase_name = match.group(1).strip()
|
|
2503
|
+
|
|
2504
|
+
# Extract section text until next "### " or end
|
|
2505
|
+
start = match.start()
|
|
2506
|
+
next_section = re.search(r"\n### ", roadmap_text[start + 1:])
|
|
2507
|
+
if next_section:
|
|
2508
|
+
section = roadmap_text[start : start + 1 + next_section.start()]
|
|
2509
|
+
else:
|
|
2510
|
+
section = roadmap_text[start:]
|
|
2511
|
+
|
|
2512
|
+
# Extract goal
|
|
2513
|
+
goal_match = re.search(r"\*\*Goal\*\*:\s*(.+)", section)
|
|
2514
|
+
goal = goal_match.group(1).strip() if goal_match else ""
|
|
2515
|
+
|
|
2516
|
+
# Extract pre-work flags with two-tier detection:
|
|
2517
|
+
# 1. Keyword check: does **Flag** appear at all?
|
|
2518
|
+
# 2. Full regex: does it match "Likely/Unlikely (reason)"?
|
|
2519
|
+
detail_keys = {"Discuss": "topics", "Design": "focus", "Research": "topics"}
|
|
2520
|
+
prework: dict[str, dict[str, str]] = {}
|
|
2521
|
+
|
|
2522
|
+
for flag_type, detail_key in detail_keys.items():
|
|
2523
|
+
# Tier 1: keyword presence check (case-insensitive)
|
|
2524
|
+
keyword_present = bool(
|
|
2525
|
+
re.search(rf"\*\*{flag_type}\*\*", section, re.IGNORECASE)
|
|
2526
|
+
)
|
|
2527
|
+
|
|
2528
|
+
# Tier 2: full regex match (case-insensitive, greedy reason capture)
|
|
2529
|
+
flag_match = re.search(
|
|
2530
|
+
rf"\*\*{flag_type}\*\*:\s*(Likely|Unlikely)(?:\s*\((.+)\))?",
|
|
2531
|
+
section,
|
|
2532
|
+
re.IGNORECASE,
|
|
2533
|
+
)
|
|
2534
|
+
|
|
2535
|
+
if flag_match:
|
|
2536
|
+
recommended = flag_match.group(1).capitalize()
|
|
2537
|
+
reason = (flag_match.group(2) or "").strip()
|
|
2538
|
+
status = "ok"
|
|
2539
|
+
elif keyword_present:
|
|
2540
|
+
# Keyword found but format doesn't match — non-standard
|
|
2541
|
+
recommended = ""
|
|
2542
|
+
reason = ""
|
|
2543
|
+
status = "parse_error"
|
|
2544
|
+
else:
|
|
2545
|
+
# Keyword absent — older roadmap format
|
|
2546
|
+
recommended = ""
|
|
2547
|
+
reason = ""
|
|
2548
|
+
status = "parse_error"
|
|
2549
|
+
|
|
2550
|
+
# Extract detail line (topics/focus)
|
|
2551
|
+
detail = ""
|
|
2552
|
+
if recommended == "Likely":
|
|
2553
|
+
detail_match = re.search(
|
|
2554
|
+
rf"\*\*{flag_type} {detail_key}\*\*:\s*(.+)", section, re.IGNORECASE
|
|
2555
|
+
)
|
|
2556
|
+
if detail_match:
|
|
2557
|
+
detail = detail_match.group(1).strip()
|
|
2558
|
+
|
|
2559
|
+
prework[flag_type.lower()] = {
|
|
2560
|
+
"recommended": recommended,
|
|
2561
|
+
"reason": reason,
|
|
2562
|
+
"detail": detail,
|
|
2563
|
+
"status": status,
|
|
2564
|
+
}
|
|
2565
|
+
|
|
2566
|
+
return {"name": phase_name, "goal": goal, "prework": prework}
|
|
2567
|
+
|
|
2568
|
+
|
|
2569
|
+
def _determine_prework_suggestion(
|
|
2570
|
+
prework: dict[str, dict[str, str]],
|
|
2571
|
+
has_context: bool,
|
|
2572
|
+
has_design: bool,
|
|
2573
|
+
has_research: bool,
|
|
2574
|
+
) -> tuple[str, str]:
|
|
2575
|
+
"""Apply routing logic to determine next suggested command.
|
|
2576
|
+
|
|
2577
|
+
Returns (command_name, reason) tuple. Flags with parse errors are skipped
|
|
2578
|
+
in the routing waterfall (never treated as Likely or Unlikely).
|
|
2579
|
+
"""
|
|
2580
|
+
if prework["discuss"].get("status") != "parse_error":
|
|
2581
|
+
if prework["discuss"]["recommended"] == "Likely" and not has_context:
|
|
2582
|
+
return "discuss-phase", prework["discuss"]["reason"] or "clarify vision"
|
|
2583
|
+
if prework["design"].get("status") != "parse_error":
|
|
2584
|
+
if prework["design"]["recommended"] == "Likely" and not has_design:
|
|
2585
|
+
return "design-phase", prework["design"]["reason"] or "create UI/UX specs"
|
|
2586
|
+
if prework["research"].get("status") != "parse_error":
|
|
2587
|
+
if prework["research"]["recommended"] == "Likely" and not has_research:
|
|
2588
|
+
return "research-phase", prework["research"]["reason"] or "investigate approach"
|
|
2589
|
+
|
|
2590
|
+
# Check if any flags had parse errors with no other Likely flag triggering
|
|
2591
|
+
has_parse_error = any(
|
|
2592
|
+
prework[f].get("status") == "parse_error" for f in ("discuss", "design", "research")
|
|
2593
|
+
)
|
|
2594
|
+
if has_parse_error:
|
|
2595
|
+
return "plan-phase", "ready to plan (some flags unreadable — verify ROADMAP.md)"
|
|
2596
|
+
return "plan-phase", "ready to plan"
|
|
2597
|
+
|
|
2598
|
+
|
|
2599
|
+
def cmd_prework_status(args: argparse.Namespace) -> None:
|
|
2600
|
+
"""Show pre-work status and routing suggestion for a phase.
|
|
2601
|
+
|
|
2602
|
+
Contract:
|
|
2603
|
+
Args: phase (str) — phase number (e.g., 5, 05, 2.1)
|
|
2604
|
+
Output: human-readable text — phase info, pre-work status, suggestion
|
|
2605
|
+
Exit codes: 0 = success, 1 = ROADMAP.md missing or phase not found
|
|
2606
|
+
Side effects: read-only
|
|
2607
|
+
"""
|
|
2608
|
+
phase = normalize_phase(args.phase)
|
|
2609
|
+
planning = find_planning_dir()
|
|
2610
|
+
|
|
2611
|
+
# Parse ROADMAP.md
|
|
2612
|
+
roadmap = planning / "ROADMAP.md"
|
|
2613
|
+
if not roadmap.is_file():
|
|
2614
|
+
print("Error: No ROADMAP.md found", file=sys.stderr)
|
|
2615
|
+
sys.exit(1)
|
|
2616
|
+
|
|
2617
|
+
roadmap_text = roadmap.read_text(encoding="utf-8")
|
|
2618
|
+
phase_info = _parse_phase_section(roadmap_text, phase)
|
|
2619
|
+
if not phase_info:
|
|
2620
|
+
print(f"Error: Phase {phase} not found in ROADMAP.md", file=sys.stderr)
|
|
2621
|
+
sys.exit(1)
|
|
2622
|
+
|
|
2623
|
+
# Check artifacts
|
|
2624
|
+
phase_dir = find_phase_dir(planning, phase)
|
|
2625
|
+
has_context = False
|
|
2626
|
+
has_design = False
|
|
2627
|
+
has_research = False
|
|
2628
|
+
if phase_dir and phase_dir.is_dir():
|
|
2629
|
+
has_context = any(phase_dir.glob("*-CONTEXT.md"))
|
|
2630
|
+
has_design = any(phase_dir.glob("*-DESIGN.md"))
|
|
2631
|
+
has_research = any(phase_dir.glob("*-RESEARCH.md"))
|
|
2632
|
+
|
|
2633
|
+
# Routing
|
|
2634
|
+
suggested_cmd, reason = _determine_prework_suggestion(
|
|
2635
|
+
phase_info["prework"], has_context, has_design, has_research
|
|
2636
|
+
)
|
|
2637
|
+
|
|
2638
|
+
# Format output
|
|
2639
|
+
print(f"Phase {phase}: {phase_info['name']}")
|
|
2640
|
+
print(f"Goal: {phase_info['goal']}")
|
|
2641
|
+
print()
|
|
2642
|
+
print("Pre-work:")
|
|
2643
|
+
|
|
2644
|
+
artifact_done = {
|
|
2645
|
+
"discuss": has_context,
|
|
2646
|
+
"design": has_design,
|
|
2647
|
+
"research": has_research,
|
|
2648
|
+
}
|
|
2649
|
+
detail_labels = {"discuss": "Topics", "design": "Focus", "research": "Topics"}
|
|
2650
|
+
|
|
2651
|
+
has_any_parse_error = False
|
|
2652
|
+
for flag in ("discuss", "design", "research"):
|
|
2653
|
+
pw = phase_info["prework"][flag]
|
|
2654
|
+
if pw.get("status") == "parse_error":
|
|
2655
|
+
has_any_parse_error = True
|
|
2656
|
+
print(f" {flag.capitalize()}: [parse error] — read ROADMAP.md phase section for this flag")
|
|
2657
|
+
elif pw["recommended"] == "Likely":
|
|
2658
|
+
done = artifact_done[flag]
|
|
2659
|
+
status = "done" if done else "not started"
|
|
2660
|
+
line = f" {flag.capitalize()}: Likely ({status})"
|
|
2661
|
+
if pw["reason"]:
|
|
2662
|
+
line += f" — {pw['reason']}"
|
|
2663
|
+
print(line)
|
|
2664
|
+
if pw["detail"]:
|
|
2665
|
+
print(f" {detail_labels[flag]}: {pw['detail']}")
|
|
2666
|
+
else:
|
|
2667
|
+
print(f" {flag.capitalize()}: Unlikely")
|
|
2668
|
+
|
|
2669
|
+
# Existing artifacts
|
|
2670
|
+
existing = []
|
|
2671
|
+
if has_context:
|
|
2672
|
+
existing.append("CONTEXT.md")
|
|
2673
|
+
if has_design:
|
|
2674
|
+
existing.append("DESIGN.md")
|
|
2675
|
+
if has_research:
|
|
2676
|
+
existing.append("RESEARCH.md")
|
|
2677
|
+
|
|
2678
|
+
print()
|
|
2679
|
+
if existing:
|
|
2680
|
+
print(f"Existing: {', '.join(existing)}")
|
|
2681
|
+
else:
|
|
2682
|
+
print("Existing: (none)")
|
|
2683
|
+
|
|
2684
|
+
print(f"Suggested: /ms:{suggested_cmd} {phase} — {reason}")
|
|
2685
|
+
|
|
2686
|
+
if has_any_parse_error:
|
|
2687
|
+
print(f"\nNote: Some pre-work flags could not be parsed. Read ROADMAP.md phase section for Phase {phase} to determine accurate routing.")
|
|
2688
|
+
|
|
2689
|
+
|
|
2220
2690
|
# ===================================================================
|
|
2221
2691
|
# Subcommand: check-artifact
|
|
2222
2692
|
# ===================================================================
|
|
@@ -3812,6 +4282,12 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
3812
4282
|
p = subparsers.add_parser("create-phase-dirs", help="Create phase directories from ROADMAP.md")
|
|
3813
4283
|
p.set_defaults(func=cmd_create_phase_dirs)
|
|
3814
4284
|
|
|
4285
|
+
# --- phase-renumber ---
|
|
4286
|
+
p = subparsers.add_parser("phase-renumber", help="Renumber phase dirs and files after phase removal")
|
|
4287
|
+
p.add_argument("phase", help="The removed phase number (e.g., 17 or 17.1)")
|
|
4288
|
+
p.add_argument("--dry-run", action="store_true", help="Preview renames without executing")
|
|
4289
|
+
p.set_defaults(func=cmd_phase_renumber)
|
|
4290
|
+
|
|
3815
4291
|
# --- gather-milestone-stats ---
|
|
3816
4292
|
p = subparsers.add_parser("gather-milestone-stats", help="Gather milestone readiness and git statistics")
|
|
3817
4293
|
p.add_argument("start_phase", type=int, help="Start phase number")
|
|
@@ -3877,6 +4353,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
3877
4353
|
p.add_argument("phase", help="Phase number")
|
|
3878
4354
|
p.set_defaults(func=cmd_list_artifacts)
|
|
3879
4355
|
|
|
4356
|
+
# --- prework-status ---
|
|
4357
|
+
p = subparsers.add_parser("prework-status", help="Show pre-work status and routing suggestion")
|
|
4358
|
+
p.add_argument("phase", help="Phase number (e.g., 5, 05, 2.1)")
|
|
4359
|
+
p.set_defaults(func=cmd_prework_status)
|
|
4360
|
+
|
|
3880
4361
|
# --- check-artifact ---
|
|
3881
4362
|
p = subparsers.add_parser("check-artifact", help="Check if specific artifact exists")
|
|
3882
4363
|
p.add_argument("phase", help="Phase number")
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: ms-verify-fixer
|
|
3
|
-
description: Investigates and fixes single UAT issues. Spawned by verify-work when lightweight investigation fails.
|
|
4
|
-
model: sonnet
|
|
5
|
-
tools: Read, Write, Edit, Bash, Grep, Glob
|
|
6
|
-
color: orange
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
<role>
|
|
10
|
-
You are a Mindsystem verify-fixer. You investigate a single UAT issue that the main orchestrator couldn't resolve with lightweight investigation, find the root cause, implement a fix, and commit it.
|
|
11
|
-
|
|
12
|
-
You are spawned by `/ms:verify-work` when an issue requires deeper investigation (failed 2-3 quick checks).
|
|
13
|
-
|
|
14
|
-
Your job: Find root cause, implement minimal fix, commit with proper message, return result for re-testing.
|
|
15
|
-
</role>
|
|
16
|
-
|
|
17
|
-
<context_you_receive>
|
|
18
|
-
Your prompt will include:
|
|
19
|
-
|
|
20
|
-
- **Issue details**: test name, expected behavior, actual behavior, severity
|
|
21
|
-
- **Phase info**: phase name, current mock state (if any)
|
|
22
|
-
- **Relevant files**: suspected files from initial investigation
|
|
23
|
-
- **What was checked**: results of lightweight investigation already done
|
|
24
|
-
</context_you_receive>
|
|
25
|
-
|
|
26
|
-
<investigation_approach>
|
|
27
|
-
You have fresh 200k context. Use it to investigate thoroughly.
|
|
28
|
-
|
|
29
|
-
**1. Start from what's known**
|
|
30
|
-
- Read the files already identified as suspicious
|
|
31
|
-
- Review what was already checked (don't repeat)
|
|
32
|
-
- Look for adjacent code that might be involved
|
|
33
|
-
|
|
34
|
-
**2. Form specific hypothesis**
|
|
35
|
-
- Be precise: "useEffect missing dependency" not "something with state"
|
|
36
|
-
- Make it falsifiable: you can prove it wrong with a test
|
|
37
|
-
|
|
38
|
-
**3. Test one thing at a time**
|
|
39
|
-
- Add logging if needed
|
|
40
|
-
- Run the code to observe
|
|
41
|
-
- Don't change multiple things at once
|
|
42
|
-
|
|
43
|
-
**4. When you find it**
|
|
44
|
-
- Verify with evidence (log output, test result)
|
|
45
|
-
- Implement minimal fix
|
|
46
|
-
- Test that fix resolves the issue
|
|
47
|
-
</investigation_approach>
|
|
48
|
-
|
|
49
|
-
<fix_protocol>
|
|
50
|
-
**Mocks are currently stashed** - your working tree is clean.
|
|
51
|
-
|
|
52
|
-
**1. Implement fix**
|
|
53
|
-
- Make the smallest change that addresses root cause
|
|
54
|
-
- Don't refactor surrounding code
|
|
55
|
-
- Don't add "improvements" beyond the fix
|
|
56
|
-
|
|
57
|
-
**2. Commit with proper message**
|
|
58
|
-
```bash
|
|
59
|
-
git add [specific files]
|
|
60
|
-
git commit -m "fix({phase}-uat): {description}"
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
Use the `{phase}-uat` scope so patches can find UAT fixes later.
|
|
64
|
-
|
|
65
|
-
**3. Document what you did**
|
|
66
|
-
- Which file(s) changed
|
|
67
|
-
- What the fix actually does
|
|
68
|
-
- Why this addresses the root cause
|
|
69
|
-
</fix_protocol>
|
|
70
|
-
|
|
71
|
-
<return_formats>
|
|
72
|
-
|
|
73
|
-
**When fix applied successfully:**
|
|
74
|
-
|
|
75
|
-
```markdown
|
|
76
|
-
## FIX COMPLETE
|
|
77
|
-
|
|
78
|
-
**Root cause:** {specific cause with evidence}
|
|
79
|
-
|
|
80
|
-
**Fix applied:** {what was changed and why}
|
|
81
|
-
|
|
82
|
-
**Commit:** {hash}
|
|
83
|
-
|
|
84
|
-
**Files changed:**
|
|
85
|
-
- {file}: {change description}
|
|
86
|
-
|
|
87
|
-
**Re-test instruction:** {specific step for user to verify}
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
**When investigation is inconclusive:**
|
|
91
|
-
|
|
92
|
-
```markdown
|
|
93
|
-
## INVESTIGATION INCONCLUSIVE
|
|
94
|
-
|
|
95
|
-
**What was checked:**
|
|
96
|
-
- {area}: {finding}
|
|
97
|
-
- {area}: {finding}
|
|
98
|
-
|
|
99
|
-
**Hypotheses eliminated:**
|
|
100
|
-
- {hypothesis}: {why ruled out}
|
|
101
|
-
|
|
102
|
-
**Remaining possibilities:**
|
|
103
|
-
- {possibility 1}
|
|
104
|
-
- {possibility 2}
|
|
105
|
-
|
|
106
|
-
**Recommendation:** {suggested next steps}
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
</return_formats>
|
|
110
|
-
|
|
111
|
-
<constraints>
|
|
112
|
-
- Do NOT modify mock code (it's stashed)
|
|
113
|
-
- Do NOT make architectural changes (stop and report the issue)
|
|
114
|
-
- Do NOT fix unrelated issues you discover (note them for later)
|
|
115
|
-
- Do commit your fix before returning
|
|
116
|
-
- Do use `fix({phase}-uat):` commit message format
|
|
117
|
-
</constraints>
|
|
118
|
-
|
|
119
|
-
<success_criteria>
|
|
120
|
-
- [ ] Root cause identified with evidence
|
|
121
|
-
- [ ] Minimal fix implemented
|
|
122
|
-
- [ ] Fix committed with proper message format
|
|
123
|
-
- [ ] Clear re-test instruction provided
|
|
124
|
-
- [ ] Return uses correct format (FIX COMPLETE or INVESTIGATION INCONCLUSIVE)
|
|
125
|
-
</success_criteria>
|