Skip to content

Commit 43956ee

Browse files
Merge pull request #15 from alvarosanchez/agent/issue-14-config-profiles
Store repository and profile storage under ~/.config by default
2 parents 88ee0be + be45c38 commit 43956ee

File tree

8 files changed

+120
-30
lines changed

8 files changed

+120
-30
lines changed

AGENTS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,11 @@ Rule of thumb: keep command classes thin; move business logic to services.
122122

123123
## 12) Filesystem and config assumptions
124124
- Default registry file: `~/.config/ocp/config.json`
125-
- Default cache root: `~/.cache/ocp`
125+
- Default repository storage root: `~/.config/ocp`
126126
- Default OpenCode config target: `~/.config/opencode`
127127
- Common system property overrides:
128128
- `ocp.config.dir`
129-
- `ocp.cache.dir`
129+
- `ocp.cache.dir` (legacy storage override)
130130
- `ocp.opencode.config.dir`
131131
- `ocp.working.dir`
132132

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,15 +92,16 @@ When `extends_from` is set, parent profiles are resolved first. Shared JSON/JSON
9292
Default paths:
9393

9494
- Registry: `~/.config/ocp/config.json`
95-
- Cache root: `~/.cache/ocp`
96-
- Local clones: `~/.cache/ocp/repositories/<repo-name>` (using the configured repository name)
95+
- Repository storage root: `~/.config/ocp`
96+
- Local clones: `~/.config/ocp/repositories/<repo-name>` (using the configured repository name)
97+
- Resolved merged profiles: `~/.config/ocp/resolved-profiles/<profile-name>/`
9798
- Backups: `~/.config/ocp/backups/<timestamp>/...`
9899
- OpenCode config target: `~/.config/opencode`
99100

100101
Optional JVM system property overrides:
101102

102103
- `ocp.config.dir`
103-
- `ocp.cache.dir`
104+
- `ocp.cache.dir` (legacy storage override)
104105
- `ocp.opencode.config.dir`
105106
- `ocp.working.dir`
106107

SPEC.md

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,15 @@ ocp profile use my-company
4343
### Default paths
4444

4545
- OCP registry: `~/.config/ocp/config.json`
46-
- OCP cache root: `~/.cache/ocp`
47-
- Repository clone directory: `~/.cache/ocp/repositories/<repo-name>`
48-
- Repository metadata file: `~/.cache/ocp/repositories/<repo-name>/repository.json`
46+
- OCP storage root: `~/.config/ocp`
47+
- Repository clone directory: `~/.config/ocp/repositories/<repo-name>`
48+
- Repository metadata file: `~/.config/ocp/repositories/<repo-name>/repository.json`
49+
- Resolved merged profile directory: `~/.config/ocp/resolved-profiles/<profile-name>/`
4950

5051
### Path overrides (for tests and advanced usage)
5152

5253
- Config directory override: JVM system property `ocp.config.dir`
53-
- Cache directory override: JVM system property `ocp.cache.dir`
54+
- Legacy storage directory override: JVM system property `ocp.cache.dir`
5455
- OpenCode config directory override: JVM system property `ocp.opencode.config.dir`
5556
- Working directory override for local create commands: JVM system property `ocp.working.dir`
5657

@@ -65,7 +66,7 @@ ocp profile use my-company
6566
{
6667
"name": "my-repo",
6768
"uri": "git@github.com:my-company/my-repo.git",
68-
"localPath": "/home/user/.cache/ocp/repositories/my-repo"
69+
"localPath": "/home/user/.config/ocp/repositories/my-repo"
6970
}
7071
]
7172
}
@@ -76,7 +77,7 @@ Rules:
7677
- `config.activeProfile` defaults to `null` (no active profile selected).
7778
- `repositories[*].uri` is required.
7879
- `repositories[*].name` is required.
79-
- `repositories[*].localPath` is derived from cache directory and repository name.
80+
- `repositories[*].localPath` is derived from repository storage directory and repository name.
8081

8182
### `repository.json` schema
8283

@@ -138,7 +139,7 @@ oss/opencode.json
138139
| `ocp profile` | Implemented | Print currently active profile with repository/version metadata and update hints. |
139140
| `ocp profile create [name]` | Implemented | Create profile folder and register it in repository metadata. Defaults to `default` when no name is provided. |
140141
| `ocp profile use <name>` | Implemented | Switch active profile by linking profile files to OpenCode config location. |
141-
| `ocp repository add <uri> --name <name>` | Implemented | Clone repository into local cache and register it in `config.json`. |
142+
| `ocp repository add <uri> --name <name>` | Implemented | Clone repository into local storage and register it in `config.json`. |
142143
| `ocp repository list` | Implemented | Print configured repositories as rounded CLI boxes with name, URI, local clone path, and resolved profile names from each repository metadata file. |
143144
| `ocp repository delete <name>` | Implemented | Remove repository entry from registry and delete local clone. |
144145
| `ocp repository create <name> [--profile-name <profile>]` | Implemented | Initialize new profile repository with `repository.json` and initial profile. |
@@ -150,7 +151,7 @@ oss/opencode.json
150151

