@cleocode/skills 2026.10.12 → 2026.10.13

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cleocode/skills",
3
- "version": "2026.10.12",
3
+ "version": "2026.10.13",
4
4
  "description": "CLEO skill definitions - bundled with CLEO monorepo",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -2,14 +2,14 @@
2
2
  name: ct-cleo
3
3
  description: CLEO task management protocol - session, task, and workflow guidance. Use when managing tasks, sessions, or multi-agent workflows with the CLEO CLI protocol.
4
4
  metadata:
5
- version: 2.24.11
5
+ version: 2.24.13
6
6
  tier: core
7
7
  install: harness
8
8
  covers:
9
9
  - packages/cleo/src/cli/commands/session.ts
10
10
  - packages/cleo/src/cli/commands/focus.ts
11
11
  - packages/cleo/src/cli/commands/sticky.ts
12
- lastReviewed: 2026-10-10
12
+ lastReviewed: 2026-10-13
13
13
  stability: stable
14
14
  ---
15
15
 
@@ -463,4 +463,4 @@ BRAIN ID going forward.
463
463
 
464
464
  ## Evidence must prove task criteria
465
465
 
466
- Merged PRs and passing CI are provenance. Implementation requires changed artifacts related to the task; testing and review require their own actual results (`ci:<pr>` once the PR merged and the project sets `evidence.ciSatisfies`; else `tool:test-affected` when `testing.affectedCommand` is configured, otherwise a targeted `test-run:<json>` or `tool:test`; plan with `cleo done <id> --plan`, never run the suite by hand and again via `tool:test`). Run tests, typechecks and builds one at a time through `cleo run --wait --class <test|build|full-build> -- <cmd>` (exit 75: not admitted yet, wait and retry); never wrap `cleo run` or `cleo verify` in another queue or wrapper, and never raise heap or worker counts (`NODE_OPTIONS`, `--maxWorkers`). For tasks with canonical criteria, append explicit links such as `satisfies:T1234#AC1` to each relevant gate's evidence. Fetch the PR merge commit so artifact hashes can be inspected. A changed criterion invalidates its recorded proof. Completing a child preserves an open parent whose own criteria remain unproven; child waivers never transfer to parent criteria.
466
+ Merged PRs and passing CI are provenance. Implementation requires changed artifacts related to the task; testing and review require their own actual results (`ci:<pr>` once the PR merged and the project sets `evidence.ciSatisfies`; else `tool:test-affected` when `testing.affectedCommand` is configured, otherwise a targeted `test-run:<json>` or `tool:test`; after merge a scoped run carries only while the change's files are byte-identical, or when it is a `test-run:` recorded on a main HEAD descending from the merge, on the current tree, covering the change (T13673); plan with `cleo done <id> --plan`, which never plans a whole-suite `tool:test` itself (T13647); never run the suite by hand and again via `tool:test`). Run tests, typechecks and builds one at a time through `cleo run --wait --class <test|build|full-build> -- <cmd>` (exit 75: not admitted yet, wait and retry); never wrap `cleo run` or `cleo verify` in another queue or wrapper, and never raise heap or worker counts (`NODE_OPTIONS`, `--maxWorkers`). For tasks with canonical criteria, append explicit links such as `satisfies:T1234#AC1` to each relevant gate's evidence. Fetch the PR merge commit so artifact hashes can be inspected. A changed criterion invalidates its recorded proof. Completing a child preserves an open parent whose own criteria remain unproven; child waivers never transfer to parent criteria.
@@ -2,7 +2,7 @@
2
2
  name: ct-council
3
3
  description: Convene "The Council" — a 5-advisor, shuffled gate-based peer-review, chairman-synthesis workflow for reviewing a plan, decision, architecture, or piece of work inside the current project. Use when the user says "convene the council" (or "counsel"), "get the council on this", "council review", "run the five advisors", "stress-test this", "get multiple perspectives", or asks for a rigorous multi-angle challenge of a proposal (Contrarian, First Principles, Expansionist, Outsider, Executor → shuffled peer review with pass/fail gates → convergence detector → Chairman verdict). Operates on the current codebase — each advisor grounds their analysis in actual files/commits before opining. Output is validated by scripts/validate.py.
4
4
  metadata:
5
- version: 1.0.0
5
+ version: 1.1.1
6
6
  tier: on-demand
7
7
  install: harness
8
8
  stability: stable
@@ -67,6 +67,7 @@ Every run lives under a run directory (`<run-dir>/`, created by `scripts/run_cou
67
67
  | `<run-dir>/phase3.md` | **Orchestrator** (or Chairman agent if delegated) | Phase 3 |
68
68
  | `<run-dir>/output.md` | **Orchestrator** (assembled from above) | After Phase 3 |
69
69
  | `<run-dir>/verdict.md`, `tldr.md` | **Auto-generated** by `run_council.py ingest` | After validate |
70
+ | CleoDocs store doc (slug `council-run-<run_id>`) | **Auto-written** by `run_council.py ingest` via `cleo docs add`/`cleo docs update` | After validate (T13363 AC2) |
70
71
 
71
72
  The agent file-write contract: each Phase-1 / Phase-2 subagent must use the `Write` tool to save its full output markdown to the path above and return ONLY a one-line confirmation. The orchestrator does NOT include the agent's full output in its return-context — that bloats the orchestrator window unnecessarily.
72
73
 
@@ -295,7 +296,7 @@ The `--title` flag is optional; if omitted the title is auto-derived from the qu
295
296
 
296
297
  ### Validation + auto-generation
297
298
 
298
- `scripts/run_council.py ingest <run-dir>` validates `output.md`, then automatically writes `verdict.md` and `tldr.md` to the same run dir, then appends a telemetry record to `.cleo/council-runs.jsonl`. Direct usage:
299
+ `scripts/run_council.py ingest <run-dir>` validates `output.md`, then automatically writes `verdict.md` and `tldr.md` to the same run dir, then appends a telemetry record to `.cleo/council-runs.jsonl`, then pushes the assembled `output.md` into the CleoDocs store as a sectioned `note` doc. Direct usage:
299
300
 
300
301
  ```bash
301
302
  # Full output (default)
@@ -306,9 +307,30 @@ python3 scripts/validate.py --phase 0 <phase0.md> # only H1 + evidence pack
306
307
  python3 scripts/validate.py --phase 1 <file.md> # +5 advisor sections
307
308
  python3 scripts/validate.py --phase 2 <file.md> # +5 peer reviews
308
309
 
309
- python3 scripts/run_council.py ingest <run-dir> # validate + verdict.md + tldr.md + telemetry
310
+ python3 scripts/run_council.py ingest <run-dir> # validate + verdict.md + tldr.md + telemetry + store doc
310
311
  ```
311
312
 
313
+ ### Store write (T13363 AC2)
314
+
315
+ `ingest` also pushes the run's output document into the CleoDocs store so the per-phase
316
+ `##` sections land as CRDT-merged sections instead of only loose files:
317
+
318
+ - **Slug** — `council-run-<run_id>` (stable per run; re-ingesting the same run updates
319
+ the same doc instead of creating a new one). The slug is stamped into the INDEX entry
320
+ as `docs_slug` only when the store write succeeded.
321
+ - **Kind** — `note`; owner is `--owner <id>` if passed, else the `CLEO_SESSION_ID`
322
+ environment variable, else the session the CLI binds to (`cleo session status`; under
323
+ Claude Code only `CLAUDE_CODE_SESSION_ID` is set and the CLI resolves it). The markdown
324
+ is piped via `--content -` (stdin), never argv.
325
+ - **Ordered, non-fatal + additive** — the store write runs after the telemetry record and
326
+ the INDEX entry are written; those and the raw run dir remain the source of truth. Every
327
+ `cleo` call has a 60 s timeout. If it fails, times out, or no owner is resolvable,
328
+ ingest logs a warning and continues. `--no-store` skips the write.
329
+
330
+ Retrieve a run's doc with `cleo docs fetch council-run-<run_id>`; each re-ingest that
331
+ changes the bytes bumps the doc's version (see the `docVersion` field in the update
332
+ envelope).
333
+
312
334
  The validator **auto-detects partial files** when no `--phase` is passed: if no Phase 3 header is present, it validates up to the highest phase the file contains and prints a stderr suggestion. This prevents the "12 missing-section errors" wall of red when you're checking a phase0.md or phase1-*.md mid-flight.
313
335
 
314
336
  Exit code 0 = structurally valid. Non-zero = fix the violations before surfacing the verdict.
@@ -13,14 +13,22 @@ It does NOT spawn agents itself (the orchestrator does that — see SKILL.md
13
13
  3. Validator + telemetry hooks: a validated `output.md` is automatically
14
14
  appended to .cleo/council-runs.jsonl with optional --tokens / --wall-clock
15
15
  stamps.
16
- 4. A `--scenario` flag that names the shakedown (1..8) for telemetry
16
+ 4. Store write (T13363 AC2): a validated `output.md` is ALSO pushed into the
17
+ CleoDocs store as a sectioned `note` doc (slug `council-run-<run_id>`)
18
+ via `cleo docs add` / `cleo docs update` (idempotent re-ingest updates
19
+ the same doc). The store write is ADDITIVE and NON-FATAL — the raw run
20
+ dir + JSONL telemetry remain the audit source of truth.
21
+ 5. A `--scenario` flag that names the shakedown (1..8) for telemetry
17
22
  filtering and exit-criteria reporting.
18
23
 
19
24
  Subcommands:
20
25
 
21
26
  init <question> create a new run directory + skeleton phase0 prompt
22
27
  validate <run-dir> run validate.py on the assembled output.md
23
- ingest <run-dir> validate + emit telemetry to the JSONL log
28
+ ingest <run-dir> validate + emit telemetry to the JSONL log AND push
29
+ output.md into the CleoDocs store (slug
30
+ council-run-<run_id>; --no-store to skip,
31
+ --owner to override the doc owner)
24
32
  list show all runs under the configured runs dir
25
33
 
26
34
  Convention: assemble phase outputs into <run-dir>/output.md by hand or by
@@ -39,6 +47,7 @@ import argparse
39
47
  import datetime as _dt
40
48
  import hashlib
41
49
  import json
50
+ import os
42
51
  import re
43
52
  import subprocess
44
53
  import sys
@@ -234,6 +243,136 @@ def cmd_validate(args) -> int:
234
243
  return code
235
244
 
236
245
 
246
+ # ─── CleoDocs store write (T13363 AC2) ──────────────────────────────────────
247
+ #
248
+ # The run's output document (assembled output.md — one `##` phase per agent)
249
+ # is pushed into the CleoDocs store so concurrent multi-agent edits land as
250
+ # CRDT-merged sections instead of loose files. Raw run artifacts under
251
+ # .cleo/council-runs/ stay the telemetry source of truth; the store write is
252
+ # additive and non-fatal (a failure warns and ingest continues).
253
+
254
+ def docs_slug_for_run(run_id: str) -> str:
255
+ """Stable per-run store slug: `council-run-<run_id>` (kebab-case, ≤80 chars)."""
256
+ return f"council-run-{run_id}"
257
+
258
+
259
+ # Bound on every `cleo` subprocess: a hung CLI must never block ingest.
260
+ CLEO_TIMEOUT_SEC = 60
261
+
262
+
263
+ def _bound_cleo_session() -> str | None:
264
+ """The CLEO session the CLI itself binds to (CLAUDE_CODE_SESSION_ID, the
265
+ terminal, …), read from `cleo session status`. None on any failure."""
266
+ try:
267
+ r = subprocess.run(
268
+ ["cleo", "session", "status", "--field", "/data/session/session/id", "--quiet"],
269
+ capture_output=True, text=True, timeout=CLEO_TIMEOUT_SEC,
270
+ )
271
+ except (OSError, subprocess.TimeoutExpired):
272
+ return None
273
+ sid = (r.stdout or "").strip()
274
+ return sid if r.returncode == 0 and sid.startswith("ses_") else None
275
+
276
+
277
+ def resolve_docs_owner(args) -> str | None:
278
+ """Owner for the store doc: --owner flag, else CLEO_SESSION_ID, else the
279
+ session the CLI binds to (`cleo session status`; under Claude Code only
280
+ CLAUDE_CODE_SESSION_ID is set, and the CLI resolves it).
281
+
282
+ Returns None when no owner can be determined — the caller then skips the
283
+ store write (non-fatal).
284
+ """
285
+ owner = getattr(args, "owner", None)
286
+ if owner:
287
+ return owner
288
+ return os.environ.get("CLEO_SESSION_ID") or _bound_cleo_session()
289
+
290
+
291
+ def _docs_subprocess_env(owner: str) -> dict:
292
+ env = dict(os.environ)
293
+ # Attribute the write to the owning session when the owner IS a session.
294
+ if owner.startswith("ses_"):
295
+ env["CLEO_SESSION_ID"] = owner
296
+ return env
297
+
298
+
299
+ def _run_cleo_docs(cmd: list, *, content: str, env: dict) -> subprocess.CompletedProcess:
300
+ # Content flows via stdin (`--content -`): argv never carries the markdown,
301
+ # so verdicts of any size are safe from shell-quoting limits.
302
+ return subprocess.run(
303
+ cmd, input=content, capture_output=True, text=True, env=env,
304
+ timeout=CLEO_TIMEOUT_SEC,
305
+ )
306
+
307
+
308
+ def push_run_to_docs_store(
309
+ *,
310
+ run_dir: Path,
311
+ meta: dict,
312
+ content: str,
313
+ owner: str,
314
+ ) -> bool:
315
+ """Push output.md into the CleoDocs store as a sectioned `note` doc.
316
+
317
+ Idempotent: `cleo docs add` first; on E_SLUG_RESERVED (re-ingest of a
318
+ known run) falls back to `cleo docs update`, which noops when the bytes
319
+ are unchanged. Returns True when the doc is in the store; on any failure
320
+ prints a warning and returns False — NEVER raises into ingest.
321
+ """
322
+ rid = meta.get("run_id") or run_dir.name
323
+ slug = docs_slug_for_run(rid)
324
+ title = (meta.get("title") or f"Council run {rid}")[:DEFAULT_TITLE_MAX_LEN]
325
+ question = meta.get("question") or ""
326
+ desc = f"Council run {rid} — {question} (audit trail: {run_dir})".strip()
327
+ env = _docs_subprocess_env(owner)
328
+
329
+ add_cmd = [
330
+ "cleo", "docs", "add", owner,
331
+ "--content", "-",
332
+ "--type", "note",
333
+ "--slug", slug,
334
+ f"--title={title}", # one token: a title starting with "-" is never a flag
335
+ "--desc", desc,
336
+ "--attached-by", "council",
337
+ "--json",
338
+ ]
339
+ try:
340
+ result = _run_cleo_docs(add_cmd, content=content, env=env)
341
+ except (OSError, subprocess.TimeoutExpired) as e:
342
+ print(f"⚠️ Store write skipped — could not run `cleo docs add`: {e}", file=sys.stderr)
343
+ return False
344
+
345
+ if result.returncode == 0:
346
+ print(f"📦 Verdict doc written to store: slug '{slug}' (owner {owner})", file=sys.stderr)
347
+ return True
348
+
349
+ combined = (result.stdout or "") + (result.stderr or "")
350
+ if "E_SLUG_RESERVED" not in combined:
351
+ detail = combined.strip().splitlines()[0] if combined.strip() else f"exit {result.returncode}"
352
+ print(f"⚠️ Store write failed (`cleo docs add` {slug}): {detail}", file=sys.stderr)
353
+ return False
354
+
355
+ # Re-ingest of a known run: update the existing doc instead.
356
+ update_cmd = [
357
+ "cleo", "docs", "update", slug,
358
+ "--content", "-",
359
+ "--message", f"council re-ingest {rid}",
360
+ "--json",
361
+ ]
362
+ try:
363
+ result = _run_cleo_docs(update_cmd, content=content, env=env)
364
+ except (OSError, subprocess.TimeoutExpired) as e:
365
+ print(f"⚠️ Store update skipped — could not run `cleo docs update`: {e}", file=sys.stderr)
366
+ return False
367
+ if result.returncode == 0:
368
+ print(f"📦 Verdict doc updated in store: slug '{slug}'", file=sys.stderr)
369
+ return True
370
+ combined = (result.stdout or "") + (result.stderr or "")
371
+ detail = combined.strip().splitlines()[0] if combined.strip() else f"exit {result.returncode}"
372
+ print(f"⚠️ Store update failed (`cleo docs update` {slug}): {detail}", file=sys.stderr)
373
+ return False
374
+
375
+
237
376
  def cmd_ingest(args) -> int:
238
377
  run_dir = Path(args.run_dir)
239
378
  output_path = run_dir / "output.md"
@@ -331,6 +470,20 @@ def cmd_ingest(args) -> int:
331
470
  "wall_clock_seconds": args.wall_clock,
332
471
  })
