ro 4.4.0 → 5.1.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.
- checksums.yaml +4 -4
 - data/Gemfile.lock +42 -16
 - data/MIGRATION.md +320 -0
 - data/README.md +31 -19
 - data/a.yml +60 -0
 - data/bin/ro +10 -0
 - data/lib/ro/_lib.rb +1 -1
 - data/lib/ro/asset.rb +48 -6
 - data/lib/ro/collection.rb +51 -13
 - data/lib/ro/migrator.rb +285 -0
 - data/lib/ro/node.rb +53 -13
 - data/lib/ro/root.rb +75 -1
 - data/lib/ro/script/migrate.rb +204 -0
 - data/lib/ro/script/server.rb +1 -1
 - data/lib/ro.rb +1 -0
 - data/public/api/ro/index-1.json +82 -148
 - data/public/api/ro/index.json +82 -148
 - data/public/api/ro/nerd/fastest-possible-embeddings/index.json +7 -8
 - data/public/api/ro/nerd/ima/index.json +3 -4
 - data/public/api/ro/nerd/index/index.json +5 -6
 - data/public/api/ro/nerd/index-1.json +15 -18
 - data/public/api/ro/nerd/index.json +15 -18
 - data/public/api/ro/pages/contact/index.json +4 -5
 - data/public/api/ro/pages/cv/index.json +3 -4
 - data/public/api/ro/pages/disco/index.json +9 -10
 - data/public/api/ro/pages/index/index.json +2 -3
 - data/public/api/ro/pages/index-1.json +25 -82
 - data/public/api/ro/pages/index.json +25 -82
 - data/public/api/ro/pages/jess/index.json +4 -5
 - data/public/api/ro/pages/now/index.json +3 -4
 - data/public/api/ro/posts/almost-died-in-an-ice-cave/index.json +21 -22
 - data/public/api/ro/posts/facebook-and-global-extremism/index.json +8 -9
 - data/public/api/ro/posts/index-1.json +42 -48
 - data/public/api/ro/posts/index.json +42 -48
 - data/public/api/ro/posts/lemmings-considered-harmful/index.json +3 -4
 - data/public/api/ro/posts/lost-in-the-desert/index.json +3 -4
 - data/public/api/ro/posts/mission/index.json +3 -4
 - data/public/api/ro/posts/return-your-laptop/index.json +4 -5
 - data/ro.gemspec +247 -18
 - data/specs/001-simplify-asset-structure/IMPLEMENTATION_SUMMARY.md +212 -0
 - data/specs/001-simplify-asset-structure/checklists/requirements.md +36 -0
 - data/specs/001-simplify-asset-structure/contracts/collection_api.md +407 -0
 - data/specs/001-simplify-asset-structure/contracts/migrator_api.md +461 -0
 - data/specs/001-simplify-asset-structure/contracts/node_api.md +294 -0
 - data/specs/001-simplify-asset-structure/data-model.md +381 -0
 - data/specs/001-simplify-asset-structure/plan.md +90 -0
 - data/specs/001-simplify-asset-structure/quickstart.md +575 -0
 - data/specs/001-simplify-asset-structure/research.md +333 -0
 - data/specs/001-simplify-asset-structure/spec.md +127 -0
 - data/specs/001-simplify-asset-structure/tasks.md +349 -0
 - data/test/fixtures/new_structure/mixed/test-json.json +5 -0
 - data/test/fixtures/new_structure/mixed/test-yaml.yml +3 -0
 - data/test/fixtures/new_structure/posts/metadata-only.yml +7 -0
 - data/test/fixtures/new_structure/posts/nested-test/assets/subdirectory/image.png +2 -0
 - data/test/fixtures/new_structure/posts/nested-test.yml +7 -0
 - data/test/fixtures/new_structure/posts/sample-post/assets/body.md +5 -0
 - data/test/fixtures/new_structure/posts/sample-post/assets/image.jpg +2 -0
 - data/test/fixtures/new_structure/posts/sample-post.yml +7 -0
 - data/test/fixtures/old_structure/posts/assets-only/assets/test.txt +1 -0
 - data/test/fixtures/old_structure/posts/sample-post/assets/body.md +5 -0
 - data/test/fixtures/old_structure/posts/sample-post/assets/image.jpg +2 -0
 - data/test/fixtures/old_structure/posts/sample-post/attributes.yml +2 -0
 - data/test/integration/ro_integration_test.rb +165 -0
 - data/test/test_helper.rb +149 -0
 - data/test/tmp/migration_test_1760746513.backup.20251018001513/migration_test_1760746513/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760746513.backup.20251018001513/migration_test_1760746513/posts/sample-post/attributes.yml +7 -0
 - data/test/tmp/migration_test_1760746513.backup.20251018001513/migration_test_1760746513/posts/sample-post/body.md +5 -0
 - data/test/tmp/migration_test_1760746513.backup.20251018001513/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760746513.backup.20251018001513/posts/sample-post/attributes.yml +7 -0
 - data/test/tmp/migration_test_1760746513.backup.20251018001513/posts/sample-post/body.md +5 -0
 - data/test/tmp/migration_test_1760746556.backup.20251018001556/migration_test_1760746556/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760746556.backup.20251018001556/migration_test_1760746556/posts/sample-post/attributes.yml +7 -0
 - data/test/tmp/migration_test_1760746556.backup.20251018001556/migration_test_1760746556/posts/sample-post/body.md +5 -0
 - data/test/tmp/migration_test_1760746556.backup.20251018001556/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760746556.backup.20251018001556/posts/sample-post/attributes.yml +7 -0
 - data/test/tmp/migration_test_1760746556.backup.20251018001556/posts/sample-post/body.md +5 -0
 - data/test/tmp/migration_test_1760755248.backup.20251018024048/migration_test_1760755248/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760755248.backup.20251018024048/migration_test_1760755248/posts/sample-post/attributes.yml +7 -0
 - data/test/tmp/migration_test_1760755248.backup.20251018024048/migration_test_1760755248/posts/sample-post/body.md +5 -0
 - data/test/tmp/migration_test_1760755248.backup.20251018024048/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760755248.backup.20251018024048/posts/sample-post/attributes.yml +7 -0
 - data/test/tmp/migration_test_1760755248.backup.20251018024048/posts/sample-post/body.md +5 -0
 - data/test/tmp/migration_test_1760758803.backup.20251018034003/migration_test_1760758803/posts/sample-post/body.md +5 -0
 - data/test/tmp/migration_test_1760758803.backup.20251018034003/migration_test_1760758803/posts/sample-post/image.jpg +2 -0
 - data/test/tmp/migration_test_1760758803.backup.20251018034003/migration_test_1760758803/posts/sample-post.yml +7 -0
 - data/test/tmp/migration_test_1760758803.backup.20251018034003/posts/sample-post/body.md +5 -0
 - data/test/tmp/migration_test_1760758803.backup.20251018034003/posts/sample-post/image.jpg +2 -0
 - data/test/tmp/migration_test_1760758803.backup.20251018034003/posts/sample-post.yml +7 -0
 - data/test/tmp/migration_test_1760758869.backup.20251018034109/migration_test_1760758869/posts/sample-post/assets/body.md +5 -0
 - data/test/tmp/migration_test_1760758869.backup.20251018034109/migration_test_1760758869/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760758869.backup.20251018034109/migration_test_1760758869/posts/sample-post/attributes.yml +2 -0
 - data/test/tmp/migration_test_1760758869.backup.20251018034109/posts/sample-post/assets/body.md +5 -0
 - data/test/tmp/migration_test_1760758869.backup.20251018034109/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760758869.backup.20251018034109/posts/sample-post/attributes.yml +2 -0
 - data/test/tmp/migration_test_1760758920.backup.20251018034200/migration_test_1760758920/posts/sample-post/assets/body.md +5 -0
 - data/test/tmp/migration_test_1760758920.backup.20251018034200/migration_test_1760758920/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760758920.backup.20251018034200/migration_test_1760758920/posts/sample-post/attributes.yml +2 -0
 - data/test/tmp/migration_test_1760758920.backup.20251018034200/posts/sample-post/assets/body.md +5 -0
 - data/test/tmp/migration_test_1760758920.backup.20251018034200/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760758920.backup.20251018034200/posts/sample-post/attributes.yml +2 -0
 - data/test/tmp/migration_test_1760824728.backup.20251018215848/migration_test_1760824728/posts/assets-only/assets/test.txt +1 -0
 - data/test/tmp/migration_test_1760824728.backup.20251018215848/migration_test_1760824728/posts/sample-post/assets/body.md +5 -0
 - data/test/tmp/migration_test_1760824728.backup.20251018215848/migration_test_1760824728/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760824728.backup.20251018215848/migration_test_1760824728/posts/sample-post/attributes.yml +2 -0
 - data/test/tmp/migration_test_1760824728.backup.20251018215848/posts/assets-only/assets/test.txt +1 -0
 - data/test/tmp/migration_test_1760824728.backup.20251018215848/posts/sample-post/assets/body.md +5 -0
 - data/test/tmp/migration_test_1760824728.backup.20251018215848/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760824728.backup.20251018215848/posts/sample-post/attributes.yml +2 -0
 - data/test/tmp/migration_test_1760844153.backup.20251019032233/migration_test_1760844153/posts/assets-only/assets/test.txt +1 -0
 - data/test/tmp/migration_test_1760844153.backup.20251019032233/migration_test_1760844153/posts/sample-post/assets/body.md +5 -0
 - data/test/tmp/migration_test_1760844153.backup.20251019032233/migration_test_1760844153/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760844153.backup.20251019032233/migration_test_1760844153/posts/sample-post/attributes.yml +2 -0
 - data/test/tmp/migration_test_1760844153.backup.20251019032233/posts/assets-only/assets/test.txt +1 -0
 - data/test/tmp/migration_test_1760844153.backup.20251019032233/posts/sample-post/assets/body.md +5 -0
 - data/test/tmp/migration_test_1760844153.backup.20251019032233/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760844153.backup.20251019032233/posts/sample-post/attributes.yml +2 -0
 - data/test/tmp/migration_test_1760940939.backup.20251020061539/migration_test_1760940939/posts/assets-only/assets/test.txt +1 -0
 - data/test/tmp/migration_test_1760940939.backup.20251020061539/migration_test_1760940939/posts/sample-post/assets/body.md +5 -0
 - data/test/tmp/migration_test_1760940939.backup.20251020061539/migration_test_1760940939/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760940939.backup.20251020061539/migration_test_1760940939/posts/sample-post/attributes.yml +2 -0
 - data/test/tmp/migration_test_1760940939.backup.20251020061539/posts/assets-only/assets/test.txt +1 -0
 - data/test/tmp/migration_test_1760940939.backup.20251020061539/posts/sample-post/assets/body.md +5 -0
 - data/test/tmp/migration_test_1760940939.backup.20251020061539/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760940939.backup.20251020061539/posts/sample-post/attributes.yml +2 -0
 - data/test/tmp/migration_test_1760941048.backup.20251020061728/migration_test_1760941048/posts/assets-only/assets/test.txt +1 -0
 - data/test/tmp/migration_test_1760941048.backup.20251020061728/migration_test_1760941048/posts/sample-post/assets/body.md +5 -0
 - data/test/tmp/migration_test_1760941048.backup.20251020061728/migration_test_1760941048/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760941048.backup.20251020061728/migration_test_1760941048/posts/sample-post/attributes.yml +2 -0
 - data/test/tmp/migration_test_1760941048.backup.20251020061728/posts/assets-only/assets/test.txt +1 -0
 - data/test/tmp/migration_test_1760941048.backup.20251020061728/posts/sample-post/assets/body.md +5 -0
 - data/test/tmp/migration_test_1760941048.backup.20251020061728/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760941048.backup.20251020061728/posts/sample-post/attributes.yml +2 -0
 - data/test/tmp/new_structure_test_1760746452/mixed/test-json.json +5 -0
 - data/test/tmp/new_structure_test_1760746452/mixed/test-yaml.yml +3 -0
 - data/test/tmp/new_structure_test_1760746452/posts/metadata-only.yml +7 -0
 - data/test/tmp/new_structure_test_1760746452/posts/nested-test/subdirectory/image.png +2 -0
 - data/test/tmp/new_structure_test_1760746452/posts/nested-test.yml +7 -0
 - data/test/tmp/new_structure_test_1760746452/posts/sample-post/body.md +5 -0
 - data/test/tmp/new_structure_test_1760746452/posts/sample-post/image.jpg +2 -0
 - data/test/tmp/new_structure_test_1760746452/posts/sample-post.yml +7 -0
 - data/test/unit/asset_test.rb +90 -0
 - data/test/unit/collection_test.rb +127 -0
 - data/test/unit/migrator_test.rb +209 -0
 - data/test/unit/node_test.rb +138 -0
 - metadata +127 -19
 - data/public/api/ro/pages/about/index.json +0 -60
 - /data/public/ro/nerd/{fastest-possible-embeddings/attributes.yml → fastest-possible-embeddings.yml} +0 -0
 - /data/public/ro/nerd/{ima/attributes.yml → ima.yml} +0 -0
 - /data/public/ro/nerd/{index/attributes.yml → index.yml} +0 -0
 - /data/public/ro/pages/{contact/attributes.yml → contact.yml} +0 -0
 - /data/public/ro/pages/{cv/attributes.yml → cv.yml} +0 -0
 - /data/public/ro/pages/{disco/attributes.yml → disco.yml} +0 -0
 - /data/public/ro/pages/{index/attributes.yml → index.yml} +0 -0
 - /data/public/ro/pages/{jess/attributes.yml → jess.yml} +0 -0
 - /data/public/ro/pages/{now/attributes.yml → now.yml} +0 -0
 - /data/public/ro/posts/{almost-died-in-an-ice-cave/attributes.yml → almost-died-in-an-ice-cave.yml} +0 -0
 - /data/public/ro/posts/{facebook-and-global-extremism/attributes.yml → facebook-and-global-extremism.yml} +0 -0
 - /data/public/ro/posts/{lemmings-considered-harmful/attributes.yml → lemmings-considered-harmful.yml} +0 -0
 - /data/public/ro/posts/{lost-in-the-desert/attributes.yml → lost-in-the-desert.yml} +0 -0
 - /data/public/ro/posts/{mission/attributes.yml → mission.yml} +0 -0
 - /data/public/ro/posts/{return-your-laptop/attributes.yml → return-your-laptop.yml} +0 -0
 