151152
- `ocp repository add` requires both URI and repository name.
152153
- Trim URI and repository name before validation and persistence.
153-
- Compute `localPath` from cache directory and configured repository name.
154+
- Compute `localPath` from repository storage directory and configured repository name.
154155

155156
### Profile uniqueness
156157

@@ -169,7 +170,7 @@ oss/opencode.json
169170

170171
### Profile switching and backups (target behavior)
171172

172-
- Source directory: `~/.cache/ocp/repositories/<repo-name>/<profile-name>/`
173+
- Source directory: `~/.config/ocp/repositories/<repo-name>/<profile-name>/`
173174
- Target directory: `~/.config/opencode/`
174175
- For each profile file:
175176
- If target does not exist: create symlink.

src/main/java/com/github/alvarosanchez/ocp/service/ProfileService.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -990,7 +990,7 @@ private List<ProfileEntry> readProfiles(Path profilesFile) {
990990
}
991991

992992
private Path resolvedProfileDirectory(String profileName) {
993-
return cacheDirectory().resolve("resolved-profiles").resolve(profileName);
993+
return profileStorageDirectory().resolve("resolved-profiles").resolve(profileName);
994994
}
995995

996996
private void deleteRecursively(Path path) {
@@ -1012,12 +1012,12 @@ private void deleteRecursively(Path path) {
10121012
}
10131013
}
10141014

