1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
|
#+TITLE: Project Consolidation — Fold ~/projects AI Projects into Home — Spec
#+AUTHOR: Craig Jennings
#+DATE: 2026-06-11
* Metadata
| Status | Ready — Codex spec-review confirmed 2026-06-11 |
| Owner | Craig Jennings |
| Reviewer | Codex (spec-review, 2026-06-11) |
| Related | [[file:../todo.org::*Project consolidation into home][todo.org task]] |
* Summary
Fold every AI-managed project under =~/projects/= (except =work=) into the =home= project as area subdirectories, with full git history preserved, so all personal tasks live in one =todo.org= and can be prioritized against each other. The end state: =~/projects/= holds =home= and =work=; =~/code/= holds standalone code projects. Every step is reversible — originals are snapshotted, server bare repos stay untouched until a cooling-off period ends, and a written restore procedure covers both single-project and full rollback.
* Problem / Context
Craig manages 12 AI projects under =~/projects/= beside =home= and =work=. Each has its own =.ai/= session machinery, =todo.org=, inbox, memory dir, and git repo on cjennings.net. That isolation was the design — but the life-management projects (finances, jr-estate, danneel, health, kit, clipper, documents…) are all facets of one life, and their tasks compete for the same hours. Today there is no single surface where a [#A] in jr-estate can be weighed against a [#A] in finances; each project's priorities are graded against siblings only. Sessions fragment the same way: a morning touching finances, danneel, and home means three separate session launches, three inboxes, three memory stores, and cross-project handoff files between them.
The forces: (1) one prioritization surface requires one task file (or at least one repo); (2) these projects are critical — legal disputes, estate settlement, finances — so the migration must be provably reversible; (3) per-project git histories carry evidentiary and reference value (especially danneel and jr-estate) and must survive queryably; (4) the =.ai/= ecosystem (sessions, memories, inboxes, workflows) has per-project state that must merge without loss; (5) other machines (velox at minimum) hold clones whose remotes must not break silently.
Survey of the candidates (2026-06-11):
| Project | What it is | .git | Sessions | Open tasks | Memories | Inbox |
|------------------+---------------------------------------------+-------+----------+------------+----------+-------|
| clipper | 19 Clipper St SF rental property | 23M | 6 | 1 | 0 | 0 |
| danneel | 4319 Danneel construction dispute (legal) | 613M | 45 | 10 | 0 | 0 |
| documents | Disaster-prep document vault | 61M | 7 | 8 | 0 | 2 |
| elibrary | Ebook + music library management | 2.4M | 6 | 0 | 2 | 3 |
| fb-photo-scraper | Third-party FB gallery scraper (clone) | 3.4M | 0 | 0 | 0 | 0 |
| finances | Personal finance (Craig + Christine) | 251M | 24 | 43 | 1 | 4 |
| health | Personal health management | 5.9M | 27 | 8 | 1 | 3 |
| jr-estate | JR estate settlement (legal, trust, taxes) | 704M | 44 | 18 | 7 | 2 |
| kit | Keep In Touch — relationship management | 17M | 20 | 20 | 3 | 4 |
| little-elisper | The Little LISPer worked in elisp (study) | 4.7M | 2 | 0 | 0 | 0 |
| philosophy | Philosophy study + discussion notes | 30M | 5 | 0 | 0 | 0 |
| website | Personal Hugo site (deployed from homelab) | 8.6M | 10 | 11 | 0 | 3 |
All have origin on cjennings.net except fb-photo-scraper (GitHub clone). All =.ai/= dirs are tracked (personal-project model). No project has a live =session-context.org=. Several have small dirty trees (elibrary 2, finances 1, health 1, kit 4, little-elisper 1 files) that must be resolved pre-fold.
* Goals and Non-Goals
** Goals
- One repo, one =todo.org=, one priority scheme, one =.ai/= session for all personal (non-work) project management.
- Full git history of every folded project preserved and queryable in place (=git log <area>/= works).
- All =.ai/= state merged without loss: session archives, memories, inboxes, someday-maybe, project workflows/scripts.
- A written, tested restore path for any single project and for the whole migration.
- =~/code/= becomes the only home for standalone code projects.
** Non-Goals
- No restructuring of home's existing content (homelab docs, assets, music reconciliation dirs stay where they are; re-nesting them under an =infra/= area is vNext).
- No change to the =work= project in any way.
- No task content rewriting beyond re-grading priorities to the unified scheme — bodies, links, and histories move as-is.
- No server-side bare-repo deletion during the migration (archival is a separate, later, post-cooling step).
- No renaming of the =home= project.
** Scope tiers
- v1: fold the nine life-management projects (clipper, danneel, documents, elibrary, finances, health, jr-estate, kit, philosophy); relocate the code-shaped three (website, little-elisper, fb-photo-scraper) per Decision 2; ecosystem updates (emacs agenda, rulesets references, velox).
- Out of scope: work; any =~/code/= project; home's internal restructure.
- vNext: server bare-repo archival after cooling-off; optional =infra/= re-nesting of homelab content; per-area README normalization.
* Design
** End-state layout
Each folded project becomes a top-level area directory in home, preserving its internal structure:
#+begin_example
~/projects/home/
clipper/ danneel/ documents/ elibrary/
finances/ health/ jr-estate/ kit/ philosophy/
assets/ docs/ homelab-inventory/ inbox/ scripts/ (existing home content, unchanged)
todo.org (unified)
.ai/ (single session machinery)
#+end_example
This matches the established area pattern (work's =deepsat/assets/=, the working-files convention's =<area>/assets/=). Each area keeps its own =assets/=, =docs/=, internal org files, and a per-area =.gitignore= carrying its old ignore patterns (git honors nested ignores; prefixing patterns into the root ignore is error-prone).
** Git history — filter-repo then merge
For each source project, on a throwaway clone (never the original). =--no-local= forces a real transport-style clone instead of hardlinked/shared objects — the conservative shape git-filter-repo's manual recommends, and what makes the clone a genuine fresh-clone safety check. =branch= is the source's actual default branch (fb-photo-scraper is on =master=; assume nothing):
#+begin_example
branch=$(git -C ~/projects/<name> symbolic-ref --short HEAD)
git clone --no-local ~/projects/<name> /tmp/fold-<name>
cd /tmp/fold-<name>
git filter-repo --to-subdirectory-filter <name>
cd ~/projects/home
git remote add fold-<name> /tmp/fold-<name>
git fetch fold-<name>
git merge --allow-unrelated-histories -m "feat(<name>): fold <name> project into home" "fold-<name>/$branch"
git remote remove fold-<name>
#+end_example
=git filter-repo --force= is not part of this runbook. If filter-repo refuses to run, the fresh-clone safety check failed — stop, write down why, and fix the clone rather than overriding.
=filter-repo= rewrites every historical path under =<name>/=, so after the merge =git log <name>/somefile= shows the file's full history with original commit messages, authors, and dates. The original repo and its server bare are never touched — the rewrite happens on the temp clone only.
The merged home =.git= grows to roughly 1.8G (dominated by danneel 613M + jr-estate 704M + finances 251M). Acceptable for a private single-user server; noted as a clone-time cost.
** The .ai/ and task merge (per project, after the git merge)
The git merge lands the project's files under =<name>/=, including its old =.ai/= and =todo.org=. A post-merge commit then redistributes that state:
1. /Sessions:/ =git mv <name>/.ai/sessions/*= into home's =.ai/sessions/=, inserting the area into the name: =YYYY-MM-DD-HH-MM-<desc>.org= → =YYYY-MM-DD-HH-MM-<name>-<desc>.org=. Dated names make collisions near-impossible; the prefix preserves provenance.
2. /todo.org:/ append the project's open work as a new top-level section =* <Area> Open Work= (matching =* Home Open Work=), and its resolved section likewise. Each imported section heading carries a properties drawer marking provenance — =:MIGRATED_FROM: <name>= and =:MIGRATED_ON: YYYY-MM-DD= — so the import boundary stays visible to future edits and to the restore runbook. Walk the incoming tasks with Craig to re-grade priorities onto the unified scheme (home's A-D impact/urgency ladder, generalized beyond infra) and add an area tag (=:finances:=, =:jrestate:=, =:danneel:=, …). Then delete =<name>/todo.org=.
3. /notes.org:/ the project's Project-Specific Context moves to =<name>/notes.org= (area-local reference, linked from home's =.ai/notes.org=). Active Reminders and Pending Decisions merge into home's =.ai/notes.org= with area attribution.
4. /Inbox:/ process each project's inbox to zero before the fold (preferred), or move unprocessed items into home's =inbox/= renamed =YYYY-MM-DD-from-<name>-<orig>.ext=.
5. /Workflows + scripts:/ copy =<name>/.ai/project-workflows/*= and =project-scripts/*= into home's, after a filename-collision check (13 workflow files exist across sources; any collision is resolved by area-prefixing the incoming file). Then delete the area's old =.ai/= machinery (=protocols.org=, =workflows/=, =scripts/= — all template-synced duplicates).
6. /someday-maybe.org:/ append under an =* <Area>= header in home's.
7. /Memory:/ copy =~/.claude/projects/-home-cjennings-projects-<name>/memory/*.md= into home's memory dir (rename on slug collision), append index lines to =MEMORY.md=, dedupe against existing entries, then archive the source memory dir into the backup tar (it lives outside git).
8. /Links:/ =grep -rn "projects/<name>" ~/projects/home ~/.emacs.d ~/sync/org ~/org/roam= and fix every absolute reference to the new path. Record the before-count, fix, re-run, and classify any remaining hits as historical (session archives, this spec) or live — live hits block the fold's close. Relative links inside the area survive the move untouched because internal structure is preserved.
** Per-fold manifest
Every fold produces a tracked manifest at =docs/consolidation-manifest-<name>.org=, written as the fold proceeds and committed with the redistribution commit. The manifest is the restore contract and the verification record — without it, "every step is reversible" is a slogan. It records:
- /Source state:/ source path, HEAD sha, branch, =git status --porcelain=v1= output at gate time (must be empty), origin URL.
- /Tracked universe:/ the =git ls-files= listing from the source (or a sha256 of it, with the listing in an appendix block). After the merge, every path must exist under =home/<name>/= or appear in the redistribution map below.
- /Untracked, ignored, and inbox inventories:/ each file listed with its explicit disposition — processed, moved (to where), archived, or intentionally dropped. Nothing leaves the source tree without a line here.
- /Redistribution map:/ sessions moved (old → new names), todo.org section markers added (=:MIGRATED_FROM:= headings), notes/someday-maybe merges, workflows/scripts copied (with collision resolutions), memory files copied + =MEMORY.md= lines added, link rewrites made (=path:line=, before → after).
- /Commits:/ the fold merge commit sha and the redistribution commit sha.
- /Retired path:/ where the source dir went.
Verification per fold checks path lists against this manifest, not file counts — a count can pass while losing files and fail on intentional redistribution.
** Safety net and restore
Layered, oldest-to-newest:
- /Layer 0 — filesystem snapshot./ Before anything: =snapper create --description pre-consolidation= (ratio's root is btrfs with snapper) plus a belt-and-suspenders tar of the memory dirs: =tar czf ~/backups/claude-memory-pre-consolidation-$(date +%F).tgz -C ~/.claude projects=. Content-only restore is sufficient for the memory tar — memory files are plain-text markdown the harness reads by path; no permissions, ACL, or xattr metadata is load-bearing, so plain =tar czf= is the contract.
- /Layer 1 — server bare repos untouched./ Origin repos on cjennings.net remain exactly as they are through v1. They hold every byte of every project's history independent of anything done locally.
- /Layer 2 — pre-fold tags./ Home gets =git tag pre-consolidation= before the first fold and =git tag pre-fold-<name>= before each subsequent one, pushed to origin.
- /Layer 3 — retired dirs./ After a fold is verified, the source dir moves to =~/projects/.retired/<name>= (not deleted). Deleted only after the cooling-off period.
- /Cooling-off:/ 30 days minimum after the final fold, and not before velox is migrated. Only then does vNext server archival (move bares to =~/git/archive/=) become eligible.
*** Restore one project — two modes
/Mode A — resurrect standalone (leaves home alone)./ =git clone cjennings@cjennings.net:git/<name>.git ~/projects/<name>=, restore its memory dir from the tar. The folded copy in home stays as a harmless duplicate (or is removed later via Mode B). This is the fast path when the need is "I want the project back," not "the fold was wrong."
/Mode B — remove the folded state from home./ A fold is two commits plus out-of-git side effects; removal must unwind all of it, using the manifest:
1. =git revert <redistribution-commit>= then =git revert -m 1 <merge-commit>=, in that order (newest first). This is the supported path while the fold is recent — before later edits touch the shared files.
2. If either revert conflicts (todo.org and =.ai/notes.org= are hot files — expected once home has moved on), abort it and instead excise manually from the manifest's redistribution map: delete the =:MIGRATED_FROM: <name>= todo.org sections, the area-prefixed session files, the copied workflows/scripts, and =git rm -r <name>/= — one removal commit citing the manifest.
3. Out-of-git effects either way: delete the copied memory files and their =MEMORY.md= index lines (named in the manifest); re-fix any link rewrites if the old path is coming back.
/Restore everything:/ snapper rollback (or restore the snapshot's =~/projects/=), restore the memory tar, =git reset --hard pre-consolidation= on home plus a coordinated forced push — acceptable on a single-user remote, with the tag as the anchor.
** Multi-machine
velox (and any other machine with clones) keeps working against the untouched server bares until its own migration step: pull home (which brings all folded content), then retire its local =~/projects/<name>= clones the same way. Nothing breaks in the interim — the old remotes still exist; they're just frozen. The =ai= launcher discovers projects by =.ai/protocols.org= presence, so retired dirs (moved under =.retired/=, outside its scan roots) drop out automatically. =inbox-send= targets shrink the same way; any rulesets doc or workflow that names a folded project as a handoff target gets updated in the final phase.
* Alternatives Considered
** Keep separate repos, unify only the agenda (org-agenda-files spanning all todo.orgs)
- Good, because zero migration risk and Craig's emacs agenda can already span files.
- Bad, because it solves only prioritization-viewing, not management: 10 sessions, 10 inboxes, 10 memory stores remain; Claude still can't see or rebalance the whole picture in one session; cross-project handoffs persist.
- Bad, because priority schemes stay divergent per file.
- Neutral, because it could serve as an interim state, but it builds nothing toward the end goal.
** git subtree add per project
- Good, because one command per fold, no external tooling.
- Bad, because history isn't path-rewritten: =git log <name>/file= doesn't follow into pre-merge history without =--follow= gymnastics, weakening the evidentiary value of danneel/jr-estate histories.
- Neutral, because content-wise the result is identical; only history ergonomics differ.
** Import working trees only, archive old repos (no history merge)
- Good, because the home repo stays small and the procedure is trivially simple.
- Bad, because in-place history is lost — every "when did this clause change" question requires resurrecting an archived repo.
- Neutral, because Layer-1 bares preserve history regardless; this is about whether history is /at hand/.
** One new "life" super-repo instead of growing home
- Good, because a clean slate avoids home's existing 109M history and infra identity.
- Bad, because home is already the hub (biggest session history, the template patterns, Craig's habits) and would itself need folding in — strictly more work for a cosmetic gain.
* Decisions
** D1 — Merge strategy: filter-repo + merge per project
- State: accepted
- Context: critical legal/financial histories must stay queryable in place; restore must be possible regardless.
- Decision: We will fold each project with =git filter-repo --to-subdirectory-filter= on a temp clone, merged with =--allow-unrelated-histories=.
- Consequences: easier — full per-area history in one repo, originals untouched; harder — home =.git= grows to ~1.8G, and =git-filter-repo= becomes a migration dependency (AUR: =git-filter-repo=).
** D2 — Disposition of the code-shaped three
- State: accepted (Craig, 2026-06-11)
- Context: website (Hugo codebase), little-elisper (code study), fb-photo-scraper (third-party clone, no .ai) are code-shaped, and the target model says code lives standalone in =~/code/=.
- Decision: We will move website and little-elisper to =~/code/= as standalone AI projects (plain =mv= + memory-dir rename, same as the homelab→home rename runbook), and delete fb-photo-scraper (it's an unmodified upstream clone — re-cloneable from =https://github.com/budavariam/traverse_facebook_galleries.git=, branch =master=; recorded here so the proof survives the deletion).
- Consequences: easier — =~/projects/= reaches the clean end state (home + work); harder — website's 11 open tasks stay in their own todo.org, outside the unified prioritization (acceptable: they're code tasks, not life tasks).
** D3 — Unified todo.org: one file, per-area top-level sections
- State: accepted
- Context: cross-area prioritization wants one surface; the staleness script, agenda, and review workflows all operate on one file today.
- Decision: We will keep a single =todo.org= with =* <Area> Open Work= top-level sections mirroring =* Home Open Work=, unified under home's A-D priority scheme, with area tags on every imported task.
- Consequences: easier — one review rotation, one grep, one agenda file covers everything; harder — the file grows to roughly 5-6k lines (~110 incoming open tasks), so reads lean on Grep/offset and the section discipline matters more.
** D4 — Per-area task triage at fold time
- State: accepted
- Context: each project graded priorities against siblings only; merging without re-grading would make cross-area priorities meaningless.
- Decision: We will walk each incoming area's open tasks with Craig at fold time, re-grading to the unified scheme (the task-review walk shape, applied per area).
- Consequences: easier — the unified list is trustworthy from day one; harder — the big folds (finances at 43 tasks) cost a real review session each.
** D5 — Cooling-off before any destruction
- State: accepted
- Context: "if it goes south, I need a way to restore."
- Decision: We will destroy nothing for 30 days after the final fold: source dirs go to =~/projects/.retired/=, server bares stay, snapshots and tags persist. fb-photo-scraper deletion (D2) is the one exception — it's an unmodified upstream clone.
- Consequences: easier — every layer of the restore path stays live through the risky window; harder — ~2G of retired duplicates sit on disk for a month.
* Implementation phases
Each phase ends with a working tree, a pushed commit, and a verification gate. One project per session is the expected pace; phases 3+ are repetitions of the runbook proven in phase 2.
** Phase 0 — Pre-flight (global prep; gates per project at its turn)
Install =git-filter-repo=. Snapper snapshot + memory-dir tar. Tag and push =pre-consolidation= on home.
The *live inventory gate* lives at =docs/consolidation-manifest-inventory.org= and is regenerated *per project, immediately before that project's fold* — not all at once. Craig keeps using not-yet-folded projects (staged freeze), so a single up-front table would go stale by Phase 3; the per-fold regeneration is the gate. Fields per project: default branch, origin URL, dirty count (=git status --porcelain=), ahead/behind vs upstream, inbox file count, =session-context.org= presence, memory file count, project-workflow/-script names (collision candidates), and disposition (fold / relocate / delete / out-of-scope). The gate passes only when: clean tree, ahead/behind 0/0, inbox empty, no live session-context. A failing project gets fixed (wrap, commit, process, push) and its row regenerated before its fold begins.
D2 is resolved (2026-06-11); no open decisions remain in this phase.
** Phase 1 — Pilot fold: philosophy
Smallest life project, no todo.org, no memories, empty inbox. Run the full fold runbook (git merge + redistribution + manifest + verification). Then the *rollback drill*: in a disposable =--no-local= clone of home (or a throwaway branch), run the Mode-B removal runbook against the pilot's manifest and verify the folded state is fully gone — sessions, workflows, =<name>/= tree. Discard the clone. The legal and financial folds must never be the first test of the removal story. This phase hardens the runbook appendix; expect to amend the spec from what's learned (history entry, not rewrite).
** Phase 2 — Dress rehearsal: clipper
Nearly as small (23M, no memories, empty inbox) but adds the one runbook path the pilot can't exercise: the todo.org import — =* Clipper Open Work= section, =:MIGRATED_FROM:= markers, and a one-task triage with Craig. After this phase every runbook step has run at least once except the memory merge (premieres in Phase 3; lowest-risk step — plain file copies outside git, tar-backed).
** Phase 3 — jr-estate
The priority fold. 704M history, 18-task triage, 44 sessions, and the first memory merge (7 files). One session.
** Phase 4 — danneel
613M history, 10-task triage, 45 sessions, 1 project-workflow. One session.
** Phase 5 — finances
251M history and the heaviest triage (43 tasks). One session, possibly two if the triage runs long.
** Phase 6 — Remaining folds + code relocations (together)
Fold documents, elibrary, health, kit (~36 incoming tasks, elibrary/health/kit memories, kit's 4 project-workflows collision-checked). Move website and little-elisper to =~/code/= (mv + memory-dir rename per the homelab→home runbook); delete fb-photo-scraper (origin recorded in D2). After this phase =~/projects/= contains home, work, and =.retired/=.
** Phase 7 — Ecosystem pass
Fix every absolute-path reference (emacs config, org-roam, agenda-files, bookmarks, rulesets docs naming folded projects as inbox-send/cross-agent targets). Migrate velox (pull home, retire its clones). Write the KB node recording the new layout. Start the 30-day cooling clock; file a dated vNext task for server bare archival and =.retired/= deletion.
* Acceptance criteria
- [ ] =~/projects/= contains exactly =home=, =work=, and =.retired/=.
- [ ] For each folded project, =git log --oneline <name>/ | tail= in home shows its earliest original commits.
- [ ] Manifest parity per fold: every path in the source's =git ls-files= listing exists under =home/<name>/= or appears in the manifest's redistribution map; every untracked/ignored/inbox file has a recorded disposition. Path-list comparison, not counts.
- [ ] =todo.org= passes org-lint; every imported task carries an area tag and an A-D priority Craig re-graded; imported sections carry =:MIGRATED_FROM:= markers.
- [ ] =task-review-staleness.sh --list todo.org 20= run after each fold surfaces the imported area's tasks as depth-2 review units alongside existing areas (proves the rotation spans the whole list).
- [ ] Per fold: source memory basenames diffed against home's memory dir and =MEMORY.md= entries — all accounted for; source memory dirs are in the backup tar.
- [ ] Link-rewrite check per fold: the before-grep count is recorded in the manifest, and the after-grep over =~/.emacs.d ~/sync/org ~/org/roam ~/projects/home= returns only hits classified historical (session archives, this spec) — zero live links.
- [ ] Layer-1 restore drill passes: clone one folded project from its untouched server bare into =/tmp=, confirm it's whole.
- [ ] Mode-B rollback drill (Phase 1 pilot) passes: the pilot fold is fully removable from a disposable clone using only its manifest.
- [ ] A fresh Claude session in home can answer "what are my top 5 tasks across all areas?" from the unified todo.org.
- [ ] velox runs a clean session in home post-migration with no stale-remote errors.
* Readiness dimensions
- Data model & ownership: every file keeps its owner (Craig); =.ai/= state redistributes per the merge map above; memory dirs are the one store outside git — covered by the tar in Phase 0 and the copy step per fold.
- Errors, empty states & failure: every fold step is git-tracked, so a failed fold is =git reset --hard <pre-fold tag>= plus re-running from the temp clone (which is rebuilt from scratch each attempt). filter-repo failures abort before anything touches home.
- Security & privacy: all content stays on the private cjennings.net remote; no new exposure surface. The merged repo concentrates sensitive material (legal + financial + health) in one clone — same machines, same threat model as today.
- Observability: each fold is one merge commit + one redistribution commit, tagged; progress is the phase checklist in todo.org; verification gates are the acceptance criteria run per-fold.
- Performance & scale: ~1.8G final =.git=; clone cost noted. todo.org at 5-6k lines stays well within Grep/offset workflows. No runtime performance surface.
- Reuse & lost opportunities: reuses the homelab→home rename runbook (memory-dir rename, link sweep), the task-review walk for triage, snapper for snapshots, and the established area-dir pattern. git-filter-repo over hand-rolled rewrites.
- Architecture fit & weak points: area dirs match the working-files convention. Weak point: the =.ai/= redistribution is manual and per-project — mitigated by the pilot phase hardening a written runbook before the critical folds.
- Config surface: none — no knobs. The one dependency is the =git-filter-repo= package.
- Documentation plan: this spec is the migration doc; the fold runbook and manifest template live in the appendix below (refined by the Phase 2 pilot); the KB node in Phase 6 records the end state for all future agents.
- Dev tooling: N/A because the migration is one-shot; the runbook commands in Design are the tooling.
- Rollout, compatibility & rollback: staged per-project rollout, multi-machine sequencing (velox last), layered rollback (snapshot / untouched bares / tags / retired dirs), 30-day cooling before any destruction. Dry-run equivalent: the pilot fold.
- External APIs & deps: =git-filter-repo= (AUR, stable, widely used — verify installed in Phase 0). No network APIs.
* Risks, Rabbit Holes, and Drawbacks
- /Link rot is the long tail./ Absolute =file:= links to old project paths can lurk in org-roam, emacs bookmarks, calendar event descriptions, and Keep notes. The Phase 6 grep covers the file-based stores; Keep and calendar references can't be grepped — accept that stragglers get fixed on encounter.
- /todo.org scale./ 5-6k lines is fine for tools, but the agenda view gets dense. If it becomes noise, the vNext escape hatch is per-area =#+CATEGORY= or splitting resolved sections to an archive file — not re-splitting projects.
- /Triage fatigue./ Re-grading ~110 tasks is the human bottleneck. Mitigation: it's split across the fold phases, and each area's walk uses the existing 7-at-a-time review muscle.
- /Workflow collisions./ 13 project-workflow files across sources; names look distinct but the check is mandatory per fold.
- /The merged repo is a bigger blast radius./ A bad force-push or corrupting operation now touches everything. Mitigation: the same layered backups, plus home already carries this responsibility for its own content.
- /Drawback accepted:/ per-project session isolation disappears — one project's noisy session history now shares a dir with everything. The area prefix on archived session names keeps provenance.
* Appendix — Fold runbook (per project)
Refined by the Phase 2 pilot; until then this is the v0 contract. Every step either succeeds with the expected output or the fold stops — no improvising past a failed step.
1. /Gate./ Regenerate the project's row in the live inventory (Phase 0 fields). Require: clean =git status --porcelain=, ahead/behind 0/0, inbox empty, no =session-context.org=. Fix and regenerate, or stop.
2. /Manifest open./ Create =docs/consolidation-manifest-<name>.org= from the template below; fill source state and the =git ls-files= listing; inventory untracked/ignored files with dispositions. The Redistribution row reads "the commit introducing this manifest" — a commit sha can't be embedded in its own commit (pilot learning, 2026-06-11); the merge sha is known beforehand and is recorded literally.
3. /Tag./ =git tag pre-fold-<name> && git push origin pre-fold-<name>=.
4. /History fold./ The filter-repo + merge block from Design (with =--no-local=, =$branch=, no =--force=). Record the merge sha in the manifest.
5. /Redistribute./ Steps 1-8 of the =.ai/= and task merge map, recording each move in the manifest's redistribution map as it happens. One commit; record its sha.
6. /Verify./ Manifest parity (path lists), org-lint on todo.org, staleness-script check, memory diff, link before/after grep. All green or the fold stops here for repair.
7. /Retire./ =mv ~/projects/<name> ~/projects/.retired/<name>=; record the path. Push home.
** Manifest template
#+begin_example
,#+TITLE: Consolidation manifest — <name>
| Source path | ~/projects/<name> |
| Origin | <url> |
| Branch / HEAD | <branch> / <sha> |
| Gate state | clean / 0-0 / inbox 0 / no session-context |
| Merge commit | <sha> |
| Redistribution | <sha> |
| Retired to | ~/projects/.retired/<name> |
,* Tracked universe
<git ls-files output, or sha256 + appendix>
,* Untracked / ignored / inbox dispositions
| file | disposition (processed / moved-to / archived / dropped) |
,* Redistribution map
- Sessions: <old> → <new> …
- todo.org: section ":MIGRATED_FROM: <name>" added at <heading>
- Workflows/scripts copied: <names + collision resolutions>
- Memory: <files copied> + MEMORY.md lines added
- Link rewrites: <file:line before → after>
,* Link grep
- Before: <count> | After: <count, all classified historical>
#+end_example
* Review dispositions
Findings from the 2026-06-11 Codex review (review file deleted on processing per spec-response). Modified items below; *everything else was accepted as written* — H1 (manifest + redistribution-aware restore), H2 (path-list verification, porcelain gate, find-prune fix by removal), H3 (=--no-local=, no =--force=), H4 (live inventory gate), M2 (pilot rollback drill), M3 (fb-photo-scraper re-clone pointer), the UX provenance markers, the documentation appendix, and the memory/link verification commands.
- /M1 (memory tar metadata) — modified:/ the review offered preserving metadata or declaring content-only sufficient. Chose content-only: memory files are plain-text markdown the harness reads by path; no permissions/ACL/xattr metadata is load-bearing. Stated in Layer 0 rather than adding =--xattrs --acls=.
- /Test strategy item 2 (staleness fixture test) — modified:/ a live =task-review-staleness.sh --list todo.org 20= check after each fold replaces a new fixture-based unit test. The script already has its own bats suite; the migration-specific question ("are imported area tasks depth-2 review units?") is answered better by the live check on the real file, per fold, than by a one-shot fixture.
- /Open question 2 (revert vs manifest-removal as the supported runbook) — modified:/ the "choose one" framing doesn't survive the time axis. Supported path: revert both commits (redistribution first) while the fold is recent; once shared files have moved on and reverts conflict, the manifest-driven removal commit is the path. Both are now written in Restore Mode B; the manifest makes the fallback safe, which is why it exists.
* Review and iteration history
** 2026-06-11 Thu @ 15:11:32 -0500 — Claude Code (home, with Craig) — author
- What changed: re-sequenced the implementation phases to Craig's chosen order — philosophy pilot, clipper dress rehearsal (added as its own phase so the todo.org-import path is proven before real data), then jr-estate → danneel → finances by urgency, with the remaining folds + code relocations merged into one closing phase before the ecosystem pass. Phase 0's live inventory gate is now explicitly per-project-at-its-turn, matching the staged-freeze approach (Craig keeps using not-yet-folded projects until their turn).
- Why: Craig wants the critical legal/financial projects consolidated early after a proven runbook, and chose staged freeze over a full shutdown. The clipper rehearsal closes the gap where the pilot (no todo.org) never exercises task import.
- Artifacts: todo.org phase tasks re-sequenced to match.
** 2026-06-11 Thu @ 14:13:49 -0500 — Codex — reviewer
- What changed or was recommended: assigned =Ready= after re-running spec-review against the incorporated spec; no further blocking review notes and no new review file.
- Why: the prior blockers are now covered by the per-fold manifest contract, live inventory gate, =--no-local= filter-repo runbook, redistribution-aware restore, Phase 2 rollback drill, and manifest-based acceptance criteria.
- Artifacts: this spec; [[file:../todo.org::*Project consolidation into home][todo.org tracking task]] updated with Ready status and implementation phase tasks.
** 2026-06-11 Thu @ 13:20:54 -0500 — Claude Code (home) — responder
- What changed: all four blocking findings accepted and woven in — =--no-local= + no-=--force= runbook with a =$branch= variable (H3, H4's master/main catch), a Per-fold manifest section as the restore/verification contract (H1, H2), two-mode single-project restore covering the redistribution commit (H1), Phase 0 live inventory gate (H4), Phase 2 Mode-B rollback drill (M2), manifest-parity acceptance criteria replacing file counts (H2), fb-photo-scraper re-clone pointer in D2 (M3), =:MIGRATED_FROM:= provenance markers (UX), runbook appendix + manifest template (docs). Three points modified with reasons in Review dispositions; nothing rejected.
- Why: the review's core finding was right — restore and verification only covered the history merge, not the redistribution commit and the untracked-file universe. The manifest is the single artifact that fixes both.
- Artifacts: review file (deleted on processing); dispositions section above; todo.org tracking task updated.
** 2026-06-11 Thu @ 13:09:29 -0500 — Codex — reviewer
- What changed or was recommended: assigned =Not ready= and wrote a blocking review focused on exact rollback semantics, per-fold manifests, live inventory gating, =git clone --no-local= for filter-repo safety, and stronger verification than raw file counts.
- Why: the design direction is sound, but implementation would still require inventing how to unwind redistributed =.ai/=, =todo.org=, inbox, and memory state safely after each fold.
- Artifacts: review file deleted during the response pass; retained via the dispositions section above.
** 2026-06-11 Thu @ 12:08:03 -0500 — Claude (with Craig) — author
- What: initial draft.
- Why: Craig asked for a consolidation design with restore guarantees — one prioritization surface for all personal projects.
- Artifacts: survey data gathered live from ~/projects on ratio; todo.org task cross-linked.
|