@atlashub/smartstack-cli 3.19.0 → 3.21.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.
Files changed (35) hide show
  1. package/dist/index.js +53 -1
  2. package/dist/index.js.map +1 -1
  3. package/dist/mcp-entry.mjs +1 -0
  4. package/dist/mcp-entry.mjs.map +1 -1
  5. package/package.json +1 -1
  6. package/templates/agents/gitflow/cleanup.md +5 -1
  7. package/templates/agents/gitflow/finish.md +2 -0
  8. package/templates/agents/gitflow/init-clone.md +13 -0
  9. package/templates/agents/gitflow/init-validate.md +14 -0
  10. package/templates/agents/gitflow/status.md +6 -0
  11. package/templates/project/api.ts.template +8 -29
  12. package/templates/project/appsettings.json.template +1 -0
  13. package/templates/skills/business-analyse/html/ba-interactive.html +562 -150
  14. package/templates/skills/business-analyse/html/src/scripts/01-data-init.js +11 -6
  15. package/templates/skills/business-analyse/html/src/scripts/02-navigation.js +209 -4
  16. package/templates/skills/business-analyse/html/src/scripts/04-render-modules.js +2 -8
  17. package/templates/skills/business-analyse/html/src/scripts/05-render-specs.js +57 -2
  18. package/templates/skills/business-analyse/html/src/scripts/07-render-handoff.js +3 -1
  19. package/templates/skills/business-analyse/html/src/scripts/08-editing.js +112 -22
  20. package/templates/skills/business-analyse/html/src/scripts/11-review-panel.js +7 -0
  21. package/templates/skills/business-analyse/html/src/styles/02-layout.css +1 -1
  22. package/templates/skills/business-analyse/html/src/styles/03-navigation.css +89 -31
  23. package/templates/skills/business-analyse/html/src/styles/05-modules.css +64 -0
  24. package/templates/skills/business-analyse/html/src/template.html +8 -76
  25. package/templates/skills/business-analyse/references/deploy-data-build.md +9 -7
  26. package/templates/skills/business-analyse/references/html-data-mapping.md +20 -28
  27. package/templates/skills/business-analyse/references/validate-incremental-html.md +2 -1
  28. package/templates/skills/business-analyse/steps/step-02-decomposition.md +16 -3
  29. package/templates/skills/business-analyse/steps/step-03c-compile.md +55 -2
  30. package/templates/skills/business-analyse/steps/step-03d-validate.md +82 -15
  31. package/templates/skills/business-analyse/steps/step-05a-handoff.md +77 -3
  32. package/templates/skills/business-analyse/steps/step-05b-deploy.md +27 -0
  33. package/templates/skills/gitflow/_shared.md +65 -17
  34. package/templates/skills/gitflow/phases/status.md +8 -3
  35. package/templates/skills/gitflow/steps/step-start.md +3 -0
@@ -34,21 +34,50 @@ Validate the module specification for completeness and consistency, write to fea
34
34
 
35
35
  #### 9a. Completeness Checks
36
36
 
