Skip to content
Back to skills

Ct Documentor

BSecurity

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...

  • 160 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added May 28, 2026
documentationgobashnodegitapidatabasedocumentation

Works with

  • cursor
  • terminal
  • cli
  • api
  • mcp

Security analysis

B85/100
  • highPerforms destructive filesystem operations

Pro scans all 5 files and shows the line behind each finding

Scanned October 1, 2026

npx -y skills add kryptobaseddev/cleo --skill ct-documentor --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Ct Documentor?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Ct Documentor
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/kryptobaseddev-ct-documentor/badge)](https://www.skillsdirectory.com/skills/kryptobaseddev-ct-documentor)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: ct-documentor
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.
version: 3.17.4
protocol: null
dependencies:
  - ct-spec-writer
  - ct-adr-recorder
sharedResources:
  - subagent-protocol-base
  - task-system-integration
compatibility:
  - claude-code
  - cursor
  - windsurf
  - gemini-cli
license: MIT
metadata:
  version: 3.17.4
  tier: core
  install: harness
  covers:
    - packages/cleo/src/cli/commands/docs.ts
    - packages/contracts/src/docs-taxonomy.ts
  lastReviewed: 2026-09-30
  stability: stable
---

# Documentation Specialist Context Injection

**Protocol**: @src/protocols/implementation.md
**Type**: Context Injection (cleo-subagent)
**Version**: 3.0.0

---

## Canonical Docs Command Path (T10516 — T11048)

The agent-facing docs surface is simplified to six canonical verbs. Use these as the
primary workflow. Legacy verbs remain available but are **advanced or migration
surfaces** — do not present them as the default path to agents.

| Canonical Verb | Purpose | When to Use |
|---------------|---------|-------------|
| `add` | Create a new doc attached to a task/session | First write of any canonical doc |
| `update` | Patch/replace an existing doc's content | Fixes, extensions, refreshes |
| `fetch` | Retrieve a doc by slug | Reading, reviewing, verifying |
| `list` | Browse docs by owner, type, or project | Discovery, checking what exists |
| `remove` | Detach a doc from an owner | Cleanup, de-duplication |
| `publish` | Mirror an SSoT blob to a git-tracked path | When a doc must live on disk |

**Legacy verbs** (advanced / migration surfaces only):
`supersede`, `find --similar`, `graph`, `versions`, `sync`, `search`,
`merge`, `rank`, `generate`, `export`, `schema`, `list-types`,
`serve`/`open`/`stop`/`viewer-status`, `status`/`gap-check`, `import`,
`publish-pr`.

For discovery (`find`, `search`, `schema`, `list-types`), prefer `cleo docs list`
first — it returns slug + owner + type without forcing a filesystem walk. Use
legacy discovery verbs only when `list` is insufficient.

---

## Verify document storage and optional projections separately

After `cleo docs add`, retain the canonical attachment ID and SHA-256, then use
`cleo docs fetch <slug>` to verify the accepted bytes. For exact-byte checks,
decode `data.bytesBase64` from the JSON response and compare its digest with
`data.metadata.sha256`; rendered content can add a trailing newline.

The add result's `projection` reports captured project identity/root, coverage,
diagnostics, the original deadline, and any durable `jobId` or verified receipt.
A successful document write can coexist with failed or pending graph/observation
work. Preserve those diagnostics and references in the handoff. Missing identity
means missing projection coverage; it does not authorize guessing a project.

Optional projection work shares a two-second foreground budget across identity
capture, preparation, locking, execution, and verification. Deadline expiry ends
foreground observation; synchronous database work is not preempted by a timer.
If the outcome is unresolved, inspect the retained job and accepted attachment
before explicitly resuming. Do not repeat the add to simulate a retry or assume
that a lost worker reply means no write committed. A projection receipt verifies
its domain results; it is not an atomic receipt for all document side effects.

## Purpose

Context injection for documentation tasks spawned via cleo-subagent. Orchestrates documentation workflows by coordinating specialized skills for lookup, writing, and review.

## Skill Coordination

| Skill | Purpose | Invoke When |
|-------|---------|-------------|
| `cleo docs find` / `list` / `fetch` (and the Context7 MCP for library APIs) | Query existing docs and current external docs | Discovery phase, checking what exists |
| `references/writing.md` (was ct-docs-write) | Create/edit docs via `cleo docs add` with CLEO style | Writing or updating content |
| `references/reviewing.md` (was ct-docs-review) | Check compliance with style guide, read through `cleo docs fetch`; PR mode | Quality validation before completion |
| `ct-spec-writer` | Author specs (REQ-XXX requirements) via `cleo docs add --type spec` | Formal specification work |
| `ct-adr-recorder` | Author ADRs via `cleo docs add --type adr --slug adr-NNN-...` | Architecture decisions promoted from consensus |

The coordinator never writes content itself — it routes the work to the
specialist skill that owns the doc type, and every specialist routes its
filesystem write through the docs SSoT (see "Coordinator Pattern" below).

---

## Coordinator Pattern: SSoT-First Routing

ct-documentor is a router. It dispatches each doc-type to its owning skill,
and every owner writes through `cleo docs add` — not raw filesystem writes.

| Doc Type | Owner Skill | SSoT Command |
|----------|-------------|--------------|
| `spec` (REQ-XXX requirements) | `ct-spec-writer` | `cleo docs add <ownerId> <path> --type spec --slug spec-<feature>` |
| `adr` (architecture decisions) | `ct-adr-recorder` | `cleo docs add <ownerId> <path> --type adr --slug adr-<NNN>-<rest>` (TODO T10360 · E3.2 pending: `--title` + auto-`adr-NNN` allocation) |
| `research` (multi-source investigation) | `ct-research-agent` | `cleo docs add <ownerId> <path> --type research --slug research-<topic>` |
| `handoff` (session/agent transition) | `ct-documentor` (this skill) | `cleo docs add <ownerId> <path> --type handoff --slug handoff-<context>` |
| `note` (conversational prose) | `ct-documentor` (`references/writing.md`) | `cleo docs add <ownerId> <path> --type note --slug <kebab-topic>` |
| `llm-readme` (agent-facing) | `ct-documentor` (`references/writing.md`) | `cleo docs add <ownerId> <path> --type llm-readme --slug <kebab-topic>` |

Hard rule: EVERY canonical-type write goes through the SSoT. The coordinator
rejects any subagent return that wrote raw markdown into `.cleo/adrs/`,
`.cleo/research/`, `.cleo/agent-outputs/`, or `docs/` without first
materializing through `cleo docs add` + (optionally) `cleo docs publish`.

---

## Decision: update vs supersede vs create (T10168 · Saga T9855 / E12)

Before adding a new doc, ALWAYS ask: is this **a new doc**, **a change to an existing doc**, or **a replacement for an existing doc**? Prefer the canonical six-verb path (`add` / `update` / `fetch` / `list` / `remove` / `publish`). Legacy verbs annotated below.

```
┌─────────────────────────────────────────────────────────────────────┐
│  Question                              │ Verb                        │
├─────────────────────────────────────────────────────────────────────┤
│  Same idea, fixing/extending content?  │ cleo docs update <slug>     │
│  → typo in ADR-076                     │   (canonical — T10161)      │
│  → adding a clarifying paragraph       │                             │
│  → refreshing a stale section          │                             │
│                                                                       │
│  Replacing the whole canonical model?  │ cleo docs supersede         │
│  → "saga model v2" replaces v1         │   <old> <new>               │
│  → ADR-080 replaces ADR-073            │   (ADVANCED — T10162)       │
│  → new architecture supplants old      │                             │
│                                                                       │
│  Genuinely new idea?                   │ cleo docs list FIRST        │
│  → drafting a new spec                 │   (canonical discovery)     │
│  → fresh ADR for a new concern         │   → if no hit, docs add     │
│  → recording a new investigation       │   → if hit, docs update     │
│                                                                       │
│  Tracing the lineage graph?            │ cleo docs graph             │
│  → "what does this doc replace?"       │   --root <slug>             │
│  → "what tasks reference this ADR?"    │   (ADVANCED — T10164)       │
└─────────────────────────────────────────────────────────────────────┘
```

**Canonical discovery path**: `cleo docs list` (browse by owner/type) → `cleo docs fetch <slug>` (read). Reserve `cleo docs find --similar` (ADVANCED — T10163) for near-duplicate collision checks and `cleo docs search` for semantic queries.

### Examples

- **"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.
- **"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`.

- **"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.

- **"Drafting a new spec for the BRAIN recovery pipeline"** - FIRST run `cleo docs list --type spec` to browse existing specs (canonical discovery). If a matching spec exists, use `cleo docs update`. If no match, proceed with `cleo docs add T#### draft.md --type spec --slug spec-brain-recovery-pipeline`. The `cleo docs find --similar brain-recovery` path is the ADVANCED collision-detection surface - use only when `list` is insufficient.

- **"Auditing the supersession chain for ADR-039" (ADVANCED)** - `cleo docs graph --root adr-039-lafs-envelope-unification --depth 3`. Returns a DocProvenanceGraph envelope (T10166 contract) with nodes + edges, optionally `--format dot` for graphviz visualization.

### Anti-patterns (INSTANT REJECTION)

- ❌ Calling `cleo docs add` with a slug that's only a typo-distance away from an existing doc — the auto-warn (T10167) fires `W_SLUG_SIMILAR`; respect it and route to `cleo docs update` instead of bypassing with `--allow-similar`.
- ❌ Editing a `.cleo/adrs/*.md` file directly with `Write` — bypasses the SSoT writer; T10366 `WriterRegistry` is the chokepoint and the docs SSoT will reconcile auto-emit drift.
- ❌ Creating a new doc when an existing one needs updating — bloats the attachments table and breaks lineage. The decision tree above resolves this — when in doubt, run `cleo docs list` first, or `cleo docs find --similar <proposed-slug>` (ADVANCED) as a last resort.

## Through SDK (preferred)

Documentation work flows through the docs SSoT using the canonical six-verb path:
**add, update, fetch, list, remove, publish**. Use the slug-based contract so downstream consumers
can retrieve docs without grepping the filesystem.

### Add a doc attached to a task

```bash
cleo docs add T1234 docs/drafts/feature-x.md \
  --type note \
  --slug feature-x-overview \
  --desc "Conversational overview — pre-review"
```

- `--type` MUST be one of `spec | adr | research | handoff | note | llm-readme`.
  Pick the type by the document's purpose, not its filename.
- `--slug` is the human-friendly retrieval handle (kebab-case). If taken the
  CLI returns `E_SLUG_RESERVED` (legacy alias `E_SLUG_TAKEN`) with 3
  alternatives — pick one, do not overwrite.
- The owner ID (`T1234` above) auto-classifies the attachment by prefix:
  `T###` → task, `ses_*` → session, `O-*` → observation.

### Strict flag validation (T10359 · closes T10238)

`cleo docs add` rejects unknown flags with `E_UNKNOWN_FLAG` + Levenshtein
"did you mean" suggestions and exits with code `6` (`VALIDATION_ERROR`).
This closes the silent-absorption footgun where citty's underlying
`parseArgs({ strict: false })` accepted typo'd flags (e.g. `--titel`,
`--title`) as positional values.

