@cleocode/skills 2.0.0
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/dispatch-config.json +404 -0
- package/index.d.ts +178 -0
- package/index.js +405 -0
- package/package.json +14 -0
- package/profiles/core.json +7 -0
- package/profiles/full.json +10 -0
- package/profiles/minimal.json +7 -0
- package/profiles/recommended.json +7 -0
- package/provider-skills-map.json +97 -0
- package/skills/_shared/cleo-style-guide.md +84 -0
- package/skills/_shared/manifest-operations.md +810 -0
- package/skills/_shared/placeholders.json +433 -0
- package/skills/_shared/skill-chaining-patterns.md +237 -0
- package/skills/_shared/subagent-protocol-base.md +223 -0
- package/skills/_shared/task-system-integration.md +232 -0
- package/skills/_shared/testing-framework-config.md +110 -0
- package/skills/ct-cleo/SKILL.md +490 -0
- package/skills/ct-cleo/references/anti-patterns.md +19 -0
- package/skills/ct-cleo/references/loom-lifecycle.md +136 -0
- package/skills/ct-cleo/references/orchestrator-constraints.md +55 -0
- package/skills/ct-cleo/references/session-protocol.md +162 -0
- package/skills/ct-codebase-mapper/SKILL.md +82 -0
- package/skills/ct-contribution/SKILL.md +521 -0
- package/skills/ct-contribution/templates/contribution-init.json +21 -0
- package/skills/ct-dev-workflow/SKILL.md +423 -0
- package/skills/ct-docs-lookup/SKILL.md +66 -0
- package/skills/ct-docs-review/SKILL.md +175 -0
- package/skills/ct-docs-write/SKILL.md +108 -0
- package/skills/ct-documentor/SKILL.md +231 -0
- package/skills/ct-epic-architect/SKILL.md +305 -0
- package/skills/ct-epic-architect/references/bug-epic-example.md +172 -0
- package/skills/ct-epic-architect/references/commands.md +201 -0
- package/skills/ct-epic-architect/references/feature-epic-example.md +210 -0
- package/skills/ct-epic-architect/references/migration-epic-example.md +244 -0
- package/skills/ct-epic-architect/references/output-format.md +92 -0
- package/skills/ct-epic-architect/references/patterns.md +284 -0
- package/skills/ct-epic-architect/references/refactor-epic-example.md +412 -0
- package/skills/ct-epic-architect/references/research-epic-example.md +226 -0
- package/skills/ct-epic-architect/references/shell-escaping.md +86 -0
- package/skills/ct-epic-architect/references/skill-aware-execution.md +195 -0
- package/skills/ct-grade/SKILL.md +230 -0
- package/skills/ct-grade/agents/analysis-reporter.md +203 -0
- package/skills/ct-grade/agents/blind-comparator.md +157 -0
- package/skills/ct-grade/agents/scenario-runner.md +134 -0
- package/skills/ct-grade/eval-viewer/__pycache__/generate_grade_review.cpython-314.pyc +0 -0
- package/skills/ct-grade/eval-viewer/generate_grade_review.py +1138 -0
- package/skills/ct-grade/eval-viewer/generate_grade_viewer.py +544 -0
- package/skills/ct-grade/eval-viewer/generate_review.py +283 -0
- package/skills/ct-grade/eval-viewer/grade-review.html +1574 -0
- package/skills/ct-grade/eval-viewer/viewer.html +219 -0
- package/skills/ct-grade/evals/evals.json +94 -0
- package/skills/ct-grade/references/ab-test-methodology.md +150 -0
- package/skills/ct-grade/references/domains.md +137 -0
- package/skills/ct-grade/references/grade-spec.md +236 -0
- package/skills/ct-grade/references/scenario-playbook.md +234 -0
- package/skills/ct-grade/references/token-tracking.md +120 -0
- package/skills/ct-grade/scripts/__pycache__/audit_analyzer.cpython-314.pyc +0 -0
- package/skills/ct-grade/scripts/__pycache__/run_ab_test.cpython-314.pyc +0 -0
- package/skills/ct-grade/scripts/__pycache__/run_all.cpython-314.pyc +0 -0
- package/skills/ct-grade/scripts/__pycache__/token_tracker.cpython-314.pyc +0 -0
- package/skills/ct-grade/scripts/audit_analyzer.py +279 -0
- package/skills/ct-grade/scripts/generate_report.py +283 -0
- package/skills/ct-grade/scripts/run_ab_test.py +504 -0
- package/skills/ct-grade/scripts/run_all.py +287 -0
- package/skills/ct-grade/scripts/setup_run.py +183 -0
- package/skills/ct-grade/scripts/token_tracker.py +630 -0
- package/skills/ct-grade-v2-1/SKILL.md +237 -0
- package/skills/ct-grade-v2-1/agents/analysis-reporter.md +203 -0
- package/skills/ct-grade-v2-1/agents/blind-comparator.md +157 -0
- package/skills/ct-grade-v2-1/agents/scenario-runner.md +179 -0
- package/skills/ct-grade-v2-1/evals/evals.json +74 -0
- package/skills/ct-grade-v2-1/grade-viewer/__pycache__/build_op_stats.cpython-314.pyc +0 -0
- package/skills/ct-grade-v2-1/grade-viewer/__pycache__/generate_grade_review.cpython-314.pyc +0 -0
- package/skills/ct-grade-v2-1/grade-viewer/build_op_stats.py +174 -0
- package/skills/ct-grade-v2-1/grade-viewer/eval-analysis.json +41 -0
- package/skills/ct-grade-v2-1/grade-viewer/eval-report.md +34 -0
- package/skills/ct-grade-v2-1/grade-viewer/generate_grade_review.py +1023 -0
- package/skills/ct-grade-v2-1/grade-viewer/generate_grade_viewer.py +548 -0
- package/skills/ct-grade-v2-1/grade-viewer/grade-review-eval.html +613 -0
- package/skills/ct-grade-v2-1/grade-viewer/grade-review.html +1532 -0
- package/skills/ct-grade-v2-1/grade-viewer/viewer.html +620 -0
- package/skills/ct-grade-v2-1/manifest-entry.json +31 -0
- package/skills/ct-grade-v2-1/references/ab-testing.md +233 -0
- package/skills/ct-grade-v2-1/references/domains-ssot.md +156 -0
- package/skills/ct-grade-v2-1/references/grade-spec-v2.md +167 -0
- package/skills/ct-grade-v2-1/references/playbook-v2.md +393 -0
- package/skills/ct-grade-v2-1/references/token-tracking.md +202 -0
- package/skills/ct-grade-v2-1/scripts/generate_report.py +419 -0
- package/skills/ct-grade-v2-1/scripts/run_ab_test.py +493 -0
- package/skills/ct-grade-v2-1/scripts/run_scenario.py +396 -0
- package/skills/ct-grade-v2-1/scripts/setup_run.py +207 -0
- package/skills/ct-grade-v2-1/scripts/token_tracker.py +175 -0
- package/skills/ct-memory/SKILL.md +84 -0
- package/skills/ct-orchestrator/INSTALL.md +61 -0
- package/skills/ct-orchestrator/README.md +69 -0
- package/skills/ct-orchestrator/SKILL.md +380 -0
- package/skills/ct-orchestrator/manifest-entry.json +19 -0
- package/skills/ct-orchestrator/orchestrator-prompt.txt +17 -0
- package/skills/ct-orchestrator/references/SUBAGENT-PROTOCOL-BLOCK.md +66 -0
- package/skills/ct-orchestrator/references/autonomous-operation.md +167 -0
- package/skills/ct-orchestrator/references/lifecycle-gates.md +98 -0
- package/skills/ct-orchestrator/references/orchestrator-compliance.md +271 -0
- package/skills/ct-orchestrator/references/orchestrator-handoffs.md +85 -0
- package/skills/ct-orchestrator/references/orchestrator-patterns.md +164 -0
- package/skills/ct-orchestrator/references/orchestrator-recovery.md +113 -0
- package/skills/ct-orchestrator/references/orchestrator-spawning.md +271 -0
- package/skills/ct-orchestrator/references/orchestrator-tokens.md +180 -0
- package/skills/ct-research-agent/SKILL.md +226 -0
- package/skills/ct-skill-creator/.cleo/.context-state.json +13 -0
- package/skills/ct-skill-creator/.cleo/logs/cleo.2026-03-07.1.log +24 -0
- package/skills/ct-skill-creator/.cleo/tasks.db +0 -0
- package/skills/ct-skill-creator/SKILL.md +356 -0
- package/skills/ct-skill-creator/agents/analyzer.md +276 -0
- package/skills/ct-skill-creator/agents/comparator.md +204 -0
- package/skills/ct-skill-creator/agents/grader.md +225 -0
- package/skills/ct-skill-creator/assets/eval_review.html +146 -0
- package/skills/ct-skill-creator/eval-viewer/__pycache__/generate_review.cpython-314.pyc +0 -0
- package/skills/ct-skill-creator/eval-viewer/generate_review.py +471 -0
- package/skills/ct-skill-creator/eval-viewer/viewer.html +1325 -0
- package/skills/ct-skill-creator/manifest-entry.json +17 -0
- package/skills/ct-skill-creator/references/dynamic-context.md +228 -0
- package/skills/ct-skill-creator/references/frontmatter.md +83 -0
- package/skills/ct-skill-creator/references/invocation-control.md +165 -0
- package/skills/ct-skill-creator/references/output-patterns.md +86 -0
- package/skills/ct-skill-creator/references/provider-deployment.md +175 -0
- package/skills/ct-skill-creator/references/schemas.md +430 -0
- package/skills/ct-skill-creator/references/workflows.md +28 -0
- package/skills/ct-skill-creator/scripts/__init__.py +1 -0
- package/skills/ct-skill-creator/scripts/__pycache__/__init__.cpython-314.pyc +0 -0
- package/skills/ct-skill-creator/scripts/__pycache__/aggregate_benchmark.cpython-314.pyc +0 -0
- package/skills/ct-skill-creator/scripts/__pycache__/generate_report.cpython-314.pyc +0 -0
- package/skills/ct-skill-creator/scripts/__pycache__/improve_description.cpython-314.pyc +0 -0
- package/skills/ct-skill-creator/scripts/__pycache__/init_skill.cpython-314.pyc +0 -0
- package/skills/ct-skill-creator/scripts/__pycache__/quick_validate.cpython-314.pyc +0 -0
- package/skills/ct-skill-creator/scripts/__pycache__/run_eval.cpython-314.pyc +0 -0
- package/skills/ct-skill-creator/scripts/__pycache__/run_loop.cpython-314.pyc +0 -0
- package/skills/ct-skill-creator/scripts/__pycache__/utils.cpython-314.pyc +0 -0
- package/skills/ct-skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/skills/ct-skill-creator/scripts/generate_report.py +326 -0
- package/skills/ct-skill-creator/scripts/improve_description.py +247 -0
- package/skills/ct-skill-creator/scripts/init_skill.py +306 -0
- package/skills/ct-skill-creator/scripts/package_skill.py +110 -0
- package/skills/ct-skill-creator/scripts/quick_validate.py +97 -0
- package/skills/ct-skill-creator/scripts/run_eval.py +310 -0
- package/skills/ct-skill-creator/scripts/run_loop.py +328 -0
- package/skills/ct-skill-creator/scripts/utils.py +47 -0
- package/skills/ct-skill-validator/SKILL.md +178 -0
- package/skills/ct-skill-validator/agents/ecosystem-checker.md +151 -0
- package/skills/ct-skill-validator/assets/valid-skill-example.md +13 -0
- package/skills/ct-skill-validator/evals/eval_set.json +14 -0
- package/skills/ct-skill-validator/evals/evals.json +52 -0
- package/skills/ct-skill-validator/manifest-entry.json +20 -0
- package/skills/ct-skill-validator/references/cleo-ecosystem-rules.md +163 -0
- package/skills/ct-skill-validator/references/validation-rules.md +168 -0
- package/skills/ct-skill-validator/scripts/__init__.py +0 -0
- package/skills/ct-skill-validator/scripts/__pycache__/audit_body.cpython-314.pyc +0 -0
- package/skills/ct-skill-validator/scripts/__pycache__/check_ecosystem.cpython-314.pyc +0 -0
- package/skills/ct-skill-validator/scripts/__pycache__/generate_validation_report.cpython-314.pyc +0 -0
- package/skills/ct-skill-validator/scripts/__pycache__/validate.cpython-314.pyc +0 -0
- package/skills/ct-skill-validator/scripts/audit_body.py +242 -0
- package/skills/ct-skill-validator/scripts/check_ecosystem.py +169 -0
- package/skills/ct-skill-validator/scripts/check_manifest.py +172 -0
- package/skills/ct-skill-validator/scripts/generate_validation_report.py +442 -0
- package/skills/ct-skill-validator/scripts/validate.py +422 -0
- package/skills/ct-spec-writer/SKILL.md +189 -0
- package/skills/ct-stickynote/README.md +14 -0
- package/skills/ct-stickynote/SKILL.md +46 -0
- package/skills/ct-task-executor/SKILL.md +296 -0
- package/skills/ct-validator/SKILL.md +216 -0
- package/skills/manifest.json +469 -0
- package/skills.json +281 -0
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
CLEO Skill Validator — Full compliance gauntlet.
|
|
4
|
+
Validates a skill directory against the complete CLEO skill standard.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
validate.py <skill-directory>
|
|
8
|
+
validate.py <skill-directory> --manifest path/to/manifest.json
|
|
9
|
+
validate.py <skill-directory> --manifest path/to/manifest.json --dispatch-config path/to/dispatch-config.json
|
|
10
|
+
validate.py <skill-directory> --provider-map path/to/provider-skills-map.json
|
|
11
|
+
validate.py <skill-directory> --json
|
|
12
|
+
"""
|
|
13
|
+
import sys
|
|
14
|
+
import re
|
|
15
|
+
import json
|
|
16
|
+
import yaml
|
|
17
|
+
import argparse
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
V2_STANDARD = {
|
|
21
|
+
"name", "description", "argument-hint", "disable-model-invocation",
|
|
22
|
+
"user-invocable", "allowed-tools", "model", "context", "agent", "hooks",
|
|
23
|
+
"license",
|
|
24
|
+
}
|
|
25
|
+
CLEO_ONLY = {
|
|
26
|
+
"version", "tier", "core", "category", "protocol",
|
|
27
|
+
"dependencies", "sharedResources", "compatibility",
|
|
28
|
+
"token_budget", "capabilities", "constraints",
|
|
29
|
+
"metadata", "tags", "triggers", "mvi_scope", "requires_tiers",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
MANIFEST_REQUIRED_FIELDS = [
|
|
33
|
+
"name", "version", "description", "path", "status",
|
|
34
|
+
"tier", "token_budget", "capabilities", "constraints",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def validate_skill(skill_path, manifest_path=None, dispatch_config_path=None, provider_map_path=None):
|
|
39
|
+
"""Run the full v2 validation gauntlet on a skill directory.
|
|
40
|
+
|
|
41
|
+
Returns (results, errors, warnings) where results is a list of
|
|
42
|
+
(tier, severity, message) tuples.
|
|
43
|
+
"""
|
|
44
|
+
skill_dir = Path(skill_path).resolve()
|
|
45
|
+
skill_name = skill_dir.name
|
|
46
|
+
errors = 0
|
|
47
|
+
warnings = 0
|
|
48
|
+
results = []
|
|
49
|
+
|
|
50
|
+
def error(tier, msg):
|
|
51
|
+
nonlocal errors
|
|
52
|
+
errors += 1
|
|
53
|
+
results.append((tier, "ERROR", msg))
|
|
54
|
+
|
|
55
|
+
def warn(tier, msg):
|
|
56
|
+
nonlocal warnings
|
|
57
|
+
warnings += 1
|
|
58
|
+
results.append((tier, "WARN", msg))
|
|
59
|
+
|
|
60
|
+
def ok(tier, msg):
|
|
61
|
+
results.append((tier, "OK", msg))
|
|
62
|
+
|
|
63
|
+
# ── Tier 1 — Structure ──────────────────────────────────────────────
|
|
64
|
+
tier = 1
|
|
65
|
+
skill_md = skill_dir / "SKILL.md"
|
|
66
|
+
|
|
67
|
+
if not skill_md.exists():
|
|
68
|
+
error(tier, "SKILL.md does not exist")
|
|
69
|
+
return results, errors, warnings
|
|
70
|
+
|
|
71
|
+
ok(tier, "SKILL.md exists")
|
|
72
|
+
|
|
73
|
+
raw_content = skill_md.read_text(encoding="utf-8")
|
|
74
|
+
|
|
75
|
+
if not raw_content.startswith("---"):
|
|
76
|
+
error(tier, "SKILL.md does not start with '---' (no frontmatter block)")
|
|
77
|
+
return results, errors, warnings
|
|
78
|
+
|
|
79
|
+
ok(tier, "Content starts with '---'")
|
|
80
|
+
|
|
81
|
+
fm_match = re.match(r"^---\n(.*?)\n---", raw_content, re.DOTALL)
|
|
82
|
+
if not fm_match:
|
|
83
|
+
error(tier, "Could not extract frontmatter (missing closing '---')")
|
|
84
|
+
return results, errors, warnings
|
|
85
|
+
|
|
86
|
+
ok(tier, "Frontmatter block extracted")
|
|
87
|
+
|
|
88
|
+
raw_frontmatter = fm_match.group(1)
|
|
89
|
+
try:
|
|
90
|
+
frontmatter = yaml.safe_load(raw_frontmatter)
|
|
91
|
+
except yaml.YAMLError as e:
|
|
92
|
+
error(tier, f"Frontmatter is not valid YAML: {e}")
|
|
93
|
+
return results, errors, warnings
|
|
94
|
+
|
|
95
|
+
ok(tier, "Frontmatter is valid YAML")
|
|
96
|
+
|
|
97
|
+
if not isinstance(frontmatter, dict):
|
|
98
|
+
error(tier, "Frontmatter is not a dictionary (key: value pairs expected)")
|
|
99
|
+
return results, errors, warnings
|
|
100
|
+
|
|
101
|
+
ok(tier, "Frontmatter is a dict")
|
|
102
|
+
|
|
103
|
+
for key in frontmatter:
|
|
104
|
+
if key in CLEO_ONLY:
|
|
105
|
+
error(tier, f"Move '{key}' to manifest.json (CLEO-only field)")
|
|
106
|
+
else:
|
|
107
|
+
pass # valid or unknown keys checked in tier 2
|
|
108
|
+
|
|
109
|
+
if not any(r[1] == "ERROR" and "CLEO-only" in r[2] for r in results):
|
|
110
|
+
ok(tier, "No CLEO-only fields in frontmatter")
|
|
111
|
+
|
|
112
|
+
# ── Tier 2 — Frontmatter Quality ────────────────────────────────────
|
|
113
|
+
tier = 2
|
|
114
|
+
|
|
115
|
+
# name checks
|
|
116
|
+
name_val = frontmatter.get("name")
|
|
117
|
+
if name_val is None:
|
|
118
|
+
error(tier, "'name' field is missing")
|
|
119
|
+
else:
|
|
120
|
+
if not isinstance(name_val, str):
|
|
121
|
+
error(tier, "'name' must be a string")
|
|
122
|
+
else:
|
|
123
|
+
if not re.match(r"^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", name_val):
|
|
124
|
+
error(tier, f"'name' must be hyphen-case (got: '{name_val}')")
|
|
125
|
+
if "--" in name_val:
|
|
126
|
+
error(tier, "'name' must not contain consecutive hyphens")
|
|
127
|
+
if name_val.startswith("-") or name_val.endswith("-"):
|
|
128
|
+
error(tier, "'name' must not start or end with a hyphen")
|
|
129
|
+
if len(name_val) > 64:
|
|
130
|
+
error(tier, f"'name' exceeds 64 characters (got: {len(name_val)})")
|
|
131
|
+
if name_val != skill_name:
|
|
132
|
+
warn(tier, f"'name' field ('{name_val}') does not match directory name ('{skill_name}')")
|
|
133
|
+
if not any(r[1] == "ERROR" and "'name'" in r[2] for r in results if r[0] == 2):
|
|
134
|
+
ok(tier, "'name' is valid")
|
|
135
|
+
|
|
136
|
+
# description checks
|
|
137
|
+
desc_val = frontmatter.get("description")
|
|
138
|
+
if desc_val is None:
|
|
139
|
+
error(tier, "'description' field is missing")
|
|
140
|
+
else:
|
|
141
|
+
if not isinstance(desc_val, str):
|
|
142
|
+
error(tier, "'description' must be a string")
|
|
143
|
+
else:
|
|
144
|
+
if "<" in desc_val or ">" in desc_val:
|
|
145
|
+
error(tier, "'description' must not contain '<' or '>' characters")
|
|
146
|
+
if len(desc_val) > 1024:
|
|
147
|
+
error(tier, f"'description' exceeds 1024 characters (got: {len(desc_val)})")
|
|
148
|
+
if len(desc_val) < 50:
|
|
149
|
+
warn(tier, f"'description' is shorter than 50 characters (got: {len(desc_val)})")
|
|
150
|
+
trigger_indicators = ["when", "use when", "use for"]
|
|
151
|
+
has_trigger = any(ind in desc_val.lower() for ind in trigger_indicators)
|
|
152
|
+
if not has_trigger:
|
|
153
|
+
warn(tier, "'description' should contain a trigger indicator ('when', 'use when', 'use for')")
|
|
154
|
+
if desc_val.startswith("I "):
|
|
155
|
+
warn(tier, "'description' should not start with 'I ' (use third person)")
|
|
156
|
+
if not any(r[1] == "ERROR" and "'description'" in r[2] for r in results if r[0] == 2):
|
|
157
|
+
ok(tier, "'description' is valid")
|
|
158
|
+
|
|
159
|
+
# YAML multiline pitfall check
|
|
160
|
+
if "description: >" in raw_frontmatter or "description: |" in raw_frontmatter:
|
|
161
|
+
warn(tier, "'description' uses YAML multiline syntax (> or |) which can cause unexpected whitespace")
|
|
162
|
+
|
|
163
|
+
# context checks
|
|
164
|
+
context_val = frontmatter.get("context")
|
|
165
|
+
if context_val is not None:
|
|
166
|
+
if context_val != "fork":
|
|
167
|
+
error(tier, f"'context' must be 'fork' if present (got: '{context_val}')")
|
|
168
|
+
else:
|
|
169
|
+
if "agent" not in frontmatter:
|
|
170
|
+
warn(tier, "'context' is 'fork' but no 'agent' field specified")
|
|
171
|
+
ok(tier, "'context' is valid")
|
|
172
|
+
|
|
173
|
+
# boolean field checks
|
|
174
|
+
dmi_val = frontmatter.get("disable-model-invocation")
|
|
175
|
+
if dmi_val is not None and not isinstance(dmi_val, bool):
|
|
176
|
+
error(tier, "'disable-model-invocation' must be a boolean")
|
|
177
|
+
|
|
178
|
+
ui_val = frontmatter.get("user-invocable")
|
|
179
|
+
if ui_val is not None and not isinstance(ui_val, bool):
|
|
180
|
+
error(tier, "'user-invocable' must be a boolean")
|
|
181
|
+
|
|
182
|
+
# contradictory flags
|
|
183
|
+
if (isinstance(dmi_val, bool) and dmi_val is True and
|
|
184
|
+
isinstance(ui_val, bool) and ui_val is False):
|
|
185
|
+
error(tier, "Contradictory: 'disable-model-invocation' is true AND 'user-invocable' is false (skill cannot be invoked at all)")
|
|
186
|
+
|
|
187
|
+
# argument-hint checks
|
|
188
|
+
ah_val = frontmatter.get("argument-hint")
|
|
189
|
+
if ah_val is not None:
|
|
190
|
+
if not isinstance(ah_val, str):
|
|
191
|
+
error(tier, "'argument-hint' must be a string")
|
|
192
|
+
elif len(ah_val) > 100:
|
|
193
|
+
error(tier, f"'argument-hint' exceeds 100 characters (got: {len(ah_val)})")
|
|
194
|
+
|
|
195
|
+
# allowed-tools checks
|
|
196
|
+
at_val = frontmatter.get("allowed-tools")
|
|
197
|
+
if at_val is not None:
|
|
198
|
+
if not isinstance(at_val, (str, list)):
|
|
199
|
+
error(tier, "'allowed-tools' must be a string or list")
|
|
200
|
+
|
|
201
|
+
# model checks
|
|
202
|
+
model_val = frontmatter.get("model")
|
|
203
|
+
if model_val is not None and not isinstance(model_val, str):
|
|
204
|
+
error(tier, "'model' must be a string")
|
|
205
|
+
|
|
206
|
+
# agent checks
|
|
207
|
+
agent_val = frontmatter.get("agent")
|
|
208
|
+
if agent_val is not None and not isinstance(agent_val, str):
|
|
209
|
+
error(tier, "'agent' must be a string")
|
|
210
|
+
|
|
211
|
+
# hooks checks
|
|
212
|
+
hooks_val = frontmatter.get("hooks")
|
|
213
|
+
if hooks_val is not None and not isinstance(hooks_val, dict):
|
|
214
|
+
error(tier, "'hooks' must be a dict")
|
|
215
|
+
|
|
216
|
+
# ── Tier 3 — Body Quality ───────────────────────────────────────────
|
|
217
|
+
tier = 3
|
|
218
|
+
|
|
219
|
+
# Extract body (content after second ---)
|
|
220
|
+
parts = raw_content.split("---", 2)
|
|
221
|
+
body = parts[2].strip() if len(parts) >= 3 else ""
|
|
222
|
+
|
|
223
|
+
if not body:
|
|
224
|
+
warn(tier, "Body is empty (no content after frontmatter)")
|
|
225
|
+
else:
|
|
226
|
+
ok(tier, "Body is present")
|
|
227
|
+
|
|
228
|
+
body_lines = body.split("\n")
|
|
229
|
+
line_count = len(body_lines)
|
|
230
|
+
|
|
231
|
+
if line_count >= 600:
|
|
232
|
+
error(tier, f"Body is too long: {line_count} lines (max 600)")
|
|
233
|
+
elif line_count >= 400:
|
|
234
|
+
warn(tier, f"Body is getting long: {line_count} lines (warn threshold: 400)")
|
|
235
|
+
else:
|
|
236
|
+
ok(tier, f"Body length OK ({line_count} lines)")
|
|
237
|
+
|
|
238
|
+
# Placeholder scan — case-sensitive to avoid matching "todo app", "replace with X", etc.
|
|
239
|
+
placeholders = [r"\[Required:", r"\bTODO\b", r"\bREPLACE\b", r"\[Add content", r"\bFIXME\b", r"\bTBD\b"]
|
|
240
|
+
for pattern in placeholders:
|
|
241
|
+
matches = re.findall(pattern, body)
|
|
242
|
+
if matches:
|
|
243
|
+
clean_pattern = re.sub(r"[\\(?\[\])]", "", pattern).strip("\\b")
|
|
244
|
+
warn(tier, f"Placeholder text found: '{clean_pattern}' ({len(matches)} occurrence(s))")
|
|
245
|
+
|
|
246
|
+
# Section headers check for long bodies
|
|
247
|
+
if line_count > 200:
|
|
248
|
+
section_headers = re.findall(r"^## ", body, re.MULTILINE)
|
|
249
|
+
if not section_headers:
|
|
250
|
+
warn(tier, "Body exceeds 200 lines but has no '## ' section headers")
|
|
251
|
+
else:
|
|
252
|
+
ok(tier, f"Body has {len(section_headers)} section header(s)")
|
|
253
|
+
|
|
254
|
+
# File reference existence checks
|
|
255
|
+
# Strip fenced code blocks first — paths inside ``` are examples, not live references
|
|
256
|
+
body_no_fences = re.sub(r"```[\s\S]*?```", "", body)
|
|
257
|
+
refs = re.findall(r"(?:references|scripts)/[\w./-]+", body_no_fences)
|
|
258
|
+
for ref in refs:
|
|
259
|
+
# Skip cross-skill paths (preceded by / or ${ anywhere in the body)
|
|
260
|
+
escaped = re.escape(ref)
|
|
261
|
+
if re.search(r"[/$]" + escaped, body_no_fences):
|
|
262
|
+
continue
|
|
263
|
+
# Skip example prose: line contains illustrative markers
|
|
264
|
+
ref_line = next((l for l in body_no_fences.split("\n") if ref in l), "")
|
|
265
|
+
if re.search(r"\b(examples?|e\.g\.|such as|like `|would be|illustrat)\b", ref_line, re.IGNORECASE):
|
|
266
|
+
continue
|
|
267
|
+
ref_path = skill_dir / ref
|
|
268
|
+
if not ref_path.exists():
|
|
269
|
+
warn(tier, f"Referenced file does not exist: {ref}")
|
|
270
|
+
|
|
271
|
+
# ── Tier 4 — CLEO Integration ──────────────────────────────────────
|
|
272
|
+
tier = 4
|
|
273
|
+
|
|
274
|
+
if manifest_path:
|
|
275
|
+
manifest_file = Path(manifest_path).resolve()
|
|
276
|
+
if not manifest_file.exists():
|
|
277
|
+
error(tier, f"Manifest file not found: {manifest_path}")
|
|
278
|
+
else:
|
|
279
|
+
try:
|
|
280
|
+
manifest_data = json.loads(manifest_file.read_text(encoding="utf-8"))
|
|
281
|
+
except json.JSONDecodeError as e:
|
|
282
|
+
error(tier, f"Manifest is not valid JSON: {e}")
|
|
283
|
+
manifest_data = None
|
|
284
|
+
|
|
285
|
+
if manifest_data is not None:
|
|
286
|
+
skills_list = manifest_data.get("skills", [])
|
|
287
|
+
matching = [s for s in skills_list if s.get("name") == skill_name]
|
|
288
|
+
|
|
289
|
+
if not matching:
|
|
290
|
+
warn(tier, f"Skill '{skill_name}' not found in manifest.json skills[]")
|
|
291
|
+
else:
|
|
292
|
+
ok(tier, f"Skill '{skill_name}' found in manifest.json")
|
|
293
|
+
entry = matching[0]
|
|
294
|
+
for field in MANIFEST_REQUIRED_FIELDS:
|
|
295
|
+
if field not in entry:
|
|
296
|
+
warn(tier, f"Manifest entry missing required field: '{field}'")
|
|
297
|
+
|
|
298
|
+
if dispatch_config_path:
|
|
299
|
+
dc_file = Path(dispatch_config_path).resolve()
|
|
300
|
+
if not dc_file.exists():
|
|
301
|
+
error(tier, f"Dispatch config file not found: {dispatch_config_path}")
|
|
302
|
+
else:
|
|
303
|
+
try:
|
|
304
|
+
dc_data = json.loads(dc_file.read_text(encoding="utf-8"))
|
|
305
|
+
except json.JSONDecodeError as e:
|
|
306
|
+
error(tier, f"Dispatch config is not valid JSON: {e}")
|
|
307
|
+
dc_data = None
|
|
308
|
+
|
|
309
|
+
if dc_data is not None:
|
|
310
|
+
overrides = dc_data.get("skill_overrides", {})
|
|
311
|
+
if skill_name not in overrides:
|
|
312
|
+
warn(tier, f"Skill '{skill_name}' not found in dispatch-config.json skill_overrides")
|
|
313
|
+
else:
|
|
314
|
+
ok(tier, f"Skill '{skill_name}' found in dispatch-config.json")
|
|
315
|
+
|
|
316
|
+
# ── Tier 5 — Provider Compatibility ─────────────────────────────────
|
|
317
|
+
tier = 5
|
|
318
|
+
|
|
319
|
+
if provider_map_path:
|
|
320
|
+
pm_file = Path(provider_map_path).resolve()
|
|
321
|
+
if not pm_file.exists():
|
|
322
|
+
error(tier, f"Provider map file not found: {provider_map_path}")
|
|
323
|
+
else:
|
|
324
|
+
try:
|
|
325
|
+
pm_data = json.loads(pm_file.read_text(encoding="utf-8"))
|
|
326
|
+
except json.JSONDecodeError as e:
|
|
327
|
+
error(tier, f"Provider map is not valid JSON: {e}")
|
|
328
|
+
pm_data = None
|
|
329
|
+
|
|
330
|
+
if pm_data is not None:
|
|
331
|
+
# Check if skill is referenced anywhere in the provider map
|
|
332
|
+
pm_text = json.dumps(pm_data)
|
|
333
|
+
if skill_name not in pm_text:
|
|
334
|
+
warn(tier, f"Skill '{skill_name}' not referenced in provider-skills-map.json")
|
|
335
|
+
else:
|
|
336
|
+
ok(tier, f"Skill '{skill_name}' found in provider-skills-map.json")
|
|
337
|
+
|
|
338
|
+
return results, errors, warnings
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _print_report(skill_name, results, errors, warnings):
|
|
342
|
+
"""Print the structured validation report."""
|
|
343
|
+
print(f"\n=== CLEO Skill Validator: {skill_name} ===\n")
|
|
344
|
+
|
|
345
|
+
tier_names = {
|
|
346
|
+
1: "Tier 1 — Structure",
|
|
347
|
+
2: "Tier 2 — Frontmatter Quality",
|
|
348
|
+
3: "Tier 3 — Body Quality",
|
|
349
|
+
4: "Tier 4 — CLEO Integration",
|
|
350
|
+
5: "Tier 5 — Provider Compatibility",
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
current_tier = None
|
|
354
|
+
for tier_num, severity, msg in results:
|
|
355
|
+
if tier_num != current_tier:
|
|
356
|
+
current_tier = tier_num
|
|
357
|
+
print(f"{tier_names.get(tier_num, f'Tier {tier_num}')}")
|
|
358
|
+
|
|
359
|
+
if severity == "OK":
|
|
360
|
+
print(f" \u2705 {msg}")
|
|
361
|
+
elif severity == "ERROR":
|
|
362
|
+
print(f" \u274c ERROR: {msg}")
|
|
363
|
+
elif severity == "WARN":
|
|
364
|
+
print(f" \u26a0\ufe0f WARN: {msg}")
|
|
365
|
+
|
|
366
|
+
print(f"\n=== SUMMARY ===")
|
|
367
|
+
print(f"Errors: {errors}")
|
|
368
|
+
print(f"Warnings: {warnings}")
|
|
369
|
+
|
|
370
|
+
if errors > 0:
|
|
371
|
+
print(f"Result: FAIL")
|
|
372
|
+
elif warnings > 0:
|
|
373
|
+
print(f"Result: PASS (with warnings)")
|
|
374
|
+
else:
|
|
375
|
+
print(f"Result: PASS")
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def main():
|
|
379
|
+
parser = argparse.ArgumentParser(
|
|
380
|
+
description="CLEO Skill Validator — Full compliance gauntlet"
|
|
381
|
+
)
|
|
382
|
+
parser.add_argument("skill_dir", help="Path to the skill directory to validate")
|
|
383
|
+
parser.add_argument("--manifest", help="Path to manifest.json for CLEO integration check")
|
|
384
|
+
parser.add_argument("--dispatch-config", help="Path to dispatch-config.json for dispatch override check")
|
|
385
|
+
parser.add_argument("--provider-map", help="Path to provider-skills-map.json for provider compatibility check")
|
|
386
|
+
parser.add_argument("--json", action="store_true", help="Output results as JSON instead of human-readable text")
|
|
387
|
+
|
|
388
|
+
args = parser.parse_args()
|
|
389
|
+
|
|
390
|
+
skill_path = Path(args.skill_dir).resolve()
|
|
391
|
+
if not skill_path.is_dir():
|
|
392
|
+
print(f"Error: '{args.skill_dir}' is not a directory", file=sys.stderr)
|
|
393
|
+
sys.exit(1)
|
|
394
|
+
|
|
395
|
+
skill_name = skill_path.name
|
|
396
|
+
results, errors, warnings = validate_skill(
|
|
397
|
+
skill_path,
|
|
398
|
+
manifest_path=args.manifest,
|
|
399
|
+
dispatch_config_path=args.dispatch_config,
|
|
400
|
+
provider_map_path=args.provider_map,
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
if getattr(args, "json"):
|
|
404
|
+
output = {
|
|
405
|
+
"skill_name": skill_name,
|
|
406
|
+
"results": [
|
|
407
|
+
{"tier": t, "severity": s, "message": m}
|
|
408
|
+
for t, s, m in results
|
|
409
|
+
],
|
|
410
|
+
"errors": errors,
|
|
411
|
+
"warnings": warnings,
|
|
412
|
+
"passed": errors == 0,
|
|
413
|
+
}
|
|
414
|
+
print(json.dumps(output, indent=2))
|
|
415
|
+
else:
|
|
416
|
+
_print_report(skill_name, results, errors, warnings)
|
|
417
|
+
|
|
418
|
+
sys.exit(1 if errors > 0 else 0)
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
if __name__ == "__main__":
|
|
422
|
+
main()
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ct-spec-writer
|
|
3
|
+
description: Technical specification writing using RFC 2119 language for clear, unambiguous requirements. Creates protocol specifications, technical requirements, API specifications, and architecture documents with testable requirements and compliance criteria. Use when writing specifications, defining protocols, documenting requirements, or creating API contracts. Triggers on specification tasks, protocol definition needs, or requirement documentation.
|
|
4
|
+
version: 2.0.0
|
|
5
|
+
tier: 2
|
|
6
|
+
core: false
|
|
7
|
+
category: recommended
|
|
8
|
+
protocol: specification
|
|
9
|
+
dependencies: []
|
|
10
|
+
sharedResources:
|
|
11
|
+
- subagent-protocol-base
|
|
12
|
+
- task-system-integration
|
|
13
|
+
compatibility:
|
|
14
|
+
- claude-code
|
|
15
|
+
- cursor
|
|
16
|
+
- windsurf
|
|
17
|
+
- gemini-cli
|
|
18
|
+
license: MIT
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# Specification Writer Context Injection
|
|
22
|
+
|
|
23
|
+
**Protocol**: @src/protocols/specification.md
|
|
24
|
+
**Type**: Context Injection (cleo-subagent)
|
|
25
|
+
**Version**: 2.0.0
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Purpose
|
|
30
|
+
|
|
31
|
+
Context injection for specification writing tasks spawned via cleo-subagent. Provides domain expertise for creating clear, unambiguous technical specifications using RFC 2119 language.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Capabilities
|
|
36
|
+
|
|
37
|
+
1. **Protocol Specifications** - Define behavior rules with RFC 2119 keywords
|
|
38
|
+
2. **Technical Requirements** - Document system requirements with constraints
|
|
39
|
+
3. **API Specifications** - Define interfaces, schemas, and contracts
|
|
40
|
+
4. **Architecture Documents** - Document system design decisions
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## RFC 2119 Keywords (MANDATORY)
|
|
45
|
+
|
|
46
|
+
Use these keywords with their precise meanings:
|
|
47
|
+
|
|
48
|
+
| Keyword | Meaning | Compliance |
|
|
49
|
+
|---------|---------|------------|
|
|
50
|
+
| **MUST** | Absolute requirement | 95-98% |
|
|
51
|
+
| **MUST NOT** | Absolute prohibition | 93-97% |
|
|
52
|
+
| **SHOULD** | Recommended unless good reason exists | 75-85% |
|
|
53
|
+
| **SHOULD NOT** | Discouraged unless good reason exists | 75-85% |
|
|
54
|
+
| **MAY** | Truly optional | 40-60% |
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Specification Structure
|
|
59
|
+
|
|
60
|
+
### Standard Layout
|
|
61
|
+
|
|
62
|
+
```markdown
|
|
63
|
+
# {Specification Title} v{X.Y.Z}
|
|
64
|
+
|
|
65
|
+
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHOULD",
|
|
66
|
+
"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document
|
|
67
|
+
are to be interpreted as described in RFC 2119.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Overview
|
|
72
|
+
|
|
73
|
+
{2-3 sentence summary of what this spec defines}
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Definitions
|
|
78
|
+
|
|
79
|
+
| Term | Definition |
|
|
80
|
+
|------|------------|
|
|
81
|
+
| {term} | {definition} |
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Requirements
|
|
86
|
+
|
|
87
|
+
### {Category 1}
|
|
88
|
+
|
|
89
|
+
**REQ-001**: {Requirement description}
|
|
90
|
+
- Rationale: {Why this requirement exists}
|
|
91
|
+
- Verification: {How to verify compliance}
|
|
92
|
+
|
|
93
|
+
### {Category 2}
|
|
94
|
+
|
|
95
|
+
**REQ-002**: {Requirement description}
|
|
96
|
+
...
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Constraints
|
|
101
|
+
|
|
102
|
+
| ID | Constraint | Enforcement |
|
|
103
|
+
|----|------------|-------------|
|
|
104
|
+
| CON-001 | {constraint} | {how enforced} |
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Compliance
|
|
109
|
+
|
|
110
|
+
A system is compliant if:
|
|
111
|
+
1. {condition 1}
|
|
112
|
+
2. {condition 2}
|
|
113
|
+
3. {condition 3}
|
|
114
|
+
|
|
115
|
+
Non-compliant implementations SHOULD {remediation}.
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Writing Guidelines
|
|
121
|
+
|
|
122
|
+
### Be Precise
|
|
123
|
+
- Every requirement MUST be testable
|
|
124
|
+
- Avoid ambiguous terms ("appropriate", "reasonable", "adequate")
|
|
125
|
+
- Use specific values, not ranges when possible
|
|
126
|
+
|
|
127
|
+
### Be Complete
|
|
128
|
+
- Define all terms that might be misunderstood
|
|
129
|
+
- Cover error cases and edge conditions
|
|
130
|
+
- Specify what happens when requirements conflict
|
|
131
|
+
|
|
132
|
+
### Be Organized
|
|
133
|
+
- Group related requirements
|
|
134
|
+
- Use consistent numbering (REQ-XXX, CON-XXX)
|
|
135
|
+
- Cross-reference related sections
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Output Location
|
|
140
|
+
|
|
141
|
+
Specifications go in: `docs/specs/{{SPEC_NAME}}.md`
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Task System Integration
|
|
146
|
+
|
|
147
|
+
@skills/_shared/task-system-integration.md
|
|
148
|
+
|
|
149
|
+
### Execution Sequence
|
|
150
|
+
|
|
151
|
+
1. Read task: `{{TASK_SHOW_CMD}} {{TASK_ID}}`
|
|
152
|
+
2. Start task: `{{TASK_START_CMD}} {{TASK_ID}}` (if not already started by orchestrator)
|
|
153
|
+
3. Write specification to `docs/specs/{{SPEC_NAME}}.md`
|
|
154
|
+
4. Append manifest entry to `{{MANIFEST_PATH}}`
|
|
155
|
+
5. Complete task: `{{TASK_COMPLETE_CMD}} {{TASK_ID}}`
|
|
156
|
+
6. Return summary message
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Subagent Protocol
|
|
161
|
+
|
|
162
|
+
@skills/_shared/subagent-protocol-base.md
|
|
163
|
+
|
|
164
|
+
### Output Requirements
|
|
165
|
+
|
|
166
|
+
1. MUST write specification to: `docs/specs/{{SPEC_NAME}}.md`
|
|
167
|
+
2. MUST append ONE line to: `{{MANIFEST_PATH}}`
|
|
168
|
+
3. MUST return ONLY: "Specification complete. See MANIFEST.jsonl for summary."
|
|
169
|
+
4. MUST NOT return specification content in response
|
|
170
|
+
|
|
171
|
+
### Manifest Entry Format
|
|
172
|
+
|
|
173
|
+
```json
|
|
174
|
+
{"id":"spec-{{SPEC_NAME}}-{{DATE}}","file":"{{DATE}}_spec-{{SPEC_NAME}}.md","title":"Specification: {{TITLE}}","date":"{{DATE}}","status":"complete","agent_type":"specification","topics":["specification","{{DOMAIN}}"],"key_findings":["Defined N requirements in M categories","Established X constraints with enforcement rules","Compliance criteria: summary"],"actionable":true,"needs_followup":["{{IMPLEMENTATION_TASK_IDS}}"],"linked_tasks":["{{TASK_ID}}"]}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Completion Checklist
|
|
180
|
+
|
|
181
|
+
- [ ] Task started via `{{TASK_START_CMD}}` (if not already started)
|
|
182
|
+
- [ ] RFC 2119 header included
|
|
183
|
+
- [ ] All requirements numbered (REQ-XXX)
|
|
184
|
+
- [ ] All constraints numbered (CON-XXX)
|
|
185
|
+
- [ ] Compliance section defines pass/fail
|
|
186
|
+
- [ ] Specification written to docs/specs/
|
|
187
|
+
- [ ] Manifest entry appended
|
|
188
|
+
- [ ] Task completed via `{{TASK_COMPLETE_CMD}}`
|
|
189
|
+
- [ ] Return summary message only
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ct-stickynote
|
|
3
|
+
description: Quick ephemeral sticky notes for project-wide capture before formal classification
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
category: productivity
|
|
6
|
+
tier: 0
|
|
7
|
+
protocol: null
|
|
8
|
+
tags: [sticky, notes, capture, quick, ephemeral]
|
|
9
|
+
triggers: [note, sticky, jot, capture]
|
|
10
|
+
compatibility: [claude-code, gemini-cli, codex-cli, opencode]
|
|
11
|
+
dependencies: []
|
|
12
|
+
sharedResources: []
|
|
13
|
+
license: MIT
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Sticky Notes Skill
|
|
17
|
+
|
|
18
|
+
Quick capture ephemeral notes that fill the gap between session notes and formal tasks.
|
|
19
|
+
|
|
20
|
+
## When to Use
|
|
21
|
+
|
|
22
|
+
Use sticky notes for:
|
|
23
|
+
- Quick thoughts that don't fit a formal task yet
|
|
24
|
+
- Temporary reminders
|
|
25
|
+
- Ideas that need refinement before becoming tasks
|
|
26
|
+
- Notes that span multiple sessions
|
|
27
|
+
|
|
28
|
+
## Operations
|
|
29
|
+
|
|
30
|
+
| Operation | Usage | Example |
|
|
31
|
+
|-----------|-------|---------|
|
|
32
|
+
| `sticky.add` | Create sticky | `cleo sticky add "Refactor auth middleware" --tag bug --color red` |
|
|
33
|
+
| `sticky.list` | List active | `cleo sticky list --tag bug` |
|
|
34
|
+
| `sticky.show` | Show details | `cleo sticky show SN-001` |
|
|
35
|
+
| `sticky.convert` | Promote to task/memory | `cleo sticky convert SN-001 --to-task` |
|
|
36
|
+
| `sticky.archive` | Archive | `cleo sticky archive SN-001` |
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
cleo skill install library:ct-stickynote
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Auto-Archive
|
|
45
|
+
|
|
46
|
+
Stickies auto-archive after 30 days if not converted.
|