mindsystem-cc 4.1.2 → 4.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.
- package/agents/ms-browser-verifier.md +102 -0
- package/bin/install.js +2 -3
- package/commands/ms/audit-milestone.md +1 -1
- package/commands/ms/config.md +69 -19
- package/commands/ms/create-roadmap.md +13 -0
- package/commands/ms/doctor.md +7 -3
- package/commands/ms/execute-phase.md +21 -14
- package/mindsystem/references/browser-verification.md +76 -0
- package/mindsystem/templates/config.json +4 -1
- package/mindsystem/workflows/complete-milestone.md +1 -1
- package/mindsystem/workflows/doctor-fixes.md +32 -3
- package/mindsystem/workflows/execute-phase.md +34 -2
- package/package.json +2 -2
- package/scripts/ms-tools.py +448 -52
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ms-browser-verifier
|
|
3
|
+
description: Automated functional verification via browser. Tests observable truths, fixes issues inline, reports patterns for knowledge compounding.
|
|
4
|
+
model: sonnet
|
|
5
|
+
tools: Read, Write, Edit, Bash, Grep, Glob
|
|
6
|
+
skills:
|
|
7
|
+
- agent-browser
|
|
8
|
+
color: green
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
<role>
|
|
12
|
+
You are a Mindsystem browser verifier. You test observable truths from VERIFICATION.md by interacting with the app in a real browser. When you find issues, you fix them inline and re-verify.
|
|
13
|
+
|
|
14
|
+
**Critical mindset:** Test what users see, not what code claims. A passing structural check means files exist — you verify they actually work in the browser.
|
|
15
|
+
</role>
|
|
16
|
+
|
|
17
|
+
<process>
|
|
18
|
+
|
|
19
|
+
<step name="start_dev_server">
|
|
20
|
+
Check if dev server is already running by probing common ports (5173, 3000, 8080, 4200, 3001).
|
|
21
|
+
|
|
22
|
+
If not running:
|
|
23
|
+
1. Read `package.json` scripts for dev command (`dev`, `start`, `serve`)
|
|
24
|
+
2. Start via `npm run {script}` or `yarn {script}` based on lock file presence
|
|
25
|
+
3. Retry port probe with backoff (1s, 2s, 4s) until ready or timeout (30s)
|
|
26
|
+
</step>
|
|
27
|
+
|
|
28
|
+
<step name="check_auth_state">
|
|
29
|
+
Auth is handled by the orchestrator before spawning this agent. If the orchestrator provides a storage state file path, load it when launching the browser to restore the authenticated session.
|
|
30
|
+
|
|
31
|
+
Open the app URL headless. If redirected to login, report auth failure and exit — do not attempt to automate auth.
|
|
32
|
+
</step>
|
|
33
|
+
|
|
34
|
+
<step name="extract_testable_truths">
|
|
35
|
+
Read VERIFICATION.md from the phase directory. Extract observable truths and filter for browser-testable ones.
|
|
36
|
+
|
|
37
|
+
Read plans and summaries to understand pages, routes, and components involved.
|
|
38
|
+
|
|
39
|
+
**Browser-testable:** UI renders, navigation works, form submission succeeds, data displays correctly, interactions produce expected state changes.
|
|
40
|
+
|
|
41
|
+
**Not browser-testable:** API internals, database state, background job execution, server-side logging.
|
|
42
|
+
</step>
|
|
43
|
+
|
|
44
|
+
<step name="verify_each_truth">
|
|
45
|
+
Create the screenshots directory:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
mkdir -p {screenshots_dir}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
For each testable truth:
|
|
52
|
+
1. Navigate to the relevant page/route
|
|
53
|
+
2. Take a screenshot, save to `{screenshots_dir}/{truth-slug}.png`
|
|
54
|
+
3. Interact as needed (click, type, submit)
|
|
55
|
+
4. Wait for `networkidle` before verifying expected state
|
|
56
|
+
5. Verify expected state (element exists, text matches, route changed)
|
|
57
|
+
6. Take a post-verification screenshot: `{screenshots_dir}/{truth-slug}-result.png`
|
|
58
|
+
|
|
59
|
+
**Critical:** Re-snapshot after every DOM change. Element references go stale after navigation or dynamic updates.
|
|
60
|
+
</step>
|
|
61
|
+
|
|
62
|
+
<step name="fix_issues">
|
|
63
|
+
If an issue is found:
|
|
64
|
+
1. Investigate: read source files for the component/page
|
|
65
|
+
2. Fix: use Edit/Write to correct the issue
|
|
66
|
+
3. Wait for hot reload (probe dev server)
|
|
67
|
+
4. Re-verify in browser
|
|
68
|
+
5. If fixed: commit with `fix({phase}-browser): {description}`
|
|
69
|
+
6. If fix fails after one retry: flag as unresolved, continue to next truth
|
|
70
|
+
</step>
|
|
71
|
+
|
|
72
|
+
<step name="close_and_report">
|
|
73
|
+
Close the browser when all truths are verified.
|
|
74
|
+
|
|
75
|
+
Return a structured report to the orchestrator:
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
## Browser Verification Report
|
|
79
|
+
|
|
80
|
+
**Tested:** {count} | **Passed:** {count} | **Fixed:** {count} | **Unresolved:** {count}
|
|
81
|
+
|
|
82
|
+
### Fixes Applied
|
|
83
|
+
- {what was wrong} → {what was fixed} | Files: {changed files}
|
|
84
|
+
|
|
85
|
+
### Unresolved Issues
|
|
86
|
+
- {description} | Attempted: {what was tried}
|
|
87
|
+
|
|
88
|
+
### Patterns for Knowledge
|
|
89
|
+
- {recurring pattern observed across multiple truths}
|
|
90
|
+
```
|
|
91
|
+
</step>
|
|
92
|
+
|
|
93
|
+
</process>
|
|
94
|
+
|
|
95
|
+
<rules>
|
|
96
|
+
- Save all screenshots to `{screenshots_dir}` — never to temp or working directory
|
|
97
|
+
- Re-snapshot after every DOM change (refs go stale)
|
|
98
|
+
- Wait for networkidle before verifying
|
|
99
|
+
- Do not attempt to automate auth (orchestrator handles it)
|
|
100
|
+
- One retry per fix, then flag and move on
|
|
101
|
+
- Commit each fix atomically with `fix({phase}-browser):` prefix
|
|
102
|
+
</rules>
|
package/bin/install.js
CHANGED
|
@@ -25,9 +25,8 @@ ${cyan} ███╗ ███╗██╗███╗ ██╗███
|
|
|
25
25
|
╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚═════╝ ╚══════╝ ╚═╝ ╚══════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝${reset}
|
|
26
26
|
|
|
27
27
|
Mindsystem ${dim}v${pkg.version}${reset}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
Based on GSD by TÂCHES.
|
|
28
|
+
The engineer's meta-prompting system for Claude Code.
|
|
29
|
+
Amplify your workflow. Compound your knowledge.
|
|
31
30
|
`;
|
|
32
31
|
|
|
33
32
|
// Parse args
|
|
@@ -202,7 +202,7 @@ Skip code review step (proceed to next steps).
|
|
|
202
202
|
```bash
|
|
203
203
|
# Find first commit in milestone (first phase commit)
|
|
204
204
|
FIRST_PHASE=$(ls -d .planning/phases/*/ | sort -V | head -1 | xargs basename | cut -d- -f1)
|
|
205
|
-
FIRST_COMMIT=$(
|
|
205
|
+
FIRST_COMMIT=$(ms-tools find-phase-commits ${FIRST_PHASE} | tail -1)
|
|
206
206
|
|
|
207
207
|
# Get all implementation files changed since first commit
|
|
208
208
|
CHANGED_FILES=$(git diff --name-only ${FIRST_COMMIT}^..HEAD | grep -E '\.(dart|ts|tsx|js|jsx|swift|kt|py|go|rs)$')
|
package/commands/ms/config.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ms:config
|
|
3
|
-
description: Configure Mindsystem preferences — code reviewers, mockups, gitignore, git remote, task tracker
|
|
3
|
+
description: Configure Mindsystem preferences — code reviewers, mockups, browser verification, gitignore, git remote, task tracker
|
|
4
4
|
allowed-tools:
|
|
5
5
|
- Read
|
|
6
6
|
- Write
|
|
@@ -12,7 +12,7 @@ allowed-tools:
|
|
|
12
12
|
|
|
13
13
|
Configure Mindsystem preferences for the current project.
|
|
14
14
|
|
|
15
|
-
Manages code reviewer agents, mockup preferences, .gitignore patterns for `.planning/` artifacts, git remote setup, and task tracker integration. Run anytime to reconfigure — idempotent.
|
|
15
|
+
Manages code reviewer agents, mockup preferences, browser verification, .gitignore patterns for `.planning/` artifacts, git remote setup, and task tracker integration. Run anytime to reconfigure — idempotent.
|
|
16
16
|
|
|
17
17
|
</objective>
|
|
18
18
|
|
|
@@ -33,6 +33,36 @@ Manages code reviewer agents, mockup preferences, .gitignore patterns for `.plan
|
|
|
33
33
|
git remote -v 2>/dev/null || echo "NO_REMOTE"
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
+
**Determine mode** from `code_review` values in config.json:
|
|
37
|
+
|
|
38
|
+
- **Setup mode** — all `code_review` values are null (or no config file): first-time configuration.
|
|
39
|
+
- **Edit mode** — any `code_review` value is non-null: reconfiguration.
|
|
40
|
+
|
|
41
|
+
</step>
|
|
42
|
+
|
|
43
|
+
<step name="route">
|
|
44
|
+
|
|
45
|
+
**Setup mode:** Proceed through all setting steps sequentially (git_remote → code_reviewers → gitignore_patterns → mockup_preferences → browser_verification → task_tracker). Then go to `validation_summary`.
|
|
46
|
+
|
|
47
|
+
**Edit mode:** Display all current settings with values from config.json, git remote, and .gitignore:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
## Current Settings
|
|
51
|
+
|
|
52
|
+
1. **Git remote** — {remote URL or "none configured"}
|
|
53
|
+
2. **Code reviewers** — adhoc: {value or "not set"}, phase: {value or "not set"}, milestone: {value or "not set"}
|
|
54
|
+
3. **Gitignore** — {current .planning/ patterns or "no .planning/ patterns"}
|
|
55
|
+
4. **Mockups** — open: {auto / ask / off}
|
|
56
|
+
5. **Browser verification** — {enabled / disabled}
|
|
57
|
+
6. **Task tracker** — {type + cli path, or "none"}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Ask: "Which settings would you like to change? Enter the numbers (e.g. 1, 3, 5), 'all' to reconfigure everything, or 'done' if everything looks good."
|
|
61
|
+
|
|
62
|
+
- **"done"** → skip to `validation_summary` (no changes)
|
|
63
|
+
- **"all"** → proceed through all setting steps sequentially
|
|
64
|
+
- **Specific numbers** → proceed through only the corresponding setting steps, skip the rest
|
|
65
|
+
|
|
36
66
|
</step>
|
|
37
67
|
|
|
38
68
|
<step name="git_remote">
|
|
@@ -100,12 +130,14 @@ Use AskUserQuestion (multiSelect):
|
|
|
100
130
|
- options:
|
|
101
131
|
- "Phase patch files (`.planning/phases/**/*.patch`)" — Large binary diffs, regeneratable
|
|
102
132
|
- "Design mockups (`.planning/phases/**/*.html`)" — Generated HTML mockups from design-phase
|
|
133
|
+
- "Browser screenshots (`.planning/phases/**/screenshots/`)" — Browser verification screenshots
|
|
103
134
|
|
|
104
135
|
Apply selected patterns to `.gitignore`. Create the file if needed:
|
|
105
136
|
|
|
106
137
|
```bash
|
|
107
|
-
echo '.planning/phases/**/*.patch' >> .gitignore
|
|
108
|
-
echo '.planning/phases/**/*.html' >> .gitignore
|
|
138
|
+
echo '.planning/phases/**/*.patch' >> .gitignore # if selected
|
|
139
|
+
echo '.planning/phases/**/*.html' >> .gitignore # if selected
|
|
140
|
+
echo '.planning/phases/**/screenshots/' >> .gitignore # if selected
|
|
109
141
|
```
|
|
110
142
|
|
|
111
143
|
If no selections: skip gitignore changes.
|
|
@@ -142,6 +174,34 @@ ms-tools config-set open_mockups "$VALUE"
|
|
|
142
174
|
|
|
143
175
|
</step>
|
|
144
176
|
|
|
177
|
+
<step name="browser_verification">
|
|
178
|
+
|
|
179
|
+
Read current value:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
CURRENT=$(ms-tools config-get browser_verification.enabled --default "true")
|
|
183
|
+
echo "Current browser_verification.enabled: $CURRENT"
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Use AskUserQuestion:
|
|
187
|
+
- header: "Browser verification"
|
|
188
|
+
- question: "Enable automated browser verification during execute-phase? (Tests your web UI after code changes)"
|
|
189
|
+
- options:
|
|
190
|
+
- "Enabled (Recommended)" — Automatically test web UI after phase execution
|
|
191
|
+
- "Disabled" — Skip browser verification
|
|
192
|
+
|
|
193
|
+
Map selection:
|
|
194
|
+
- "Enabled" → `true`
|
|
195
|
+
- "Disabled" → `false`
|
|
196
|
+
|
|
197
|
+
Update config.json:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
ms-tools config-set browser_verification --json '{"enabled": true}' # or false
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
</step>
|
|
204
|
+
|
|
145
205
|
<step name="task_tracker">
|
|
146
206
|
|
|
147
207
|
Read current value:
|
|
@@ -193,6 +253,7 @@ Configuration updated:
|
|
|
193
253
|
|
|
194
254
|
- Code reviewers: [adhoc / phase / milestone values]
|
|
195
255
|
- Mockup open: [auto / ask / off]
|
|
256
|
+
- Browser verification: [enabled / disabled]
|
|
196
257
|
- Gitignore: [patterns added, or "no changes"]
|
|
197
258
|
- Git remote: [remote URL, or "none configured"]
|
|
198
259
|
- Task tracker: [type + cli path, or "none"]
|
|
@@ -217,28 +278,17 @@ EOF
|
|
|
217
278
|
|
|
218
279
|
</step>
|
|
219
280
|
|
|
220
|
-
<step name="done">
|
|
221
|
-
|
|
222
|
-
Present next steps using the "Next Up" format:
|
|
223
|
-
|
|
224
|
-
- **If PROJECT.md exists:** recommend `/ms:new-milestone` — Discover what to build next, create requirements and roadmap
|
|
225
|
-
- **If no PROJECT.md:** recommend `/ms:new-project` — Initialize project with business context and vision
|
|
226
|
-
|
|
227
|
-
Also list: `/ms:doctor` — Verify subsystems and artifact health
|
|
228
|
-
|
|
229
|
-
</step>
|
|
230
|
-
|
|
231
281
|
</process>
|
|
232
282
|
|
|
233
283
|
<success_criteria>
|
|
234
284
|
|
|
285
|
+
- [ ] Setup mode triggered when all code_review values null; edit mode otherwise
|
|
286
|
+
- [ ] Edit mode displays numbered settings with current values
|
|
287
|
+
- [ ] Only user-selected settings modified in edit mode
|
|
235
288
|
- [ ] Changes committed (if any)
|
|
236
|
-
- [ ] User routed to next step
|
|
237
289
|
- [ ] Gitignore patterns applied (if selected)
|
|
238
290
|
- [ ] Git remote offered (if missing)
|
|
239
291
|
- [ ] Validation summary displayed
|
|
240
|
-
- [ ]
|
|
241
|
-
- [ ] Config.json open_mockups value set (or preserved if skipped)
|
|
242
|
-
- [ ] Config.json task_tracker value set (or preserved if skipped)
|
|
292
|
+
- [ ] Terminates after commit/summary — no Next Up routing
|
|
243
293
|
|
|
244
294
|
</success_criteria>
|
|
@@ -292,6 +292,19 @@ EOF
|
|
|
292
292
|
```
|
|
293
293
|
</step>
|
|
294
294
|
|
|
295
|
+
<step name="create_phase_dirs">
|
|
296
|
+
Create phase directories from the roadmap:
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
ms-tools create-phase-dirs
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
git add .planning/phases/
|
|
304
|
+
git commit -m "chore: create phase directories from roadmap"
|
|
305
|
+
```
|
|
306
|
+
</step>
|
|
307
|
+
|
|
295
308
|
<step name="done">
|
|
296
309
|
```
|
|
297
310
|
Requirements and roadmap created:
|
package/commands/ms/doctor.md
CHANGED
|
@@ -13,7 +13,7 @@ allowed-tools:
|
|
|
13
13
|
---
|
|
14
14
|
|
|
15
15
|
<objective>
|
|
16
|
-
Run health checks on project configuration. Detect and fix structural drift across
|
|
16
|
+
Run health checks on project configuration. Detect and fix structural drift across 12 categories: subsystem vocabulary, milestone directory structure, milestone naming convention, phase archival, knowledge files, phase summaries, PLAN cleanup, CLI wrappers and environment diagnostics, research API keys, phase directory naming, browser verification prerequisites, and Mindsystem version.
|
|
17
17
|
|
|
18
18
|
Idempotent.
|
|
19
19
|
</objective>
|
|
@@ -119,6 +119,8 @@ Display results as a markdown table:
|
|
|
119
119
|
| PLAN cleanup | FAIL | 9 leftover PLAN.md files |
|
|
120
120
|
| CLI wrappers | FAIL | Not resolvable; bin dir not in PATH |
|
|
121
121
|
| Research API Keys | WARN | PERPLEXITY_API_KEY not set |
|
|
122
|
+
| Phase directory naming | FAIL | 1 non-canonical directory |
|
|
123
|
+
| Browser Verification | WARN | Web project, missing agent-browser CLI |
|
|
122
124
|
| Mindsystem version | WARN | v3.21.0 → v3.22.1 available |
|
|
123
125
|
```
|
|
124
126
|
|
|
@@ -141,7 +143,7 @@ If "Skip" → go to `report`.
|
|
|
141
143
|
|
|
142
144
|
If "Review each" → use AskUserQuestion for each failed check with its details and options: "Fix" / "Skip". Only run fixes for accepted checks.
|
|
143
145
|
|
|
144
|
-
Apply fixes in dependency order: fix_subsystems → fix_milestone_dirs → fix_milestone_naming → fix_phase_archival → fix_plan_cleanup → fix_knowledge. Skip any fix whose check passed or was skipped by user.
|
|
146
|
+
Apply fixes in dependency order: fix_subsystems → fix_milestone_dirs → fix_milestone_naming → fix_phase_archival → fix_plan_cleanup → fix_phase_dirs → fix_knowledge. Skip any fix whose check passed or was skipped by user.
|
|
145
147
|
|
|
146
148
|
Phase summaries are resolved by fix_phase_archival. CLI wrapper failures have specific fixes: bin dir not in PATH → restart Claude Code session; missing wrappers or bin dir → re-run `npx mindsystem-cc`; uv not found → `curl -LsSf https://astral.sh/uv/install.sh | sh`. WARN checks (Research API Keys, missing uv) are informational — no automated fix, only displayed in the report.
|
|
147
149
|
</step>
|
|
@@ -179,6 +181,8 @@ Final summary table:
|
|
|
179
181
|
| PLAN cleanup | PASS | ... |
|
|
180
182
|
| CLI wrappers | PASS | ... |
|
|
181
183
|
| Research API Keys | PASS | ... |
|
|
184
|
+
| Phase directory naming | PASS | ... |
|
|
185
|
+
| Browser Verification | PASS | ... |
|
|
182
186
|
| Mindsystem version | PASS | ... |
|
|
183
187
|
|
|
184
188
|
All checks passed.
|
|
@@ -195,6 +199,6 @@ Include counts: checks total, passed, warned, fixed during this run.
|
|
|
195
199
|
- [ ] Re-scan verifies all checks pass after fixes
|
|
196
200
|
- [ ] Each fix group committed atomically
|
|
197
201
|
- [ ] Fixes applied in dependency order: subsystems → dirs → milestone naming → archival → cleanup → knowledge
|
|
198
|
-
- [ ] All
|
|
202
|
+
- [ ] All 12 categories reported with PASS/FAIL/WARN/SKIP
|
|
199
203
|
- [ ] Clean project reports all PASS with no fix prompts
|
|
200
204
|
</success_criteria>
|
|
@@ -77,29 +77,35 @@ ms-tools find-phase "$ARGUMENTS"
|
|
|
77
77
|
- `passed` → continue to step 7
|
|
78
78
|
- `gaps_found` → present gaps, route via gap-closure-routing.md triage
|
|
79
79
|
|
|
80
|
-
7. **
|
|
80
|
+
7. **Browser verification (web projects)**
|
|
81
|
+
- Run `ms-tools browser-check` for prerequisites
|
|
82
|
+
- If ready: handle auth, spawn `ms-browser-verifier` for functional testing
|
|
83
|
+
- Verifier tests observable truths in browser, fixes issues inline
|
|
84
|
+
- If skipped: not a web project or browser verification disabled
|
|
85
|
+
|
|
86
|
+
8. **Code review (optional)**
|
|
81
87
|
- Read `code_review.phase` from config.json (default: `ms-code-simplifier`)
|
|
82
|
-
- If `"skip"`: proceed to step
|
|
88
|
+
- If `"skip"`: proceed to step 9
|
|
83
89
|
- Spawn code review agent with phase file scope
|
|
84
90
|
- If changes made: commit as `refactor({phase}): code review improvements`
|
|
85
91
|
|
|
86
|
-
|
|
92
|
+
9. **Generate phase patch**
|
|
87
93
|
- Run: `ms-tools generate-phase-patch ${PHASE_NUMBER}`
|
|
88
94
|
- Outputs to `.planning/phases/{phase_dir}/{phase}-changes.patch`
|
|
89
95
|
- Verify: patch file exists OR skip message logged
|
|
90
96
|
- Note: Patch captures all changes including simplifications
|
|
91
97
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
+
10. **Consolidate knowledge**
|
|
99
|
+
- Spawn `ms-consolidator` with phase directory and number
|
|
100
|
+
- Consolidator reads phase artifacts and existing knowledge files
|
|
101
|
+
- Produces updated `.planning/knowledge/{subsystem}.md` files
|
|
102
|
+
- Deletes PLAN.md files (execution instructions consumed)
|
|
103
|
+
- Verify: knowledge files written to `.planning/knowledge/`
|
|
98
104
|
|
|
99
|
-
|
|
105
|
+
11. **Update roadmap and state**
|
|
100
106
|
- Update ROADMAP.md, STATE.md
|
|
101
107
|
|
|
102
|
-
|
|
108
|
+
12. **Update requirements**
|
|
103
109
|
Mark phase requirements as Complete:
|
|
104
110
|
- Read ROADMAP.md, find this phase's `Requirements:` line (e.g., "AUTH-01, AUTH-02")
|
|
105
111
|
- Read REQUIREMENTS.md traceability table
|
|
@@ -107,7 +113,7 @@ ms-tools find-phase "$ARGUMENTS"
|
|
|
107
113
|
- Write updated REQUIREMENTS.md
|
|
108
114
|
- Skip if: REQUIREMENTS.md doesn't exist, or phase has no Requirements line
|
|
109
115
|
|
|
110
|
-
|
|
116
|
+
13. **Commit phase completion**
|
|
111
117
|
Bundle all phase metadata updates in one commit:
|
|
112
118
|
- Stage: `git add .planning/ROADMAP.md .planning/STATE.md`
|
|
113
119
|
- Stage knowledge files: `git add .planning/knowledge/*.md`
|
|
@@ -115,10 +121,10 @@ ms-tools find-phase "$ARGUMENTS"
|
|
|
115
121
|
- Stage REQUIREMENTS.md if updated: `git add .planning/REQUIREMENTS.md`
|
|
116
122
|
- Commit: `docs({phase}): complete {phase-name} phase`
|
|
117
123
|
|
|
118
|
-
|
|
124
|
+
14. **Offer next steps**
|
|
119
125
|
- Route to next action (see `<offer_next>`)
|
|
120
126
|
|
|
121
|
-
|
|
127
|
+
15. **Update last command:** `ms-tools set-last-command "ms:execute-phase $ARGUMENTS"`
|
|
122
128
|
</process>
|
|
123
129
|
|
|
124
130
|
<offer_next>
|
|
@@ -247,6 +253,7 @@ After all plans in phase complete:
|
|
|
247
253
|
- [ ] All incomplete plans in phase executed
|
|
248
254
|
- [ ] Code review completed (or skipped if config says "skip")
|
|
249
255
|
- [ ] Phase goal verified (Must-Haves checked against codebase)
|
|
256
|
+
- [ ] Browser verification completed or skipped (non-web/disabled)
|
|
250
257
|
- [ ] VERIFICATION.md created in phase directory
|
|
251
258
|
- [ ] Patch file generated OR explicitly skipped with message
|
|
252
259
|
- [ ] Knowledge files written to .planning/knowledge/ (consolidation complete)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<browser_verification>
|
|
2
|
+
|
|
3
|
+
# Browser Verification Reference
|
|
4
|
+
|
|
5
|
+
Lazily loaded by execute-phase when `ms-tools browser-check` returns READY (exit 0).
|
|
6
|
+
|
|
7
|
+
## Auth Flow
|
|
8
|
+
|
|
9
|
+
Handle browser authentication before spawning the verifier agent.
|
|
10
|
+
|
|
11
|
+
**Step 1: Check for existing auth state**
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
AUTH_STATE=".agent-browser-state.json"
|
|
15
|
+
[ -f "$AUTH_STATE" ] && echo "HAS_STATE" || echo "NO_STATE"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**Step 2: Detect dev server URL**
|
|
19
|
+
|
|
20
|
+
Probe common ports to find the running dev server:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
for PORT in 5173 3000 8080 4200 3001; do
|
|
24
|
+
curl -s -o /dev/null -w "%{http_code}" "http://localhost:$PORT" 2>/dev/null | grep -q "200\|301\|302" && echo "http://localhost:$PORT" && break
|
|
25
|
+
done
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Store the result as `{dev_url}`.
|
|
29
|
+
|
|
30
|
+
**Step 3: Validate or establish auth**
|
|
31
|
+
|
|
32
|
+
If `HAS_STATE`:
|
|
33
|
+
1. Open app headless at `{dev_url}`
|
|
34
|
+
2. Check current URL — if redirected to a login/auth path, auth has expired
|
|
35
|
+
3. If still on app pages: auth valid, proceed to Spawn
|
|
36
|
+
|
|
37
|
+
If `NO_STATE` or auth expired:
|
|
38
|
+
1. Close headless browser
|
|
39
|
+
2. Open `{dev_url}` in **headed** mode (visible browser)
|
|
40
|
+
3. Use AskUserQuestion:
|
|
41
|
+
- header: "Browser authentication"
|
|
42
|
+
- question: "Please log in to the app in the browser window. Select 'Done' when you've logged in."
|
|
43
|
+
- options: ["Done — I'm logged in", "Skip browser verification"]
|
|
44
|
+
4. If user logged in: save browser state to `.agent-browser-state.json`, close headed browser, ensure gitignored:
|
|
45
|
+
```bash
|
|
46
|
+
grep -q "\.agent-browser-state\.json" .gitignore 2>/dev/null || echo '.agent-browser-state.json' >> .gitignore
|
|
47
|
+
```
|
|
48
|
+
5. If user skips: proceed to code_review, skip browser verification
|
|
49
|
+
|
|
50
|
+
## Spawn
|
|
51
|
+
|
|
52
|
+
Spawn the browser verifier agent after auth is established:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
Task(
|
|
56
|
+
prompt="Run browser verification for phase {phase_number}.
|
|
57
|
+
|
|
58
|
+
Phase directory: {phase_dir}
|
|
59
|
+
Phase goal: {phase_goal}
|
|
60
|
+
VERIFICATION.md: {verification_path}
|
|
61
|
+
Dev URL: {dev_url}
|
|
62
|
+
Auth state: .agent-browser-state.json
|
|
63
|
+
Screenshots directory: {phase_dir}/screenshots
|
|
64
|
+
|
|
65
|
+
Read VERIFICATION.md for observable truths. Test browser-testable ones.
|
|
66
|
+
Save all screenshots to {phase_dir}/screenshots/.
|
|
67
|
+
Fix issues inline. Return structured report.",
|
|
68
|
+
subagent_type="ms-browser-verifier"
|
|
69
|
+
)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**After verifier returns:**
|
|
73
|
+
|
|
74
|
+
If fixes were made, include the report summary in the consolidator prompt (step `consolidate_knowledge`) so browser-discovered patterns are captured in knowledge files.
|
|
75
|
+
|
|
76
|
+
</browser_verification>
|
|
@@ -38,7 +38,7 @@ When a milestone completes, this workflow:
|
|
|
38
38
|
|
|
39
39
|
**PHASE-SUMMARIES** consolidates all `*-SUMMARY.md` files from phase directories, organized by phase and plan, before artifacts are deleted.
|
|
40
40
|
|
|
41
|
-
**phases/** contains the phase directories themselves (with remaining files like `.patch`, `mockups/`) moved from `.planning/phases/`.
|
|
41
|
+
**phases/** contains the phase directories themselves (with remaining files like `.patch`, `mockups/`, `screenshots/`) moved from `.planning/phases/`.
|
|
42
42
|
|
|
43
43
|
**REQUIREMENTS archive** contains:
|
|
44
44
|
- All v1 requirements marked complete with outcomes
|
|
@@ -80,7 +80,7 @@ For each flat file like `milestones/v0.1-ROADMAP.md`:
|
|
|
80
80
|
3. `git mv` the file, stripping the version prefix from the filename:
|
|
81
81
|
`git mv .planning/milestones/v0.1-ROADMAP.md .planning/milestones/v0.1/ROADMAP.md`
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
New milestones use slug-based directories (e.g., `milestones/mvp/`, `milestones/push-notifications/`). Old v-prefixed directories from previous format are valid and handled.
|
|
84
84
|
|
|
85
85
|
Commit:
|
|
86
86
|
|
|
@@ -106,7 +106,7 @@ EOF
|
|
|
106
106
|
2. **Resolve slugs** — For each versioned dir, match to MILESTONES.md name mapping:
|
|
107
107
|
- Standard dirs: version matches directly (v0.1 → "MVP" → slug "mvp")
|
|
108
108
|
- Nested dirs: match sub-directory name to the milestone name in MILESTONES.md (v2.0.0/quests → "Quests Feature" → slug "quests-feature")
|
|
109
|
-
- Derive
|
|
109
|
+
- Derive slugs from names: lowercase, hyphenated (e.g., "Demo Release" → "demo-release"). Claude proposes, user confirms
|
|
110
110
|
|
|
111
111
|
3. **Present mapping** to user with AskUserQuestion:
|
|
112
112
|
|
|
@@ -236,6 +236,35 @@ EOF
|
|
|
236
236
|
```
|
|
237
237
|
</step>
|
|
238
238
|
|
|
239
|
+
<step name="fix_phase_dirs">
|
|
240
|
+
**Only if Phase Directory Naming failed or warned.**
|
|
241
|
+
|
|
242
|
+
1. Create any missing phase directories:
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
ms-tools create-phase-dirs
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
2. For non-canonical directories (reported as FAIL), rename using `git mv`:
|
|
249
|
+
|
|
250
|
+
Parse each non-canonical suggestion from the doctor check output (format: `{old} → git mv .planning/phases/{old} .planning/phases/{canonical}`) and execute the `git mv`.
|
|
251
|
+
|
|
252
|
+
Commit:
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
git add .planning/phases/
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
git commit -m "$(cat <<'EOF'
|
|
260
|
+
chore(doctor): fix phase directory naming
|
|
261
|
+
|
|
262
|
+
Created missing and renamed non-canonical phase directories.
|
|
263
|
+
EOF
|
|
264
|
+
)"
|
|
265
|
+
```
|
|
266
|
+
</step>
|
|
267
|
+
|
|
239
268
|
<step name="fix_knowledge">
|
|
240
269
|
**Only if Knowledge Files failed.**
|
|
241
270
|
|
|
@@ -253,7 +282,7 @@ If `SUMMARIES > 0`: **artifact mode**. If `SUMMARIES == 0`: **source code mode**
|
|
|
253
282
|
|
|
254
283
|
### 2. Spawn subagent
|
|
255
284
|
|
|
256
|
-
Spawn a `general-purpose` subagent (Task tool) with the following structured prompt. Inject detected mode, subsystem list from config.json, and
|
|
285
|
+
Spawn a `general-purpose` subagent (Task tool) with the following structured prompt. Inject detected mode, subsystem list from config.json, and detection results (SUMMARIES, HAS_CODEBASE_DOCS, HAS_PROJECT) into the prompt.
|
|
257
286
|
|
|
258
287
|
---
|
|
259
288
|
|
|
@@ -291,6 +291,38 @@ If the verifier's return includes "Items Not Verified Programmatically" (uncerta
|
|
|
291
291
|
Read `~/.claude/mindsystem/references/routing/gap-closure-routing.md` and follow its triage instructions to present gap summary and route to the appropriate primitive based on scope analysis.
|
|
292
292
|
</step>
|
|
293
293
|
|
|
294
|
+
<step name="browser_verification">
|
|
295
|
+
Run browser verification prerequisites check:
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
ms-tools browser-check
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**If exit 0 (READY):**
|
|
302
|
+
|
|
303
|
+
Read `~/.claude/mindsystem/references/browser-verification.md` and follow its Auth Flow and Spawn sections.
|
|
304
|
+
|
|
305
|
+
After verifier returns, if fixes were made:
|
|
306
|
+
- Report: "Browser verification: {N} issues found and fixed"
|
|
307
|
+
- Include report summary in consolidator prompt (step `consolidate_knowledge`)
|
|
308
|
+
|
|
309
|
+
**If exit 1 (MISSING_DEPS):**
|
|
310
|
+
|
|
311
|
+
Parse output for missing items. Use AskUserQuestion:
|
|
312
|
+
- header: "Browser verification"
|
|
313
|
+
- question: "Browser verification prerequisites are missing. How to proceed?"
|
|
314
|
+
- options:
|
|
315
|
+
- "Install missing dependencies" — follow install instructions from output
|
|
316
|
+
- "Skip browser verification" — proceed to code_review
|
|
317
|
+
|
|
318
|
+
If user installs: re-run `ms-tools browser-check`.
|
|
319
|
+
If user skips: proceed to code_review.
|
|
320
|
+
|
|
321
|
+
**If exit 2 (SKIP):**
|
|
322
|
+
|
|
323
|
+
Proceed silently to code_review.
|
|
324
|
+
</step>
|
|
325
|
+
|
|
294
326
|
<step name="code_review">
|
|
295
327
|
Read code review agent name from config:
|
|
296
328
|
|
|
@@ -311,7 +343,7 @@ Use CODE_REVIEW value directly as agent name.
|
|
|
311
343
|
1. **Gather changed files:**
|
|
312
344
|
```bash
|
|
313
345
|
# Get all files changed in this phase's commits
|
|
314
|
-
PHASE_COMMITS=$(
|
|
346
|
+
PHASE_COMMITS=$(ms-tools find-phase-commits ${PHASE_NUMBER})
|
|
315
347
|
CHANGED_FILES=$(git diff --name-only $(echo "$PHASE_COMMITS" | tail -1)^..HEAD | grep -E '\.(dart|ts|tsx|js|jsx|swift|kt|py|go|rs)$')
|
|
316
348
|
```
|
|
317
349
|
|
|
@@ -447,7 +479,7 @@ Check what changed during this phase:
|
|
|
447
479
|
|
|
448
480
|
```bash
|
|
449
481
|
# Get all source changes from this phase's commits
|
|
450
|
-
PHASE_COMMITS=$(
|
|
482
|
+
PHASE_COMMITS=$(ms-tools find-phase-commits {phase_number})
|
|
451
483
|
if [ -n "$PHASE_COMMITS" ]; then
|
|
452
484
|
FIRST=$(echo "$PHASE_COMMITS" | tail -1)
|
|
453
485
|
git diff --name-only ${FIRST}^..HEAD 2>/dev/null | grep -v "^\.planning/"
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mindsystem-cc",
|
|
3
|
-
"version": "4.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "4.2.0",
|
|
4
|
+
"description": "The engineer's meta-prompting system for Claude Code.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"mindsystem-cc": "bin/install.js"
|
|
7
7
|
},
|
package/scripts/ms-tools.py
CHANGED
|
@@ -109,13 +109,63 @@ def normalize_phase(phase_str: str) -> str:
|
|
|
109
109
|
|
|
110
110
|
|
|
111
111
|
def find_phase_dir(planning: Path, phase: str) -> Path | None:
|
|
112
|
-
"""Find the phase directory matching a normalized phase number.
|
|
112
|
+
"""Find the phase directory matching a normalized phase number.
|
|
113
|
+
|
|
114
|
+
Tries three patterns in priority order:
|
|
115
|
+
1. Canonical padded: {phase}-* (e.g., 05-*)
|
|
116
|
+
2. Unpadded variant: {raw}-* (e.g., 5-*) — only when raw != phase
|
|
117
|
+
3. Bare directory: {phase}/ or {raw}/ — only directories
|
|
118
|
+
"""
|
|
113
119
|
phases_dir = planning / "phases"
|
|
114
120
|
if not phases_dir.is_dir():
|
|
115
121
|
return None
|
|
122
|
+
|
|
123
|
+
# Derive raw (unpadded) form: "05" -> "5", "02.1" -> "2.1"
|
|
124
|
+
raw_match = re.match(r"^0*(\d.*)", phase)
|
|
125
|
+
raw = raw_match.group(1) if raw_match else phase
|
|
126
|
+
|
|
127
|
+
# Tier 1: canonical padded glob
|
|
116
128
|
matches = sorted(phases_dir.glob(f"{phase}-*"))
|
|
117
129
|
dirs = [m for m in matches if m.is_dir()]
|
|
118
|
-
|
|
130
|
+
if dirs:
|
|
131
|
+
return dirs[0]
|
|
132
|
+
|
|
133
|
+
# Tier 2: unpadded variant glob (skip when raw == phase)
|
|
134
|
+
if raw != phase:
|
|
135
|
+
matches = sorted(phases_dir.glob(f"{raw}-*"))
|
|
136
|
+
dirs = [m for m in matches if m.is_dir()]
|
|
137
|
+
if dirs:
|
|
138
|
+
return dirs[0]
|
|
139
|
+
|
|
140
|
+
# Tier 3: bare directory
|
|
141
|
+
bare_padded = phases_dir / phase
|
|
142
|
+
if bare_padded.is_dir():
|
|
143
|
+
return bare_padded
|
|
144
|
+
if raw != phase:
|
|
145
|
+
bare_raw = phases_dir / raw
|
|
146
|
+
if bare_raw.is_dir():
|
|
147
|
+
return bare_raw
|
|
148
|
+
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def parse_roadmap_phases(roadmap_path: Path) -> list[tuple[str, str]]:
|
|
153
|
+
"""Parse phase headers from ROADMAP.md.
|
|
154
|
+
|
|
155
|
+
Returns list of (phase_number, phase_name) tuples.
|
|
156
|
+
Strips markers like (INSERTED), (Generated) from names.
|
|
157
|
+
"""
|
|
158
|
+
if not roadmap_path.is_file():
|
|
159
|
+
return []
|
|
160
|
+
text = roadmap_path.read_text(encoding="utf-8")
|
|
161
|
+
results: list[tuple[str, str]] = []
|
|
162
|
+
for line in text.splitlines():
|
|
163
|
+
m = re.match(r"^###\s+Phase\s+(\d+(?:\.\d+)?)\s*:\s*(.+)$", line)
|
|
164
|
+
if m:
|
|
165
|
+
num = m.group(1)
|
|
166
|
+
name = re.sub(r"\s*\([A-Z][A-Za-z]*\)\s*$", "", m.group(2)).strip()
|
|
167
|
+
results.append((num, name))
|
|
168
|
+
return results
|
|
119
169
|
|
|
120
170
|
|
|
121
171
|
def run_git(*args: str) -> str:
|
|
@@ -297,6 +347,80 @@ def build_exclude_pathspecs() -> list[str]:
|
|
|
297
347
|
return [f":!{p}" for p in PATCH_EXCLUSIONS]
|
|
298
348
|
|
|
299
349
|
|
|
350
|
+
# -------------------------------------------------------------------
|
|
351
|
+
# Helper: find_phase_commit_hashes
|
|
352
|
+
# -------------------------------------------------------------------
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def find_phase_commit_hashes(phase_input: str, suffix: str = "") -> list[str]:
|
|
356
|
+
"""Find commit hashes matching a phase's commit convention.
|
|
357
|
+
|
|
358
|
+
Contract:
|
|
359
|
+
Args: phase_input (str), suffix (str, optional)
|
|
360
|
+
Output: list of commit hash strings (newest first)
|
|
361
|
+
Side effects: none (reads git log only)
|
|
362
|
+
"""
|
|
363
|
+
padded_phase = normalize_phase(phase_input)
|
|
364
|
+
# Strip leading zeros from integer part, preserve decimal: "02.1" -> "2.1"
|
|
365
|
+
m = re.match(r"^(\d+)(.*)", padded_phase)
|
|
366
|
+
raw_phase = str(int(m.group(1))) + m.group(2) if m else padded_phase
|
|
367
|
+
|
|
368
|
+
# Build alternation pattern matching both padded and raw forms
|
|
369
|
+
if padded_phase == raw_phase:
|
|
370
|
+
phase_alt = re.escape(raw_phase)
|
|
371
|
+
else:
|
|
372
|
+
phase_alt = f"(?:0*{re.escape(raw_phase)})"
|
|
373
|
+
|
|
374
|
+
# Build commit pattern based on suffix
|
|
375
|
+
if suffix:
|
|
376
|
+
if suffix == "uat-fixes":
|
|
377
|
+
commit_pattern = f"\\({phase_alt}-uat\\):"
|
|
378
|
+
else:
|
|
379
|
+
commit_pattern = f"\\({phase_alt}-{re.escape(suffix)}\\):"
|
|
380
|
+
else:
|
|
381
|
+
commit_pattern = f"\\({phase_alt}-"
|
|
382
|
+
|
|
383
|
+
# Find matching commits
|
|
384
|
+
try:
|
|
385
|
+
log_output = run_git("log", "--oneline")
|
|
386
|
+
except subprocess.CalledProcessError:
|
|
387
|
+
raise
|
|
388
|
+
|
|
389
|
+
hashes = []
|
|
390
|
+
for line in log_output.splitlines():
|
|
391
|
+
if re.search(commit_pattern, line):
|
|
392
|
+
hashes.append(line.split()[0])
|
|
393
|
+
return hashes
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
# -------------------------------------------------------------------
|
|
397
|
+
# Subcommand: find-phase-commits
|
|
398
|
+
# -------------------------------------------------------------------
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def cmd_find_phase_commits(args: argparse.Namespace) -> None:
|
|
402
|
+
"""Print commit hashes matching a phase's commit convention.
|
|
403
|
+
|
|
404
|
+
Contract:
|
|
405
|
+
Args: phase (str), --suffix (str, optional)
|
|
406
|
+
Output: text — one commit hash per line (newest first), empty if no matches
|
|
407
|
+
Exit codes: 0 = success (including zero matches), 1 = git error
|
|
408
|
+
Side effects: none
|
|
409
|
+
"""
|
|
410
|
+
git_root = find_git_root()
|
|
411
|
+
import os
|
|
412
|
+
os.chdir(git_root)
|
|
413
|
+
|
|
414
|
+
try:
|
|
415
|
+
hashes = find_phase_commit_hashes(args.phase, args.suffix)
|
|
416
|
+
except subprocess.CalledProcessError:
|
|
417
|
+
print("Error: Failed to read git log", file=sys.stderr)
|
|
418
|
+
sys.exit(1)
|
|
419
|
+
|
|
420
|
+
for h in hashes:
|
|
421
|
+
print(h)
|
|
422
|
+
|
|
423
|
+
|
|
300
424
|
# ===================================================================
|
|
301
425
|
# Subcommand: update-state
|
|
302
426
|
# ===================================================================
|
|
@@ -475,6 +599,166 @@ def cmd_validate_execution_order(args: argparse.Namespace) -> None:
|
|
|
475
599
|
print(f"PASS: {len(disk_plans)} plans across {wave_count} waves")
|
|
476
600
|
|
|
477
601
|
|
|
602
|
+
# ===================================================================
|
|
603
|
+
# Subcommand: browser-check
|
|
604
|
+
# ===================================================================
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
def _get_claude_config_dir() -> Path:
|
|
608
|
+
"""Cross-platform Claude Code config directory."""
|
|
609
|
+
if sys.platform == "win32":
|
|
610
|
+
appdata = os.environ.get("APPDATA")
|
|
611
|
+
if appdata:
|
|
612
|
+
return Path(appdata) / "Claude"
|
|
613
|
+
return Path.home() / "AppData" / "Roaming" / "Claude"
|
|
614
|
+
return Path.home() / ".claude"
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
WEB_FRAMEWORK_DEPS = {
|
|
618
|
+
"react", "react-dom", "vue", "next", "nuxt", "@angular/core",
|
|
619
|
+
"svelte", "@sveltejs/kit", "solid-js", "astro", "@remix-run/react",
|
|
620
|
+
"gatsby", "preact",
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
WEB_CONFIG_FILES = [
|
|
624
|
+
"next.config.*", "nuxt.config.*", "vite.config.*", "angular.json",
|
|
625
|
+
"svelte.config.*", "astro.config.*", "remix.config.*",
|
|
626
|
+
]
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
def _detect_web_project(git_root: Path) -> tuple[bool, str]:
|
|
630
|
+
"""Detect if project is a web project. Returns (is_web, signal_description)."""
|
|
631
|
+
# Signal 1: package.json deps
|
|
632
|
+
pkg = git_root / "package.json"
|
|
633
|
+
if pkg.is_file():
|
|
634
|
+
try:
|
|
635
|
+
data = json.loads(pkg.read_text(encoding="utf-8"))
|
|
636
|
+
all_deps = {**data.get("dependencies", {}), **data.get("devDependencies", {})}
|
|
637
|
+
found = WEB_FRAMEWORK_DEPS & set(all_deps.keys())
|
|
638
|
+
if found:
|
|
639
|
+
return True, f"{', '.join(sorted(found))} in package.json"
|
|
640
|
+
except (json.JSONDecodeError, OSError):
|
|
641
|
+
pass
|
|
642
|
+
|
|
643
|
+
# Signal 2: Framework config files (glob at root)
|
|
644
|
+
for pattern in WEB_CONFIG_FILES:
|
|
645
|
+
if list(git_root.glob(pattern)):
|
|
646
|
+
return True, f"{pattern} found"
|
|
647
|
+
|
|
648
|
+
# Signal 3: One level deep package.json (monorepo)
|
|
649
|
+
for child_pkg in git_root.glob("*/package.json"):
|
|
650
|
+
if "node_modules" in str(child_pkg):
|
|
651
|
+
continue
|
|
652
|
+
try:
|
|
653
|
+
data = json.loads(child_pkg.read_text(encoding="utf-8"))
|
|
654
|
+
all_deps = {**data.get("dependencies", {}), **data.get("devDependencies", {})}
|
|
655
|
+
found = WEB_FRAMEWORK_DEPS & set(all_deps.keys())
|
|
656
|
+
if found:
|
|
657
|
+
return True, f"{', '.join(sorted(found))} in {child_pkg.relative_to(git_root)}"
|
|
658
|
+
except (json.JSONDecodeError, OSError):
|
|
659
|
+
pass
|
|
660
|
+
|
|
661
|
+
# Signal 4: PROJECT.md tech stack mentions
|
|
662
|
+
project_md = git_root / ".planning" / "PROJECT.md"
|
|
663
|
+
if project_md.is_file():
|
|
664
|
+
try:
|
|
665
|
+
content = project_md.read_text(encoding="utf-8").lower()
|
|
666
|
+
web_keywords = ["react", "vue", "next.js", "nextjs", "nuxt", "angular", "svelte", "sveltekit", "astro", "remix"]
|
|
667
|
+
for kw in web_keywords:
|
|
668
|
+
if kw in content:
|
|
669
|
+
return True, f"'{kw}' mentioned in PROJECT.md"
|
|
670
|
+
except OSError:
|
|
671
|
+
pass
|
|
672
|
+
|
|
673
|
+
return False, "no web framework in package.json, no framework config files, no web stack in PROJECT.md"
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
def _check_skill_installed(skill_name: str) -> tuple[bool, str]:
|
|
677
|
+
"""Check if a Claude Code skill is installed. Cross-platform."""
|
|
678
|
+
config_dir = _get_claude_config_dir()
|
|
679
|
+
locations = [
|
|
680
|
+
("user", config_dir / "skills" / skill_name / "SKILL.md"),
|
|
681
|
+
("project", Path.cwd() / ".claude" / "skills" / skill_name / "SKILL.md"),
|
|
682
|
+
]
|
|
683
|
+
for label, path in locations:
|
|
684
|
+
if path.is_file():
|
|
685
|
+
return True, f"{label}: {path}"
|
|
686
|
+
return False, "not found in ~/.claude/skills/ or .claude/skills/"
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
def cmd_browser_check(args: argparse.Namespace) -> None:
|
|
690
|
+
"""Check browser verification prerequisites.
|
|
691
|
+
|
|
692
|
+
Contract:
|
|
693
|
+
Output: text — section-based status report
|
|
694
|
+
Exit codes: 0 = READY, 1 = MISSING_DEPS, 2 = SKIP
|
|
695
|
+
Side effects: read-only
|
|
696
|
+
"""
|
|
697
|
+
git_root = find_git_root()
|
|
698
|
+
planning = git_root / ".planning"
|
|
699
|
+
|
|
700
|
+
# 1. Config check
|
|
701
|
+
print("=== Browser Verification Config ===")
|
|
702
|
+
enabled = True # default
|
|
703
|
+
config_path = planning / "config.json" if planning.is_dir() else None
|
|
704
|
+
if config_path and config_path.is_file():
|
|
705
|
+
try:
|
|
706
|
+
config = json.loads(config_path.read_text(encoding="utf-8"))
|
|
707
|
+
bv = config.get("browser_verification", {})
|
|
708
|
+
if isinstance(bv, dict):
|
|
709
|
+
enabled = bv.get("enabled", True)
|
|
710
|
+
# else: malformed, default to enabled
|
|
711
|
+
except (json.JSONDecodeError, OSError):
|
|
712
|
+
pass # malformed config, default to enabled
|
|
713
|
+
print(f"Status: {'enabled' if enabled else 'disabled'}")
|
|
714
|
+
|
|
715
|
+
if not enabled:
|
|
716
|
+
print("\nResult: SKIP (disabled in config)")
|
|
717
|
+
sys.exit(2)
|
|
718
|
+
|
|
719
|
+
# 2. Web project check
|
|
720
|
+
print("\n=== Web Project Detection ===")
|
|
721
|
+
is_web, signal = _detect_web_project(git_root)
|
|
722
|
+
print(f"Web project: {is_web}")
|
|
723
|
+
print(f"Signal: {signal}")
|
|
724
|
+
|
|
725
|
+
if not is_web:
|
|
726
|
+
print("\nResult: SKIP (not a web project)")
|
|
727
|
+
sys.exit(2)
|
|
728
|
+
|
|
729
|
+
# 3. CLI check
|
|
730
|
+
print("\n=== Browser CLI ===")
|
|
731
|
+
cli_path = shutil.which("agent-browser")
|
|
732
|
+
if cli_path:
|
|
733
|
+
print(f"agent-browser: {cli_path}")
|
|
734
|
+
else:
|
|
735
|
+
print("agent-browser: not found")
|
|
736
|
+
print("Install: npm install -g agent-browser")
|
|
737
|
+
|
|
738
|
+
# 4. Skill check
|
|
739
|
+
print("\n=== Browser Skill ===")
|
|
740
|
+
skill_ok, skill_detail = _check_skill_installed("agent-browser")
|
|
741
|
+
if skill_ok:
|
|
742
|
+
print(f"agent-browser skill: {skill_detail}")
|
|
743
|
+
else:
|
|
744
|
+
print(f"agent-browser skill: {skill_detail}")
|
|
745
|
+
print("Install: add agent-browser skill to ~/.claude/skills/")
|
|
746
|
+
|
|
747
|
+
# Result
|
|
748
|
+
missing: list[str] = []
|
|
749
|
+
if not cli_path:
|
|
750
|
+
missing.append("agent-browser CLI")
|
|
751
|
+
if not skill_ok:
|
|
752
|
+
missing.append("agent-browser skill")
|
|
753
|
+
|
|
754
|
+
if missing:
|
|
755
|
+
print(f"\nResult: MISSING_DEPS ({', '.join(missing)})")
|
|
756
|
+
sys.exit(1)
|
|
757
|
+
|
|
758
|
+
print("\nResult: READY")
|
|
759
|
+
sys.exit(0)
|
|
760
|
+
|
|
761
|
+
|
|
478
762
|
# ===================================================================
|
|
479
763
|
# Subcommand: doctor-scan
|
|
480
764
|
# ===================================================================
|
|
@@ -530,12 +814,6 @@ def cmd_doctor_scan(args: argparse.Namespace) -> None:
|
|
|
530
814
|
else:
|
|
531
815
|
skip_count += 1
|
|
532
816
|
|
|
533
|
-
def format_phase_prefix(phase: str) -> str:
|
|
534
|
-
if "." in phase:
|
|
535
|
-
int_part, dec_part = phase.split(".", 1)
|
|
536
|
-
return f"{int(int_part):02d}.{dec_part}"
|
|
537
|
-
return f"{int(phase):02d}"
|
|
538
|
-
|
|
539
817
|
def parse_phase_numbers(line: str) -> list[str]:
|
|
540
818
|
"""Parse phase numbers from a 'Phases completed' line."""
|
|
541
819
|
range_match = re.search(r"(\d+)-(\d+)", line)
|
|
@@ -629,7 +907,7 @@ def cmd_doctor_scan(args: argparse.Namespace) -> None:
|
|
|
629
907
|
orphans: list[str] = []
|
|
630
908
|
for line in phase_lines:
|
|
631
909
|
for phase_num in parse_phase_numbers(line):
|
|
632
|
-
prefix =
|
|
910
|
+
prefix = normalize_phase(phase_num)
|
|
633
911
|
if phases_dir.is_dir():
|
|
634
912
|
for d in phases_dir.glob(f"{prefix}-*/"):
|
|
635
913
|
if d.is_dir():
|
|
@@ -728,7 +1006,7 @@ def cmd_doctor_scan(args: argparse.Namespace) -> None:
|
|
|
728
1006
|
leftovers: list[str] = []
|
|
729
1007
|
for line in phase_lines:
|
|
730
1008
|
for phase_num in parse_phase_numbers(line):
|
|
731
|
-
prefix =
|
|
1009
|
+
prefix = normalize_phase(phase_num)
|
|
732
1010
|
if phases_dir.is_dir():
|
|
733
1011
|
for d in phases_dir.glob(f"{prefix}-*/"):
|
|
734
1012
|
if d.is_dir():
|
|
@@ -877,6 +1155,84 @@ def cmd_doctor_scan(args: argparse.Namespace) -> None:
|
|
|
877
1155
|
record("WARN", "Research API Keys")
|
|
878
1156
|
print()
|
|
879
1157
|
|
|
1158
|
+
# ---- CHECK 10: Phase Directory Naming ----
|
|
1159
|
+
print("=== Phase Directory Naming ===")
|
|
1160
|
+
roadmap_path = planning / "ROADMAP.md"
|
|
1161
|
+
roadmap_phases = parse_roadmap_phases(roadmap_path)
|
|
1162
|
+
if not roadmap_phases:
|
|
1163
|
+
print("Status: SKIP")
|
|
1164
|
+
print("No ROADMAP.md or no phases found")
|
|
1165
|
+
record("SKIP", "Phase Directory Naming")
|
|
1166
|
+
else:
|
|
1167
|
+
non_canonical: list[str] = []
|
|
1168
|
+
missing_dirs: list[str] = []
|
|
1169
|
+
for num, name in roadmap_phases:
|
|
1170
|
+
padded = normalize_phase(num)
|
|
1171
|
+
slug = slugify(name)
|
|
1172
|
+
canonical = f"{padded}-{slug}"
|
|
1173
|
+
canonical_path = phases_dir / canonical if phases_dir.is_dir() else None
|
|
1174
|
+
if canonical_path and canonical_path.is_dir():
|
|
1175
|
+
continue
|
|
1176
|
+
found = find_phase_dir(planning, padded)
|
|
1177
|
+
if found is not None:
|
|
1178
|
+
non_canonical.append(f" {found.name} → git mv .planning/phases/{found.name} .planning/phases/{canonical}")
|
|
1179
|
+
else:
|
|
1180
|
+
missing_dirs.append(f" {canonical} (missing, run: ms-tools create-phase-dirs)")
|
|
1181
|
+
if non_canonical:
|
|
1182
|
+
print("Status: FAIL")
|
|
1183
|
+
print(f"Found {len(non_canonical)} non-canonical phase directory name(s):")
|
|
1184
|
+
for line in non_canonical:
|
|
1185
|
+
print(line)
|
|
1186
|
+
record("FAIL", "Phase Directory Naming")
|
|
1187
|
+
elif missing_dirs:
|
|
1188
|
+
print("Status: WARN")
|
|
1189
|
+
print(f"Found {len(missing_dirs)} missing phase directory(ies):")
|
|
1190
|
+
for line in missing_dirs:
|
|
1191
|
+
print(line)
|
|
1192
|
+
record("WARN", "Phase Directory Naming")
|
|
1193
|
+
else:
|
|
1194
|
+
print("Status: PASS")
|
|
1195
|
+
print("All phase directories use canonical naming")
|
|
1196
|
+
record("PASS", "Phase Directory Naming")
|
|
1197
|
+
print()
|
|
1198
|
+
|
|
1199
|
+
# ---- CHECK 11: Browser Verification ----
|
|
1200
|
+
print("=== Browser Verification ===")
|
|
1201
|
+
bv_enabled = True
|
|
1202
|
+
bv_config = config.get("browser_verification", {})
|
|
1203
|
+
if isinstance(bv_config, dict):
|
|
1204
|
+
bv_enabled = bv_config.get("enabled", True)
|
|
1205
|
+
|
|
1206
|
+
if not bv_enabled:
|
|
1207
|
+
print("Status: SKIP")
|
|
1208
|
+
print("Disabled in config.json")
|
|
1209
|
+
record("SKIP", "Browser Verification")
|
|
1210
|
+
else:
|
|
1211
|
+
is_web, web_signal = _detect_web_project(git_root)
|
|
1212
|
+
if not is_web:
|
|
1213
|
+
print("Status: SKIP")
|
|
1214
|
+
print(f"Not a web project ({web_signal})")
|
|
1215
|
+
record("SKIP", "Browser Verification")
|
|
1216
|
+
else:
|
|
1217
|
+
bv_missing: list[str] = []
|
|
1218
|
+
cli_path = shutil.which("agent-browser")
|
|
1219
|
+
if not cli_path:
|
|
1220
|
+
bv_missing.append("agent-browser CLI (npm install -g agent-browser)")
|
|
1221
|
+
skill_ok, _ = _check_skill_installed("agent-browser")
|
|
1222
|
+
if not skill_ok:
|
|
1223
|
+
bv_missing.append("agent-browser skill")
|
|
1224
|
+
if bv_missing:
|
|
1225
|
+
print("Status: WARN")
|
|
1226
|
+
print(f"Web project detected ({web_signal}) but missing:")
|
|
1227
|
+
for m in bv_missing:
|
|
1228
|
+
print(f" {m}")
|
|
1229
|
+
record("WARN", "Browser Verification")
|
|
1230
|
+
else:
|
|
1231
|
+
print("Status: PASS")
|
|
1232
|
+
print(f"Web project ({web_signal}), CLI and skill installed")
|
|
1233
|
+
record("PASS", "Browser Verification")
|
|
1234
|
+
print()
|
|
1235
|
+
|
|
880
1236
|
# ---- SUMMARY ----
|
|
881
1237
|
total = pass_count + warn_count + fail_count + skip_count
|
|
882
1238
|
print("=== Summary ===")
|
|
@@ -890,6 +1246,44 @@ def cmd_doctor_scan(args: argparse.Namespace) -> None:
|
|
|
890
1246
|
print("All checks passed")
|
|
891
1247
|
|
|
892
1248
|
|
|
1249
|
+
# ===================================================================
|
|
1250
|
+
# Subcommand: create-phase-dirs
|
|
1251
|
+
# ===================================================================
|
|
1252
|
+
|
|
1253
|
+
|
|
1254
|
+
def cmd_create_phase_dirs(args: argparse.Namespace) -> None:
|
|
1255
|
+
"""Create phase directories from ROADMAP.md headers.
|
|
1256
|
+
|
|
1257
|
+
Reads ### Phase N: Name headers, creates .planning/phases/{padded}-{slug}/
|
|
1258
|
+
for each. Skips phases that already have a directory (found via resilient
|
|
1259
|
+
find_phase_dir).
|
|
1260
|
+
"""
|
|
1261
|
+
planning = find_planning_dir()
|
|
1262
|
+
roadmap_path = planning / "ROADMAP.md"
|
|
1263
|
+
phases = parse_roadmap_phases(roadmap_path)
|
|
1264
|
+
if not phases:
|
|
1265
|
+
print("Error: No phases found in ROADMAP.md (or file missing)", file=sys.stderr)
|
|
1266
|
+
sys.exit(1)
|
|
1267
|
+
|
|
1268
|
+
phases_dir = planning / "phases"
|
|
1269
|
+
phases_dir.mkdir(parents=True, exist_ok=True)
|
|
1270
|
+
|
|
1271
|
+
created = 0
|
|
1272
|
+
skipped = 0
|
|
1273
|
+
for num, name in phases:
|
|
1274
|
+
padded = normalize_phase(num)
|
|
1275
|
+
if find_phase_dir(planning, padded) is not None:
|
|
1276
|
+
skipped += 1
|
|
1277
|
+
continue
|
|
1278
|
+
slug = slugify(name)
|
|
1279
|
+
dir_name = f"{padded}-{slug}"
|
|
1280
|
+
(phases_dir / dir_name).mkdir(parents=True, exist_ok=True)
|
|
1281
|
+
print(f"Created: {dir_name}")
|
|
1282
|
+
created += 1
|
|
1283
|
+
|
|
1284
|
+
print(f"\n{created} created, {skipped} skipped (already exist)")
|
|
1285
|
+
|
|
1286
|
+
|
|
893
1287
|
# ===================================================================
|
|
894
1288
|
# Subcommand: gather-milestone-stats
|
|
895
1289
|
# ===================================================================
|
|
@@ -982,28 +1376,32 @@ def cmd_gather_milestone_stats(args: argparse.Namespace) -> None:
|
|
|
982
1376
|
|
|
983
1377
|
all_commits: list[str] = []
|
|
984
1378
|
|
|
985
|
-
# Integer phases
|
|
1379
|
+
# Integer phases — try both padded and raw formats
|
|
986
1380
|
for i in range(start, end + 1):
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1381
|
+
padded = normalize_phase(str(i))
|
|
1382
|
+
raw = str(i)
|
|
1383
|
+
for p in ([padded, raw] if padded != raw else [padded]):
|
|
1384
|
+
try:
|
|
1385
|
+
out = run_git("log", "--all", "--format=%H %ai %s", f"--grep=({p}-")
|
|
1386
|
+
if out:
|
|
1387
|
+
all_commits.extend(out.splitlines())
|
|
1388
|
+
except subprocess.CalledProcessError:
|
|
1389
|
+
pass
|
|
994
1390
|
|
|
995
|
-
# Decimal phases
|
|
1391
|
+
# Decimal phases — try both directory-derived and padded forms
|
|
996
1392
|
for d in sorted(phases_dir.iterdir()):
|
|
997
1393
|
if not d.is_dir():
|
|
998
1394
|
continue
|
|
999
1395
|
phase_num = d.name.split("-", 1)[0]
|
|
1000
1396
|
if "." in phase_num and in_range(phase_num, start, end):
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1397
|
+
padded = normalize_phase(phase_num)
|
|
1398
|
+
for p in ([padded, phase_num] if padded != phase_num else [padded]):
|
|
1399
|
+
try:
|
|
1400
|
+
out = run_git("log", "--all", "--format=%H %ai %s", f"--grep=({p}-")
|
|
1401
|
+
if out:
|
|
1402
|
+
all_commits.extend(out.splitlines())
|
|
1403
|
+
except subprocess.CalledProcessError:
|
|
1404
|
+
pass
|
|
1007
1405
|
|
|
1008
1406
|
# Deduplicate and sort by date
|
|
1009
1407
|
seen: set[str] = set()
|
|
@@ -1071,38 +1469,24 @@ def cmd_generate_phase_patch(args: argparse.Namespace) -> None:
|
|
|
1071
1469
|
import os
|
|
1072
1470
|
os.chdir(git_root)
|
|
1073
1471
|
|
|
1074
|
-
|
|
1075
|
-
if re.match(r"^\d$", phase_input):
|
|
1076
|
-
phase_number = f"{int(phase_input):02d}"
|
|
1077
|
-
else:
|
|
1078
|
-
phase_number = phase_input
|
|
1472
|
+
padded_phase = normalize_phase(phase_input)
|
|
1079
1473
|
|
|
1080
|
-
# Determine commit pattern
|
|
1081
1474
|
if suffix:
|
|
1082
1475
|
if suffix == "uat-fixes":
|
|
1083
|
-
|
|
1084
|
-
print(f"Generating UAT fixes patch for phase {phase_number}...")
|
|
1476
|
+
print(f"Generating UAT fixes patch for phase {padded_phase}...")
|
|
1085
1477
|
else:
|
|
1086
|
-
|
|
1087
|
-
print(f"Generating {suffix} patch for phase {phase_number}...")
|
|
1478
|
+
print(f"Generating {suffix} patch for phase {padded_phase}...")
|
|
1088
1479
|
else:
|
|
1089
|
-
|
|
1090
|
-
print(f"Generating patch for phase {phase_number}...")
|
|
1480
|
+
print(f"Generating patch for phase {padded_phase}...")
|
|
1091
1481
|
|
|
1092
|
-
# Find matching commits
|
|
1093
1482
|
try:
|
|
1094
|
-
|
|
1483
|
+
phase_commits = find_phase_commit_hashes(phase_input, suffix)
|
|
1095
1484
|
except subprocess.CalledProcessError:
|
|
1096
1485
|
print("Error: Failed to read git log", file=sys.stderr)
|
|
1097
1486
|
sys.exit(1)
|
|
1098
1487
|
|
|
1099
|
-
phase_commits = []
|
|
1100
|
-
for line in log_output.splitlines():
|
|
1101
|
-
if re.search(commit_pattern, line):
|
|
1102
|
-
phase_commits.append(line.split()[0])
|
|
1103
|
-
|
|
1104
1488
|
if not phase_commits:
|
|
1105
|
-
print(
|
|
1489
|
+
print("No commits found matching phase convention")
|
|
1106
1490
|
print("Patch skipped")
|
|
1107
1491
|
return
|
|
1108
1492
|
|
|
@@ -1120,7 +1504,7 @@ def cmd_generate_phase_patch(args: argparse.Namespace) -> None:
|
|
|
1120
1504
|
|
|
1121
1505
|
# Find output directory
|
|
1122
1506
|
phases_dir = Path(".planning/phases")
|
|
1123
|
-
phase_dir_matches = sorted(phases_dir.glob(f"{
|
|
1507
|
+
phase_dir_matches = sorted(phases_dir.glob(f"{padded_phase}-*")) if phases_dir.is_dir() else []
|
|
1124
1508
|
phase_dir = str(phase_dir_matches[0]) if phase_dir_matches else str(phases_dir)
|
|
1125
1509
|
|
|
1126
1510
|
Path(phase_dir).mkdir(parents=True, exist_ok=True)
|
|
@@ -1128,9 +1512,9 @@ def cmd_generate_phase_patch(args: argparse.Namespace) -> None:
|
|
|
1128
1512
|
|
|
1129
1513
|
# Determine output filename
|
|
1130
1514
|
if suffix:
|
|
1131
|
-
patch_file = f"{phase_dir}/{
|
|
1515
|
+
patch_file = f"{phase_dir}/{padded_phase}-{suffix}.patch"
|
|
1132
1516
|
else:
|
|
1133
|
-
patch_file = f"{phase_dir}/{
|
|
1517
|
+
patch_file = f"{phase_dir}/{padded_phase}-changes.patch"
|
|
1134
1518
|
|
|
1135
1519
|
# Generate diff
|
|
1136
1520
|
exclude_args = build_exclude_pathspecs()
|
|
@@ -2776,10 +3160,8 @@ def cmd_uat_init(args: argparse.Namespace) -> None:
|
|
|
2776
3160
|
|
|
2777
3161
|
phase_dir = find_phase_dir(planning, phase)
|
|
2778
3162
|
if phase_dir is None:
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
phase_dir = phases_dir / phase
|
|
2782
|
-
phase_dir.mkdir(parents=True, exist_ok=True)
|
|
3163
|
+
print(f"Error: Phase directory not found for {phase}. Run: ms-tools create-phase-dirs", file=sys.stderr)
|
|
3164
|
+
sys.exit(1)
|
|
2783
3165
|
|
|
2784
3166
|
phase_name = phase_dir.name
|
|
2785
3167
|
uat = UATFile.from_init_json(data, phase_name)
|
|
@@ -3234,10 +3616,18 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
3234
3616
|
p.add_argument("phase_dir", help="Phase directory path")
|
|
3235
3617
|
p.set_defaults(func=cmd_validate_execution_order)
|
|
3236
3618
|
|
|
3619
|
+
# --- browser-check ---
|
|
3620
|
+
p = subparsers.add_parser("browser-check", help="Check browser verification prerequisites")
|
|
3621
|
+
p.set_defaults(func=cmd_browser_check)
|
|
3622
|
+
|
|
3237
3623
|
# --- doctor-scan ---
|
|
3238
3624
|
p = subparsers.add_parser("doctor-scan", help="Diagnostic scan of .planning/ tree")
|
|
3239
3625
|
p.set_defaults(func=cmd_doctor_scan)
|
|
3240
3626
|
|
|
3627
|
+
# --- create-phase-dirs ---
|
|
3628
|
+
p = subparsers.add_parser("create-phase-dirs", help="Create phase directories from ROADMAP.md")
|
|
3629
|
+
p.set_defaults(func=cmd_create_phase_dirs)
|
|
3630
|
+
|
|
3241
3631
|
# --- gather-milestone-stats ---
|
|
3242
3632
|
p = subparsers.add_parser("gather-milestone-stats", help="Gather milestone readiness and git statistics")
|
|
3243
3633
|
p.add_argument("start_phase", type=int, help="Start phase number")
|
|
@@ -3250,6 +3640,12 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
3250
3640
|
p.add_argument("--suffix", default="", help="Filter commits and customize output filename")
|
|
3251
3641
|
p.set_defaults(func=cmd_generate_phase_patch)
|
|
3252
3642
|
|
|
3643
|
+
# --- find-phase-commits ---
|
|
3644
|
+
p = subparsers.add_parser("find-phase-commits", help="Find commit hashes matching phase convention")
|
|
3645
|
+
p.add_argument("phase", help="Phase number (e.g., 04 or 4)")
|
|
3646
|
+
p.add_argument("--suffix", default="", help="Filter by suffix (e.g., uat)")
|
|
3647
|
+
p.set_defaults(func=cmd_find_phase_commits)
|
|
3648
|
+
|
|
3253
3649
|
# --- generate-adhoc-patch ---
|
|
3254
3650
|
p = subparsers.add_parser("generate-adhoc-patch", help="Generate patch from an adhoc commit or range")
|
|
3255
3651
|
p.add_argument("commit", help="Start commit hash")
|