| 
         @@ -0,0 +1,333 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Research: Simplify Asset Directory Structure
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            **Date**: 2025-10-17
         
     | 
| 
      
 4 
     | 
    
         
            +
            **Feature**: 001-simplify-asset-structure
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            ## Overview
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            This document contains research findings and technical decisions for simplifying the ro gem's asset directory structure from nested format to flattened format.
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            ## Key Technical Decisions
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            ### Decision 1: Node Discovery Pattern
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            **What was chosen**: Change from directory-based node discovery to file-based node discovery
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            **Rationale**:
         
     | 
| 
      
 17 
     | 
    
         
            +
            - **Old approach**: Collections iterate subdirectories, each subdirectory is a node containing `attributes.yml`
         
     | 
| 
      
 18 
     | 
    
         
            +
            - **New approach**: Collections scan for `.yml`/`.json`/`.toml` files, derive node identifier from filename
         
     | 
| 
      
 19 
     | 
    
         
            +
            - **Why**: The new structure places metadata at the collection level (`identifier.yml`) rather than nested (`identifier/attributes.yml`), so node discovery must change from "find directories with attributes files" to "find metadata files, derive corresponding asset directories"
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
            **Alternatives considered**:
         
     | 
| 
      
 22 
     | 
    
         
            +
            - **Keep directory-based discovery**: Would require maintaining both `identifier/` directory AND `identifier.yml` file, defeating the purpose of simplification
         
     | 
