migration — from REPOS.json + .scripts/ to grex
Users on the legacy Python .scripts/ meta-repo migrate by running grex import --from-repos-json ./REPOS.json. Both systems can coexist during transition.
Legacy source system
repo/
├── .scripts/
│ ├── init.py add.py rm.py sync.py track.py test.py
│ ├── lib/
│ └── hooks/
├── REPOS.json # [{url, path}, ...]
└── .gitignore # hand-curated, sub-repo dirs appended
REPOS.json shape:
[
{"url": "https://github.com/grex-org/grex-tui.git", "path": "grex-tui"},
{"url": "https://github.com/grex-org/grex-core.git", "path": "grex-core"}
]
Legacy shell native scripts (.ps1/.sh) are irrelevant in grex — Rust std::fs + built-in actions replace them.
Import command
grex import --from-repos-json ./REPOS.json
Behavior:
- Read + parse
REPOS.json. Validateurlandpath(bare name) on every entry. - For each entry not already in
grex.jsonl(bypath), emit anaddevent withtype: meta(or--default-type <...>). - For each entry already present with matching URL, skip.
- For each entry with same
pathbut differenturl, abort unless--force(then emitupdate). - Optionally
--migrate-gitignore: rewrite.gitignoreto use the managed-block format, preserving pre-existing lines outside the managed region.
grex import --from-repos-json ./REPOS.json --migrate-gitignore
Idempotent: re-running is a no-op once imported.
Disk-scan variant
grex import --scan
Walks workspace root one level deep, detects directories with .git/ not yet in grex.jsonl. For each, reads git config --get remote.origin.url, emits an add event. Skips entries without a remote.
Combinable with --from-repos-json: both sources processed, deduplicated by path.
Pack type for imported entries
Legacy REPOS.json carries no pack type info. Default assumption:
- If the imported dir contains a
.grex/pack.yaml, use its declaredtype. - Else use
--default-type(flag), which defaults tometa(safe: meta packs have no actions, so no surprise side effects on first install).
User can later convert to declarative or scripted by adding a .grex/pack.yaml in the imported pack's own repo.
From v1.1.1+, plain-git children (no .grex/pack.yaml) walk via synthetic-manifest fallback — grex import --from-repos-json followed by grex sync works end-to-end on the bootstrap pattern (REPOS.json + flat-sibling git repos). See pack-spec.md §"Plain-git children" for the synthesis rule.
Coexistence during transition
Both systems can run against the same workspace if:
.scripts/remains in place unmodified.grex.jsonlis added alongsideREPOS.json..gitignoreis in managed-block format and lists everypathfrom BOTH sources.
grex doctor in coexistence mode:
- Warns (non-fatal) if
REPOS.jsonhas entries missing fromgrex.jsonl. - Warns if
.scripts/is still present whilegrex.jsonlexists. - Suggests running
grex import --from-repos-jsonor retiring.scripts/.
Disambiguation rules
Same path in both sources:
REPOS.json | grex.jsonl | Action |
|---|---|---|
| present | absent | add event emitted |
| present (url A) | present (url A) | no-op |
| present (url A) | present (url B) | error, abort without --force |
| absent | present | no-op |
| present | tombstoned | skip, log info |
--force resolves URL conflicts by emitting update from the REPOS.json value.
Path rule transition
Legacy REPOS.json required bare path (no separators). v1 grex preserves this. Nested paths (e.g. packs/foo) are deferred to v1.x; will require path-normalization + collision detection.
Retirement of .scripts/
Post-migration:
- Verify
grex lsmatches expected pack list. - Run
grex sync --parallel 8. - Delete
.scripts/viagit rm -r .scripts/. - Delete
REPOS.json. git config core.hooksPath .grex/hooks(grex installs these oninit).- Commit.
grex doctor after retirement should exit 0 on clean workspace.
Rollback
Nothing in grex import mutates .scripts/ or REPOS.json. Rollback = delete grex.jsonl + grex.lock.jsonl + revert .gitignore (if --migrate-gitignore used). No data loss path.