@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.
- package/dist/index.js +53 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-entry.mjs +1 -0
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +1 -1
- package/templates/agents/gitflow/cleanup.md +5 -1
- package/templates/agents/gitflow/finish.md +2 -0
- package/templates/agents/gitflow/init-clone.md +13 -0
- package/templates/agents/gitflow/init-validate.md +14 -0
- package/templates/agents/gitflow/status.md +6 -0
- package/templates/project/api.ts.template +8 -29
- package/templates/project/appsettings.json.template +1 -0
- package/templates/skills/business-analyse/html/ba-interactive.html +562 -150
- package/templates/skills/business-analyse/html/src/scripts/01-data-init.js +11 -6
- package/templates/skills/business-analyse/html/src/scripts/02-navigation.js +209 -4
- package/templates/skills/business-analyse/html/src/scripts/04-render-modules.js +2 -8
- package/templates/skills/business-analyse/html/src/scripts/05-render-specs.js +57 -2
- package/templates/skills/business-analyse/html/src/scripts/07-render-handoff.js +3 -1
- package/templates/skills/business-analyse/html/src/scripts/08-editing.js +112 -22
- package/templates/skills/business-analyse/html/src/scripts/11-review-panel.js +7 -0
- package/templates/skills/business-analyse/html/src/styles/02-layout.css +1 -1
- package/templates/skills/business-analyse/html/src/styles/03-navigation.css +89 -31
- package/templates/skills/business-analyse/html/src/styles/05-modules.css +64 -0
- package/templates/skills/business-analyse/html/src/template.html +8 -76
- package/templates/skills/business-analyse/references/deploy-data-build.md +9 -7
- package/templates/skills/business-analyse/references/html-data-mapping.md +20 -28
- package/templates/skills/business-analyse/references/validate-incremental-html.md +2 -1
- package/templates/skills/business-analyse/steps/step-02-decomposition.md +16 -3
- package/templates/skills/business-analyse/steps/step-03c-compile.md +55 -2
- package/templates/skills/business-analyse/steps/step-03d-validate.md +82 -15
- package/templates/skills/business-analyse/steps/step-05a-handoff.md +77 -3
- package/templates/skills/business-analyse/steps/step-05b-deploy.md +27 -0
- package/templates/skills/gitflow/_shared.md +65 -17
- package/templates/skills/gitflow/phases/status.md +8 -3
- 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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
|
41
|
-
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
|
|
|
45
|
-
|
|
|
46
|
-
|
|
|
47
|
-
|
|
|
48
|
-
|
|
|
49
|
-
|
|
|
50
|
-
|
|
|
51
|
-
|
|
|
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:
|
|
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 (
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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 "$
|
|
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**
|