333
472
  print(f"📋 INDEX.jsonl updated at {_index_path(runs_dir)}", file=sys.stderr)
473
+
474
+ # T13363 AC2 — push the run's output document into the CleoDocs store, AFTER
475
+ # the telemetry record and the INDEX entry exist (the audit source of truth).
476
+ # Additive + non-fatal; docs_slug is stamped only when the write succeeded.
477
+ if not getattr(args, "no_store", False):
478
+ owner = resolve_docs_owner(args)
479
+ if owner is None:
480
+ print(
481
+ "⚠️ Store write skipped — no owner (pass --owner <id>, set CLEO_SESSION_ID, "
482
+ "or start a cleo session)",
483
+ file=sys.stderr,
484
+ )
485
+ elif push_run_to_docs_store(run_dir=run_dir, meta=meta, content=md, owner=owner) and meta.get("run_id"):
486
+ _upsert_index(run_dir.parent, meta["run_id"], {"docs_slug": docs_slug_for_run(meta["run_id"])})
334
487
  return 0
335
488
 
336
489
 
@@ -550,6 +703,15 @@ def main():
550
703
  p_ing.add_argument("--log", default=str(DEFAULT_LOG_PATH))
551
704
  p_ing.add_argument("--tokens", type=int, default=None)
552
705
  p_ing.add_argument("--wall-clock", type=float, default=None)