1015-
private Path cacheDirectory() {
1015+
private Path profileStorageDirectory() {
10161016
String configuredPath = System.getProperty("ocp.cache.dir");
10171017
if (configuredPath != null && !configuredPath.isBlank()) {
10181018
return Path.of(configuredPath);
10191019
}
1020-
return Path.of(System.getProperty("user.home"), ".cache", "ocp");
1020+
return configDirectory();
10211021
}
10221022

10231023
private Path configDirectory() {

src/main/java/com/github/alvarosanchez/ocp/service/RepositoryService.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ private Path repositoriesFile() {
289289
}
290290

291291
private Path repositoriesDirectory() {
292-
return cacheDirectory().resolve("repositories");
292+
return repositoryStorageDirectory().resolve("repositories");
293293
}
294294

295295
private Path configDirectory() {
@@ -300,12 +300,12 @@ private Path configDirectory() {
300300
return Path.of(System.getProperty("user.home"), ".config", "ocp");
301301
}
302302

303-
private Path cacheDirectory() {
303+
private Path repositoryStorageDirectory() {
304304
String configuredPath = System.getProperty("ocp.cache.dir");
305305
if (configuredPath != null && !configuredPath.isBlank()) {
306306
return Path.of(configuredPath);
307307
}
308-
return Path.of(System.getProperty("user.home"), ".cache", "ocp");
308+
return configDirectory();
309309
}
310310

311311
private Path workingDirectory() {

src/test/java/com/github/alvarosanchez/ocp/command/RepositoryBoxRendererTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ void printWrapsRepositoryDetailsToColumnsLimit() {
2626
ConfiguredRepository repository = new ConfiguredRepository(
2727
"repo-with-very-long-name",
2828
"ssh://git@very.long.company.internal:7999/teams/devops/profiles-and-configurations-with-super-long-identifier.git",
29-
"/Users/alvaro/.cache/ocp/repositories/repo-with-very-long-name/branch-with-very-long-name-and-no-breaks",
29+
"/Users/alvaro/.config/ocp/repositories/repo-with-very-long-name/branch-with-very-long-name-and-no-breaks",
3030
List.of("alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta")
3131
);
3232

src/test/java/com/github/alvarosanchez/ocp/service/ProfileServiceTest.java

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ void useProfileMergesSharedJsonAndKeepsParentOnlyJson() throws IOException {
146146
)
147147
)
148148
);
149-
Path repositoryDir = Path.of(System.getProperty("ocp.cache.dir"), "repositories", "repo-local");
149+
Path repositoryDir = repositoriesRootDirectory().resolve("repo-local");
150150
Path parentDir = repositoryDir.resolve("oca");
151151
Path childDir = repositoryDir.resolve("oca-oh-my-opencode");
152152
Files.createDirectories(parentDir);
@@ -190,7 +190,7 @@ void useProfileMergesJsoncWhenChildContainsComments() throws IOException {
190190
)
191191
)
192192
);
193-
Path repositoryDir = Path.of(System.getProperty("ocp.cache.dir"), "repositories", "repo-local");
193+
Path repositoryDir = repositoriesRootDirectory().resolve("repo-local");
194194
Path parentDir = repositoryDir.resolve("oca");
195195
Path childDir = repositoryDir.resolve("oca-personal");
196196
Files.createDirectories(parentDir);
@@ -230,6 +230,56 @@ void useProfileMergesJsoncWhenChildContainsComments() throws IOException {
230230
);
231231
}
232232

233+
@Test
234+
void useProfileStoresMergedResolvedFilesUnderConfigDirectoryWhenCacheOverrideIsNotConfigured() throws IOException {
235+
String configuredCacheDir = System.getProperty("ocp.cache.dir");
236+
if (configuredCacheDir != null) {
237+
System.clearProperty("ocp.cache.dir");
238+
}
239+
try {
240+
writeRepositoryMetadata(
241+
"repo-local",
242+
new RepositoryConfigFile(
243+
List.of(
244+
new ProfileEntry("parent"),
245+
new ProfileEntry("child", null, "parent")
246+
)
247+
)
248+
);
249+
Path repositoryDir = repositoriesRootDirectory().resolve("repo-local");
250+
Path parentDir = repositoryDir.resolve("parent");
251+
Path childDir = repositoryDir.resolve("child");
252+
Files.createDirectories(parentDir);
253+
Files.createDirectories(childDir);
254+
Files.writeString(parentDir.resolve("opencode.json"), "{\"theme\":\"dark\"}");
255+
Files.writeString(childDir.resolve("opencode.json"), "{\"model\":\"gpt-5\"}");
256+
257+
writeConfig(List.of(new RepositoryEntry("repo-local", "git@github.com:acme/repo-local.git", null)));
258+
profileService = new ProfileService(objectMapper, repositoryService, gitRepositoryClient);
259+
260+
assertTrue(profileService.useProfile("child"));
261+
262+
Path opencodeFile = Path.of(System.getProperty("ocp.opencode.config.dir")).resolve("opencode.json");
263+
assertTrue(Files.isSymbolicLink(opencodeFile));
264+
Path target = Files.readSymbolicLink(opencodeFile);
265+
assertTrue(target.startsWith(Path.of(System.getProperty("ocp.config.dir"), "resolved-profiles", "child")));
266+
} finally {
267+
if (configuredCacheDir == null) {
268+
System.clearProperty("ocp.cache.dir");
269+
} else {
270+
System.setProperty("ocp.cache.dir", configuredCacheDir);
271+
}
272+
}
273+
}
274+
275+
private Path repositoriesRootDirectory() {
276+
String configuredCacheDir = System.getProperty("ocp.cache.dir");
277+
if (configuredCacheDir != null && !configuredCacheDir.isBlank()) {
278+
return Path.of(configuredCacheDir).resolve("repositories");
279+
}
280+
return Path.of(System.getProperty("ocp.config.dir"), "repositories");
281+
}
282+
233283
private void writeConfig(List<RepositoryEntry> repositories) throws IOException {
234284
Path configDir = Path.of(System.getProperty("ocp.config.dir"));
235285
Files.createDirectories(configDir);
@@ -245,7 +295,7 @@ private void writeRepositoryMetadata(String repositoryName, List<String> profile
245295
}
246296

247297
private void writeRepositoryMetadata(String repositoryName, RepositoryConfigFile configFile) throws IOException {
248-
Path repositoryDir = Path.of(System.getProperty("ocp.cache.dir"), "repositories", repositoryName);
298+
Path repositoryDir = repositoriesRootDirectory().resolve(repositoryName);
249299
Files.createDirectories(repositoryDir);
250300
Files.writeString(repositoryDir.resolve("repository.json"), objectMapper.writeValueAsString(configFile));
251301
}

src/test/java/com/github/alvarosanchez/ocp/service/RepositoryServiceTest.java

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ void loadReturnsEmptyWhenConfigFileDoesNotExist() {
6868
}
6969

7070
@Test
71-
void loadNormalizesEntriesUsingRepositoryNameAndCacheDirectory() throws IOException {
71+
void loadNormalizesEntriesUsingRepositoryNameAndConfiguredStorageDirectory() throws IOException {
7272
writeConfig(
7373
new OcpConfigFile(
7474
new OcpConfigOptions(),
@@ -87,17 +87,47 @@ void loadNormalizesEntriesUsingRepositoryNameAndCacheDirectory() throws IOExcept
8787
assertEquals("alpha-repo", repositories.get(0).name());
8888
assertEquals("git@github.com:acme/alpha.git", repositories.get(0).uri());
8989
assertEquals(
90-
Path.of(System.getProperty("ocp.cache.dir"), "repositories", "alpha-repo").toString(),
90+
repositoriesRootDirectory().resolve("alpha-repo").toString(),
9191
repositories.get(0).localPath()
9292
);
9393
assertEquals("custom", repositories.get(1).name());
9494
assertEquals("https://github.com/acme/beta.git", repositories.get(1).uri());
9595
assertEquals(
96-
Path.of(System.getProperty("ocp.cache.dir"), "repositories", "custom").toString(),
96+
repositoriesRootDirectory().resolve("custom").toString(),
9797
repositories.get(1).localPath()
9898
);
9999
}
100100

101+
@Test
102+
void loadNormalizesEntriesUsingConfigDirectoryWhenCacheOverrideIsNotConfigured() throws IOException {
103+
String configuredCacheDir = System.getProperty("ocp.cache.dir");
104+
if (configuredCacheDir != null) {
105+
System.clearProperty("ocp.cache.dir");
106+
}
107+
try {
108+
writeConfig(
109+
new OcpConfigFile(
110+
new OcpConfigOptions(),
111+
List.of(new RepositoryEntry("alpha-repo", "git@github.com:acme/alpha.git", null))
112+
)
113+
);
114+
115+
List<RepositoryEntry> repositories = repositoryService.load();
116+
117+
assertEquals(1, repositories.size());
118+
assertEquals(
119+
Path.of(System.getProperty("ocp.config.dir"), "repositories", "alpha-repo").toString(),
120+
repositories.getFirst().localPath()
121+
);
122+
} finally {
123+
if (configuredCacheDir == null) {
124+
System.clearProperty("ocp.cache.dir");
125+
} else {
126+
System.setProperty("ocp.cache.dir", configuredCacheDir);
127+
}
128+
}
129+
}
130+
101131
@Test
102132
void loadWrapsReadErrorsAsUncheckedIOException() throws IOException {
103133
Path configDir = Path.of(System.getProperty("ocp.config.dir"));
@@ -144,28 +174,36 @@ void listConfiguredRepositoriesIncludesUriLocalPathAndResolvedProfiles() throws
144174
assertEquals("repo-one", repositories.get(0).name());
145175
assertEquals("git@github.com:acme/repo-one.git", repositories.get(0).uri());
146176
assertEquals(
147-
Path.of(System.getProperty("ocp.cache.dir"), "repositories", "repo-one").toString(),
177+
repositoriesRootDirectory().resolve("repo-one").toString(),
148178
repositories.get(0).localPath()
149179
);
150180
assertEquals(List.of("alpha", "beta"), repositories.get(0).resolvedProfiles());
151181

152182
assertEquals("repo-two", repositories.get(1).name());
153183
assertEquals("https://github.com/acme/repo-two.git", repositories.get(1).uri());
154184
assertEquals(
155-
Path.of(System.getProperty("ocp.cache.dir"), "repositories", "repo-two").toString(),
185+
repositoriesRootDirectory().resolve("repo-two").toString(),
156186
repositories.get(1).localPath()
157187
);
158188
assertEquals(List.of("gamma"), repositories.get(1).resolvedProfiles());
159189
}
160190

191+
private Path repositoriesRootDirectory() {
192+
String configuredCacheDir = System.getProperty("ocp.cache.dir");
193+
if (configuredCacheDir != null && !configuredCacheDir.isBlank()) {
194+
return Path.of(configuredCacheDir).resolve("repositories");
195+
}
196+
return Path.of(System.getProperty("ocp.config.dir"), "repositories");
197+
}
198+
161199
private void writeConfig(OcpConfigFile configFile) throws IOException {
162200
Path configDir = Path.of(System.getProperty("ocp.config.dir"));
163201
Files.createDirectories(configDir);
164202
Files.writeString(configDir.resolve("config.json"), objectMapper.writeValueAsString(configFile));
165203
}
166204

167205
private void writeRepositoryMetadata(String repositoryName, RepositoryConfigFile repositoryConfigFile) throws IOException {
168-
Path repositoryPath = Path.of(System.getProperty("ocp.cache.dir"), "repositories", repositoryName);
206+
Path repositoryPath = repositoriesRootDirectory().resolve(repositoryName);
169207
Files.createDirectories(repositoryPath);
170208
Files.writeString(repositoryPath.resolve("repository.json"), objectMapper.writeValueAsString(repositoryConfigFile));
171209
}

0 commit comments

Comments
 (0)