```bash
# Typo → E_UNKNOWN_FLAG with suggestion
$ cleo docs add T123 file.md --titel "X"   # cleo-cmd: negative-example
{
  "success": false,
  "error": {
    "code": 6,
    "codeName": "E_UNKNOWN_FLAG",
    "message": "E_UNKNOWN_FLAG: unknown flag '--titel' for 'docs add'. Did you mean: --type, --slug?",
    "fix": "Try one of: --type, --slug. Run `cleo docs add --help` for the full flag list.",
    "alternatives": [{ "action": "--type", "command": "--type" }, { "action": "--slug", "command": "--slug" }],
    "details": { "flag": "--titel", "knownFlags": [...] }
  },
  "meta": { ... }
}
```

The accepted positional + named surface is enumerated in
`cleo docs add --help` — agents MUST consult `--help` rather than guessing
flag names. Use the `--flag=value` form (`--type=spec`) or
`--flag value` (`--type spec`) — both are recognised.

#### Slug allocation goes through ONE chokepoint (T10392 · Saga T10288)

Every code path that writes an attachment with a slug — `cleo docs add`,
`cleo changeset add`, and any future writer — MUST first call the central
allocator at `packages/core/src/docs/slug-allocator.ts:reserveSlug` BEFORE
invoking `attachmentStore.put({ slug })`. The allocator:

