@cleocode/skills 2026.5.111 → 2026.5.113

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.5.111",
3
+ "version": "2026.5.113",
4
4
  "description": "CLEO skill definitions - bundled with CLEO monorepo",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -1,11 +1,15 @@
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. Coordinates ct-docs-lookup, ct-docs-write, ct-docs-review, 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.5.0
4
+ version: 3.11.0
5
5
  tier: 3
6
6
  core: false
7
7
  category: specialist
8
8
  protocol: null
9
+ metadata:
10
+ version: 3.11.0
11
+ lastReviewed: 2026-05-24
12
+ stability: stable
9
13
  dependencies:
10
14
  - ct-docs-lookup
11
15
  - ct-docs-write
@@ -264,6 +268,222 @@ Contract for the docs-add path:
264
268
  `data.slug`, `data.attachmentId`, and `data.sha256` — round-trip
265
269
  identical to what `cleo changeset add` emits.
266
270
 
271
+ #### CI gate — DocKind Writer Uniqueness (T10369)
272
+
273
+ `scripts/lint-dockind-writer-uniqueness.mjs` (CI job:
274
+ `DocKind Writer Uniqueness (T10369)`) enforces the
275
+ WriterRegistry invariants at PR-time. It refuses to merge a PR that:
276
+
277
+ 1. Adds a new entry to `BUILTIN_DOC_KINDS` (in
278
+ `packages/contracts/src/docs-taxonomy.ts`) without a matching
279
+ descriptor in `writer-registry.ts` (`dockind-coverage-missing`).
280
+ 2. Declares more than one descriptor for the same DocKind
281
+ (`dockind-coverage-collision` — the registry itself throws at module
282
+ load too, this gate surfaces it earlier in CI).
283
+ 3. Has a `mode: 'ssot-first'` descriptor that does NOT match
284
+ `.cleo/canon.yml`'s `canonicalHome` for the same kind, or vice versa
285
+ (`canon-yml-ssot-first-drift`).
286
+ 4. Adds a NEW raw `writeFileSync(path.md, …)` / `writeFile(path.md, …)`
287
+ call inside `packages/core/src/**` that is not in
288
+ `.lint-dockind-writer-baseline.json` (`unregistered-md-write`).
289
+
290
+ Schema-parity rules (#1-#3) are ALWAYS strict — there is no baseline.
291
+ The unregistered-md-write rule runs in baseline mode by default; count
292
+ decreases always pass, count increases fail. The two legitimate
293
+ non-DocKind `.md` writers (`packages/core/src/sessions/handoff-markdown.ts`
294
+ for session snapshots and `packages/core/src/changesets/writer.ts` for
295
+ the canonical `changeset` DocKind) are allowlisted in the script.
296
+
297
+ Per-line opt-out (use sparingly): append
298
+ `// dockind-writer-allowed: <reason>` on the writeFile line.
299
+
300
+ #### Audit complement — manual-write sweep (T10372)
301
+
302
+ `scripts/sweep-manual-doc-writes.mjs` (CI job:
303
+ `Manual Write Sweep (T10372)`) is the read-only audit counterpart to
304
+ the writer-uniqueness lint. Where T10369 prevents *new* raw `.md`
305
+ writers from landing in `packages/core/src/**`, this sweep walks every
306
+ `*.md` file *already* added under `.cleo/canon.yml`'s `rawMdPaths`
307
+ directories since the T9791 docs-import cutoff (commit `251814e86`)
308
+ and classifies each one against the docs SSoT:
309
+
310
+ | Remediation | Meaning | Fix |
311
+ |---|---|---|
312
+ | `in-sync` | File SHA matches a blob already in the SSoT — bytes are tracked. | None. |
313
+ | `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. |
314
+ | `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>`. |
315
+ | `deleted` | File was added since the cutoff but no longer exists on disk. | Informational only — does not count toward `unresolved`. |
316
+
317
+ Each run writes a timestamped report to
318
+ `audit/manual-write-sweep-<date>.json` and prints the summary block to
319
+ stdout. The CI job uploads the report as a workflow artefact on every
320
+ run and is wired with `continue-on-error: true` initially so the
321
+ existing orphan corpus does not break PRs. Saga T10288 / Epic T10293
322
+ E5.3 closes the orphan migration; the gate flips strict after that
323
+ lands.
324
+
325
+ ```bash
326
+ # Local invocation — uses the globally-installed `cleo` on PATH.
327
+ node scripts/sweep-manual-doc-writes.mjs
328
+
329
+ # CI / monorepo build — point at the just-built local CLI bundle.
330
+ node scripts/sweep-manual-doc-writes.mjs \
331
+ --cleo-bin "node packages/cleo/dist/cli/index.js" \
332
+ --allow-unresolved
333
+ ```
334
+
335
+ Exit codes: `0` (clean OR `--allow-unresolved`), `1` (at least one
336
+ `orphan` or `drift` entry), `2` (canon.yml parse failure, git not
337
+ available, SSoT query failed).
338
+
339
+ ### T10179 + T10203 manual-write migration (T10371)
340
+
341
+ Saga T10176's two known raw-write workarounds are normalised:
342
+
343
+ | Original file | SSoT slug | Type | Notes |
344
+ |---|---|---|---|
345
+ | `docs/research/t10179-executor-probe-result.md` | `t10179-executor-probe` | research | in-sync via earlier T9791 import — verified by SHA. |
346
+ | `.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. |
347
+ | `.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. |
348
+
349
+ Round-trip parity is regression-locked by
350
+ `packages/core/src/docs/__tests__/manual-write-migration.test.ts`. The
351
+ test embeds the canonical bytes inline and asserts that
352
+ `createAttachmentStore().put(...) → findBySlug(...)` returns the same
353
+ SHA-256 it started with. Any future migration that silently rewrites or
354
+ recompresses these blobs fails the test.
355
+
356
+ ### Sweep-driven remediation loop (T10373)
357
+
358
+ T10371 only covers the *known* manual-write set declared in the original
359
+ Saga T10176 disposition. The T10372 sweep surfaces *every* orphan
360
+ remaining under `rawMdPaths` at the moment it runs. T10373 closes the
361
+ loop by consuming the sweep report and migrating each orphan into the
362
+ SSoT using the same `cleo docs add --slug` pattern T10371 established.
363
+
364
+ The recurring pattern (use this any time the sweep flags fresh
365
+ orphans):
366
+
367
+ 1. Run the sweep: `node scripts/sweep-manual-doc-writes.mjs`. The
368
+ report lands at `audit/manual-write-sweep-<date>.json`.
369
+ 2. For each `orphan` entry, derive the migration tuple:
370
+ - `--type` from the file's parent directory (`.cleo/adrs/` → `adr`,
371
+ `.cleo/research/` → `research`, `.cleo/agent-outputs/` →
372
+ `handoff` or `note` based on content, `.cleo/rcasd/` → `rcasd`).
373
+ - `--slug` from the filename — lowercase, kebab-case, no
374
+ extension (e.g. `ADR-085-cross-db-invariants.md` →
375
+ `adr-085-cross-db-invariants`).
376
+ - `<owner-id>` from the file's frontmatter `task:` field if
377
+ present, otherwise from `cleo find "<filename-keyword>"`.
378
+ 3. Run `cleo docs add <ownerId> <file> --type <kind> --slug <slug>
379
+ --desc "<sweep-remediation context>"`. The `--desc` should
380
+ reference the originating task ID so future operators can trace
381
+ the migration.
382
+ 4. Verify via `cleo docs fetch <slug>` and re-run the sweep — the
383
+ `orphan` count MUST drop by the number of files migrated.
384
+ 5. Add the new (slug, sha256, type, ownerId) row to a
385
+ round-trip parity test alongside the T10371 set. The canonical
386
+ example lives at
387
+ `packages/core/src/docs/__tests__/sweep-remediation.test.ts`.
388
+
389
+ T10373 migrated five orphans this way: `ADR-083`, `ADR-085`,
390
+ `T10268-saga-closeout`, `t10292-e4-cli-verb-matrix`, and
391
+ `t10292-e4-sdk-import-edges`. The last two were direct fallout from
392
+ the pre-T10389 worktree-unreachable bug — T10353 and T10354 workers
393
+ fell back to raw filesystem writes because `cleo docs add` rejected
394
+ inside their spawned worktrees. Re-publishing the bytes via the SSoT
395
+ proves the round-trip and closes the loop the bug opened.
396
+
397
+ If a sweep run surfaces a file that should genuinely stay as raw
398
+ markdown (e.g. an audit log not meant for SSoT propagation), add an
399
+ entry to `audit/sweep-exemptions.yml` rather than migrating it. The
400
+ sweep script honours exemptions and does not flag them as orphans.
401
+
402
+ ### Stuck-saga closure via `cleo saga reconcile` (T10374 · Saga T10288 / Epic T10293)
403
+
404
+ When a Saga's docs-related closeout was completed under the saga's
405
+ member Epics — every Epic flipped to `status='done'` — but the parent
406
+ Saga row itself is still `pending`, the recovery verb is
407
+ `cleo saga reconcile <sagaId>`. This is the cron-safe T10121 path
408
+ that the ADR-076 / T10113 auto-close path delivers; sagas that pre-date
409
+ the auto-close path (T9625 is the canonical example) need an explicit
410
+ nudge.
411
+
412
+ The recipe — use this any time a docs-canon Saga is observably stuck
413
+ even though its members have all shipped via `cleo docs add` /
414
+ `cleo docs publish`:
415
+
416
+ 1. Verify member-Epic terminality:
417
+ `for E in <memberIds>; do cleo show $E | jq '.data.task.status'; done`.
418
+ Every member must be `done`, `cancelled`, or `archived` before
419
+ reconcile will close the parent. If any member is genuinely stuck,
420
+ close THAT one first (evidence-based per ADR-051) — do NOT cancel
421
+ a member just to satisfy the gate.
422
+ 2. Verify the SSoT fetch-gate the Saga's acceptance gates on (typically
423
+ a research plan or closure note):
424
+ `cleo docs fetch <slug>` — must return `success: true` with the
425
+ expected bytes.
426
+ 3. Reconcile: `cleo saga reconcile <sagaId>`. The verb is idempotent
427
+ (re-runs return `action: 'no-op'`) and never modifies member rows.
428
+ 4. Confirm: `cleo show <sagaId>` — `status` must be `done` and
429
+ `completedAt` populated. The action is appended to
430
+ `.cleo/audit/saga-reconcile.jsonl` for audit.
431
+ 5. Write a closure-evidence handoff via
432
+ `cleo docs add <taskId> <file> --type handoff --slug <saga>-closure-evidence`
433
+ capturing: member statuses (table), reconcile envelope output,
434
+ sibling-saga sanity check (no cross-saga side effects), and
435
+ ADR-076 + T10113 path validation. The slug `t9625-closure-evidence`
436
+ is the canonical reference.
437
+
438
+ Regression coverage for this path lives at
439
+ `packages/core/src/sagas/__tests__/t9625-closure.test.ts` and locks
440
+ three invariants: stuck-saga closure (AC1), sibling-saga isolation
441
+ (AC2), and idempotency (AC3). Add a new case there whenever you close
442
+ another stuck docs-canon Saga so the recovery pattern stays under
443
+ test.
444
+
445
+ ### Docs->memory auto-emit (T9976 · regression-tested by T10375)
446
+
447
+ Every successful `cleo docs add` fires a fire-and-forget memory observation
448
+ into `brain_observations`. The CLI never blocks on this write — a BRAIN
449
+ failure cannot fail `docs add` — but the observation is the bridge that
450
+ makes `cleo memory find '<slug>'` surface attached docs.
451
+
452
+ **Title shape**: `"Doc attached: <slug>"` (or `"Doc attached: <attachmentId>"`
453
+ when no slug is provided). This is what the FTS index matches on, so the
454
+ slug is also a memory-discovery key — not just a docs-lookup key.
455
+
456
+ **Narrative payload** (the {@link DocAttachmentObservationPayload} contract):
457
+
458
+ ```jsonc
459
+ {
460
+ "kind": "doc-attachment", // discriminator
461
+ "attachmentId": "<id>", // assigned by the docs store
462
+ "ownerId": "<T#### | SG-#### | …>",
463
+ "slug": "<kebab-slug>", // omitted only when --slug not passed
464
+ "type": "<docKind>", // omitted only when --type not passed
465
+ "addedAt": "<ISO 8601 timestamp>"
466
+ }
467
+ ```
468
+
469
+ The payload is consumed by `cleo memory verify <observationId>` for
470
+ round-trip checks against the docs store — see AC3 of the original T9976
471
+ suite at `packages/cleo/src/dispatch/domains/__tests__/docs-memory-observation.test.ts`.
472
+
473
+ **Retroactive sweeps**: when migrating manual `Write`-based docs back into
474
+ the SSoT (the T10371 + T10373 pattern), the auto-emit fires uniformly for
475
+ the kebab-case slugs the sweep uses (`t<num>-<kebab>`, `adr-<num>-<kebab>`).
476
+ Regression coverage lives at
477
+ `packages/cleo/src/dispatch/domains/__tests__/docs-memory-observation-retroactive.test.ts`
478
+ (T10375). Add a new case to that table whenever you discover a slug shape
479
+ not yet under test.
480
+
481
+ **Anti-pattern**: do NOT write a `cleo memory observe` manually after a
482
+ `cleo docs add` — the auto-emit already happened, and the duplicate
483
+ observation pollutes the FTS index. Use `cleo memory backfill-docs` (AC4
484
+ of T9976) only to repair attachments that pre-date the auto-emit feature
485
+ or were written outside the SSoT.
486
+
267
487
  ### Slug similarity warn (T10361 · closes T10167)
268
488
 
269
489
  `cleo docs add` runs a fuzzy-match check against existing slugs for the