mindsystem-cc 4.0.3 → 4.1.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 +39 -22
- package/agents/ms-researcher.md +3 -3
- package/agents/ms-roadmapper.md +11 -6
- package/bin/install.js +29 -8
- package/commands/ms/audit-milestone.md +35 -42
- package/commands/ms/create-roadmap.md +5 -7
- package/commands/ms/doctor.md +3 -3
- package/commands/ms/help.md +14 -14
- package/commands/ms/new-milestone.md +3 -3
- package/commands/ms/research-milestone.md +339 -0
- package/mindsystem/references/questioning.md +1 -1
- package/mindsystem/references/routing/between-milestones-routing.md +1 -1
- package/mindsystem/templates/milestone-context.md +1 -1
- package/mindsystem/templates/milestone-research.md +89 -0
- package/mindsystem/templates/requirements.md +1 -1
- package/mindsystem/templates/roadmap.md +20 -0
- package/mindsystem/templates/tech-debt.md +4 -4
- package/mindsystem/workflows/complete-milestone.md +4 -4
- package/mindsystem/workflows/define-requirements.md +12 -14
- package/mindsystem/workflows/verify-work.md +14 -49
- package/package.json +1 -1
- package/scripts/ms-tools.py +290 -14
- package/agents/ms-integration-checker.md +0 -424
- package/agents/ms-research-synthesizer.md +0 -248
- package/commands/ms/research-project.md +0 -353
- package/mindsystem/templates/research-project/ARCHITECTURE.md +0 -204
- package/mindsystem/templates/research-project/FEATURES.md +0 -147
- package/mindsystem/templates/research-project/PITFALLS.md +0 -200
- package/mindsystem/templates/research-project/STACK.md +0 -120
- package/mindsystem/templates/research-project/SUMMARY.md +0 -170
- package/mindsystem/templates/research-project-output.md +0 -81
- package/mindsystem/workflows/research-project.md +0 -23
|
@@ -538,7 +538,7 @@ Archived to milestones/{slug}/:
|
|
|
538
538
|
- phases/ (phase directories moved from .planning/phases/)
|
|
539
539
|
- MILESTONE-AUDIT.md (if audit was run)
|
|
540
540
|
- CONTEXT.md (if milestone context existed)
|
|
541
|
-
-
|
|
541
|
+
- MILESTONE-RESEARCH.md (if existed)
|
|
542
542
|
|
|
543
543
|
Cleaned:
|
|
544
544
|
- Raw phase artifacts deleted (CONTEXT, DESIGN, RESEARCH, SUMMARY, UAT, VERIFICATION, EXECUTION-ORDER)
|
|
@@ -548,7 +548,7 @@ Cleaned:
|
|
|
548
548
|
Deleted (fresh for next milestone):
|
|
549
549
|
- ROADMAP.md
|
|
550
550
|
- REQUIREMENTS.md
|
|
551
|
-
- .
|
|
551
|
+
- MILESTONE-RESEARCH.md (archived to milestone)
|
|
552
552
|
|
|
553
553
|
Updated:
|
|
554
554
|
- MILESTONES.md (new entry)
|
|
@@ -574,7 +574,7 @@ Shipped:
|
|
|
574
574
|
Archived to milestones/{slug}/:
|
|
575
575
|
- ROADMAP.md
|
|
576
576
|
- REQUIREMENTS.md
|
|
577
|
-
-
|
|
577
|
+
- MILESTONE-RESEARCH.md (if existed)
|
|
578
578
|
|
|
579
579
|
Summary: .planning/MILESTONES.md
|
|
580
580
|
|
|
@@ -590,7 +590,7 @@ Summary: .planning/MILESTONES.md
|
|
|
590
590
|
|
|
591
591
|
**Next milestone flow:**
|
|
592
592
|
1. `/ms:new-milestone` — discover what to build, update PROJECT.md with goals
|
|
593
|
-
2. `/ms:research-
|
|
593
|
+
2. `/ms:research-milestone` — (optional) research ecosystem
|
|
594
594
|
3. `/ms:create-roadmap` — define requirements and plan how to build it
|
|
595
595
|
|
|
596
596
|
---
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Define concrete, checkable requirements for v1.
|
|
3
3
|
|
|
4
4
|
Two modes:
|
|
5
|
-
1. **With research** — Transform
|
|
5
|
+
1. **With research** — Transform MILESTONE-RESEARCH.md Product Landscape into scoped requirements
|
|
6
6
|
2. **Without research** — Gather requirements through questioning
|
|
7
7
|
</purpose>
|
|
8
8
|
|
|
@@ -11,8 +11,7 @@ Two modes:
|
|
|
11
11
|
|
|
12
12
|
1. ~/.claude/mindsystem/templates/requirements.md
|
|
13
13
|
2. .planning/PROJECT.md
|
|
14
|
-
3. .planning/
|
|
15
|
-
4. .planning/research/SUMMARY.md (if exists)
|
|
14
|
+
3. .planning/MILESTONE-RESEARCH.md (if exists)
|
|
16
15
|
</required_reading>
|
|
17
16
|
|
|
18
17
|
<process>
|
|
@@ -20,7 +19,7 @@ Two modes:
|
|
|
20
19
|
<step name="detect_mode">
|
|
21
20
|
Check for research:
|
|
22
21
|
```bash
|
|
23
|
-
[ -f .planning/
|
|
22
|
+
[ -f .planning/MILESTONE-RESEARCH.md ] && echo "HAS_RESEARCH" || echo "NO_RESEARCH"
|
|
24
23
|
```
|
|
25
24
|
|
|
26
25
|
**If HAS_RESEARCH:** Follow steps load_context → present_features → scope_categories
|
|
@@ -33,17 +32,16 @@ Read PROJECT.md and extract:
|
|
|
33
32
|
- Stated constraints (budget, timeline, tech limitations)
|
|
34
33
|
- Any explicit scope boundaries from project definition
|
|
35
34
|
|
|
36
|
-
Read
|
|
35
|
+
Read MILESTONE-RESEARCH.md and extract from Product Landscape:
|
|
37
36
|
- Table stakes (users expect these)
|
|
38
37
|
- Differentiators (competitive advantage)
|
|
39
38
|
- Anti-features (commonly requested, often problematic)
|
|
40
|
-
- Feature dependencies
|
|
41
|
-
- MVP vs full product recommendations
|
|
42
39
|
|
|
43
|
-
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
-
|
|
40
|
+
Extract from other sections:
|
|
41
|
+
- Technology constraints and decisions
|
|
42
|
+
- Architecture dependencies
|
|
43
|
+
- Feasibility constraints
|
|
44
|
+
- Key risks and pitfalls
|
|
47
45
|
</step>
|
|
48
46
|
|
|
49
47
|
<step name="load_project" mode="without_research">
|
|
@@ -105,7 +103,7 @@ Here are the features for [domain]:
|
|
|
105
103
|
- OAuth (Google, GitHub)
|
|
106
104
|
- 2FA
|
|
107
105
|
|
|
108
|
-
**Research notes:** [any relevant notes from
|
|
106
|
+
**Research notes:** [any relevant notes from MILESTONE-RESEARCH.md]
|
|
109
107
|
|
|
110
108
|
---
|
|
111
109
|
|
|
@@ -114,8 +112,8 @@ Here are the features for [domain]:
|
|
|
114
112
|
```
|
|
115
113
|
|
|
116
114
|
For each category, include:
|
|
117
|
-
- Table stakes from
|
|
118
|
-
- Differentiators from
|
|
115
|
+
- Table stakes from MILESTONE-RESEARCH.md
|
|
116
|
+
- Differentiators from MILESTONE-RESEARCH.md
|
|
119
117
|
- Any anti-features flagged (with warnings)
|
|
120
118
|
- Complexity notes where relevant
|
|
121
119
|
</step>
|
|
@@ -200,11 +200,7 @@ Read current batch from UAT.md (test descriptions needed for presenting to user)
|
|
|
200
200
|
**1. Handle mock generation (if needed):**
|
|
201
201
|
|
|
202
202
|
If `mock_type` is not null AND different from previous batch:
|
|
203
|
-
- Revert old mocks
|
|
204
|
-
```bash
|
|
205
|
-
git checkout -- <mocked_files>
|
|
206
|
-
```
|
|
207
|
-
- Clear mocked_files: `ms-tools uat-update $PHASE_NUMBER --session mocked_files=`
|
|
203
|
+
- Revert old mocks: `ms-tools uat-revert-mocks $PHASE_NUMBER`
|
|
208
204
|
- Go to `generate_mocks`
|
|
209
205
|
|
|
210
206
|
If `mock_type` is null or same as previous:
|
|
@@ -382,53 +378,28 @@ Progress auto-recalculates on every `uat-update` call. No manual progress recalc
|
|
|
382
378
|
**Apply fix inline:**
|
|
383
379
|
|
|
384
380
|
**1. Stash mocks (if active):**
|
|
385
|
-
|
|
386
|
-
git stash push -m "mocks-batch-{N}" -- <mocked_files>
|
|
387
|
-
```
|
|
388
|
-
Use `mocked_files` list from UAT.md frontmatter.
|
|
381
|
+
`ms-tools uat-stash-mocks $PHASE_NUMBER`
|
|
389
382
|
|
|
390
383
|
**2. Make the fix:**
|
|
391
384
|
- Edit the file(s)
|
|
392
385
|
- Test that fix compiles/runs
|
|
393
386
|
|
|
394
|
-
**3. Commit
|
|
387
|
+
**3. Commit and record fix:**
|
|
395
388
|
```bash
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
# Check if this is a retry AND HEAD matches the test's previous fix_commit:
|
|
399
|
-
PREV_FIX=$(ms-tools uat-status $PHASE_NUMBER | python3 -c "import sys,json; d=json.load(sys.stdin); t=[x for x in d['fixing_tests'] if x['num']==N]; print(t[0].get('fix_commit','') if t else '')" 2>/dev/null)
|
|
400
|
-
HEAD_SHORT=$(git rev-parse --short HEAD)
|
|
401
|
-
|
|
402
|
-
if [ "$PREV_FIX" = "$HEAD_SHORT" ] && [ -n "$PREV_FIX" ]; then
|
|
403
|
-
git commit --amend --no-edit
|
|
404
|
-
else
|
|
405
|
-
git commit -m "fix({phase}-uat): {description}"
|
|
406
|
-
fi
|
|
389
|
+
ms-tools uat-fix-commit $PHASE_NUMBER --test N --message "fix({phase}-uat): {description}" <files>
|
|
407
390
|
```
|
|
391
|
+
Handles amend-on-retry automatically (amends if HEAD matches previous fix_commit for this test).
|
|
408
392
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
**4. Record in UAT.md via ms-tools:**
|
|
393
|
+
**4. Record fix details in UAT.md:**
|
|
394
|
+
Use the `hash` from fix-commit JSON output:
|
|
412
395
|
```bash
|
|
413
|
-
|
|
414
|
-
ms-tools uat-update $PHASE_NUMBER --test N fix_status=applied fix_commit=$FIX_HASH
|
|
415
|
-
echo '{"commit":"'$FIX_HASH'","test":N,"description":"what was fixed","files":["changed.ts"]}' | ms-tools uat-update $PHASE_NUMBER --append-fix
|
|
396
|
+
echo '{"commit":"HASH","test":N,"description":"what was fixed","files":["changed.ts"]}' | ms-tools uat-update $PHASE_NUMBER --append-fix
|
|
416
397
|
```
|
|
417
398
|
|
|
418
|
-
`append-fix` updates in-place if a fix for the same test already exists (amend support).
|
|
419
|
-
|
|
420
399
|
**5. Restore mocks:**
|
|
421
|
-
|
|
422
|
-
git stash pop
|
|
423
|
-
```
|
|
400
|
+
`ms-tools uat-pop-mocks $PHASE_NUMBER`
|
|
424
401
|
|
|
425
|
-
|
|
426
|
-
```bash
|
|
427
|
-
# Conflict means fix touched a mocked file — take the fix version
|
|
428
|
-
git checkout --theirs <conflicted-file>
|
|
429
|
-
git add <conflicted-file>
|
|
430
|
-
```
|
|
431
|
-
Remove conflicted file from `mocked_files` list in UAT.md (mock no longer needed for that file).
|
|
402
|
+
Conflicts are resolved automatically (fix version wins, conflicted files removed from mocked_files).
|
|
432
403
|
|
|
433
404
|
**6. Request re-test:**
|
|
434
405
|
```
|
|
@@ -444,9 +415,7 @@ Go to `handle_retest`.
|
|
|
444
415
|
**Spawn fixer subagent for complex issue:**
|
|
445
416
|
|
|
446
417
|
**1. Stash mocks (if active):**
|
|
447
|
-
|
|
448
|
-
git stash push -m "mocks-batch-{N}" -- <mocked_files>
|
|
449
|
-
```
|
|
418
|
+
`ms-tools uat-stash-mocks $PHASE_NUMBER`
|
|
450
419
|
|
|
451
420
|
**2. Spawn ms-verify-fixer:**
|
|
452
421
|
```
|
|
@@ -489,12 +458,11 @@ Mocks are stashed — working tree is clean.
|
|
|
489
458
|
|
|
490
459
|
**If FIX COMPLETE:**
|
|
491
460
|
- Record fix via ms-tools (same as `apply_fix` step 4: `uat-update --test N` + `--append-fix`)
|
|
492
|
-
- Restore mocks: `
|
|
493
|
-
- Handle conflicts as in `apply_fix`
|
|
461
|
+
- Restore mocks: `ms-tools uat-pop-mocks $PHASE_NUMBER`
|
|
494
462
|
- Request re-test
|
|
495
463
|
|
|
496
464
|
**If INVESTIGATION INCONCLUSIVE:**
|
|
497
|
-
- Restore mocks: `
|
|
465
|
+
- Restore mocks: `ms-tools uat-pop-mocks $PHASE_NUMBER`
|
|
498
466
|
- Present options:
|
|
499
467
|
```
|
|
500
468
|
Investigation didn't find root cause.
|
|
@@ -607,10 +575,7 @@ The `--session current_batch=N` call auto-syncs the Current Batch section with t
|
|
|
607
575
|
**Complete UAT session:**
|
|
608
576
|
|
|
609
577
|
**1. Revert mocks:**
|
|
610
|
-
|
|
611
|
-
git checkout -- <mocked_files>
|
|
612
|
-
```
|
|
613
|
-
Use `mocked_files` list from UAT.md frontmatter. Clear the list after reverting.
|
|
578
|
+
`ms-tools uat-revert-mocks $PHASE_NUMBER`
|
|
614
579
|
|
|
615
580
|
**2. Generate UAT fixes patch (if fixes were made):**
|
|
616
581
|
```bash
|
package/package.json
CHANGED
package/scripts/ms-tools.py
CHANGED
|
@@ -761,19 +761,67 @@ def cmd_doctor_scan(args: argparse.Namespace) -> None:
|
|
|
761
761
|
record("PASS", "PLAN Cleanup")
|
|
762
762
|
print()
|
|
763
763
|
|
|
764
|
-
# ---- CHECK 7: CLI Wrappers ----
|
|
764
|
+
# ---- CHECK 7: CLI Wrappers & Environment ----
|
|
765
765
|
print("=== CLI Wrappers ===")
|
|
766
766
|
wrapper_names = ["ms-tools", "ms-lookup", "ms-compare-mockups"]
|
|
767
|
-
|
|
768
|
-
|
|
767
|
+
|
|
768
|
+
# 7a: Check bin directory exists
|
|
769
|
+
global_bin = Path.home() / ".claude" / "bin"
|
|
770
|
+
local_bin = Path(".claude") / "bin"
|
|
771
|
+
bin_dir = global_bin if global_bin.is_dir() else (local_bin if local_bin.is_dir() else None)
|
|
772
|
+
|
|
773
|
+
if bin_dir is None:
|
|
769
774
|
print("Status: FAIL")
|
|
770
|
-
print(
|
|
771
|
-
print("Fix: re-run `npx mindsystem-cc` to
|
|
775
|
+
print("Bin directory not found (~/.claude/bin/ or .claude/bin/)")
|
|
776
|
+
print("Fix: re-run `npx mindsystem-cc` to generate wrappers")
|
|
772
777
|
record("FAIL", "CLI Wrappers")
|
|
773
778
|
else:
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
779
|
+
# 7b: Check wrapper files present
|
|
780
|
+
missing_files = [w for w in wrapper_names if not (bin_dir / w).exists()]
|
|
781
|
+
if missing_files:
|
|
782
|
+
print("Status: FAIL")
|
|
783
|
+
print(f"Wrapper files missing from {bin_dir}: {', '.join(missing_files)}")
|
|
784
|
+
print("Fix: re-run `npx mindsystem-cc` to regenerate wrappers")
|
|
785
|
+
record("FAIL", "CLI Wrappers")
|
|
786
|
+
else:
|
|
787
|
+
# 7c: Check bin dir in PATH
|
|
788
|
+
path_dirs = os.environ.get("PATH", "").split(os.pathsep)
|
|
789
|
+
bin_in_path = str(bin_dir.resolve()) in [os.path.realpath(p) for p in path_dirs]
|
|
790
|
+
|
|
791
|
+
# 7d: Check wrappers resolvable
|
|
792
|
+
missing_wrappers = [w for w in wrapper_names if shutil.which(w) is None]
|
|
793
|
+
|
|
794
|
+
if missing_wrappers:
|
|
795
|
+
print("Status: FAIL")
|
|
796
|
+
print(f"Not resolvable: {', '.join(missing_wrappers)}")
|
|
797
|
+
if not bin_in_path:
|
|
798
|
+
print(f"Cause: {bin_dir} not in PATH")
|
|
799
|
+
print("Fix: restart Claude Code session (PATH hook fires on SessionStart)")
|
|
800
|
+
else:
|
|
801
|
+
print("Fix: re-run `npx mindsystem-cc` to regenerate wrappers and PATH hook")
|
|
802
|
+
record("FAIL", "CLI Wrappers")
|
|
803
|
+
else:
|
|
804
|
+
print(f"All {len(wrapper_names)} CLI wrappers found on PATH")
|
|
805
|
+
|
|
806
|
+
# 7e: Check uv available
|
|
807
|
+
uv_ok = shutil.which("uv") is not None
|
|
808
|
+
# 7f: Check Python available
|
|
809
|
+
py_ok = shutil.which("python3") is not None or shutil.which("python") is not None
|
|
810
|
+
|
|
811
|
+
issues = []
|
|
812
|
+
if not uv_ok:
|
|
813
|
+
issues.append("uv not found — install: `curl -LsSf https://astral.sh/uv/install.sh | sh`")
|
|
814
|
+
if not py_ok:
|
|
815
|
+
issues.append("Python not found — install Python 3.10+")
|
|
816
|
+
|
|
817
|
+
if issues:
|
|
818
|
+
print("Status: WARN")
|
|
819
|
+
for issue in issues:
|
|
820
|
+
print(f" {issue}")
|
|
821
|
+
record("WARN", "CLI Wrappers")
|
|
822
|
+
else:
|
|
823
|
+
print("Status: PASS")
|
|
824
|
+
record("PASS", "CLI Wrappers")
|
|
777
825
|
print()
|
|
778
826
|
|
|
779
827
|
# ---- CHECK 8: Milestone Naming Convention ----
|
|
@@ -1306,15 +1354,15 @@ def cmd_archive_milestone_files(args: argparse.Namespace) -> None:
|
|
|
1306
1354
|
print("Archived: MILESTONE-CONTEXT.md → CONTEXT.md")
|
|
1307
1355
|
archived += 1
|
|
1308
1356
|
|
|
1309
|
-
#
|
|
1310
|
-
|
|
1311
|
-
if
|
|
1312
|
-
shutil.move(str(
|
|
1313
|
-
print("Archived:
|
|
1357
|
+
# MILESTONE-RESEARCH.md
|
|
1358
|
+
research_file = planning_dir / "MILESTONE-RESEARCH.md"
|
|
1359
|
+
if research_file.is_file():
|
|
1360
|
+
shutil.move(str(research_file), str(milestone_dir / "MILESTONE-RESEARCH.md"))
|
|
1361
|
+
print("Archived: MILESTONE-RESEARCH.md")
|
|
1314
1362
|
archived += 1
|
|
1315
1363
|
|
|
1316
1364
|
if archived == 0:
|
|
1317
|
-
print("No optional files to archive (audit, context, research all absent)")
|
|
1365
|
+
print("No optional files to archive (audit, context, milestone research all absent)")
|
|
1318
1366
|
else:
|
|
1319
1367
|
print()
|
|
1320
1368
|
print(f"Archived {archived} item(s) to milestones/{milestone}/")
|
|
@@ -2415,6 +2463,7 @@ class UATFile:
|
|
|
2415
2463
|
"current_batch": 1,
|
|
2416
2464
|
"mocked_files": [],
|
|
2417
2465
|
"pre_work_stash": None,
|
|
2466
|
+
"stash_ref": None,
|
|
2418
2467
|
}
|
|
2419
2468
|
|
|
2420
2469
|
# Build tests
|
|
@@ -2685,6 +2734,22 @@ class UATFile:
|
|
|
2685
2734
|
return "\n".join(lines)
|
|
2686
2735
|
|
|
2687
2736
|
|
|
2737
|
+
def _load_uat(args_phase: str) -> tuple[Path, "UATFile"]:
|
|
2738
|
+
"""Load UAT file for a phase. Returns (uat_path, uat). Exits 1 if missing."""
|
|
2739
|
+
phase = normalize_phase(args_phase)
|
|
2740
|
+
planning = find_planning_dir()
|
|
2741
|
+
phase_dir = find_phase_dir(planning, phase)
|
|
2742
|
+
if phase_dir is None:
|
|
2743
|
+
print(f"Error: Phase directory not found for {phase}", file=sys.stderr)
|
|
2744
|
+
sys.exit(1)
|
|
2745
|
+
uat_path = phase_dir / f"{phase_dir.name}-UAT.md"
|
|
2746
|
+
if not uat_path.is_file():
|
|
2747
|
+
print(f"Error: UAT file not found: {uat_path}", file=sys.stderr)
|
|
2748
|
+
sys.exit(1)
|
|
2749
|
+
uat = UATFile.parse(uat_path.read_text(encoding="utf-8"))
|
|
2750
|
+
return uat_path, uat
|
|
2751
|
+
|
|
2752
|
+
|
|
2688
2753
|
# ===================================================================
|
|
2689
2754
|
# Subcommand: uat-init
|
|
2690
2755
|
# ===================================================================
|
|
@@ -2857,6 +2922,7 @@ def cmd_uat_status(args: argparse.Namespace) -> None:
|
|
|
2857
2922
|
"pending_tests": pending_tests,
|
|
2858
2923
|
"blocked_tests": blocked_tests,
|
|
2859
2924
|
"pre_work_stash": uat.frontmatter.get("pre_work_stash"),
|
|
2925
|
+
"stash_ref": uat.frontmatter.get("stash_ref"),
|
|
2860
2926
|
"path": str(uat_path),
|
|
2861
2927
|
}
|
|
2862
2928
|
|
|
@@ -2864,6 +2930,193 @@ def cmd_uat_status(args: argparse.Namespace) -> None:
|
|
|
2864
2930
|
sys.stdout.write("\n")
|
|
2865
2931
|
|
|
2866
2932
|
|
|
2933
|
+
# ===================================================================
|
|
2934
|
+
# Subcommand: uat-stash-mocks
|
|
2935
|
+
# ===================================================================
|
|
2936
|
+
|
|
2937
|
+
|
|
2938
|
+
def cmd_uat_stash_mocks(args: argparse.Namespace) -> None:
|
|
2939
|
+
"""Stash mocked files before applying a fix.
|
|
2940
|
+
|
|
2941
|
+
Contract:
|
|
2942
|
+
Args: phase (str) — phase number
|
|
2943
|
+
Output: JSON — stash_ref and files list
|
|
2944
|
+
Exit codes: 0 = success (or no-op), 1 = git failure
|
|
2945
|
+
Side effects: git stash push, updates UAT.md stash_ref
|
|
2946
|
+
"""
|
|
2947
|
+
uat_path, uat = _load_uat(args.phase)
|
|
2948
|
+
|
|
2949
|
+
mocked_files = uat.frontmatter.get("mocked_files", [])
|
|
2950
|
+
if not mocked_files:
|
|
2951
|
+
print("No mocked files to stash", file=sys.stderr)
|
|
2952
|
+
return
|
|
2953
|
+
|
|
2954
|
+
current_batch = uat.frontmatter.get("current_batch", 1)
|
|
2955
|
+
|
|
2956
|
+
try:
|
|
2957
|
+
run_git("stash", "push", "-m", f"mocks-batch-{current_batch}", "--", *mocked_files)
|
|
2958
|
+
except subprocess.CalledProcessError as e:
|
|
2959
|
+
print(f"Error: git stash push failed: {e.stderr}", file=sys.stderr)
|
|
2960
|
+
sys.exit(1)
|
|
2961
|
+
|
|
2962
|
+
uat.update_session({"stash_ref": "stash@{0}"})
|
|
2963
|
+
uat_path.write_text(uat.serialize(), encoding="utf-8")
|
|
2964
|
+
|
|
2965
|
+
output = {"stash_ref": "stash@{0}", "files": mocked_files}
|
|
2966
|
+
json.dump(output, sys.stdout, cls=_SafeEncoder)
|
|
2967
|
+
sys.stdout.write("\n")
|
|
2968
|
+
|
|
2969
|
+
|
|
2970
|
+
# ===================================================================
|
|
2971
|
+
# Subcommand: uat-pop-mocks
|
|
2972
|
+
# ===================================================================
|
|
2973
|
+
|
|
2974
|
+
|
|
2975
|
+
def cmd_uat_pop_mocks(args: argparse.Namespace) -> None:
|
|
2976
|
+
"""Restore stashed mocks after fix is committed.
|
|
2977
|
+
|
|
2978
|
+
Contract:
|
|
2979
|
+
Args: phase (str) — phase number
|
|
2980
|
+
Output: JSON — status and conflicts list
|
|
2981
|
+
Exit codes: 0 = success (or no-op), 1 = unexpected failure
|
|
2982
|
+
Side effects: git stash pop, updates UAT.md stash_ref/mocked_files
|
|
2983
|
+
"""
|
|
2984
|
+
uat_path, uat = _load_uat(args.phase)
|
|
2985
|
+
|
|
2986
|
+
stash_ref = uat.frontmatter.get("stash_ref")
|
|
2987
|
+
if not stash_ref:
|
|
2988
|
+
print("No stash to pop", file=sys.stderr)
|
|
2989
|
+
return
|
|
2990
|
+
|
|
2991
|
+
try:
|
|
2992
|
+
run_git("stash", "pop", stash_ref)
|
|
2993
|
+
uat.update_session({"stash_ref": ""})
|
|
2994
|
+
uat_path.write_text(uat.serialize(), encoding="utf-8")
|
|
2995
|
+
output = {"status": "restored", "conflicts": []}
|
|
2996
|
+
json.dump(output, sys.stdout, cls=_SafeEncoder)
|
|
2997
|
+
sys.stdout.write("\n")
|
|
2998
|
+
except subprocess.CalledProcessError:
|
|
2999
|
+
# Check for merge conflicts
|
|
3000
|
+
try:
|
|
3001
|
+
conflict_output = run_git("diff", "--name-only", "--diff-filter=U")
|
|
3002
|
+
except subprocess.CalledProcessError:
|
|
3003
|
+
conflict_output = ""
|
|
3004
|
+
|
|
3005
|
+
conflicts = [f.strip() for f in conflict_output.splitlines() if f.strip()]
|
|
3006
|
+
if not conflicts:
|
|
3007
|
+
print("Error: stash pop failed with no merge conflicts — unexpected failure", file=sys.stderr)
|
|
3008
|
+
sys.exit(1)
|
|
3009
|
+
|
|
3010
|
+
# Resolve conflicts by taking the fix version (theirs)
|
|
3011
|
+
for f in conflicts:
|
|
3012
|
+
run_git("checkout", "--theirs", f)
|
|
3013
|
+
run_git("add", f)
|
|
3014
|
+
|
|
3015
|
+
# Drop the stash (failed pop doesn't auto-drop)
|
|
3016
|
+
try:
|
|
3017
|
+
run_git("stash", "drop", stash_ref)
|
|
3018
|
+
except subprocess.CalledProcessError:
|
|
3019
|
+
pass # Best effort
|
|
3020
|
+
|
|
3021
|
+
# Remove conflicted files from mocked_files
|
|
3022
|
+
mocked = uat.frontmatter.get("mocked_files", [])
|
|
3023
|
+
removed = [f for f in conflicts if f in mocked]
|
|
3024
|
+
uat.frontmatter["mocked_files"] = [f for f in mocked if f not in conflicts]
|
|
3025
|
+
uat.update_session({"stash_ref": ""})
|
|
3026
|
+
uat_path.write_text(uat.serialize(), encoding="utf-8")
|
|
3027
|
+
|
|
3028
|
+
output = {"status": "restored_with_conflicts", "conflicts": conflicts, "removed_from_mocks": removed}
|
|
3029
|
+
json.dump(output, sys.stdout, cls=_SafeEncoder)
|
|
3030
|
+
sys.stdout.write("\n")
|
|
3031
|
+
|
|
3032
|
+
|
|
3033
|
+
# ===================================================================
|
|
3034
|
+
# Subcommand: uat-fix-commit
|
|
3035
|
+
# ===================================================================
|
|
3036
|
+
|
|
3037
|
+
|
|
3038
|
+
def cmd_uat_fix_commit(args: argparse.Namespace) -> None:
|
|
3039
|
+
"""Stage files, commit (or amend), and record fix in UAT.md.
|
|
3040
|
+
|
|
3041
|
+
Contract:
|
|
3042
|
+
Args: phase (str), --test (int), --message (str), files (list)
|
|
3043
|
+
Output: JSON — hash and amend flag
|
|
3044
|
+
Exit codes: 0 = success, 1 = no files or git failure
|
|
3045
|
+
Side effects: git add/commit, updates UAT.md fix_status/fix_commit
|
|
3046
|
+
"""
|
|
3047
|
+
uat_path, uat = _load_uat(args.phase)
|
|
3048
|
+
|
|
3049
|
+
if not args.files:
|
|
3050
|
+
print("Error: No files to commit", file=sys.stderr)
|
|
3051
|
+
sys.exit(1)
|
|
3052
|
+
|
|
3053
|
+
test_num = args.test
|
|
3054
|
+
|
|
3055
|
+
# Find previous fix_commit for this test
|
|
3056
|
+
prev_fix_commit = ""
|
|
3057
|
+
for t in uat.tests:
|
|
3058
|
+
if t["num"] == str(test_num):
|
|
3059
|
+
prev_fix_commit = t.get("fix_commit", "")
|
|
3060
|
+
break
|
|
3061
|
+
|
|
3062
|
+
run_git("add", *args.files)
|
|
3063
|
+
|
|
3064
|
+
amend = False
|
|
3065
|
+
if prev_fix_commit:
|
|
3066
|
+
head_short = run_git("rev-parse", "--short", "HEAD")
|
|
3067
|
+
if head_short == prev_fix_commit:
|
|
3068
|
+
amend = True
|
|
3069
|
+
|
|
3070
|
+
if amend:
|
|
3071
|
+
run_git("commit", "--amend", "--no-edit")
|
|
3072
|
+
else:
|
|
3073
|
+
run_git("commit", "-m", args.message)
|
|
3074
|
+
|
|
3075
|
+
new_hash = run_git("rev-parse", "--short", "HEAD")
|
|
3076
|
+
uat.update_test(test_num, {"fix_status": "applied", "fix_commit": new_hash})
|
|
3077
|
+
uat_path.write_text(uat.serialize(), encoding="utf-8")
|
|
3078
|
+
|
|
3079
|
+
output = {"hash": new_hash, "amend": amend}
|
|
3080
|
+
json.dump(output, sys.stdout, cls=_SafeEncoder)
|
|
3081
|
+
sys.stdout.write("\n")
|
|
3082
|
+
|
|
3083
|
+
|
|
3084
|
+
# ===================================================================
|
|
3085
|
+
# Subcommand: uat-revert-mocks
|
|
3086
|
+
# ===================================================================
|
|
3087
|
+
|
|
3088
|
+
|
|
3089
|
+
def cmd_uat_revert_mocks(args: argparse.Namespace) -> None:
|
|
3090
|
+
"""Revert mocked files and clear the mocked_files list.
|
|
3091
|
+
|
|
3092
|
+
Contract:
|
|
3093
|
+
Args: phase (str) — phase number
|
|
3094
|
+
Output: JSON — reverted files list
|
|
3095
|
+
Exit codes: 0 = success (or no-op), 1 = git failure
|
|
3096
|
+
Side effects: git checkout, clears mocked_files in UAT.md
|
|
3097
|
+
"""
|
|
3098
|
+
uat_path, uat = _load_uat(args.phase)
|
|
3099
|
+
|
|
3100
|
+
mocked_files = uat.frontmatter.get("mocked_files", [])
|
|
3101
|
+
if not mocked_files:
|
|
3102
|
+
print("No mocked files to revert", file=sys.stderr)
|
|
3103
|
+
return
|
|
3104
|
+
|
|
3105
|
+
try:
|
|
3106
|
+
run_git("checkout", "--", *mocked_files)
|
|
3107
|
+
except subprocess.CalledProcessError as e:
|
|
3108
|
+
print(f"Error: git checkout failed: {e.stderr}", file=sys.stderr)
|
|
3109
|
+
sys.exit(1)
|
|
3110
|
+
|
|
3111
|
+
reverted = list(mocked_files)
|
|
3112
|
+
uat.frontmatter["mocked_files"] = []
|
|
3113
|
+
uat_path.write_text(uat.serialize(), encoding="utf-8")
|
|
3114
|
+
|
|
3115
|
+
output = {"reverted": reverted}
|
|
3116
|
+
json.dump(output, sys.stdout, cls=_SafeEncoder)
|
|
3117
|
+
sys.stdout.write("\n")
|
|
3118
|
+
|
|
3119
|
+
|
|
2867
3120
|
# ===================================================================
|
|
2868
3121
|
# config-get / config-set / config-delete
|
|
2869
3122
|
# ===================================================================
|
|
@@ -3072,6 +3325,29 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
3072
3325
|
p.add_argument("phase", help="Phase number")
|
|
3073
3326
|
p.set_defaults(func=cmd_uat_status)
|
|
3074
3327
|
|
|
3328
|
+
# --- uat-stash-mocks ---
|
|
3329
|
+
p = subparsers.add_parser("uat-stash-mocks", help="Stash mocked files before fix")
|
|
3330
|
+
p.add_argument("phase", help="Phase number")
|
|
3331
|
+
p.set_defaults(func=cmd_uat_stash_mocks)
|
|
3332
|
+
|
|
3333
|
+
# --- uat-pop-mocks ---
|
|
3334
|
+
p = subparsers.add_parser("uat-pop-mocks", help="Restore stashed mocks after fix")
|
|
3335
|
+
p.add_argument("phase", help="Phase number")
|
|
3336
|
+
p.set_defaults(func=cmd_uat_pop_mocks)
|
|
3337
|
+
|
|
3338
|
+
# --- uat-fix-commit ---
|
|
3339
|
+
p = subparsers.add_parser("uat-fix-commit", help="Commit fix and record in UAT.md")
|
|
3340
|
+
p.add_argument("phase", help="Phase number")
|
|
3341
|
+
p.add_argument("--test", type=int, required=True, help="Test number")
|
|
3342
|
+
p.add_argument("--message", required=True, help="Commit message")
|
|
3343
|
+
p.add_argument("files", nargs="*", help="Files to stage and commit")
|
|
3344
|
+
p.set_defaults(func=cmd_uat_fix_commit)
|
|
3345
|
+
|
|
3346
|
+
# --- uat-revert-mocks ---
|
|
3347
|
+
p = subparsers.add_parser("uat-revert-mocks", help="Revert mocked files to clean state")
|
|
3348
|
+
p.add_argument("phase", help="Phase number")
|
|
3349
|
+
p.set_defaults(func=cmd_uat_revert_mocks)
|
|
3350
|
+
|
|
3075
3351
|
# --- config-get ---
|
|
3076
3352
|
p = subparsers.add_parser("config-get", help="Read a value from config.json by dot-path")
|
|
3077
3353
|
p.add_argument("key", help="Dot-notation key (e.g. subsystems, code_review.phase, subsystems.0)")
|