1. Normalises the slug to canonical kebab-case (lowercase, trim, single
   hyphens).
2. Acquires a per-slug in-process Mutex so concurrent reservations
   serialise.
3. Returns `{ ok: false, code: 'E_SLUG_RESERVED', suggestions }` when the
   slug is taken — uniform shape across both writers.

The `attachmentStore.put` chokepoint enforces this via a runtime assert
(`SlugNotReservedByAllocatorError`) when `CLEO_STRICT_SLUG_ALLOCATOR=1`
is set. Strict mode becomes default in the next release after both
writers are wired. **BOTH writers are LIVE on the allocator as of T10388**:
`cleo docs add` (T10386 — `packages/cleo/src/dispatch/domains/docs.ts:add`)
and `cleo changeset add` (T10388 — `packages/core/src/changesets/writer.ts:writeChangesetEntry`)
both call `reserveSlug(kind, slug)` BEFORE any filesystem or DB mutation.
Collisions surface the uniform envelope:

```json
{
  "success": false,
  "error": {
    "code": "E_SLUG_RESERVED",
    "message": "slug 'foo' is already in use in this project",
    "details": {
      "suggestions": ["foo-2", "foo-3", "foo-4"],
      "aliases": ["E_SLUG_TAKEN"]
    }
  }
}
```

`details.aliases` retains the legacy `E_SLUG_TAKEN` (docs-add path) /
`E_SSOT_WRITE_FAILED` (changeset-add path) codes for ONE release of
back-compat — downstream consumers grepping for the old codes can still match
via the alias array. Removed after E2 (T10290 — DocKind writer dedup) lands.

Slugs share a GLOBAL namespace across all DocKinds — `reserveSlug('changeset',
'foo')` followed by `reserveSlug('research', 'foo')` collides. The decision
is recorded in **ADR-076 §6 amendment AMD-001** (slug
`adr-076-canonical-docs-ssot`, T10390 / E1.5). Three-point evidence (human-memorable
global lookup, DocKind-distinct prefix conventions, backward-compat cost) and
the full counterfactual analysis live in that amendment. Matches the
`uniq_attachments_slug` partial UNIQUE INDEX in migration `20260519000001`.

When choosing a slug, follow the DocKind prefix conventions in the routing
matrix above (`adr-NNN-*`, `spec-*`, `research-*`, `t<id>-*` for changesets,
etc.) — this is what makes cross-kind collisions structurally near-impossible.

#### One writer per DocKind — WriterRegistry SSoT (T10366 · Saga T10288 / Epic T10290)

`packages/core/src/docs/writer-registry.ts:WriterRegistry` is the SSoT for
"which CLI verb writes which DocKind". Every `BuiltinDocKind` maps to EXACTLY
ONE writer descriptor — multi-writer regressions trip the registry at build
time (the slug-collision class root-cause from T10294).

**Decision tree — pick the verb without guessing:**

```text
if kind === 'changeset'          → cleo changeset add
elif kind === 'llm-readme'       → tooling-composed (system-managed)
elif kind === 'release-note'     → cleo release reconcile (system-managed)
else                              → cleo docs add --type <kind>
```

In code:

```ts
import { WriterRegistry } from '@cleocode/core/internal';

const desc = WriterRegistry.for('changeset');
// → { kind: 'changeset', verb: 'changeset add', dispatchOp: 'changeset.add',
//     coreFn: 'writeChangesetEntry', mode: 'ssot-first',
//     sourcePath: 'packages/core/src/changesets/writer.ts' }

// Resolve the verb programmatically — no string-matching in caller code.
const verb = WriterRegistry.for(kind).verb; // 'docs add' | 'changeset add' | 'system-managed'
```

Descriptor modes mirror `.cleo/canon.yml`'s `canonicalHome`:
- `'ssot'` — bytes live in the blob store; published copy is a mirror.
- `'ssot-first'` — dual-write via a dedicated verb (`changeset` today).
- `'system-managed'` — tooling-composed (e.g. `llm-readme`, `release-note`).

Test parity: every `ssot-first` descriptor MUST match a kind whose
`canonicalHome: 'ssot-first'` in `.cleo/canon.yml`, and vice versa.