| 
      
 23 
     | 
    
         
            +
            - **Dual mode (support both)**: Adds complexity and makes migration harder; spec specifies preference for old structure until migration, not simultaneous support
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            **Implementation impact**:
         
     | 
| 
      
 26 
     | 
    
         
            +
            - Modify `Collection#each` in `lib/ro/collection.rb` (lines 45-67)
         
     | 
| 
      
 27 
     | 
    
         
            +
            - Modify `Collection#node_for` in `lib/ro/collection.rb` (line 33-35)
         
     | 
| 
      
 28 
     | 
    
         
            +
            - Change from `subdirectories.each` to scanning for metadata files with supported extensions
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
            ### Decision 2: Node Initialization Pattern
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
            **What was chosen**: Decouple node path from metadata file location
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
            **Rationale**:
         
     | 
| 
      
 35 
     | 
    
         
            +
            - **Old pattern**: `Node.new(path)` where `path` IS the node directory containing `attributes.yml`
         
     | 
| 
      
 36 
     | 
    
         
            +
            - **New pattern**: `Node.new(path, metadata_file)` where `path` is the collection, `metadata_file` points to `identifier.yml`
         
     | 
| 
      
 37 
     | 
    
         
            +
            - **Why**: In the old structure, the node's path was the directory containing everything. In the new structure, the metadata file is at the collection level, and the asset directory is a sibling. The node needs to know both locations.
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
            **Alternatives considered**:
         
     | 
| 
      
 40 
     | 
    
         
            +
            - **Metadata file only**: Pass only the metadata file path, derive everything from it. Rejected because it would require more path manipulation and lose context of the collection.
         
     | 
| 
      
 41 
     | 
    
         
            +
            - **Keep single path parameter**: Would require complex logic to detect whether path points to old or new structure. Rejected for clarity.
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
            **Implementation impact**:
         
     | 
| 
      
 44 
     | 
    
         
            +
            - Modify `Node#initialize` in `lib/ro/node.rb` (lines ~20-30)
         
     | 
| 
      
 45 
     | 
    
         
            +
            - Modify `Node#_load_base_attributes` in `lib/ro/node.rb` (lines 57-63) to load from external metadata file
         
     | 
| 
      
 46 
     | 
    
         
            +
            - Update all Node instantiation call sites in Collection and Root classes
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
            ### Decision 3: Asset Directory Resolution
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
            **What was chosen**: Remove `assets/` subdirectory nesting
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
            **Rationale**:
         
     | 
| 
      
 53 
     | 
    
         
            +
            - **Old approach**: `node.path.join('assets')` returns `identifier/assets/`
         
     | 
| 
      
 54 
     | 
    
         
            +
            - **New approach**: `node.asset_dir` returns `identifier/` (same as node directory)
         
     | 
| 
      
 55 
     | 
    
         
            +
            - **Why**: The new structure eliminates the `assets/` subdirectory, placing all files directly in the `identifier/` directory alongside the metadata file
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
            **Alternatives considered**:
         
     | 
| 
      
 58 
     | 
    
         
            +
            - **Keep `assets/` subdirectory**: Would not achieve the simplification goal from the spec
         
     | 
| 
      
 59 
     | 
    
         
            +
            - **Flat structure (no directory at all)**: Would make it impossible to have multiple files per asset. Rejected because spec explicitly includes `identifier/` directory for "additional data"
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
            **Implementation impact**:
         
     | 
| 
      
 62 
     | 
    
         
            +
            - Modify `Node#asset_dir` in `lib/ro/node.rb` (line 209-211)
         
     | 
| 
      
 63 
     | 
    
         
            +
            - Modify `Node#_ignored_files` in `lib/ro/node.rb` (lines 153-165) to update ignore patterns
         
     | 
| 
      
 64 
     | 
    
         
            +
            - Modify `Asset` path splitting in `lib/ro/asset.rb` (line 12) - currently splits on `/assets/`
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
            ### Decision 4: Backward Compatibility Strategy
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
            **What was chosen**: Prefer old structure until explicit migration, then breaking change in v5.0
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
            **Rationale**:
         
     | 
| 
      
 71 
     | 
    
         
            +
            - **User decision**: Spec FR-011 states "prefer old structure when both exist until migration"
         
     | 
| 
      
 72 
     | 
    
         
            +
            - **User decision**: Spec FR-012 states "breaking change with major version bump"
         
     | 
| 
      
 73 
     | 
    
         
            +
            - **Why**: Safest approach for existing users - they can test the new structure without risking data loss, then migrate when ready
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
            **Alternatives considered**:
         
     | 
| 
      
 76 
     | 
    
         
            +
            - **Simultaneous support**: Would require complex detection logic and feature flags. Rejected per user decision.
         
     | 
| 
      
 77 
     | 
    
         
            +
            - **Automatic migration on load**: Too risky - could modify user data unexpectedly. Rejected for safety.
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
            **Implementation impact**:
         
     | 
| 
      
 80 
     | 
    
         
            +
            - Migration tool must be run explicitly by users
         
     | 
| 
      
 81 
     | 
    
         
            +
            - Version bump from 4.4.0 → 5.0.0
         
     | 
| 
      
 82 
     | 
    
         
            +
            - Update README and CHANGELOG to document breaking change
         
     | 