37
- | Section | Minimum | Status |
38
- |---------|---------|--------|
39
- | actors | 2 | PASS/FAIL |
40
- | useCases | 2 | PASS/FAIL |
41
- | functionalRequirements | 4 | PASS/FAIL |
42
- | permissionMatrix | 1 resource × 2 roles | PASS/FAIL |
43
- | entities | 1 | PASS/FAIL |
44
- | entitySchemaFormat | attributes[] not fields[] (BLOCKING) | PASS/FAIL |
45
- | wireframes | 1 per section (BLOCKING) | PASS/FAIL |
46
- | wireframeSchema | All required fields present (BLOCKING) | PASS/FAIL |
47
- | gherkinScenarios | 2 per UC | PASS/FAIL |
48
- | validations | 1 | PASS/FAIL |
49
- | messages | 4 | PASS/FAIL |
50
- | lifeCycles | 1 (if entity has status) | PASS/FAIL |
51
- | seedDataCore | 7 sections present | PASS/FAIL (BLOCKING) |
37
+ > **CRITICAL:** Checks MUST count ACTUAL elements in arrays, NOT declarative values.
38
+ > A check that reports PASS when the array is empty is a LIE and BLOCKS downstream quality.
39
+
40
+ | Section | Minimum | How to verify | Status |
41
+ |---------|---------|---------------|--------|
42
+ | actors | 2 | `specification.actors.length >= 2` | PASS/FAIL |
43
+ | useCases | 2 | `specification.useCases.length >= 2` | PASS/FAIL |
44
+ | functionalRequirements | 4 | `specification.functionalRequirements.length >= 4` | PASS/FAIL |
45
+ | permissionMatrix | 1 resource × 2 roles | `specification.permissionMatrix.permissions.length >= 1 && specification.permissionMatrix.roleAssignments.length >= 2` | PASS/FAIL |
46
+ | entities | 1 | `analysis.entities.length >= 1` | PASS/FAIL |
47
+ | entitySchemaFormat | attributes[] not fields[] (BLOCKING) | `analysis.entities.every(e => e.attributes?.length > 0)` | PASS/FAIL |
48
+ | entityAttributeTypes | ALL attributes have `type` field (BLOCKING) | `analysis.entities.every(e => e.attributes.every(a => a.type))` | PASS/FAIL |
49
+ | wireframes | 1 per section (BLOCKING) | `specification.wireframes.length >= specification.sections.length` (count REAL elements) | PASS/FAIL |
50
+ | wireframeSchema | All required fields present (BLOCKING) | `specification.wireframes.every(w => w.screen && w.section && (w.mockup \|\| w.content))` | PASS/FAIL |
51
+ | sections | 1 (BLOCKING) | `specification.sections.length >= 1` (EVERY module needs at least 1 section) | PASS/FAIL |
52
+ | gherkinScenarios | 1 array entry | `Array.isArray(specification.gherkinScenarios) && specification.gherkinScenarios.length >= 1` | PASS/FAIL |
53
+ | gherkinFormat | Array not object (BLOCKING) | `Array.isArray(specification.gherkinScenarios)` (NOT a single object) | PASS/FAIL |
54
+ | validations | 1 | `specification.validations.length >= 1` | PASS/FAIL |
55
+ | validationFormat | rules[] array (BLOCKING) | `specification.validations.every(v => Array.isArray(v.rules))` (NOT singular `rule`) | PASS/FAIL |
56
+ | messages | 4 | `specification.messages.length >= 4` | PASS/FAIL |
57
+ | messageFormat | `message` field present (BLOCKING) | `specification.messages.every(m => m.message)` | PASS/FAIL |
58
+ | lifeCycles | 1 (if entity has status) | `specification.lifeCycles.length >= 1` (if any entity has status/state field) | PASS/FAIL |
59
+ | seedDataCore | 7 arrays present with content | See detailed check below | PASS/FAIL (BLOCKING) |
60
+ | apiEndpoints | 1 | `specification.apiEndpoints.length >= 1` | PASS/FAIL |
61
+ | i18nKeys | present | `specification.i18nKeys !== undefined && specification.i18nKeys !== null` | PASS/FAIL |
62
+ | navigationIcons | non-null | `specification.seedDataCore.navigationModules.every(m => m.icon !== null)` | PASS/FAIL |
63
+
64
+ **seedDataCore detailed check (BLOCKING):**
65
+ ```javascript
66
+ const sdc = specification.seedDataCore;
67
+ const checks = [
68
+ { key: "navigationModules", actual: sdc.navigationModules?.length || 0, min: 1 },
69
+ { key: "navigationSections", actual: sdc.navigationSections?.length || 0, min: 1 }, // EVERY module needs ≥1 section
70
+ { key: "navigationResources", actual: sdc.navigationResources?.length || 0, min: 1 },
71
+ { key: "navigationTranslations", actual: sdc.navigationTranslations?.length || 0, min: 2 }, // min fr+en
72
+ { key: "permissions", actual: sdc.permissions?.length || 0, min: 1 },
73
+ { key: "rolePermissions", actual: sdc.rolePermissions?.length || 0, min: 1 },
74
+ { key: "permissionConstants", actual: sdc.permissionConstants?.length || 0, min: 1 }
75
+ ];
76
+ const failures = checks.filter(c => c.actual < c.min);
77
+ IF failures.length > 0:
78
+ BLOCKING ERROR: "seedDataCore incomplete — empty arrays: {failures.map(f => f.key).join(', ')}"
79
+ → Fix: Ensure specification.sections[] has ≥1 entry, then re-run 8f-bis transform
80
+ ```
52
81
 
