@cleocode/skills 2026.5.83 → 2026.5.86
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/_shared/__tests__/lifecycle-protocol-reconcile.test.ts +112 -0
- package/skills/_shared/__tests__/loom-adr-links.test.ts +163 -0
- package/skills/_shared/__tests__/loom-stage-coverage.test.ts +167 -0
- package/skills/ct-adr-recorder/SKILL.md +92 -0
- package/skills/ct-adr-recorder/__tests__/skill-adr-recorder.test.ts +65 -0
- package/skills/ct-consensus-voter/SKILL.md +14 -0
- package/skills/ct-contribution/SKILL.md +80 -0
- package/skills/ct-docs-lookup/SKILL.md +116 -1
- package/skills/ct-docs-lookup/references/ctx7-workflow.md +198 -0
- package/skills/ct-docs-lookup/references/library-id-resolution.md +217 -0
- package/skills/ct-docs-lookup/references/version-specific-docs.md +220 -0
- package/skills/ct-docs-review/SKILL.md +133 -1
- package/skills/ct-docs-review/__tests__/skill-docs-review.test.ts +53 -0
- package/skills/ct-docs-review/references/inline-comment-patterns.md +268 -0
- package/skills/ct-docs-review/references/pr-review-mode.md +270 -0
- package/skills/ct-docs-review/references/style-violations.md +341 -0
- package/skills/ct-docs-write/SKILL.md +157 -1
- package/skills/ct-docs-write/__tests__/skill-docs-write.test.ts +55 -0
- package/skills/ct-docs-write/references/audience-targeting.md +305 -0
- package/skills/ct-docs-write/references/cleo-style-guide.md +234 -0
- package/skills/ct-docs-write/references/markdown-patterns.md +329 -0
- package/skills/ct-documentor/SKILL.md +11 -0
- package/skills/ct-documentor/references/anti-patterns.md +216 -0
- package/skills/ct-documentor/references/chain-orchestration.md +194 -0
- package/skills/ct-documentor/references/doc-types-and-templates.md +301 -0
- package/skills/ct-documentor/references/style-coordination.md +195 -0
- package/skills/ct-epic-architect/SKILL.md +15 -0
- package/skills/ct-ivt-looper/SKILL.md +32 -0
- package/skills/ct-release-orchestrator/SKILL.md +16 -0
- package/skills/ct-research-agent/SKILL.md +24 -0
- package/skills/ct-research-agent/references/anti-patterns.md +154 -0
- package/skills/ct-research-agent/references/citation-and-evidence.md +140 -0
- package/skills/ct-research-agent/references/source-strategy.md +116 -0
- package/skills/ct-research-agent/references/triggers-and-routing.md +93 -0
- package/skills/ct-skill-validator/SKILL.md +19 -0
- package/skills/ct-skill-validator/scripts/check_depth.py +306 -0
- package/skills/ct-spec-writer/SKILL.md +86 -1
- package/skills/ct-spec-writer/__tests__/skill-spec-writer.test.ts +60 -0
- package/skills/ct-spec-writer/references/anti-patterns.md +176 -0
- package/skills/ct-spec-writer/references/rfc2119-language.md +138 -0
- package/skills/ct-spec-writer/references/spec-templates.md +233 -0
- package/skills/ct-spec-writer/references/traceability-matrix.md +145 -0
- package/skills/ct-task-executor/SKILL.md +25 -0
- package/skills/ct-task-executor/references/acceptance-criteria-mapping.md +163 -0
- package/skills/ct-task-executor/references/anti-patterns.md +201 -0
- package/skills/ct-task-executor/references/common-failures.md +193 -0
- package/skills/ct-task-executor/references/evidence-and-gates.md +179 -0
- package/skills/ct-task-executor/references/implementation-patterns.md +160 -0
- package/skills/ct-validator/SKILL.md +44 -0
- package/skills/ct-validator/references/anti-patterns.md +194 -0
- package/skills/ct-validator/references/compliance-reports.md +199 -0
- package/skills/ct-validator/references/schema-checking.md +191 -0
- package/skills/ct-validator/references/validation-modes.md +185 -0
- package/skills/manifest.json +82 -16
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Source Strategy
|
|
2
|
+
|
|
3
|
+
How to pick sources, what order to query them in, and how to bound the search
|
|
4
|
+
so that the research task completes within its token budget. The skill has
|
|
5
|
+
three primary source channels — web, Context7, and the local codebase — and
|
|
6
|
+
each carries different reliability and currency trade-offs.
|
|
7
|
+
|
|
8
|
+
## Source Hierarchy
|
|
9
|
+
|
|
10
|
+
| Tier | Source | Strength | Weakness |
|
|
11
|
+
|------|--------|----------|----------|
|
|
12
|
+
| 1 | Local codebase | Ground truth for the project | Reflects past decisions, not future ones |
|
|
13
|
+
| 2 | Context7 (`ctx7 docs`) | Current official library docs | Limited to libraries published to Context7 |
|
|
14
|
+
| 3 | Web search | Breadth, recency, community wisdom | Variable signal-to-noise |
|
|
15
|
+
| 4 | LLM general knowledge | Conceptual framing | Stale, hallucination-prone |
|
|
16
|
+
|
|
17
|
+
Always query top-down. Codebase first — the answer may already exist as
|
|
18
|
+
prior art. Context7 second when a specific library or framework is named.
|
|
19
|
+
Web third when the question is open-ended. LLM general knowledge SHOULD be
|
|
20
|
+
used only to frame the question, never as a citable source.
|
|
21
|
+
|
|
22
|
+
## Codebase Search Patterns
|
|
23
|
+
|
|
24
|
+
The skill operates in a worktree; the entire repository is reachable via
|
|
25
|
+
`Grep`, `Glob`, and `Read`. Use these patterns to find prior art quickly.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# Find existing ADRs on the topic
|
|
29
|
+
Grep: pattern="<keyword>" path=".cleo/adrs/" output_mode="files_with_matches"
|
|
30
|
+
|
|
31
|
+
# Find prior research notes
|
|
32
|
+
Grep: pattern="<keyword>" path=".cleo/agent-outputs/" output_mode="files_with_matches"
|
|
33
|
+
|
|
34
|
+
# Find BRAIN decisions/patterns/observations on the topic
|
|
35
|
+
cleo memory find "<keyword>"
|
|
36
|
+
|
|
37
|
+
# Find related tasks
|
|
38
|
+
cleo find "<keyword>"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
When the topic touches code symbols, also use the GitNexus tools — they
|
|
42
|
+
return execution flows, callers, and impact rings that grep cannot surface.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
gitnexus_query({query: "<concept>"}) # process-grouped flows
|
|
46
|
+
gitnexus_context({name: "<symbol>"}) # 360 view of a symbol
|
|
47
|
+
gitnexus_impact({target: "<symbol>"}) # blast radius
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Context7 (ctx7) Workflow
|
|
51
|
+
|
|
52
|
+
For any question that names a library, framework, SDK, or CLI tool, use the
|
|
53
|
+
`ctx7` CLI before the open web. The workflow is two-step.
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# Step 1 — resolve the library ID
|
|
57
|
+
npx ctx7@latest library "<library-name>" "<user-question>"
|
|
58
|
+
|
|
59
|
+
# Step 2 — fetch docs for the resolved ID
|
|
60
|
+
npx ctx7@latest docs <libraryId> "<user-question>"
|
|
61
|
+
|
|
62
|
+
# Optional — retry with sandboxed agents pulling source + web
|
|
63
|
+
npx ctx7@latest docs <libraryId> "<user-question>" --research
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The official library name is required — pass `"Next.js"` not `"nextjs"`.
|
|
67
|
+
Version-specific docs use the `/org/project/version` form (e.g.
|
|
68
|
+
`/vercel/next.js/v14.3.0`). Pass the user's full question as the query —
|
|
69
|
+
specific queries return better matches than single words.
|
|
70
|
+
|
|
71
|
+
## Web Search Tactics
|
|
72
|
+
|
|
73
|
+
Web search is the most variable channel. Apply these filters to keep the
|
|
74
|
+
signal high.
|
|
75
|
+
|
|
76
|
+
- Prefer the canonical source (official docs, GitHub README, RFC) over
|
|
77
|
+
blog posts, Medium, StackOverflow.
|
|
78
|
+
- Prefer recent results — append `2026`, `2025`, or the current year when
|
|
79
|
+
the topic is moving fast (LLM APIs, build tools, framework migrations).
|
|
80
|
+
- Cross-check at least two sources before stating a fact. A single blog
|
|
81
|
+
post is a lead, not a finding.
|
|
82
|
+
- Avoid AI-generated content farms — sites that publish hundreds of
|
|
83
|
+
thin "guides" daily are not citable.
|
|
84
|
+
- Strip tracking parameters from URLs when citing — they break and they
|
|
85
|
+
leak provenance.
|
|
86
|
+
|
|
87
|
+
## Time-Boxing
|
|
88
|
+
|
|
89
|
+
Research is unbounded by nature; the skill MUST self-limit. Use this
|
|
90
|
+
schedule when the task does not specify otherwise.
|
|
91
|
+
|
|
92
|
+
| Topic complexity | Time budget | Sources to query |
|
|
93
|
+
|------------------|-------------|------------------|
|
|
94
|
+
| Single library/API question | 5 min | Context7 only |
|
|
95
|
+
| "What is the current best practice for X" | 15 min | Web + Context7 |
|
|
96
|
+
| "Compare options A, B, C for use-case Y" | 30 min | Web + Context7 + codebase |
|
|
97
|
+
| "Audit current implementation against state of the art" | 60 min | All sources |
|
|
98
|
+
|
|
99
|
+
When the budget is exhausted, write up what was found with `status: partial`
|
|
100
|
+
and list remaining questions in `needs_followup`. Partial research is more
|
|
101
|
+
useful than abandoned research.
|
|
102
|
+
|
|
103
|
+
## Citation Format
|
|
104
|
+
|
|
105
|
+
Every finding MUST carry a source. Acceptable formats:
|
|
106
|
+
|
|
107
|
+
```markdown
|
|
108
|
+
- According to [the Next.js routing docs](https://nextjs.org/docs/app), ...
|
|
109
|
+
- Context7 (`/vercel/next.js/v15`) confirms that ...
|
|
110
|
+
- See `.cleo/adrs/ADR-065-release-pipeline.md` §3 for prior decision on ...
|
|
111
|
+
- The current implementation at `packages/cleo/src/commands/release.ts:42` ...
|
|
112
|
+
- BRAIN observation `O-mpd07uma-0` records the pub1-diagnoser refusal pattern.
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Unsourced claims SHOULD be flagged with `[unsourced]` or moved to a
|
|
116
|
+
`Hypotheses` section. The reader must always be able to verify.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Triggers and Routing
|
|
2
|
+
|
|
3
|
+
When to load `ct-research-agent`, what tasks it owns, and how it hands off
|
|
4
|
+
to neighboring skills in the RCASD-IVTR+C pipeline. The skill operates as the
|
|
5
|
+
`research` protocol within the orchestrator's stage taxonomy and is the first
|
|
6
|
+
skill invoked when an Epic enters its research lifecycle stage.
|
|
7
|
+
|
|
8
|
+
## Primary Triggers
|
|
9
|
+
|
|
10
|
+
Load this skill when the orchestrator (or the user) requests any of the
|
|
11
|
+
following — the dispatch matrix in `manifest.json` enumerates the canonical
|
|
12
|
+
keyword set.
|
|
13
|
+
|
|
14
|
+
| Trigger phrase | Routing decision |
|
|
15
|
+
|----------------|------------------|
|
|
16
|
+
| "research <topic>" | Direct invocation — proceed with multi-source pull |
|
|
17
|
+
| "investigate <area>" | Same as research; emphasize codebase + web blend |
|
|
18
|
+
| "explore options for <decision>" | Treat as research → ct-consensus-voter |
|
|
19
|
+
| "what does the literature say about X" | Web + Context7 lookup, no codebase |
|
|
20
|
+
| "audit current <subsystem> implementation" | Codebase-only research; skip web |
|
|
21
|
+
| "compare libraries for <use-case>" | Web + Context7 with `--research` flag |
|
|
22
|
+
| "due-diligence on <vendor/tool>" | Web + reputation + license scan |
|
|
23
|
+
|
|
24
|
+
The orchestrator's `cleo orchestrate spawn` prompt always carries a
|
|
25
|
+
`stage: research` hint when the task's `pipelineStage` field equals
|
|
26
|
+
`research`. The skill SHOULD honor that hint and SHOULD NOT advance the
|
|
27
|
+
pipeline stage on its own — that is the orchestrator's responsibility once
|
|
28
|
+
the research manifest entry is appended.
|
|
29
|
+
|
|
30
|
+
## Anti-Triggers (Do NOT Load)
|
|
31
|
+
|
|
32
|
+
The skill MUST NOT be loaded for the following requests because they belong
|
|
33
|
+
to sibling skills that have narrower context budgets and stricter contracts.
|
|
34
|
+
|
|
35
|
+
| Request | Correct skill |
|
|
36
|
+
|---------|---------------|
|
|
37
|
+
| "fix this failing test" | `ct-task-executor` |
|
|
38
|
+
| "write the spec for X" | `ct-spec-writer` |
|
|
39
|
+
| "validate this implementation against the spec" | `ct-validator` |
|
|
40
|
+
| "decompose this epic into tasks" | `ct-epic-architect` |
|
|
41
|
+
| "look up Next.js 15 middleware API" | `ct-docs-lookup` (single-library fetch) |
|
|
42
|
+
| "decide between A and B" (no investigation needed) | `ct-consensus-voter` |
|
|
43
|
+
| "explain this function" | (no skill — direct read suffices) |
|
|
44
|
+
|
|
45
|
+
A useful heuristic: if the user already knows the answer and just wants it
|
|
46
|
+
written down, route to `ct-spec-writer` or `ct-docs-write`. Research is for
|
|
47
|
+
when the answer is not yet known.
|
|
48
|
+
|
|
49
|
+
## Routing to Downstream Skills
|
|
50
|
+
|
|
51
|
+
Research outputs typically feed one of these next stages. The manifest
|
|
52
|
+
`chains_to` array enumerates the legal handoffs.
|
|
53
|
+
|
|
54
|
+
1. **`ct-spec-writer`** — when findings produce testable requirements.
|
|
55
|
+
Pass the research file path and the synthesized recommendations as
|
|
56
|
+
spec input. Use this when the task description contains "spec",
|
|
57
|
+
"contract", "RFC", or "protocol".
|
|
58
|
+
2. **`ct-epic-architect`** — when findings change the scope estimate of
|
|
59
|
+
an Epic. The architect re-decomposes based on the new evidence.
|
|
60
|
+
3. **`ct-consensus-voter`** — when research surfaces two-or-more viable
|
|
61
|
+
options with comparable trade-offs. The voter resolves the choice
|
|
62
|
+
under HITL when confidence < 0.5.
|
|
63
|
+
4. **`ct-task-executor`** — only when the research itself contains an
|
|
64
|
+
actionable next step that does not require a spec (e.g. "bump the
|
|
65
|
+
library version" or "delete deprecated path"). Rare.
|
|
66
|
+
|
|
67
|
+
## Decision Tree
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
Is the answer already known and just needs documentation?
|
|
71
|
+
├── YES → ct-docs-write
|
|
72
|
+
└── NO → continue
|
|
73
|
+
│
|
|
74
|
+
Is this a single-library API question?
|
|
75
|
+
├── YES → ct-docs-lookup
|
|
76
|
+
└── NO → ct-research-agent (this skill)
|
|
77
|
+
│
|
|
78
|
+
After research, do findings produce requirements?
|
|
79
|
+
├── YES → ct-spec-writer next
|
|
80
|
+
└── NO → continue
|
|
81
|
+
│
|
|
82
|
+
Do findings produce a decision between options?
|
|
83
|
+
├── YES → ct-consensus-voter next
|
|
84
|
+
└── NO → return manifest only, no chain
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Stage Hint Compliance
|
|
88
|
+
|
|
89
|
+
The orchestrator's `pipeline_manifest` table tracks per-task stage
|
|
90
|
+
progression. The research skill MUST append exactly one entry with
|
|
91
|
+
`agent_type: "research"` and MUST NOT mutate stage fields directly. If
|
|
92
|
+
findings indicate a stage advance is warranted, the skill SHOULD include
|
|
93
|
+
that suggestion in `needs_followup` so the orchestrator can act on it.
|
|
@@ -36,8 +36,27 @@ python ${CLAUDE_SKILL_DIR}/scripts/audit_body.py <skill-dir>
|
|
|
36
36
|
|
|
37
37
|
# Manifest alignment check:
|
|
38
38
|
python ${CLAUDE_SKILL_DIR}/scripts/check_manifest.py <skill-dir> <manifest.json>
|
|
39
|
+
|
|
40
|
+
# Progressive-disclosure depth check (T9684 — CI gate):
|
|
41
|
+
python ${CLAUDE_SKILL_DIR}/scripts/check_depth.py <skill-dir>
|
|
42
|
+
|
|
43
|
+
# Repo-wide depth sweep:
|
|
44
|
+
python ${CLAUDE_SKILL_DIR}/scripts/check_depth.py <repo-root> --all
|
|
39
45
|
```
|
|
40
46
|
|
|
47
|
+
**Depth rule (T9684):** A skill PASSES when ANY of:
|
|
48
|
+
|
|
49
|
+
- SKILL.md body has ≥ 100 content lines, OR
|
|
50
|
+
- `references/` subdir has ≥ 3 markdown files, OR
|
|
51
|
+
- `manifest.json` `references[]` array enumerates ≥ 3 files (all on disk)
|
|
52
|
+
|
|
53
|
+
Pre-existing stubs are allowlisted with follow-up task IDs in
|
|
54
|
+
`scripts/check_depth.py::ALLOWLIST`. Gold-standard skills:
|
|
55
|
+
`ct-orchestrator` (9 refs) and `ct-skill-creator` (7 refs).
|
|
56
|
+
|
|
57
|
+
The depth check runs on every PR touching `packages/skills/skills/**`
|
|
58
|
+
via `.github/workflows/skills-depth-check.yml`.
|
|
59
|
+
|
|
41
60
|
**Iteration rule**: If errors > 0, fix them in the skill's SKILL.md, re-run `validate.py`.
|
|
42
61
|
Repeat until errors = 0. Do not proceed to Phase 2 while errors remain.
|
|
43
62
|
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
CLEO Skill Depth Check — progressive-disclosure-depth rule (T9684).
|
|
4
|
+
|
|
5
|
+
Fails when a skill's SKILL.md is below the depth threshold AND it has
|
|
6
|
+
no references/ subdir with at least the manifest-declared reference
|
|
7
|
+
files. The gold standard is ct-orchestrator (9 references) and
|
|
8
|
+
ct-skill-creator (7 references); the rule is calibrated to flag
|
|
9
|
+
stubs without forcing every skill to that depth.
|
|
10
|
+
|
|
11
|
+
Rule logic:
|
|
12
|
+
PASS when ANY of:
|
|
13
|
+
- SKILL.md body has >= MIN_BODY_LINES content lines
|
|
14
|
+
- references/ subdir exists with >= MIN_REF_FILES files
|
|
15
|
+
- manifest.json references[] array populated with file paths that
|
|
16
|
+
all exist on disk
|
|
17
|
+
|
|
18
|
+
FAIL when:
|
|
19
|
+
- SKILL.md body < MIN_BODY_LINES lines AND
|
|
20
|
+
- references/ missing or has < MIN_REF_FILES files AND
|
|
21
|
+
- manifest.json references[] empty or files missing
|
|
22
|
+
|
|
23
|
+
Error message points at the gold standard and lists expected files
|
|
24
|
+
from the manifest entry (when present) so the fix is obvious.
|
|
25
|
+
|
|
26
|
+
Usage:
|
|
27
|
+
check_depth.py <skill-directory>
|
|
28
|
+
check_depth.py <skill-directory> --manifest path/to/manifest.json
|
|
29
|
+
check_depth.py <skill-directory> --all # walk all skills under a root
|
|
30
|
+
check_depth.py <skill-directory> --json
|
|
31
|
+
"""
|
|
32
|
+
import sys
|
|
33
|
+
import re
|
|
34
|
+
import json
|
|
35
|
+
import argparse
|
|
36
|
+
from pathlib import Path
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# Calibration knobs — set against the post-T9567 state.
|
|
40
|
+
# Adjust here, then re-run the audit to verify all 19 active skills pass.
|
|
41
|
+
MIN_BODY_LINES = 100
|
|
42
|
+
MIN_REF_FILES = 3
|
|
43
|
+
GOLD_STANDARDS = ("ct-orchestrator", "ct-skill-creator")
|
|
44
|
+
|
|
45
|
+
# Allowlist — pre-existing stub skills exempted at T9567 (E-SKILLS-DEPTH-BACKFILL).
|
|
46
|
+
# Each entry MUST have a follow-up task ID. Remove the entry once that task lands
|
|
47
|
+
# a depth backfill. New entries require owner approval — do not add silently.
|
|
48
|
+
ALLOWLIST: dict[str, str] = {
|
|
49
|
+
"ct-codebase-mapper": "T9567-followup: pre-existing; depth-backfill deferred",
|
|
50
|
+
"ct-master-tac": "T9567-followup: pre-existing; depth-backfill deferred",
|
|
51
|
+
"ct-memory": "T9567-followup: pre-existing; depth-backfill deferred",
|
|
52
|
+
"ct-stickynote": "T9567-followup: ephemeral note skill; minimal-by-design",
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def count_body_lines(skill_md_path: Path) -> int:
|
|
57
|
+
"""Count content lines in the SKILL.md body (excluding frontmatter)."""
|
|
58
|
+
if not skill_md_path.exists():
|
|
59
|
+
return 0
|
|
60
|
+
raw = skill_md_path.read_text(encoding="utf-8")
|
|
61
|
+
if not raw.startswith("---"):
|
|
62
|
+
# No frontmatter — count whole file
|
|
63
|
+
return len(raw.split("\n"))
|
|
64
|
+
parts = raw.split("---", 2)
|
|
65
|
+
if len(parts) < 3:
|
|
66
|
+
return 0
|
|
67
|
+
body = parts[2].strip()
|
|
68
|
+
if not body:
|
|
69
|
+
return 0
|
|
70
|
+
return len(body.split("\n"))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def manifest_references_for(skill_name: str, manifest_path: Path) -> list[str]:
|
|
74
|
+
"""Return the references array from manifest.json for the given skill,
|
|
75
|
+
or empty list if not present."""
|
|
76
|
+
if not manifest_path.exists():
|
|
77
|
+
return []
|
|
78
|
+
try:
|
|
79
|
+
data = json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
80
|
+
except json.JSONDecodeError:
|
|
81
|
+
return []
|
|
82
|
+
for entry in data.get("skills", []):
|
|
83
|
+
if entry.get("name") == skill_name:
|
|
84
|
+
return entry.get("references", []) or []
|
|
85
|
+
return []
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def repo_root_of(skill_dir: Path) -> Path:
|
|
89
|
+
"""Walk up from skill_dir to the repo root (heuristic: contains
|
|
90
|
+
`packages/skills/skills/manifest.json`)."""
|
|
91
|
+
cur = skill_dir.resolve()
|
|
92
|
+
for _ in range(10):
|
|
93
|
+
if (cur / "packages" / "skills" / "skills" / "manifest.json").exists():
|
|
94
|
+
return cur
|
|
95
|
+
if cur.parent == cur:
|
|
96
|
+
break
|
|
97
|
+
cur = cur.parent
|
|
98
|
+
return skill_dir # fallback
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def check_depth(skill_path: Path, manifest_path: Path | None = None) -> tuple[bool, dict]:
|
|
102
|
+
"""Run the depth check on a single skill directory.
|
|
103
|
+
|
|
104
|
+
Returns (passed, report_dict).
|
|
105
|
+
"""
|
|
106
|
+
skill_dir = Path(skill_path).resolve()
|
|
107
|
+
skill_name = skill_dir.name
|
|
108
|
+
skill_md = skill_dir / "SKILL.md"
|
|
109
|
+
refs_dir = skill_dir / "references"
|
|
110
|
+
|
|
111
|
+
report: dict = {
|
|
112
|
+
"skill_name": skill_name,
|
|
113
|
+
"path": str(skill_dir),
|
|
114
|
+
"body_lines": 0,
|
|
115
|
+
"ref_files_on_disk": 0,
|
|
116
|
+
"manifest_references": [],
|
|
117
|
+
"manifest_references_missing": [],
|
|
118
|
+
"thresholds": {
|
|
119
|
+
"min_body_lines": MIN_BODY_LINES,
|
|
120
|
+
"min_ref_files": MIN_REF_FILES,
|
|
121
|
+
},
|
|
122
|
+
"passed": False,
|
|
123
|
+
"reasons": [],
|
|
124
|
+
"remediation": [],
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# Threshold A: body length
|
|
128
|
+
body_lines = count_body_lines(skill_md)
|
|
129
|
+
report["body_lines"] = body_lines
|
|
130
|
+
body_passes = body_lines >= MIN_BODY_LINES
|
|
131
|
+
|
|
132
|
+
# Threshold B: references/ subdir
|
|
133
|
+
ref_files_on_disk = 0
|
|
134
|
+
if refs_dir.is_dir():
|
|
135
|
+
ref_files_on_disk = len([p for p in refs_dir.iterdir() if p.is_file() and p.suffix == ".md"])
|
|
136
|
+
report["ref_files_on_disk"] = ref_files_on_disk
|
|
137
|
+
refs_dir_passes = ref_files_on_disk >= MIN_REF_FILES
|
|
138
|
+
|
|
139
|
+
# Threshold C: manifest.json references populated and on disk
|
|
140
|
+
manifest_passes = False
|
|
141
|
+
if manifest_path is None:
|
|
142
|
+
# Auto-locate from repo root
|
|
143
|
+
root = repo_root_of(skill_dir)
|
|
144
|
+
manifest_path = root / "packages" / "skills" / "skills" / "manifest.json"
|
|
145
|
+
|
|
146
|
+
if manifest_path.exists():
|
|
147
|
+
manifest_refs = manifest_references_for(skill_name, manifest_path)
|
|
148
|
+
report["manifest_references"] = manifest_refs
|
|
149
|
+
if manifest_refs:
|
|
150
|
+
root = repo_root_of(skill_dir)
|
|
151
|
+
base = root / "packages" / "skills"
|
|
152
|
+
missing = []
|
|
153
|
+
for rel in manifest_refs:
|
|
154
|
+
ref_abs = base / rel
|
|
155
|
+
if not ref_abs.exists():
|
|
156
|
+
# Try relative to skill_dir as fallback
|
|
157
|
+
fallback = skill_dir / Path(rel).relative_to(Path(rel).parts[0]) \
|
|
158
|
+
if Path(rel).parts else None
|
|
159
|
+
if fallback is None or not fallback.exists():
|
|
160
|
+
missing.append(rel)
|
|
161
|
+
report["manifest_references_missing"] = missing
|
|
162
|
+
manifest_passes = (len(manifest_refs) >= MIN_REF_FILES and not missing)
|
|
163
|
+
|
|
164
|
+
# Decide pass/fail — ANY threshold passes ⇒ depth check passes.
|
|
165
|
+
passed = body_passes or refs_dir_passes or manifest_passes
|
|
166
|
+
report["passed"] = passed
|
|
167
|
+
|
|
168
|
+
if body_passes:
|
|
169
|
+
report["reasons"].append(f"body_lines={body_lines} >= {MIN_BODY_LINES}")
|
|
170
|
+
if refs_dir_passes:
|
|
171
|
+
report["reasons"].append(f"references/ has {ref_files_on_disk} files >= {MIN_REF_FILES}")
|
|
172
|
+
if manifest_passes:
|
|
173
|
+
report["reasons"].append(
|
|
174
|
+
f"manifest references[] populated with {len(report['manifest_references'])} files (all on disk)"
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# Allowlist override — exempted skills pass with a reason captured.
|
|
178
|
+
if not passed and skill_name in ALLOWLIST:
|
|
179
|
+
passed = True
|
|
180
|
+
report["passed"] = True
|
|
181
|
+
report["allowlisted"] = True
|
|
182
|
+
report["allowlist_reason"] = ALLOWLIST[skill_name]
|
|
183
|
+
report["reasons"].append(f"allowlisted: {ALLOWLIST[skill_name]}")
|
|
184
|
+
|
|
185
|
+
if not passed:
|
|
186
|
+
report["reasons"].append("none of the three thresholds met")
|
|
187
|
+
report["remediation"] = [
|
|
188
|
+
f"Expand SKILL.md body to >= {MIN_BODY_LINES} content lines (currently {body_lines}), OR",
|
|
189
|
+
f"Add references/ subdir with >= {MIN_REF_FILES} markdown files (currently {ref_files_on_disk}), OR",
|
|
190
|
+
"Populate manifest.json references[] array for this skill with file paths.",
|
|
191
|
+
f"Gold-standard examples: {', '.join(GOLD_STANDARDS)}.",
|
|
192
|
+
]
|
|
193
|
+
if report["manifest_references_missing"]:
|
|
194
|
+
report["remediation"].append(
|
|
195
|
+
"Manifest references[] lists files that do not exist on disk: "
|
|
196
|
+
+ ", ".join(report["manifest_references_missing"])
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
return passed, report
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _print_report(report: dict) -> None:
|
|
203
|
+
"""Print a single skill's depth report."""
|
|
204
|
+
name = report["skill_name"]
|
|
205
|
+
status = "PASS" if report["passed"] else "FAIL"
|
|
206
|
+
icon = "✅" if report["passed"] else "❌"
|
|
207
|
+
print(f"\n{icon} {status} {name}")
|
|
208
|
+
print(f" body_lines={report['body_lines']} (min {MIN_BODY_LINES})")
|
|
209
|
+
print(f" ref_files_on_disk={report['ref_files_on_disk']} (min {MIN_REF_FILES})")
|
|
210
|
+
print(f" manifest_references={len(report['manifest_references'])} files")
|
|
211
|
+
if report["manifest_references_missing"]:
|
|
212
|
+
print(f" manifest_references_missing={report['manifest_references_missing']}")
|
|
213
|
+
for r in report["reasons"]:
|
|
214
|
+
print(f" - {r}")
|
|
215
|
+
if not report["passed"]:
|
|
216
|
+
print(" remediation:")
|
|
217
|
+
for r in report["remediation"]:
|
|
218
|
+
print(f" * {r}")
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def walk_all_skills(root: Path) -> list[Path]:
|
|
222
|
+
"""Find all skill directories under packages/skills/skills/.
|
|
223
|
+
Skips manifest.json, _shared/, and any dir without SKILL.md."""
|
|
224
|
+
base = root if (root / "SKILL.md").exists() else (root / "packages" / "skills" / "skills")
|
|
225
|
+
if not base.is_dir():
|
|
226
|
+
return []
|
|
227
|
+
skills = []
|
|
228
|
+
for entry in sorted(base.iterdir()):
|
|
229
|
+
if not entry.is_dir():
|
|
230
|
+
continue
|
|
231
|
+
if entry.name.startswith("_") or entry.name.startswith("."):
|
|
232
|
+
continue
|
|
233
|
+
if (entry / "SKILL.md").exists():
|
|
234
|
+
skills.append(entry)
|
|
235
|
+
return skills
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def main() -> int:
|
|
239
|
+
parser = argparse.ArgumentParser(
|
|
240
|
+
description="CLEO Skill Depth Check (T9684) — progressive-disclosure-depth"
|
|
241
|
+
)
|
|
242
|
+
parser.add_argument("skill_dir", help="Path to the skill directory (or repo root if --all)")
|
|
243
|
+
parser.add_argument("--manifest", help="Path to manifest.json (auto-located if omitted)")
|
|
244
|
+
parser.add_argument(
|
|
245
|
+
"--all", action="store_true",
|
|
246
|
+
help="Walk every skill under packages/skills/skills/",
|
|
247
|
+
)
|
|
248
|
+
parser.add_argument("--json", action="store_true", help="Output JSON instead of text")
|
|
249
|
+
args = parser.parse_args()
|
|
250
|
+
|
|
251
|
+
arg_path = Path(args.skill_dir).resolve()
|
|
252
|
+
manifest = Path(args.manifest).resolve() if args.manifest else None
|
|
253
|
+
|
|
254
|
+
if args.all:
|
|
255
|
+
skills = walk_all_skills(arg_path)
|
|
256
|
+
if not skills:
|
|
257
|
+
print(f"Error: no skill directories found under {arg_path}", file=sys.stderr)
|
|
258
|
+
return 1
|
|
259
|
+
all_reports = []
|
|
260
|
+
total_fail = 0
|
|
261
|
+
for s in skills:
|
|
262
|
+
passed, report = check_depth(s, manifest)
|
|
263
|
+
all_reports.append(report)
|
|
264
|
+
if not passed:
|
|
265
|
+
total_fail += 1
|
|
266
|
+
if args.json:
|
|
267
|
+
print(json.dumps({
|
|
268
|
+
"summary": {
|
|
269
|
+
"total": len(all_reports),
|
|
270
|
+
"passed": len(all_reports) - total_fail,
|
|
271
|
+
"failed": total_fail,
|
|
272
|
+
"thresholds": {
|
|
273
|
+
"min_body_lines": MIN_BODY_LINES,
|
|
274
|
+
"min_ref_files": MIN_REF_FILES,
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
"skills": all_reports,
|
|
278
|
+
}, indent=2))
|
|
279
|
+
else:
|
|
280
|
+
print(f"=== CLEO Skill Depth Check (all skills under {arg_path}) ===")
|
|
281
|
+
for r in all_reports:
|
|
282
|
+
_print_report(r)
|
|
283
|
+
print(f"\n=== SUMMARY ===")
|
|
284
|
+
print(f"Total skills: {len(all_reports)}")
|
|
285
|
+
print(f"Passed: {len(all_reports) - total_fail}")
|
|
286
|
+
print(f"Failed: {total_fail}")
|
|
287
|
+
return 1 if total_fail > 0 else 0
|
|
288
|
+
|
|
289
|
+
# Single-skill mode
|
|
290
|
+
if not arg_path.is_dir():
|
|
291
|
+
print(f"Error: '{args.skill_dir}' is not a directory", file=sys.stderr)
|
|
292
|
+
return 1
|
|
293
|
+
if not (arg_path / "SKILL.md").exists():
|
|
294
|
+
print(f"Error: '{args.skill_dir}' has no SKILL.md", file=sys.stderr)
|
|
295
|
+
return 1
|
|
296
|
+
|
|
297
|
+
passed, report = check_depth(arg_path, manifest)
|
|
298
|
+
if args.json:
|
|
299
|
+
print(json.dumps(report, indent=2))
|
|
300
|
+
else:
|
|
301
|
+
_print_report(report)
|
|
302
|
+
return 0 if passed else 1
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
if __name__ == "__main__":
|
|
306
|
+
sys.exit(main())
|
|
@@ -6,6 +6,10 @@ tier: 2
|
|
|
6
6
|
core: false
|
|
7
7
|
category: recommended
|
|
8
8
|
protocol: specification
|
|
9
|
+
loomStage: specification
|
|
10
|
+
adrRefs:
|
|
11
|
+
- ADR-014
|
|
12
|
+
- ADR-023
|
|
9
13
|
dependencies: []
|
|
10
14
|
sharedResources:
|
|
11
15
|
- subagent-protocol-base
|
|
@@ -136,9 +140,70 @@ Non-compliant implementations SHOULD {remediation}.
|
|
|
136
140
|
|
|
137
141
|
---
|
|
138
142
|
|
|
143
|
+
## Through SDK (preferred)
|
|
144
|
+
|
|
145
|
+
Specifications are first-class docs SSoT records — created via
|
|
146
|
+
`cleo docs add --type spec`, auto-attached to the parent task, and
|
|
147
|
+
addressable by a stable slug. This is the canonical write path; the
|
|
148
|
+
legacy "write to `docs/specs/<NAME>.md` and commit" pattern is
|
|
149
|
+
deprecated below.
|
|
150
|
+
|
|
151
|
+
### Write the spec attached to its parent task
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
cleo docs add T1234 docs/specs/auth-protocol.md \
|
|
155
|
+
--type spec \
|
|
156
|
+
--slug auth-protocol-v2 \
|
|
157
|
+
--desc "Auth protocol v2 — RFC 2119 requirements"
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
- `--type spec` is the canonical taxonomy value for a specification.
|
|
161
|
+
Other allowed values: `adr | research | handoff | note | llm-readme`.
|
|
162
|
+
- `--slug` is the kebab-case retrieval handle. Use the spec topic +
|
|
163
|
+
version (e.g. `auth-protocol-v2`, `release-pipeline-v3`). The CLI
|
|
164
|
+
returns `E_SLUG_TAKEN` with 3 alternatives on collision — pick one
|
|
165
|
+
rather than silently overwriting.
|
|
166
|
+
- The owner ID (`T1234`) auto-attaches the spec to its parent task so
|
|
167
|
+
downstream stages (`ct-validator`, decomposition, implementation)
|
|
168
|
+
can discover the spec via `cleo docs list --task T1234 --type spec`.
|
|
169
|
+
|
|
170
|
+
### Publish the spec to a git-tracked path (when the spec must ship on disk)
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
cleo docs publish --for T1234 --to docs/specs/auth-protocol.md
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Atomic tmp-then-rename. The published file lands in the next commit;
|
|
177
|
+
the SSoT blob remains canonical and continues to track future versions.
|
|
178
|
+
|
|
179
|
+
### Fetch the spec back by slug
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
cleo docs fetch auth-protocol-v2 # latest version
|
|
183
|
+
cleo docs versions --for T1234 # every SHA version
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Discover sibling specs in this project
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
cleo docs list --type spec --project # every spec in the project
|
|
190
|
+
cleo docs list --task T1234 --type spec # specs attached to T1234
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Deprecated: Direct filesystem write
|
|
194
|
+
|
|
195
|
+
The legacy "write to `docs/specs/{{SPEC_NAME}}.md` and commit" pattern
|
|
196
|
+
is deprecated. The on-disk file drifts from the SSoT, the spec has no
|
|
197
|
+
slug for downstream skills to retrieve it by, and the task↔spec
|
|
198
|
+
linkage exists only as a path convention. Migrate to
|
|
199
|
+
`cleo docs add --type spec --slug <name>` for every new spec — and use
|
|
200
|
+
`cleo docs sync --from docs/specs/<name>.md --for <taskId>` to
|
|
201
|
+
back-fill existing on-disk specs into the SSoT.
|
|
202
|
+
|
|
139
203
|
## Output Location
|
|
140
204
|
|
|
141
|
-
|
|
205
|
+
Spec blobs live in the docs SSoT; published copies on disk go in
|
|
206
|
+
`docs/specs/{{SPEC_NAME}}.md`.
|
|
142
207
|
|
|
143
208
|
---
|
|
144
209
|
|
|
@@ -187,3 +252,23 @@ Specifications go in: `docs/specs/{{SPEC_NAME}}.md`
|
|
|
187
252
|
- [ ] Manifest entry appended
|
|
188
253
|
- [ ] Task completed via `{{TASK_COMPLETE_CMD}}`
|
|
189
254
|
- [ ] Return summary message only
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## See also / References
|
|
259
|
+
|
|
260
|
+
This skill binds to the **specification** LOOM lifecycle stage. Governing ADRs:
|
|
261
|
+
|
|
262
|
+
- [ADR-014 — RCASD rename and protocol validation](../../../../.cleo/adrs/ADR-014-rcasd-rename-and-protocol-validation.md) — defines the specification stage's role inside the RCASD-IVTR+C lifecycle.
|
|
263
|
+
- [ADR-023 — protocol validation dispatch](../../../../.cleo/adrs/ADR-023-protocol-validation-dispatch.md) — defines how specifications are validated before decomposition.
|
|
264
|
+
|
|
265
|
+
LOOM coverage matrix: [docs/skills/loom-coverage-matrix.md](../../../../docs/skills/loom-coverage-matrix.md).
|
|
266
|
+
|
|
267
|
+
## See references/
|
|
268
|
+
|
|
269
|
+
Progressive disclosure — load on demand only:
|
|
270
|
+
|
|
271
|
+
- `references/rfc2119-language.md` — keyword semantics, positive/negative examples, decision rubric
|
|
272
|
+
- `references/spec-templates.md` — protocol, API, architecture, requirements scaffolds + naming + versioning
|
|
273
|
+
- `references/traceability-matrix.md` — three-way trace from source to REQ to test, drift detection
|
|
274
|
+
- `references/anti-patterns.md` — ten failure modes seen in past spec drafts
|