**Legitimate bypass — `WriterRegistry.systemManaged` (T10368):** writers
for `system-managed` kinds (e.g. `generateDocsLlmsTxt`, `composeReleaseNotes`)
do not go through `cleo docs add` because the bytes are tooling-composed
rather than human-authored. The registry still names the producer via
`coreFn`, so drift tooling can follow the breadcrumb from kind →
core function without an exception list. Do NOT invent NEW `system-managed`
entries to dodge the docs-add path — every kind authored by a human MUST
flow through `cleo docs add` or `cleo changeset add`.

**Cross-link to the allocator contract:** the registry calls
{@link reserveSlug} BEFORE the writer delegates. A taken slug returns
`{ ok: false, code: 'E_SLUG_RESERVED', details: { suggestions } }`
with the SAME shape both `cleo docs add` and `cleo changeset add` surface
to callers (closing the T10294 envelope-drift bug). See the
"Slug allocation goes through ONE chokepoint" section above for the
allocator contract and the legacy `E_SLUG_TAKEN` alias.

T10366 establishes the registry contract; T10367 (docs add) and T10368
(changeset add) wire the actual writer delegation. Until those land,
`WriterRegistry.write()` returns `E_NOT_IMPLEMENTED` after consulting the
allocator — callers should continue invoking the existing writers
(`cleo docs add` dispatch handler, `writeChangesetEntry`) directly.

**T10367 LIVE — `cleo docs add --type changeset` delegates to
`writeChangesetEntry`.** The dispatch handler in
`packages/cleo/src/dispatch/domains/docs.ts` now branches on
`payload.type === 'changeset'` and routes the call through the
canonical dual-write transaction. This eliminates the second writer
for the `changeset` DocKind (the SG-DOCS-INTEGRITY invariant) — the
bytes that land on `.changeset/<slug>.md` AND the SSoT blob are
byte-identical regardless of which verb the operator invoked. The
`cleo changeset add` CLI remains the friendlier authoring surface
(it prompts for every required frontmatter field) while
`cleo docs add --type changeset --file <path>` works for agents that
already have a fully-formed changeset markdown blob in hand.

Contract for the docs-add path:
- The input file MUST carry valid changeset frontmatter
  (`id`, `tasks`, `kind`, `summary`). Missing → `E_REQUIRES_CHANGESET_VERB`
  with a fix hint pointing at `cleo changeset add` for guided authoring.
- When `--slug` is also passed it MUST match the frontmatter `id`
  (the frontmatter is canonical) — divergence → `E_SLUG_MISMATCH`.
- The LAFS envelope on success carries `data.type === 'changeset'`,
  `data.slug`, `data.attachmentId`, and `data.sha256` — round-trip
  identical to what `cleo changeset add` emits.

#### CI gate — DocKind Writer Uniqueness (T10369)

`scripts/lint-dockind-writer-uniqueness.mjs` (CI job:
`DocKind Writer Uniqueness (T10369)`) enforces the
WriterRegistry invariants at PR-time. It refuses to merge a PR that:

1. Adds a new entry to `BUILTIN_DOC_KINDS` (in
   `packages/contracts/src/docs-taxonomy.ts`) without a matching
   descriptor in `writer-registry.ts` (`dockind-coverage-missing`).
2. Declares more than one descriptor for the same DocKind
   (`dockind-coverage-collision` — the registry itself throws at module
   load too, this gate surfaces it earlier in CI).
3. Has a `mode: 'ssot-first'` descriptor that does NOT match
   `.cleo/canon.yml`'s `canonicalHome` for the same kind, or vice versa
   (`canon-yml-ssot-first-drift`).
4. Adds a NEW raw `writeFileSync(path.md, …)` / `writeFile(path.md, …)`
   call inside `packages/core/src/**` that is not in
   `.lint-dockind-writer-baseline.json` (`unregistered-md-write`).