53
82
  #### 9b. Consistency Checks
54
83
 
@@ -278,6 +307,44 @@ ba-writer.updateStatus({module_feature_id}, "specified")
278
307
  ba-writer.updateModuleStatus({feature_id}, {currentModule.code}, "specified")
279
308
  ```
280
309
 
310
+ #### 11-POST-CHECK: Verify Written Data (BLOCKING)
311
+
312
+ > **CRITICAL — Data loss prevention.** After writing, READ BACK the module feature.json and verify the following arrays are **non-empty**:
313
+
314
+ ```javascript
315
+ // READ BACK the written feature.json
316
+ const written = ba-reader.read({module_feature_id});
317
+
318
+ // BLOCKING checks — if ANY fails, the write was incomplete
319
+ const checks = [
320
+ { key: "specification.actors", actual: written.specification?.actors?.length, min: 2 },
321
+ { key: "specification.useCases", actual: written.specification?.useCases?.length, min: 2 },
322
+ { key: "specification.wireframes", actual: written.specification?.wireframes?.length, min: 1 },
323
+ { key: "specification.sections", actual: written.specification?.sections?.length, min: 1 },
324
+ { key: "specification.seedDataCore", actual: Object.keys(written.specification?.seedDataCore || {}).length, min: 7 },
325
+ { key: "specification.lifeCycles", actual: written.specification?.lifeCycles?.length, min: 0 },
326
+ { key: "specification.gherkinScenarios",actual: written.specification?.gherkinScenarios?.length,min: 1 },
327
+ { key: "specification.apiEndpoints", actual: written.specification?.apiEndpoints?.length, min: 1 }
328
+ ];
329
+
330
+ const failures = checks.filter(c => (c.actual || 0) < c.min);
331
+
332
+ IF failures.length > 0:
333
+ BLOCKING ERROR: "Feature.json write INCOMPLETE — missing data in: {failures.map(f => f.key).join(', ')}"
334
+ → Re-execute section 11 write with ALL specification data
335
+ → DO NOT proceed to next module until ALL checks pass
336
+
337
+ // SPECIAL CHECK: wireframes content verification
338
+ IF written.specification?.wireframes?.length > 0:
339
+ const emptyMockups = written.specification.wireframes.filter(wf => !wf.mockup && !wf.content);
340
+ IF emptyMockups.length > 0:
341
+ WARNING: "{emptyMockups.length} wireframes have empty mockup content — verify step-03b data"
342
+ ```
343
+
344
+ > **WHY:** Step-03b generates wireframes in memory, step-03c only writes seedDataCore explicitly.
345
+ > If the comprehensive write in section 11 is skipped or partial, wireframes/sections are LOST.
346
+ > This POST-CHECK catches the data loss BEFORE advancing to the next module.
347
+
281
348
  ---
282
349
 
283
350
  ### 11-bis. Deploy Incremental Interactive HTML (MANDATORY)
@@ -194,7 +194,7 @@ See [references/handoff-file-templates.md](../references/handoff-file-templates.
194
194
  |----------|--------|-----------|
195
195
  | **4.1 Domain** | `analysis.entities[]` | Entities, ValueObjects, Enums, Exceptions |
196
196
  | **4.2 Application** | `analysis.useCases[]` | Services, DTOs, Validators |
197
- | **4.3 Infrastructure** | `analysis.entities[]` | EF Configurations, DbSet, DI |
197
+ | **4.3 Infrastructure** | `analysis.entities[]` | EF Configurations, DbSet, DI. **DEPENDENCY:** Each EF config task MUST `dependsOn` its corresponding domain entity task. |
198
198
  | **4.4 API** | `specification.apiEndpoints[]` | Controllers with `{ContextShort}` mapping |
199
199
  | **4.5 Frontend** | `specification.uiWireframes[]` | Pages, Components, Hooks + wireframe traceability |
200
200
  | **4.6 SeedData** | `specification.seedDataCore` | 5 CORE + business + IClientSeedDataProvider |
@@ -204,6 +204,7 @@ See [references/handoff-file-templates.md](../references/handoff-file-templates.
204
204
  - All backend paths include `{ContextPascal}/{ApplicationName}/` hierarchy
205
205
  - Frontend pages MUST have `linkedWireframes[]` + `wireframeAcceptanceCriteria`
206
206
  - SeedData: 5 CORE entries ALWAYS + IClientSeedDataProvider for client projects
207
+ - **Acceptance Criteria Mapping:** Each task's `acceptanceCriteria` MUST be derived from its own `linkedFRs[]` entries (lookup FR → `acceptanceCriteria`). NEVER map by sequential FR index — use the task's explicit linkedFRs to resolve the correct criteria.
207
208
  - Path convention: `Persistence/Seeding/Data/` (NEVER `Data/SeedData/`)
208
209
 
209
210
  ---
@@ -228,6 +229,52 @@ For each endpoint: operation, method, route, linkedUC, linkedFR, permissions, re
228
229
 
229
230
  Total endpoints = count of specification.apiEndpoints[] across all modules.
230
231
 
232
+ ### 6b. Frontend Task Splitting (MANDATORY)
233
+
234
+ > **CRITICAL:** Frontend MUST be split into discrete tasks, NOT bundled as a single mega-task.
235
+ > A single mega-task covering 5-8 files prevents granular error recovery in ralph-loop.
236
+
237
+ For each module, generate these SEPARATE frontend file entries (not one monolithic entry):
238
+
239
+ ```json
240
+ "frontend": [
241
+ { "path": "src/pages/{Mod}/{ListPage}Page.tsx", "type": "Page", "linkedWireframes": ["{module}-list"], "module": "{moduleCode}" },
242
+ { "path": "src/pages/{Mod}/{DetailPage}Page.tsx", "type": "Page", "linkedWireframes": ["{module}-detail"], "module": "{moduleCode}" },
243
+ { "path": "src/hooks/use{Module}.ts", "type": "Hook", "module": "{moduleCode}" },
244
+ { "path": "src/i18n/{module}.json", "type": "I18n", "module": "{moduleCode}" }
245
+ ]
246
+ ```
247
+
248
+ Additional per-section pages (dashboard, import, etc.) are separate entries.
249
+
250
+ **Route wiring task (MANDATORY — separate entry):**
251
+ ```json
252
+ { "path": "src/App.tsx", "type": "RouteWiring", "module": "{moduleCode}", "description": "Wire {module} routes into App.tsx Layout wrappers (standard + tenant-prefixed blocks). BLOCKING: pages without route wiring = blank pages." }
253
+ ```
254
+
255
+ ### 6c. Cross-Module PRD (MANDATORY for multi-module features)
256
+
257
+ > **CRITICAL:** Cross-module integration tasks MUST exist in a dedicated PRD file.
258
+ > Without this, ralph-loop will NOT execute E2E tests or FK validation.
259
+
260
+ Generate `.ralph/prd-CrossModule.json` with:
261
+ ```json
262
+ {
263
+ "$version": "3.0.0",
264
+ "implementation": {
265
+ "filesToCreate": {
266
+ "tests": [
267
+ { "path": "src/Tests/Integration/CrossModule/ForeignKeyValidationTests.cs", "type": "IntegrationTests", "module": "CrossModule" },
268
+ { "path": "src/Tests/Integration/CrossModule/PermissionMatrixTests.cs", "type": "SecurityTests", "module": "CrossModule" },
269
+ { "path": "src/Tests/Integration/CrossModule/E2EFlowTests.cs", "type": "E2ETests", "module": "CrossModule" }
270
+ ]
271
+ }
272
+ }
273
+ }
274
+ ```
275
+
276
+ Add to progress.txt after all module tasks.
277
+
231
278
  ---
232
279
 
233
280
  ### 7. Write Handoff to Feature.json
@@ -317,7 +364,7 @@ const seedDataCore = {
317
364
  code: m.code,
318
365
  label: m.name || m.code,
319
366
  description: m.description,
320
- icon: null, // set by ralph-loop
367
+ icon: inferIconFromModule(m) || "folder", // MUST be non-null — use sensible default
321
368
  iconType: "lucide",
322
369
  route: `/business/${master.metadata.application.toLowerCase()}/${m.code.toLowerCase()}`,
323
370
  displayOrder: (i + 1) * 10
@@ -386,6 +433,28 @@ const seedDataCore = {
386
433
  )
387
434
  };
388
435
 
436
+ // Icon inference from module context (NEVER leave as null)
437
+ function inferIconFromModule(module) {
438
+ const codeLC = module.code.toLowerCase();
439
+ const iconMap = {
440
+ 'employee': 'users', 'staff': 'users', 'personnel': 'users', 'user': 'users',
441
+ 'project': 'folder-kanban', 'task': 'check-square', 'work': 'briefcase',
442
+ 'time': 'clock', 'schedule': 'calendar', 'planning': 'calendar-days',
443
+ 'absence': 'calendar-off', 'leave': 'calendar-off', 'vacation': 'palm-tree',
444
+ 'finance': 'wallet', 'billing': 'receipt', 'invoice': 'file-text',
445
+ 'inventory': 'warehouse', 'stock': 'package', 'product': 'shopping-bag',
446
+ 'customer': 'building', 'client': 'building-2', 'contact': 'contact',
447
+ 'report': 'bar-chart', 'dashboard': 'layout-dashboard', 'analytics': 'trending-up',
448
+ 'document': 'file-text', 'notification': 'bell', 'setting': 'settings',
449
+ 'order': 'shopping-cart', 'vehicle': 'car', 'maintenance': 'wrench',
450
+ 'audit': 'shield-check', 'workflow': 'git-branch', 'approval': 'check-circle'
451
+ };
452
+ for (const [keyword, icon] of Object.entries(iconMap)) {
453
+ if (codeLC.includes(keyword)) return icon;
454
+ }
455
+ return "folder"; // fallback — NEVER null
456
+ }
457
+
389
458
  ba-writer.enrichSection({
390
459
  featureId: {feature_id},
391
460
  section: "seedDataCore",
@@ -393,12 +462,17 @@ ba-writer.enrichSection({
393
462
  })
394
463
  ```
