@cleocode/skills 2026.5.3 → 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.
Files changed (29) hide show
  1. package/package.json +1 -1
  2. package/skills/ct-council/SKILL.md +0 -377
  3. package/skills/ct-council/optimization/HARDENING-PLAYBOOK.md +0 -107
  4. package/skills/ct-council/optimization/README.md +0 -74
  5. package/skills/ct-council/optimization/scenarios.yaml +0 -121
  6. package/skills/ct-council/optimization/scripts/campaign.py +0 -543
  7. package/skills/ct-council/optimization/scripts/test_campaign.py +0 -143
  8. package/skills/ct-council/references/chairman.md +0 -119
  9. package/skills/ct-council/references/contrarian.md +0 -70
  10. package/skills/ct-council/references/evidence-pack.md +0 -145
  11. package/skills/ct-council/references/examples.md +0 -235
  12. package/skills/ct-council/references/executor.md +0 -83
  13. package/skills/ct-council/references/expansionist.md +0 -68
  14. package/skills/ct-council/references/first-principles.md +0 -73
  15. package/skills/ct-council/references/outsider.md +0 -73
  16. package/skills/ct-council/references/peer-review.md +0 -125
  17. package/skills/ct-council/scripts/analyze_runs.py +0 -293
  18. package/skills/ct-council/scripts/fixtures/executor_multi.md +0 -198
  19. package/skills/ct-council/scripts/fixtures/missing_advisor.md +0 -117
  20. package/skills/ct-council/scripts/fixtures/missing_convergence.md +0 -190
  21. package/skills/ct-council/scripts/fixtures/thin_evidence.md +0 -193
  22. package/skills/ct-council/scripts/fixtures/valid.md +0 -226
  23. package/skills/ct-council/scripts/fixtures/valid_with_llmtxt.md +0 -226
  24. package/skills/ct-council/scripts/llmtxt_ref.py +0 -223
  25. package/skills/ct-council/scripts/run_council.py +0 -578
  26. package/skills/ct-council/scripts/telemetry.py +0 -624
  27. package/skills/ct-council/scripts/test_telemetry.py +0 -509
  28. package/skills/ct-council/scripts/test_validate.py +0 -452
  29. package/skills/ct-council/scripts/validate.py +0 -396