| 
      
 83 
     | 
    
         
            +
            - Potentially add deprecation warnings in 4.x versions (out of scope for this feature)
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
            ### Decision 5: Migration Tool Design
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
            **What was chosen**: Standalone migration script with dry-run mode
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
            **Rationale**:
         
     | 
| 
      
 90 
     | 
    
         
            +
            - **Pattern**: `ro migrate [collection_path] [--dry-run] [--backup]`
         
     | 
| 
      
 91 
     | 
    
         
            +
            - **Why**:
         
     | 
| 
      
 92 
     | 
    
         
            +
              - Explicit operation reduces risk of accidental data modification
         
     | 
| 
      
 93 
     | 
    
         
            +
              - Dry-run mode allows users to preview changes
         
     | 
| 
      
 94 
     | 
    
         
            +
              - Backup option provides safety net
         
     | 
| 
      
 95 
     | 
    
         
            +
              - Can be run incrementally on specific collections
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
      
 97 
     | 
    
         
            +
            **Alternatives considered**:
         
     | 
| 
      
 98 
     | 
    
         
            +
            - **In-place detection and migration**: Auto-migrate on first load. Rejected as too dangerous.
         
     | 
| 
      
 99 
     | 
    
         
            +
            - **Rake task only**: Would work but less discoverable. CLI command is more user-friendly.
         
     | 
| 
      
 100 
     | 
    
         
            +
            - **Bidirectional migration**: Support going back to old structure. Rejected as unnecessary - this is a one-way breaking change.
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
            **Implementation impact**:
         
     | 
| 
      
 103 
     | 
    
         
            +
            - Create `lib/ro/script/migrator.rb`
         
     | 
| 
      
 104 
     | 
    
         
            +
            - Add CLI command in `bin/ro` or Rakefile
         
     | 
| 
      
 105 
     | 
    
         
            +
            - Must handle:
         
     | 
| 
      
 106 
     | 
    
         
            +
              - Moving `identifier/attributes.yml` → `identifier.yml`
         
     | 
| 
      
 107 
     | 
    
         
            +
              - Moving `identifier/assets/**/*` → `identifier/`
         
     | 
| 
      
 108 
     | 
    
         
            +
              - Moving `identifier/body.md` and other root files → `identifier/`
         
     | 
| 
      
 109 
     | 
    
         
            +
              - Preserving file permissions and timestamps
         
     | 
| 
      
 110 
     | 
    
         
            +
              - Handling errors gracefully with rollback capability
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
            ### Decision 6: Testing Strategy
         
     | 
| 
      
 113 
     | 
    
         
            +
             
     | 
| 
      
 114 
     | 
    
         
            +
            **What was chosen**: TDD approach with comprehensive test coverage before implementation
         
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
      
 116 
     | 
    
         
            +
            **Rationale**:
         
     | 
| 
      
 117 
     | 
    
         
            +
            - **Current state**: No tests exist in the repository
         
     | 
| 
      
 118 
     | 
    
         
            +
            - **Risk**: Core refactoring without tests is extremely dangerous
         
     | 
| 
      
 119 
     | 
    
         
            +
            - **Approach**:
         
     | 
| 
      
 120 
     | 
    
         
            +
              1. Create tests for OLD structure first (document current behavior)
         
     | 
| 
      
 121 
     | 
    
         
            +
              2. Ensure all tests pass (baseline)
         
     | 
| 
      
 122 
     | 
    
         
            +
              3. Implement new structure
         
     | 
| 
      
 123 
     | 
    
         
            +
              4. Create tests for NEW structure
         
     | 
| 
      
 124 
     | 
    
         
            +
              5. Ensure both pass during transition
         
     | 
| 
      
 125 
     | 
    
         
            +
              6. Remove old structure support after migration
         
     | 
| 
      
 126 
     | 
    
         
            +
             
     | 
| 
      
 127 
     | 
    
         
            +
            **Alternatives considered**:
         
     | 
| 
      
 128 
     | 
    
         
            +
            - **Write tests after implementation**: Rejected - too risky for core refactoring
         
     | 
| 
      
 129 
     | 
    
         
            +
            - **Minimal test coverage**: Rejected - this is a breaking change affecting all users
         
     | 
| 
      
 130 
     | 
    
         
            +
             
     | 
| 
      
 131 
     | 
    
         
            +
            **Implementation impact**:
         
     | 
| 
      
 132 
     | 
    
         
            +
            - Create `test/` directory with unit, functional, integration subdirectories
         
     | 
| 
      
 133 
     | 
    
         
            +
            - Create test fixtures in both old and new structure formats
         
     | 
| 
      
 134 
     | 
    
         
            +
            - Write tests for Node, Collection, Asset, Root, and Migrator classes
         
     | 
| 
      
 135 
     | 
    
         
            +
            - Integration tests for end-to-end asset loading/writing/migration
         
     | 
| 
      
 136 
     | 
    
         
            +
             
     | 
| 
      
 137 
     | 
    
         
            +
            ## Technical Constraints
         
     | 
| 
      
 138 
     | 
    
         
            +
             
     | 
| 
      
 139 
     | 
    
         
            +
            ### File System Compatibility
         
     | 
| 
      
 140 
     | 
    
         
            +
             
     | 
| 
      
 141 
     | 
    
         
            +
            **Research**: Need to support cross-platform file paths (Linux, macOS, Windows)
         
     | 
| 
      
 142 
     | 
    
         
            +
             
     | 
| 
      
 143 
     | 
    
         
            +
            **Findings**:
         
     | 
| 
      
 144 
     | 
    
         
            +
            - Ruby's `Pathname` class (already used in ro codebase) handles cross-platform paths
         
     | 
| 
      
 145 
     | 
    
         
            +
            - YAML/JSON filename extensions are case-sensitive on Linux, case-insensitive on Windows
         
     | 
| 
      
 146 
     | 
    
         
            +
            - Must test migration tool on all platforms
         
     | 
| 
      
 147 
     | 
    
         
            +
             
     | 
| 
      
 148 
     | 
    
         
            +
            **Decision**: Continue using `Pathname`, add explicit case-handling for file extension detection
         
     | 
| 
      
 149 
     | 
    
         
            +
             
     | 
| 
      
 150 
     | 
    
         
            +
            ### Metadata Format Support
         
     | 
| 
      
 151 
     | 
    
         
            +
             
     | 
| 
      
 152 
     | 
    
         
            +
            **Research**: Which metadata formats must be supported?
         
     | 
| 
      
 153 
     | 
    
         
            +
             
     | 
| 
      
 154 
     | 
    
         
            +
            **Findings from codebase**:
         
     | 
| 
      
 155 
     | 
    
         
            +
            - `Node#_load_base_attributes` (lib/ro/node.rb:57) uses glob: `"attributes.{yml,yaml,json}"`
         
     | 
| 
      
 156 
     | 
    
         
            +
            - Only YAML and JSON are currently supported
         
     | 
| 
      
 157 
     | 
    
         
            +
            - Spec mentions TOML support (FR-005)
         
     | 
| 
      
 158 
     | 
    
         
            +
             
     | 
| 
      
 159 
     | 
    
         
            +
            **Decision**:
         
     | 
| 
      
 160 
     | 
    
         
            +
            - Phase 1: Support existing formats (YAML, JSON)
         
     | 