395
464
 
396
- **POST-CHECK (non-blocking):**
465
+ **POST-CHECK (BLOCKING for icons, warning for counts):**
397
466
  ```
398
467
  IF seedDataCore.navigationModules.length !== master.modules.length:
399
468
  WARNING: seedDataCore has ${seedDataCore.navigationModules.length} nav modules but ${master.modules.length} modules exist
400
469
  IF seedDataCore.permissions.length === 0:
401
470
  WARNING: seedDataCore has 0 permissions — applicationRoles may be missing
471
+ IF seedDataCore.navigationModules.some(m => !m.icon || m.icon === null):
472
+ BLOCKING ERROR: "Navigation modules with null icons detected — ralph-loop will render empty navigation"
473
+ → Fix: Re-run inferIconFromModule() for modules with null icons
474
+ IF seedDataCore.navigationSections.length === 0:
475
+ WARNING: "0 navigation sections — every module should have at least 1 section (e.g., 'list')"
402
476
  ```
403
477
 
404
478
  #### 7c. Master Handoff (after ALL modules written + seedDataCore generated)
@@ -357,6 +357,33 @@ After writing the HTML file, verify:
357
357
  BLOCKING_ERROR("Module mismatch: expected [${expectedModules}] but wireframes has [${wireframeModules}]")
