@ai-content-space/loopx 0.1.2 → 0.1.3
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 +343 -56
- package/README.zh-CN.md +392 -0
- package/package.json +4 -1
- package/plugins/loopx/.codex-plugin/plugin.json +1 -1
- package/plugins/loopx/scripts/plugin-install.test.mjs +1 -0
- package/plugins/loopx/skills/archive/SKILL.md +39 -0
- package/plugins/loopx/skills/build/SKILL.md +111 -9
- package/plugins/loopx/skills/clarify/SKILL.md +121 -1
- package/plugins/loopx/skills/debug/SKILL.md +296 -0
- package/plugins/loopx/skills/debug/condition-based-waiting.md +115 -0
- package/plugins/loopx/skills/debug/defense-in-depth.md +122 -0
- package/plugins/loopx/skills/debug/find-polluter.sh +63 -0
- package/plugins/loopx/skills/debug/root-cause-tracing.md +169 -0
- package/plugins/loopx/skills/go-style/SKILL.md +71 -0
- package/plugins/loopx/skills/kratos/SKILL.md +74 -0
- package/plugins/loopx/skills/kratos/references/advanced-features.md +314 -0
- package/plugins/loopx/skills/kratos/references/architecture.md +488 -0
- package/plugins/loopx/skills/kratos/references/configuration.md +399 -0
- package/plugins/loopx/skills/kratos/references/http-customization.md +512 -0
- package/plugins/loopx/skills/kratos/references/middleware-logging.md +400 -0
- package/plugins/loopx/skills/kratos/references/proto-api-design.md +432 -0
- package/plugins/loopx/skills/kratos/references/security-auth.md +411 -0
- package/plugins/loopx/skills/kratos/references/troubleshooting.md +385 -0
- package/plugins/loopx/skills/plan/SKILL.md +22 -2
- package/plugins/loopx/skills/review/SKILL.md +98 -1
- package/plugins/loopx/skills/tdd/SKILL.md +371 -0
- package/plugins/loopx/skills/tdd/testing-anti-patterns.md +299 -0
- package/plugins/loopx/skills/verify/SKILL.md +139 -0
- package/scripts/codex-stop-hook.mjs +71 -0
- package/scripts/codex-workflow-hook.mjs +153 -0
- package/skills/archive/SKILL.md +39 -0
- package/skills/build/SKILL.md +111 -9
- package/skills/clarify/SKILL.md +121 -1
- package/skills/debug/SKILL.md +296 -0
- package/skills/debug/condition-based-waiting.md +115 -0
- package/skills/debug/defense-in-depth.md +122 -0
- package/skills/debug/find-polluter.sh +63 -0
- package/skills/debug/root-cause-tracing.md +169 -0
- package/skills/go-style/SKILL.md +71 -0
- package/skills/kratos/SKILL.md +74 -0
- package/skills/kratos/references/advanced-features.md +314 -0
- package/skills/kratos/references/architecture.md +488 -0
- package/skills/kratos/references/configuration.md +399 -0
- package/skills/kratos/references/http-customization.md +512 -0
- package/skills/kratos/references/middleware-logging.md +400 -0
- package/skills/kratos/references/proto-api-design.md +432 -0
- package/skills/kratos/references/security-auth.md +411 -0
- package/skills/kratos/references/troubleshooting.md +385 -0
- package/skills/plan/SKILL.md +18 -2
- package/skills/review/SKILL.md +98 -1
- package/skills/tdd/SKILL.md +371 -0
- package/skills/tdd/testing-anti-patterns.md +299 -0
- package/skills/verify/SKILL.md +139 -0
- package/src/build-runtime.mjs +303 -26
- package/src/build-stop-gate.mjs +94 -0
- package/src/cli.mjs +47 -5
- package/src/codex-exec-runtime.mjs +105 -5
- package/src/context-manifest.mjs +172 -0
- package/src/install-discovery.mjs +352 -5
- package/src/next-skill.mjs +57 -5
- package/src/plan-runtime.mjs +79 -122
- package/src/review-runtime.mjs +378 -0
- package/src/runtime-maintenance.mjs +428 -14
- package/src/template-governance.mjs +223 -0
- package/src/workflow.mjs +1941 -117
- package/src/workspace-context.mjs +166 -0
- package/src/workspace-memory.mjs +69 -0
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
# Proto API Design
|
|
2
|
+
|
|
3
|
+
Guide for defining HTTP APIs with protobuf in Kratos.
|
|
4
|
+
|
|
5
|
+
## When to Use
|
|
6
|
+
|
|
7
|
+
- Creating new service APIs
|
|
8
|
+
- Defining HTTP routes with path variables
|
|
9
|
+
- Setting up field validation
|
|
10
|
+
- Configuring buf generation
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## HTTP Proto Definition Patterns
|
|
15
|
+
|
|
16
|
+
### Basic Path Variables
|
|
17
|
+
|
|
18
|
+
```protobuf
|
|
19
|
+
// Simple variable extraction
|
|
20
|
+
message GetUserRequest {
|
|
21
|
+
string user_id = 1; // Extracted from URL
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
service UserService {
|
|
25
|
+
rpc GetUser(GetUserRequest) returns (User) {
|
|
26
|
+
option (google.api.http) = {
|
|
27
|
+
get: "/v1/users/{user_id}"
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// URL: GET /v1/users/123 → user_id = "123"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Pattern Matching
|
|
35
|
+
|
|
36
|
+
Use patterns to restrict URL format and extract structured data:
|
|
37
|
+
|
|
38
|
+
```protobuf
|
|
39
|
+
// Single segment match (*)
|
|
40
|
+
message ListUsersRequest {
|
|
41
|
+
string parent = 1; // Value will be "projects/123"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
service UserService {
|
|
45
|
+
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse) {
|
|
46
|
+
option (google.api.http) = {
|
|
47
|
+
get: "/v1/{parent=projects/*}/users"
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// URL: GET /v1/projects/123/users → parent = "projects/123"
|
|
52
|
+
|
|
53
|
+
// Multi-segment match (**)
|
|
54
|
+
message GetResourceRequest {
|
|
55
|
+
string path = 1; // Value will be "a/b/c/d"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
service ResourceService {
|
|
59
|
+
rpc GetResource(GetResourceRequest) returns (Resource) {
|
|
60
|
+
option (google.api.http) = {
|
|
61
|
+
get: "/v1/{path=**}"
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// URL: GET /v1/a/b/c/d → path = "a/b/c/d"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### HTTP Method Mapping
|
|
69
|
+
|
|
70
|
+
```protobuf
|
|
71
|
+
// GET - query parameters from URL
|
|
72
|
+
rpc GetUser(GetUserRequest) returns (User) {
|
|
73
|
+
option (google.api.http) = {
|
|
74
|
+
get: "/v1/users/{user_id}"
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// POST - body contains resource
|
|
79
|
+
rpc CreateUser(CreateUserRequest) returns (User) {
|
|
80
|
+
option (google.api.http) = {
|
|
81
|
+
post: "/v1/users"
|
|
82
|
+
body: "user" // Map 'user' field to request body
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// PUT - full resource replacement
|
|
87
|
+
rpc UpdateUser(UpdateUserRequest) returns (User) {
|
|
88
|
+
option (google.api.http) = {
|
|
89
|
+
put: "/v1/users/{user_id}"
|
|
90
|
+
body: "user"
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// PATCH - partial update
|
|
95
|
+
rpc PatchUser(PatchUserRequest) returns (User) {
|
|
96
|
+
option (google.api.http) = {
|
|
97
|
+
patch: "/v1/users/{user_id}"
|
|
98
|
+
body: "user"
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// DELETE - no body
|
|
103
|
+
rpc DeleteUser(DeleteUserRequest) returns (google.protobuf.Empty) {
|
|
104
|
+
option (google.api.http) = {
|
|
105
|
+
delete: "/v1/users/{user_id}"
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Custom Methods
|
|
111
|
+
|
|
112
|
+
Use `:` suffix for custom operations beyond CRUD:
|
|
113
|
+
|
|
114
|
+
```protobuf
|
|
115
|
+
// Custom action on a resource
|
|
116
|
+
rpc ActivateUser(ActivateUserRequest) returns (User) {
|
|
117
|
+
option (google.api.http) = {
|
|
118
|
+
post: "/v1/users/{name}:activate"
|
|
119
|
+
body: "*"
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
// URL: POST /v1/users/123:activate
|
|
123
|
+
|
|
124
|
+
// Batch operations
|
|
125
|
+
rpc BatchCreateUsers(BatchCreateUsersRequest) returns (BatchCreateUsersResponse) {
|
|
126
|
+
option (google.api.http) = {
|
|
127
|
+
post: "/v1/users:batchCreate"
|
|
128
|
+
body: "requests"
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
// URL: POST /v1/users:batchCreate
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Multiple Routes (additional_bindings)
|
|
135
|
+
|
|
136
|
+
One RPC can serve multiple URL patterns:
|
|
137
|
+
|
|
138
|
+
```protobuf
|
|
139
|
+
rpc GetResource(GetResourceRequest) returns (Resource) {
|
|
140
|
+
option (google.api.http) = {
|
|
141
|
+
get: "/v1/{name=projects/*/resources/*}"
|
|
142
|
+
additional_bindings {
|
|
143
|
+
get: "/v1/{name=locations/*/resources/*}"
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
// URL: GET /v1/projects/123/resources/456
|
|
148
|
+
// URL: GET /v1/locations/us-east1/resources/456
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Pagination Pattern
|
|
152
|
+
|
|
153
|
+
Standard pagination fields:
|
|
154
|
+
|
|
155
|
+
```protobuf
|
|
156
|
+
message ListBooksRequest {
|
|
157
|
+
string parent = 1; // "shelves/shelf1"
|
|
158
|
+
int32 page_size = 2; // Max results per page
|
|
159
|
+
string page_token = 3; // Token from previous response
|
|
160
|
+
string order_by = 4; // Optional: "name desc"
|
|
161
|
+
string filter = 5; // Optional: "age>18"
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
message ListBooksResponse {
|
|
165
|
+
repeated Book books = 1;
|
|
166
|
+
string next_page_token = 2; // Empty = no more data
|
|
167
|
+
int32 total_size = 3; // Optional: total count
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
service BookService {
|
|
171
|
+
rpc ListBooks(ListBooksRequest) returns (ListBooksResponse) {
|
|
172
|
+
option (google.api.http) = {
|
|
173
|
+
get: "/v1/{parent=shelves/*}/books"
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## buf.validate Field Validation
|
|
182
|
+
|
|
183
|
+
Use buf's protovalidate for runtime validation:
|
|
184
|
+
|
|
185
|
+
### String Validation
|
|
186
|
+
|
|
187
|
+
```protobuf
|
|
188
|
+
message User {
|
|
189
|
+
string name = 1 [
|
|
190
|
+
(buf.validate.field).string.min_len = 1,
|
|
191
|
+
(buf.validate.field).string.max_len = 100,
|
|
192
|
+
(buf.validate.field).string.pattern = "^[a-zA-Z]+$"
|
|
193
|
+
];
|
|
194
|
+
|
|
195
|
+
string email = 2 [
|
|
196
|
+
(buf.validate.field).string.email = true
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
string website = 3 [
|
|
200
|
+
(buf.validate.field).string.uri = true
|
|
201
|
+
];
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Integer Validation
|
|
206
|
+
|
|
207
|
+
```protobuf
|
|
208
|
+
message Product {
|
|
209
|
+
int32 quantity = 1 [
|
|
210
|
+
(buf.validate.field).int32.gte = 0,
|
|
211
|
+
(buf.validate.field).int32.lte = 1000
|
|
212
|
+
];
|
|
213
|
+
|
|
214
|
+
int32 status = 2 [
|
|
215
|
+
(buf.validate.field).int32.in = [0, 1, 2, 3]
|
|
216
|
+
];
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Duration Validation
|
|
221
|
+
|
|
222
|
+
```protobuf
|
|
223
|
+
import "google/protobuf/duration.proto";
|
|
224
|
+
|
|
225
|
+
message Server {
|
|
226
|
+
google.protobuf.Duration timeout = 1 [
|
|
227
|
+
(buf.validate.field).required = true,
|
|
228
|
+
(buf.validate.field).duration = {
|
|
229
|
+
gt: {seconds: 1} // Greater than 1 second
|
|
230
|
+
lte: {seconds: 600} // Less than or equal 10 minutes
|
|
231
|
+
}
|
|
232
|
+
];
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Enum Validation
|
|
237
|
+
|
|
238
|
+
```protobuf
|
|
239
|
+
enum Status {
|
|
240
|
+
UNKNOWN = 0;
|
|
241
|
+
ACTIVE = 1;
|
|
242
|
+
INACTIVE = 2;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
message User {
|
|
246
|
+
Status status = 1 [
|
|
247
|
+
(buf.validate.field).enum.defined_only = true,
|
|
248
|
+
(buf.validate.field).enum.in = [1, 2]
|
|
249
|
+
];
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Required Fields
|
|
254
|
+
|
|
255
|
+
```protobuf
|
|
256
|
+
message Request {
|
|
257
|
+
string name = 1 [(buf.validate.field).required = true];
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## buf.gen.yaml Configuration
|
|
264
|
+
|
|
265
|
+
Example configuration for Kratos projects:
|
|
266
|
+
|
|
267
|
+
```yaml
|
|
268
|
+
version: v1
|
|
269
|
+
managed:
|
|
270
|
+
enabled: true
|
|
271
|
+
override:
|
|
272
|
+
- file_option: go_package
|
|
273
|
+
module: buf.build/bufbuild/protovalidate
|
|
274
|
+
value: buf.build/go/protovalidate
|
|
275
|
+
|
|
276
|
+
plugins:
|
|
277
|
+
# Go struct generation
|
|
278
|
+
- remote: buf.build/protocolbuffers/go
|
|
279
|
+
out: api
|
|
280
|
+
opt: paths=source_relative
|
|
281
|
+
|
|
282
|
+
# gRPC service generation
|
|
283
|
+
- remote: buf.build/grpc/go
|
|
284
|
+
out: api
|
|
285
|
+
opt:
|
|
286
|
+
- paths=source_relative
|
|
287
|
+
- require_unimplemented_servers=false
|
|
288
|
+
|
|
289
|
+
# Validation code generation
|
|
290
|
+
- remote: buf.build/bufbuild/validate-go
|
|
291
|
+
out: api
|
|
292
|
+
opt: paths=source_relative
|
|
293
|
+
|
|
294
|
+
# OpenAPI documentation
|
|
295
|
+
- remote: buf.build/community/google-gnostic-openapi
|
|
296
|
+
out: docs
|
|
297
|
+
opt:
|
|
298
|
+
- paths=source_relative
|
|
299
|
+
- naming=proto
|
|
300
|
+
|
|
301
|
+
# Kratos HTTP gateway (local plugin)
|
|
302
|
+
- local: protoc-gen-go-http
|
|
303
|
+
out: api
|
|
304
|
+
opt: paths=source_relative
|
|
305
|
+
|
|
306
|
+
# Kratos error codes (local plugin)
|
|
307
|
+
- local: protoc-gen-go-errors
|
|
308
|
+
out: api
|
|
309
|
+
opt: paths=source_relative
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
**Install local plugins:**
|
|
313
|
+
```bash
|
|
314
|
+
kratos upgrade # Installs protoc-gen-go-http, protoc-gen-go-errors
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## Critical Pitfall: Route Override Order
|
|
320
|
+
|
|
321
|
+
**Problem:** Route definitions are processed in order. A generic route can shadow a specific one.
|
|
322
|
+
|
|
323
|
+
```protobuf
|
|
324
|
+
// WRONG ORDER - specific route shadowed
|
|
325
|
+
rpc GetUser(GetUserRequest) returns (User) {
|
|
326
|
+
option (google.api.http) = {get: "/v1/user/{user_id}"}; // Generic
|
|
327
|
+
}
|
|
328
|
+
rpc GetProfile(GetProfileRequest) returns (Profile) {
|
|
329
|
+
option (google.api.http) = {get: "/v1/user/profile"}; // Specific - shadowed!
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// CORRECT ORDER - specific route first
|
|
333
|
+
rpc GetProfile(GetProfileRequest) returns (Profile) {
|
|
334
|
+
option (google.api.http) = {get: "/v1/user/profile"}; // Specific first
|
|
335
|
+
}
|
|
336
|
+
rpc GetUser(GetUserRequest) returns (User) {
|
|
337
|
+
option (google.api.http) = {get: "/v1/user/{user_id}"}; // Generic last
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
**Why this matters:** HTTP routers match in order. `{user_id}` can match "profile", causing unexpected behavior.
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## Error Code Design
|
|
346
|
+
|
|
347
|
+
Follow industry standards for error codes:
|
|
348
|
+
|
|
349
|
+
### Alibaba Format (5-digit string)
|
|
350
|
+
|
|
351
|
+
```
|
|
352
|
+
[A/B/C][NNNN]
|
|
353
|
+
|
|
354
|
+
A = User error (bad request)
|
|
355
|
+
B = System error (business logic)
|
|
356
|
+
C = Third-party error (external service)
|
|
357
|
+
|
|
358
|
+
NNNN = Error number within category
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
Examples:
|
|
362
|
+
- `A0001` - Invalid parameter (user error)
|
|
363
|
+
- `B0101` - Database connection failed (system error)
|
|
364
|
+
- `C0201` - Payment gateway timeout (third-party error)
|
|
365
|
+
|
|
366
|
+
### Proto Definition
|
|
367
|
+
|
|
368
|
+
```protobuf
|
|
369
|
+
enum ErrorReason {
|
|
370
|
+
USER_NOT_FOUND = 0;
|
|
371
|
+
INVALID_PARAMETER = 1;
|
|
372
|
+
INTERNAL_ERROR = 2;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// In service code:
|
|
376
|
+
var ErrUserNotFound = errors.NotFound(
|
|
377
|
+
ErrorReason_USER_NOT_FOUND.String(),
|
|
378
|
+
"user not found"
|
|
379
|
+
)
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### HTTP Error Response
|
|
383
|
+
|
|
384
|
+
```json
|
|
385
|
+
{
|
|
386
|
+
"code": "A0001",
|
|
387
|
+
"message": "Invalid parameter: user_id must be positive",
|
|
388
|
+
"details": "See documentation at https://api.example.com/docs/errors/A0001"
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
## OpenAPI Documentation
|
|
395
|
+
|
|
396
|
+
buf.gen.yaml generates OpenAPI specs automatically:
|
|
397
|
+
|
|
398
|
+
```yaml
|
|
399
|
+
- remote: buf.build/community/google-gnostic-openapi
|
|
400
|
+
out: docs
|
|
401
|
+
opt:
|
|
402
|
+
- paths=source_relative
|
|
403
|
+
- naming=proto
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
Add proto-level documentation:
|
|
407
|
+
|
|
408
|
+
```protobuf
|
|
409
|
+
import "gnostic/openapi/v3/annotations.proto";
|
|
410
|
+
|
|
411
|
+
option (gnostic.openapi.v3.document) = {
|
|
412
|
+
info: {
|
|
413
|
+
title: "My API"
|
|
414
|
+
version: "1.0.0"
|
|
415
|
+
description: "API description"
|
|
416
|
+
}
|
|
417
|
+
servers: [
|
|
418
|
+
{url: "https://api.example.com", description: "Production"}
|
|
419
|
+
]
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
service MyService {
|
|
423
|
+
rpc GetUser(GetUserRequest) returns (User) {
|
|
424
|
+
option (google.api.http) = {get: "/v1/users/{user_id}"};
|
|
425
|
+
option (gnostic.openapi.v3.operation) = {
|
|
426
|
+
operation_id: "getUser"
|
|
427
|
+
summary: "Get a user by ID"
|
|
428
|
+
description: "Returns the user with the specified ID"
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
```
|