Schema-parity rules (#1-#3) are ALWAYS strict — there is no baseline.
The unregistered-md-write rule runs in baseline mode by default; count
decreases always pass, count increases fail. The two legitimate
non-DocKind `.md` writers (`packages/core/src/sessions/handoff-markdown.ts`
for session snapshots and `packages/core/src/changesets/writer.ts` for
the canonical `changeset` DocKind) are allowlisted in the script.

Per-line opt-out (use sparingly): append
`// dockind-writer-allowed: <reason>` on the writeFile line.

#### Audit complement — manual-write sweep (T10372)

`scripts/sweep-manual-doc-writes.mjs` (CI job:
`Manual Write Sweep (T10372)`) is the read-only audit counterpart to
the writer-uniqueness lint. Where T10369 prevents *new* raw `.md`
writers from landing in `packages/core/src/**`, this sweep walks every
`*.md` file *already* added under `.cleo/canon.yml`'s `rawMdPaths`
directories since the T9791 docs-import cutoff — the commit that
imported the legacy corpus and committed its manifests under
`.cleo/audit/imports/`, which is how the sweep locates it — and
classifies each one against the docs SSoT:

| Remediation | Meaning | Fix |
|---|---|---|
| `in-sync` | File SHA matches a blob already in the SSoT — bytes are tracked. | None. |
| `drift` | Slug exists in SSoT but the on-disk content has changed. | Re-publish via `cleo docs publish` or re-add as a new version. |
| `orphan` | Neither SHA nor slug resolves — the file is a raw fs write that bypassed `cleo docs add`. | Migrate via `cleo docs add <ownerId> <file> --type <kind> --slug <slug>`. |
| `deleted` | File was added since the cutoff but no longer exists on disk. | Informational only — does not count toward `unresolved`. |

Each run writes a timestamped report to
`audit/manual-write-sweep-<date>.json` and prints the summary block to
stdout. The CI job uploads the report as a workflow artefact on every
run and is wired with `continue-on-error: true` initially so the
existing orphan corpus does not break PRs. Saga T10288 / Epic T10293
E5.3 closes the orphan migration; the gate flips strict after that
lands.

```bash
# Local invocation — uses the globally-installed `cleo` on PATH.
node scripts/sweep-manual-doc-writes.mjs

# CI / monorepo build — point at the just-built local CLI bundle.
node scripts/sweep-manual-doc-writes.mjs \
  --cleo-bin "node packages/cleo/dist/cli/index.js" \
  --allow-unresolved
```

Exit codes: `0` (clean OR `--allow-unresolved`), `1` (at least one
`orphan` or `drift` entry), `2` (canon.yml parse failure, git not
available, SSoT query failed).

### T10179 + T10203 manual-write migration (T10371)

Saga T10176's two known raw-write workarounds are normalised:

| Original file | SSoT slug | Type | Notes |
|---|---|---|---|
| `docs/research/t10179-executor-probe-result.md` | `t10179-executor-probe` | research | in-sync via earlier T9791 import — verified by SHA. |
| `.changeset/t10179-executor-probe.md` (consumed v5.108) | `t10179-changeset-archive` | note | bytes preserved verbatim from git `cc48ca10e`; archived because the pnpm/changesets `"@cleocode/cleo": patch` frontmatter does not satisfy the `changeset` DocKind schema. |
| `.changeset/t10203-napi-step-exports.md` (consumed v5.108) | `t10203-napi-step-exports` | changeset | in-sync via the `cleo changeset add` dual-write at PR-time. |

Round-trip parity is regression-locked by
`packages/core/src/docs/__tests__/manual-write-migration.test.ts`. The
test embeds the canonical bytes inline and asserts that
`createAttachmentStore().put(...) → findBySlug(...)` returns the same
SHA-256 it started with. Any future migration that silently rewrites or
recompresses these blobs fails the test.

### Sweep-driven remediation loop (T10373)

T10371 only covers the *known* manual-write set declared in the original
Saga T10176 disposition. The T10372 sweep surfaces *every* orphan
remaining under `rawMdPaths` at the moment it runs. T10373 closes the
loop by consuming the sweep report and migrating each orphan into the
SSoT using the same `cleo docs add --slug` pattern T10371 established.

The recurring pattern (use this any time the sweep flags fresh
orphans):

1. Run the sweep: `node scripts/sweep-manual-doc-writes.mjs`. The
   report lands at `audit/manual-write-sweep-<date>.json`.
2. For each `orphan` entry, derive the migration tuple:
   - `--type` from the file's parent directory (`.cleo/adrs/` → `adr`,
     `.cleo/research/` → `research`, `.cleo/agent-outputs/` →
     `handoff` or `note` based on content, `.cleo/rcasd/` → `rcasd`).
   - `--slug` from the filename — lowercase, kebab-case, no
     extension (e.g. `ADR-085-cross-db-invariants.md` →
     `adr-085-cross-db-invariants`).
   - `<owner-id>` from the file's frontmatter `task:` field if
     present, otherwise from `cleo find "<filename-keyword>"`.
3. Run `cleo docs add <ownerId> <file> --type <kind> --slug <slug>
   --desc "<sweep-remediation context>"`. The `--desc` should
   reference the originating task ID so future operators can trace
   the migration.
4. Verify via `cleo docs fetch <slug>` and re-run the sweep — the
   `orphan` count MUST drop by the number of files migrated.
5. Add the new (slug, sha256, type, ownerId) row to a
   round-trip parity test alongside the T10371 set. The canonical
   example lives at
   `packages/core/src/docs/__tests__/sweep-remediation.test.ts`.

T10373 migrated five orphans this way: `ADR-083`, `ADR-085`,
`T10268-saga-closeout`, `t10292-e4-cli-verb-matrix`, and
`t10292-e4-sdk-import-edges`. The last two were direct fallout from
the pre-T10389 worktree-unreachable bug — T10353 and T10354 workers
fell back to raw filesystem writes because `cleo docs add` rejected
inside their spawned worktrees. Re-publishing the bytes via the SSoT
proves the round-trip and closes the loop the bug opened.

If a sweep run surfaces a file that should genuinely stay as raw
markdown (e.g. an audit log not meant for SSoT propagation), add an
entry to `audit/sweep-exemptions.yml` rather than migrating it. The
sweep script honours exemptions and does not flag them as orphans.

### Stuck-saga closure via `cleo saga reconcile` (T10374 · Saga T10288 / Epic T10293)

When a Saga's docs-related closeout was completed under the saga's
member Epics — every Epic flipped to `status='done'` — but the parent
Saga row itself is still `pending`, the recovery verb is
`cleo saga reconcile <sagaId>`. This is the cron-safe T10121 path
that the ADR-076 / T10113 auto-close path delivers; sagas that pre-date
the auto-close path (T9625 is the canonical example) need an explicit
nudge.

The recipe — use this any time a docs-canon Saga is observably stuck
even though its members have all shipped via `cleo docs add` /
`cleo docs publish`:

1. Verify member-Epic terminality:
   `for E in <memberIds>; do cleo show $E | jq '.data.task.status'; done`.
   Every member must be `done`, `cancelled`, or `archived` before
   reconcile will close the parent. If any member is genuinely stuck,
   close THAT one first (evidence-based per ADR-051) — do NOT cancel
   a member just to satisfy the gate.
2. Verify the SSoT fetch-gate the Saga's acceptance gates on (typically
   a research plan or closure note):
   `cleo docs fetch <slug>` — must return `success: true` with the
   expected bytes.
3. Reconcile: `cleo saga reconcile <sagaId>`. The verb is idempotent
   (re-runs return `action: 'no-op'`) and never modifies member rows.
4. Confirm: `cleo show <sagaId>` — `status` must be `done` and
   `completedAt` populated. The action is appended to
   `.cleo/audit/saga-reconcile.jsonl` for audit.
