oh-my-space 0.9.1 → 0.11.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,94 @@ 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
76
51
  ```
77
52
 
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).
53
+ ## Layout
79
54
 
80
- A typical new-branch flow:
55
+ A workspace with two declared repositories, `api` and `web`, looks like this:
81
56
 
82
- ```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
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)
87
63
  ```
88
64
 
89
- ## Layout
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.
66
+
67
+ ## Typical branch flow
90
68
 
91
- For an alias `api` checked out on `main`:
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:
92
70
 
71
+ ```bash
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
93
76
  ```
94
- .gitmodules # registers oms/api -> origin url, branch
95
- oms/
96
- └── api/ # git submodule (a normal working tree, on a branch)
77
+
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.
79
+
80
+ Omit the alias or branch on `oms switch` and `oms checkout` to pick one interactively — synced submodules, and local or `origin/*` branches respectively.
81
+
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:
85
+
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.
89
+
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
+ ## Workspace skills
108
+
109
+ `oms` publishes three installable agent skills that carry the workspace Git guardrail to sessions started anywhere in the workspace — including at the root, which the [`oms agent install`](#ai-agent-workflow) marker under `oms/` cannot reach. They are installed with the external Vercel Labs `skills` tool (`npx skills`):
110
+
111
+ ```bash
112
+ npx skills add divlook/oh-my-space # project scope: install at the workspace root
113
+ npx skills add divlook/oh-my-space -g # global scope: available in every workspace
114
+ npx skills add divlook/oh-my-space --skill oms-pointer # install one skill by name
115
+ npx skills add divlook/oh-my-space --list # list the available skills without installing
97
116
  ```
98
117
 
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.
118
+ Project scope is the default and recommended: these skills are only relevant in an `oms.yaml` workspace, so a project install keeps them out of unrelated repositories. `oms skills` prints these commands, and `oms skills --install` runs the project install for you, resolving to the workspace root first and forwarding extra arguments (`-g`, `--skill`, `--list`, `--copy`) straight through.
100
119
 
101
- ## Managing source repositories
120
+ The three skills are named by the Git domain each manages:
121
+
122
+ | Skill | Use it when | What it does |
123
+ | --- | --- | --- |
124
+ | `oms-workspace` | Scope-ambiguous Git work in the workspace — committing from the root, a moved `oms status` pointer, a push, or adding/removing a repo with `oms sync`/`oms unsync`. | Establishes workspace state and root-versus-submodule scope before acting, and separates repo add/remove topology from recording a moved pointer. |
125
+ | `oms-pointer` | After `oms commit` or `oms pull` moves a submodule's commit. | Records the moved root pointer with `oms record`, so a submodule change is not left without a recorded pointer and the root pointer is not committed by mistake. |
126
+ | `oms-branch` | Starting or switching a branch inside a submodule. | Chooses `oms switch` (new local branch) versus `oms checkout` (track a remote branch) and avoids detached HEAD. |
127
+
128
+ Skill firing is best-effort — an agent loads a skill only when it judges the skill's description relevant — so the skills complement, rather than replace, `oms <command> --help` and the always-on marker block inside `oms/`. Each skill defers exact `oms status --json` field semantics to `oms status --help`, the version-matched authoritative source that ships with the installed CLI.
129
+
130
+ ## Command reference
102
131
 
103
132
  `oms.yaml` declares each source repo with `alias`, a `remotes` mapping (which must include `origin`), and optional `branch` (the baseline).
104
133
 
@@ -106,14 +135,18 @@ oms/
106
135
  | --- | --- | --- | --- |
107
136
  | `oms init` | current directory | Writes a starter `oms.yaml`. | Refuses if `oms.yaml` exists; use `--force`. Does not gitignore `oms/`. |
108
137
  | `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. |
138
+ | `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 ...`). |
139
+ | `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. |
140
+ | `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. |
141
+ | `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
142
  | `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
143
  | `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
144
  | `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. |
145
+ | `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`). |
146
+ | `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`. |
147
+ | `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 ...`). |
148
+ | `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). |
149
+ | `oms skills` | anywhere (`--install` resolves to root) | Prints the `npx skills add divlook/oh-my-space` commands (project scope and `-g` global) to install the workspace skills. | `--install` delegates to `npx skills add`, forwarding extra args (`-g`, `--skill <name>`, `--list`). Run outside a workspace without `-g`, it errors and points to the global install. See [Workspace skills](#workspace-skills). |
117
150
  | `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
151
 
119
152
  ## Updating the CLI
@@ -162,20 +195,31 @@ Rules:
162
195
  - Remaining characters: ASCII lowercase letters, digits, `-`, `_`, `@`.
163
196
  - Not allowed: uppercase letters, `/`, `\`, `.`, whitespace.
164
197
  - 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.
198
+ - `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`.
199
+ - `branch` is optional. When omitted, the remote's default branch is used as the baseline.
167
200
 
168
201
  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
202
 
170
- ## Migrating between versions
203
+ ## Migration guides
171
204
 
172
205
  Detailed migration steps are organized per version under [`docs/migrations/`](./docs/migrations/).
173
206
 
207
+ - [0.9.x → 0.10.0](./docs/migrations/0.9.x-to-0.10.0.md) — scopes each command to a single Git boundary and makes root pointer commits explicit via `oms record`
174
208
  - [0.7.x → 0.8.0](./docs/migrations/0.7.x-to-0.8.0.md) — splits `oms checkout` into `oms switch` (local branches) and `oms checkout` (remote branches)
175
209
  - [0.5.x → 0.6.0](./docs/migrations/0.5.x-to-0.6.0.md) — switches the data model from bare clone + worktrees back to git submodules
176
210
  - [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
211
  - [0.2.x → 0.3.0](./docs/migrations/0.2.x-to-0.3.0.md) — (historical) switched submodules to bare clone + worktrees
178
212
 
213
+ ## Local development
214
+
215
+ This repository targets the Node.js version in [`.nvmrc`](./.nvmrc) (`24`). After cloning:
216
+
217
+ ```bash
218
+ nvm use
219
+ npm ci
220
+ npm test
221
+ ```
222
+
179
223
  ## License
180
224
 
181
225
  [MIT](./LICENSE)
@@ -1 +1 @@
1
- {"commit":"3c598f24e0cfc453bc81728553b0c40fb1b34591"}
1
+ {"commit":"c69910168d4bea81b7b02079ee868a5d99b3cf2d"}