| 
      
 161 
     | 
    
         
            +
            - Phase 2 (optional): Add TOML support if requested
         
     | 
| 
      
 162 
     | 
    
         
            +
            - Extension detection must be explicit: `.yml`, `.yaml`, `.json`, (`.toml`)
         
     | 
| 
      
 163 
     | 
    
         
            +
             
     | 
| 
      
 164 
     | 
    
         
            +
            ### Performance Considerations
         
     | 
| 
      
 165 
     | 
    
         
            +
             
     | 
| 
      
 166 
     | 
    
         
            +
            **Research**: How to maintain <100ms asset lookup performance (SC-001)?
         
     | 
| 
      
 167 
     | 
    
         
            +
             
     | 
| 
      
 168 
     | 
    
         
            +
            **Findings**:
         
     | 
| 
      
 169 
     | 
    
         
            +
            - Current implementation uses `Pathname#glob` for file discovery
         
     | 
| 
      
 170 
     | 
    
         
            +
            - Ruby's `Dir.glob` is generally fast for up to 10,000 files
         
     | 
| 
      
 171 
     | 
    
         
            +
            - No caching is currently implemented
         
     | 
| 
      
 172 
     | 
    
         
            +
            - Performance bottleneck is likely I/O, not directory structure
         
     | 
| 
      
 173 
     | 
    
         
            +
             
     | 
| 
      
 174 
     | 
    
         
            +
            **Decision**:
         
     | 
| 
      
 175 
     | 
    
         
            +
            - New structure should be FASTER (less nesting = fewer stat calls)
         
     | 
| 
      
 176 
     | 
    
         
            +
            - No caching needed initially
         
     | 
| 
      
 177 
     | 
    
         
            +
            - If performance issues arise, add memoization to Collection#nodes
         
     | 
| 
      
 178 
     | 
    
         
            +
             
     | 
| 
      
 179 
     | 
    
         
            +
            ## Migration Edge Cases
         
     | 
| 
      
 180 
     | 
    
         
            +
             
     | 
| 
      
 181 
     | 
    
         
            +
            ### Case 1: Identifier with Special Characters
         
     | 
| 
      
 182 
     | 
    
         
            +
             
     | 
| 
      
 183 
     | 
    
         
            +
            **Scenario**: Asset identifier contains spaces, unicode, or special chars
         
     | 
| 
      
 184 
     | 
    
         
            +
            **Example**: `my post!.yml` and `my post!/`
         
     | 
| 
      
 185 
     | 
    
         
            +
             
     | 
| 
      
 186 
     | 
    
         
            +
            **Research**: How do filesystems handle these?
         
     | 
| 
      
 187 
     | 
    
         
            +
            - **Linux/macOS**: Allow most characters except `/` and null
         
     | 
| 
      
 188 
     | 
    
         
            +
            - **Windows**: Disallows `< > : " | ? * \`
         
     | 
| 
      
 189 
     | 
    
         
            +
            - **Assumption from spec**: "Asset identifiers are valid filenames"
         
     | 
| 
      
 190 
     | 
    
         
            +
             
     | 
| 
      
 191 
     | 
    
         
            +
            **Decision**: Trust the spec assumption - if it exists in old format, it should migrate cleanly
         
     | 
| 
      
 192 
     | 
    
         
            +
             
     | 
| 
      
 193 
     | 
    
         
            +
            ### Case 2: Metadata-Only Assets
         
     | 
| 
      
 194 
     | 
    
         
            +
             
     | 
| 
      
 195 
     | 
    
         
            +
            **Scenario**: Asset has `identifier.yml` but no `identifier/` directory
         
     | 
| 
      
 196 
     | 
    
         
            +
            **Spec reference**: FR-007 "handle metadata only"
         
     | 
| 
      
 197 
     | 
    
         
            +
             
     | 
| 
      
 198 
     | 
    
         
            +
            **Decision**: Perfectly valid in new structure, no special handling needed
         
     | 
| 
      
 199 
     | 
    
         
            +
             
     | 
| 
      
 200 
     | 
    
         
            +
            ### Case 3: Files-Only Assets
         
     | 
| 
      
 201 
     | 
    
         
            +
             
     | 
| 
      
 202 
     | 
    
         
            +
            **Scenario**: Asset has `identifier/` directory but no metadata file
         
     | 
| 
      
 203 
     | 
    
         
            +
            **Spec reference**: FR-008 "handle files only"
         
     | 
| 
      
 204 
     | 
    
         
            +
             
     | 
| 
      
 205 
     | 
    
         
            +
            **Research**: In old structure, would be `identifier/` with no `attributes.yml`
         
     | 
| 
      
 206 
     | 
    
         
            +
             
     | 
| 
      
 207 
     | 
    
         
            +
            **Decision**:
         
     | 
| 
      
 208 
     | 
    
         
            +
            - In new structure, create empty `identifier.yml` with minimal metadata
         
     | 
| 
      
 209 
     | 
    
         
            +
            - Or: Skip during migration, log as warning
         
     | 
| 
      
 210 
     | 
    
         
            +
            - **Requires clarification**: Ask user preference during implementation
         
     | 
| 
      
 211 
     | 
    
         
            +
             
     | 
| 
      
 212 
     | 
    
         
            +
            ### Case 4: Both Structures Exist
         
     | 
| 
      
 213 
     | 
    
         
            +
             
     | 
| 
      
 214 
     | 
    
         
            +
            **Scenario**: Migration interrupted, both `identifier/attributes.yml` AND `identifier.yml` exist
         
     | 
| 
      
 215 
     | 
    
         
            +
            **Spec reference**: FR-011 "prefer old structure until migrated"
         
     | 
| 
      
 216 
     | 
    
         
            +
             
     | 
| 
      
 217 
     | 
    
         
            +
            **Decision**:
         
     | 
| 
      
 218 
     | 
    
         
            +
            - Detection logic: If `identifier/attributes.yml` exists, treat as old structure (ignore `identifier.yml`)
         
     | 
| 
      
 219 
     | 
    
         
            +
            - Migration tool should check for this and warn user
         
     | 
| 
      
 220 
     | 
    
         
            +
            - Post-migration cleanup: Remove old structure only after verification
         
     | 
| 
      
 221 
     | 
    
         
            +
             
     | 
| 
      
 222 
     | 
    
         
            +
            ### Case 5: Nested Asset Directories
         
     | 
| 
      
 223 
     | 
    
         
            +
             
     | 
| 
      
 224 
     | 
    
         
            +
            **Scenario**: Assets in subdirectories like `identifier/assets/images/photo.jpg`
         
     | 
| 
      
 225 
     | 
    
         
            +
            **Edge case from spec**: "nested directories within asset directory"
         
     | 
| 
      
 226 
     | 
    
         
            +
             
     | 
| 
      
 227 
     | 
    
         
            +
            **Decision**:
         
     | 
| 
      
 228 
     | 
    
         
            +
            - Old: `identifier/assets/images/photo.jpg`
         
     | 
| 
      
 229 
     | 
    
         
            +
            - New: `identifier/images/photo.jpg`
         
     | 
| 
      
 230 
     | 
    
         
            +
            - Migration preserves directory structure, just removes `assets/` prefix
         
     | 
| 
      
 231 
     | 
    
         
            +
             
     | 
| 
      
 232 
     | 
    
         
            +
            ### Case 6: Non-Asset Files in Node Directory
         
     | 
| 
      
 233 
     | 
    
         
            +
             
     | 
| 
      
 234 
     | 
    
         
            +
            **Scenario**: Files like `body.md`, `samples/`, etc. in node directory
         
     | 
| 
      
 235 
     | 
    
         
            +
            **Example**: `public/ro/pages/disco/` has `body.md`, `samples/`, and `assets/`
         
     | 
| 
      
 236 
     | 
    
         
            +
             
     | 
| 
      
 237 
     | 
    
         
            +
            **Decision**:
         
     | 
| 
      
 238 
     | 
    
         
            +
            - Old: `identifier/body.md`, `identifier/assets/`, `identifier/samples/`
         
     | 
| 
      
 239 
     | 
    
         
            +
            - New: All go in `identifier/`: `identifier/body.md`, `identifier/samples/`
         
     | 
| 
      
 240 
     | 
    
         
            +
            - These are NOT in `assets/` currently, so they stay at same relative position
         
     | 
| 
      
 241 
     | 
    
         
            +
            - `Node#_ignored_files` explicitly ignores `assets/**/**`, so these files are already treated as node content
         
     | 