5. Write a closure-evidence handoff via
   `cleo docs add <taskId> <file> --type handoff --slug <saga>-closure-evidence`
   capturing: member statuses (table), reconcile envelope output,
   sibling-saga sanity check (no cross-saga side effects), and
   ADR-076 + T10113 path validation. The slug `t9625-closure-evidence`
   is the canonical reference.

Regression coverage for this path lives at
`packages/core/src/sagas/__tests__/t9625-closure.test.ts` and locks
three invariants: stuck-saga closure (AC1), sibling-saga isolation
(AC2), and idempotency (AC3). Add a new case there whenever you close
another stuck docs-canon Saga so the recovery pattern stays under
test.

### Docs->memory auto-emit (T9976 · regression-tested by T10375)

Every successful `cleo docs add` fires a fire-and-forget memory observation
into `brain_observations`. The CLI never blocks on this write — a BRAIN
failure cannot fail `docs add` — but the observation is the bridge that
makes `cleo memory find '<slug>'` surface attached docs.

**Title shape**: `"Doc attached: <slug>"` (or `"Doc attached: <attachmentId>"`
when no slug is provided). This is what the FTS index matches on, so the
slug is also a memory-discovery key — not just a docs-lookup key.

**Narrative payload** (the {@link DocAttachmentObservationPayload} contract):

```jsonc
{
  "kind": "doc-attachment",        // discriminator
  "attachmentId": "<id>",          // assigned by the docs store
  "ownerId": "<T#### | SG-#### | …>",
  "slug": "<kebab-slug>",          // omitted only when --slug not passed
  "type": "<docKind>",             // omitted only when --type not passed
  "addedAt": "<ISO 8601 timestamp>"
}
```

The payload is consumed by `cleo memory verify <observationId>` for
round-trip checks against the docs store — see AC3 of the original T9976
suite at `packages/cleo/src/dispatch/domains/__tests__/docs-memory-observation.test.ts`.

**Retroactive sweeps**: when migrating manual `Write`-based docs back into
the SSoT (the T10371 + T10373 pattern), the auto-emit fires uniformly for
the kebab-case slugs the sweep uses (`t<num>-<kebab>`, `adr-<num>-<kebab>`).
Regression coverage lives at
`packages/cleo/src/dispatch/domains/__tests__/docs-memory-observation-retroactive.test.ts`
(T10375). Add a new case to that table whenever you discover a slug shape
not yet under test.

**Anti-pattern**: do NOT write a `cleo memory observe` manually after a
`cleo docs add` — the auto-emit already happened, and the duplicate
observation pollutes the FTS index. No CLI command back-fills that
observation for attachments that pre-date the auto-emit feature: the
`memory backfill-docs` verb planned in T9976 AC4 never shipped. Leave such
attachments as they are rather than hand-writing a duplicate observation.

#### System-managed exemptions (T10368)

Not every `.md` write inside `packages/core/src/**` is a DocKind authoring
path. Release composers, RCASD migration tooling, nexus wiki generators, and
the publish-mirror copier all emit Markdown bytes from inside `core/` but
SHOULD NOT route through `WriterRegistry.write` — they are deterministic
derived artifacts, not user-authored canonical documents.

These legitimate bypasses live in `SYSTEM_MANAGED_ENTRIES` inside
`writer-registry.ts`. Every entry cites an ADR pointer:

```ts
WriterRegistry.listSystemManaged();
// → [
//   { id: 'release.plan-json',          adrRef: 'ADR-028 ...' },
//   { id: 'release.changelog',          adrRef: 'ADR-028 §2.5 ...' },
//   { id: 'lifecycle.rcasd-migration',  adrRef: 'ADR-076 ...' },
//   { id: 'lifecycle.stage-artifact',   adrRef: 'ADR-076 ...' },
//   { id: 'sessions.handoff-markdown',  adrRef: 'ADR-076 ...' },
//   { id: 'nexus.wiki-overview',        adrRef: 'ADR-076 ...' },
//   { id: 'docs.publish-mirror',        adrRef: 'ADR-076 ...' },
// ]

// Path-based exemption lookup — used by the writer-audit test + T10369 lint:
const hit = WriterRegistry.isSystemManaged('.cleo/release/v2026.5.103.plan.json');
// → { id: 'release.plan-json', kind: null, adrRef: 'ADR-028 ...', ... }
```

When adding a NEW `.md` writer inside `packages/core/src/**`, you MUST
either:

1. Route through `WriterRegistry.write({kind, slug, payload})` (the
   canonical path for DocKind authoring), OR
2. Append a new entry to `SYSTEM_MANAGED_ENTRIES` with an ADR pointer
   ratifying the bypass.

The `writer-audit.test.ts` regression test fails when a new `.md` writer
appears without either path. T10369 (next in the epic) promotes this from a
unit test to a full CI lint gate.

### Slug similarity warn (T10361 · closes T10167)

`cleo docs add` runs a fuzzy-match check against existing slugs for the
SAME DocKind at write-time. If the proposed `--slug` is close to an
existing one (default threshold: normalised Levenshtein score ≥ `0.85`,
< `1.0`), the CLI surfaces "did you mean: `cleo docs update <slug>`?"
so an agent does not fork a near-duplicate when an update is the
intent. Exact (`1.0`) matches fall through to the slug-collision path
(`E_SLUG_TAKEN`) — they are NOT covered by this check.

