oh-my-space 0.9.1 → 0.10.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.
package/README.md CHANGED
@@ -1,38 +1,27 @@
1
1
  # oh-my-space
2
2
 
3
- `oh-my-space` provides the `oms` CLI for managing external source repositories declared in `oms.yaml`. Each registered repo is checked out as a **git submodule** at `oms/<alias>/`, so its pinned commit, branch, and history are tracked natively by your project — and `oms` wraps the submodule foot-guns (detached HEAD, remote-first branch creation, easy-to-miss pointer moves) behind a small, friendly command set.
3
+ [![npm version](https://img.shields.io/npm/v/oh-my-space.svg)](https://www.npmjs.com/package/oh-my-space)
4
4
 
5
- Package name: `oh-my-space`
5
+ `oh-my-space` is a small CLI for managing multi-repo workspaces with Git submodules.
6
6
 
7
- Command name: `oms`
7
+ Declare external repositories in `oms.yaml` and sync them into `oms/<alias>/`. Your parent project records each repo's exact commit while you work with normal branch, pull, and push flows.
8
8
 
9
- ## Why submodules + a wrapper
9
+ ## When to use it
10
10
 
11
- Git submodules give you three things for free: a **reproducible pin** (the parent records each source's exact commit), **visibility** (`git status` shows when a pointer moved), and **history** (the pointer travels with your commits). What they make awkward is day-to-day branch work. `oms` keeps the native benefits and removes the friction:
12
-
13
- - **Create a new branch locally, with no remote precondition.** `oms switch <alias> <branch>` makes the branch right away even if it does not exist on the remote yet; `oms checkout <alias> <branch>` fetches origin and checks out an existing remote branch as a tracking branch. The remote branch is created lazily on your first `oms push`.
14
- - **Always on a branch, never detached.** `oms sync` attaches the baseline branch at the pinned commit instead of leaving a detached HEAD.
15
- - **Pointer moves stay visible.** `oms pull` / `oms push` stage the updated gitlink in the parent so you can see and commit it; `oms status` surfaces drift.
16
-
17
- > Sharing a pin with your team still requires pushing the commit — that is inherent to any pinning model. `oms` removes the *local* friction; it cannot make an unpushed commit reproducible for others.
11
+ - You work across several repositories from one project workspace and want them checked out side by side.
12
+ - You want each source repo pinned to an exact commit so your workspace stays reproducible.
13
+ - You want to stay on a real branch during everyday submodule work instead of landing in a detached HEAD.
14
+ - You want pointer changes to show up in `git status` so you can review them before committing.
18
15
 
19
16
  ## Requirements
20
17
 
21
- - [Node.js](https://nodejs.org) `>=20.19.0` for running `oms`; development uses the version in `.nvmrc` (`24`)
22
- - git `>=2.40` (`git switch` + submodule commands)
23
- - The workspace must be a git repository (`git init`), since sources are submodules of it.
24
-
25
- For local development:
26
-
27
- ```bash
28
- nvm use
29
- npm ci
30
- npm test
31
- ```
18
+ - [Node.js](https://nodejs.org) `>=20.19.0` to run `oms`.
19
+ - git `>=2.40` for `git switch` and the submodule commands `oms` relies on.
20
+ - Run `oms` from a Git repository. For a new workspace, run `git init` first, since sources are tracked as submodules of it.
32
21
 
33
22
  ## Install
34
23
 
35
- Install globally with your package manager of choice:
24
+ Install `oh-my-space` to use the `oms` command. Install it globally with your package manager of choice:
36
25
 
37
26
  ```bash
38
27
  npm install -g oh-my-space
@@ -43,7 +32,7 @@ bun install -g oh-my-space
43
32
 
44
33
  ## Quick start
45
34
 
46
- Run `oms init` to scaffold a starter `oms.yaml`, or create one by hand in your project root:
35
+ Run `oms init` to scaffold a starter `oms.yaml` in your project root, then edit it down to the repositories you need. A minimal one-repo config looks like this:
47
36
 
48
37
  ```yaml
49
38
  # yaml-language-server: $schema=https://raw.githubusercontent.com/divlook/oh-my-space/main/oms.schema.json
@@ -51,54 +40,71 @@ repos:
51
40
  - alias: api
52
41
  remotes:
53
42
  origin: git@github.com:example/api.git
54
- branch: main
55
- - alias: web
56
- remotes:
57
- origin: git@github.com:example/web.git
58
- upstream: git@github.com:upstream/web.git
43
+ branch: main # optional; defaults to the remote's default branch
59
44
  ```
60
45
 
61
- Sync and work:
46
+ Sync the declared repositories and check their state:
62
47
 
63
48
  ```bash
64
- oms init # scaffold a starter oms.yaml
65
- oms doctor # validate oms.yaml + submodule state
66
- oms sync --list # list registered source repos
67
- oms sync <alias>... # add/initialize submodules on their baseline branch
68
- oms sync --all # sync every registered source repo
69
- oms status # branch / pointer / dirty / ahead-behind per submodule
70
- oms switch <alias> <branch> # switch to a local branch, or create a brand-new one
71
- oms checkout <alias> <branch> # fetch origin, then check out a remote branch (tracking)
72
- oms push <alias>... # push the branch (lazy remote) + stage the pointer
73
- oms push <alias> --remote upstream # target a specific remote (repeatable; defaults to origin)
74
- oms unsync <alias>... # deinitialize and remove a submodule
75
- oms update --check # check whether a newer oms CLI is available
49
+ oms sync --all # add/initialize every declared repo on its baseline branch
50
+ oms status # branch / pointer / dirty / ahead-behind per submodule
51
+ ```
52
+
53
+ ## Layout
54
+
55
+ A workspace with two declared repositories, `api` and `web`, looks like this:
56
+
57
+ ```
58
+ oms.yaml # declares each source repo (alias, remotes, branch)
59
+ .gitmodules # registers each oms/<alias> -> origin url, branch
60
+ oms/
61
+ ├── api/ # git submodule (a normal working tree, on a branch)
62
+ └── web/ # git submodule (a normal working tree, on a branch)
76
63
  ```
77
64
 
78
- For `oms switch` and `oms checkout`, omitting the alias and/or branch prompts you to pick one interactively (synced submodules, and local or `origin/*` branches respectively).
65
+ `oms.yaml` is your declaration, `.gitmodules` and each `oms/<alias>` gitlink are tracked in your project history, and every directory under `oms/` is a normal checked-out repository you can branch, edit, and commit in.
79
66
 
80
- A typical new-branch flow:
67
+ ## Typical branch flow
68
+
69
+ Start a branch, commit inside the submodule, push it, then record the pointer move in your project history. Each command stays in a single Git scope:
81
70
 
82
71
  ```bash
83
- oms switch api feature/login # local branch, no remote needed
84
- # ... edit, commit inside oms/api ...
85
- oms push api # creates origin/feature/login and stages the pointer
86
- git add oms/api && git commit # record the pointer in your project history
72
+ oms switch api feature/login # local branch, no remote needed
73
+ oms commit api -m "feat: add login" # commit inside oms/api (submodule only)
74
+ oms push api # creates origin/feature/login (submodule branch only)
75
+ oms record api # commit the moved oms/api pointer in your project history
87
76
  ```
88
77
 
89
- ## Layout
78
+ `oms commit` and `oms push` stay inside the submodule and never touch the root gitlink; `oms record` is the only command that commits an existing root pointer update. After a successful `oms commit`/`oms pull`/`oms push`, `oms` prints an `oms record <alias>` hint when the pointer has moved. Run `oms status --json` for a machine-readable view of which scope changed.
90
79
 
91
- For an alias `api` checked out on `main`:
80
+ Omit the alias or branch on `oms switch` and `oms checkout` to pick one interactively — synced submodules, and local or `origin/*` branches respectively.
92
81
 
93
- ```
94
- .gitmodules # registers oms/api -> origin url, branch
95
- oms/
96
- └── api/ # git submodule (a normal working tree, on a branch)
97
- ```
82
+ ## How `oms` uses Git submodules
83
+
84
+ `oms` does not replace Git submodules. It adds a small command layer for the workflow details that make submodules awkward. Submodules already give you a reproducible pin (the parent records each source's exact commit), visibility (`git status` shows when a pointer moved), and history (the pointer travels with your commits). The friction is in everyday branch work, and that is what `oms` smooths over:
98
85
 
99
- `.gitmodules` and the `oms/<alias>` gitlink are part of your project history. Submodules must **not** be gitignored; `oms sync` removes a stale `oms/` entry from `.gitignore` if a previous version added one.
86
+ - **Start branches locally.** `oms switch <alias> <branch>` starts a local branch right away, even before it exists on the remote. `oms checkout <alias> <branch>` fetches origin and checks out an existing remote branch as a tracking branch. The remote branch is created lazily on your first `oms push`.
87
+ - **Stay on a branch.** `oms sync` attaches the baseline branch at the pinned commit instead of leaving a detached HEAD, so everyday submodule work never strands you off a branch.
88
+ - **Keep pointer moves visible, commit them explicitly.** `oms pull` and `oms push` synchronize only the submodule branch — they never stage or commit the root gitlink. The moved pointer shows up in `git status`, `oms status` flags when a submodule has drifted from the recorded pointer, and `oms record <alias>` commits that pointer update in the parent repo.
100
89
 
101
- ## Managing source repositories
90
+ Submodules must **not** be gitignored, since the `oms/<alias>` gitlink is what records each pinned commit. `oms sync` removes a stale `oms/` entry from `.gitignore` if a previous version added one.
91
+
92
+ > `oms` makes local submodule work easier, but reproducible sharing still requires pushing the source commit (`oms push`) and recording the parent pointer (`oms record`).
93
+
94
+ ## AI agent workflow
95
+
96
+ When an AI coding agent works in a workspace, the main risk is operating in the wrong Git scope — the root repository versus an `oms/<alias>/` submodule. Two features make that boundary explicit:
97
+
98
+ - **`oms status --json`** prints exactly one machine-readable JSON object on stdout (schema-versioned) describing the workspace root, the current alias, root submodule pointers, and each submodule's branch, dirtiness, and ahead/behind state. An agent can inspect it before deciding where to branch or commit.
99
+ - **`oms agent install`** writes a concise, marker-delimited instruction block into `oms/AGENTS.md` and/or `oms/CLAUDE.md`:
100
+
101
+ ```bash
102
+ oms agent install --target both # or: --target agents | --target claude
103
+ ```
104
+
105
+ These are **root-repository files under `oms/`, not submodule files**. The managed block is delimited by `<!-- OMS START -->` / `<!-- OMS END -->`; content outside the markers is preserved, and `oms agent uninstall` removes only that block (deleting the file if it becomes empty). The files are created but never staged, so you review and commit them yourself.
106
+
107
+ ## Command reference
102
108
 
103
109
  `oms.yaml` declares each source repo with `alias`, a `remotes` mapping (which must include `origin`), and optional `branch` (the baseline).
104
110
 
@@ -106,14 +112,17 @@ oms/
106
112
  | --- | --- | --- | --- |
107
113
  | `oms init` | current directory | Writes a starter `oms.yaml`. | Refuses if `oms.yaml` exists; use `--force`. Does not gitignore `oms/`. |
108
114
  | `oms doctor` | project root or child path | Checks `oms.yaml`, git availability, that the workspace is a git repo, and each alias's submodule state. | Returns exit 2 if any warning is raised. |
109
- | `oms sync <alias>` / `--all` | workspace root | Registers missing repos with `git submodule add`, initializes registered-but-uninitialized ones, fetches, and attaches the baseline branch. | Reproduces the recorded pointer on a fresh clone. |
110
- | `oms status [alias...]` / `--all` | anywhere under root | Prints branch, pointer state (`ok`/`moved`/`uninit`), dirtiness, and ahead/behind for each submodule. | `moved` means the working commit differs from the recorded pointer — stage/commit it. |
115
+ | `oms sync <alias>` / `--all` | workspace root | Registers missing repos with `git submodule add`, initializes registered-but-uninitialized ones, fetches, and attaches the baseline branch. | Reproduces the recorded pointer on a fresh clone. Topology changes (`.gitmodules`, `oms/<alias>`) are left unstaged by default; commit them via the prompt or `--commit` (`chore(oms): add ...`). |
116
+ | `oms status [alias...]` / `--all` | anywhere under root | Prints branch, pointer state (`ok`/`moved`/`uninit`/`missing`/`conflict`), dirtiness, and ahead/behind for each submodule. | `moved` means the working commit differs from the recorded pointer — record it with `oms record`. `--json` prints one machine-readable object on stdout for tooling and agents. |
117
+ | `oms commit [alias]` | workspace root or inside `oms/<alias>/` | Commits source changes inside the selected submodule only; never the root gitlink. | `-m <message>` is required (repeatable). Commits existing staged changes as-is, otherwise stages all with `git add -A`. Infers the alias from the current `oms/<alias>/` directory. |
118
+ | `oms record [alias]` | workspace root or inside `oms/<alias>/` | Commits an existing root gitlink pointer update for one alias (`chore(oms): update <alias> submodule to <sha>`). | Root repo only, path-limited to `oms/<alias>`; refuses unrelated staged changes. Not for adds/removals — use `oms sync`/`oms unsync`. |
111
119
  | `oms switch [alias] [branch]` | workspace root | `git switch` to a LOCAL branch, creating it locally if it does not exist yet (no remote required). | `--from <ref>` sets the start point for a new branch. Omit alias/branch to pick interactively (or create a new branch). |
112
120
  | `oms checkout [alias] [branch]` | workspace root | `git fetch origin --prune`, then check out a REMOTE branch (`origin/*`) as a local tracking branch (or switch to an existing local counterpart). | Omit alias/branch to pick interactively. To create a brand-new local branch, use `oms switch`. |
113
121
  | `oms fetch ...` | workspace root | `git fetch <remote> --prune` in each submodule. | `--remote <name>` (repeatable) picks the remote(s); omit to choose interactively, defaults to `origin`. |
114
- | `oms pull ...` | workspace root | `git pull --ff-only <remote>` on each submodule's current branch, then stages the moved pointer. | Requires the submodule to be on a branch. `--remote <name>` selects a single remote (defaults to `origin`). |
115
- | `oms push <alias>...` | workspace root | `git push <remote> <branch>` (creating the remote branch on first push), then stages the moved pointer. | `--remote <name>` (repeatable) picks the remote(s), defaults to `origin`; upstream is set only for `origin`. `--commit` also commits the pointer update in the parent. |
116
- | `oms unsync <alias>` / `--all` | workspace root | `git submodule deinit` + `git rm` for the alias; drops an empty `.gitmodules`. | Keeps the `oms.yaml` entry. Use `--force` to discard uncommitted changes. |
122
+ | `oms pull ...` | workspace root | `git pull --ff-only <remote>` on each submodule's current branch. | Submodule branch only — never stages or commits the root gitlink. Rejects a dirty submodule; prints an `oms record <alias>` hint when the pointer moves. `--remote <name>` selects a single remote (defaults to `origin`). |
123
+ | `oms push <alias>...` | workspace root | `git push <remote> <branch>` (creating the remote branch on first push). | Submodule branch only — never stages or commits the root gitlink. `--commit`/`--record` are unsupported; record the root pointer with `oms record <alias>`. `--remote <name>` (repeatable) picks the remote(s); upstream is set only for `origin`. |
124
+ | `oms unsync <alias>` / `--all` | workspace root | `git submodule deinit` + `git rm` for the alias; drops an empty `.gitmodules`. | Keeps the `oms.yaml` entry. Use `--force` to discard uncommitted changes. Removal topology is left unstaged by default; commit via the prompt or `--commit` (`chore(oms): remove ...`). |
125
+ | `oms agent install` / `uninstall` | workspace root | Manages a marker-delimited OMS instruction block in `oms/AGENTS.md` and/or `oms/CLAUDE.md` (root-repo files). | `--target agents\|claude\|both` (omit to choose interactively). Does not stage files. See [AI agent workflow](#ai-agent-workflow). |
117
126
  | `oms update` | anywhere | Checks the npm registry and safely updates the installed `oms` CLI only when it detects a confident global install. | Use `--check` for a non-mutating check. Use `--yes` to skip the confirmation prompt for confident global updates. Project-local, temporary runner, development, and unknown installs print guidance only. |
118
127
 
119
128
  ## Updating the CLI
@@ -162,12 +171,39 @@ Rules:
162
171
  - Remaining characters: ASCII lowercase letters, digits, `-`, `_`, `@`.
163
172
  - Not allowed: uppercase letters, `/`, `\`, `.`, whitespace.
164
173
  - Pattern: `/^[a-z0-9][a-z0-9_@-]*$/`.
165
- - `remotes` is required and must include an `origin` entry; each value is a clonable git URL. `origin` becomes the submodule's primary remote, and additional remotes are configured on `oms sync`.
166
- - `branch` is optional; when omitted the remote's default branch is used as the baseline.
174
+ - `remotes` is required and must include an `origin` entry. Each value is a clonable git URL. `origin` becomes the submodule's primary remote, and additional remotes are configured on `oms sync`.
175
+ - `branch` is optional. When omitted, the remote's default branch is used as the baseline.
167
176
 
168
177
  JSON schema: [`oms.schema.json`](./oms.schema.json) (also reachable at `https://raw.githubusercontent.com/divlook/oh-my-space/main/oms.schema.json` for YAML LSPs).
169
178
 
170
- ## Migrating between versions
179
+ ## Behavior changes
180
+
181
+ The AI-submodule-workflow update scopes each command to a single Git boundary and makes root pointer commits explicit. If you have existing automation, note these changes:
182
+
183
+ - **`oms push --commit` is removed** (and `oms push --record` is unsupported). It fails before pushing. Push the submodule branch, then record the root pointer separately:
184
+
185
+ ```bash
186
+ # before
187
+ oms push api --commit
188
+
189
+ # after
190
+ oms push api # pushes the submodule branch only
191
+ oms record api # commits the moved oms/api pointer in the parent
192
+ ```
193
+
194
+ - **`oms pull` and `oms push` no longer stage the root gitlink.** They synchronize only the submodule branch; the moved pointer shows up in `git status` and is committed with `oms record <alias>`.
195
+
196
+ - **`oms sync` and `oms unsync` no longer leave the root index staged.** Topology changes (`.gitmodules` and `oms/<alias>`) stay in the working tree, unstaged. Create the topology commit through the interactive prompt or with `--commit`:
197
+
198
+ ```bash
199
+ # before: sync left .gitmodules + oms/api staged for you to commit
200
+ oms sync api && git commit -m "add api"
201
+
202
+ # after: topology is unstaged by default
203
+ oms sync api --commit # creates chore(oms): add api submodule
204
+ ```
205
+
206
+ ## Migration guides
171
207
 
172
208
  Detailed migration steps are organized per version under [`docs/migrations/`](./docs/migrations/).
173
209
 
@@ -176,6 +212,16 @@ Detailed migration steps are organized per version under [`docs/migrations/`](./
176
212
  - [0.3.x → 0.4.0](./docs/migrations/0.3.x-to-0.4.0.md) — renames `sources.yaml`/`sources/` to `oms.yaml`/`oms/`
177
213
  - [0.2.x → 0.3.0](./docs/migrations/0.2.x-to-0.3.0.md) — (historical) switched submodules to bare clone + worktrees
178
214
 
215
+ ## Local development
216
+
217
+ This repository targets the Node.js version in [`.nvmrc`](./.nvmrc) (`24`). After cloning:
218
+
219
+ ```bash
220
+ nvm use
221
+ npm ci
222
+ npm test
223
+ ```
224
+
179
225
  ## License
180
226
 
181
227
  [MIT](./LICENSE)
@@ -1 +1 @@
1
- {"commit":"3c598f24e0cfc453bc81728553b0c40fb1b34591"}
1
+ {"commit":"0cc84f79dc3659f53fd76a39f9e5ecbe33337651"}