mindsystem-cc 3.5.0 → 3.6.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.
@@ -0,0 +1,168 @@
1
+ ---
2
+ name: ms-flutter-code-quality
3
+ description: Refactors Flutter/Dart code to follow quality guidelines. Applies code patterns, widget organization, folder structure, and simplification. Spawned by execute-phase/do-work.
4
+ model: sonnet
5
+ tools: Read, Write, Edit, Bash, Grep, Glob, WebFetch
6
+ color: cyan
7
+ skills:
8
+ - flutter-code-quality
9
+ - flutter-code-simplification
10
+ ---
11
+
12
+ You are an expert Flutter/Dart code quality specialist. Your job is to refactor code so it's clean, scalable, and maintainable by applying established guidelines.
13
+
14
+ **Core principle:** Apply the guidelines. Verify with tests. Report what was fixed.
15
+
16
+ <input_contract>
17
+ You receive:
18
+ - A list of files to refactor (via git diff or explicit list)
19
+ - Files are Flutter/Dart code (.dart extension)
20
+
21
+ You return:
22
+ - Refactored files that follow guidelines
23
+ - Verification results (analyze + test)
24
+ - Report of what was changed
25
+ </input_contract>
26
+
27
+ ## Key Principles
28
+
29
+ ### 1. Preserve Behavior (Non-negotiable)
30
+ Functionality comes before code quality. Only improve code quality if you can maintain functionality. Refactor structure, not logic — the code must do the same thing in a cleaner way.
31
+
32
+ ### 2. Apply Guidelines
33
+ If code doesn't follow the guidelines, refactor it so it does. The guidelines exist to be applied, not considered.
34
+
35
+ ### 3. Verify with Tests
36
+ Run `flutter analyze` and `flutter test` after changes. If verification fails, revert that specific change and continue with others.
37
+
38
+ ### 4. Comprehensive Coverage
39
+ Apply four lenses:
40
+ 1. Code quality patterns (anti-patterns, idioms, type safety)
41
+ 2. Widget organization (build structure, consistent ordering)
42
+ 3. Folder structure (flat, feature-based)
43
+ 4. Simplification (clarity, DRY, remove unnecessary complexity)
44
+
45
+ ## Four-Pass Refactoring
46
+
47
+ ### Pass 1: Code Quality Patterns
48
+
49
+ Fetch guidelines first:
50
+ ```
51
+ WebFetch: https://gist.githubusercontent.com/rolandtolnay/edf9ea7d5adf218f45accb3411f0627c/raw/flutter-code-quality-guidelines.md
52
+ ```
53
+
54
+ Replace anti-patterns:
55
+ - `useState<bool>` for loading → provider state
56
+ - Manual try-catch in providers → `AsyncValue.guard()`
57
+ - `.toList()..sort()` → `.sorted()`
58
+ - Functions with 4+ params → define inside build()
59
+ - Hardcoded hex colors → `context.color.*`
60
+ - `.asData?.value` → `.value`
61
+ - Inline filtering → computed property on entity
62
+
63
+ Apply positive patterns:
64
+ - Sealed classes for complex state
65
+ - Records for multiple return values
66
+ - Computed properties on entities/enums
67
+ - `firstWhereOrNull` with fallbacks
68
+ - Immutable collection methods
69
+
70
+ ### Pass 2: Widget Organization
71
+
72
+ Enforce build() structure:
73
+ - Order: providers → hooks → derived values → widget tree
74
+ - Local variables for unconditional widgets
75
+ - Builder functions for conditional rendering
76
+ - Extract file-private widgets to own file
77
+ - Move functions with 4+ params inside build()
78
+
79
+ Enforce async UX:
80
+ - Loading from provider state, not useState
81
+ - Error handling via `ref.listen` + toast
82
+ - First-load errors with retry button
83
+
84
+ ### Pass 3: Folder Structure
85
+
86
+ Enforce organization:
87
+ - Feature-based folders
88
+ - Screens at feature root
89
+ - `widgets/` only when 2+ widgets
90
+ - `providers/` only when 2+ providers
91
+ - `domain/` for models and repositories
92
+ - Flatten deep `lib/features/x/presentation/` paths
93
+
94
+ ### Pass 4: Simplification
95
+
96
+ Apply `flutter-code-simplification` skill principles:
97
+
98
+ - Repeated null-checks → extract to local variable
99
+ - Duplicated logic → extract to shared method
100
+ - Scattered boolean flags → consolidate to sealed class or enum
101
+ - Large build() methods → extract to builder methods
102
+ - Unnecessary indirection → simplify to direct calls
103
+
104
+ ## Process
105
+
106
+ 1. **Identify targets** - Parse scope to find modified .dart files
107
+ 2. **Fetch guidelines** - WebFetch flutter-code-quality-guidelines.md from gist
108
+ 3. **Refactor Pass 1** - Apply code quality patterns
109
+ 4. **Refactor Pass 2** - Apply widget organization rules
110
+ 5. **Refactor Pass 3** - Apply folder structure conventions
111
+ 6. **Refactor Pass 4** - Apply simplification principles
112
+ 7. **Verify** - Run `fvm flutter analyze` and `fvm flutter test`
113
+ 8. **If verification fails** - Revert the failing change, continue with others
114
+ 9. **Report** - Document what was refactored
115
+
116
+ <output_format>
117
+
118
+ **If changes were made:**
119
+ ```
120
+ ## Refactoring Complete
121
+
122
+ **Files:** [count] analyzed, [count] modified
123
+
124
+ ### Code Quality
125
+ - `path/file.dart:42` - useState → provider state
126
+ - `path/file.dart:67` - .toList()..sort() → .sorted()
127
+
128
+ ### Widget Organization
129
+ - `path/file.dart:120` - Reordered build(): providers → hooks → derived → tree
130
+
131
+ ### Folder Structure
132
+ - Moved `path/nested/widget.dart` → `path/widget.dart`
133
+
134
+ ### Simplification
135
+ - `path/file.dart:150` - Extracted repeated logic to `_buildHeader()`
136
+
137
+ ### Verification
138
+ - flutter analyze: pass
139
+ - flutter test: pass
140
+
141
+ ### Modified Files
142
+ [list of file paths]
143
+ ```
144
+
145
+ **If no changes needed:**
146
+ ```
147
+ ## Refactoring Complete
148
+
149
+ **Files:** [count] analyzed, 0 modified
150
+
151
+ Code already follows guidelines.
152
+
153
+ ### Verification
154
+ - flutter analyze: pass
155
+ - flutter test: pass
156
+ ```
157
+
158
+ </output_format>
159
+
160
+ <success_criteria>
161
+ - All functionality preserved — no behavior changes
162
+ - Guidelines fetched from gist
163
+ - All target .dart files refactored through four passes
164
+ - Code follows guidelines after refactoring
165
+ - `flutter analyze` passes
166
+ - `flutter test` passes
167
+ - Report documents what was changed
168
+ </success_criteria>
@@ -4,11 +4,13 @@ description: Simplifies Flutter/Dart code for clarity, consistency, and maintain
4
4
  model: sonnet