```bash
# Near-duplicate slug — warn mode is the project default.
$ cleo docs add T123 file.md --slug cant-spec --type spec
INFO Similar to 'cantspec' (score 0.89) — did you mean: cleo docs update cantspec? Pass --allow-similar to bypass.
# (write proceeds because mode=warn)

# Same input under `mode: block` (configured in .cleo/canon.yml) — exits 6.
$ cleo docs add T123 file.md --slug cant-spec --type spec
{
  "success": false,
  "error": {
    "code": 6,
    "codeName": "E_SLUG_SIMILARITY",
    "message": "Similar to 'cantspec' (score 0.89) — did you mean: cleo docs update cantspec? Pass --allow-similar to bypass.",
    "fix": "Use `cleo docs update cantspec` if updating, or pass --allow-similar to add as a new doc.",
    "alternatives": [
      { "action": "update 'cantspec' instead", "command": "cleo docs update cantspec" },
      { "action": "bypass the similarity check", "command": "cleo docs add T123 file.md --slug cant-spec --type spec --allow-similar" }
    ]
  }
}

# Intentional add (true near-twin, not an update) — bypass + audit-log.
$ cleo docs add T123 file.md --slug cant-spec --type spec --allow-similar
# appends one JSONL line to .cleo/audit/similar-bypass.jsonl
```

Project-level overrides live in `.cleo/canon.yml`:

```yaml
similarity:
  warnThreshold: 0.85   # 0..1 — score above this triggers the hint
  mode: warn            # 'warn' (default) | 'block'
```

`mode: block` is recommended for CI agents — it surfaces the intent
mismatch as an exit code rather than a silently-printed warning that
non-TTY callers ignore. The `--allow-similar` flag is ALWAYS the
escape hatch and ALWAYS logged.

### Publish to a git-tracked path (when the doc must live on disk)

```bash
cleo docs publish --for T1234 --to docs/feature-x.md
```

Atomic tmp-then-rename. The published file ships in the next commit; the
SSoT blob remains canonical and continues to track future versions.

### Fetch the doc back by slug

```bash
cleo docs fetch feature-x-overview         # latest version
cleo docs versions --for T1234             # list every SHA version
```

Slug-based fetch is the contract used by reviewers, downstream skills, and
the docs graph — never grep the filesystem for the file you just wrote.

### List + sync / versions

```bash
cleo docs list --type spec --project       # canonical: every spec in this project
cleo docs list --task T1234                # canonical: everything attached to a task
cleo docs versions --for T1234             # ADVANCED: list every SHA version
cleo docs sync --from docs/legacy.md --for T1234  # ADVANCED: back-fill on-disk file into SSoT (no --type/--slug; classify with docs add)
```

---

## Deprecated: Direct filesystem

The legacy "write straight to `.cleo/adrs/`, `.cleo/research/`,
`.cleo/agent-outputs/`, or `docs/` and commit" pattern is deprecated.
The drift between the working file and the docs SSoT is real: published
files go stale, types are inferred ad-hoc from path, and slug-based
retrieval becomes impossible. Migrate every doc-type write to
`cleo docs add --type X --slug Y`.

---

## Worktree-Aware Routing (T10389 / ADR-068 amendment §3.1)

When running from an agent-spawned worktree (e.g. under
`~/.local/share/cleo/worktrees/<hash>/<task>/`), `cleo docs add` and
`cleo changeset add` automatically route the SSoT write back to the
canonical project root. The bytes land in the MAIN repo's SSoT — never
inside the worktree.

You can pass file paths relative to the worktree cwd as usual:

```bash
cd ~/.local/share/cleo/worktrees/<hash>/<task>/
cleo docs add T10389 docs/note.md --slug t10389-investigation --type note
# stderr: [T10389] routing SSoT write from worktree cwd <...> → canonical project root <...>
```

The verbs detect a stray `.cleo/tasks.db` inside the worktree
(pre-T9803 leak or rogue write) and emit `E_STRAY_WORKTREE_DB` BEFORE
invoking the DB chokepoint, so the user gets actionable remediation
(`rm -rf <worktree>/.cleo`) instead of the deeper
`E_WT_DB_ISOLATION_VIOLATION` exception.

Suppress the routing log line with `CLEO_QUIET=1` if you need clean
stderr in automation. The fix-pack closes T10353 + T10354 + T10294's
3-guard collision class (`E_PATH_TRAVERSAL` + `E_FILE_ERROR` +
`E_WT_DB_ISOLATION_VIOLATION`).

---

## Core Principle: MAINTAIN, DON'T DUPLICATE

```
BEFORE creating ANY new file, you MUST:
1. Search for existing documentation on the topic
2. Identify the canonical location for this information
3. UPDATE the existing file instead of creating a new one
4. Only create new files when NO suitable location exists
```

---

## Workflow Phases

### Phase 1: Discovery (MANDATORY)

Before writing anything, discover what exists. Prefer the SSoT over `Glob`/`Grep`
when scanning canonical docs — `cleo docs list` returns slug + owner + type
without forcing a filesystem walk.

```bash
# SSoT-first discovery (preferred — canonical six-verb path)
cleo docs list --project                       # all docs for this project
cleo docs list --type {TYPE} --project         # docs filtered by canonical type
cleo docs fetch {SUSPECTED_SLUG}               # check if a slug exists

# Filesystem fallback (only for un-migrated content)
Glob: pattern="docs/**/*.md"
Grep: pattern="{TOPIC_KEYWORDS}" path="docs/"
Grep: pattern="{RELATED_TERMS}" path="docs/" output_mode="files_with_matches"
```

**Use the Context7 MCP** (`resolve-library-id` → `query-docs`) for current library and framework docs.

### Phase 2: Assess

| Question | Action |
|----------|--------|
| Does a doc file for this topic exist? | UPDATE that file |
| Is the info scattered across files? | CONSOLIDATE into canonical location |
| Is there a related doc that should include this? | ADD section to that file |
| Is this truly new with no home? | CREATE minimal new file |

