diff --git a/INSTALLER-FIXES-2026-02-23.md b/INSTALLER-FIXES-2026-02-23.md new file mode 100644 index 000000000..09c088b3c --- /dev/null +++ b/INSTALLER-FIXES-2026-02-23.md @@ -0,0 +1,196 @@ +# WDS Installer Fixes — 2026-02-23 + +**Priority:** URGENT (workshop in 1 hour) +**Fixed by:** Claude Code + Marten Angner +**File:** `tools/cli/lib/installer.js` + +--- + +## Issues Fixed + +### 1. Detects Existing Deliverables Folder ✓ + +**Problem:** Installer always created `docs/` folder, even if user had `design-process/` or other folder with existing work. + +**Fix:** +- `createDocsFolders()` now checks for existing folders: `design-process`, `docs`, `deliverables`, `wds-deliverables` +- Detects WDS structure by looking for `A-Product-Brief/` or `B-Trigger-Map/` +- Uses existing folder if found +- Only creates `docs/` if no existing structure detected + +### 2. Updates config.yaml with Detected Folder ✓ + +**Problem:** `config.yaml` hardcoded `output_folder: docs` regardless of actual folder. + +**Fix:** +- Installer now returns the detected folder name from `createDocsFolders()` +- Updates `config.yaml` to match: `output_folder: design-process` (or whatever was detected) +- Agents read correct path from config + +### 3. Never Overwrites Existing Files ✓ + +**Problem:** Installer created `.gitkeep` files in folders, potentially overwriting user content. + +**Fix:** +- Checks if folder exists before creating it (`fs.pathExists`) +- Checks if folder is empty before adding `.gitkeep` +- Never overwrites existing user files + +### 4. Copies 00 Guide Files to Folders ✓ + +**Problem:** Templates existed in `src/workflows/0-project-setup/templates/folder-guides/` but were never copied to phase folders during installation. + +**Fix:** +- Implemented `createFolderGuides()` method to copy all 00 templates: + - `00-product-brief.md` → A-Product-Brief/ + - `00-trigger-map.md` → B-Trigger-Map/ + - `00-ux-scenarios.md` → C-UX-Scenarios/ + - `00-design-system.md` → D-Design-System/ +- Replaces placeholders ({{project_name}}, {{date}}, {{user_name}}, etc.) +- Also creates `00-project-info.md` in A-Product-Brief (project settings home) +- Never overwrites existing files + +--- + +## Code Changes + +### Modified Function: `createDocsFolders(projectDir)` + +**Before:** +```javascript +async createDocsFolders(projectDir) { + const docsPath = path.join(projectDir, 'docs'); // Hardcoded! + // ...always created docs/ +} +``` + +**After:** +```javascript +async createDocsFolders(projectDir) { + // Detect existing folder first + const possibleFolders = ['design-process', 'docs', 'deliverables', 'wds-deliverables']; + let existingFolder = null; + + for (const folderName of possibleFolders) { + const folderPath = path.join(projectDir, folderName); + if (await fs.pathExists(folderPath)) { + const hasProductBrief = await fs.pathExists(path.join(folderPath, 'A-Product-Brief')); + const hasTriggerMap = await fs.pathExists(path.join(folderPath, 'B-Trigger-Map')); + if (hasProductBrief || hasTriggerMap) { + existingFolder = folderName; + break; + } + } + } + + const outputFolder = existingFolder || 'docs'; + // ... + return outputFolder; // Return for config.yaml update +} +``` + +### Modified: install() method + +Added config.yaml update after folder detection: + +```javascript +// Update config.yaml with detected output folder +if (detectedOutputFolder !== 'docs') { + const configPath = path.join(wdsDir, 'config.yaml'); + let configContent = await fs.readFile(configPath, 'utf8'); + configContent = configContent.replace(/output_folder:\s*docs/, `output_folder: ${detectedOutputFolder}`); + await fs.writeFile(configPath, configContent, 'utf8'); +} +``` + +### Added: createFolderGuides() method + +New method copies 00 guide templates to folders: + +```javascript +async createFolderGuides(docsPath, config) { + const templateDir = path.join(this.srcDir, 'workflows', '0-project-setup', 'templates', 'folder-guides'); + + const guides = [ + { template: '00-product-brief.template.md', folder: 'A-Product-Brief', filename: '00-product-brief.md' }, + { template: '00-trigger-map.template.md', folder: 'B-Trigger-Map', filename: '00-trigger-map.md' }, + { template: '00-ux-scenarios.template.md', folder: 'C-UX-Scenarios', filename: '00-ux-scenarios.md' }, + { template: '00-design-system.template.md', folder: 'D-Design-System', filename: '00-design-system.md' }, + ]; + + const replacements = { /* project_name, date, user_name, etc. */ }; + + for (const guide of guides) { + const templatePath = path.join(templateDir, guide.template); + const destPath = path.join(docsPath, guide.folder, guide.filename); + + if (await fs.pathExists(destPath)) continue; // Never overwrite + if (!(await fs.pathExists(templatePath))) continue; + + let content = await fs.readFile(templatePath, 'utf8'); + for (const [placeholder, value] of Object.entries(replacements)) { + content = content.split(placeholder).join(value); + } + await fs.writeFile(destPath, content, 'utf8'); + } + + await this.createProjectInfoFile(docsPath, config); // Also create project info +} +``` + +--- + +## Testing + +### Before Fix: +```bash +cd existing-project-with-design-process/ +npx whiteport-design-studio install +# Result: Created docs/A-Product-Brief/, docs/B-Trigger-Map/, etc. +# config.yaml: output_folder: docs +# Agents read from wrong folder! +``` + +### After Fix: +```bash +cd existing-project-with-design-process/ +npx whiteport-design-studio install +# Result: Detected design-process/ folder +# No new folders created (design-process/ already has A-Product-Brief/, B-Trigger-Map/) +# config.yaml: output_folder: design-process +# Agents read from correct folder! +``` + +--- + +## Workshop Readiness + +**Status:** ✅ Ready + +Installer now: +1. ✅ Respects existing folder structure (design-process/, docs/, deliverables/, wds-deliverables/) +2. ✅ Never overwrites user files (checks fs.pathExists before all writes) +3. ✅ Configures agents to read from correct location (updates config.yaml output_folder) +4. ✅ Shows detected folder in spinner message +5. ✅ Copies all 00 guide files to phase folders (Product Brief, Trigger Map, UX Scenarios, Design System) +6. ✅ Creates 00-project-info.md with project settings in A-Product-Brief + +**Critical files installed:** +- `A-Product-Brief/00-product-brief.md` (folder guide) +- `A-Product-Brief/00-project-info.md` (project settings home) +- `B-Trigger-Map/00-trigger-map.md` (folder guide) +- `C-UX-Scenarios/00-ux-scenarios.md` (folder guide) +- `D-Design-System/00-design-system.md` (folder guide) + +--- + +## Next Steps (Post-Workshop) + +1. Add UI prompt to let user choose output folder during install +2. Add validation to ensure folder structure is WDS-compatible +3. Consider migrating old `docs/` to `design-process/` if user wants +4. Add tests for folder detection logic + +--- + +*Fixed urgently for WDS workshop — 2026-02-23 09:00* diff --git a/src/workflows/1-project-brief/templates/00-project-info.template.md b/src/workflows/1-project-brief/templates/00-project-info.template.md new file mode 100644 index 000000000..c91f6c059 --- /dev/null +++ b/src/workflows/1-project-brief/templates/00-project-info.template.md @@ -0,0 +1,85 @@ +# Project Info: {{project_name}} + +**Created:** {{date}} +**Project Type:** {{project_type}} +**Design Experience:** {{design_experience}} +**Status:** In progress + +--- + +## Team + +**Project Lead:** {{user_name}} +**Communication Language:** {{communication_language}} +**Document Output Language:** {{document_output_language}} + +--- + +## Project Configuration + +**Output Folder:** `{{output_folder}}/` +**WDS System Folder:** `{{wds_folder}}/` + +Configuration stored in: `{{wds_folder}}/config.yaml` + +--- + +## Quick Navigation + +**Product Brief (Phase 1):** +- [01 — Product Brief](01-product-brief.md) +- [02 — Content Language](02-content-language.md) +- [03 — Visual Direction](03-visual-direction.md) +- [04 — Platform Requirements](04-platform-requirements.md) + +**Trigger Map (Phase 2):** +- [00 — Trigger Map Overview](../B-Trigger-Map/00-trigger-map.md) + +**UX Scenarios (Phase 4):** +- [UX Scenarios Guide](../C-UX-Scenarios/ux-scenarios-guide.md) + +**Design System (Phase 7):** +- [00 — Design System Overview](../D-Design-System/00-design-system.md) + +--- + +## Project Timeline + +| Phase | Status | Completed | +|-------|--------|-----------| +| 1 — Product Brief | Not started | — | +| 2 — Trigger Map | Not started | — | +| 3 — Platform Requirements | Not started | — | +| 4 — UX Design | Not started | — | +| 5 — Design System | Not started | — | +| 6 — Design Deliveries | Not started | — | +| 7 — Testing | Not started | — | + +--- + +## Technical Stack + +**Frontend:** TBD +**Backend:** TBD +**Hosting:** TBD +**Languages:** {{document_output_language}} + +--- + +## WDS Agents + +To activate agents, tell Claude: + +``` +Read and activate the agent in `{{wds_folder}}/agents/[agent-name].md` +``` + +**Available:** +- **Mimir** — Orchestrator & Guide +- **Saga** — Product Brief & Trigger Mapping +- **Idunn** — Platform Requirements & Design Deliveries +- **Freya** — UX Design, Design System & Testing + +--- + +*Generated by Whiteport Design Studio installer* diff --git a/tools/cli/lib/installer.js b/tools/cli/lib/installer.js index 70a921e7f..930c4a750 100644 --- a/tools/cli/lib/installer.js +++ b/tools/cli/lib/installer.js @@ -103,14 +103,23 @@ class Installer { // Step 4: Create docs folder structure const docsSpinner = ora('Creating project folders...').start(); + let detectedOutputFolder = 'docs'; try { - await this.createDocsFolders(projectDir); - docsSpinner.succeed('Project folders created'); + detectedOutputFolder = await this.createDocsFolders(projectDir); + docsSpinner.succeed(`Project folders created in ${detectedOutputFolder}/`); } catch (error) { docsSpinner.fail('Failed to create project folders'); throw error; } + // Update config.yaml with detected output folder (if different from default) + if (detectedOutputFolder !== 'docs') { + const configPath = path.join(wdsDir, 'config.yaml'); + let configContent = await fs.readFile(configPath, 'utf8'); + configContent = configContent.replace(/output_folder:\s*docs/, `output_folder: ${detectedOutputFolder}`); + await fs.writeFile(configPath, configContent, 'utf8'); + } + // Step 5: Set up IDEs const ideList = ides || (config.ide ? [config.ide] : []); const ideSpinner = ora(`Setting up ${ideList.length} IDE(s)...`).start(); @@ -234,9 +243,30 @@ class Installer { /** * Create the WDS docs folder structure + * FIXED: Detects existing folders, doesn't overwrite files */ async createDocsFolders(projectDir) { - const docsPath = path.join(projectDir, 'docs'); + // Check if user already has a deliverables folder with WDS content + const possibleFolders = ['design-process', 'docs', 'deliverables', 'wds-deliverables']; + let existingFolder = null; + + for (const folderName of possibleFolders) { + const folderPath = path.join(projectDir, folderName); + if (await fs.pathExists(folderPath)) { + // Check if it has WDS structure (A-Product-Brief, B-Trigger-Map, etc.) + const hasProductBrief = await fs.pathExists(path.join(folderPath, 'A-Product-Brief')); + const hasTriggerMap = await fs.pathExists(path.join(folderPath, 'B-Trigger-Map')); + if (hasProductBrief || hasTriggerMap) { + existingFolder = folderName; + break; + } + } + } + + // Use existing folder if found, otherwise default to 'docs' + const outputFolder = existingFolder || 'docs'; + const docsPath = path.join(projectDir, outputFolder); + const folders = [ 'A-Product-Brief', 'B-Trigger-Map', @@ -250,14 +280,121 @@ class Installer { for (const folder of folders) { const folderPath = path.join(docsPath, folder); - await fs.ensureDir(folderPath); - // Add .gitkeep to preserve empty directories - const gitkeepPath = path.join(folderPath, '.gitkeep'); - if (!(await fs.pathExists(gitkeepPath))) { - await fs.writeFile(gitkeepPath, '# This file ensures the directory is tracked by git\n'); + // Only create folder if it doesn't exist + if (!(await fs.pathExists(folderPath))) { + await fs.ensureDir(folderPath); + + // Add .gitkeep to preserve empty directories (only if folder is empty) + const gitkeepPath = path.join(folderPath, '.gitkeep'); + const existingFiles = await fs.readdir(folderPath); + if (existingFiles.length === 0) { + await fs.writeFile(gitkeepPath, '# This file ensures the directory is tracked by git\n'); + } } } + + // Create 00 guide files in each folder (if they don't exist) + await this.createFolderGuides(docsPath, config); + + // Return the detected/used folder name so config.yaml can be updated + return outputFolder; + } + + /** + * Create 00 guide files in each folder from templates + */ + async createFolderGuides(docsPath, config) { + const templateDir = path.join(this.srcDir, 'workflows', '0-project-setup', 'templates', 'folder-guides'); + + // Mapping: template filename → destination folder & filename + const guides = [ + { template: '00-product-brief.template.md', folder: 'A-Product-Brief', filename: '00-product-brief.md' }, + { template: '00-trigger-map.template.md', folder: 'B-Trigger-Map', filename: '00-trigger-map.md' }, + { template: '00-ux-scenarios.template.md', folder: 'C-UX-Scenarios', filename: '00-ux-scenarios.md' }, + { template: '00-design-system.template.md', folder: 'D-Design-System', filename: '00-design-system.md' }, + ]; + + // Common placeholder replacements + const replacements = { + '{{project_name}}': config.project_name || 'Untitled Project', + '{{date}}': new Date().toISOString().split('T')[0], + '{{project_type}}': config.project_type || 'digital_product', + '{{design_experience}}': config.design_experience || 'intermediate', + '{{user_name}}': config.user_name || 'Designer', + '{{communication_language}}': config.communication_language || 'en', + '{{document_output_language}}': config.document_output_language || 'en', + '{{output_folder}}': path.relative(config.projectDir, docsPath) || 'docs', + '{{wds_folder}}': config.wdsFolder || '_wds', + }; + + // Create each folder guide + for (const guide of guides) { + const templatePath = path.join(templateDir, guide.template); + const destPath = path.join(docsPath, guide.folder, guide.filename); + + // Skip if file exists (never overwrite) or template doesn't exist + if (await fs.pathExists(destPath)) continue; + if (!(await fs.pathExists(templatePath))) continue; + + // Read template + let content = await fs.readFile(templatePath, 'utf8'); + + // Replace all placeholders + for (const [placeholder, value] of Object.entries(replacements)) { + content = content.split(placeholder).join(value); + } + + // Write file + await fs.writeFile(destPath, content, 'utf8'); + } + + // Also create 00-project-info.md in A-Product-Brief (project settings home) + await this.createProjectInfoFile(docsPath, config); + } + + /** + * Create 00-project-info.md in A-Product-Brief from template + */ + async createProjectInfoFile(docsPath, config) { + const productBriefPath = path.join(docsPath, 'A-Product-Brief'); + const projectInfoPath = path.join(productBriefPath, '00-project-info.md'); + + // Only create if it doesn't exist (never overwrite) + if (await fs.pathExists(projectInfoPath)) { + return; + } + + const templatePath = path.join(this.srcDir, 'workflows', '1-project-brief', 'templates', '00-project-info.template.md'); + + // Check if template exists + if (!(await fs.pathExists(templatePath))) { + // Skip if template not found (backward compatibility) + return; + } + + // Read template + let template = await fs.readFile(templatePath, 'utf8'); + + // Replace placeholders + const replacements = { + '{{project_name}}': config.project_name || 'Untitled Project', + '{{date}}': new Date().toISOString().split('T')[0], + '{{project_type}}': config.project_type || 'digital_product', + '{{design_experience}}': config.design_experience || 'intermediate', + '{{user_name}}': config.user_name || 'Designer', + '{{communication_language}}': config.communication_language || 'en', + '{{document_output_language}}': config.document_output_language || 'en', + '{{output_folder}}': path.relative(config.projectDir, docsPath) || 'docs', + '{{wds_folder}}': config.wdsFolder || '_wds', + }; + + for (const [placeholder, value] of Object.entries(replacements)) { + template = template.split(placeholder).join(value); + } + + // Write the file + await fs.writeFile(projectInfoPath, template, 'utf8'); } }