Skip to content

Commit b46cd17

Browse files
authored
Merge pull request #699 from code-payments/chore/fetch-protos-skill
chore: add fetch-protos skill
2 parents 6061847 + 9d0bb77 commit b46cd17

1 file changed

Lines changed: 236 additions & 0 deletions

File tree

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
---
2+
name: fetch-protos
3+
description: >
4+
Fetch latest protobuf definitions, verify build, summarize API changes,
5+
and scaffold new service stubs. Usage: /fetch-protos [flipcash|opencode] [commit_sha]
6+
user-invocable: true
7+
argument-hint: "[flipcash|opencode] [commit_sha]"
8+
allowed-tools:
9+
- Bash
10+
- Read
11+
- Edit
12+
- Write
13+
- Glob
14+
- Grep
15+
- Agent
16+
---
17+
18+
# Fetch Protos
19+
20+
Fetch protobuf definitions from upstream repos, verify they compile, summarize
21+
API changes, and scaffold missing service layer implementations.
22+
23+
## Pre-flight context
24+
25+
- Current proto files: !`find definitions/*/protos/src/main/proto -name "*.proto" 2>/dev/null | wc -l | tr -d ' '` proto files across targets
26+
- Git status: !`git status --short definitions/`
27+
28+
## Input
29+
30+
Parse `$ARGUMENTS` to determine targets and optional commit SHA.
31+
32+
**Rules:**
33+
- Known targets: `flipcash`, `opencode`
34+
- If no targets specified, fetch **both** (`flipcash` and `opencode`)
35+
- A hex string (7+ chars) as the last argument is treated as a commit SHA
36+
- Examples:
37+
- `/fetch-protos` → fetch flipcash + opencode at HEAD
38+
- `/fetch-protos flipcash` → fetch flipcash only
39+
- `/fetch-protos opencode abc1234` → fetch opencode at commit abc1234
40+
- `/fetch-protos flipcash opencode` → fetch both explicitly
41+
42+
## Steps
43+
44+
### Step 1 — Fetch protos
45+
46+
For each target, run the fetch script from the repo root:
47+
48+
```bash
49+
bash scripts/fetch-protos.sh -t <target> [commit_sha]
50+
```
51+
52+
Target-to-repo mapping (handled by the script):
53+
| Target | Repository |
54+
|--------|-----------|
55+
| `flipcash` | `git@github.com:code-payments/flipcash2-protobuf-api.git` |
56+
| `opencode` | `git@github.com:code-payments/ocp-protobuf-api.git` |
57+
58+
Show the script output to the user.
59+
60+
### Step 2 — Diff and summarize changes
61+
62+
Run `git diff` on the proto directories to identify what changed:
63+
64+
```bash
65+
git diff --stat definitions/
66+
git diff definitions/
67+
```
68+
69+
For each changed `.proto` file, summarize:
70+
- **New RPCs** added to services
71+
- **Modified RPCs** (changed request/response types or fields)
72+
- **Removed RPCs**
73+
- **New/modified messages** and fields
74+
75+
Present a structured change summary table to the user. If nothing changed, report
76+
that protos are already up to date and stop here.
77+
78+
### Step 3 — Build verification
79+
80+
Build the definitions modules to verify the protos compile:
81+
82+
```bash
83+
./gradlew :definitions:flipcash:models:assembleDebug :definitions:opencode:models:assembleDebug
84+
```
85+
86+
Only build the targets that were fetched. If the build fails, show errors and stop.
87+
88+
### Step 4 — Detect service layer impact
89+
90+
For each new or modified RPC found in Step 2:
91+
92+
1. Identify which service proto file it belongs to (e.g., `account/v1/flipcash_account_service.proto`)
93+
2. Search for the corresponding Api class in `services/<target>/src/**/network/api/`
94+
3. Check if a method exists for the RPC
95+
4. If the RPC is new, check whether Service, Repository, and Controller layers also need updates
96+
97+
Present a report:
98+
99+
| RPC | Api | Service | Repository | Controller | Status |
100+
|-----|-----|---------|------------|------------|--------|
101+
| `NewRpc` | missing | missing | missing | missing | **New — needs scaffolding** |
102+
| `ModifiedRpc` | exists | exists | exists | exists | **Signature may need update** |
103+
104+
### Step 5 — Scaffold new service stubs
105+
106+
For RPCs marked as needing scaffolding, ask the user if they want to scaffold them.
107+
If confirmed, generate code following the patterns below.
108+
109+
#### Api method pattern
110+
111+
Location: `services/<target>/src/main/kotlin/.../internal/network/api/<ServiceName>Api.kt`
112+
113+
```kotlin
114+
// @Singleton class with @Inject constructor taking qualified ManagedChannel
115+
// private val api = XxxGrpcKt.XxxCoroutineStub(managedChannel).withWaitForReady()
116+
117+
suspend fun newRpc(owner: KeyPair, ...): RpcServiceName.NewRpcResponse {
118+
val request = RpcServiceName.NewRpcRequest.newBuilder()
119+
.apply { setAuth(authenticate(owner)) } // or .apply { setSignature(sign(owner)) }
120+
// ... set other fields
121+
.build()
122+
123+
request.validate().orThrow()
124+
125+
return withContext(Dispatchers.IO) {
126+
api.newRpc(request)
127+
}
128+
}
129+
```
130+
131+
Key conventions:
132+
- Use `authenticate(owner)` for Flipcash endpoints (returns `Common.Auth`)
133+
- Use `sign(owner)` for OpenCode endpoints (returns `Model.Signature`)
134+
- Always call `request.validate().orThrow()` before the RPC
135+
- Always dispatch on `Dispatchers.IO`
136+
- Return raw proto response type
137+
138+
#### Service method pattern
139+
140+
Location: `services/<target>/src/main/kotlin/.../internal/network/services/<ServiceName>Service.kt`
141+
142+
```kotlin
143+
// internal class with @Inject constructor(private val api: XxxApi)
144+
145+
suspend fun newRpc(owner: KeyPair, ...): Result<DomainType> {
146+
return runCatching {
147+
api.newRpc(owner, ...)
148+
}.foldWithSuppression(
149+
onSuccess = { response ->
150+
when (response.result) {
151+
RpcServiceName.NewRpcResponse.Result.OK -> Result.success(/* mapped value */)
152+
RpcServiceName.NewRpcResponse.Result.DENIED -> Result.failure(NewRpcError.Denied())
153+
RpcServiceName.NewRpcResponse.Result.UNRECOGNIZED -> Result.failure(NewRpcError.Unrecognized())
154+
else -> Result.failure(NewRpcError.Other())
155+
}
156+
},
157+
onFailure = { cause ->
158+
Result.failure(cause.toValidationOrElse { NewRpcError.Other(cause = it) })
159+
}
160+
)
161+
}
162+
```
163+
164+
#### Error sealed class pattern
165+
166+
Location: `services/<target>/src/main/kotlin/.../models/Errors.kt`
167+
168+
```kotlin
169+
sealed class NewRpcError(
170+
override val message: String? = null,
171+
override val cause: Throwable? = null
172+
) : CodeServerError(message, cause) {
173+
class Denied : NewRpcError("Denied")
174+
class Unrecognized : NewRpcError("Unrecognized"), NotifiableError
175+
data class Other(override val cause: Throwable? = null) : NewRpcError(message = cause?.message, cause = cause), NotifiableError
176+
}
177+
```
178+
179+
Add a subclass for each non-OK result enum value in the proto response. Mark
180+
`Unrecognized` and `Other` with `NotifiableError`. Mark expected/benign errors
181+
(e.g., `NotFound`, `Denied`) without `NotifiableError`.
182+
183+
#### Repository pattern
184+
185+
- **Interface** in `services/<target>/src/main/kotlin/.../repository/`:
186+
```kotlin
187+
suspend fun newRpc(...): Result<DomainType>
188+
```
189+
- **Internal impl** in `services/<target>/src/main/kotlin/.../internal/repositories/`:
190+
```kotlin
191+
override suspend fun newRpc(...): Result<DomainType> {
192+
return service.newRpc(...)
193+
.map { mapper.map(it) } // if domain mapping needed
194+
.onFailure { if (it !is NewRpcError.ExpectedCase) ErrorUtils.handleError(it) }
195+
}
196+
```
197+
198+
#### Controller pattern
199+
200+
Location: `services/<target>/src/main/kotlin/.../controllers/<ServiceName>Controller.kt`
201+
202+
```kotlin
203+
// @Singleton class with @Inject constructor(repository, userManager)
204+
205+
suspend fun newRpc(...): Result<DomainType> {
206+
val owner = userManager.accountCluster?.authority?.keyPair
207+
?: return Result.failure(Throwable("No account cluster"))
208+
return repository.newRpc(owner, ...)
209+
}
210+
```
211+
212+
#### Hilt wiring
213+
214+
If a new Repository interface+impl pair was created, add a `@Provides` binding in
215+
the corresponding Hilt module (`FlipcashModule.kt` or `OpenCodeModule.kt`).
216+
217+
### Step 6 — Review and commit
218+
219+
Show the user a summary of all changes (proto updates + any scaffolded code).
220+
221+
Offer to commit with a conventional commit message:
222+
```
223+
chore(protos): update <target> protobuf definitions
224+
```
225+
226+
If service stubs were also scaffolded, suggest a separate commit:
227+
```
228+
feat(<target>): scaffold service stubs for new RPCs
229+
```
230+
231+
## Never
232+
233+
- Edit generated protobuf code in `definitions/*/models/build/`
234+
- Commit without user approval
235+
- Skip build verification
236+
- Scaffold service code without asking the user first

0 commit comments

Comments
 (0)