@cleocode/skills 2026.5.4 → 2026.5.5
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/package.json +1 -1
- package/skills/ct-council/SKILL.md +0 -377
- package/skills/ct-council/optimization/HARDENING-PLAYBOOK.md +0 -107
- package/skills/ct-council/optimization/README.md +0 -74
- package/skills/ct-council/optimization/scenarios.yaml +0 -121
- package/skills/ct-council/optimization/scripts/campaign.py +0 -543
- package/skills/ct-council/optimization/scripts/test_campaign.py +0 -143
- package/skills/ct-council/references/chairman.md +0 -119
- package/skills/ct-council/references/contrarian.md +0 -70
- package/skills/ct-council/references/evidence-pack.md +0 -145
- package/skills/ct-council/references/examples.md +0 -235
- package/skills/ct-council/references/executor.md +0 -83
- package/skills/ct-council/references/expansionist.md +0 -68
- package/skills/ct-council/references/first-principles.md +0 -73
- package/skills/ct-council/references/outsider.md +0 -73
- package/skills/ct-council/references/peer-review.md +0 -125
- package/skills/ct-council/scripts/analyze_runs.py +0 -293
- package/skills/ct-council/scripts/fixtures/executor_multi.md +0 -198
- package/skills/ct-council/scripts/fixtures/missing_advisor.md +0 -117
- package/skills/ct-council/scripts/fixtures/missing_convergence.md +0 -190
- package/skills/ct-council/scripts/fixtures/thin_evidence.md +0 -193
- package/skills/ct-council/scripts/fixtures/valid.md +0 -226
- package/skills/ct-council/scripts/fixtures/valid_with_llmtxt.md +0 -226
- package/skills/ct-council/scripts/llmtxt_ref.py +0 -223
- package/skills/ct-council/scripts/run_council.py +0 -578
- package/skills/ct-council/scripts/telemetry.py +0 -624
- package/skills/ct-council/scripts/test_telemetry.py +0 -509
- package/skills/ct-council/scripts/test_validate.py +0 -452
- package/skills/ct-council/scripts/validate.py +0 -396
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
# The Council — Should we add a retry-on-timeout wrapper to outbound HTTP calls?
|
|
2
|
-
|
|
3
|
-
## Evidence pack
|
|
4
|
-
|
|
5
|
-
1. `packages/core/src/http.ts:L12-L58` — current httpGet/httpPost; no retry logic.
|
|
6
|
-
2. `packages/core/src/circuit-breaker.ts` — exists with zero callers.
|
|
7
|
-
3. commit `a1b2c3d "drop retries from http client"` — retries removed 18 months ago citing retry storms.
|
|
8
|
-
|
|
9
|
-
## Phase 1 — Advisor analyses
|
|
10
|
-
|
|
11
|
-
### Advisor: Contrarian
|
|
12
|
-
|
|
13
|
-
**Frame:** Assume the plan is wrong. What fails first? What's been overlooked? Why is this a worse idea than it looks?
|
|
14
|
-
|
|
15
|
-
**Evidence anchored:**
|
|
16
|
-
- commit `a1b2c3d` — retries were pulled for a documented reason.
|
|
17
|
-
- `packages/core/src/http.ts` — zero per-caller rate limits.
|
|
18
|
-
|
|
19
|
-
**Findings (failure modes):**
|
|
20
|
-
1. **Retry storm** — triggers when upstream latency spikes. Fails by multiplying load. Detected silently until breaker trips.
|
|
21
|
-
|
|
22
|
-
**Verdict from this lens:** Plan re-introduces a known incident class.
|
|
23
|
-
|
|
24
|
-
**Single sharpest point:** A global retry wrapper without a circuit breaker reproduces the exact bug retries were removed to fix.
|
|
25
|
-
|
|
26
|
-
### Advisor: First Principles
|
|
27
|
-
|
|
28
|
-
**Frame:** Ignore everything that was said. What is actually true here? Break this down to first principles and answer from zero.
|
|
29
|
-
|
|
30
|
-
**Evidence anchored:**
|
|
31
|
-
- RFC 7231 — HTTP POST semantics.
|
|
32
|
-
- `packages/core/src/http.ts:L12-L58` — current implementation.
|
|
33
|
-
|
|
34
|
-
**Atomic truths:**
|
|
35
|
-
1. Some errors are transient; some are not.
|
|
36
|
-
2. Idempotent requests can be retried; non-idempotent cannot.
|
|
37
|
-
3. Unbounded retries are always wrong.
|
|
38
|
-
|
|
39
|
-
**Reconstructed solution:** classify errors, retry only idempotent methods on retryable errors, gate through circuit breaker.
|
|
40
|
-
|
|
41
|
-
**Reconstruction vs. plan:**
|
|
42
|
-
- Convergences: retry on timeout.
|
|
43
|
-
- Divergences: no idempotency classification — genuine error.
|
|
44
|
-
|
|
45
|
-
**Verdict from this lens:** Plan covers one-third of correct design.
|
|
46
|
-
|
|
47
|
-
**Single sharpest point:** Non-idempotent requests cannot be blindly retried.
|
|
48
|
-
|
|
49
|
-
### Advisor: Expansionist
|
|
50
|
-
|
|
51
|
-
**Frame:** Forget the constraints. What's the biggest version of this? What opportunity is sitting right in front of us that nobody is talking about?
|
|
52
|
-
|
|
53
|
-
**Evidence anchored:**
|
|
54
|
-
- `packages/core/src/circuit-breaker.ts` — dormant asset.
|
|
55
|
-
- `MEMORY.md` — flaky CI is #2 pain source.
|
|
56
|
-
|
|
57
|
-
**Findings (opportunities):**
|
|
58
|
-
1. **Wire breaker** — captures system-wide resilience. Asymmetry: 200 lines for permanent optionality.
|
|
59
|
-
|
|
60
|
-
**Verdict from this lens:** Owner is thinking too small; there's a platform layer hiding here.
|
|
61
|
-
|
|
62
|
-
**Single sharpest point:** Wire the circuit breaker as part of this change for asymmetric upside.
|
|
63
|
-
|
|
64
|
-
### Advisor: Outsider
|
|
65
|
-
|
|
66
|
-
**Frame:** You have no context. Ignore all backstory. Look only at what's in front of you. Tell me what a complete stranger would conclude.
|
|
67
|
-
|
|
68
|
-
**Evidence anchored:**
|
|
69
|
-
- `packages/core/src/circuit-breaker.ts` — zero callers.
|
|
70
|
-
- `docs/adr/ADR-021-http-client.md` — "revisit when circuit-breaker lands".
|
|
71
|
-
|
|
72
|
-
**Findings (stranger's eyes):**
|
|
73
|
-
1. Unused module whose ADR says it's the precondition for this exact change.
|
|
74
|
-
|
|
75
|
-
**What the artifact claims vs. shows:** ADR claims design awaits the breaker; code shows breaker has landed.
|
|
76
|
-
|
|
77
|
-
**Verdict from this lens:** Project prepared but didn't close the loop.
|
|
78
|
-
|
|
79
|
-
**Single sharpest point:** ADR-021 says do this when the breaker lands; the breaker has landed; plan ignores both.
|
|
80
|
-
|
|
81
|
-
### Advisor: Executor
|
|
82
|
-
|
|
83
|
-
**Frame:** Don't analyze. Don't debate. What is the single most important action to take right now? Give me one step I can start in the next hour.
|
|
84
|
-
|
|
85
|
-
**Evidence anchored:**
|
|
86
|
-
- `packages/core/test/http.test.ts::handles timeout` — baseline test.
|
|
87
|
-
- `packages/core/src/circuit-breaker.ts` — has `execute()` method.
|
|
88
|
-
|
|
89
|
-
**The action (one):**
|
|
90
|
-
Write `packages/core/test/http-retry.test.ts` with one failing test: httpGet retries once on TimeoutError, httpPost does not.
|
|
91
|
-
|
|
92
|
-
**Expected outcome (60 minutes from now):**
|
|
93
|
-
New test file, one failing test with message "retries not implemented".
|
|
94
|
-
|
|
95
|
-
**What this unblocks:**
|
|
96
|
-
Test-first implementation; forces idempotency decision.
|
|
97
|
-
|
|
98
|
-
**Verdict from this lens:** Pin the design in a failing test before prose.
|
|
99
|
-
|
|
100
|
-
**Single sharpest point:** Write packages/core/test/http-retry.test.ts with retry-on-GET / no-retry-on-POST assertion.
|
|
101
|
-
|
|
102
|
-
## Phase 2 — Shuffled peer reviews
|
|
103
|
-
|
|
104
|
-
### Contrarian reviewing First Principles
|
|
105
|
-
|
|
106
|
-
**Gate results:**
|
|
107
|
-
- G1 Rigor: PASS — "Non-idempotent requests cannot be blindly retried" is specific.
|
|
108
|
-
- G2 Evidence grounding: PASS — cited RFC 7231 and http.ts.
|
|
109
|
-
- G3 Frame integrity: PASS — stayed in atomic-truth lane.
|
|
110
|
-
- G4 Actionability: PASS — "idempotency classification is mandatory" is decidable.
|
|
111
|
-
|
|
112
|
-
**Strongest finding (from reviewee):** The idempotency atom.
|
|
113
|
-
|
|
114
|
-
**Gap from Contrarian's frame:** Didn't name the incident class duplicate writes enable.
|
|
115
|
-
|
|
116
|
-
**What I would add:** Idempotency omission is not just correctness — it's data-integrity hazard.
|
|
117
|
-
|
|
118
|
-
**Disposition:** Accept — correctness framing holds.
|
|
119
|
-
|
|
120
|
-
### First Principles reviewing Expansionist
|
|
121
|
-
|
|
122
|
-
**Gate results:**
|
|
123
|
-
- G1 Rigor: PASS — names specific opportunity (wire the breaker).
|
|
124
|
-
- G2 Evidence grounding: PASS — cites circuit-breaker.ts.
|
|
125
|
-
- G3 Frame integrity: PASS — stayed on upside.
|
|
126
|
-
- G4 Actionability: PASS — "wire the breaker as part of this change".
|
|
127
|
-
|
|
128
|
-
**Strongest finding (from reviewee):** Dormant-asset observation.
|
|
129
|
-
|
|
130
|
-
**Gap from First Principles' frame:** 2s timeout claim not derived from atoms.
|
|
131
|
-
|
|
132
|
-
**What I would add:** Breaker is a correctness tool, not a performance tool.
|
|
133
|
-
|
|
134
|
-
**Disposition:** Accept — core upside is valid.
|
|
135
|
-
|
|
136
|
-
### Expansionist reviewing Outsider
|
|
137
|
-
|
|
138
|
-
**Gate results:**
|
|
139
|
-
- G1 Rigor: PASS — crisp claim/reality gap.
|
|
140
|
-
- G2 Evidence grounding: PASS — cited exact ADR sentence.
|
|
141
|
-
- G3 Frame integrity: PASS — no backstory smuggled.
|
|
142
|
-
- G4 Actionability: PASS — observation directly decides plan revision.
|
|
143
|
-
|
|
144
|
-
**Strongest finding (from reviewee):** ADR says do this when breaker lands; breaker has landed.
|
|
145
|
-
|
|
146
|
-
**Gap from Expansionist's frame:** Didn't notice the upside of the unused breaker.
|
|
147
|
-
|
|
148
|
-
**What I would add:** Gap isn't just mismatch; it's shovel-ready upgrade.
|
|
149
|
-
|
|
150
|
-
**Disposition:** Accept — observation is decisive.
|
|
151
|
-
|
|
152
|
-
### Outsider reviewing Executor
|
|
153
|
-
|
|
154
|
-
**Gate results:**
|
|
155
|
-
- G1 Rigor: PASS — action is precise, outcome unambiguous.
|
|
156
|
-
- G2 Evidence grounding: PASS — test file anchor.
|
|
157
|
-
- G3 Frame integrity: PASS — one action, no analysis creep.
|
|
158
|
-
- G4 Actionability: PASS — test-writing.
|
|
159
|
-
|
|
160
|
-
**Strongest finding (from reviewee):** Pin design in failing test before code.
|
|
161
|
-
|
|
162
|
-
**Gap from Outsider's frame:** Action is standard TDD, not novel.
|
|
163
|
-
|
|
164
|
-
**What I would add:** Nothing — recognizable good practice validates it.
|
|
165
|
-
|
|
166
|
-
**Disposition:** Accept — start now.
|
|
167
|
-
|
|
168
|
-
### Executor reviewing Contrarian
|
|
169
|
-
|
|
170
|
-
**Gate results:**
|
|
171
|
-
- G1 Rigor: PASS — names specific incident class with trigger.
|
|
172
|
-
- G2 Evidence grounding: PASS — cites historical commit and ADR.
|
|
173
|
-
- G3 Frame integrity: PASS — pure risk, no upside creep.
|
|
174
|
-
- G4 Actionability: PASS — failure is mitigable by wiring the breaker first.
|
|
175
|
-
|
|
176
|
-
**Strongest finding (from reviewee):** Retry-storm claim.
|
|
177
|
-
|
|
178
|
-
**Gap from Executor's frame:** Says dangerous but doesn't name the action that discharges the risk.
|
|
179
|
-
|
|
180
|
-
**What I would add:** The action is: wire the breaker before retries.
|
|
181
|
-
|
|
182
|
-
**Disposition:** Accept — risk is real, mitigation is bounded.
|
|
183
|
-
|
|
184
|
-
## Phase 2.5 — Convergence check
|
|
185
|
-
|
|
186
|
-
Compared the five "single sharpest point" statements. Distinct subjects: retry storms, idempotency, breaker wiring, ADR-precondition gap, test-first action. No convergence flag raised. Proceeding to Phase 3.
|
|
187
|
-
|
|
188
|
-
## Phase 3 — Chairman's verdict
|
|
189
|
-
|
|
190
|
-
### Gate summary
|
|
191
|
-
|
|
192
|
-
| Advisor | G1 Rigor | G2 Evidence | G3 Frame | G4 Actionability | Weight |
|
|
193
|
-
|---|---|---|---|---|---|
|
|
194
|
-
| Contrarian | PASS | PASS | PASS | PASS | full |
|
|
195
|
-
| First Principles | PASS | PASS | PASS | PASS | full |
|
|
196
|
-
| Expansionist | PASS | PASS | PASS | PASS | full |
|
|
197
|
-
| Outsider | PASS | PASS | PASS | PASS | full |
|
|
198
|
-
| Executor | PASS | PASS | PASS | PASS | full |
|
|
199
|
-
|
|
200
|
-
### Recommendation
|
|
201
|
-
|
|
202
|
-
Do not ship retries alone. Wire the circuit breaker first, then add retries scoped to idempotent methods (GET) only, driven by a failing test.
|
|
203
|
-
|
|
204
|
-
### Why this, not the alternatives
|
|
205
|
-
|
|
206
|
-
Four of five frames converged on the same structural point from different angles: Contrarian flagged retry storms as re-incident risk; First Principles derived idempotency as an atomic truth the plan violated; Outsider identified ADR-021's explicit precondition as already met; Executor chose an action that forces the idempotency decision into a test. The Expansionist's broader frame (system-wide resilience layer) was sharp but out of scope; defer that to follow-up. No unresolved contention.
|
|
207
|
-
|
|
208
|
-
### What each advisor got right (carried forward)
|
|
209
|
-
|
|
210
|
-
- **Contrarian's fatal flaw to mitigate:** A retry wrapper without a circuit breaker reproduces the 18-month-old retry-storm incident.
|
|
211
|
-
- **First Principles' atomic truth worth protecting:** Non-idempotent requests cannot be blindly retried.
|
|
212
|
-
- **Expansionist's upside to pursue (or defer):** The dormant circuit breaker is a system-wide resilience asset; wire it in-scope for this change.
|
|
213
|
-
- **Outsider's pattern flag:** ADR-021 said this exact precondition must be met; it has been; the plan ignores both facts.
|
|
214
|
-
- **Executor's action (validated):** Write `packages/core/test/http-retry.test.ts` asserting retry-on-GET, no-retry-on-POST.
|
|
215
|
-
|
|
216
|
-
### Conditions on the recommendation
|
|
217
|
-
|
|
218
|
-
Conditional on: (1) the failing test being written first, (2) the circuit breaker wired in the same PR as retries, not a follow-up.
|
|
219
|
-
|
|
220
|
-
### Next 60-minute action
|
|
221
|
-
|
|
222
|
-
Write `packages/core/test/http-retry.test.ts` with one failing test: httpGet retries once on TimeoutError, httpPost does not retry. Document the idempotency decision in the describe block.
|
|
223
|
-
|
|
224
|
-
### Confidence
|
|
225
|
-
|
|
226
|
-
High — four independent frames converged; no unresolved contention; action is startable immediately.
|
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
# The Council — Should we add a retry-on-timeout wrapper to outbound HTTP calls?
|
|
2
|
-
|
|
3
|
-
## Evidence pack
|
|
4
|
-
|
|
5
|
-
1. `packages/core/src/http.ts:L12-L58` — current httpGet/httpPost; no retry logic.
|
|
6
|
-
2. `llmtxt:circuit-breaker-spec@v1` — compressed external spec, fetched via scripts/llmtxt_ref.py.
|
|
7
|
-
3. commit `a1b2c3d "drop retries from http client"` — retries removed 18 months ago citing retry storms.
|
|
8
|
-
|
|
9
|
-
## Phase 1 — Advisor analyses
|
|
10
|
-
|
|
11
|
-
### Advisor: Contrarian
|
|
12
|
-
|
|
13
|
-
**Frame:** Assume the plan is wrong. What fails first? What's been overlooked? Why is this a worse idea than it looks?
|
|
14
|
-
|
|
15
|
-
**Evidence anchored:**
|
|
16
|
-
- commit `a1b2c3d` — retries were pulled for a documented reason.
|
|
17
|
-
- `packages/core/src/http.ts` — zero per-caller rate limits.
|
|
18
|
-
|
|
19
|
-
**Findings (failure modes):**
|
|
20
|
-
1. **Retry storm** — triggers when upstream latency spikes. Fails by multiplying load. Detected silently until breaker trips.
|
|
21
|
-
|
|
22
|
-
**Verdict from this lens:** Plan re-introduces a known incident class.
|
|
23
|
-
|
|
24
|
-
**Single sharpest point:** A global retry wrapper without a circuit breaker reproduces the exact bug retries were removed to fix.
|
|
25
|
-
|
|
26
|
-
### Advisor: First Principles
|
|
27
|
-
|
|
28
|
-
**Frame:** Ignore everything that was said. What is actually true here? Break this down to first principles and answer from zero.
|
|
29
|
-
|
|
30
|
-
**Evidence anchored:**
|
|
31
|
-
- RFC 7231 — HTTP POST semantics.
|
|
32
|
-
- `packages/core/src/http.ts:L12-L58` — current implementation.
|
|
33
|
-
|
|
34
|
-
**Atomic truths:**
|
|
35
|
-
1. Some errors are transient; some are not.
|
|
36
|
-
2. Idempotent requests can be retried; non-idempotent cannot.
|
|
37
|
-
3. Unbounded retries are always wrong.
|
|
38
|
-
|
|
39
|
-
**Reconstructed solution:** classify errors, retry only idempotent methods on retryable errors, gate through circuit breaker.
|
|
40
|
-
|
|
41
|
-
**Reconstruction vs. plan:**
|
|
42
|
-
- Convergences: retry on timeout.
|
|
43
|
-
- Divergences: no idempotency classification — genuine error.
|
|
44
|
-
|
|
45
|
-
**Verdict from this lens:** Plan covers one-third of correct design.
|
|
46
|
-
|
|
47
|
-
**Single sharpest point:** Non-idempotent requests cannot be blindly retried.
|
|
48
|
-
|
|
49
|
-
### Advisor: Expansionist
|
|
50
|
-
|
|
51
|
-
**Frame:** Forget the constraints. What's the biggest version of this? What opportunity is sitting right in front of us that nobody is talking about?
|
|
52
|
-
|
|
53
|
-
**Evidence anchored:**
|
|
54
|
-
- `packages/core/src/circuit-breaker.ts` — dormant asset.
|
|
55
|
-
- `MEMORY.md` — flaky CI is #2 pain source.
|
|
56
|
-
|
|
57
|
-
**Findings (opportunities):**
|
|
58
|
-
1. **Wire breaker** — captures system-wide resilience. Asymmetry: 200 lines for permanent optionality.
|
|
59
|
-
|
|
60
|
-
**Verdict from this lens:** Owner is thinking too small; there's a platform layer hiding here.
|
|
61
|
-
|
|
62
|
-
**Single sharpest point:** Wire the circuit breaker as part of this change for asymmetric upside.
|
|
63
|
-
|
|
64
|
-
### Advisor: Outsider
|
|
65
|
-
|
|
66
|
-
**Frame:** You have no context. Ignore all backstory. Look only at what's in front of you. Tell me what a complete stranger would conclude.
|
|
67
|
-
|
|
68
|
-
**Evidence anchored:**
|
|
69
|
-
- `packages/core/src/circuit-breaker.ts` — zero callers.
|
|
70
|
-
- `docs/adr/ADR-021-http-client.md` — "revisit when circuit-breaker lands".
|
|
71
|
-
|
|
72
|
-
**Findings (stranger's eyes):**
|
|
73
|
-
1. Unused module whose ADR says it's the precondition for this exact change.
|
|
74
|
-
|
|
75
|
-
**What the artifact claims vs. shows:** ADR claims design awaits the breaker; code shows breaker has landed.
|
|
76
|
-
|
|
77
|
-
**Verdict from this lens:** Project prepared but didn't close the loop.
|
|
78
|
-
|
|
79
|
-
**Single sharpest point:** ADR-021 says do this when the breaker lands; the breaker has landed; plan ignores both.
|
|
80
|
-
|
|
81
|
-
### Advisor: Executor
|
|
82
|
-
|
|
83
|
-
**Frame:** Don't analyze. Don't debate. What is the single most important action to take right now? Give me one step I can start in the next hour.
|
|
84
|
-
|
|
85
|
-
**Evidence anchored:**
|
|
86
|
-
- `packages/core/test/http.test.ts::handles timeout` — baseline test.
|
|
87
|
-
- `packages/core/src/circuit-breaker.ts` — has `execute()` method.
|
|
88
|
-
|
|
89
|
-
**The action (one):**
|
|
90
|
-
Write `packages/core/test/http-retry.test.ts` with one failing test: httpGet retries once on TimeoutError, httpPost does not.
|
|
91
|
-
|
|
92
|
-
**Expected outcome (60 minutes from now):**
|
|
93
|
-
New test file, one failing test with message "retries not implemented".
|
|
94
|
-
|
|
95
|
-
**What this unblocks:**
|
|
96
|
-
Test-first implementation; forces idempotency decision.
|
|
97
|
-
|
|
98
|
-
**Verdict from this lens:** Pin the design in a failing test before prose.
|
|
99
|
-
|
|
100
|
-
**Single sharpest point:** Write packages/core/test/http-retry.test.ts with retry-on-GET / no-retry-on-POST assertion.
|
|
101
|
-
|
|
102
|
-
## Phase 2 — Shuffled peer reviews
|
|
103
|
-
|
|
104
|
-
### Contrarian reviewing First Principles
|
|
105
|
-
|
|
106
|
-
**Gate results:**
|
|
107
|
-
- G1 Rigor: PASS — "Non-idempotent requests cannot be blindly retried" is specific.
|
|
108
|
-
- G2 Evidence grounding: PASS — cited RFC 7231 and http.ts.
|
|
109
|
-
- G3 Frame integrity: PASS — stayed in atomic-truth lane.
|
|
110
|
-
- G4 Actionability: PASS — "idempotency classification is mandatory" is decidable.
|
|
111
|
-
|
|
112
|
-
**Strongest finding (from reviewee):** The idempotency atom.
|
|
113
|
-
|
|
114
|
-
**Gap from Contrarian's frame:** Didn't name the incident class duplicate writes enable.
|
|
115
|
-
|
|
116
|
-
**What I would add:** Idempotency omission is not just correctness — it's data-integrity hazard.
|
|
117
|
-
|
|
118
|
-
**Disposition:** Accept — correctness framing holds.
|
|
119
|
-
|
|
120
|
-
### First Principles reviewing Expansionist
|
|
121
|
-
|
|
122
|
-
**Gate results:**
|
|
123
|
-
- G1 Rigor: PASS — names specific opportunity (wire the breaker).
|
|
124
|
-
- G2 Evidence grounding: PASS — cites circuit-breaker.ts.
|
|
125
|
-
- G3 Frame integrity: PASS — stayed on upside.
|
|
126
|
-
- G4 Actionability: PASS — "wire the breaker as part of this change".
|
|
127
|
-
|
|
128
|
-
**Strongest finding (from reviewee):** Dormant-asset observation.
|
|
129
|
-
|
|
130
|
-
**Gap from First Principles' frame:** 2s timeout claim not derived from atoms.
|
|
131
|
-
|
|
132
|
-
**What I would add:** Breaker is a correctness tool, not a performance tool.
|
|
133
|
-
|
|
134
|
-
**Disposition:** Accept — core upside is valid.
|
|
135
|
-
|
|
136
|
-
### Expansionist reviewing Outsider
|
|
137
|
-
|
|
138
|
-
**Gate results:**
|
|
139
|
-
- G1 Rigor: PASS — crisp claim/reality gap.
|
|
140
|
-
- G2 Evidence grounding: PASS — cited exact ADR sentence.
|
|
141
|
-
- G3 Frame integrity: PASS — no backstory smuggled.
|
|
142
|
-
- G4 Actionability: PASS — observation directly decides plan revision.
|
|
143
|
-
|
|
144
|
-
**Strongest finding (from reviewee):** ADR says do this when breaker lands; breaker has landed.
|
|
145
|
-
|
|
146
|
-
**Gap from Expansionist's frame:** Didn't notice the upside of the unused breaker.
|
|
147
|
-
|
|
148
|
-
**What I would add:** Gap isn't just mismatch; it's shovel-ready upgrade.
|
|
149
|
-
|
|
150
|
-
**Disposition:** Accept — observation is decisive.
|
|
151
|
-
|
|
152
|
-
### Outsider reviewing Executor
|
|
153
|
-
|
|
154
|
-
**Gate results:**
|
|
155
|
-
- G1 Rigor: PASS — action is precise, outcome unambiguous.
|
|
156
|
-
- G2 Evidence grounding: PASS — test file anchor.
|
|
157
|
-
- G3 Frame integrity: PASS — one action, no analysis creep.
|
|
158
|
-
- G4 Actionability: PASS — test-writing.
|
|
159
|
-
|
|
160
|
-
**Strongest finding (from reviewee):** Pin design in failing test before code.
|
|
161
|
-
|
|
162
|
-
**Gap from Outsider's frame:** Action is standard TDD, not novel.
|
|
163
|
-
|
|
164
|
-
**What I would add:** Nothing — recognizable good practice validates it.
|
|
165
|
-
|
|
166
|
-
**Disposition:** Accept — start now.
|
|
167
|
-
|
|
168
|
-
### Executor reviewing Contrarian
|
|
169
|
-
|
|
170
|
-
**Gate results:**
|
|
171
|
-
- G1 Rigor: PASS — names specific incident class with trigger.
|
|
172
|
-
- G2 Evidence grounding: PASS — cites historical commit and ADR.
|
|
173
|
-
- G3 Frame integrity: PASS — pure risk, no upside creep.
|
|
174
|
-
- G4 Actionability: PASS — failure is mitigable by wiring the breaker first.
|
|
175
|
-
|
|
176
|
-
**Strongest finding (from reviewee):** Retry-storm claim.
|
|
177
|
-
|
|
178
|
-
**Gap from Executor's frame:** Says dangerous but doesn't name the action that discharges the risk.
|
|
179
|
-
|
|
180
|
-
**What I would add:** The action is: wire the breaker before retries.
|
|
181
|
-
|
|
182
|
-
**Disposition:** Accept — risk is real, mitigation is bounded.
|
|
183
|
-
|
|
184
|
-
## Phase 2.5 — Convergence check
|
|
185
|
-
|
|
186
|
-
Compared the five "single sharpest point" statements. Distinct subjects: retry storms, idempotency, breaker wiring, ADR-precondition gap, test-first action. No convergence flag raised. Proceeding to Phase 3.
|
|
187
|
-
|
|
188
|
-
## Phase 3 — Chairman's verdict
|
|
189
|
-
|
|
190
|
-
### Gate summary
|
|
191
|
-
|
|
192
|
-
| Advisor | G1 Rigor | G2 Evidence | G3 Frame | G4 Actionability | Weight |
|
|
193
|
-
|---|---|---|---|---|---|
|
|
194
|
-
| Contrarian | PASS | PASS | PASS | PASS | full |
|
|
195
|
-
| First Principles | PASS | PASS | PASS | PASS | full |
|
|
196
|
-
| Expansionist | PASS | PASS | PASS | PASS | full |
|
|
197
|
-
| Outsider | PASS | PASS | PASS | PASS | full |
|
|
198
|
-
| Executor | PASS | PASS | PASS | PASS | full |
|
|
199
|
-
|
|
200
|
-
### Recommendation
|
|
201
|
-
|
|
202
|
-
Do not ship retries alone. Wire the circuit breaker first, then add retries scoped to idempotent methods (GET) only, driven by a failing test.
|
|
203
|
-
|
|
204
|
-
### Why this, not the alternatives
|
|
205
|
-
|
|
206
|
-
Four of five frames converged on the same structural point from different angles: Contrarian flagged retry storms as re-incident risk; First Principles derived idempotency as an atomic truth the plan violated; Outsider identified ADR-021's explicit precondition as already met; Executor chose an action that forces the idempotency decision into a test. The Expansionist's broader frame (system-wide resilience layer) was sharp but out of scope; defer that to follow-up. No unresolved contention.
|
|
207
|
-
|
|
208
|
-
### What each advisor got right (carried forward)
|
|
209
|
-
|
|
210
|
-
- **Contrarian's fatal flaw to mitigate:** A retry wrapper without a circuit breaker reproduces the 18-month-old retry-storm incident.
|
|
211
|
-
- **First Principles' atomic truth worth protecting:** Non-idempotent requests cannot be blindly retried.
|
|
212
|
-
- **Expansionist's upside to pursue (or defer):** The dormant circuit breaker is a system-wide resilience asset; wire it in-scope for this change.
|
|
213
|
-
- **Outsider's pattern flag:** ADR-021 said this exact precondition must be met; it has been; the plan ignores both facts.
|
|
214
|
-
- **Executor's action (validated):** Write `packages/core/test/http-retry.test.ts` asserting retry-on-GET, no-retry-on-POST.
|
|
215
|
-
|
|
216
|
-
### Conditions on the recommendation
|
|
217
|
-
|
|
218
|
-
Conditional on: (1) the failing test being written first, (2) the circuit breaker wired in the same PR as retries, not a follow-up.
|
|
219
|
-
|
|
220
|
-
### Next 60-minute action
|
|
221
|
-
|
|
222
|
-
Write `packages/core/test/http-retry.test.ts` with one failing test: httpGet retries once on TimeoutError, httpPost does not retry. Document the idempotency decision in the describe block.
|
|
223
|
-
|
|
224
|
-
### Confidence
|
|
225
|
-
|
|
226
|
-
High — four independent frames converged; no unresolved contention; action is startable immediately.
|
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
llmtxt_ref.py — fetch an llmtxt overview for use as a Council evidence-pack item.
|
|
4
|
-
|
|
5
|
-
The llmtxt.my service provides progressive-disclosure document overviews that
|
|
6
|
-
compress long external docs (library refs, API specs, ADRs) into 60–80% fewer
|
|
7
|
-
tokens, letting the Council ground in external material without blowing out the
|
|
8
|
-
evidence pack when five advisor subagents each receive it.
|
|
9
|
-
|
|
10
|
-
Usage:
|
|
11
|
-
llmtxt_ref.py <slug>[@<version>]
|
|
12
|
-
llmtxt_ref.py <slug> --no-cache
|
|
13
|
-
llmtxt_ref.py <slug> --json
|
|
14
|
-
llmtxt_ref.py <slug> --raw # suppress the evidence-pack header
|
|
15
|
-
|
|
16
|
-
Output (stdout):
|
|
17
|
-
Markdown-formatted overview, ready to paste into a Phase 0 evidence pack.
|
|
18
|
-
|
|
19
|
-
Auth:
|
|
20
|
-
Public documents: no auth required (anonymous mode handled server-side).
|
|
21
|
-
Private/org documents: set LLMTXT_API_KEY (Bearer token, prefix 'llmtxt_').
|
|
22
|
-
|
|
23
|
-
Rate limits (anonymous, per-IP): 60 reads/min. The wrapper honors
|
|
24
|
-
x-ratelimit-remaining / x-ratelimit-reset / retry-after headers and surfaces
|
|
25
|
-
warnings on stderr.
|
|
26
|
-
|
|
27
|
-
Cache:
|
|
28
|
-
~/.cache/council/llmtxt/<slug>/<version>.md (immutable — indefinite)
|
|
29
|
-
~/.cache/council/llmtxt/<slug>/_latest.md (60s TTL)
|
|
30
|
-
|
|
31
|
-
Override cache location with COUNCIL_CACHE_DIR; override API base with
|
|
32
|
-
LLMTXT_API_BASE (default: https://api.llmtxt.my).
|
|
33
|
-
|
|
34
|
-
Exit codes:
|
|
35
|
-
0 — success, overview printed
|
|
36
|
-
1 — network / service error
|
|
37
|
-
2 — not found (404) or not accessible without auth
|
|
38
|
-
3 — invalid slug format / usage error
|
|
39
|
-
"""
|
|
40
|
-
|
|
41
|
-
from __future__ import annotations
|
|
42
|
-
|
|
43
|
-
import argparse
|
|
44
|
-
import http.cookiejar as cookiejar
|
|
45
|
-
import json
|
|
46
|
-
import os
|
|
47
|
-
import re
|
|
48
|
-
import sys
|
|
49
|
-
import time
|
|
50
|
-
import urllib.error
|
|
51
|
-
import urllib.request
|
|
52
|
-
from pathlib import Path
|
|
53
|
-
|
|
54
|
-
API_BASE = os.environ.get("LLMTXT_API_BASE", "https://api.llmtxt.my")
|
|
55
|
-
CACHE_DIR = Path(os.environ.get(
|
|
56
|
-
"COUNCIL_CACHE_DIR",
|
|
57
|
-
str(Path.home() / ".cache" / "council" / "llmtxt"),
|
|
58
|
-
))
|
|
59
|
-
COOKIE_JAR_PATH = CACHE_DIR.parent / "cookies.txt"
|
|
60
|
-
LATEST_TTL_SECONDS = 60
|
|
61
|
-
TIMEOUT = 30
|
|
62
|
-
|
|
63
|
-
SLUG_PATTERN = re.compile(r"^[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?$")
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def parse_ref(ref: str) -> tuple[str, str | None]:
|
|
67
|
-
"""Parse a <slug> or <slug>@<version> reference. Raises ValueError on malformed slug."""
|
|
68
|
-
if "@" in ref:
|
|
69
|
-
slug, version = ref.split("@", 1)
|
|
70
|
-
if not version:
|
|
71
|
-
raise ValueError(f"Empty version in reference: {ref!r}")
|
|
72
|
-
else:
|
|
73
|
-
slug, version = ref, None
|
|
74
|
-
if not SLUG_PATTERN.match(slug):
|
|
75
|
-
raise ValueError(
|
|
76
|
-
f"Invalid slug format: {slug!r}. "
|
|
77
|
-
"Expected lowercase alphanumeric with dashes, 1–64 chars, no leading/trailing dash."
|
|
78
|
-
)
|
|
79
|
-
return slug, version
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def cache_path(slug: str, version: str | None) -> Path:
|
|
83
|
-
"""Return the cache path for (slug, version). No filesystem side effects."""
|
|
84
|
-
if version:
|
|
85
|
-
# Sanitize version for filename safety without altering the slug.
|
|
86
|
-
safe_version = re.sub(r"[^a-zA-Z0-9._-]", "_", version)
|
|
87
|
-
return CACHE_DIR / slug / f"{safe_version}.md"
|
|
88
|
-
return CACHE_DIR / slug / "_latest.md"
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def cache_is_fresh(path: Path, immutable: bool) -> bool:
|
|
92
|
-
"""True if the cached file exists and (if mutable) is newer than LATEST_TTL_SECONDS."""
|
|
93
|
-
if not path.exists():
|
|
94
|
-
return False
|
|
95
|
-
if immutable:
|
|
96
|
-
return True
|
|
97
|
-
age = time.time() - path.stat().st_mtime
|
|
98
|
-
return age < LATEST_TTL_SECONDS
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def _build_opener() -> urllib.request.OpenerDirector:
|
|
102
|
-
"""Build a URL opener with persistent cookie jar so anonymous sessions survive invocations."""
|
|
103
|
-
jar = cookiejar.MozillaCookieJar(str(COOKIE_JAR_PATH))
|
|
104
|
-
if COOKIE_JAR_PATH.exists():
|
|
105
|
-
try:
|
|
106
|
-
jar.load(ignore_discard=True)
|
|
107
|
-
except Exception:
|
|
108
|
-
# Corrupt cookie file — ignore and overwrite on next save.
|
|
109
|
-
pass
|
|
110
|
-
opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(jar))
|
|
111
|
-
return opener
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
def _save_cookies(opener: urllib.request.OpenerDirector) -> None:
|
|
115
|
-
for handler in opener.handlers:
|
|
116
|
-
if isinstance(handler, urllib.request.HTTPCookieProcessor):
|
|
117
|
-
jar = handler.cookiejar
|
|
118
|
-
if isinstance(jar, cookiejar.MozillaCookieJar):
|
|
119
|
-
COOKIE_JAR_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
120
|
-
try:
|
|
121
|
-
jar.save(ignore_discard=True)
|
|
122
|
-
except Exception:
|
|
123
|
-
pass
|
|
124
|
-
return
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
def fetch_overview(slug: str, version: str | None, timeout: int = TIMEOUT) -> str:
|
|
128
|
-
"""Fetch overview from api.llmtxt.my. Raises LookupError for 404, RuntimeError otherwise.
|
|
129
|
-
|
|
130
|
-
Uses a persistent cookie jar so anonymous sessions (better-auth anonymous plugin,
|
|
131
|
-
24h TTL) survive across invocations. For private/org documents, set LLMTXT_API_KEY.
|
|
132
|
-
"""
|
|
133
|
-
url = f"{API_BASE}/api/documents/{slug}/overview"
|
|
134
|
-
if version:
|
|
135
|
-
url += f"?version={version}"
|
|
136
|
-
req = urllib.request.Request(url, headers={"User-Agent": "council-skill/1.0"})
|
|
137
|
-
api_key = os.environ.get("LLMTXT_API_KEY")
|
|
138
|
-
if api_key:
|
|
139
|
-
req.add_header("Authorization", f"Bearer {api_key}")
|
|
140
|
-
|
|
141
|
-
opener = _build_opener()
|
|
142
|
-
try:
|
|
143
|
-
with opener.open(req, timeout=timeout) as resp:
|
|
144
|
-
body = resp.read().decode("utf-8")
|
|
145
|
-
remaining = resp.headers.get("x-ratelimit-remaining")
|
|
146
|
-
if remaining and remaining.isdigit() and int(remaining) == 0:
|
|
147
|
-
reset = resp.headers.get("x-ratelimit-reset", "unknown")
|
|
148
|
-
print(f"⚠️ llmtxt rate limit exhausted; resets at {reset}.", file=sys.stderr)
|
|
149
|
-
_save_cookies(opener)
|
|
150
|
-
return body
|
|
151
|
-
except urllib.error.HTTPError as e:
|
|
152
|
-
# Even on error, the server may have set an anonymous session cookie.
|
|
153
|
-
_save_cookies(opener)
|
|
154
|
-
if e.code == 404:
|
|
155
|
-
raise LookupError(f"Document not found or not accessible: {slug}") from e
|
|
156
|
-
if e.code == 429:
|
|
157
|
-
try:
|
|
158
|
-
payload = json.loads(e.read().decode("utf-8"))
|
|
159
|
-
retry = payload.get("retryAfter", e.headers.get("retry-after", "unknown"))
|
|
160
|
-
except Exception:
|
|
161
|
-
retry = e.headers.get("retry-after", "unknown")
|
|
162
|
-
raise RuntimeError(f"Rate limited by api.llmtxt.my; retry after {retry}s") from e
|
|
163
|
-
if e.code in (401, 403):
|
|
164
|
-
raise RuntimeError(
|
|
165
|
-
f"HTTP {e.code}: {e.reason}. "
|
|
166
|
-
"Document requires auth — set LLMTXT_API_KEY (Bearer llmtxt_<43-char-token>)."
|
|
167
|
-
) from e
|
|
168
|
-
raise RuntimeError(f"HTTP {e.code}: {e.reason}") from e
|
|
169
|
-
except urllib.error.URLError as e:
|
|
170
|
-
raise RuntimeError(f"Network error: {e.reason}") from e
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
def get_overview(slug: str, version: str | None = None, use_cache: bool = True) -> str:
|
|
174
|
-
"""Cache-aware read. Returns the overview body as markdown."""
|
|
175
|
-
path = cache_path(slug, version)
|
|
176
|
-
immutable = version is not None
|
|
177
|
-
if use_cache and cache_is_fresh(path, immutable):
|
|
178
|
-
return path.read_text()
|
|
179
|
-
body = fetch_overview(slug, version)
|
|
180
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
181
|
-
path.write_text(body)
|
|
182
|
-
return body
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
def format_for_evidence_pack(slug: str, version: str | None, body: str) -> str:
|
|
186
|
-
"""Wrap the overview body with an evidence-pack-friendly header."""
|
|
187
|
-
ref = f"llmtxt:{slug}" + (f"@{version}" if version else "")
|
|
188
|
-
return f"<!-- evidence-pack item: `{ref}` -->\n\n{body.rstrip()}\n"
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
def main():
|
|
192
|
-
parser = argparse.ArgumentParser(
|
|
193
|
-
description="Fetch an llmtxt overview for a Council evidence pack.",
|
|
194
|
-
)
|
|
195
|
-
parser.add_argument("ref", help="Document reference: <slug> or <slug>@<version>")
|
|
196
|
-
parser.add_argument("--no-cache", action="store_true", help="Bypass the local cache.")
|
|
197
|
-
parser.add_argument("--json", action="store_true", help="Emit the raw API response.")
|
|
198
|
-
parser.add_argument("--raw", action="store_true", help="Suppress the evidence-pack header.")
|
|
199
|
-
args = parser.parse_args()
|
|
200
|
-
|
|
201
|
-
try:
|
|
202
|
-
slug, version = parse_ref(args.ref)
|
|
203
|
-
except ValueError as e:
|
|
204
|
-
print(f"❌ {e}", file=sys.stderr)
|
|
205
|
-
sys.exit(3)
|
|
206
|
-
|
|
207
|
-
try:
|
|
208
|
-
body = get_overview(slug, version, use_cache=not args.no_cache)
|
|
209
|
-
except LookupError as e:
|
|
210
|
-
print(f"❌ {e}", file=sys.stderr)
|
|
211
|
-
sys.exit(2)
|
|
212
|
-
except RuntimeError as e:
|
|
213
|
-
print(f"❌ {e}", file=sys.stderr)
|
|
214
|
-
sys.exit(1)
|
|
215
|
-
|
|
216
|
-
if args.json or args.raw:
|
|
217
|
-
print(body)
|
|
218
|
-
else:
|
|
219
|
-
print(format_for_evidence_pack(slug, version, body))
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if __name__ == "__main__":
|
|
223
|
-
main()
|