5
5
  tools: Read, Write, Edit, Bash, Grep, Glob
6
6
  color: cyan
7
+ skills:
8
+ - flutter-code-simplification
7
9
  ---
8
10
 
9
11
  You are an expert Flutter/Dart code simplification specialist. Your expertise lies in making code easier to read, understand, and maintain without changing what it does. You prioritize readable, explicit code over overly compact solutions.
10
12
 
11
- **Core principle:** Simplification means making code easier to reason about — not making it shorter at the cost of clarity.
13
+ Apply simplification principles and Flutter patterns from the `flutter-code-simplification` skill.
12
14
 
13
15
  <input_contract>
14
16
  You receive:
@@ -21,55 +23,6 @@ You return:
21
23
  - If no changes needed: clear statement that code already follows good patterns
22
24
  </input_contract>
23
25
 
24
- ## Key Principles
25
-
26
- ### 1. Preserve Functionality
27
- Never change what the code does—only how it does it. All original features, outputs, and behaviors must remain intact.
28
-
29
- ### 2. Enhance Clarity
30
- - Reduce unnecessary complexity and nesting
31
- - Eliminate redundant code and abstractions
32
- - Improve readability through clear naming
33
- - Consolidate related logic and duplicates (DRY)
34
- - Choose clarity over brevity—explicit code is often better than compact code
35
-
36
- ### 3. Maintain Balance
37
- Avoid over-simplification that could:
38
- - Create overly clever solutions that are hard to understand
39
- - Combine too many concerns into single functions/components
40
- - Remove helpful abstractions that improve code organization
41
- - Prioritize "fewer lines" over readability
42
- - Make code harder to debug or extend
43
-
44
- ### 4. Apply Judgment
45
- Use your expertise to determine what improves the code. These principles guide your decisions—they are not a checklist. If a change doesn't clearly improve clarity while preserving behavior, don't make it.
46
-
47
- ## Flutter Patterns to Consider
48
-
49
- These are common opportunities in Flutter/Dart code. Apply when they genuinely improve clarity.
50
-
51
- **State & Data:**
52
- - Scattered boolean flags → sealed class variants with switch expressions (when it consolidates and clarifies)
53
- - Same parameters repeated across functions → records or typed classes
54
- - Manual try-catch in providers → `AsyncValue.guard()` with centralized error handling
55
- - Check `ref.mounted` after async operations
56
-
57
- **Widget Structure:**
58
- - Large `build()` methods → extract into local variables or builder methods
59
- - Widgets with many boolean parameters → consider composition or typed mode objects
60
- - Keep build() order: providers → hooks → derived values → widget tree
61
-
62
- **Collections:**
63
- - Mutation patterns → immutable methods (`.sorted()`, `.where()`, etc.)
64
- - Null-unsafe access → `firstWhereOrNull` with fallbacks
65
- - Repeated enum switches → computed properties on the enum itself
66
-
67
- **Code Organization:**
68
- - Duplicated logic across files → extract to shared location
69
- - Related methods scattered in class → group by concern
70
- - Unnecessary indirection (factories creating one type, wrappers adding no behavior) → use concrete types directly
71
- - **Exception:** API layer interfaces with implementation in same file are intentional (interface provides at-a-glance documentation)
72
-
73
26
  ## Process
74
27
 
75
28
  1. **Identify targets** - Parse scope to find modified .dart files
package/bin/install.js CHANGED
@@ -4,6 +4,7 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
  const os = require('os');
6
6
  const readline = require('readline');
7
+ const crypto = require('crypto');
7
8
 
8
9
  // Colors (using 256-color mode for better terminal compatibility)
9
10
  const cyan = '\x1b[38;5;37m'; // Closest to #2FA7A0 in 256-color palette
@@ -54,6 +55,7 @@ function parseConfigDirArg() {
54
55
  return null;
55
56
  }
56
57
  const explicitConfigDir = parseConfigDirArg();
58
+ const hasForce = args.includes('--force') || args.includes('-f');
57
59
  const hasHelp = args.includes('--help') || args.includes('-h');
58
60
 
59
61
  console.log(banner);
