mindsystem-cc 4.4.0 → 4.4.1

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 CHANGED
@@ -2,21 +2,43 @@
2
2
 
3
3
  ![Mindsystem Bento Grid](assets/bento-image.webp)
4
4
 
5
- Amplifies every step of the development workflow you already follow — discovery, research, design, planning, execution, verification. Each one refined, parallelized, and compounded into persistent knowledge. Built for lean teams and solo builders who want to multiply their output without giving up control.
5
+ # Mindsystem
6
6
 
7
- ```bash
8
- npx mindsystem-cc
9
- ```
7
+ Amplifies every step of the development workflow you already follow.
10
8
 
11
9
  [![npm version](https://img.shields.io/npm/v/mindsystem-cc?style=flat-square&logo=npm&logoColor=white&color=CB3837)](https://www.npmjs.com/package/mindsystem-cc)
10
+ [![npm downloads](https://img.shields.io/npm/dm/mindsystem-cc?style=flat-square)](https://www.npmjs.com/package/mindsystem-cc)
12
11
  [![License](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](LICENSE)
12
+ [![Built for Claude Code](https://img.shields.io/badge/built_for-Claude_Code-D97706?style=flat-square)](https://docs.anthropic.com/en/docs/claude-code)
13
13
 
14
- [Why Mindsystem](#why-mindsystem) · [How it works](#how-it-works) · [Walkthrough](#end-to-end-walkthrough) · [Features](#features) · [Quick start](#quick-start) · [.planning](#the-planning-directory) · [Config](#configuration) · [Commands](#command-reference) · [Troubleshooting](#troubleshooting)
14
+ Discovery, research, design, planning, execution, verification each one refined, parallelized, and compounded into persistent knowledge. Built for lean teams and solo builders who want to multiply their output without giving up control.
15
+
16
+ [Quick start](#quick-start) · [Walkthrough](#end-to-end-walkthrough) · [Features](#features) · [Commands](#command-reference)
15
17
 
16
18
  </div>
17
19
 
18
20
  ---
19
21
 
22
+ ## Quick start
23
+
24
+ ```bash
25
+ npx mindsystem-cc
26
+ ```
27
+
28
+ Then `/ms:new-project` to initialize. See the [full walkthrough](#end-to-end-walkthrough) for the complete feature-building loop, or `/ms:progress` to pick up where you left off.
29
+
30
+ ---
31
+
32
+ ## What's new in v4.4
33
+
34
+ - **User journey browser verification** — end-to-end click/fill/submit testing instead of screenshot-only observation. Catches unreachable pages with no navigation path from the UI.
35
+ - **Single-plan mode (default)** — one plan per phase, eliminating multi-agent orchestration overhead with 1M context windows.
36
+ - **Full phase specification** for add-phase and insert-phase — both commands now derive goal, success criteria, and requirements mapping.
37
+
38
+ See [CHANGELOG.md](CHANGELOG.md) for the complete history.
39
+
40
+ ---
41
+
20
42
  ## Why Mindsystem
21
43
 
22
44
  Fully autonomous coding tools take a spec and run until a product emerges. That works for prototypes. Mindsystem takes the opposite approach — it follows the workflow a thorough engineer already uses, and amplifies each step:
@@ -87,7 +109,7 @@ Execution happens in fresh subagent contexts, so each plan gets up to 200k token
87
109
 
88
110
  ### Project setup
89
111
 
90
- Before building features, initialize the project once with `/ms:new-project`. For existing codebases, also run `/ms:map-codebase` (analyzes your stack, conventions, and architecture into 7 structured documents) and `/ms:doctor` (validates config and generates per-subsystem knowledge files from source code). See [Quick start](#quick-start) for the exact sequences.
112
+ Before building features, initialize the project once with `/ms:new-project`. For existing codebases, also run `/ms:map-codebase` (analyzes your stack, conventions, and architecture into 7 structured documents) and `/ms:doctor` (validates config and generates per-subsystem knowledge files from source code). See [Getting started](#getting-started) for the exact sequences.
91
113
 
92
114
  Everything below is the feature-building loop, identical for new and existing projects.
93
115
 
@@ -277,6 +299,10 @@ Requirements you want but haven't shipped yet are tracked in `PROJECT.md` with o
277
299
 
278
300
  `/ms:add-todo` with Linear-inspired metadata: priority (1-4), estimate (XS-XL), inferred subsystem. Todos live as flat files in `.planning/todos/`. Address them later via `/ms:adhoc`, which reads the problem description, executes the work, and moves the todo to `done/`.
279
301
 
302
+ ### Task tracker integration
303
+
304
+ Configure a Linear connection via `/ms:config` and pass ticket IDs directly to `/ms:adhoc` (e.g., `/ms:adhoc MIN-123`). The system auto-fetches the ticket's title and description as work context, tags commits with the ticket ID, and on completion posts a solution summary, attaches the commit, and marks the ticket done. Zero overhead when unconfigured — the integration is lazy-loaded only when a ticket ID is detected.
305
+
280
306
  ### Codebase mapping
281
307
 
282
308
  `/ms:map-codebase` spawns 4 parallel agents producing 7 structured documents: stack, architecture, conventions, testing, integrations, directory structure, and concerns. Use on brownfield projects so Mindsystem respects your existing patterns.
@@ -297,7 +323,7 @@ Requirements you want but haven't shipped yet are tracked in `PROJECT.md` with o
297
323
 
298
324
  ---
299
325
 
300
- ## Quick start
326
+ ## Getting started
301
327
 
302
328
  ### Project setup (one-time)
303
329
 
@@ -324,6 +350,7 @@ Once the project is set up, the workflow is the same for new and existing codeba
324
350
  ```
325
351
  /ms:new-milestone # Discover what to build next
326
352
  /ms:create-roadmap # Define requirements, map to phases
353
+ /ms:discuss-phase 1 # Gather context before planning
327
354
  /ms:plan-phase 1 # Create detailed plan
328
355
  /ms:execute-phase 1 # Run in fresh subagents
329
356
  /ms:verify-work 1 # Manual acceptance testing
@@ -498,7 +525,7 @@ Full docs live in `/ms:help`.
498
525
  | `/ms:execute-phase <number>` | Run all unexecuted plans in fresh subagents |
499
526
  | `/ms:verify-work [number]` | Batched manual UAT with inline fixes |
500
527
  | `/ms:debug [description]` | Structured debugging that survives `/clear` |
501
- | `/ms:adhoc <description>` | Knowledge-aware execution for discovered work |
528
+ | `/ms:adhoc <description>` | Knowledge-aware execution for discovered work or Linear ticket IDs |
502
529
  | `/ms:compound [input]` | Compound out-of-pipeline changes into knowledge files |
503
530
  | `/ms:add-phase <description>` | Append a new phase to the roadmap |
504
531
  | `/ms:insert-phase <after> <description>` | Insert urgent work between phases |
@@ -53,7 +53,7 @@ git remote -v 2>/dev/null || echo "NO_REMOTE"
53
53
  2. **Code reviewers** — adhoc: {value or "not set"}, phase: {value or "not set"}, milestone: {value or "not set"}
54
54
  3. **Gitignore** — {current .planning/ patterns or "no .planning/ patterns"}
55
55
  4. **Mockups** — open: {auto / ask / off}
56
- 5. **Browser verification** — {enabled / disabled}
56
+ 5. **Browser verification** — {enabled / disabled}, web project: {yes / no / auto-detect}
57
57
  6. **Plan mode** — {single plan (default) / multi-plan}
58
58
  7. **Task tracker** — {type + cli path, or "none"}
59
59
  ```
@@ -198,9 +198,30 @@ Map selection:
198
198
  Update config.json:
199
199
 
200
200
  ```bash
201
- ms-tools config-set browser_verification --json '{"enabled": true}' # or false
201
+ ms-tools config-set browser_verification.enabled --json 'true' # or false
202
202
  ```
203
203
 
204
+ **If enabled**, detect and confirm web project status:
205
+
206
+ ```bash
207
+ ms-tools detect-web
208
+ ```
209
+
210
+ Present the auto-detection result via AskUserQuestion:
211
+ - header: "Web project detection"
212
+ - question: "Is this a web project with a browser-renderable UI? Auto-detection returned: {detected result}"
213
+ - options:
214
+ - "Yes — this is a web project" (recommended if detected)
215
+ - "No — this is not a web project"
216
+ - "Auto-detect (don't persist)" — remove any existing override
217
+
218
+ Map selection:
219
+ - "Yes" → `ms-tools config-set browser_verification.web_project --json 'true'`
220
+ - "No" → `ms-tools config-set browser_verification.web_project --json 'false'`
221
+ - "Auto-detect" → `ms-tools config-delete browser_verification.web_project`
222
+
223
+ **If disabled**, skip the web_project question.
224
+
204
225
  </step>
205
226
 
206
227
  <step name="multi_plan">
@@ -145,7 +145,7 @@ If "Review each" → use AskUserQuestion for each failed check with its details
145
145
 
146
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.
147
147
 
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.
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. Research API Keys check provides platform-aware setup guidance (shell export on macOS/Linux, settings.json on Windows) and checks both environment variables and `~/.claude/settings.json` env section as fallback.
149
149
  </step>
150
150
 
151
151
  <step name="apply_fixes">
@@ -108,7 +108,7 @@ Initialize new project with brief and configuration.
108
108
  Usage: `/ms:new-project`
109
109
 
110
110
  **`/ms:config`**
111
- Configure Mindsystem preferences — code reviewers, mockup preferences, browser verification, plan mode, gitignore patterns, git remote.
111
+ Configure Mindsystem preferences — code reviewers, mockup preferences, browser verification, plan mode, gitignore patterns, git remote, task tracker.
112
112
 
113
113
  - Use when: you want to set up code review agents, mockup open behavior, manage which .planning/ artifacts are git-ignored, or push your repo to GitHub.
114
114
  - Edits `.planning/config.json` and `.gitignore`
@@ -366,11 +366,13 @@ Execute discovered work with knowledge-aware planning and execution.
366
366
  - Use when: you discover work mid-session that can be completed in a single context window without multi-phase orchestration.
367
367
  - Bridges the gap between `/ms:add-todo` (capture for later) and `/ms:insert-phase` (multi-plan coordination)
368
368
  - Knowledge-aware: loads relevant knowledge files before planning, consolidates learnings after execution
369
+ - Accepts Linear ticket IDs when task tracker is configured via `/ms:config` — auto-fetches context, tags commits, and finalizes the ticket on completion
369
370
  - Spawns Explore agents for codebase understanding, ms-adhoc-planner for plan creation, ms-executor for execution
370
371
  - Creates per-execution artifacts in `.planning/adhoc/{timestamp}-{slug}/`
371
372
  - Updates knowledge files via ms-consolidator after execution
372
373
 
373
374
  Usage: `/ms:adhoc Fix auth token not refreshing on 401`
375
+ Usage: `/ms:adhoc MIN-123` (Linear ticket — auto-fetches context, finalizes on completion)
374
376
  Usage: `/ms:adhoc Refactor API error handling to use consistent format`
375
377
 
376
378
  ### Utility Commands
@@ -276,7 +276,9 @@ Milestone name: $ARGUMENTS (optional — will emerge during discovery if not pro
276
276
 
277
277
  19. **Route to next step:**
278
278
 
279
- Default to `/ms:research-milestone` — domain research improves roadmap quality for most milestones. Only recommend `/ms:create-roadmap` directly when the domain is clearly familiar, no open questions surfaced, and there are no external APIs, new libraries, or unfamiliar patterns involved.
279
+ Determine primary suggestion:
280
+ - Default: `/ms:research-milestone` — domain research improves roadmap quality for most milestones
281
+ - Only `/ms:create-roadmap` when domain is clearly familiar, no open questions surfaced, and no external APIs, new libraries, or unfamiliar patterns
280
282
 
281
283
  ```
282
284
  Milestone [Name] initialized.
@@ -289,17 +291,20 @@ Milestone name: $ARGUMENTS (optional — will emerge during discovery if not pro
289
291
 
290
292
  ## ▶ Next Up
291
293
 
292
- `/ms:[recommended-command]` — [one-line reason from conversation]
294
+ `/ms:{suggested}` — [one-line reason from conversation]
293
295
 
294
296
  <sub>`/clear` first → fresh context window</sub>
295
297
 
296
298
  ---
297
299
 
298
- **Also available:** `/ms:[alternative-command]` — [brief reason]
300
+ **Also available:**
301
+ - `/ms:{alternative}` — [brief reason]
299
302
 
300
303
  ---
301
304
  ```
302
305
 
306
+ `{suggested}` and `{alternative}` are always one of: `research-milestone`, `create-roadmap`.
307
+
303
308
  </process>
304
309
 
305
310
  <success_criteria>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mindsystem-cc",
3
- "version": "4.4.0",
3
+ "version": "4.4.1",
4
4
  "description": "The engineer's meta-prompting system for Claude Code.",
5
5
  "bin": {
6
6
  "mindsystem-cc": "bin/install.js"
@@ -614,6 +614,18 @@ def _get_claude_config_dir() -> Path:
614
614
  return Path.home() / ".claude"
615
615
 
616
616
 
617
+ def _get_settings_env_var(key: str) -> str:
618
+ """Read an env var from ~/.claude/settings.json env section."""
619
+ settings_path = _get_claude_config_dir() / "settings.json"
620
+ if not settings_path.is_file():
621
+ return ""
622
+ try:
623
+ settings = json.loads(settings_path.read_text(encoding="utf-8"))
624
+ return settings.get("env", {}).get(key, "")
625
+ except (json.JSONDecodeError, OSError):
626
+ return ""
627
+
628
+
617
629
  WEB_FRAMEWORK_DEPS = {
618
630
  "react", "react-dom", "vue", "next", "nuxt", "@angular/core",
619
631
  "svelte", "@sveltejs/kit", "solid-js", "astro", "@remix-run/react",
@@ -625,6 +637,25 @@ WEB_CONFIG_FILES = [
625
637
  "svelte.config.*", "astro.config.*", "remix.config.*",
626
638
  ]
627
639
 
640
+ SERVERSIDE_FRAMEWORK_FILES = [
641
+ "artisan", # Laravel
642
+ "config.ru", # Ruby Rack (Rails, Sinatra)
643
+ "wp-config.php", # WordPress
644
+ "wp-config-sample.php", # WordPress (fresh install)
645
+ ]
646
+
647
+ SERVERSIDE_COMPOSER_DEPS = {
648
+ "yiisoft/yii2", "yiisoft/yii", "laravel/framework",
649
+ "symfony/framework-bundle", "symfony/symfony",
650
+ "cakephp/cakephp", "codeigniter4/framework",
651
+ }
652
+
653
+ SERVERSIDE_GEMFILE_KEYWORDS = ["rails", "sinatra"]
654
+
655
+ SERVERSIDE_PYTHON_KEYWORDS = ["django", "flask", "fastapi"]
656
+
657
+ SERVERSIDE_PYTHON_FILES = ["requirements.txt", "pyproject.toml", "Pipfile"]
658
+
628
659
 
629
660
  def _detect_web_project(git_root: Path) -> tuple[bool, str]:
630
661
  """Detect if project is a web project. Returns (is_web, signal_description)."""
@@ -645,6 +676,46 @@ def _detect_web_project(git_root: Path) -> tuple[bool, str]:
645
676
  if list(git_root.glob(pattern)):
646
677
  return True, f"{pattern} found"
647
678
 
679
+ # Signal 2b: Server-side framework marker files
680
+ for marker in SERVERSIDE_FRAMEWORK_FILES:
681
+ if (git_root / marker).is_file():
682
+ return True, f"{marker} found"
683
+
684
+ # Signal 2c: composer.json deps
685
+ composer = git_root / "composer.json"
686
+ if composer.is_file():
687
+ try:
688
+ data = json.loads(composer.read_text(encoding="utf-8"))
689
+ all_deps = {**data.get("require", {}), **data.get("require-dev", {})}
690
+ found = SERVERSIDE_COMPOSER_DEPS & set(all_deps.keys())
691
+ if found:
692
+ return True, f"{', '.join(sorted(found))} in composer.json"
693
+ except (json.JSONDecodeError, OSError):
694
+ pass
695
+
696
+ # Signal 2d: Gemfile keywords
697
+ gemfile = git_root / "Gemfile"
698
+ if gemfile.is_file():
699
+ try:
700
+ content = gemfile.read_text(encoding="utf-8").lower()
701
+ for kw in SERVERSIDE_GEMFILE_KEYWORDS:
702
+ if kw in content:
703
+ return True, f"'{kw}' in Gemfile"
704
+ except OSError:
705
+ pass
706
+
707
+ # Signal 2e: Python dependency files
708
+ for pyfile in SERVERSIDE_PYTHON_FILES:
709
+ pypath = git_root / pyfile
710
+ if pypath.is_file():
711
+ try:
712
+ content = pypath.read_text(encoding="utf-8").lower()
713
+ for kw in SERVERSIDE_PYTHON_KEYWORDS:
714
+ if kw in content:
715
+ return True, f"'{kw}' in {pyfile}"
716
+ except OSError:
717
+ pass
718
+
648
719
  # Signal 3: One level deep package.json (monorepo)
649
720
  for child_pkg in git_root.glob("*/package.json"):
650
721
  if "node_modules" in str(child_pkg):
@@ -663,7 +734,16 @@ def _detect_web_project(git_root: Path) -> tuple[bool, str]:
663
734
  if project_md.is_file():
664
735
  try:
665
736
  content = project_md.read_text(encoding="utf-8").lower()
666
- web_keywords = ["react", "vue", "next.js", "nextjs", "nuxt", "angular", "svelte", "sveltekit", "astro", "remix"]
737
+ web_keywords = [
738
+ # JS frameworks
739
+ "react", "vue", "next.js", "nextjs", "nuxt", "angular", "svelte", "sveltekit", "astro", "remix",
740
+ # Server-side web frameworks
741
+ "yii", "laravel", "symfony", "codeigniter", "cakephp",
742
+ "django", "flask", "fastapi", "rails", "ruby on rails",
743
+ "spring boot", "spring mvc",
744
+ "asp.net", "blazor",
745
+ "wordpress",
746
+ ]
667
747
  for kw in web_keywords:
668
748
  if kw in content:
669
749
  return True, f"'{kw}' mentioned in PROJECT.md"
@@ -718,9 +798,25 @@ def cmd_browser_check(args: argparse.Namespace) -> None:
718
798
 
719
799
  # 2. Web project check
720
800
  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}")
801
+ web_project_override = None
802
+ if config_path and config_path.is_file():
803
+ try:
804
+ config = json.loads(config_path.read_text(encoding="utf-8"))
805
+ bv = config.get("browser_verification", {})
806
+ if isinstance(bv, dict) and "web_project" in bv:
807
+ web_project_override = bv["web_project"]
808
+ except (json.JSONDecodeError, OSError):
809
+ pass
810
+
811
+ if web_project_override is not None:
812
+ is_web = bool(web_project_override)
813
+ signal = "config override"
814
+ print(f"Web project: {is_web} (config override)")
815
+ print(f"Signal: {signal}")
816
+ else:
817
+ is_web, signal = _detect_web_project(git_root)
818
+ print(f"Web project: {is_web}")
819
+ print(f"Signal: {signal}")
724
820
 
725
821
  if not is_web:
726
822
  print("\nResult: SKIP (not a web project)")
@@ -759,6 +855,26 @@ def cmd_browser_check(args: argparse.Namespace) -> None:
759
855
  sys.exit(0)
760
856
 
761
857
 
858
+ # ===================================================================
859
+ # Subcommand: detect-web
860
+ # ===================================================================
861
+
862
+
863
+ def cmd_detect_web(args: argparse.Namespace) -> None:
864
+ """Expose _detect_web_project() for use by config command.
865
+
866
+ Contract:
867
+ Output: text — detected + signal
868
+ Exit codes: 0 = web project, 1 = not web project
869
+ Side effects: read-only
870
+ """
871
+ git_root = find_git_root()
872
+ is_web, signal = _detect_web_project(git_root)
873
+ print(f"detected: {is_web}")
874
+ print(f"signal: {signal}")
875
+ sys.exit(0 if is_web else 1)
876
+
877
+
762
878
  # ===================================================================
763
879
  # Subcommand: doctor-scan
764
880
  # ===================================================================
@@ -1131,28 +1247,50 @@ def cmd_doctor_scan(args: argparse.Namespace) -> None:
1131
1247
 
1132
1248
  # ---- CHECK 9: Research API Keys ----
1133
1249
  print("=== Research API Keys ===")
1134
- c7_key = os.environ.get("CONTEXT7_API_KEY", "")
1135
- pplx_key = os.environ.get("PERPLEXITY_API_KEY", "")
1136
- if c7_key and pplx_key:
1137
- print("Status: PASS")
1138
- print("All research API keys configured")
1139
- record("PASS", "Research API Keys")
1140
- else:
1250
+ api_keys = {
1251
+ "CONTEXT7_API_KEY": {
1252
+ "enables": "library documentation lookup via Context7",
1253
+ "without": "falls back to WebSearch/WebFetch (less authoritative)",
1254
+ "url": "https://context7.com → copy API key",
1255
+ },
1256
+ "PERPLEXITY_API_KEY": {
1257
+ "enables": "deep research via Perplexity AI",
1258
+ "without": "falls back to WebSearch/WebFetch (less comprehensive)",
1259
+ "url": "https://perplexity.ai/settings/api → copy API key",
1260
+ },
1261
+ }
1262
+ missing_keys: list[str] = []
1263
+ settings_only_keys: list[str] = []
1264
+ for key_name, info in api_keys.items():
1265
+ env_val = os.environ.get(key_name, "")
1266
+ settings_val = _get_settings_env_var(key_name)
1267
+ if env_val:
1268
+ pass # configured via environment
1269
+ elif settings_val:
1270
+ settings_only_keys.append(key_name)
1271
+ else:
1272
+ missing_keys.append(key_name)
1273
+ print(f"{key_name}: not set")
1274
+ print(f" Enables: {info['enables']}")
1275
+ print(f" Without: {info['without']}")
1276
+ if sys.platform == "win32":
1277
+ print(f" Set up: {info['url']} → add to ~/.claude/settings.json env section")
1278
+ print(f" Or set via Windows System Environment Variables")
1279
+ else:
1280
+ shell_rc = "~/.zshrc" if sys.platform == "darwin" else "~/.bashrc"
1281
+ print(f" Set up: {info['url']} → export {key_name}=<key> in {shell_rc}")
1282
+ print(f" Or add to ~/.claude/settings.json env section")
1283
+ if missing_keys:
1141
1284
  print("Status: WARN")
1142
- missing_keys: list[str] = []
1143
- if not c7_key:
1144
- missing_keys.append("CONTEXT7_API_KEY")
1145
- print("CONTEXT7_API_KEY: not set")
1146
- print(" Enables: library documentation lookup via Context7")
1147
- print(" Without: falls back to WebSearch/WebFetch (less authoritative)")
1148
- print(" Set up: https://context7.com → copy API key → export CONTEXT7_API_KEY=<key>")
1149
- if not pplx_key:
1150
- missing_keys.append("PERPLEXITY_API_KEY")
1151
- print("PERPLEXITY_API_KEY: not set")
1152
- print(" Enables: deep research via Perplexity AI")
1153
- print(" Without: falls back to WebSearch/WebFetch (less comprehensive)")
1154
- print(" Set up: https://perplexity.ai/settings/api → copy API key → export PERPLEXITY_API_KEY=<key>")
1155
1285
  record("WARN", "Research API Keys")
1286
+ else:
1287
+ print("Status: PASS")
1288
+ if settings_only_keys:
1289
+ print("All research API keys configured")
1290
+ print(f" Note: {', '.join(settings_only_keys)} found in settings.json — restart Claude Code session to activate")
1291
+ else:
1292
+ print("All research API keys configured")
1293
+ record("PASS", "Research API Keys")
1156
1294
  print()
1157
1295
 
1158
1296
  # ---- CHECK 10: Phase Directory Naming ----
@@ -1204,15 +1342,32 @@ def cmd_doctor_scan(args: argparse.Namespace) -> None:
1204
1342
  if isinstance(bv_config, dict):
1205
1343
  bv_enabled = bv_config.get("enabled", True)
1206
1344
 
1345
+ bv_web_override = None
1346
+ if isinstance(bv_config, dict) and "web_project" in bv_config:
1347
+ bv_web_override = bv_config["web_project"]
1348
+
1349
+ bv_hint = ""
1350
+
1207
1351
  if not bv_enabled:
1208
1352
  print("Status: SKIP")
1209
1353
  print("Disabled in config.json")
1210
1354
  record("SKIP", "Browser Verification")
1355
+ elif bv_web_override is False:
1356
+ is_web = False
1357
+ print("Status: SKIP")
1358
+ print("web_project: false in config.json")
1359
+ record("SKIP", "Browser Verification")
1211
1360
  else:
1212
- is_web, web_signal = _detect_web_project(git_root)
1361
+ if bv_web_override is True:
1362
+ is_web, web_signal = True, "config override"
1363
+ else:
1364
+ is_web, web_signal = _detect_web_project(git_root)
1365
+
1213
1366
  if not is_web:
1367
+ bv_hint = "Tip: If this is a web project, run /ms:config to enable browser verification."
1214
1368
  print("Status: SKIP")
1215
1369
  print(f"Not a web project ({web_signal})")
1370
+ print(bv_hint)
1216
1371
  record("SKIP", "Browser Verification")
1217
1372
  else:
1218
1373
  bv_missing: list[str] = []
@@ -1243,6 +1398,8 @@ def cmd_doctor_scan(args: argparse.Namespace) -> None:
1243
1398
  elif not is_web:
1244
1399
  print("Status: SKIP")
1245
1400
  print(f"Not a web project ({web_signal})")
1401
+ if bv_hint:
1402
+ print(bv_hint)
1246
1403
  record("SKIP", "Screenshot Optimization")
1247
1404
  else:
1248
1405
  if shutil.which("cwebp"):
@@ -3643,6 +3800,10 @@ def build_parser() -> argparse.ArgumentParser:
3643
3800
  p = subparsers.add_parser("browser-check", help="Check browser verification prerequisites")
3644
3801
  p.set_defaults(func=cmd_browser_check)
3645
3802
 
3803
+ # --- detect-web ---
3804
+ p = subparsers.add_parser("detect-web", help="Detect if project is a web project")
3805
+ p.set_defaults(func=cmd_detect_web)
3806
+
3646
3807
  # --- doctor-scan ---
3647
3808
  p = subparsers.add_parser("doctor-scan", help="Diagnostic scan of .planning/ tree")
3648
3809
  p.set_defaults(func=cmd_doctor_scan)