@seanyao/roll 4.630.1 → 4.702.2
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/CHANGELOG.md +74 -0
- package/README.md +65 -55
- package/conventions/global/AGENTS.md +8 -7
- package/dist/roll.mjs +13277 -8746
- package/docs/INDEX.md +32 -0
- package/docs/architecture.md +444 -0
- package/docs/difftest-freeze-paradigm.md +113 -0
- package/docs/live-console.md +203 -0
- package/docs/manifesto.md +65 -0
- package/docs/migration/role-taxonomy-v4.md +60 -0
- package/docs/verification.md +83 -0
- package/guide/INDEX.md +86 -0
- package/guide/assets/layouts/cards-2.png +0 -0
- package/guide/assets/layouts/cards-3.png +0 -0
- package/guide/assets/layouts/cards-4.png +0 -0
- package/guide/assets/layouts/compare.png +0 -0
- package/guide/assets/layouts/highlight.png +0 -0
- package/guide/assets/layouts/pipeline.png +0 -0
- package/guide/assets/layouts/plain.png +0 -0
- package/guide/assets/layouts/quote.png +0 -0
- package/guide/assets/layouts/timeline.png +0 -0
- package/guide/en/acceptance-evidence.md +231 -0
- package/guide/en/ai-agents.md +185 -0
- package/guide/en/backlog-github-sync.md +108 -0
- package/guide/en/changelog.md +66 -0
- package/guide/en/configuration.md +112 -0
- package/guide/en/consistency.md +58 -0
- package/guide/en/conventions.md +113 -0
- package/guide/en/dream.md +121 -0
- package/guide/en/faq.md +855 -0
- package/guide/en/feedback.md +31 -0
- package/guide/en/getting-started.md +103 -0
- package/guide/en/installation.md +86 -0
- package/guide/en/legacy-onboarding.md +195 -0
- package/guide/en/loop-data-layout.md +256 -0
- package/guide/en/loop-driven-architecture.md +186 -0
- package/guide/en/loop.md +1324 -0
- package/guide/en/methodology.md +715 -0
- package/guide/en/migration-2.0.md +154 -0
- package/guide/en/overview.md +190 -0
- package/guide/en/pairing.md +151 -0
- package/guide/en/patterns/README.md +76 -0
- package/guide/en/patterns/graft-pattern.md +110 -0
- package/guide/en/patterns/replant-pattern.md +114 -0
- package/guide/en/patterns/seed-pattern.md +132 -0
- package/guide/en/peer.md +71 -0
- package/guide/en/pr-review.md +62 -0
- package/guide/en/practices/engineering-common-sense.md +395 -0
- package/guide/en/pricing.md +116 -0
- package/guide/en/project-setup.md +126 -0
- package/guide/en/roll-doc-audit.md +98 -0
- package/guide/en/skills.md +206 -0
- package/guide/en/test-isolation.md +51 -0
- package/guide/en/testing/quality-rubric.md +340 -0
- package/guide/en/testing.md +123 -0
- package/guide/en/tools.md +173 -0
- package/guide/skills.md +30 -0
- package/guide/zh/acceptance-evidence.md +194 -0
- package/guide/zh/ai-agents.md +170 -0
- package/guide/zh/backlog-github-sync.md +105 -0
- package/guide/zh/changelog.md +57 -0
- package/guide/zh/configuration.md +99 -0
- package/guide/zh/consistency.md +48 -0
- package/guide/zh/conventions.md +96 -0
- package/guide/zh/dream.md +97 -0
- package/guide/zh/faq.md +773 -0
- package/guide/zh/feedback.md +30 -0
- package/guide/zh/getting-started.md +96 -0
- package/guide/zh/installation.md +83 -0
- package/guide/zh/legacy-onboarding.md +192 -0
- package/guide/zh/loop-data-layout.md +236 -0
- package/guide/zh/loop-driven-architecture.md +186 -0
- package/guide/zh/loop.md +1124 -0
- package/guide/zh/methodology.md +702 -0
- package/guide/zh/migration-2.0.md +154 -0
- package/guide/zh/overview.md +186 -0
- package/guide/zh/pairing.md +117 -0
- package/guide/zh/patterns/README.md +74 -0
- package/guide/zh/patterns/graft-pattern.md +108 -0
- package/guide/zh/patterns/replant-pattern.md +112 -0
- package/guide/zh/patterns/seed-pattern.md +130 -0
- package/guide/zh/peer.md +63 -0
- package/guide/zh/pr-review.md +54 -0
- package/guide/zh/practices/engineering-common-sense.md +393 -0
- package/guide/zh/pricing.md +97 -0
- package/guide/zh/project-setup.md +114 -0
- package/guide/zh/roll-doc-audit.md +90 -0
- package/guide/zh/skills.md +191 -0
- package/guide/zh/test-isolation.md +46 -0
- package/guide/zh/testing/quality-rubric.md +284 -0
- package/guide/zh/testing.md +116 -0
- package/guide/zh/tools.md +173 -0
- package/package.json +4 -1
- package/skills/README.md +1 -0
- package/skills/roll-.qa/SKILL.md +1 -1
- package/skills/roll-.review/SKILL.md +1 -1
- package/skills/roll-build/SKILL.md +1 -1
- package/skills/roll-build/references/full-contract.md +16 -13
- package/skills/roll-design/SKILL.md +3 -3
- package/skills/roll-design/references/full-contract.md +17 -13
- package/skills/roll-fix/SKILL.md +1 -1
- package/skills/roll-fix/references/full-contract.md +13 -10
- package/skills/roll-peer/SKILL.md +1 -1
- package/skills/roll-prime/SKILL.md +77 -0
- package/skills/roll-prime/references/explorer-annex.md +39 -0
- package/skills/roll-prime/references/supervisor-prompt.md +165 -0
- package/skills/route-cases/skills.json +10 -0
- package/template/AGENTS.md +3 -1
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
# Roll Engineering Common Sense Checklist
|
|
2
|
+
|
|
3
|
+
> **These are not best practices — they are baseline requirements.** Violations are Bugs.
|
|
4
|
+
|
|
5
|
+
## 1. Idempotency 🔁
|
|
6
|
+
|
|
7
|
+
**Definition:** Executing the same operation N times produces the same result as executing it once.
|
|
8
|
+
|
|
9
|
+
**Must test:**
|
|
10
|
+
```typescript
|
|
11
|
+
it('should be idempotent', async () => {
|
|
12
|
+
await operation(data) // 1st
|
|
13
|
+
const result1 = await getState()
|
|
14
|
+
|
|
15
|
+
await operation(data) // 2nd
|
|
16
|
+
const result2 = await getState()
|
|
17
|
+
|
|
18
|
+
await operation(data) // 3rd
|
|
19
|
+
const result3 = await getState()
|
|
20
|
+
|
|
21
|
+
expect(result1).toEqual(result2)
|
|
22
|
+
expect(result2).toEqual(result3)
|
|
23
|
+
})
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Common scenarios:**
|
|
27
|
+
- [ ] Import/ingest operations
|
|
28
|
+
- [ ] Configuration updates
|
|
29
|
+
- [ ] State changes
|
|
30
|
+
- [ ] API calls
|
|
31
|
+
- [ ] File writes
|
|
32
|
+
|
|
33
|
+
**Anti-pattern (this time):** Running ingest repeatedly -> files duplicated 7 times
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 2. Cross-Module Contract Consistency 🔗
|
|
38
|
+
|
|
39
|
+
**Definition:** Data/IDs/formats shared across multiple modules must be exactly consistent.
|
|
40
|
+
|
|
41
|
+
**Must check:**
|
|
42
|
+
```typescript
|
|
43
|
+
// Checklist
|
|
44
|
+
- [ ] Is the ID generation algorithm consistent?
|
|
45
|
+
- [ ] Is the data serialization format consistent?
|
|
46
|
+
- [ ] Is path handling consistent? (e.g., / vs -)
|
|
47
|
+
- [ ] Has it been extracted into a shared function/constant?
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Test template:**
|
|
51
|
+
```typescript
|
|
52
|
+
it('should generate same ID across modules', () => {
|
|
53
|
+
const scannerId = generateScannerId('articles/test.md')
|
|
54
|
+
const inboxId = generateInboxId('articles/test.md')
|
|
55
|
+
expect(scannerId).toEqual(inboxId)
|
|
56
|
+
})
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Anti-pattern (this time):** Scanner used `-` to replace `/`, inbox used raw path -> deduplication failed
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 3. Data Flow Integrity 🌊
|
|
64
|
+
|
|
65
|
+
**Definition:** The complete pipeline from producer to consumer must be connected end-to-end.
|
|
66
|
+
|
|
67
|
+
**Must verify:**
|
|
68
|
+
```typescript
|
|
69
|
+
// Integration test - must exist
|
|
70
|
+
describe('Data Flow: Producer -> Consumer', () => {
|
|
71
|
+
it('should write data that consumer can read', async () => {
|
|
72
|
+
await producer.write(testData)
|
|
73
|
+
const result = await consumer.read()
|
|
74
|
+
expect(result).toEqual(testData)
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Checklist:**
|
|
80
|
+
- [ ] Who writes the data? (Producer)
|
|
81
|
+
- [ ] Who reads the data? (Consumer)
|
|
82
|
+
- [ ] What is the intermediate storage? (state/file/cache)
|
|
83
|
+
- [ ] Is there an integration test to verify?
|
|
84
|
+
|
|
85
|
+
**Anti-pattern (this time):** Ingest didn't write state, status couldn't read it -> showed 0
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 4. Atomicity ⚛️
|
|
90
|
+
|
|
91
|
+
**Definition:** An operation either fully succeeds or does not execute at all (no intermediate state).
|
|
92
|
+
|
|
93
|
+
**Must consider:**
|
|
94
|
+
- [ ] How to roll back on partial failure?
|
|
95
|
+
- [ ] Is there a transaction mechanism?
|
|
96
|
+
- [ ] How is data consistency guaranteed after a crash?
|
|
97
|
+
|
|
98
|
+
**Test template:**
|
|
99
|
+
```typescript
|
|
100
|
+
it('should be atomic', async () => {
|
|
101
|
+
try {
|
|
102
|
+
await operation([item1, item2, INVALID_ITEM, item4])
|
|
103
|
+
} catch (e) {
|
|
104
|
+
// After failure, processed items should be rolled back
|
|
105
|
+
const state = await getState()
|
|
106
|
+
expect(state).toEqual(initialState)
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## 5. Input Validation 🛡️
|
|
114
|
+
|
|
115
|
+
**Definition:** Never trust any external input — it must be validated.
|
|
116
|
+
|
|
117
|
+
**Must check:**
|
|
118
|
+
- [ ] Null/undefined handling
|
|
119
|
+
- [ ] Type checking
|
|
120
|
+
- [ ] Range checking (array length, numeric range)
|
|
121
|
+
- [ ] Special character/injection attack protection
|
|
122
|
+
- [ ] File path traversal protection
|
|
123
|
+
|
|
124
|
+
**Test template:**
|
|
125
|
+
```typescript
|
|
126
|
+
it('should handle invalid inputs gracefully', async () => {
|
|
127
|
+
await expect(operation(null)).rejects.toThrow()
|
|
128
|
+
await expect(operation('')).rejects.toThrow()
|
|
129
|
+
await expect(operation({})).rejects.toThrow()
|
|
130
|
+
})
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## 6. Graceful Degradation 🪂
|
|
136
|
+
|
|
137
|
+
**Definition:** When a dependency fails, the system should still provide limited functionality.
|
|
138
|
+
|
|
139
|
+
**Must consider:**
|
|
140
|
+
- [ ] What if an external API fails?
|
|
141
|
+
- [ ] What if the database connection drops?
|
|
142
|
+
- [ ] Is there a fallback mechanism?
|
|
143
|
+
- [ ] What feedback does the user get?
|
|
144
|
+
|
|
145
|
+
**Test template:**
|
|
146
|
+
```typescript
|
|
147
|
+
it('should degrade gracefully when dependency fails', async () => {
|
|
148
|
+
mockDependency.toThrow('Network error')
|
|
149
|
+
|
|
150
|
+
// Should not crash
|
|
151
|
+
const result = await operation()
|
|
152
|
+
|
|
153
|
+
// Should return fallback value or partial result
|
|
154
|
+
expect(result).toEqual(fallbackValue)
|
|
155
|
+
})
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## 7. Observability 👁️
|
|
161
|
+
|
|
162
|
+
**Definition:** System state must be visible and traceable.
|
|
163
|
+
|
|
164
|
+
**Must provide:**
|
|
165
|
+
- [ ] Progress feedback (for long-running operations)
|
|
166
|
+
- [ ] Status query interface (e.g., status command)
|
|
167
|
+
- [ ] Error logs (failure reasons)
|
|
168
|
+
- [ ] Key metrics (counts, durations)
|
|
169
|
+
|
|
170
|
+
**Improvements this time:**
|
|
171
|
+
- Added `kkb status` showing raw files statistics ✅
|
|
172
|
+
- Added `kkb compile` progress feedback ✅
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## 8. Concurrency Safety 🧵
|
|
177
|
+
|
|
178
|
+
**Definition:** Shared resource access across multiple threads/processes must be safe.
|
|
179
|
+
|
|
180
|
+
**Must consider:**
|
|
181
|
+
- [ ] File read/write conflicts
|
|
182
|
+
- [ ] Database transaction isolation levels
|
|
183
|
+
- [ ] Locking for shared in-memory state
|
|
184
|
+
- [ ] Race conditions
|
|
185
|
+
|
|
186
|
+
**Test template:**
|
|
187
|
+
```typescript
|
|
188
|
+
it('should handle concurrent writes', async () => {
|
|
189
|
+
await Promise.all([
|
|
190
|
+
operation(data1),
|
|
191
|
+
operation(data2),
|
|
192
|
+
operation(data3)
|
|
193
|
+
])
|
|
194
|
+
|
|
195
|
+
// Verify final state consistency
|
|
196
|
+
const state = await getState()
|
|
197
|
+
expect(state).toBeValid()
|
|
198
|
+
})
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Mandatory Check Process
|
|
204
|
+
|
|
205
|
+
At the **Test Design Review** phase of each Story, the following must be answered:
|
|
206
|
+
|
|
207
|
+
```markdown
|
|
208
|
+
### Engineering Common Sense Checklist
|
|
209
|
+
- [ ] **Idempotency**: Can it be run repeatedly? Are there tests?
|
|
210
|
+
- [ ] **Cross-Module Contract**: Are IDs/formats/algorithms consistent?
|
|
211
|
+
- [ ] **Data Flow**: Is the producer -> consumer pipeline complete?
|
|
212
|
+
- [ ] **Atomicity**: Will partial failures roll back?
|
|
213
|
+
- [ ] **Input Validation**: Are all inputs validated?
|
|
214
|
+
- [ ] **Graceful Degradation**: What happens when a dependency fails?
|
|
215
|
+
- [ ] **Observability**: Can the user see progress/status?
|
|
216
|
+
- [ ] **Concurrency Safety**: Is multi-threaded access safe?
|
|
217
|
+
|
|
218
|
+
**If any item is not met, tests/design must be added before writing implementation code.**
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## 9. Shell Script Performance 🐚
|
|
224
|
+
|
|
225
|
+
**Definition:** `$()` command substitution forks a subshell. In hot paths (functions called per-test, per-catalog-entry, or per-message-lookup) this is the dominant cost.
|
|
226
|
+
|
|
227
|
+
**Rule of thumb:**
|
|
228
|
+
- 1 subshell fork ≈ 2–3 ms on Linux
|
|
229
|
+
- 1000 forks in a test suite setup = +2–3 s per test run
|
|
230
|
+
- 1739 tests × 2.3 s = **~67 minutes of wasted CI time** (observed in this repo)
|
|
231
|
+
|
|
232
|
+
**Must avoid in hot paths:**
|
|
233
|
+
```bash
|
|
234
|
+
# ❌ Subshell fork — slow when called thousands of times
|
|
235
|
+
upper="$(echo "$lang" | tr '[:lower:]' '[:upper:]')"
|
|
236
|
+
upper="$(_some_helper_function "$lang")"
|
|
237
|
+
safe="$(_sanitize_key "$key")"
|
|
238
|
+
|
|
239
|
+
# ✅ Inline equivalents — zero fork
|
|
240
|
+
case "$lang" in
|
|
241
|
+
en|EN) upper=EN ;;
|
|
242
|
+
zh|ZH) upper=ZH ;;
|
|
243
|
+
*) upper="$(printf '%s' "$lang" | tr '[:lower:]' '[:upper:]')" ;; # rare path OK
|
|
244
|
+
esac
|
|
245
|
+
safe="${key//[^A-Za-z0-9_]/_}" # parameter expansion, no fork
|
|
246
|
+
printf -v "$varname" '%s' "$val" # printf -v instead of subshell assignment
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
**Compatibility trap — the "fixed twice" anti-pattern:**
|
|
250
|
+
```
|
|
251
|
+
PR #211: fixed $(echo | tr) → ${lang^^} ✅ fast, but bash 4+ only
|
|
252
|
+
PR #213: fixed bash 3.2 compat → $(_helper) ❌ reintroduced the subshell
|
|
253
|
+
PR #218: fixed both → inline case ✅ fast + bash 3.2 safe
|
|
254
|
+
```
|
|
255
|
+
When fixing for compatibility, verify the fix does not reintroduce the original problem.
|
|
256
|
+
Add a timing assertion or benchmark test to guard the regression.
|
|
257
|
+
|
|
258
|
+
**Dead code from inlining:**
|
|
259
|
+
When inlining a helper function's logic, remove the original function and grep for all callers.
|
|
260
|
+
A leftover function with zero callers misleads future contributors into calling it via `$()`
|
|
261
|
+
and reintroducing the fork.
|
|
262
|
+
|
|
263
|
+
**Must check:**
|
|
264
|
+
- [ ] Is this function called inside a loop, setup(), or per-entry catalog loading?
|
|
265
|
+
- [ ] Does it use `$()` anywhere? Can it be replaced with `case`, `${var//...}`, or `printf -v`?
|
|
266
|
+
- [ ] After inlining, is the original helper now dead code? Remove it.
|
|
267
|
+
- [ ] Is there a timing test or CI cap to catch regressions? (see `ROLL_TEST_TIME_CAP`)
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## 10. Shell Resource Cleanup 🧹
|
|
272
|
+
|
|
273
|
+
**Definition:** Every `trap` set must be explicitly reset. Temporary files and lock files must never outlive their owning process.
|
|
274
|
+
|
|
275
|
+
**trap - EXIT pattern:**
|
|
276
|
+
```bash
|
|
277
|
+
# ❌ Dangling trap — EXIT handler persists in the calling shell after return
|
|
278
|
+
local tmp; tmp=$(mktemp)
|
|
279
|
+
trap "rm -f '$tmp'" EXIT
|
|
280
|
+
# ... do work ...
|
|
281
|
+
mv "$tmp" "$dst"
|
|
282
|
+
return 0 # EXIT trap still armed — any later exit fires rm on a stale path
|
|
283
|
+
|
|
284
|
+
# ✅ Always reset after use
|
|
285
|
+
local tmp; tmp=$(mktemp)
|
|
286
|
+
trap "rm -f '$tmp'" EXIT
|
|
287
|
+
# ... do work ...
|
|
288
|
+
mv "$tmp" "$dst" 2>/dev/null || rm -f "$tmp"
|
|
289
|
+
trap - EXIT # disarm before return
|
|
290
|
+
return 0
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
**Directory scope in cleanup helpers:**
|
|
294
|
+
```bash
|
|
295
|
+
# ❌ Wrong — scans the env-var default, not the actual path in use
|
|
296
|
+
_cleanup_tmp() {
|
|
297
|
+
local dir; dir=$(dirname "${MY_PATH:-$HOME/.default/path}")
|
|
298
|
+
...
|
|
299
|
+
}
|
|
300
|
+
_do_work() {
|
|
301
|
+
local custom_path="$1" # may differ from MY_PATH
|
|
302
|
+
_cleanup_tmp # scans wrong directory!
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
# ✅ Pass the actual directory explicitly
|
|
306
|
+
_cleanup_tmp() {
|
|
307
|
+
local dir="${1:-$(dirname "${MY_PATH:-$HOME/.default/path}")}"
|
|
308
|
+
...
|
|
309
|
+
}
|
|
310
|
+
_do_work() {
|
|
311
|
+
local custom_path="$1"
|
|
312
|
+
_cleanup_tmp "$(dirname "$custom_path")"
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
**Must check:**
|
|
317
|
+
- [ ] Every `trap "…" EXIT` has a matching `trap - EXIT` before the function returns.
|
|
318
|
+
- [ ] Cleanup helpers accept the target directory as an argument — no implicit env-var path.
|
|
319
|
+
- [ ] Temporary file names are quoted in trap strings: `"rm -f '$tmp'"` not `"rm -f $tmp"`.
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## 11. Test Reliability 🧪
|
|
324
|
+
|
|
325
|
+
**Definition:** Tests must not rely on environmental assumptions that can silently fail on different hosts.
|
|
326
|
+
|
|
327
|
+
**PID assumptions:**
|
|
328
|
+
```bash
|
|
329
|
+
# ❌ Assumes pid_max ≤ 99999 — fails silently in containers with large pid_max
|
|
330
|
+
local stale_tmp="runs.jsonl.tmp.99999"
|
|
331
|
+
|
|
332
|
+
# ✅ Use a process that is provably dead
|
|
333
|
+
bash -c 'exit 0' &
|
|
334
|
+
local dead_pid=$!
|
|
335
|
+
wait "$dead_pid" 2>/dev/null || true
|
|
336
|
+
local stale_tmp="runs.jsonl.tmp.${dead_pid}"
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
**Heredoc scope:**
|
|
340
|
+
Functions defined outside a heredoc are not available inside it when the generated
|
|
341
|
+
script executes. Calling them silently no-ops (if guarded with `|| true`) or crashes.
|
|
342
|
+
```bash
|
|
343
|
+
# ❌ _some_helper is not defined in the generated script's scope
|
|
344
|
+
cat > script.sh <<'EOF'
|
|
345
|
+
_some_helper 2>/dev/null || true # silent no-op — always "succeeds"
|
|
346
|
+
EOF
|
|
347
|
+
|
|
348
|
+
# ✅ Either inline the logic, or define the function inside the heredoc
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
**Must check:**
|
|
352
|
+
- [ ] Does the test use a hardcoded PID, port, or path that may conflict on the host?
|
|
353
|
+
- [ ] Does the test call a function that is only defined in the outer shell, not inside a heredoc?
|
|
354
|
+
- [ ] Is the "dead process" check using an actual exited process, not a guessed PID?
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
## Automated Safeguards
|
|
359
|
+
|
|
360
|
+
### CI Gate Rules
|
|
361
|
+
```yaml
|
|
362
|
+
# .github/roll-checks.yml — enforced as a CI gate on every PR
|
|
363
|
+
checks:
|
|
364
|
+
idempotency:
|
|
365
|
+
- pattern: "ingest|import|sync"
|
|
366
|
+
require_test: "idempotency"
|
|
367
|
+
|
|
368
|
+
cross_module_contract:
|
|
369
|
+
- files: ["src/*/index.ts"]
|
|
370
|
+
check: "shared_id_generation"
|
|
371
|
+
|
|
372
|
+
data_flow:
|
|
373
|
+
- require_integration_test: true
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
These run as CI gates on every PR. Slower-burn issues — dead code, doc staleness,
|
|
377
|
+
structural drift — are caught by `roll-.dream`, the nightly code-health scan that
|
|
378
|
+
files `REFACTOR-XXX` entries back into the backlog.
|
|
379
|
+
|
|
380
|
+
### Pre-Commit Hook
|
|
381
|
+
```bash
|
|
382
|
+
#!/bin/bash
|
|
383
|
+
# .git/hooks/pre-commit
|
|
384
|
+
echo "🔍 Checking engineering common sense..."
|
|
385
|
+
|
|
386
|
+
# Check idempotency tests
|
|
387
|
+
if git diff --cached --name-only | grep -q "ingest\|import\|sync"; then
|
|
388
|
+
if ! grep -r "idempotency\|repeated run\|multiple times" tests/ 2>/dev/null; then
|
|
389
|
+
echo "❌ Missing idempotency tests!"
|
|
390
|
+
exit 1
|
|
391
|
+
fi
|
|
392
|
+
fi
|
|
393
|
+
|
|
394
|
+
echo "✅ Basic checks passed"
|
|
395
|
+
```
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Pricing — Cost Visibility & Price Snapshots
|
|
2
|
+
|
|
3
|
+
Roll computes per-cycle costs at **public per-token pricing** — not your
|
|
4
|
+
subscription rate, but a comparable number you can track across projects and
|
|
5
|
+
agents. This doc covers the `roll config prices` command, snapshot mechanism, and
|
|
6
|
+
how historical costs survive price changes.
|
|
7
|
+
|
|
8
|
+
## Where Costs Appear
|
|
9
|
+
|
|
10
|
+
The dashboard (`roll loop status`) shows two cost-related columns per cycle:
|
|
11
|
+
|
|
12
|
+
| Column | What it means |
|
|
13
|
+
|--------|---------------|
|
|
14
|
+
| **model** | Agent + version used (e.g. `deepseek-v4-pro`, `claude-sonnet-4-6`). Drives which price rates apply. |
|
|
15
|
+
| **cost** | Cost computed at public per-token rates × token usage. Currency follows the vendor: `$` for Anthropic (USD), `¥` for DeepSeek and Kimi (CNY). |
|
|
16
|
+
|
|
17
|
+
Roll uses **price snapshots** (not hardcoded constants) to look up rates.
|
|
18
|
+
Snapshots live in `lib/prices/` under your Roll install directory.
|
|
19
|
+
|
|
20
|
+
## Supported Vendors
|
|
21
|
+
|
|
22
|
+
| Vendor | Currency | Source |
|
|
23
|
+
|--------|----------|--------|
|
|
24
|
+
| Anthropic (Claude) | USD | `platform.claude.com/docs/en/about-claude/pricing` |
|
|
25
|
+
| DeepSeek | CNY | `api-docs.deepseek.com/zh-cn/quick_start/pricing/` |
|
|
26
|
+
| Kimi (Moonshot) | CNY | `platform.kimi.com/docs/pricing/chat` |
|
|
27
|
+
|
|
28
|
+
## `roll config prices` Command
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
roll config prices show # Print the current price table (all vendors)
|
|
32
|
+
roll config prices refresh # Fetch official pricing docs for all vendors, diff, write if changed
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### `roll config prices show`
|
|
36
|
+
|
|
37
|
+
Prints the active snapshots' metadata and a table of all known models with
|
|
38
|
+
their per-million-token rates:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
in Base input tokens
|
|
42
|
+
out Output tokens
|
|
43
|
+
cw Cache write tokens (billed at a premium over input)
|
|
44
|
+
cr Cache read tokens (billed at a deep discount)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Rates are **per million tokens**, in the vendor's native currency.
|
|
48
|
+
|
|
49
|
+
### `roll config prices refresh`
|
|
50
|
+
|
|
51
|
+
Fetches the official pricing page from each vendor, parses the rate table,
|
|
52
|
+
and diffs it against the latest local snapshot. Supports per-vendor refresh:
|
|
53
|
+
`roll config prices refresh anthropic|deepseek|kimi`.
|
|
54
|
+
|
|
55
|
+
- **Rates changed** → writes a new snapshot file (`snapshot-YYYY-MM-DD.json`),
|
|
56
|
+
prints a diff (red = removed, green = added), and the dashboard picks up the
|
|
57
|
+
new rates on the next render.
|
|
58
|
+
- **No change** → prints `up to date` and exits.
|
|
59
|
+
|
|
60
|
+
If the network is down or the page can't be parsed, the command exits with an
|
|
61
|
+
error message — **existing snapshots are never overwritten by a failed fetch**.
|
|
62
|
+
|
|
63
|
+
## Price Snapshots
|
|
64
|
+
|
|
65
|
+
Each snapshot is a JSON file in `lib/prices/` named by date:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
lib/prices/
|
|
69
|
+
snapshot-2026-05-22.json
|
|
70
|
+
snapshot-2026-06-01.json ← written by refresh when rates changed
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
A snapshot contains:
|
|
74
|
+
|
|
75
|
+
| Field | Description |
|
|
76
|
+
|-------|-------------|
|
|
77
|
+
| `version` | ISO 8601 timestamp of when the snapshot was created |
|
|
78
|
+
| `effective_at` | Date the vendor started charging these rates |
|
|
79
|
+
| `source_url` | URL of the official pricing page used |
|
|
80
|
+
| `prices` | Dict of `model → {in, out, cache_create, cache_read}` rates per million tokens |
|
|
81
|
+
|
|
82
|
+
Snapshots are **never deleted** — every version is kept so you can audit which
|
|
83
|
+
rate table applied at any point in time.
|
|
84
|
+
|
|
85
|
+
## History Cost Solidification
|
|
86
|
+
|
|
87
|
+
When a loop cycle finishes, Roll writes **two extra fields** into the usage
|
|
88
|
+
event:
|
|
89
|
+
|
|
90
|
+
| Field | Purpose |
|
|
91
|
+
|-------|---------|
|
|
92
|
+
| `cost_list_usd` | The cost at that moment's prices — frozen forever |
|
|
93
|
+
| `prices_version` | Which snapshot version was used to compute it |
|
|
94
|
+
|
|
95
|
+
The dashboard reads `cost_list_usd` first when rendering historical cycles.
|
|
96
|
+
If the field is missing (cycles from before this feature shipped), it falls
|
|
97
|
+
back to computing with the *current* snapshot and appends a dim `[legacy]`
|
|
98
|
+
marker.
|
|
99
|
+
|
|
100
|
+
**Net effect:** vendor price changes, `roll config prices refresh`, and Roll upgrades
|
|
101
|
+
never rewrite historical cycle costs. "What you actually spent" is a fact —
|
|
102
|
+
it stays put.
|
|
103
|
+
|
|
104
|
+
## FAQ
|
|
105
|
+
|
|
106
|
+
**Q: Does the cost column reflect my actual bill?**
|
|
107
|
+
No. It uses public per-token rates. If you're on a subscription (Claude Pro,
|
|
108
|
+
Team, etc.) your real cost is lower. Think of it as a comparable number.
|
|
109
|
+
|
|
110
|
+
**Q: What happens when prices change?**
|
|
111
|
+
Run `roll config prices refresh`. If rates changed, a new snapshot is written and new
|
|
112
|
+
cycles use it. Old cycles keep their frozen `cost_list_usd`.
|
|
113
|
+
|
|
114
|
+
**Q: Can I add prices for a different vendor?**
|
|
115
|
+
Yes — `roll config prices refresh --vendor deepseek` (or `kimi`). The `--vendor` flag
|
|
116
|
+
tells the fetcher which vendor's pricing page to scrape.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Roll — Project Setup
|
|
2
|
+
|
|
3
|
+
## Initialize a Project
|
|
4
|
+
|
|
5
|
+
From your project root:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
roll init
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`roll init` diagnoses the current directory state before any mutation:
|
|
12
|
+
|
|
13
|
+
1. **Empty directory** — fresh start. Roll writes `AGENTS.md` plus an empty
|
|
14
|
+
`.roll/` scaffold (`backlog.md`, `features/`, `domain/`) ready for scoped
|
|
15
|
+
agent bindings in `.roll/agents.yaml`. No prompts. This is the **seed** adoption pattern — see
|
|
16
|
+
[patterns/seed-pattern.md](patterns/seed-pattern.md).
|
|
17
|
+
2. **PRD/docs-only** — Roll finds requirements or product docs but no source or
|
|
18
|
+
manifests. It treats this as a new-project path and points you to design; it
|
|
19
|
+
does not route to existing-codebase onboarding.
|
|
20
|
+
3. **Existing codebase without Roll** — Roll detects existing source files but no
|
|
21
|
+
`.roll/` directory. It does **not** silently scaffold; instead it surfaces
|
|
22
|
+
`$roll-onboard`, an interactive skill that scans the codebase, asks a short
|
|
23
|
+
set of cognition / scope / privacy questions, and produces
|
|
24
|
+
`.roll/init-diagnosis.yaml` and `.roll/onboard-plan.yaml` for review. After
|
|
25
|
+
reviewing the paired artifacts, run `roll init --apply`. It prints a checkpoint listing each planned file
|
|
26
|
+
operation, including action, target path, merge/create mode, and owner-content
|
|
27
|
+
handling, then waits for confirmation in an interactive terminal. In
|
|
28
|
+
non-interactive automation, use `roll init --apply --auto` after that review.
|
|
29
|
+
This is the **graft** pattern — see
|
|
30
|
+
[legacy-onboarding.md](legacy-onboarding.md) and
|
|
31
|
+
[patterns/graft-pattern.md](patterns/graft-pattern.md).
|
|
32
|
+
4. **Already initialized** — `.roll/`, `AGENTS.md`, backlog, and features are
|
|
33
|
+
present. Roll prints `Already initialized` and `Next: roll status`.
|
|
34
|
+
5. **Partial Roll** — some Roll markers are present but the setup is incomplete.
|
|
35
|
+
Roll prints the missing pieces and any stale pre-v2 markers. `roll init --repair`
|
|
36
|
+
previews the repair and asks in an interactive terminal; non-interactive
|
|
37
|
+
automation must use `roll init --repair --auto`. Repair only creates missing
|
|
38
|
+
Roll-owned files or merges Roll-owned sections, and records the changes in
|
|
39
|
+
`.roll/onboard-changeset.yaml` so `roll setup offboard` can reverse them later.
|
|
40
|
+
|
|
41
|
+
After any of these paths, `roll next` is the continuation command. It reads the
|
|
42
|
+
same Roll markers plus `.roll/brief.md`, `.roll/onboard-plan.yaml`, and
|
|
43
|
+
`.roll/backlog.md`, then prints one next action: design from the brief, review
|
|
44
|
+
and apply an onboard plan, repair partial markers, run the old-layout migration,
|
|
45
|
+
start the loop on the next Todo card, or inspect status when nothing is
|
|
46
|
+
actionable.
|
|
47
|
+
|
|
48
|
+
Upgrading from a pre-2.0 layout (`BACKLOG.md` at root or `docs/features/`)?
|
|
49
|
+
Run `npx @seanyao/roll@2 migrate` first — see
|
|
50
|
+
[migration-2.0.md](migration-2.0.md). `roll init` will refuse to scaffold on top
|
|
51
|
+
of a half-migrated project.
|
|
52
|
+
|
|
53
|
+
## Sync After Updating Roll
|
|
54
|
+
|
|
55
|
+
When roll ships a new version of its conventions or skills, sync them into your project:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
roll sync
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`sync` only overwrites files that roll manages (skills and global conventions). Your project-specific files (`.roll/backlog.md`, project source, etc.) are never touched.
|
|
62
|
+
|
|
63
|
+
## Typical First-Use Sequence
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
curl -fsSL https://seanyao.github.io/roll/install | bash # install roll
|
|
67
|
+
roll setup # configure AI tools globally (one time per machine)
|
|
68
|
+
cd my-project
|
|
69
|
+
roll init # diagnose and route this project
|
|
70
|
+
roll next # continue with design/apply/repair/migrate/loop/status
|
|
71
|
+
roll loop on # enable autonomous execution
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`roll setup` syncs conventions for the AI tools installed on the machine.
|
|
75
|
+
Agent semantics live in `~/.roll/agents.yaml` (Machine Scope) and
|
|
76
|
+
`.roll/agents.yaml` (Project Scope). Run `roll agent migrate --dry-run` to
|
|
77
|
+
convert older `primary_agent`, local-agent, pairing, or v3 route-slot data into
|
|
78
|
+
the scoped model.
|
|
79
|
+
|
|
80
|
+
## What Gets Created
|
|
81
|
+
|
|
82
|
+
| File | Purpose |
|
|
83
|
+
|------|---------|
|
|
84
|
+
| `AGENTS.md` | Agent conventions: domain model, scope, coding standards (root-level — entry point for every AI client) |
|
|
85
|
+
| `.roll/backlog.md` | Story tracking (Epic / Feature / Story / Fix / Refactor) |
|
|
86
|
+
| `.roll/features/` | Per-feature deep docs (one file per Feature) |
|
|
87
|
+
| `.roll/domain/` | DDD models, context map, architecture records |
|
|
88
|
+
|
|
89
|
+
## Re-initializing
|
|
90
|
+
|
|
91
|
+
`roll init` is safe to repeat. Complete projects get `Next: roll status`;
|
|
92
|
+
partial projects get `roll init --repair` instead of another scaffold pass.
|
|
93
|
+
|
|
94
|
+
## See Also
|
|
95
|
+
|
|
96
|
+
- [installation.md](installation.md) — install and update roll
|
|
97
|
+
- [conventions.md](conventions.md) — AGENTS.md structure and conventions
|
|
98
|
+
- [patterns/](patterns/README.md) — three adoption patterns (seed / graft / replant)
|
|
99
|
+
- [legacy-onboarding.md](legacy-onboarding.md) — graft Roll into an existing codebase
|
|
100
|
+
- [migration-2.0.md](migration-2.0.md) — upgrade a pre-2.0 project to `.roll/` layout
|
|
101
|
+
- [loop.md](loop.md) — enable autonomous execution
|
|
102
|
+
|
|
103
|
+
## Git Hooks Auto-Setup (US-INFRA-008/009)
|
|
104
|
+
|
|
105
|
+
Roll's TCR pre-commit gate lives in `hooks/pre-commit`. Git ignores this directory
|
|
106
|
+
by default — `core.hooksPath` must point to it. Roll configures this automatically
|
|
107
|
+
in three places so you never hit a "TCR gate bypassed" window:
|
|
108
|
+
|
|
109
|
+
1. **`roll setup`** — configures `core.hooksPath=hooks` in the current repo.
|
|
110
|
+
2. **Autonomous loop cycle preflight** — each cycle ensures the worktree it runs in
|
|
111
|
+
has the correct hooks path.
|
|
112
|
+
3. **Claude Code SessionStart hook** (`.claude/settings.json`) — runs
|
|
113
|
+
`git config core.hooksPath hooks` every time a new Claude Code session opens in
|
|
114
|
+
this project.
|
|
115
|
+
|
|
116
|
+
**Manual override:** If you've intentionally set `core.hooksPath` to something else,
|
|
117
|
+
Roll will not overwrite it. The auto-setup only fires when the value is unset or
|
|
118
|
+
equals the git default `.git/hooks`.
|
|
119
|
+
|
|
120
|
+
**Troubleshooting:** If commits are going through without running tests:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
git config core.hooksPath # should print: hooks
|
|
124
|
+
ls hooks/pre-commit # should exist and be executable
|
|
125
|
+
roll setup # re-runs the configuration step
|
|
126
|
+
```
|