| 
      
 242 
     | 
    
         
            +
             
     | 
| 
      
 243 
     | 
    
         
            +
            ## Dependencies and Integration Points
         
     | 
| 
      
 244 
     | 
    
         
            +
             
     | 
| 
      
 245 
     | 
    
         
            +
            ### Dependency 1: Pathname Library
         
     | 
| 
      
 246 
     | 
    
         
            +
             
     | 
| 
      
 247 
     | 
    
         
            +
            **Usage**: Core path manipulation throughout codebase
         
     | 
| 
      
 248 
     | 
    
         
            +
            **Impact**: None - Pathname will continue to work with new structure
         
     | 
| 
      
 249 
     | 
    
         
            +
            **Action**: No changes needed
         
     | 
| 
      
 250 
     | 
    
         
            +
             
     | 
| 
      
 251 
     | 
    
         
            +
            ### Dependency 2: Front Matter Parser
         
     | 
| 
      
 252 
     | 
    
         
            +
             
     | 
| 
      
 253 
     | 
    
         
            +
            **Usage**: `front_matter_parser` gem extracts YAML frontmatter from markdown
         
     | 
| 
      
 254 
     | 
    
         
            +
            **Impact**: None - frontmatter parsing is independent of file location
         
     | 
| 
      
 255 
     | 
    
         
            +
            **Action**: No changes needed
         
     | 
| 
      
 256 
     | 
    
         
            +
             
     | 
| 
      
 257 
     | 
    
         
            +
            ### Dependency 3: Kramdown
         
     | 
| 
      
 258 
     | 
    
         
            +
             
     | 
| 
      
 259 
     | 
    
         
            +
            **Usage**: Markdown rendering for body content
         
     | 
| 
      
 260 
     | 
    
         
            +
            **Impact**: None - markdown rendering is independent of file location
         
     | 
| 
      
 261 
     | 
    
         
            +
            **Action**: No changes needed
         
     | 
| 
      
 262 
     | 
    
         
            +
             
     | 
| 
      
 263 
     | 
    
         
            +
            ### Integration Point 1: Static API Builder
         
     | 
| 
      
 264 
     | 
    
         
            +
             
     | 
| 
      
 265 
     | 
    
         
            +
            **Location**: `lib/ro/script/builder.rb`
         
     | 
| 
      
 266 
     | 
    
         
            +
            **Current behavior**: Iterates nodes, generates JSON API
         
     | 
| 
      
 267 
     | 
    
         
            +
            **Impact**: Should work transparently if Node interface unchanged
         
     | 
| 
      
 268 
     | 
    
         
            +
            **Action**: Verify builder still works after Node refactoring, add integration test
         
     | 
| 
      
 269 
     | 
    
         
            +
             
     | 
| 
      
 270 
     | 
    
         
            +
            ### Integration Point 2: Dev Server
         
     | 
| 
      
 271 
     | 
    
         
            +
             
     | 
| 
      
 272 
     | 
    
         
            +
            **Location**: `lib/ro/script/server.rb`
         
     | 
| 
      
 273 
     | 
    
         
            +
            **Current behavior**: Serves content from ro directory structure
         
     | 
| 
      
 274 
     | 
    
         
            +
            **Impact**: Should work transparently if Node interface unchanged
         
     | 
| 
      
 275 
     | 
    
         
            +
            **Action**: Test server manually after implementation
         
     | 
| 
      
 276 
     | 
    
         
            +
             
     | 
| 
      
 277 
     | 
    
         
            +
            ### Integration Point 3: GitHub Pages Workflow
         
     | 
| 
      
 278 
     | 
    
         
            +
             
     | 
| 
      
 279 
     | 
    
         
            +
            **Location**: `.github/workflows/gh-pages.yml`
         
     | 
| 
      
 280 
     | 
    
         
            +
            **Current behavior**: Builds static API and deploys to GitHub Pages
         
     | 
| 
      
 281 
     | 
    
         
            +
            **Impact**: Must work with migrated `public/ro/` structure
         
     | 
| 
      
 282 
     | 
    
         
            +
            **Action**: Migrate `public/ro/` as part of this feature, test workflow
         
     | 
| 
      
 283 
     | 
    
         
            +
             
     | 
| 
      
 284 
     | 
    
         
            +
            ## Best Practices
         
     | 
| 
      
 285 
     | 
    
         
            +
             
     | 
| 
      
 286 
     | 
    
         
            +
            ### Ruby Gem Versioning
         
     | 
| 
      
 287 
     | 
    
         
            +
             
     | 
| 
      
 288 
     | 
    
         
            +
            **Research**: Semantic versioning for breaking changes
         
     | 
| 
      
 289 
     | 
    
         
            +
            **Best practice**: Major version bump for breaking changes (4.x → 5.0)
         
     | 
| 
      
 290 
     | 
    
         
            +
            **Action**: Update version in `lib/ro.rb` and `ro.gemspec`
         
     | 
| 
      
 291 
     | 
    
         
            +
             
     | 
| 
      
 292 
     | 
    
         
            +
            ### Ruby Testing Patterns
         
     | 
| 
      
 293 
     | 
    
         
            +
             
     | 
| 
      
 294 
     | 
    
         
            +
            **Research**: Conventions for Ruby testing without RSpec/Minitest
         
     | 
| 
      
 295 
     | 
    
         
            +
            **Findings**: The Rakefile already defines a custom test runner
         
     | 
| 
      
 296 
     | 
    
         
            +
            **Best practice**: Follow existing convention (test/**/*_test.rb)
         
     | 
| 
      
 297 
     | 
    
         
            +
            **Action**: Create tests matching existing Rake task pattern
         
     | 
| 
      
 298 
     | 
    
         
            +
             
     | 
| 
      
 299 
     | 
    
         
            +
            ### File System Operations
         
     | 