358
358
  ```
359
359
 
360
+ 9. **MODULE DATA COMPLETENESS** — every module in FEATURE_DATA.modules[] must carry ALL fields from feature.json
361
+ ```
362
+ FOR each htmlModule in FEATURE_DATA.modules[]:
363
+ masterModule = master.modules.find(m => m.code === htmlModule.code)
364
+
365
+ // name MUST be display name, NOT code
366
+ IF htmlModule.name === htmlModule.code AND masterModule.name !== masterModule.code:
367
+ BLOCKING_ERROR("Module '${htmlModule.code}' has name === code. Expected: '${masterModule.name}'")
368
+
369
+ // anticipatedSections MUST be present (drives Structure tab + navigation tree)
370
+ IF masterModule.anticipatedSections?.length > 0 AND (!htmlModule.anticipatedSections OR htmlModule.anticipatedSections.length === 0):
371
+ BLOCKING_ERROR("Module '${htmlModule.code}' lost anticipatedSections: master has ${masterModule.anticipatedSections.length}, HTML has 0")
372
+ → FIX: Copy anticipatedSections from master.modules[].anticipatedSections
373
+
374
+ // dependencies/dependents MUST be present (drives dependency graph)
375
+ IF masterModule.dependencies?.length > 0 AND (!htmlModule.dependencies OR htmlModule.dependencies.length === 0):
376
+ BLOCKING_ERROR("Module '${htmlModule.code}' lost dependencies array")
377
+ ```
378
+
379
+ Display verification table:
380
+ ```
381
+ POST-CHECK: Module data completeness
382
+ | Module | name | sections | deps | wireframes | specs | Match |
383
+ |----------------|---------|----------|------|------------|-------|-------|
384
+ | {code} | {name} | {n} | {n} | {n} | OK | OK/FAIL |
385
+ ```
386
+
360
387
  **IF ANY CHECK FAILS → DO NOT PROCEED. Fix the data mapping and regenerate.**
361
388
 
362
389
  ---
@@ -57,32 +57,34 @@ normalize_path_for_platform() {
57
57
  fi
58
58
  ;;
59
59
  *)
60
- # Normalize backslashes to forward slashes
61
- echo "${input_path//\\//}"
62
- ;;
63
- esac
64
- }
65
-
66
- # Translates a path to storage-neutral format for config.json (forward slashes, drive letter)
67
- normalize_path_for_storage() {
68
- local input_path="$1"
69
- case "$GF_PLATFORM" in
70
- wsl)
71
- # WSL path → Windows-style for config: /mnt/d/foo/bar → D:/foo/bar
60
+ # WSL path Windows path: /mnt/d/foo → D:/foo
72
61
  if [[ "$input_path" =~ ^/mnt/([a-z])/(.*) ]]; then
73
62
  local drive=$(echo "${BASH_REMATCH[1]}" | tr '[:lower:]' '[:upper:]')
74
63
  echo "${drive}:/${BASH_REMATCH[2]}"
75
64
  else
76
- echo "$input_path"
65
+ # Normalize backslashes to forward slashes
66
+ echo "${input_path//\\//}"
77
67
  fi
78
68
  ;;
79
- *)
80
- # Normalize backslashes to forward slashes
81
- echo "${input_path//\\//}"
82
- ;;
83
69
  esac
84
70
  }
85
71
 
72
+ # Translates a path to storage-neutral format for config.json (forward slashes, drive letter)
73
+ # Always converts WSL paths to Windows-style, regardless of current platform
74
+ normalize_path_for_storage() {
75
+ local input_path="$1"
76
+ # WSL path → Windows-style: /mnt/d/foo → D:/foo (always, regardless of platform)
77
+ if [[ "$input_path" =~ ^/mnt/([a-z])/(.*) ]]; then
78
+ local drive=$(echo "${BASH_REMATCH[1]}" | tr '[:lower:]' '[:upper:]')
79
+ echo "${drive}:/${BASH_REMATCH[2]}"
80
+ elif [[ "$input_path" =~ ^[A-Za-z]:[\\/] ]]; then
81
+ # Normalize backslashes only
82
+ echo "${input_path//\\//}"
83
+ else
84
+ echo "$input_path"
85
+ fi
86
+ }
87
+
86
88
  # Returns current directory in platform-appropriate format
87
89
  get_current_dir() {
88
90
  local raw_pwd
@@ -314,6 +316,52 @@ cleanup_worktree_for_branch() {
314
316
 
315
317
  ---
316
318
 
319
+ ## REPAIR_WORKTREE_PATHS
320
+
321
+ Repairs worktree metadata files that contain paths from a different platform (e.g., WSL paths `/mnt/d/...` on Windows, or Windows paths `D:/...` on WSL). Scans `.bare/worktrees/*/gitdir` and the corresponding `{worktree}/.git` files.
322
+
323
+ ```bash
324
+ repair_worktree_paths() {
325
+ local BARE_DIR="$1" # Path to .bare directory
326
+ local WORKTREES_DIR="$BARE_DIR/worktrees"
327
+ [ ! -d "$WORKTREES_DIR" ] && return 0
328
+
329
+ local REPAIRED=0
330
+ for wt_dir in "$WORKTREES_DIR"/*/; do
331
+ [ ! -d "$wt_dir" ] && continue
332
+ local wt_name=$(basename "$wt_dir")
333
+ local gitdir_file="$wt_dir/gitdir"
334
+ [ ! -f "$gitdir_file" ] && continue
335
+
336
+ # Fix .bare/worktrees/{name}/gitdir
337
+ local stored_path=$(cat "$gitdir_file" | tr -d '\n\r')
338
+ local fixed_path=$(normalize_path_for_platform "$stored_path")
339
+ if [ "$stored_path" != "$fixed_path" ]; then
340
+ echo "$fixed_path" > "$gitdir_file"
341
+ echo "REPAIRED: $wt_name/gitdir → $fixed_path"
342
+ REPAIRED=$((REPAIRED + 1))
343
+ fi
344
+
345
+ # Fix the worktree's .git file
346
+ local wt_real_dir=$(dirname "$fixed_path")
347
+ if [ -f "$wt_real_dir/.git" ]; then
348
+ local git_content=$(cat "$wt_real_dir/.git" | tr -d '\n\r')
349
+ local git_path="${git_content#gitdir: }"
350
+ local fixed_git_path=$(normalize_path_for_platform "$git_path")
351
+ if [ "$git_path" != "$fixed_git_path" ]; then
352
+ echo "gitdir: $fixed_git_path" > "$wt_real_dir/.git"
353
+ echo "REPAIRED: $wt_name/.git → $fixed_git_path"
354
+ REPAIRED=$((REPAIRED + 1))
355
+ fi
356
+ fi
357
+ done
358
+
359
+ [ "$REPAIRED" -gt 0 ] && echo "Worktree paths: $REPAIRED file(s) repaired"
360
+ }
361
+ ```
362
+
363
+ ---
364
+
317
365
  ## RESOLVE_WORKSPACE
318
366
 
319
367
  Resolves the workspace JSON file by walking up the directory tree.
@@ -61,15 +61,20 @@ HOTFIXES=$(git branch -r | grep "origin/${GF_HOTFIX_PREFIX}" | sed 's/origin\///
61
61
  ### 4. Worktree Status
62
62
 
63
63
  ```bash