@@ -66,6 +68,7 @@ if (hasHelp) {
66
68
  ${cyan}-g, --global${reset} Install globally (to Claude config directory)
67
69
  ${cyan}-l, --local${reset} Install locally (to ./.claude in current directory)
68
70
  ${cyan}-c, --config-dir <path>${reset} Specify custom Claude config directory
71
+ ${cyan}-f, --force${reset} Overwrite modified files without prompting
69
72
  ${cyan}-h, --help${reset} Show this help message
70
73
 
71
74
  ${yellow}Examples:${reset}
@@ -99,6 +102,308 @@ function expandTilde(filePath) {
99
102
  return filePath;
100
103
  }
101
104
 
105
+ /**
106
+ * Compute SHA-256 checksum truncated to 16 chars
107
+ */
108
+ function computeChecksum(content) {
109
+ return crypto.createHash('sha256').update(content).digest('hex').slice(0, 16);
110
+ }
111
+
112
+ /**
113
+ * Read and parse manifest file, return null if missing/corrupted
114
+ */
115
+ function readManifest(claudeDir) {
116
+ const manifestPath = path.join(claudeDir, 'mindsystem', '.manifest.json');
117
+ try {
118
+ if (!fs.existsSync(manifestPath)) {
119
+ return null;
120
+ }
121
+ const content = fs.readFileSync(manifestPath, 'utf8');
122
+ return JSON.parse(content);
123
+ } catch (e) {
124
+ console.log(` ${yellow}⚠${reset} Manifest corrupted, treating as fresh install`);
125
+ return null;
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Write manifest JSON file
131
+ */
132
+ function writeManifest(claudeDir, manifest) {
133
+ const manifestDir = path.join(claudeDir, 'mindsystem');
134
+ fs.mkdirSync(manifestDir, { recursive: true });
135
+ const manifestPath = path.join(manifestDir, '.manifest.json');
136
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
137
+ }
138
+
139
+ /**
140
+ * Check if running in interactive TTY
141
+ */
142
+ function isInteractive() {
143
+ return process.stdin.isTTY && process.stdout.isTTY;
144
+ }
145
+
146
+ /**
147
+ * Recursively collect files with relative paths
148
+ * @param {string} baseDir - The base directory for relative path calculation
149
+ * @param {string} currentDir - The current directory being scanned
150
+ * @param {string} destPrefix - The destination prefix (e.g., 'commands/ms', 'agents')
151
+ * @returns {Array<{relativePath: string, absolutePath: string}>}
152
+ */
153
+ function collectFiles(baseDir, currentDir, destPrefix) {
154
+ const files = [];
155
+ if (!fs.existsSync(currentDir)) {
156
+ return files;
157
+ }
158
+
159
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
160
+ for (const entry of entries) {
161
+ const absolutePath = path.join(currentDir, entry.name);
162
+ const relativeToCurrent = path.relative(baseDir, absolutePath);
163
+ const relativePath = path.join(destPrefix, relativeToCurrent);
164
+
165
+ if (entry.isDirectory()) {
166
+ files.push(...collectFiles(baseDir, absolutePath, destPrefix));
167
+ } else {
168
+ files.push({ relativePath, absolutePath });
169
+ }
170
+ }
171
+ return files;
172
+ }
173
+
174
+ /**
175
+ * Build complete list of files to install from all source directories
176
+ * @param {string} src - The source directory (mindsystem package root)
177
+ * @returns {Array<{relativePath: string, absolutePath: string}>}
178
+ */
179
+ function buildInstallManifest(src) {
180
+ const files = [];
181
+
182
+ // commands/ms
183
+ const commandsSrc = path.join(src, 'commands', 'ms');
184
+ files.push(...collectFiles(commandsSrc, commandsSrc, 'commands/ms'));
185
+
186
+ // mindsystem
187
+ const mindsystemSrc = path.join(src, 'mindsystem');
188
+ files.push(...collectFiles(mindsystemSrc, mindsystemSrc, 'mindsystem'));
189
+
190
+ // agents
191
+ const agentsSrc = path.join(src, 'agents');
192
+ files.push(...collectFiles(agentsSrc, agentsSrc, 'agents'));
193
+
194
+ // scripts -> mindsystem/scripts
195
+ const scriptsSrc = path.join(src, 'scripts');
196
+ files.push(...collectFiles(scriptsSrc, scriptsSrc, 'mindsystem/scripts'));
197
+
198
+ // skills
199
+ const skillsSrc = path.join(src, 'skills');
200
+ files.push(...collectFiles(skillsSrc, skillsSrc, 'skills'));
201
+
202
+ // CHANGELOG.md -> mindsystem/CHANGELOG.md
203
+ const changelogSrc = path.join(src, 'CHANGELOG.md');
204
+ if (fs.existsSync(changelogSrc)) {
205
+ files.push({ relativePath: 'mindsystem/CHANGELOG.md', absolutePath: changelogSrc });
206
+ }
207
+
208
+ return files;
209
+ }
210
+
211
+ /**
212
+ * Compare manifests to detect orphans and conflicts
213
+ * @param {Object|null} oldManifest - Previous manifest or null for fresh install
214
+ * @param {string} claudeDir - Target directory
215
+ * @param {Array} newFiles - Files to install
216
+ * @param {string} pathPrefix - Path prefix for content replacement
217
+ * @returns {{orphans: string[], conflicts: Array<{relativePath: string, reason: string}>}}
218
+ */
219
+ function compareManifests(oldManifest, claudeDir, newFiles, pathPrefix) {
220
+ const orphans = [];
221
+ const conflicts = [];
222
+
223
+ // Build set of new file paths
224
+ const newFilePaths = new Set(newFiles.map(f => f.relativePath));
225
+
226
+ // Find orphans (files in old manifest but not in new)
227
+ if (oldManifest && oldManifest.files) {
228
+ for (const oldPath of Object.keys(oldManifest.files)) {
229
+ if (!newFilePaths.has(oldPath)) {
230
+ const fullPath = path.join(claudeDir, oldPath);
231
+ if (fs.existsSync(fullPath)) {
232
+ orphans.push(oldPath);
233
+ }
234
+ }
235
+ }
236
+ }
237
+
238
+ // Find conflicts (installed files modified by user)
239
+ if (oldManifest && oldManifest.files) {
240
+ for (const fileInfo of newFiles) {
241
+ const destPath = path.join(claudeDir, fileInfo.relativePath);
242
+ if (!fs.existsSync(destPath)) {
243
+ continue; // New file, no conflict
244
+ }
245
+
246
+ const oldChecksum = oldManifest.files[fileInfo.relativePath];
247
+ if (!oldChecksum) {
248
+ continue; // File not in old manifest, treat as new
249
+ }
250
+
251
+ // Read current installed content
252
+ const installedContent = fs.readFileSync(destPath, 'utf8');
253
+ const installedChecksum = computeChecksum(installedContent);
254
+
255
+ // If installed file differs from what we last installed, it's been modified
256
+ if (installedChecksum !== oldChecksum) {
257
+ // Read source content with path replacement to compare
258
+ let sourceContent = fs.readFileSync(fileInfo.absolutePath, 'utf8');
259
+ if (fileInfo.absolutePath.endsWith('.md')) {
260
+ sourceContent = sourceContent.replace(/~\/\.claude\//g, pathPrefix);
261
+ }
262
+ const sourceChecksum = computeChecksum(sourceContent);
263
+
264
+ // Only conflict if source is also different (user modified AND we have changes)
265
+ if (sourceChecksum !== installedChecksum) {
266
+ conflicts.push({
267
+ relativePath: fileInfo.relativePath,
268
+ reason: 'locally modified'
269
+ });
270
+ }
271
+ }
272
+ }
273
+ }
274
+
275
+ return { orphans, conflicts };
276
+ }
277
+
278
+ /**
279
+ * Interactive conflict resolution
280
+ * @param {Array} conflicts - List of conflicting files
281
+ * @param {boolean} forceOverwrite - Skip prompts and overwrite all
282
+ * @returns {Promise<{overwrite: Set<string>, keep: Set<string>}>}
283
+ */
284
+ async function resolveConflicts(conflicts, forceOverwrite) {
285
+ const overwrite = new Set();
286
+ const keep = new Set();
287
+
288
+ if (conflicts.length === 0) {
289
+ return { overwrite, keep };
290
+ }
291
+
292
+ if (forceOverwrite) {
293
+ for (const c of conflicts) {
294
+ overwrite.add(c.relativePath);
295
+ }
296
+ console.log(` ${yellow}⚠${reset} Force overwriting ${conflicts.length} modified file(s)`);
297
+ return { overwrite, keep };
298
+ }
299
+
300
+ if (!isInteractive()) {
301
+ for (const c of conflicts) {
302
+ overwrite.add(c.relativePath);
303
+ }
304
+ console.log(` ${yellow}⚠${reset} Non-interactive mode: overwriting ${conflicts.length} modified file(s)`);
305
+ return { overwrite, keep };
306
+ }
307
+
308
+ const rl = readline.createInterface({
309
+ input: process.stdin,
310
+ output: process.stdout
311
+ });
312
+
313
+ const question = (prompt) => new Promise((resolve) => rl.question(prompt, resolve));
314
+
315
+ console.log(`\n ${yellow}${conflicts.length} file(s) have local modifications:${reset}\n`);
316
+
317
+ let overwriteAll = false;
318
+ let keepAll = false;
319
+
320
+ for (const conflict of conflicts) {
321
+ if (overwriteAll) {
322
+ overwrite.add(conflict.relativePath);
323
+ continue;
324
+ }
325
+ if (keepAll) {
326
+ keep.add(conflict.relativePath);
327
+ continue;
328
+ }
329
+
330
+ console.log(` ${dim}${conflict.relativePath}${reset}`);
331
+ const answer = await question(` [O]verwrite, [K]eep, [A]ll overwrite, [N]one keep? `);
332
+
333
+ switch (answer.toLowerCase().trim()) {
334
+ case 'o':
335
+ case 'overwrite':
336
+ overwrite.add(conflict.relativePath);
337
+ break;
338
+ case 'k':
339
+ case 'keep':
340
+ keep.add(conflict.relativePath);
341
+ break;
342
+ case 'a':
343
+ case 'all':
344
+ overwriteAll = true;
345
+ overwrite.add(conflict.relativePath);
346
+ break;
347
+ case 'n':
348
+ case 'none':
349
+ keepAll = true;
350
+ keep.add(conflict.relativePath);
351
+ break;
352
+ default:
353
+ // Default to overwrite
354
+ overwrite.add(conflict.relativePath);
355
+ }
356
+ }
357
+
358
+ rl.close();
359
+ console.log('');
360
+ return { overwrite, keep };
361
+ }
362
+
363
+ /**
364
+ * Remove orphaned files and empty directories
365
+ * @param {string} claudeDir - Target directory
366
+ * @param {string[]} filesToRemove - Relative paths of files to remove
367
+ */
368
+ function cleanupOrphanedFiles(claudeDir, filesToRemove) {
369
+ if (filesToRemove.length === 0) {
370
+ return;
371
+ }
372
+
373
+ const dirsToCheck = new Set();
374
+
375
+ for (const relativePath of filesToRemove) {
376
+ const fullPath = path.join(claudeDir, relativePath);
377
+ try {
378
+ if (fs.existsSync(fullPath)) {
379
+ fs.unlinkSync(fullPath);
380
+ console.log(` ${yellow}✗${reset} Removed ${relativePath}`);
381
+ // Track parent directories for cleanup
382
+ dirsToCheck.add(path.dirname(fullPath));
383
+ }
384
+ } catch (e) {
385
+ console.log(` ${yellow}⚠${reset} Failed to remove ${relativePath}: ${e.message}`);
386
+ }
387
+ }
388
+
389
+ // Remove empty directories (deepest first)
390
+ const sortedDirs = Array.from(dirsToCheck).sort((a, b) => b.length - a.length);
391
+ for (const dir of sortedDirs) {
392
+ try {
393
+ // Don't remove the claudeDir itself or its immediate children (commands, agents, etc.)
394
+ if (dir === claudeDir || path.dirname(dir) === claudeDir) {
395
+ continue;
396
+ }
397
+ const entries = fs.readdirSync(dir);
398
+ if (entries.length === 0) {
399
+ fs.rmdirSync(dir);
400
+ }
401
+ } catch (e) {
402
+ // Ignore errors (directory not empty or doesn't exist)
403
+ }
404
+ }
405
+ }
406
+
102
407
  /**
103
408
  * Recursively copy directory, replacing paths in .md files
104
409
  */
@@ -144,10 +449,33 @@ function copyDir(srcDir, destDir) {
144
449
  }
145
450
  }
146
451
 
452
+ /**
453
+ * Install a single file with path replacement
454
+ * @param {string} srcPath - Source file path
455
+ * @param {string} destPath - Destination file path
456
+ * @param {string} pathPrefix - Path prefix for .md file replacement
457
+ */
458
+ function installFile(srcPath, destPath, pathPrefix) {
459
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
460
+
461
+ if (srcPath.endsWith('.md')) {
462
+ let content = fs.readFileSync(srcPath, 'utf8');
463
+ content = content.replace(/~\/\.claude\//g, pathPrefix);
464
+ fs.writeFileSync(destPath, content);
465
+ } else {
466
+ fs.copyFileSync(srcPath, destPath);
467
+ }
468
+
469
+ // Make shell scripts executable
470
+ if (srcPath.endsWith('.sh')) {
471
+ fs.chmodSync(destPath, '755');
472
+ }
473
+ }
474
+
147
475
  /**
148
476
  * Install to the specified directory
149
477
  */
150
- function install(isGlobal) {
478
+ async function install(isGlobal) {
151
479
  const src = path.join(__dirname, '..');
152
480
  // Priority: explicit --config-dir arg > CLAUDE_CONFIG_DIR env var > default ~/.claude
153
481
  const configDir = expandTilde(explicitConfigDir) || expandTilde(process.env.CLAUDE_CONFIG_DIR);
@@ -168,93 +496,106 @@ function install(isGlobal) {
168
496
 
169
497
  console.log(` Installing to ${cyan}${locationLabel}${reset}\n`);
170
498
 
171
- // Create commands directory
172
- const commandsDir = path.join(claudeDir, 'commands');
173
- fs.mkdirSync(commandsDir, { recursive: true });
499
+ // Phase 1: Read old manifest
500
+ const oldManifest = readManifest(claudeDir);
501
+
502
+ // Phase 2: Build list of files to install
503
+ const filesToInstall = buildInstallManifest(src);
504
+
505
+ // Phase 3: Compare and detect conflicts
506
+ const { orphans, conflicts } = compareManifests(oldManifest, claudeDir, filesToInstall, pathPrefix);
507
+
508
+ // Phase 4: Resolve conflicts interactively
509
+ const { overwrite, keep } = await resolveConflicts(conflicts, hasForce);
510
+
511
+ // Phase 5: Install files (skipping kept files)
512
+ const newManifestFiles = {};
513
+ const categories = {
514
+ 'commands/ms': { count: 0, label: 'commands/ms' },
515
+ 'mindsystem': { count: 0, label: 'mindsystem' },
516
+ 'agents': { count: 0, label: 'agents' },
517
+ 'mindsystem/scripts': { count: 0, label: 'scripts' },
518
+ 'skills': { count: 0, label: 'skills' }
519
+ };
520
+
521
+ for (const fileInfo of filesToInstall) {
522
+ const destPath = path.join(claudeDir, fileInfo.relativePath);
523
+
524
+ // Skip files user wants to keep
525
+ if (keep.has(fileInfo.relativePath)) {
526
+ // Still need to track in manifest with current checksum
527
+ const installedContent = fs.readFileSync(destPath, 'utf8');
528
+ newManifestFiles[fileInfo.relativePath] = computeChecksum(installedContent);
529
+ continue;
530
+ }
174
531
 
175
- // Copy commands/ms with path replacement
176
- const msSrc = path.join(src, 'commands', 'ms');
177
- const msDest = path.join(commandsDir, 'ms');
178
- copyWithPathReplacement(msSrc, msDest, pathPrefix);
179
- console.log(` ${green}✓${reset} Installed commands/ms`);
532
+ // Install the file
533
+ installFile(fileInfo.absolutePath, destPath, pathPrefix);
180
534
 
181
- // Copy mindsystem skill with path replacement
182
- const skillSrc = path.join(src, 'mindsystem');
183
- const skillDest = path.join(claudeDir, 'mindsystem');
184
- copyWithPathReplacement(skillSrc, skillDest, pathPrefix);
185
- console.log(` ${green}✓${reset} Installed mindsystem`);
535
+ // Compute checksum of installed content (after path replacement)
536
+ let installedContent;
537
+ if (fileInfo.absolutePath.endsWith('.md')) {
538
+ installedContent = fs.readFileSync(fileInfo.absolutePath, 'utf8');
539
+ installedContent = installedContent.replace(/~\/\.claude\//g, pathPrefix);
540
+ } else {
541
+ installedContent = fs.readFileSync(destPath, 'utf8');
542
+ }
543
+ newManifestFiles[fileInfo.relativePath] = computeChecksum(installedContent);
186
544
 
187
- // Copy agents to ~/.claude/agents (subagents must be at root level)
188
- const agentsSrc = path.join(src, 'agents');
189
- if (fs.existsSync(agentsSrc)) {
190
- const agentsDest = path.join(claudeDir, 'agents');
191
- copyWithPathReplacement(agentsSrc, agentsDest, pathPrefix);
192
- console.log(` ${green}✓${reset} Installed agents`);
545
+ // Track category counts
546
+ for (const prefix of Object.keys(categories)) {
547
+ if (fileInfo.relativePath.startsWith(prefix)) {
548
+ categories[prefix].count++;
549
+ break;
550
+ }
551
+ }
193
552
  }
194
553
 
195
- // Copy scripts to ~/.claude/mindsystem/scripts/
196
- const scriptsSrc = path.join(src, 'scripts');
197
- if (fs.existsSync(scriptsSrc)) {
198
- const scriptsDest = path.join(claudeDir, 'mindsystem', 'scripts');
199
- fs.mkdirSync(scriptsDest, { recursive: true });
200
- const scriptEntries = fs.readdirSync(scriptsSrc, { withFileTypes: true });
201
- for (const entry of scriptEntries) {
202
- const srcPath = path.join(scriptsSrc, entry.name);
203
- const destPath = path.join(scriptsDest, entry.name);
204
- if (entry.isDirectory()) {
205
- // Recursively copy directories (like ms-lookup/)
206
- copyDir(srcPath, destPath);
207
- } else {
208
- fs.copyFileSync(srcPath, destPath);
209
- // Make shell scripts executable
210
- if (entry.name.endsWith('.sh')) {
211
- fs.chmodSync(destPath, '755');
212
- }
213
- }
554
+ // Print install summaries
555
+ for (const [prefix, info] of Object.entries(categories)) {
556
+ if (info.count > 0) {
557
+ console.log(` ${green}✓${reset} Installed ${info.label}`);
214
558
  }
215
- console.log(` ${green}✓${reset} Installed scripts`);
216
-
217
- // Check Python availability for ms-lookup
218
- const msLookupPath = path.join(scriptsDest, 'ms-lookup');
219
- if (fs.existsSync(msLookupPath)) {
220
- try {
221
- const { execSync } = require('child_process');
222
- const pyVersion = execSync('python3 --version 2>&1', { encoding: 'utf8' });
223
- const versionMatch = pyVersion.match(/(\d+)\.(\d+)/);
224
- if (versionMatch) {
225
- const [, major, minor] = versionMatch;
226
- if (parseInt(major) < 3 || (parseInt(major) === 3 && parseInt(minor) < 9)) {
227
- console.log(` ${yellow}⚠${reset} Python 3.9+ required for ms-lookup (found ${major}.${minor})`);
228
- } else {
229
- console.log(` ${green}✓${reset} Installed ms-lookup CLI (Python ${major}.${minor})`);
230
- }
559
+ }
560
+
561
+ // Phase 6: Write VERSION file
562
+ const versionDest = path.join(claudeDir, 'mindsystem', 'VERSION');
563
+ fs.writeFileSync(versionDest, pkg.version);
564
+ console.log(` ${green}✓${reset} Wrote VERSION (${pkg.version})`);
565
+
566
+ // Phase 7: Check Python for ms-lookup
567
+ const msLookupPath = path.join(claudeDir, 'mindsystem', 'scripts', 'ms-lookup');
568
+ if (fs.existsSync(msLookupPath)) {
569
+ try {
570
+ const { execSync } = require('child_process');
571
+ const pyVersion = execSync('python3 --version 2>&1', { encoding: 'utf8' });
572
+ const versionMatch = pyVersion.match(/(\d+)\.(\d+)/);
573
+ if (versionMatch) {
574
+ const [, major, minor] = versionMatch;
575
+ if (parseInt(major) < 3 || (parseInt(major) === 3 && parseInt(minor) < 9)) {
576
+ console.log(` ${yellow}⚠${reset} Python 3.9+ required for ms-lookup (found ${major}.${minor})`);
577
+ } else {
578
+ console.log(` ${green}✓${reset} Installed ms-lookup CLI (Python ${major}.${minor})`);
231
579
  }
232
- } catch (e) {
233
- console.log(` ${yellow}⚠${reset} Python not found - ms-lookup CLI requires Python 3.9+`);
234
580
  }
581
+ } catch (e) {
582
+ console.log(` ${yellow}⚠${reset} Python not found - ms-lookup CLI requires Python 3.9+`);
235
583
  }
236
584
  }
237
585
 
238
- // Copy skills
239
- const skillsSrc = path.join(src, 'skills');
240
- if (fs.existsSync(skillsSrc)) {
241
- const skillsDest = path.join(claudeDir, 'skills');
242
- copyWithPathReplacement(skillsSrc, skillsDest, pathPrefix);
243
- console.log(` ${green}✓${reset} Installed skills`);
244
- }
245
-
246
- // Copy CHANGELOG.md
247
- const changelogSrc = path.join(src, 'CHANGELOG.md');
248
- const changelogDest = path.join(claudeDir, 'mindsystem', 'CHANGELOG.md');
249
- if (fs.existsSync(changelogSrc)) {
250
- fs.copyFileSync(changelogSrc, changelogDest);
251
- console.log(` ${green}✓${reset} Installed CHANGELOG.md`);
586
+ // Phase 8: Cleanup orphaned files
587
+ if (orphans.length > 0) {
588
+ console.log('');
589
+ cleanupOrphanedFiles(claudeDir, orphans);
252
590
  }
253
591
 
254
- // Write VERSION file for whats-new command
255
- const versionDest = path.join(claudeDir, 'mindsystem', 'VERSION');
256
- fs.writeFileSync(versionDest, pkg.version);
257
- console.log(` ${green}✓${reset} Wrote VERSION (${pkg.version})`);
592
+ // Phase 9: Write new manifest
593
+ const newManifest = {
594
+ version: pkg.version,
595
+ installedAt: new Date().toISOString(),
596
+ files: newManifestFiles
597
+ };
598
+ writeManifest(claudeDir, newManifest);
258
599
 
259
600
  console.log(`
260
601
  ${green}Done!${reset} Launch Claude Code and run ${cyan}/ms:help${reset}.
@@ -264,7 +605,7 @@ function install(isGlobal) {
264
605
  /**
265
606
  * Prompt for install location
266
607
  */
267
- function promptLocation() {
608
+ async function promptLocation() {
268
609
  const rl = readline.createInterface({
269
610
  input: process.stdin,
270
611
  output: process.stdout
@@ -280,25 +621,35 @@ function promptLocation() {
280
621
  ${cyan}2${reset}) Local ${dim}(./.claude)${reset} - this project only
281
622
  `);
282
623
 
283
- rl.question(` Choice ${dim}[1]${reset}: `, (answer) => {
284
- rl.close();
285
- const choice = answer.trim() || '1';
286
- const isGlobal = choice !== '2';
287
- install(isGlobal);
624
+ return new Promise((resolve) => {
625
+ rl.question(` Choice ${dim}[1]${reset}: `, async (answer) => {
626
+ rl.close();
627
+ const choice = answer.trim() || '1';
628
+ const isGlobal = choice !== '2';
629
+ await install(isGlobal);
630
+ resolve();
631
+ });
288
632
  });
289
633
  }
290
634
 
291
635
  // Main
292
- if (hasGlobal && hasLocal) {
293
- console.error(` ${yellow}Cannot specify both --global and --local${reset}`);
294
- process.exit(1);
295
- } else if (explicitConfigDir && hasLocal) {
296
- console.error(` ${yellow}Cannot use --config-dir with --local${reset}`);
297
- process.exit(1);
298
- } else if (hasGlobal) {
299
- install(true);
300
- } else if (hasLocal) {
301
- install(false);
302
- } else {
303
- promptLocation();
636
+ async function main() {
637
+ if (hasGlobal && hasLocal) {
638
+ console.error(` ${yellow}Cannot specify both --global and --local${reset}`);
639
+ process.exit(1);
640
+ } else if (explicitConfigDir && hasLocal) {
641
+ console.error(` ${yellow}Cannot use --config-dir with --local${reset}`);
642
+ process.exit(1);
643
+ } else if (hasGlobal) {
644
+ await install(true);
645
+ } else if (hasLocal) {
646
+ await install(false);
647
+ } else {
648
+ await promptLocation();
649
+ }
304
650
  }
651
+
652
+ main().catch((err) => {
653
+ console.error(` ${yellow}Error: ${err.message}${reset}`);
654
+ process.exit(1);
655
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mindsystem-cc",
3
- "version": "3.5.0",
3
+ "version": "3.6.0",
4
4
  "description": "A meta-prompting, context engineering and spec-driven development system for Claude Code by TÂCHES.",
5
5
  "bin": {
6
6
  "mindsystem-cc": "bin/install.js"
@@ -0,0 +1,142 @@
1
+ ---
2
+ name: flutter-code-quality
3
+ description: Flutter/Dart code quality, widget organization, and folder structure guidelines. Use when reviewing, refactoring, or cleaning up Flutter code after implementation.
4
+ license: MIT
5
+ metadata:
6
+ author: forgeblast
7
+ version: "1.0.0"
8
+ date: January 2026
9
+ argument-hint: <file-or-pattern>
10
+ ---
11
+
12
+ # Flutter Code Quality
13
+
14
+ Comprehensive guidelines for Flutter/Dart code quality, widget organization, and folder structure. Combines pattern-level rules (anti-patterns, idioms) with structural guidance (build method organization, folder conventions).
15
+
16
+ ## How It Works
17
+
18
+ 1. Fetch flutter-code-quality-guidelines.md from the gist URL below (always fresh)
19
+ 2. Apply embedded widget organization and folder structure rules
20
+ 3. Check files against all guidelines
21
+ 4. Output findings in terse `file:line` format
22
+
23
+ ## Code Quality Guidelines (Remote)
24
+
25
+ Fetch fresh guidelines before each review:
26
+
27
+ ```
28
+ https://gist.githubusercontent.com/rolandtolnay/edf9ea7d5adf218f45accb3411f0627c/raw/flutter-code-quality-guidelines.md
29
+ ```
30
+
31
+ Use WebFetch to retrieve. Contains: anti-patterns, widget patterns, state management, collections, hooks, theme/styling, etc.
32
+
33
+ ## Widget Organization Guidelines (Embedded)
34
+
35
+ ### Build Method Structure
36
+
37
+ Order inside `build()`:
38
+ 1. Providers (reads/watches)
39
+ 2. Hooks (if using flutter_hooks)
40
+ 3. Derived variables needed for rendering
41
+ 4. Widget variables (in render order)
42
+
43
+ ### When to Use What
44
+
45
+ | Scenario | Pattern |
46
+ |----------|---------|
47
+ | Widget always shown, no conditions | Local variable: `final header = Container(...);` |
48
+ | Widget depends on condition/null check | Builder function: `Widget? _buildContent() { if (data == null) return null; ... }` |
49
+ | Subtree is large, reusable, or has own state | Extract to standalone widget in own file |
50
+ | Function needs 3 or fewer params | Define outside `build()` as class method |
51
+ | Function needs 4+ params (esp. hooks) | Define inside `build()` to capture scope |
52
+
53
+ ### Rules
54
+
55
+ - **No file-private widgets** - If big enough to be a widget, it gets its own file
56
+ - **Define local variables in render order** - Top-to-bottom matches screen layout
57
+ - **Extract non-trivial conditions** - `final canSubmit = isValid && !isLoading && selectedId != null;`
58
+ - **Pass `WidgetRef` only** when function needs both ref and context - Use `ref.context` inside
59
+
60
+ ### Async UX Conventions
61
+
62
+ - **Button loading** - Watch provider state, not separate `useState<bool>`
63
+ - **Retriable errors** - Listen to provider, show toast on error, user retries by tapping again
64
+ - **First-load errors** - Render error view with retry button that invalidates provider
65
+
66
+ ### Sorting/Filtering
67
+
68
+ - Simple options: Use enum with computed properties
69
+ - Options with behavior: Use sealed class
70
+ - Complex multi-field filtering: Dedicated `Filter` class
71
+
72
+ ## Folder Structure Guidelines (Embedded)
73
+
74
+ ### Core Rules
75
+
76
+ 1. **Organize by feature** - Each feature gets its own folder
77
+ 2. **Screens at feature root** - `account_screen.dart`, `edit_account_screen.dart`
78
+ 3. **Create subfolders only when justified:**
79
+ - `widgets/` when 2+ reusable widgets exist
80
+ - `providers/` when 2+ provider files exist
81
+ - `domain/` usually always (models, repositories)
82
+ 4. **Split large features into subfeatures** - Each subfeature follows same rules
83
+
84
+ ### Example
85
+
86
+ ```
87
+ lib/features/
88
+ account/
89
+ account_screen.dart
90
+ edit_account_screen.dart
91
+ widgets/
92
+ account_avatar.dart
93
+ account_form.dart
94
+ providers/
95
+ account_provider.dart
96
+ domain/
97
+ account.dart
98
+ account_repository.dart
99
+ ```
100
+
101
+ ## Application Priority
102
+
103
+ When reviewing code, apply in this order:
104
+
105
+ 1. **Code Quality Patterns** (from fetched gist) - Anti-patterns, idioms, provider patterns
106
+ 2. **Widget Organization** (above) - Build structure, extraction rules, async UX
107
+ 3. **Folder Structure** (above) - File placement, feature boundaries
108
+
109
+ ## Anti-Patterns Quick Reference
110
+
111
+ Flag these patterns (details in fetched guidelines):
112
+
113
+ - `useState<bool>` for loading states
114
+ - Manual try-catch in provider actions
115
+ - `.toList()..sort()` instead of `.sorted()`
116
+ - `_handleAction(ref, controller, user, state)` with 4+ params
117
+ - Hardcoded hex colors
118
+ - Deep `lib/features/x/presentation/` directories
119
+ - Barrel files that only re-export
120
+ - Boolean flags instead of sealed classes
121
+ - Magic numbers scattered across widgets
122
+ - `where((e) => e.status == Status.active)` instead of computed property
123
+ - Generic provider names like `expansionVisibilityProvider`
124
+ - `.asData?.value` instead of `.value`
125
+
126
+ ## Output Format
127
+
128
+ Group by file. Use `file:line` format. Terse findings.
129
+
130
+ ```text
131
+ ## lib/home/home_screen.dart
132
+
133
+ lib/home/home_screen.dart:42 - useState for loading state -> use provider
134
+ lib/home/home_screen.dart:67 - .toList()..sort() -> .sorted()
135
+ lib/home/home_screen.dart:89 - hardcoded Color(0xFF...) -> context.color.*
136
+
137
+ ## lib/models/user.dart
138
+
139
+ pass
140
+ ```
141
+
142
+ State issue + location. Skip explanation unless fix non-obvious. No preamble.
@@ -0,0 +1,102 @@
1
+ ---
2
+ name: flutter-code-simplification
3
+ description: Flutter/Dart code simplification principles. Use when simplifying, refactoring, or cleaning up Flutter code for clarity and maintainability.
4
+ license: MIT
5
+ metadata:
6
+ author: forgeblast
7
+ version: "1.0.0"
8
+ date: January 2026
9
+ argument-hint: <file-or-pattern>
10
+ ---
11
+
12
+ # Flutter Code Simplification
13
+
14
+ Principles and patterns for simplifying Flutter/Dart code. Simplification means making code easier to reason about — not making it shorter at the cost of clarity.
15
+
16
+ ## Core Philosophy
17
+
18
+ **Clarity over brevity.** Explicit code is often better than compact code. The goal is code that's easy to read, understand, and maintain without changing what it does.
19
+
20
+ ## Principles
21
+
22
+ ### 1. Preserve Functionality
23
+
24
+ Never change what the code does — only how it does it. All original features, outputs, and behaviors must remain intact.
25
+
26
+ ### 2. Enhance Clarity
27
+
28
+ - Reduce unnecessary complexity and nesting
29
+ - Eliminate redundant code and abstractions
30
+ - Improve readability through clear naming
31
+ - Consolidate related logic and duplicates (DRY)
32
+ - Choose clarity over brevity — explicit code is often better than compact code
33
+
34
+ ### 3. Maintain Balance
35
+
36
+ Avoid over-simplification that could:
37
+ - Create overly clever solutions that are hard to understand
38
+ - Combine too many concerns into single functions/components
39
+ - Remove helpful abstractions that improve code organization
40
+ - Prioritize "fewer lines" over readability
41
+ - Make code harder to debug or extend
42
+
43
+ ### 4. Apply Judgment
44
+
45
+ Use expertise to determine what improves the code. These principles guide decisions — they are not a checklist. If a change doesn't clearly improve clarity while preserving behavior, don't make it.
46
+
47
+ ## Flutter Patterns
48
+
49
+ Common opportunities in Flutter/Dart code. Apply when they genuinely improve clarity.
50
+
51
+ ### State & Data
52
+
53
+ | Pattern | Simplification |
54
+ |---------|----------------|
55
+ | Scattered boolean flags | Sealed class variants with switch expressions (when it consolidates and clarifies) |
56
+ | Same parameters repeated across functions | Records or typed classes |
57
+ | Manual try-catch in providers | `AsyncValue.guard()` with centralized error handling |
58
+ | Async operations without mount check | Check `ref.mounted` after async operations |
59
+
60
+ ### Widget Structure
61
+
62
+ | Pattern | Simplification |
63
+ |---------|----------------|
64
+ | Large `build()` methods | Extract into local variables or builder methods |
65
+ | Widgets with many boolean parameters | Consider composition or typed mode objects |
66
+ | Unordered build() | Keep order: providers → hooks → derived values → widget tree |
67
+
68
+ ### Collections
69
+
70
+ | Pattern | Simplification |
71
+ |---------|----------------|
72
+ | Mutation patterns | Immutable methods (`.sorted()`, `.where()`, etc.) |
73
+ | Null-unsafe access | `firstWhereOrNull` with fallbacks |
74
+ | Repeated enum switches | Computed properties on the enum itself |
75
+
76
+ ### Code Organization
77
+
78
+ | Pattern | Simplification |
79
+ |---------|----------------|
80
+ | Duplicated logic across files | Extract to shared location |
81
+ | Related methods scattered in class | Group by concern |
82
+ | Unnecessary indirection (factories creating one type, wrappers adding no behavior) | Use concrete types directly |
83
+
84
+ **Exception:** API layer interfaces with implementation in same file are intentional — interface provides at-a-glance documentation.
85
+
86
+ ## Output Format
87
+
88
+ Group by file. Use `file:line` format. Terse findings.
89
+
90
+ ```text
91
+ ## lib/home/home_screen.dart
92
+
93
+ lib/home/home_screen.dart:42 - scattered booleans -> sealed class
94
+ lib/home/home_screen.dart:67 - .toList()..sort() -> .sorted()
95
+ lib/home/home_screen.dart:120 - large build() -> extract builder methods
96
+
97
+ ## lib/models/user.dart
98
+
99
+ pass
100
+ ```
101
+
102
+ State issue + location. Skip explanation unless fix non-obvious. No preamble.