Skip to content

Commit ea51278

Browse files
authored
feat: implement multi-destination extraction with fan-out support (#8)
Adds ability to extract files to multiple destinations simultaneously using repeated --to flags: - Made --to option repeatable to accept multiple destination paths - Added PathNormalizer.deduplicate() for destination deduplication - Implemented fail-fast validation across all destinations before copying - Added per-destination success output showing files extracted to each location - Extended clean mode to support multi-destinations * chore: mark multi-destination extraction complete with 571 tests passing Updated roadmap and Phase 3 documentation to mark 012-multi-destination-extraction as complete with 571 tests passing. Added changelog entry for v1.8.0 release. Updated spec status from Draft to Complete. Added Type Change Checklist to tasks template and optional Validation Steps section to spec template.
1 parent bfc1252 commit ea51278

File tree

21 files changed

+2977
-254
lines changed

21 files changed

+2977
-254
lines changed

.specify/memory/roadmap.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Product Roadmap: Subtree CLI
22

3-
**Version:** v1.7.0
3+
**Version:** v1.8.0
44
**Last Updated:** 2025-11-30
55

66
## Vision & Goals
@@ -34,7 +34,7 @@ Simplify git subtree management through declarative YAML configuration with safe
3434
- ✅ Multi-Pattern Extraction (5 user stories, 439 tests)
3535
- ✅ Extract Clean Mode (5 user stories, 477 tests)
3636
-**Brace Expansion: Embedded Path Separators** (4 user stories, 526 tests)
37-
- **Multi-Destination Extraction** — Fan-out to multiple `--to` paths
37+
- **Multi-Destination Extraction** (5 user stories, 571 tests) — Fan-out to multiple `--to` paths
3838
- ⏳ Lint Command — Configuration integrity validation
3939

4040
## Product-Level Metrics & Success Criteria
@@ -80,6 +80,7 @@ Simplify git subtree management through declarative YAML configuration with safe
8080

8181
## Change Log
8282

83+
- **v1.8.0** (2025-11-30): Multi-Destination Extraction complete (012-multi-destination-extraction) with 571 tests; fan-out to multiple `--to` paths, fail-fast validation, clean mode parity, bulk support (MINOR — feature complete)
8384
- **v1.7.0** (2025-11-30): Brace Expansion complete (011-brace-expansion) with 526 tests; embedded path separators, cartesian product, bash pass-through semantics (MINOR — feature complete)
8485
- **v1.6.0** (2025-11-29): Added Brace Expansion and Multi-Destination Extraction to Phase 3; marked Multi-Pattern Extraction and Extract Clean Mode complete (MINOR — new features)
8586
- **v1.5.0** (2025-11-27): Roadmap refactored to multi-file structure; added Multi-Pattern Extraction and Extract Clean Mode to Phase 3 (MINOR — new features, structural improvement)

.specify/memory/roadmap/phase-3-advanced-operations.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ Enable portable configuration validation, selective file extraction with compreh
8282
- Nested braces, escaping, numeric ranges deferred to backlog
8383
- **Delivered**: All 4 user stories (basic expansion, multiple groups, pass-through, empty alternative errors), 526 tests passing
8484

85-
### 6. Multi-Destination Extraction (Fan-Out) ⏳ PLANNED
85+
### 6. Multi-Destination Extraction (Fan-Out) ✅ COMPLETE
8686

8787
- **Purpose & user value**: Allows extracting matched files to multiple destinations simultaneously (e.g., `--to Lib/ --to Vendor/`), enabling distribution of extracted files to multiple locations without repeated commands
8888
- **Success metrics**:
@@ -95,7 +95,10 @@ Enable portable configuration validation, selective file extraction with compreh
9595
- Fan-out semantics: N files × M destinations = N×M copy operations
9696
- Directory structure preserved at each destination
9797
- YAML schema: `to: ["path1/", "path2/"]` for persisted mappings
98-
- Atomic per-destination: all files to one destination succeed or fail together
98+
- Fail-fast validation: all destinations checked upfront before any writes
99+
- PathNormalizer deduplicates equivalent paths (`Lib/`, `./Lib`, `Lib` → single destination)
100+
- Backward compatible: single `--to` and existing YAML configs unchanged
101+
- **Delivered**: All 5 user stories (CLI multi-dest, persist arrays, clean mode, fail-fast, bulk support), 571 tests passing
99102

100103
### 7. Lint Command ⏳ PLANNED
101104

@@ -116,7 +119,7 @@ Enable portable configuration validation, selective file extraction with compreh
116119
3. Multi-Pattern Extraction ✅
117120
4. Extract Clean Mode ✅
118121
5. Brace Expansion in Patterns ✅
119-
6. Multi-Destination Extraction
122+
6. Multi-Destination Extraction
120123
7. Lint Command ⏳ (final Phase 3 feature)
121124
- **Rationale**: Brace Expansion and Multi-Destination extend pattern capabilities before Lint validates all operations
122125
- **Cross-phase dependencies**: Requires Phase 2 Add Command for subtrees to exist
@@ -127,7 +130,7 @@ This phase is successful when:
127130
- All seven features complete and tested
128131
- Extract supports multiple patterns and cleanup operations
129132
- Lint provides comprehensive integrity validation
130-
- 600+ tests pass on macOS and Ubuntu (currently 526, growing)
133+
- 600+ tests pass on macOS and Ubuntu (currently 571, growing)
131134

132135
## Risks & Assumptions
133136

@@ -138,6 +141,7 @@ This phase is successful when:
138141

139142
## Phase Notes
140143

144+
- 2025-11-30: Multi-Destination Extraction complete (012-multi-destination-extraction) with 571 tests; 5 user stories delivered
141145
- 2025-11-30: Brace Expansion complete (011-brace-expansion) with 526 tests; 4 user stories delivered
142146
- 2025-11-29: Added Brace Expansion and Multi-Destination Extraction features
143147
- 2025-11-29: Extract Clean Mode complete (010-extract-clean) with 477 tests; dry-run/preview mode deferred to Phase 5 backlog

.specify/templates/spec-template.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,17 @@
113113
- **SC-002**: [Measurable metric, e.g., "System handles 1000 concurrent users without degradation"]
114114
- **SC-003**: [User satisfaction metric, e.g., "90% of users successfully complete primary task on first attempt"]
115115
- **SC-004**: [Business metric, e.g., "Reduce support tickets related to [X] by 50%"]
116+
117+
## Validation Steps *(optional)*
118+
119+
<!--
120+
If this feature requires manual validation beyond automated tests, document the steps here.
121+
Otherwise, explicitly state: "All validation covered by automated tests."
122+
123+
This section helps ensure quickstart.md or manual QA steps are planned upfront.
124+
-->
125+
126+
- [ ] Step 1: [Manual validation step, e.g., "Run `command --flag` and verify output shows X"]
127+
- [ ] Step 2: [Manual validation step, e.g., "Create config with Y and verify behavior Z"]
128+
129+
*Or state:* All validation covered by automated tests.

.specify/templates/tasks-template.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,3 +249,18 @@ With multiple developers:
249249
- Commit after each task or logical group
250250
- Stop at any checkpoint to validate story independently
251251
- Avoid: vague tasks, same file conflicts, cross-story dependencies that break independence
252+
253+
---
254+
255+
## Type Change Checklist
256+
257+
When changing a field's type (e.g., `String?``[String]}`, `Int``Int?`), verify:
258+
259+
- [ ] All `!= nil` checks updated to appropriate emptiness checks (e.g., `.isEmpty`)
260+
- [ ] All `guard let` bindings updated to new type
261+
- [ ] All comparisons updated (e.g., `== nil``.isEmpty`)
262+
- [ ] All initializers updated to match new type
263+
- [ ] Run `swift build` (or equivalent) to catch remaining issues
264+
- [ ] Update tests to use new type format
265+
266+
**Rationale**: Type changes propagate through the codebase. Missing updates cause lint errors mid-implementation.

README.md

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,17 @@ subtree extract --name mylib \
165165
--from "src/**/*.c" \
166166
--to vendor/
167167

168+
# Multi-destination extraction (012) - fan-out to multiple locations
169+
subtree extract --name mylib \
170+
--from "**/*.h" \
171+
--to Lib/include/ \
172+
--to Vendor/headers/
173+
174+
# Combined: multi-pattern + multi-destination (cartesian product)
175+
subtree extract --name mylib \
176+
--from "*.h" --from "*.c" \
177+
--to Lib/ --to Vendor/
178+
168179
# Brace expansion (011) - compact patterns with {alternatives}
169180
subtree extract --name mylib --from "*.{h,c,cpp}" --to Sources/
170181
subtree extract --name mylib --from "{src,test}/*.swift" --to Sources/
@@ -177,8 +188,8 @@ subtree extract --name crypto-lib \
177188
# With exclusions (applies to all patterns)
178189
subtree extract --name mylib --from "src/**/*.c" --to Sources/ --exclude "**/test/**"
179190

180-
# Save multi-pattern mapping for future use
181-
subtree extract --name mylib --from "include/**/*.h" --from "src/**/*.c" --to vendor/ --persist
191+
# Save multi-destination mapping for future use
192+
subtree extract --name mylib --from "**/*.h" --to Lib/ --to Vendor/ --persist
182193

183194
# Execute saved mappings from subtree.yaml
184195
subtree extract --name example-lib
@@ -193,6 +204,9 @@ Remove previously extracted files with checksum validation:
193204
# Clean specific files (validates checksums before deletion)
194205
subtree extract --clean --name mylib --from "src/**/*.c" --to Sources/
195206

207+
# Clean from multiple destinations (012)
208+
subtree extract --clean --name mylib --from "**/*.h" --to Lib/ --to Vendor/
209+
196210
# Clean all saved mappings for a subtree
197211
subtree extract --clean --name mylib
198212

@@ -258,6 +272,7 @@ subtree validate --with-remote
258272
- `--persist` - Save mapping to subtree.yaml
259273
- `--force` - Overwrite git-tracked files / force delete modified files
260274
- `--clean` - Remove extracted files (validates checksums first)
275+
- Multi-destination: Use `--to` multiple times for fan-out extraction
261276

262277
- **`validate`** - Verify subtree integrity
263278
- `--name <name>` - Validate specific subtree
@@ -287,16 +302,28 @@ subtrees:
287302
squash: true # Default: true
288303
commit: 0123456789abcdef... # Latest known commit
289304
extractions: # File extraction mappings
290-
# Single pattern (legacy format)
305+
# Single pattern, single destination (legacy format)
291306
- from: "docs/**/*.md"
292307
to: Docs/
293-
# Multi-pattern (009) - array format
308+
# Multi-pattern (009) - union extraction
294309
- from:
295310
- "include/**/*.h"
296311
- "src/**/*.c"
297312
to: vendor/
298313
exclude:
299314
- "**/test/**"
315+
# Multi-destination (012) - fan-out to multiple locations
316+
- from: "**/*.h"
317+
to:
318+
- Lib/include/
319+
- Vendor/headers/
320+
# Combined: multi-pattern + multi-destination
321+
- from:
322+
- "*.h"
323+
- "*.c"
324+
to:
325+
- Lib/
326+
- Vendor/
300327
```
301328
302329
## Platform Compatibility

0 commit comments

Comments
 (0)