64
+ # Repair cross-platform paths before listing (WSL ↔ Windows)
65
+ detect_platform
66
+ repair_worktree_paths "$(git rev-parse --git-common-dir 2>/dev/null || echo '.bare')"
67
+
64
68
  WORKTREES=$(git worktree list)
65
69
  WORKTREE_COUNT=$(echo "$WORKTREES" | wc -l)
66
70
 
67
- # Check for orphaned worktrees
71
+ # Check for orphaned worktrees (normalize paths for cross-platform compatibility)
68
72
  ORPHANED=""
69
73
  while read -r line; do
70
- PATH=$(echo "$line" | awk '{print $1}')
74
+ WT_PATH=$(echo "$line" | awk '{print $1}')
75
+ WT_PATH=$(normalize_path_for_platform "$WT_PATH")
71
76
  BRANCH=$(echo "$line" | awk '{print $3}' | tr -d '[]')
72
- [ ! -d "$PATH" ] && ORPHANED="$ORPHANED\n$PATH ($BRANCH)"
77
+ [ ! -d "$WT_PATH" ] && ORPHANED="$ORPHANED\n$WT_PATH ($BRANCH)"
73
78
  done <<< "$WORKTREES"
74
79
  ```
75
80
 
@@ -158,6 +158,9 @@ esac
158
158
 
159
159
  mkdir -p "$(dirname $WORKTREE_PATH)"
160
160
  git worktree add -b "$FULL_BRANCH" "$WORKTREE_PATH" "origin/$BASE_BRANCH"
161
+
162
+ # Fix cross-platform paths in worktree metadata (if created from different platform)
163
+ repair_worktree_paths "$(git rev-parse --git-common-dir)"
161
164
  ```
162
165
 
163
166
  **Mode: --no-worktree**