| 
      
 300 
     | 
    
         
            +
             
     | 
| 
      
 301 
     | 
    
         
            +
            **Research**: Safe file operations in Ruby
         
     | 
| 
      
 302 
     | 
    
         
            +
            **Best practices**:
         
     | 
| 
      
 303 
     | 
    
         
            +
            - Use `FileUtils.mv` for moves (atomic on same filesystem)
         
     | 
| 
      
 304 
     | 
    
         
            +
            - Use `FileUtils.cp_r` for backups
         
     | 
| 
      
 305 
     | 
    
         
            +
            - Wrap in transactions with rollback capability
         
     | 
| 
      
 306 
     | 
    
         
            +
            - Verify checksums before/after migration
         
     | 
| 
      
 307 
     | 
    
         
            +
             
     | 
| 
      
 308 
     | 
    
         
            +
            **Action**: Implement migration with:
         
     | 
| 
      
 309 
     | 
    
         
            +
            - Pre-migration validation
         
     | 
| 
      
 310 
     | 
    
         
            +
            - Atomic operations where possible
         
     | 
| 
      
 311 
     | 
    
         
            +
            - Error handling with rollback
         
     | 
| 
      
 312 
     | 
    
         
            +
            - Post-migration verification
         
     | 
| 
      
 313 
     | 
    
         
            +
             
     | 
| 
      
 314 
     | 
    
         
            +
            ### Changelog and Documentation
         
     | 
| 
      
 315 
     | 
    
         
            +
             
     | 
| 
      
 316 
     | 
    
         
            +
            **Research**: Documenting breaking changes
         
     | 
| 
      
 317 
     | 
    
         
            +
            **Best practice**:
         
     | 
| 
      
 318 
     | 
    
         
            +
            - CHANGELOG.md with clear breaking changes section
         
     | 
| 
      
 319 
     | 
    
         
            +
            - README.md with migration guide
         
     | 
| 
      
 320 
     | 
    
         
            +
            - Version upgrade guide
         
     | 
| 
      
 321 
     | 
    
         
            +
             
     | 
| 
      
 322 
     | 
    
         
            +
            **Action**: Update documentation as part of this feature
         
     | 
| 
      
 323 
     | 
    
         
            +
             
     | 
| 
      
 324 
     | 
    
         
            +
            ## Open Questions
         
     | 
| 
      
 325 
     | 
    
         
            +
             
     | 
| 
      
 326 
     | 
    
         
            +
            None - all clarifications were resolved during specification phase.
         
     | 
| 
      
 327 
     | 
    
         
            +
             
     | 
| 
      
 328 
     | 
    
         
            +
            ## References
         
     | 
| 
      
 329 
     | 
    
         
            +
             
     | 