@@ -1,396 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- validate.py — structural validator for Council run outputs.
4
-
5
- Usage:
6
- python3 validate.py <path-to-output.md>
7
- python3 validate.py --json <path> # emit JSON report on stdout
8
- python3 validate.py --strict <path> # treat warnings as failures
9
-
10
- Exit codes:
11
- 0 — valid
12
- 1 — structural violations (fatal)
13
- 2 — semantic warnings (convergence, weak grounding) with --strict
14
- """
15
-
16
- from __future__ import annotations
17
-
18
- import argparse
19
- import json
20
- import re
21
- import sys
22
- from dataclasses import dataclass, asdict
23
- from pathlib import Path
24
-
25
- ADVISORS = ["Contrarian", "First Principles", "Expansionist", "Outsider", "Executor"]
26
-
27
- PEER_REVIEW_ROTATION = [
28
- ("Contrarian", "First Principles"),
29
- ("First Principles", "Expansionist"),
30
- ("Expansionist", "Outsider"),
31
- ("Outsider", "Executor"),
32
- ("Executor", "Contrarian"),
33
- ]
34
-
35
- ADVISOR_REQUIRED_MARKERS = [
36
- "**Frame:**",
37
- "**Evidence anchored:**",
38
- "**Verdict from this lens:**",
39
- "**Single sharpest point:**",
40
- ]
41
-
42
- EXECUTOR_EXTRA_MARKERS = [
43
- "**The action (one):**",
44
- "**Expected outcome",
45
- "**What this unblocks:**",
46
- ]
47
-
48
- PEER_REVIEW_GATES = ["G1 Rigor", "G2 Evidence grounding", "G3 Frame integrity", "G4 Actionability"]
49
-
50
- PEER_REVIEW_REQUIRED_MARKERS = [
51
- "**Gate results:**",
52
- "**Strongest finding",
53
- "**Gap from",
54
- "**What I would add:**",
55
- "**Disposition:**",
56
- ]
57
-
58
- CHAIRMAN_REQUIRED_SUBSECTIONS = [
59
- "### Gate summary",
60
- "### Recommendation",
61
- "### Why this, not the alternatives",
62
- "### What each advisor got right",
63
- "### Conditions on the recommendation",
64
- "### Next 60-minute action",
65
- "### Confidence",
66
- ]
67
-
68
-
69
- @dataclass
70
- class Violation:
71
- kind: str # "structural" | "semantic" | "warning"
72
- section: str
73
- message: str
74
-
75
-
76
- class Validator:
77
- def __init__(self, md: str):
78
- self.md = md
79
- self.violations: list[Violation] = []
80
-
81
- # ─── helpers ────────────────────────────────────────────────────────────
82
-
83
- def _section_body(self, header_regex: str) -> str | None:
84
- """Return the body text under the first header matching header_regex, up to the next same-or-higher level header.
85
-
86
- Line-based scan that correctly ignores headers inside ``` code fences.
87
- """
88
- header_re = re.compile(r"^(#{1,6})\s+(.+?)\s*$")
89
- lines = self.md.split("\n")
90
- in_fence = False
91
- start_line = None # first line of body (after matching header)
92
- start_level = None
93
- end_line = len(lines) # body end (exclusive)
94
-
95
- for i, line in enumerate(lines):
96
- if line.lstrip().startswith("```"):
97
- in_fence = not in_fence
98
- continue
99
- if in_fence:
100
- continue
101
- m = header_re.match(line)
102
- if not m:
103
- continue
104
- level = len(m.group(1))
105
- title = m.group(2).strip()
106
- if start_line is None:
107
- if re.match(header_regex, title):
108
- start_line = i + 1
109
- start_level = level
110
- else:
111
- if level <= start_level:
112
- end_line = i
113
- break
114
-
115
- if start_line is None:
116
- return None
117
- return "\n".join(lines[start_line:end_line])
118
-
119
- def _fail(self, section: str, message: str, kind: str = "structural"):
120
- self.violations.append(Violation(kind=kind, section=section, message=message))
121
-
122
- # ─── checks ─────────────────────────────────────────────────────────────
123
-
124
- def check_top_header(self):
125
- m = re.search(r"^#\s+The Council\s+—\s+(.+)$", self.md, re.MULTILINE)
126
- if not m:
127
- self._fail("H1", "Missing H1: expected '# The Council — <one-line question>'.")
128
- return
129
- question = m.group(1).strip()
130
- if len(question) < 10:
131
- self._fail("H1", f"Restated question too short: {question!r}. Expected a full one-sentence decision question.")
132
-
133
- def check_evidence_pack(self):
134
- body = self._section_body(r"^Evidence pack$")
135
- if body is None:
136
- self._fail("Evidence pack", "Missing '## Evidence pack' section.")
137
- return
138
- items = re.findall(r"^\s*\d+\.\s+(.+?)(?=^\s*\d+\.\s+|\Z)", body, re.MULTILINE | re.DOTALL)
139
- count = len(items)
140
- if count < 3:
141
- self._fail("Evidence pack", f"Evidence pack has {count} items; minimum is 3.")
142
- if count > 7:
143
- self._fail("Evidence pack", f"Evidence pack has {count} items; maximum is 7.", kind="warning")
144
- for i, item in enumerate(items, 1):
145
- item_text = item.strip()
146
- # Each item must contain a rationale separator (—, --, or ":").
147
- has_rationale = ("—" in item_text) or (" -- " in item_text) or (": " in item_text and "`" in item_text)
148
- if not has_rationale:
149
- self._fail("Evidence pack", f"Item {i} appears to lack a rationale (expected 'citation — why this matters').")
150
- # Each item should have a citation-like token: backticks, file:line, sha, or URL.
151
- has_citation = bool(re.search(r"`[^`]+`|\b[0-9a-f]{7,40}\b|https?://|\.(ts|py|md|rs|tsx|js|sql)\b", item_text))
152
- if not has_citation:
153
- self._fail("Evidence pack", f"Item {i} appears to lack a citation (expected `path:line` | `symbol` | sha | URL).", kind="warning")
154
-
155
- def check_advisor_sections(self):
156
- for advisor in ADVISORS:
157
- body = self._section_body(rf"^Advisor:\s+{re.escape(advisor)}$")
158
- if body is None:
159
- self._fail(f"Advisor: {advisor}", f"Missing '### Advisor: {advisor}' section.")
160
- continue
161
- for marker in ADVISOR_REQUIRED_MARKERS:
162
- if marker not in body:
163
- self._fail(f"Advisor: {advisor}", f"Missing required marker: {marker}")
164
- # Evidence anchored must have ≥2 bullet items.
165
- ea_match = re.search(r"\*\*Evidence anchored:\*\*(.*?)(?=\n\*\*|\Z)", body, re.DOTALL)
166
- if ea_match:
167
- bullets = re.findall(r"^-\s+.+", ea_match.group(1), re.MULTILINE)
168
- if len(bullets) < 2:
169
- self._fail(f"Advisor: {advisor}", f"Evidence anchored has {len(bullets)} items; minimum is 2.")
170
- # Executor-specific markers.
171
- if advisor == "Executor":
172
- for marker in EXECUTOR_EXTRA_MARKERS:
173
- if marker not in body:
174
- self._fail("Advisor: Executor", f"Missing required marker: {marker}")
175
- self.check_executor_single_action(body)
176
-
177
- def check_executor_single_action(self, body: str):
178
- """The action must be exactly one paragraph, not a numbered or bulleted list."""
179
- m = re.search(r"\*\*The action \(one\):\*\*(.+?)(?=\n\*\*|\Z)", body, re.DOTALL)
180
- if not m:
181
- return
182
- action_body = m.group(1).strip()
183
- numbered = re.findall(r"^\s*\d+\.\s+", action_body, re.MULTILINE)
184
- bulleted = re.findall(r"^\s*[-*]\s+", action_body, re.MULTILINE)
185
- if len(numbered) > 1:
186
- self._fail("Advisor: Executor",
187
- f"'The action (one)' contains {len(numbered)} numbered items; exactly one action required.")
188
- if len(bulleted) > 1:
189
- self._fail("Advisor: Executor",
190
- f"'The action (one)' contains {len(bulleted)} bulleted items; exactly one action required.")
191
-
192
- def check_peer_reviews(self):
193
- for reviewer, reviewee in PEER_REVIEW_ROTATION:
194
- header_re = rf"^{re.escape(reviewer)} reviewing {re.escape(reviewee)}$"
195
- body = self._section_body(header_re)
196
- if body is None:
197
- self._fail("Peer review",
198
- f"Missing peer review section: '### {reviewer} reviewing {reviewee}'.")
199
- continue
200
- for marker in PEER_REVIEW_REQUIRED_MARKERS:
201
- if marker not in body:
202
- self._fail(f"{reviewer} reviewing {reviewee}",
203
- f"Missing required marker: {marker}")
204
- # Gate lines must each appear with PASS or FAIL.
205
- for gate in PEER_REVIEW_GATES:
206
- gate_re = rf"-\s+{re.escape(gate)}:\s+(PASS|FAIL)\s+—\s+.+"
207
- if not re.search(gate_re, body):
208
- self._fail(f"{reviewer} reviewing {reviewee}",
209
- f"Gate '{gate}' missing or malformed. Expected: '- {gate}: PASS|FAIL — <evidence>'.")
210
- # Disposition must be one of Accept / Modify / Reject.
211
- disp_match = re.search(r"\*\*Disposition:\*\*\s+(Accept|Modify|Reject)\b", body)
212
- if not disp_match:
213
- self._fail(f"{reviewer} reviewing {reviewee}",
214
- "Disposition must be one of: Accept | Modify | Reject.")
215
-
216
- def check_convergence_section(self):
217
- body = self._section_body(r"^Phase 2\.5\s*[—-]\s*Convergence check$")
218
- if body is None:
219
- self._fail("Phase 2.5", "Missing '## Phase 2.5 — Convergence check' section.")
220
-
221
- def check_chairman_verdict(self):
222
- body = self._section_body(r"^Phase 3\s*[—-]\s*Chairman['’]s verdict$")
223
- if body is None:
224
- self._fail("Phase 3", "Missing '## Phase 3 — Chairman's verdict' section.")
225
- return
226
- for marker in CHAIRMAN_REQUIRED_SUBSECTIONS:
227
- if marker not in body:
228
- self._fail("Phase 3", f"Missing required subsection: {marker}")
229
- # Gate summary table must reference all five advisors.
230
- for advisor in ADVISORS:
231
- if advisor not in body:
232
- self._fail("Phase 3", f"Gate summary table missing row for: {advisor}")
233
- # Next 60-minute action must have non-empty body.
234
- action_match = re.search(r"###\s+Next 60-minute action\s*\n(.+?)(?=\n###|\Z)", body, re.DOTALL)
235
- if action_match and len(action_match.group(1).strip()) < 15:
236
- self._fail("Phase 3", "Next 60-minute action is empty or too short to be actionable.")
237
- # Confidence must be low/medium/high.
238
- conf_match = re.search(r"###\s+Confidence\s*\n(.+?)(?=\n###|\Z)", body, re.DOTALL)
239
- if conf_match:
240
- conf_text = conf_match.group(1).strip().lower()
241
- if not any(level in conf_text for level in ["low", "medium", "high"]):
242
- self._fail("Phase 3", "Confidence must be one of: low | medium | high.")
243
-
244
- # ─── entry ──────────────────────────────────────────────────────────────
245
-
246
- def validate(self, phase: int | None = None) -> list[Violation]:
247
- """Run structural checks. If `phase` is given, only check sections
248
- that should exist by the end of that phase:
249
- phase=0 → H1 + evidence pack only
250
- phase=1 → +5 advisor sections
251
- phase=2 → +5 peer reviews
252
- phase=3 (or None) → +convergence + chairman (full output)
253
- """
254
- if phase is None:
255
- phase = 3
256
- self.check_top_header()
257
- self.check_evidence_pack()
258
- if phase >= 1:
259
- self.check_advisor_sections()
260
- if phase >= 2:
261
- self.check_peer_reviews()
262
- if phase >= 3:
263
- self.check_convergence_section()
264
- self.check_chairman_verdict()
265
- return self.violations
266
-
267
-
268
- def detect_phase(md: str) -> int:
269
- """Best-effort detection of which phase a partial file represents.
270
-
271
- Returns 0..3 based on which sections are present:
272
- - Phase 3 header → 3 (assume full output)
273
- - Any peer-review section header → 2
274
- - Any '### Advisor:' header → 1
275
- - Otherwise → 0 (just evidence pack)
276
-
277
- Authors typically build the file phase-by-phase; this lets the validator
278
- match the author's actual progress instead of failing on missing sections
279
- that legitimately don't exist yet.
280
- """
281
- # Use the same fence-aware scan logic as Validator._section_body would.
282
- header_re = re.compile(r"^(#{1,6})\s+(.+?)\s*$")
283
- in_fence = False
284
- has_phase3 = False
285
- has_phase2 = False
286
- has_phase1 = False
287
- for line in md.split("\n"):
288
- if line.lstrip().startswith("```"):
289
- in_fence = not in_fence
290
- continue
291
- if in_fence:
292
- continue
293
- m = header_re.match(line)
294
- if not m:
295
- continue
296
- title = m.group(2).strip()
297
- if re.match(r"^Phase 3\b", title):
298
- has_phase3 = True
299
- elif "reviewing" in title and any(a in title for a in ADVISORS):
300
- has_phase2 = True
301
- elif title.startswith("Advisor:"):
302
- has_phase1 = True
303
- if has_phase3:
304
- return 3
305
- if has_phase2:
306
- return 2
307
- if has_phase1:
308
- return 1
309
- return 0
310
-
311
-
312
- def report(violations: list[Violation], as_json: bool = False) -> str:
313
- if as_json:
314
- return json.dumps({
315
- "valid": not any(v.kind == "structural" for v in violations),
316
- "violations": [asdict(v) for v in violations],
317
- }, indent=2)
318
- if not violations:
319
- return "✅ Council output is structurally valid."
320
- lines = [f"❌ {len(violations)} violation(s) found:"]
321
- for v in violations:
322
- prefix = {"structural": "FAIL", "semantic": "SEMA", "warning": "WARN"}.get(v.kind, "????")
323
- lines.append(f" [{prefix}] {v.section}: {v.message}")
324
- return "\n".join(lines)
325
-
326
-
327
- def main():
328
- parser = argparse.ArgumentParser(
329
- description="Validate a Council run output (full or partial).",
330
- epilog=(
331
- "Use --phase N to validate a partial file that hasn't reached Phase 3 yet:\n"
332
- " --phase 0 → only H1 + evidence pack (use after writing phase0.md)\n"
333
- " --phase 1 → also 5 advisor sections\n"
334
- " --phase 2 → also 5 peer reviews\n"
335
- " (no flag) → full output (default; auto-detects partial files and suggests --phase)"
336
- ),
337
- formatter_class=argparse.RawDescriptionHelpFormatter,
338
- )
339
- parser.add_argument("path", help="Path to the markdown to validate.")
340
- parser.add_argument("--json", action="store_true", help="Emit JSON report on stdout.")
341
- parser.add_argument("--strict", action="store_true", help="Fail on warnings, not just structural errors.")
342
- parser.add_argument(
343
- "--phase",
344
- type=int,
345
- choices=[0, 1, 2, 3],
346
- default=None,
347
- help="Partial-validation mode. Without this flag, validates the full output (Phase 3).",
348
- )
349
- parser.add_argument(
350
- "--auto-detect",
351
- action="store_true",
352
- help="Auto-detect phase from file contents and validate up to that phase. Useful for in-progress files.",
353
- )
354
- args = parser.parse_args()
355
-
356
- path = Path(args.path)
357
- if not path.exists():
358
- print(f"❌ File not found: {path}", file=sys.stderr)
359
- sys.exit(1)
360
-
361
- md = path.read_text()
362
- requested_phase = args.phase
363
-
364
- # If user passed nothing AND the file has no Phase 3 header, surface a
365
- # helpful message instead of dumping a wall of "missing section" errors.
366
- if requested_phase is None and not args.auto_detect:
367
- detected = detect_phase(md)
368
- if detected < 3:
369
- phase_names = {0: "evidence pack only", 1: "through Phase 1 (advisors)", 2: "through Phase 2 (peer reviews)"}
370
- print(
371
- f"⚠️ Detected partial file (highest section present: phase {detected} — "
372
- f"{phase_names.get(detected, 'unknown')}).\n"
373
- f" Validating with --phase {detected} so missing downstream sections don't drown out real issues.\n"
374
- f" Pass --phase 3 (or wait until output.md is fully assembled) to enforce full structure.\n",
375
- file=sys.stderr,
376
- )
377
- requested_phase = detected
378
-
379
- if args.auto_detect:
380
- requested_phase = detect_phase(md)
381
-
382
- v = Validator(md)
383
- violations = v.validate(phase=requested_phase)
384
- print(report(violations, as_json=args.json))
385
-
386
- structural = [x for x in violations if x.kind == "structural"]
387
- warnings = [x for x in violations if x.kind == "warning"]
388
- if structural:
389
- sys.exit(1)
390
- if args.strict and warnings:
391
- sys.exit(2)
392
- sys.exit(0)
393
-
394
-
395
- if __name__ == "__main__":
396
- main()