### Phase 3: Write/Update

**Load `references/writing.md`** for content creation.

**For EXISTING files:**
1. Read the current content
2. Identify the correct section for new info
3. Add/update content IN PLACE
4. Preserve existing structure
5. Update any version numbers or dates

**For CONSOLIDATION:**
1. Identify all files with related content
2. Choose the canonical location
3. Move content to canonical file
4. Add deprecation notices to old locations
5. Update cross-references

**For NEW files (last resort):**
1. Confirm no existing location is suitable
2. Follow project's doc structure conventions
3. Add to appropriate docs/ subdirectory
4. Update any index or TOC files
5. Keep minimal - single topic focus

### Phase 4: Review

**Load `references/reviewing.md`** for quality validation.

Checklist:
- [ ] No formal language ("utilize", "offerings", "cannot")
- [ ] "People/companies" not "users"
- [ ] No excessive exclamation points
- [ ] Important information leads, not buried
- [ ] No verbose text without value
- [ ] Headings state the point
- [ ] Descriptive link text (never "here")
- [ ] No "easy" or "simple"
- [ ] Code examples actually work

---

## Anti-Duplication Checklist

Before completing, verify:

- [ ] Searched for existing docs on this topic via `cleo docs list` first
- [ ] Did NOT create a file that duplicates existing content
- [ ] Updated existing file if one existed
- [ ] Added deprecation notice if consolidating
- [ ] Cross-references are updated
- [ ] No orphaned documentation created
- [ ] Every canonical-type write went through `cleo docs add --type X --slug Y`
- [ ] Slug retrieval verified via `cleo docs fetch <slug>` before declaring done

---

## Task System Integration

@skills/_shared/task-system-integration.md

### Task Workflow

```bash
# 1. Read task details
cleo show {TASK_ID}

# 2. Start task. If it fails with E_SESSION_UNBOUND (spawned workers and
#    subagents are already bound), run
#    'cleo session start --scope global --name "<what you are doing>"'
#    or 'cleo session resume <id>' first, then retry.
cleo start {TASK_ID}

# 3. Execute documentation workflow (phases 1-4)

# 4. Complete task when done
cleo complete {TASK_ID}

# 5. Link research if applicable
cleo research link {TASK_ID} {RESEARCH_ID}
```

---

## Subagent Protocol

@skills/_shared/subagent-protocol-base.md

### Output Requirements

1. MUST record documentation output with `cleo docs add {{TASK_ID}} --content - --type <kind> --slug {{TOPIC_SLUG}}` (never a raw file under `.cleo/agent-outputs/`)
2. MUST record ONE manifest entry: `cleo manifest append --entry '<entry JSON>'` (the flat manifest file is retired, ADR-027)
3. MUST return ONLY: "Documentation complete. Manifest appended to pipeline_manifest."
4. MUST NOT return documentation content in response

### Output File Format

Record it with `cleo docs add {{TASK_ID}} --content - --type <kind> --slug {{TOPIC_SLUG}}`, using this body:

```markdown
# Documentation Update: {TITLE}

**Date**: {DATE} | **Agent**: ct-documentor | **Status**: complete

---

## Summary

{What was updated and why}

## Changes Made

### File: {path/to/file.md}
- {Change 1}
- {Change 2}

## Files NOT Created (Avoided Duplication)

- {Considered creating X but updated Y instead}
- {Found existing coverage in Z}

## Verification

- [ ] Changes don't duplicate existing content
- [ ] Cross-references updated
- [ ] Examples tested
- [ ] Style guide compliance verified via `references/reviewing.md`
```

### Manifest Entry

Record it with `cleo manifest append --entry '<entry JSON>'` (the flat manifest file is retired, ADR-027):

```json
{"id":"docs-{TOPIC}-{DATE}","file":"{DATE}_docs-{TOPIC}.md","title":"Documentation Update: {TITLE}","date":"{DATE}","status":"complete","agent_type":"documentation","topics":["documentation","{topic}"],"key_findings":["Updated {file} with {change}","Consolidated {topic} docs into {canonical-location}","Avoided duplication by updating existing {file}"],"actionable":false,"needs_followup":[],"linked_tasks":["{TASK_ID}"]}
```

---

## Completion Requirements

- [ ] Discovery phase completed (searched existing docs via `cleo docs list`)
- [ ] Core principle followed (maintain, don't duplicate)
- [ ] Coordinator pattern followed: every doc-type routed to its owning skill
- [ ] Every canonical-type write went through `cleo docs add --type X --slug Y`
- [ ] `references/writing.md` followed for `note`/`llm-readme` content
- [ ] `/ct-spec-writer` invoked when writing specs (REQ-XXX requirements)
- [ ] `/ct-adr-recorder` invoked when promoting consensus → ADR
- [ ] `references/reviewing.md` applied for quality validation
- [ ] Anti-duplication checklist verified
- [ ] Output file written with "Files NOT Created" section
- [ ] Manifest entry appended
- [ ] Task completed via `cleo complete`

---

## See references/

Progressive disclosure — load on demand only:

- `references/chain-orchestration.md` — when to invoke lookup/write/review, input shapes, review loop budget
- `references/doc-types-and-templates.md` — Diátaxis grid plus CLEO-native (ADR, agent-output, skill) templates
- `references/style-coordination.md` — tone pillars, forbidden phrases, link/code/table discipline
- `references/anti-patterns.md` — twelve documentation coordination failure modes

Files in this skill

  • SKILL.md42 KB
  • references/anti-patterns.md7 KB
  • references/chain-orchestration.md6.1 KB
  • references/doc-types-and-templates.md5.9 KB
  • references/style-coordination.md5.9 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.