| 
      
 330 
     | 
    
         
            +
            - [Semantic Versioning](https://semver.org/) - Version numbering for breaking changes
         
     | 
| 
      
 331 
     | 
    
         
            +
            - [Ruby Pathname Documentation](https://ruby-doc.org/stdlib-3.0.0/libdoc/pathname/rdoc/Pathname.html) - Path manipulation
         
     | 
| 
      
 332 
     | 
    
         
            +
            - [FileUtils Documentation](https://ruby-doc.org/stdlib-3.0.0/libdoc/fileutils/rdoc/FileUtils.html) - File operations
         
     | 
| 
      
 333 
     | 
    
         
            +
            - ro gem codebase exploration findings (see plan.md Phase 0 notes)
         
     | 
| 
         @@ -0,0 +1,127 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Feature Specification: Simplify Asset Directory Structure
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            **Feature Branch**: `001-simplify-asset-structure`
         
     | 
| 
      
 4 
     | 
    
         
            +
            **Created**: 2025-10-17
         
     | 
| 
      
 5 
     | 
    
         
            +
            **Status**: In Progress
         
     | 
| 
      
 6 
     | 
    
         
            +
            **Input**: User description: "attm, ro stores assets in a directory structure like ./ro/posts/teh-slug/attributes.yml, ./ro/posts/teh-slug/assets/**.**.  we want to, instead, use a more simple structure like ./ro/posts/teh-slug.yml and ./ro/posts/teh-slug/assets/**.**.  that is to say '$identifier.yml' (or json, etc) for the main data and '$identifier/assets/**.**' for asset files"
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            **Clarification**: Assets remain in the `assets/` subdirectory to prevent non-asset files (like markdown, ERB templates) from being treated as assets. Only the metadata file moves from `$identifier/attributes.yml` to `$identifier.yml`.
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            ## User Scenarios & Testing *(mandatory)*
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            ### User Story 1 - Read Asset Data (Priority: P1)
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            Users need to access asset metadata and associated files using a simpler, more intuitive directory structure where the metadata file sits alongside the asset directory instead of being nested inside it.
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            **Why this priority**: This is the core structural change that affects all asset operations. Without this, no other functionality can work with the new structure.
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
            **Independent Test**: Can be fully tested by creating an asset with the new structure (identifier.yml + identifier/ directory) and verifying that metadata can be read correctly. Delivers immediate value by making asset organization clearer.
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
            **Acceptance Scenarios**:
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
            1. **Given** an asset with identifier "my-post", **When** the system reads the asset, **Then** it loads metadata from `my-post.yml` and assets from `my-post/assets/` directory
         
     | 
| 
      
 23 
     | 
    
         
            +
            2. **Given** multiple assets exist, **When** the system lists assets, **Then** it correctly identifies each asset by matching `identifier.yml` files with corresponding `identifier/assets/` directories
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            ---
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
            ### User Story 2 - Write Asset Data (Priority: P2)
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
            Users need to create and update assets in the new simplified structure, with metadata stored in a single file at the root level rather than nested inside a subdirectory.
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
            **Why this priority**: Creating and modifying assets is essential for practical use, but depends on the ability to read assets correctly (P1).
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
            **Independent Test**: Can be tested by creating a new asset, updating its metadata, and verifying the structure matches the expected pattern (`identifier.yml` + `identifier/` for files).
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
            **Acceptance Scenarios**:
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
            1. **Given** a new asset identifier "new-post", **When** the system creates the asset, **Then** it creates `new-post.yml` for metadata and `new-post/assets/` directory for associated files
         
     | 
| 
      
 38 
     | 
    
         
            +
            2. **Given** an existing asset, **When** metadata is updated, **Then** changes are written to the `identifier.yml` file
         
     | 
| 
      
 39 
     | 
    
         
            +
            3. **Given** new files are added to an asset, **When** the system saves them, **Then** files are stored in the `identifier/assets/` directory
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
            ---
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
            ### User Story 3 - Migrate Existing Assets (Priority: P3)
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
            Users need to migrate assets from the old structure (`identifier/attributes.yml` + `identifier/assets/`) to the new structure (`identifier.yml` + `identifier/`) without data loss.
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
            **Why this priority**: Migration is necessary for existing installations but can happen after the new structure is fully functional. New users won't need this.
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
            **Independent Test**: Can be tested by creating assets in the old format, running migration, and verifying all data is preserved in the new format.
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
            **Acceptance Scenarios**:
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
            1. **Given** assets in old format (`slug/attributes.yml` + `slug/assets/`), **When** migration runs, **Then** metadata is moved to `slug.yml` and asset files remain in `slug/assets/`
         
     | 
| 
      
 54 
     | 
    
         
            +
            2. **Given** an asset with both metadata and files, **When** migration completes, **Then** all data is accessible in the new structure with only the metadata file location changed
         
     | 
| 
      
 55 
     | 
    
         
            +
            3. **Given** migration fails partway through, **When** the error occurs, **Then** the system can resume or rollback without data corruption
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
            ---
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
            ### Edge Cases
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
            - What happens when both old and new structures exist for the same identifier (e.g., `post.yml` and `post/attributes.yml` both present)?
         
     | 
| 
      
 62 
     | 
    
         
            +
            - How does the system handle assets with only metadata (no associated files directory)?
         
     | 
| 
      
 63 
     | 
    
         
            +
            - How does the system handle assets with only files (no metadata file)?
         
     | 
| 
      
 64 
     | 
    
         
            +
            - What happens when identifier contains special characters that might be invalid in filenames?
         
     | 
| 
      
 65 
     | 
    
         
            +
            - How does the system handle nested directories within the asset directory (e.g., `identifier/subdir/file.txt`)?
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
            ## Requirements *(mandatory)*
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
            ### Functional Requirements
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
            - **FR-001**: System MUST read asset metadata from files named `{identifier}.yml` (or .json, .toml, etc.) located at the collection level
         
     | 
| 
      
 72 
     | 
    
         
            +
            - **FR-002**: System MUST read asset files from directories named `{identifier}/assets/` located at the same collection level
         
     | 
| 
      
 73 
     | 
    
         
            +
            - **FR-003**: System MUST write new asset metadata to `{identifier}.yml` format at the collection level
         
     | 
| 
      
 74 
     | 
    
         
            +
            - **FR-004**: System MUST write asset files to `{identifier}/assets/` directory structure
         
     | 
| 
      
 75 
     | 
    
         
            +
            - **FR-005**: System MUST support multiple metadata formats (YAML, JSON, TOML, etc.) based on file extension
         
     | 
| 
      
 76 
     | 
    
         
            +
            - **FR-006**: System MUST correctly identify assets by detecting metadata files with supported extensions
         
     | 
| 
      
 77 
     | 
    
         
            +
            - **FR-007**: System MUST handle assets that have metadata only (no directory)
         
     | 
| 
      
 78 
     | 
    
         
            +
            - **FR-008**: System MUST handle assets that have files only (no metadata file)
         
     | 
| 
      
 79 
     | 
    
         
            +
            - **FR-009**: System MUST provide migration capability to convert from old structure (`identifier/attributes.yml` + `identifier/assets/`) to new structure (`identifier.yml` + `identifier/assets/`)
         
     | 
| 
      
 80 
     | 
    
         
            +
            - **FR-010**: System MUST preserve all data during migration without loss
         
     | 
| 
      
 81 
     | 
    
         
            +
            - **FR-011**: System MUST prefer old structure (`identifier/attributes.yml`) when both old and new structures exist for the same identifier, until explicit migration is performed
         
     | 
| 
      
 82 
     | 
    
         
            +
            - **FR-012**: This is a one-time breaking change requiring a major version bump; migration must be completed before upgrading to the new version
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
            ### Key Entities
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
            - **Asset**: Represents a content item with an identifier, metadata (stored in `{identifier}.yml`), and optional associated files (stored in `{identifier}/` directory)
         
     | 
| 
      
 87 
     | 
    
         
            +
            - **Collection**: A directory containing multiple assets, where each asset consists of a metadata file and optional asset directory at the same level
         
     | 
| 
      
 88 
     | 
    
         
            +
            - **Identifier**: Unique name for an asset within a collection, used as the base name for both the metadata file and asset directory
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
            ## Success Criteria *(mandatory)*
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
            ### Measurable Outcomes
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
            - **SC-001**: Assets can be located by identifier in under 100ms for collections containing up to 10,000 assets
         
     | 
| 
      
 95 
     | 
    
         
            +
            - **SC-002**: Migration completes without data loss for 100% of assets tested
         
     | 
| 
      
 96 
     | 
    
         
            +
            - **SC-003**: New asset structure reduces directory nesting depth by one level (from 3 to 2 levels)
         
     | 
| 
      
 97 
     | 
    
         
            +
            - **SC-004**: Developers can understand the asset structure without documentation within 30 seconds of viewing the directory tree
         
     | 
| 
      
 98 
     | 
    
         
            +
            - **SC-005**: Asset operations (read/write) work correctly for metadata-only assets, file-only assets, and combined assets in 100% of test cases
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
            ## Assumptions
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
            - Asset identifiers are valid filenames in the target filesystem (no special characters that would cause filesystem errors)
         
     | 
| 
      
 103 
     | 
    
         
            +
            - Collections are organized as directories containing multiple assets
         
     | 
| 
      
 104 
     | 
    
         
            +
            - The system already has mechanisms for reading/writing YAML, JSON, or other structured data formats
         
     | 
| 
      
 105 
     | 
    
         
            +
            - The old structure pattern is `{identifier}/attributes.yml` for metadata and `{identifier}/assets/` for files
         
     | 
| 
      
 106 
     | 
    
         
            +
            - Migration is a one-time operation that can be run as a maintenance task before upgrading to the new major version
         
     | 
| 
      
 107 
     | 
    
         
            +
            - System has file I/O capabilities to move/copy files and directories
         
     | 
| 
      
 108 
     | 
    
         
            +
            - Users will complete migration of all assets before upgrading to the new major version
         
     | 
| 
      
 109 
     | 
    
         
            +
            - This breaking change warrants a major version bump (e.g., 1.x.x → 2.0.0)
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
            ## Scope
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
            ### In Scope
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
            - Reading assets in the new structure format
         
     | 
| 
      
 116 
     | 
    
         
            +
            - Writing assets in the new structure format
         
     | 
| 
      
 117 
     | 
    
         
            +
            - Supporting multiple metadata formats (YAML, JSON, TOML, etc.)
         
     | 
| 
      
 118 
     | 
    
         
            +
            - Migrating existing assets from old to new structure
         
     | 
| 
      
 119 
     | 
    
         
            +
            - Handling edge cases (metadata-only, files-only, missing identifiers)
         
     | 
| 
      
 120 
     | 
    
         
            +
             
     | 
| 
      
 121 
     | 
    
         
            +
            ### Out of Scope
         
     | 
| 
      
 122 
     | 
    
         
            +
             
     | 
| 
      
 123 
     | 
    
         
            +
            - Changes to metadata content schema (only structure changes, not content changes)
         
     | 
| 
      
 124 
     | 
    
         
            +
            - Performance optimization beyond basic file I/O
         
     | 
| 
      
 125 
     | 
    
         
            +
            - Validation of metadata content (validation rules remain unchanged)
         
     | 
| 
      
 126 
     | 
    
         
            +
            - User interface changes (this is a structural change only)
         
     | 
| 
      
 127 
     | 
    
         
            +
            - Access control or permissions (security model remains unchanged)
         
     |