706
+ p_ing.add_argument(
707
+ "--owner", default=None,
708
+ help="Owner id for the CleoDocs store doc (default: CLEO_SESSION_ID, else the "
709
+ "session `cleo session status` binds to).",
710
+ )
711
+ p_ing.add_argument(
712
+ "--no-store", action="store_true",
713
+ help="Skip the CleoDocs store write (T13363 AC2) — raw artifacts only.",
714
+ )
553
715
  p_ing.set_defaults(func=cmd_ingest)
554
716
 
555
717
  p_list = sub.add_parser("list", help="List runs from INDEX.jsonl.")
@@ -0,0 +1,235 @@
1
+ """Tests for the CleoDocs store write in run_council.py (T13363 AC2).
2
+
3
+ The store push is additive and non-fatal: these tests mock subprocess.run
4
+ (no real `cleo` binary or store is touched) and prove the invocation shape,
5
+ the idempotent add→update fallback, and that failures never raise into
6
+ ingest.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import argparse
12
+ import json
13
+ import shutil
14
+ import subprocess
15
+ import sys
16
+ import tempfile
17
+ import unittest
18
+ from pathlib import Path
19
+ from unittest import mock
20
+
21
+ SCRIPTS_DIR = Path(__file__).resolve().parent
22
+ sys.path.insert(0, str(SCRIPTS_DIR))
23
+ import run_council # noqa: E402
24
+
25
+ CONTENT = "# The Council — retry?\n\n## Phase 1 — advisors\n\n...\n"
26
+ META = {
27
+ "run_id": "abcdef12",
28
+ "title": "Retry on timeout",
29
+ "question": "Should we retry on timeout?",
30
+ }
31
+ RUN_DIR = Path(".cleo/council-runs/20261013T000000-abcdef12")
32
+
33
+
34
+ def _completed(cmd, returncode=0, stdout="", stderr=""):
35
+ return subprocess.CompletedProcess(cmd, returncode, stdout=stdout, stderr=stderr)
36
+
37
+
38
+ class TestDocsSlug(unittest.TestCase):
39
+ def test_slug_scheme(self):
40
+ self.assertEqual(run_council.docs_slug_for_run("abcdef12"), "council-run-abcdef12")
41
+
42
+
43
+ class TestResolveOwner(unittest.TestCase):
44
+ def test_flag_wins(self):
45
+ args = mock.Mock(owner="T1234", spec=["owner"])
46
+ self.assertEqual(run_council.resolve_docs_owner(args), "T1234")
47
+
48
+ def test_env_fallback(self):
49
+ args = mock.Mock(owner=None, spec=["owner"])
50
+ with mock.patch.dict("os.environ", {"CLEO_SESSION_ID": "ses_x"}, clear=True):
51
+ self.assertEqual(run_council.resolve_docs_owner(args), "ses_x")
52
+
53
+ def test_none_when_unset(self):
54
+ args = mock.Mock(owner=None, spec=["owner"])
55
+ with mock.patch.dict("os.environ", {}, clear=True), \
56
+ mock.patch.object(run_council.subprocess, "run", side_effect=OSError("no cleo")):
57
+ self.assertIsNone(run_council.resolve_docs_owner(args))
58
+
59
+ def test_claude_code_session_only_resolves_through_the_cli(self):
60
+ # T13658: under Claude Code only CLAUDE_CODE_SESSION_ID is set; the CLI
61
+ # binds it to a cleo session, which becomes the owner.
62
+ args = mock.Mock(owner=None, spec=["owner"])
63
+ with mock.patch.dict("os.environ", {"CLAUDE_CODE_SESSION_ID": "cc-123"}, clear=True), \
64
+ mock.patch.object(run_council.subprocess, "run") as m:
65
+ m.return_value = _completed([], returncode=0, stdout="ses_bound\n")
66
+ self.assertEqual(run_council.resolve_docs_owner(args), "ses_bound")
67
+ (cmd,), kwargs = m.call_args
68
+ self.assertEqual(cmd[:3], ["cleo", "session", "status"])
69
+ self.assertIn("timeout", kwargs)
70
+
71
+ def test_hung_session_status_is_none(self):
72
+ args = mock.Mock(owner=None, spec=["owner"])
73
+ with mock.patch.dict("os.environ", {}, clear=True), \
74
+ mock.patch.object(run_council.subprocess, "run",
75
+ side_effect=subprocess.TimeoutExpired(["cleo"], 60)):
76
+ self.assertIsNone(run_council.resolve_docs_owner(args))
77
+
78
+
79
+ class TestPushRunToDocsStore(unittest.TestCase):
80
+ def _push(self, mock_run, **kwargs):
81
+ defaults = dict(run_dir=RUN_DIR, meta=dict(META), content=CONTENT, owner="ses_x")
82
+ defaults.update(kwargs)
83
+ return run_council.push_run_to_docs_store(**defaults)
84
+
85
+ def test_add_invocation_shape(self):
86
+ with mock.patch.object(run_council.subprocess, "run") as m:
87
+ m.return_value = _completed([], returncode=0, stdout='{"success":true}')
88
+ ok = self._push(m)
89
+ self.assertTrue(ok)
90
+ (cmd,), kwargs = m.call_args
91
+ self.assertEqual(cmd[:4], ["cleo", "docs", "add", "ses_x"])
92
+ self.assertIn("--content", cmd)
93
+ self.assertEqual(cmd[cmd.index("--content") + 1], "-") # stdin, never argv
94
+ self.assertEqual(cmd[cmd.index("--type") + 1], "note")
95
+ self.assertEqual(cmd[cmd.index("--slug") + 1], "council-run-abcdef12")
96
+ self.assertEqual(kwargs["input"], CONTENT) # markdown flows via stdin
97
+ self.assertEqual(kwargs["env"]["CLEO_SESSION_ID"], "ses_x")
98
+ self.assertEqual(kwargs["timeout"], run_council.CLEO_TIMEOUT_SEC)
99
+ self.assertIn("--title=Retry on timeout", cmd)
100
+
101
+ def test_dash_title_stays_one_token(self):
102
+ with mock.patch.object(run_council.subprocess, "run") as m:
103
+ m.return_value = _completed([], returncode=0)
104
+ self._push(m, meta={**META, "title": "--force everything"})
105
+ (cmd,), _ = m.call_args
106
+ self.assertIn("--title=--force everything", cmd)
107
+ self.assertNotIn("--force everything", cmd)
108
+
109
+ def test_hung_cleo_is_non_fatal(self):
110
+ with mock.patch.object(run_council.subprocess, "run",
111
+ side_effect=subprocess.TimeoutExpired(["cleo"], 60)):
112
+ self.assertFalse(self._push(mock.Mock()))
113
+
114
+ def test_session_env_only_for_session_owner(self):
115
+ with mock.patch.object(run_council.subprocess, "run") as m:
116
+ m.return_value = _completed([], returncode=0)
117
+ with mock.patch.dict("os.environ", {"CLEO_SESSION_ID": "ses_other"}, clear=True):
118
+ ok = self._push(m, owner="T1234")
119
+ self.assertTrue(ok)
120
+ (_,), kwargs = m.call_args
121
+ self.assertEqual(kwargs["env"]["CLEO_SESSION_ID"], "ses_other")
122
+
123
+ def test_slug_reserved_falls_back_to_update(self):
124
+ reserved = _completed([], returncode=1, stdout='{"success":false,"error":{"codeName":"E_SLUG_RESERVED"}}')
125
+ updated = _completed([], returncode=0, stdout='{"success":true}')
126
+ with mock.patch.object(run_council.subprocess, "run") as m:
127
+ m.side_effect = [reserved, updated]
128
+ ok = self._push(m)
129
+ self.assertTrue(ok)
130
+ self.assertEqual(m.call_count, 2)
131
+ (cmd,), _ = m.call_args
132
+ self.assertEqual(cmd[:3], ["cleo", "docs", "update"])
133
+ self.assertEqual(cmd[3], "council-run-abcdef12")
134
+ self.assertIn("abcdef12", cmd[cmd.index("--message") + 1])
135
+
136
+ def test_add_failure_is_non_fatal(self):
137
+ with mock.patch.object(run_council.subprocess, "run") as m:
138
+ m.return_value = _completed([], returncode=1, stdout='{"success":false,"error":{"codeName":"E_BOOM"}}')
139
+ ok = self._push(m)
140
+ self.assertFalse(ok)
141
+ self.assertEqual(m.call_count, 1) # no update fallback on other errors
142
+
143
+ def test_missing_binary_is_non_fatal(self):
144
+ with mock.patch.object(run_council.subprocess, "run") as m:
145
+ m.side_effect = OSError("No such file or directory: 'cleo'")
146
+ ok = self._push(m)
147
+ self.assertFalse(ok)
148
+
149
+ def test_update_failure_is_non_fatal(self):
150
+ reserved = _completed([], returncode=1, stdout="E_SLUG_RESERVED")
151
+ failed = _completed([], returncode=1, stderr="boom")
152
+ with mock.patch.object(run_council.subprocess, "run") as m:
153
+ m.side_effect = [reserved, failed]
154
+ ok = self._push(m)
155
+ self.assertFalse(ok)
156
+
157
+ def test_fallback_requires_slug_reserved_marker(self):
158
+ # A non-reserved add failure must NOT attempt an update.
159
+ with mock.patch.object(run_council.subprocess, "run") as m:
160
+ m.return_value = _completed([], returncode=1, stderr="E_VALIDATION")
161
+ ok = self._push(m)
162
+ self.assertFalse(ok)
163
+ self.assertEqual(m.call_count, 1)
164
+
165
+
166
+ class TestIngestOrdering(unittest.TestCase):
167
+ """T13659: telemetry + INDEX are written first; docs_slug only on success."""
168
+
169
+ REAL_RUN = staticmethod(subprocess.run)
170
+
171
+ def setUp(self):
172
+ self.tmp = Path(tempfile.mkdtemp(prefix="council-ingest-"))
173
+ self.run_dir = self.tmp / "runs" / "20261013T000000-abcdef12"
174
+ self.run_dir.mkdir(parents=True)
175
+ shutil.copy(SCRIPTS_DIR / "fixtures" / "valid.md", self.run_dir / "output.md")
176
+ (self.run_dir / "run.json").write_text(json.dumps(META))
177
+ self.log = self.tmp / "council-runs.jsonl"
178
+
179
+ def tearDown(self):
180
+ shutil.rmtree(self.tmp, ignore_errors=True)
181
+
182
+ def _ingest(self, cleo_behaviour):
183
+ def fake_run(cmd, *a, **kw):
184
+ if cmd and cmd[0] == "cleo":
185
+ return cleo_behaviour(cmd)
186
+ return self.REAL_RUN(cmd, *a, **kw) # real validate.py / telemetry.py
187
+
188
+ args = argparse.Namespace(run_dir=str(self.run_dir), log=str(self.log), tokens=None,
189
+ wall_clock=None, owner="ses_x", no_store=False)
190
+ with mock.patch.object(run_council.subprocess, "run", side_effect=fake_run) as m:
191
+ code = run_council.cmd_ingest(args)
192
+ index = [json.loads(line) for line in (self.tmp / "runs" / "INDEX.jsonl").read_text().splitlines()]
193
+ return code, m, index[0]
194
+
195
+ def _assert_records_written(self, code, entry):
196
+ self.assertEqual(code, 0)
197
+ self.assertTrue(self.log.read_text().strip(), "telemetry JSONL record missing")
198
+ self.assertEqual(entry["status"], "ingested")
199
+
200
+ def test_hung_cleo_does_not_block_and_records_still_land(self):
201
+ def hung(cmd):
202
+ raise subprocess.TimeoutExpired(cmd, run_council.CLEO_TIMEOUT_SEC)
203
+ code, _, entry = self._ingest(hung)
204
+ self._assert_records_written(code, entry)
205
+ self.assertNotIn("docs_slug", entry)
206
+
207
+ def test_failed_write_stamps_no_docs_slug(self):
208
+ code, _, entry = self._ingest(lambda cmd: _completed(cmd, returncode=1, stderr="E_BOOM"))
209
+ self._assert_records_written(code, entry)
210
+ self.assertNotIn("docs_slug", entry)
211
+
212
+ def test_skipped_write_stamps_no_docs_slug(self):
213
+ args_owner = mock.patch.object(run_council, "resolve_docs_owner", return_value=None)
214
+ with args_owner:
215
+ code, m, entry = self._ingest(lambda cmd: _completed(cmd, returncode=0))
216
+ self._assert_records_written(code, entry)
217
+ self.assertNotIn("docs_slug", entry)
218
+ self.assertFalse(any(c.args[0][0] == "cleo" for c in m.call_args_list))
219
+
220
+ def test_successful_write_runs_last_and_stamps_docs_slug(self):
221
+ order = []
222
+
223
+ def ok(cmd):
224
+ order.append("cleo")
225
+ self.assertTrue(self.log.read_text().strip(), "store write ran before telemetry")
226
+ self.assertTrue((self.tmp / "runs" / "INDEX.jsonl").exists(), "store write ran before INDEX")
227
+ return _completed(cmd, returncode=0)
228
+ code, _, entry = self._ingest(ok)
229
+ self._assert_records_written(code, entry)
230
+ self.assertEqual(order, ["cleo"])
231
+ self.assertEqual(entry["docs_slug"], "council-run-abcdef12")
232
+
233
+
234
+ if __name__ == "__main__":
235
+ unittest.main()
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: ct-documentor
3
3
  description: Documentation coordinator with CLEO style guide compliance. Routes every canonical-doc write (spec, adr, research, handoff, note, llm-readme) through the docs SSoT via `cleo docs add` / `cleo docs publish` / `cleo docs fetch` — never raw filesystem writes. Carries the CLEO writing and review guides as references and coordinates ct-spec-writer and ct-adr-recorder. Use when creating or updating documentation files, consolidating scattered documentation, or validating documentation against style standards. Triggers on documentation tasks, doc update requests, or style guide compliance checks.
4
- version: 3.20.0
4
+ version: 3.25.1
5
5
  protocol: null
6
6
  dependencies:
7
7
  - ct-spec-writer
@@ -16,13 +16,13 @@ compatibility:
16
16
  - gemini-cli
17
17
  license: MIT
18
18
  metadata:
19
- version: 3.20.0
19
+ version: 3.25.1
20
20
  tier: core
21
21
  install: harness
22
22
  covers:
23
23
  - packages/cleo/src/cli/commands/docs.ts
24
24
  - packages/contracts/src/docs-taxonomy.ts
25
- lastReviewed: 2026-10-12
25
+ lastReviewed: 2026-10-14
26
26
  stability: stable
27
27
  ---
28
28
 
@@ -59,12 +59,28 @@ T13361 retired the legacy verbs `search`, `find`, `rank` (use `query`),
59
59
  `import`; `sync` stays until the T9645 reingest workflow migrates, and
60
60
  `status` stays agent-facing per `cleodocs-rearch-design` §6.
61
61
 
62
+ `cleo docs stats` (T13363) is a read-only adoption-metrics probe: add:update
63
+ ratio from the docs audit log, loose-markdown count (gitignore-aware scan),
64
+ `docs_wikilinks` edge count, and live `decision_adr_links` count. Use it for
65
+ pilot baseline comparisons — it never writes.
66
+
62
67
  `cleo docs doctor` (T13447) audits the docs store — dangling `local-file`
63
68
  pointers, docVersion skew vs the audit log, empty topics/related_tasks,
64
69
  `docs_wikilinks` drift, legacy store surfaces, stale drafts — and with
65
70
  `--apply` repairs them after creating a backup automatically. Use it for
66
71
  store hygiene checks; it never edits doc content.
67
72
 
73
+ `cleo docs migrate` (T13360) imports legacy docs + history into the re-keyed
74
+ conduit store inside the migration safety harness (pre-step backup, verified
75
+ rollback, COMMIT only on full verification). It is additive and idempotent:
76
+ every imported history row is labeled `origin: migrated`, authors come from
77
+ the legacy audit log (or the honest `system:migration` attribution — never
78
+ invented), docs whose content pointers dangle are content-captured or
79
+ recorded dangling in `conduit_doc_migration_ledger`, and a re-run is a
80
+ verified no-op. Legacy slugs that already exist natively are counted as
81
+ native-conflict skips, never clobbered. Run it once per project before the
82
+ T13361/T13362 retirement lands; it needs no `--force`.
83
+
68
84
  For discovery (`find`, `search`, `schema`, `list-types`), prefer `cleo docs list`
69
85
  first — it returns slug + owner + type without forcing a filesystem walk. Use
70
86
  legacy discovery verbs only when `list` is insufficient.
@@ -173,6 +189,7 @@ Before adding a new doc, ALWAYS ask: is this **a new doc**, **a change to an exi
173
189
  - **"Fix a typo in ADR-076 saga-first-class"** - `cleo docs update adr-076-saga-first-class --file /tmp/fixed.md --message "fix typo in §2"` (canonical - T10161). NOT a new doc. NOT a supersession. Just patch in place; T10161 squashes patches within a 5-minute window so consecutive typo fixes don't bloat the audit log.
174
190
  - **"Accept a spec that review signed off"** - `cleo docs update <slug> --status accepted` (T12654). `--status` alone changes only the lifecycle status (draft, proposed, accepted, superseded, archived, deprecated); the stored bytes are kept, so there is no need to re-supply `--file` or `--content`. Any content edit without `--status` resets the doc to `draft`.
175
191
  - **"Replace a doc's body from a pipe"** - `cat new.md | cleo docs update <slug> --content -` (T13564). As in `docs add`, `-` reads stdin; an empty or bare `-` body is refused, never stored.
192
+ - **"Editing a doc another agent may touch"** - `cleo docs fetch <slug>` first, note `metadata.headVersionUid`, then `cleo docs update <slug> --file new.md --base-version-uid <headVersionUid>` (T13674). A stale base merges append edits into the head (`staleBaseMerged: true`) or records `sectionConflicts` — a concurrent writer's edit is never silently dropped; a base that no longer resolves is refused with `E_DOC_BASE_UNKNOWN`. `--file` must be UTF-8 text when a base is given (`E_DOC_BASE_UNSUPPORTED` otherwise). The update result's `headVersionUid` is the base for your next edit, so chained edits need no re-fetch.
176
193
  - **"Trace an ADR to the decisions it ratifies"** - `cleo docs link-decision <adr-slug> D####` (T13598): the slug must be an existing doc and the decision a stored one, since a link is permanent. `cleo docs fetch <adr-slug>` then lists them as `linkedDecisions`. `cleo docs unlink-decision <adr-slug> D####` cannot be undone: the revoke is a permanent tombstone, and that pair can never be re-linked.
177
194
 
178
195
  - **"Replace the entire saga-orchestration model with v2" (ADVANCED)** - `cleo docs add T9999 v2.md --type adr --slug adr-080-saga-orchestration-v2` followed by `cleo docs supersede adr-073-above-epic-naming adr-080-saga-orchestration-v2 --reason "v2 canonicalizes the SG- prefix + 4-tier hierarchy"`. Both rows survive in the attachments table; readers see `lifecycle_status=superseded` on v1 and `supersedes=adr-073` on v2.
@@ -8,12 +8,13 @@ description: >-
8
8
  gates, package boundaries, validation, data safety or anything the task asked for.
9
9
  license: MIT
10
10
  metadata:
11
- version: 1.0.0
11
+ version: 1.1.0
12
12
  tier: core
13
13
  install: harness
14
14
  covers:
15
15
  - packages/core/src/orchestration/lean-change.ts
16
- lastReviewed: 2026-10-10
16
+ - packages/cleo/src/cli/commands/lean.ts
17
+ lastReviewed: 2026-10-13
17
18
  stability: stable
18
19
  attribution: Adapted from Ponytail 5.1.0 by DietrichGebert (MIT) — https://github.com/DietrichGebert/ponytail
19
20
  ---
@@ -24,6 +25,20 @@ The best code is the code never written. Solve the whole task with the least new
24
25
  then end the reply with one or two lines: what you skipped or did not check, and any risk
25
26
  the owner must know.
26
27
 
28
+ ## Levels
29
+
30
+ | Level | What changes |
31
+ |---|---|
32
+ | `lite` | Build what was asked. When a smaller option fully works, name it in one line and let the owner pick. |
33
+ | `full` | The rules below. Default. |
34
+ | `ultra` | `full`, and question the request first: push back on any part the need does not justify. |
35
+ | `off` | No lean rules. |
36
+
37
+ The level resolves from `CLEO_LEAN_LEVEL`, then the session level, then the `lean.level`
38
+ config key, then `full`. When the owner asks for a level ("go ultra", "lean off"), set it:
39
+ `cleo lean level ultra --session` for this session, `cleo lean level lite` for the project,
40
+ `--global` for every project. `cleo lean level` alone shows the level and its source.
41
+
27
42
  ## Before you write
28
43
 
29
44
  Read the task (`cleo show <id> --full`) and the code it touches. List every place the
@@ -65,7 +80,10 @@ callers that stop working, stores it would migrate. That is scope. Extra feature
65
80
  ## Never cut
66
81
 
67
82
  - Validation at trust boundaries, error handling that prevents data loss, security,
83
+ privacy (one user's data never reaches another, e.g. many recipients go in BCC, not To),
68
84
  accessibility, and anything the task or owner asked for.
85
+ - A risk your change creates is part of the change: when the fix is small and in scope,
86
+ make it, then name it in the reply. Only report a risk you cannot fix in scope.
69
87
  - CLEO evidence: every gate gets programmatic evidence (`cleo verify`), never self-attested.
70
88
  - Type safety: no `any`, no `as unknown as X`; shared types live in `packages/contracts/`.
71
89
  - The package boundary and the `cleo check arch` gates; a smaller diff in the wrong package
@@ -2,13 +2,13 @@
2
2
  name: ct-orchestrator
3
3
  description: "Pipeline-aware orchestration skill for managing complex workflows through subagent delegation. Use when the user asks to \"orchestrate\", \"orchestrator mode\", \"run as orchestrator\", \"delegate to subagents\", \"coordinate agents\", \"spawn subagents\", \"multi-agent workflow\", \"context-protected workflow\", \"agent farm\", \"HITL orchestration\", \"pipeline management\", or needs to manage complex workflows by delegating work to subagents while protecting the main context window. Enforces ORC-001 through ORC-009 constraints. Provider-neutral — works with any AI agent runtime."
4
4
  metadata:
5
- version: 4.0.13
5
+ version: 4.0.15
6
6
  tier: core
7
7
  install: harness
8
8
  covers:
9
9
  - packages/cleo/src/cli/commands/orchestrate.ts
10
10
  - packages/core/src/orchestration/spawn-prompt.ts
11
- lastReviewed: 2026-10-11
11
+ lastReviewed: 2026-10-13
12
12
  stability: stable
13
13
  ---
14
14
 
@@ -148,6 +148,10 @@ cleo orchestrate spawn T1586 --json
148
148
 
149
149
  Returns: resolved prompt with base protocol + conditional protocol + task context + all `{{TOKEN}}` placeholders filled.
150
150
 
151
+ The prompt's `## Lean Change` block follows the effective ct-lean level (T13663):
152
+ `CLEO_LEAN_LEVEL`, then the spawning session's `cleo lean level --session`, then `lean.level`,
153
+ then `full`. At `off` the block is omitted, so set the level before spawning, not after.
154
+
151
155
  The spawn provisions the worker's worktree, and its `pnpm install` is admitted through the heavy queue or deferred to the worker; the envelope's `install.status` says which. `cleo orchestrate spawn T1586 --no-install` provisions without installing, and the envelope names the command the worker runs instead (T13524).
152
156
 
153
157
  ### Step 2: Execute (Provider-Specific)
@@ -217,6 +221,25 @@ cleo orchestrate start T1575 # Full state: session, pipeline, next task
217
221
  4. Present plan to human for approval
218
222
  ```
219
223
 
224
+ **Stage output documents go to the store (T13363 AC2).** When a stage finishes its
225
+ output document (research findings, consensus record, spec), keep the working copy at
226
+ `.cleo/rcasd/<epic>/<stage>/` AND push the finished document into the CleoDocs store
227
+ so its `##` sections land as versioned, collaboratively-mergeable sections:
228
+
229
+ ```bash
230
+ # First time — owner is the epic task, kind matches the stage
231
+ cleo docs add T1575 .cleo/rcasd/T1575/research/T1575-research.md \
232
+ --type research --slug t1575-research
233
+
234
+ # Re-runs / refreshes update the same doc (noop when bytes are unchanged)
235
+ cleo docs update t1575-research --file .cleo/rcasd/T1575/research/T1575-research.md \
236
+ --message "research stage refresh"
237
+ ```
238
+
239
+ Retrieve with `cleo docs fetch t1575-research`. The raw stage file stays the working
240
+ copy and gate input; the store doc is the durable sectioned record. Skipping the store
241
+ write is not fatal — but a stage is not "published" until the store doc exists.
242
+
220
243
  ### 3. IVTR — Execute the Work
221
244
 
222
245
  ```
@@ -226,7 +226,7 @@
226
226
  },
227
227
  {
228
228
  "name": "ct-cleo",
229
- "version": "2.24.11",
229
+ "version": "2.24.13",
230
230
  "description": "CLEO task management protocol - session, task, and workflow guidance. Use when managing tasks, sessions, or multi-agent workflows with the CLEO CLI protocol.",
231
231
  "path": "skills/ct-cleo",
232
232
  "tier": 0,
@@ -479,7 +479,7 @@
479
479
  },
480
480
  {
481
481
  "name": "ct-council",
482
- "version": "1.0.0",
482
+ "version": "1.1.1",
483
483
  "description": "Convene \"The Council\" — a 5-advisor, shuffled gate-based peer-review, chairman-synthesis workflow for reviewing a plan, decision, architecture, or piece of work inside the current project. Use when the user says \"convene the council\" (or \"counsel\"), \"get the council on this\", \"council review\", \"run the five advisors\", \"stress-test this\", \"get multiple perspectives\", or asks for a rigorous multi-angle challenge of a proposal (Contrarian, First Principles, Expansionist, Outsider, Executor → shuffled peer review with pass/fail gates → convergence detector → Chairman verdict). Operates on the current codebase — each advisor grounds their analysis in actual files/commits before opining. Output is validated by scripts/validate.py.",
484
484
  "path": "skills/ct-council",
485
485
  "tier": 1,
@@ -642,7 +642,7 @@
642
642
  },
643
643
  {
644
644
  "name": "ct-documentor",
645
- "version": "3.20.0",
645
+ "version": "3.25.1",
646
646
  "description": "Documentation coordinator with CLEO style guide compliance. Routes every canonical-doc write (spec, adr, research, handoff, note, llm-readme) through the docs SSoT via `cleo docs add` / `cleo docs publish` / `cleo docs fetch` — never raw filesystem writes. Carries the CLEO writing and review guides as references and coordinates ct-spec-writer and ct-adr-recorder. Use when creating or updating documentation files, consolidating scattered documentation, or validating documentation against style standards. Triggers on documentation tasks, doc update requests, or style guide compliance checks.",
647
647
  "path": "skills/ct-documentor",
648
648
  "tier": 0,
@@ -983,7 +983,7 @@
983
983
  },
984
984
  {
985
985
  "name": "ct-lean",
986
- "version": "1.0.0",
986
+ "version": "1.1.0",
987
987
  "description": "Lean change discipline for CLEO work: the smallest change that fully solves the task, and a reply a busy owner understands in one read. Use on every coding task (writing, fixing, refactoring, reviewing, choosing dependencies) and when the owner asks for the simplest solution, YAGNI, or complains about over-engineering or bloat. Never trades away evidence gates, package boundaries, validation, data safety or anything the task asked for.",
988
988
  "path": "skills/ct-lean",
989
989
  "tier": 0,
@@ -1001,7 +1001,7 @@
1001
1001
  },
1002
1002
  {
1003
1003
  "name": "ct-orchestrator",
1004
- "version": "4.0.13",
1004
+ "version": "4.0.15",
1005
1005
  "description": "Pipeline-aware orchestration skill for managing complex workflows through subagent delegation. Use when the user asks to \\\"orchestrate\\\", \\\"orchestrator mode\\\", \\\"run as orchestrator\\\", \\\"delegate to subagents\\\", \\\"coordinate agents\\\", \\\"spawn subagents\\\", \\\"multi-agent workflow\\\", \\\"context-protected workflow\\\", \\\"agent farm\\\", \\\"HITL orchestration\\\", \\\"pipeline management\\\", or needs to manage complex workflows by delegating work to subagents while protecting the main context window. Enforces ORC-001 through ORC-009 constraints. Provider-neutral — works with any AI agent runtime.",
1006
1006
  "path": "skills/ct-orchestrator",
1007
1007
  "tier": 0,