@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.
Files changed (171) hide show
  1. package/dispatch-config.json +404 -0
  2. package/index.d.ts +178 -0
  3. package/index.js +405 -0
  4. package/package.json +14 -0
  5. package/profiles/core.json +7 -0
  6. package/profiles/full.json +10 -0
  7. package/profiles/minimal.json +7 -0
  8. package/profiles/recommended.json +7 -0
  9. package/provider-skills-map.json +97 -0
  10. package/skills/_shared/cleo-style-guide.md +84 -0
  11. package/skills/_shared/manifest-operations.md +810 -0
  12. package/skills/_shared/placeholders.json +433 -0
  13. package/skills/_shared/skill-chaining-patterns.md +237 -0
  14. package/skills/_shared/subagent-protocol-base.md +223 -0
  15. package/skills/_shared/task-system-integration.md +232 -0
  16. package/skills/_shared/testing-framework-config.md +110 -0
  17. package/skills/ct-cleo/SKILL.md +490 -0
  18. package/skills/ct-cleo/references/anti-patterns.md +19 -0
  19. package/skills/ct-cleo/references/loom-lifecycle.md +136 -0
  20. package/skills/ct-cleo/references/orchestrator-constraints.md +55 -0
  21. package/skills/ct-cleo/references/session-protocol.md +162 -0
  22. package/skills/ct-codebase-mapper/SKILL.md +82 -0
  23. package/skills/ct-contribution/SKILL.md +521 -0
  24. package/skills/ct-contribution/templates/contribution-init.json +21 -0
  25. package/skills/ct-dev-workflow/SKILL.md +423 -0
  26. package/skills/ct-docs-lookup/SKILL.md +66 -0
  27. package/skills/ct-docs-review/SKILL.md +175 -0
  28. package/skills/ct-docs-write/SKILL.md +108 -0
  29. package/skills/ct-documentor/SKILL.md +231 -0
  30. package/skills/ct-epic-architect/SKILL.md +305 -0
  31. package/skills/ct-epic-architect/references/bug-epic-example.md +172 -0
  32. package/skills/ct-epic-architect/references/commands.md +201 -0
  33. package/skills/ct-epic-architect/references/feature-epic-example.md +210 -0
  34. package/skills/ct-epic-architect/references/migration-epic-example.md +244 -0
  35. package/skills/ct-epic-architect/references/output-format.md +92 -0
  36. package/skills/ct-epic-architect/references/patterns.md +284 -0
  37. package/skills/ct-epic-architect/references/refactor-epic-example.md +412 -0
  38. package/skills/ct-epic-architect/references/research-epic-example.md +226 -0
  39. package/skills/ct-epic-architect/references/shell-escaping.md +86 -0
  40. package/skills/ct-epic-architect/references/skill-aware-execution.md +195 -0
  41. package/skills/ct-grade/SKILL.md +230 -0
  42. package/skills/ct-grade/agents/analysis-reporter.md +203 -0
  43. package/skills/ct-grade/agents/blind-comparator.md +157 -0
  44. package/skills/ct-grade/agents/scenario-runner.md +134 -0
  45. package/skills/ct-grade/eval-viewer/__pycache__/generate_grade_review.cpython-314.pyc +0 -0
  46. package/skills/ct-grade/eval-viewer/generate_grade_review.py +1138 -0
  47. package/skills/ct-grade/eval-viewer/generate_grade_viewer.py +544 -0
  48. package/skills/ct-grade/eval-viewer/generate_review.py +283 -0
  49. package/skills/ct-grade/eval-viewer/grade-review.html +1574 -0
  50. package/skills/ct-grade/eval-viewer/viewer.html +219 -0
  51. package/skills/ct-grade/evals/evals.json +94 -0
  52. package/skills/ct-grade/references/ab-test-methodology.md +150 -0
  53. package/skills/ct-grade/references/domains.md +137 -0
  54. package/skills/ct-grade/references/grade-spec.md +236 -0
  55. package/skills/ct-grade/references/scenario-playbook.md +234 -0
  56. package/skills/ct-grade/references/token-tracking.md +120 -0
  57. package/skills/ct-grade/scripts/__pycache__/audit_analyzer.cpython-314.pyc +0 -0
  58. package/skills/ct-grade/scripts/__pycache__/run_ab_test.cpython-314.pyc +0 -0
  59. package/skills/ct-grade/scripts/__pycache__/run_all.cpython-314.pyc +0 -0
  60. package/skills/ct-grade/scripts/__pycache__/token_tracker.cpython-314.pyc +0 -0
  61. package/skills/ct-grade/scripts/audit_analyzer.py +279 -0
  62. package/skills/ct-grade/scripts/generate_report.py +283 -0
  63. package/skills/ct-grade/scripts/run_ab_test.py +504 -0
  64. package/skills/ct-grade/scripts/run_all.py +287 -0
  65. package/skills/ct-grade/scripts/setup_run.py +183 -0
  66. package/skills/ct-grade/scripts/token_tracker.py +630 -0
  67. package/skills/ct-grade-v2-1/SKILL.md +237 -0
  68. package/skills/ct-grade-v2-1/agents/analysis-reporter.md +203 -0
  69. package/skills/ct-grade-v2-1/agents/blind-comparator.md +157 -0
  70. package/skills/ct-grade-v2-1/agents/scenario-runner.md +179 -0
  71. package/skills/ct-grade-v2-1/evals/evals.json +74 -0
  72. package/skills/ct-grade-v2-1/grade-viewer/__pycache__/build_op_stats.cpython-314.pyc +0 -0
  73. package/skills/ct-grade-v2-1/grade-viewer/__pycache__/generate_grade_review.cpython-314.pyc +0 -0
  74. package/skills/ct-grade-v2-1/grade-viewer/build_op_stats.py +174 -0
  75. package/skills/ct-grade-v2-1/grade-viewer/eval-analysis.json +41 -0
  76. package/skills/ct-grade-v2-1/grade-viewer/eval-report.md +34 -0
  77. package/skills/ct-grade-v2-1/grade-viewer/generate_grade_review.py +1023 -0
  78. package/skills/ct-grade-v2-1/grade-viewer/generate_grade_viewer.py +548 -0
  79. package/skills/ct-grade-v2-1/grade-viewer/grade-review-eval.html +613 -0
  80. package/skills/ct-grade-v2-1/grade-viewer/grade-review.html +1532 -0
  81. package/skills/ct-grade-v2-1/grade-viewer/viewer.html +620 -0
  82. package/skills/ct-grade-v2-1/manifest-entry.json +31 -0
  83. package/skills/ct-grade-v2-1/references/ab-testing.md +233 -0
  84. package/skills/ct-grade-v2-1/references/domains-ssot.md +156 -0
  85. package/skills/ct-grade-v2-1/references/grade-spec-v2.md +167 -0
  86. package/skills/ct-grade-v2-1/references/playbook-v2.md +393 -0
  87. package/skills/ct-grade-v2-1/references/token-tracking.md +202 -0
  88. package/skills/ct-grade-v2-1/scripts/generate_report.py +419 -0
  89. package/skills/ct-grade-v2-1/scripts/run_ab_test.py +493 -0
  90. package/skills/ct-grade-v2-1/scripts/run_scenario.py +396 -0
  91. package/skills/ct-grade-v2-1/scripts/setup_run.py +207 -0
  92. package/skills/ct-grade-v2-1/scripts/token_tracker.py +175 -0
  93. package/skills/ct-memory/SKILL.md +84 -0
  94. package/skills/ct-orchestrator/INSTALL.md +61 -0
  95. package/skills/ct-orchestrator/README.md +69 -0
  96. package/skills/ct-orchestrator/SKILL.md +380 -0
  97. package/skills/ct-orchestrator/manifest-entry.json +19 -0
  98. package/skills/ct-orchestrator/orchestrator-prompt.txt +17 -0
  99. package/skills/ct-orchestrator/references/SUBAGENT-PROTOCOL-BLOCK.md +66 -0
  100. package/skills/ct-orchestrator/references/autonomous-operation.md +167 -0
  101. package/skills/ct-orchestrator/references/lifecycle-gates.md +98 -0
  102. package/skills/ct-orchestrator/references/orchestrator-compliance.md +271 -0
  103. package/skills/ct-orchestrator/references/orchestrator-handoffs.md +85 -0
  104. package/skills/ct-orchestrator/references/orchestrator-patterns.md +164 -0
  105. package/skills/ct-orchestrator/references/orchestrator-recovery.md +113 -0
  106. package/skills/ct-orchestrator/references/orchestrator-spawning.md +271 -0
  107. package/skills/ct-orchestrator/references/orchestrator-tokens.md +180 -0
  108. package/skills/ct-research-agent/SKILL.md +226 -0
  109. package/skills/ct-skill-creator/.cleo/.context-state.json +13 -0
  110. package/skills/ct-skill-creator/.cleo/logs/cleo.2026-03-07.1.log +24 -0
  111. package/skills/ct-skill-creator/.cleo/tasks.db +0 -0
  112. package/skills/ct-skill-creator/SKILL.md +356 -0
  113. package/skills/ct-skill-creator/agents/analyzer.md +276 -0
  114. package/skills/ct-skill-creator/agents/comparator.md +204 -0
  115. package/skills/ct-skill-creator/agents/grader.md +225 -0
  116. package/skills/ct-skill-creator/assets/eval_review.html +146 -0
  117. package/skills/ct-skill-creator/eval-viewer/__pycache__/generate_review.cpython-314.pyc +0 -0
  118. package/skills/ct-skill-creator/eval-viewer/generate_review.py +471 -0
  119. package/skills/ct-skill-creator/eval-viewer/viewer.html +1325 -0
  120. package/skills/ct-skill-creator/manifest-entry.json +17 -0
  121. package/skills/ct-skill-creator/references/dynamic-context.md +228 -0
  122. package/skills/ct-skill-creator/references/frontmatter.md +83 -0
  123. package/skills/ct-skill-creator/references/invocation-control.md +165 -0
  124. package/skills/ct-skill-creator/references/output-patterns.md +86 -0
  125. package/skills/ct-skill-creator/references/provider-deployment.md +175 -0
  126. package/skills/ct-skill-creator/references/schemas.md +430 -0
  127. package/skills/ct-skill-creator/references/workflows.md +28 -0
  128. package/skills/ct-skill-creator/scripts/__init__.py +1 -0
  129. package/skills/ct-skill-creator/scripts/__pycache__/__init__.cpython-314.pyc +0 -0
  130. package/skills/ct-skill-creator/scripts/__pycache__/aggregate_benchmark.cpython-314.pyc +0 -0
  131. package/skills/ct-skill-creator/scripts/__pycache__/generate_report.cpython-314.pyc +0 -0
  132. package/skills/ct-skill-creator/scripts/__pycache__/improve_description.cpython-314.pyc +0 -0
  133. package/skills/ct-skill-creator/scripts/__pycache__/init_skill.cpython-314.pyc +0 -0
  134. package/skills/ct-skill-creator/scripts/__pycache__/quick_validate.cpython-314.pyc +0 -0
  135. package/skills/ct-skill-creator/scripts/__pycache__/run_eval.cpython-314.pyc +0 -0
  136. package/skills/ct-skill-creator/scripts/__pycache__/run_loop.cpython-314.pyc +0 -0
  137. package/skills/ct-skill-creator/scripts/__pycache__/utils.cpython-314.pyc +0 -0
  138. package/skills/ct-skill-creator/scripts/aggregate_benchmark.py +401 -0
  139. package/skills/ct-skill-creator/scripts/generate_report.py +326 -0
  140. package/skills/ct-skill-creator/scripts/improve_description.py +247 -0
  141. package/skills/ct-skill-creator/scripts/init_skill.py +306 -0
  142. package/skills/ct-skill-creator/scripts/package_skill.py +110 -0
  143. package/skills/ct-skill-creator/scripts/quick_validate.py +97 -0
  144. package/skills/ct-skill-creator/scripts/run_eval.py +310 -0
  145. package/skills/ct-skill-creator/scripts/run_loop.py +328 -0
  146. package/skills/ct-skill-creator/scripts/utils.py +47 -0
  147. package/skills/ct-skill-validator/SKILL.md +178 -0
  148. package/skills/ct-skill-validator/agents/ecosystem-checker.md +151 -0
  149. package/skills/ct-skill-validator/assets/valid-skill-example.md +13 -0
  150. package/skills/ct-skill-validator/evals/eval_set.json +14 -0
  151. package/skills/ct-skill-validator/evals/evals.json +52 -0
  152. package/skills/ct-skill-validator/manifest-entry.json +20 -0
  153. package/skills/ct-skill-validator/references/cleo-ecosystem-rules.md +163 -0
  154. package/skills/ct-skill-validator/references/validation-rules.md +168 -0
  155. package/skills/ct-skill-validator/scripts/__init__.py +0 -0
  156. package/skills/ct-skill-validator/scripts/__pycache__/audit_body.cpython-314.pyc +0 -0
  157. package/skills/ct-skill-validator/scripts/__pycache__/check_ecosystem.cpython-314.pyc +0 -0
  158. package/skills/ct-skill-validator/scripts/__pycache__/generate_validation_report.cpython-314.pyc +0 -0
  159. package/skills/ct-skill-validator/scripts/__pycache__/validate.cpython-314.pyc +0 -0
  160. package/skills/ct-skill-validator/scripts/audit_body.py +242 -0
  161. package/skills/ct-skill-validator/scripts/check_ecosystem.py +169 -0
  162. package/skills/ct-skill-validator/scripts/check_manifest.py +172 -0
  163. package/skills/ct-skill-validator/scripts/generate_validation_report.py +442 -0
  164. package/skills/ct-skill-validator/scripts/validate.py +422 -0
  165. package/skills/ct-spec-writer/SKILL.md +189 -0
  166. package/skills/ct-stickynote/README.md +14 -0
  167. package/skills/ct-stickynote/SKILL.md +46 -0
  168. package/skills/ct-task-executor/SKILL.md +296 -0
  169. package/skills/ct-validator/SKILL.md +216 -0
  170. package/skills/manifest.json +469 -0
  171. package/skills.json +281 -0
@@ -0,0 +1,168 @@
1
+ # CLEO Skill Validator v2 — Validation Rules
2
+
3
+ Complete rule reference for the 5-tier validation system.
4
+
5
+ ## Overview
6
+
7
+ The CLEO Skill Validator v2 enforces compliance across five tiers of increasing depth:
8
+
9
+ 1. **Structure** — Does the skill have the required files and valid frontmatter?
10
+ 2. **Frontmatter Quality** — Are all frontmatter fields correct, well-formed, and non-contradictory?
11
+ 3. **Body Quality** — Is the body content complete, concise, and free of placeholders?
12
+ 4. **CLEO Integration** — Does the skill align with manifest.json and dispatch-config.json?
13
+ 5. **Provider Compatibility** — Is the skill referenced in the provider-skills-map?
14
+
15
+ Tiers 1-3 run on every validation. Tiers 4-5 are opt-in via CLI flags.
16
+
17
+ ## Allowed vs Forbidden Fields
18
+
19
+ ### V2_STANDARD (allowed in SKILL.md frontmatter)
20
+
21
+ | Field | Type | Required | Description |
22
+ |-------|------|----------|-------------|
23
+ | `name` | string | Yes | Skill identifier, hyphen-case, max 64 chars |
24
+ | `description` | string | Yes | What the skill does and when to use it, max 1024 chars |
25
+ | `argument-hint` | string | No | Shown in autocomplete, max 100 chars |
26
+ | `disable-model-invocation` | boolean | No | Prevent model from auto-invoking |
27
+ | `user-invocable` | boolean | No | Whether skill appears as slash command |
28
+ | `allowed-tools` | string or list | No | Tools pre-approved without per-use prompts |
29
+ | `model` | string | No | Override model for this skill |
30
+ | `context` | string | No | Must be "fork" if present |
31
+ | `agent` | string | No | Subagent type (Explore, Plan, etc.) |
32
+ | `hooks` | dict | No | Skill-scoped lifecycle hooks |
33
+ | `license` | string | No | License identifier |
34
+
35
+ ### CLEO_ONLY (forbidden in SKILL.md, belongs in manifest.json)
36
+
37
+ | Field | Destination |
38
+ |-------|-------------|
39
+ | `version` | manifest.json |
40
+ | `tier` | manifest.json |
41
+ | `core` | manifest.json |
42
+ | `category` | manifest.json |
43
+ | `protocol` | manifest.json |
44
+ | `dependencies` | manifest.json |
45
+ | `sharedResources` | manifest.json |
46
+ | `compatibility` | manifest.json |
47
+ | `token_budget` | manifest.json |
48
+ | `capabilities` | manifest.json |
49
+ | `constraints` | manifest.json |
50
+ | `metadata` | manifest.json |
51
+ | `tags` | manifest.json |
52
+ | `triggers` | manifest.json |
53
+ | `mvi_scope` | manifest.json |
54
+ | `requires_tiers` | manifest.json |
55
+
56
+ ## Tier 1 — Structure Rules
57
+
58
+ | Rule ID | Check | Severity | Fix |
59
+ |---------|-------|----------|-----|
60
+ | T1-001 | SKILL.md exists in skill directory | ERROR | Create SKILL.md with frontmatter and body |
61
+ | T1-002 | Content starts with `---` (frontmatter present) | ERROR | Add YAML frontmatter block at top of file |
62
+ | T1-003 | Frontmatter block can be extracted (closing `---` found) | ERROR | Add closing `---` after frontmatter |
63
+ | T1-004 | Frontmatter is valid YAML | ERROR | Fix YAML syntax errors |
64
+ | T1-005 | Frontmatter parses to a dictionary | ERROR | Frontmatter must be key: value pairs, not a scalar or list |
65
+ | T1-006 | No CLEO-only fields present in frontmatter | ERROR (per field) | Move the field to manifest-entry.json or manifest.json |
66
+
67
+ ## Tier 2 — Frontmatter Quality Rules
68
+
69
+ | Rule ID | Check | Severity | Fix |
70
+ |---------|-------|----------|-----|
71
+ | T2-001 | `name` field is present | ERROR | Add `name: my-skill-name` to frontmatter |
72
+ | T2-002 | `name` is a string | ERROR | Ensure name is a plain string value |
73
+ | T2-003 | `name` is valid hyphen-case (lowercase alphanumeric + hyphens) | ERROR | Use only `a-z`, `0-9`, and `-` |
74
+ | T2-004 | `name` has no consecutive hyphens | ERROR | Replace `--` with `-` |
75
+ | T2-005 | `name` does not start or end with hyphen | ERROR | Remove leading/trailing hyphens |
76
+ | T2-006 | `name` is 64 characters or fewer | ERROR | Shorten the name |
77
+ | T2-007 | `name` matches skill directory name | WARN | Rename to match or update frontmatter |
78
+ | T2-008 | `description` field is present | ERROR | Add description to frontmatter |
79
+ | T2-009 | `description` is a string | ERROR | Ensure description is a plain string |
80
+ | T2-010 | `description` contains no `<` or `>` characters | ERROR | Remove angle brackets |
81
+ | T2-011 | `description` is 1024 characters or fewer | ERROR | Shorten the description |
82
+ | T2-012 | `description` is at least 50 characters | WARN | Expand description with more detail |
83
+ | T2-013 | `description` contains trigger indicator (when/use when/use for) | WARN | Add usage context (e.g., "Use when auditing...") |
84
+ | T2-014 | `description` does not start with "I " | WARN | Rewrite in third person |
85
+ | T2-015 | `description` does not use YAML multiline (`>` or `\|`) | WARN | Use quoted string instead |
86
+ | T2-016 | `context` is "fork" if present | ERROR | Set to "fork" or remove |
87
+ | T2-017 | `context: fork` has accompanying `agent` field | WARN | Add `agent` field specifying subagent type |
88
+ | T2-018 | `disable-model-invocation` is boolean if present | ERROR | Set to `true` or `false` |
89
+ | T2-019 | `user-invocable` is boolean if present | ERROR | Set to `true` or `false` |
90
+ | T2-020 | No contradictory flags (DMI=true + UI=false) | ERROR | A skill must be invocable somehow; fix one flag |
91
+ | T2-021 | `argument-hint` is a string if present | ERROR | Use a plain string value |
92
+ | T2-022 | `argument-hint` is 100 characters or fewer | ERROR | Shorten the hint |
93
+ | T2-023 | `allowed-tools` is string or list if present | ERROR | Use `Tool1, Tool2` or `[Tool1, Tool2]` |
94
+ | T2-024 | `model` is a string if present | ERROR | Use model ID string |
95
+ | T2-025 | `agent` is a string if present | ERROR | Use agent type string |
96
+ | T2-026 | `hooks` is a dict if present | ERROR | Use key: value structure |
97
+
98
+ ## Tier 3 — Body Quality Rules
99
+
100
+ | Rule ID | Check | Severity | Fix |
101
+ |---------|-------|----------|-----|
102
+ | T3-001 | Body is present (non-empty content after frontmatter) | WARN | Add content below the closing `---` |
103
+ | T3-002 | Body is under 600 lines | ERROR | Split into sub-documents or trim |
104
+ | T3-003 | Body is under 400 lines | WARN | Consider trimming for token efficiency |
105
+ | T3-004 | No placeholder text (`[Required:`, `TODO`, `REPLACE`, `[Add content`, `FIXME`, `TBD`) | WARN (per match) | Replace placeholders with real content |
106
+ | T3-005 | Bodies over 200 lines have `## ` section headers | WARN | Add section structure for readability |
107
+ | T3-006 | File references (`references/`, `scripts/`) point to existing files | WARN | Create the referenced file or fix the path |
108
+
109
+ ## Tier 4 — CLEO Integration Rules
110
+
111
+ | Rule ID | Check | Severity | Fix |
112
+ |---------|-------|----------|-----|
113
+ | T4-001 | Skill found in manifest.json `skills[]` array | WARN | Add entry to manifest.json with matching name |
114
+ | T4-002 | Manifest entry has all required fields (name, version, description, path, status, tier, token_budget, capabilities, constraints) | WARN (per field) | Add missing field to manifest entry |
115
+ | T4-003 | Skill found in dispatch-config.json `skill_overrides` (if --dispatch-config provided) | WARN | Add override entry or omit flag if not needed |
116
+
117
+ ## Tier 5 — Provider Compatibility Rules
118
+
119
+ | Rule ID | Check | Severity | Fix |
120
+ |---------|-------|----------|-----|
121
+ | T5-001 | Skill referenced in provider-skills-map.json (if --provider-map provided) | WARN | Add skill to relevant provider entries |
122
+
123
+ ## Complete Rule Table
124
+
125
+ | Rule ID | Tier | Check | Severity |
126
+ |---------|------|-------|----------|
127
+ | T1-001 | 1 | SKILL.md exists | ERROR |
128
+ | T1-002 | 1 | Frontmatter present (starts with `---`) | ERROR |
129
+ | T1-003 | 1 | Frontmatter extractable (closing `---`) | ERROR |
130
+ | T1-004 | 1 | Frontmatter valid YAML | ERROR |
131
+ | T1-005 | 1 | Frontmatter is a dict | ERROR |
132
+ | T1-006 | 1 | No CLEO-only fields | ERROR |
133
+ | T2-001 | 2 | `name` present | ERROR |
134
+ | T2-002 | 2 | `name` is string | ERROR |
135
+ | T2-003 | 2 | `name` hyphen-case | ERROR |
136
+ | T2-004 | 2 | `name` no consecutive hyphens | ERROR |
137
+ | T2-005 | 2 | `name` no leading/trailing hyphens | ERROR |
138
+ | T2-006 | 2 | `name` max 64 chars | ERROR |
139
+ | T2-007 | 2 | `name` matches directory | WARN |
140
+ | T2-008 | 2 | `description` present | ERROR |
141
+ | T2-009 | 2 | `description` is string | ERROR |
142
+ | T2-010 | 2 | `description` no angle brackets | ERROR |
143
+ | T2-011 | 2 | `description` max 1024 chars | ERROR |
144
+ | T2-012 | 2 | `description` min 50 chars | WARN |
145
+ | T2-013 | 2 | `description` has trigger indicator | WARN |
146
+ | T2-014 | 2 | `description` not first person | WARN |
147
+ | T2-015 | 2 | `description` no YAML multiline | WARN |
148
+ | T2-016 | 2 | `context` is "fork" | ERROR |
149
+ | T2-017 | 2 | `context: fork` has `agent` | WARN |
150
+ | T2-018 | 2 | `disable-model-invocation` is bool | ERROR |
151
+ | T2-019 | 2 | `user-invocable` is bool | ERROR |
152
+ | T2-020 | 2 | No contradictory flags | ERROR |
153
+ | T2-021 | 2 | `argument-hint` is string | ERROR |
154
+ | T2-022 | 2 | `argument-hint` max 100 chars | ERROR |
155
+ | T2-023 | 2 | `allowed-tools` is string/list | ERROR |
156
+ | T2-024 | 2 | `model` is string | ERROR |
157
+ | T2-025 | 2 | `agent` is string | ERROR |
158
+ | T2-026 | 2 | `hooks` is dict | ERROR |
159
+ | T3-001 | 3 | Body present | WARN |
160
+ | T3-002 | 3 | Body under 600 lines | ERROR |
161
+ | T3-003 | 3 | Body under 400 lines | WARN |
162
+ | T3-004 | 3 | No placeholder text | WARN |
163
+ | T3-005 | 3 | Section headers in long bodies | WARN |
164
+ | T3-006 | 3 | File references exist | WARN |
165
+ | T4-001 | 4 | Skill in manifest | WARN |
166
+ | T4-002 | 4 | Manifest required fields | WARN |
167
+ | T4-003 | 4 | Skill in dispatch config | WARN |
168
+ | T5-001 | 5 | Skill in provider map | WARN |
File without changes
@@ -0,0 +1,242 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Deep body quality audit for CLEO skills.
4
+ Usage:
5
+ audit_body.py <skill-directory>
6
+ audit_body.py <skill-directory> --json
7
+ """
8
+ import sys
9
+ import re
10
+ import json
11
+ import argparse
12
+ from pathlib import Path
13
+
14
+
15
+ def audit_body(skill_path):
16
+ """Run a deep body quality audit on a skill's SKILL.md.
17
+
18
+ Returns (results, errors, warnings) where results is a list of
19
+ {"severity": "OK"|"WARN"|"ERROR", "section": str, "message": str} dicts.
20
+ """
21
+ skill_dir = Path(skill_path).resolve()
22
+ skill_md = skill_dir / "SKILL.md"
23
+ warnings = 0
24
+ errors = 0
25
+ results = []
26
+
27
+ def error(section, msg):
28
+ nonlocal errors
29
+ errors += 1
30
+ results.append({"severity": "ERROR", "section": section, "message": msg})
31
+
32
+ def warn(section, msg):
33
+ nonlocal warnings
34
+ warnings += 1
35
+ results.append({"severity": "WARN", "section": section, "message": msg})
36
+
37
+ def ok(section, msg):
38
+ results.append({"severity": "OK", "section": section, "message": msg})
39
+
40
+ if not skill_md.exists():
41
+ error("structure", "SKILL.md does not exist")
42
+ return results, errors, warnings
43
+
44
+ raw_content = skill_md.read_text(encoding="utf-8")
45
+
46
+ parts = raw_content.split("---", 2)
47
+ if len(parts) < 3:
48
+ error("structure", "No body found (missing frontmatter closing '---')")
49
+ return results, errors, warnings
50
+
51
+ body = parts[2].strip()
52
+
53
+ if not body:
54
+ error("structure", "Body is empty")
55
+ return results, errors, warnings
56
+
57
+ ok("structure", "Body is present")
58
+
59
+ body_lines = body.split("\n")
60
+ total_lines = len(body_lines)
61
+
62
+ # ── Section analysis ────────────────────────────────────────────────
63
+ h1_headers = re.findall(r"^# .+", body, re.MULTILINE)
64
+ h2_headers = re.findall(r"^## .+", body, re.MULTILINE)
65
+ h3_headers = re.findall(r"^### .+", body, re.MULTILINE)
66
+ total_sections = len(h2_headers) + len(h3_headers)
67
+
68
+ if len(h1_headers) > 1:
69
+ warn("section-analysis", f"Multiple '# ' top-level headings found ({len(h1_headers)})")
70
+
71
+ first_h2_line = None
72
+ first_h3_line = None
73
+ for i, line in enumerate(body_lines):
74
+ if first_h2_line is None and line.startswith("## "):
75
+ first_h2_line = i
76
+ if first_h3_line is None and line.startswith("### "):
77
+ first_h3_line = i
78
+
79
+ if first_h3_line is not None and (first_h2_line is None or first_h3_line < first_h2_line):
80
+ warn("section-analysis", "'### ' heading appears before any '## ' heading (broken hierarchy)")
81
+
82
+ if total_sections > 0:
83
+ ok("section-analysis", f"Found {total_sections} section(s) ({len(h2_headers)} h2, {len(h3_headers)} h3)")
84
+ else:
85
+ warn("section-analysis", "No section headings found")
86
+
87
+ # ── Code blocks ─────────────────────────────────────────────────────
88
+ code_blocks = re.findall(r"```[\s\S]*?```", body)
89
+ if code_blocks:
90
+ ok("code-blocks", f"Found {len(code_blocks)} code block(s)")
91
+ for block in code_blocks:
92
+ script_refs = re.findall(r"(?:scripts|references)/[\w./-]+", block)
93
+ for ref in script_refs:
94
+ # Skip cross-skill references (preceded by / or ${)
95
+ escaped = re.escape(ref)
96
+ if re.search(r"[/$]" + escaped, block):
97
+ continue
98
+ ref_path = skill_dir / ref
99
+ if not ref_path.exists():
100
+ warn("code-blocks", f"Code block references non-existent file: {ref}")
101
+ else:
102
+ ok("code-blocks", "No code blocks (not required)")
103
+
104
+ # ── Link validation ─────────────────────────────────────────────────
105
+ # Strip fenced code blocks first — links inside ``` are examples, not live refs
106
+ body_no_fences = re.sub(r"```[\s\S]*?```", "", body)
107
+ links = re.findall(r"\[([^\]]+)\]\(([^)]+)\)", body_no_fences)
108
+ if links:
109
+ broken = 0
110
+ for text, href in links:
111
+ if href.startswith("http://") or href.startswith("https://"):
112
+ continue
113
+ clean_href = href.split("#")[0]
114
+ if not clean_href:
115
+ continue
116
+ link_path = skill_dir / clean_href
117
+ if not link_path.exists():
118
+ # Check if it's on an example line
119
+ link_line = next((l for l in body_no_fences.split("\n") if f"[{text}]({href})" in l), "")
120
+ if re.search(r"\b(examples?|e\.g\.|such as|would be|illustrat)\b", link_line, re.IGNORECASE):
121
+ continue
122
+ warn("link-validation", f"Broken link: [{text}]({href}) — file not found")
123
+ broken += 1
124
+ if broken == 0:
125
+ ok("link-validation", f"All {len(links)} link(s) valid")
126
+ else:
127
+ ok("link-validation", "No links to validate")
128
+
129
+ # ── Placeholder scan — case-sensitive ───────────────────────────────
130
+ placeholder_patterns = [
131
+ (r"\[Required:", "[Required:"),
132
+ (r"\bTODO\b", "TODO"),
133
+ (r"\bREPLACE\b", "REPLACE"),
134
+ (r"\bFIXME\b", "FIXME"),
135
+ (r"\bTBD\b", "TBD"),
136
+ (r"\[Add content", "[Add content"),
137
+ ]
138
+ placeholder_found = False
139
+ for pattern, label in placeholder_patterns:
140
+ matches = re.findall(pattern, body) # no re.IGNORECASE
141
+ if matches:
142
+ warn("placeholder-scan", f"Placeholder text: '{label}' ({len(matches)} occurrence(s))")
143
+ placeholder_found = True
144
+
145
+ if not placeholder_found:
146
+ ok("placeholder-scan", "No placeholder text found")
147
+
148
+ # ── Duplicate headings ──────────────────────────────────────────────
149
+ all_headings = re.findall(r"^(#{1,6} .+)", body, re.MULTILINE)
150
+ seen: dict[str, bool] = {}
151
+ dup_found = False
152
+ for heading in all_headings:
153
+ normalized = heading.strip()
154
+ if normalized in seen:
155
+ warn("duplicate-headings", f"Duplicate heading: '{normalized}'")
156
+ dup_found = True
157
+ seen[normalized] = True
158
+
159
+ if not dup_found:
160
+ ok("duplicate-headings", "No duplicate headings")
161
+
162
+ # ── Statistics ──────────────────────────────────────────────────────
163
+ avg_per_section = total_lines / total_sections if total_sections > 0 else total_lines
164
+ results.append({
165
+ "severity": "INFO",
166
+ "section": "statistics",
167
+ "message": f"Lines: {total_lines}, Sections: {total_sections}, Avg lines/section: {avg_per_section:.1f}, Code blocks: {len(code_blocks)}, Links: {len(links)}",
168
+ })
169
+
170
+ return results, errors, warnings
171
+
172
+
173
+ def _print_report(skill_name: str, results: list, errors: int, warnings: int) -> None:
174
+ section_order = ["structure", "section-analysis", "code-blocks", "link-validation", "placeholder-scan", "duplicate-headings", "statistics"]
175
+ section_labels = {
176
+ "structure": "Structure",
177
+ "section-analysis": "Section Analysis",
178
+ "code-blocks": "Code Blocks",
179
+ "link-validation": "Link Validation",
180
+ "placeholder-scan": "Placeholder Scan",
181
+ "duplicate-headings": "Duplicate Headings",
182
+ "statistics": "Statistics",
183
+ }
184
+ print(f"\n=== CLEO Body Audit: {skill_name} ===\n")
185
+ current_section = None
186
+ for r in results:
187
+ sec = r["section"]
188
+ if sec != current_section:
189
+ current_section = sec
190
+ print(f"\n--- {section_labels.get(sec, sec)} ---")
191
+ sev = r["severity"]
192
+ msg = r["message"]
193
+ if sev == "OK":
194
+ print(f" \u2705 {msg}")
195
+ elif sev == "WARN":
196
+ print(f" \u26a0\ufe0f WARN: {msg}")
197
+ elif sev == "ERROR":
198
+ print(f" \u274c ERROR: {msg}")
199
+ elif sev == "INFO":
200
+ print(f" {msg}")
201
+
202
+ print(f"\n=== SUMMARY ===")
203
+ print(f"Errors: {errors}")
204
+ print(f"Warnings: {warnings}")
205
+ if errors > 0:
206
+ print("Result: FAIL")
207
+ elif warnings > 0:
208
+ print("Result: PASS (with warnings)")
209
+ else:
210
+ print("Result: PASS")
211
+
212
+
213
+ def main():
214
+ parser = argparse.ArgumentParser(description="Deep body quality audit for CLEO skills")
215
+ parser.add_argument("skill_dir", help="Path to the skill directory to audit")
216
+ parser.add_argument("--json", action="store_true", help="Output results as JSON")
217
+ args = parser.parse_args()
218
+
219
+ skill_path = Path(args.skill_dir).resolve()
220
+ if not skill_path.is_dir():
221
+ print(f"Error: '{args.skill_dir}' is not a directory", file=sys.stderr)
222
+ sys.exit(1)
223
+
224
+ skill_name = skill_path.name
225
+ results, errors, warnings = audit_body(skill_path)
226
+
227
+ if getattr(args, "json"):
228
+ print(json.dumps({
229
+ "skill_name": skill_name,
230
+ "results": results,
231
+ "errors": errors,
232
+ "warnings": warnings,
233
+ "passed": errors == 0,
234
+ }, indent=2))
235
+ else:
236
+ _print_report(skill_name, results, errors, warnings)
237
+
238
+ sys.exit(1 if errors > 0 else 0)
239
+
240
+
241
+ if __name__ == "__main__":
242
+ main()
@@ -0,0 +1,169 @@
1
+ #!/usr/bin/env python3
2
+ """Extract structured context from a skill for ecosystem compliance checking.
3
+
4
+ Parses SKILL.md and scans for CLEO-specific patterns (operation references,
5
+ domain mentions, lifecycle stages, deprecated verbs). Outputs a JSON context
6
+ package that the ecosystem-checker agent evaluates.
7
+
8
+ Usage:
9
+ python check_ecosystem.py <skill-dir>
10
+ python check_ecosystem.py <skill-dir> --output context.json
11
+ """
12
+
13
+ import argparse
14
+ import json
15
+ import re
16
+ import sys
17
+ from pathlib import Path
18
+
19
+ # Canonical CLEO domains
20
+ CANONICAL_DOMAINS = [
21
+ "tasks", "session", "memory", "check", "pipeline",
22
+ "orchestrate", "tools", "admin", "nexus", "sticky",
23
+ ]
24
+
25
+ # RCASD-IVTR+C lifecycle stage names (and common abbreviations)
26
+ LIFECYCLE_STAGES = [
27
+ "Research", "Consensus", "Architecture Decision", "Specification",
28
+ "Decomposition", "Implementation", "Validation", "Testing", "Release",
29
+ "Contribution", "RCASD", "IVTR", "RCSD",
30
+ ]
31
+
32
+ # Deprecated verbs that should not be used when describing CLEO operations
33
+ DEPRECATED_VERBS = [
34
+ r"\bcreate\b", # → add
35
+ r"\bsearch\b", # → find
36
+ r"(?<!\btool)\bget\b", # → show or fetch (avoid matching "get" in "getter" etc.)
37
+ ]
38
+
39
+ # Patterns indicating direct .cleo/ data manipulation (not via MCP)
40
+ DIRECT_DATA_PATTERNS = [
41
+ r"edit\s+tasks\.db",
42
+ r"modify\s+\.cleo/",
43
+ r"open\s+brain\.db",
44
+ r"directly\s+edit",
45
+ r"\.cleo/config\.json\s+directly",
46
+ r"vim\s+\.cleo/",
47
+ r"nano\s+\.cleo/",
48
+ ]
49
+
50
+ # Pattern for detecting CLEO operation references
51
+ # Matches: "query tasks.show", "mutate memory.observe", "query { domain: ...", etc.
52
+ OPERATION_PATTERN = re.compile(
53
+ r"(?:query|mutate)\s+([a-z]+\.[a-z.]+)"
54
+ r"|(?:query|mutate)\s*\{\s*domain:\s*[\"']([a-z]+)[\"']\s*,\s*operation:\s*[\"']([^\"']+)[\"']",
55
+ re.IGNORECASE,
56
+ )
57
+
58
+ # Pattern for cleo CLI commands: "cleo <verb> ..."
59
+ CLEO_CLI_PATTERN = re.compile(r"`cleo\s+([a-z]+)", re.IGNORECASE)
60
+
61
+
62
+ def extract_skill_data(skill_path: Path) -> dict:
63
+ """Extract structured data from a skill directory."""
64
+ skill_md = skill_path / "SKILL.md"
65
+ if not skill_md.exists():
66
+ return {"error": f"SKILL.md not found at {skill_md}"}
67
+
68
+ content = skill_md.read_text(encoding="utf-8")
69
+
70
+ # Parse frontmatter
71
+ frontmatter: dict = {}
72
+ body = content
73
+ fm_match = re.match(r"^---\n(.*?)\n---\n?(.*)", content, re.DOTALL)
74
+ if fm_match:
75
+ raw_fm = fm_match.group(1)
76
+ body = fm_match.group(2).strip()
77
+ # Simple YAML key extraction (not full YAML parse to avoid dependency)
78
+ for line in raw_fm.split("\n"):
79
+ if ":" in line:
80
+ key, _, val = line.partition(":")
81
+ frontmatter[key.strip()] = val.strip().strip('"').strip("'")
82
+
83
+ name = frontmatter.get("name", skill_path.name)
84
+ description = frontmatter.get("description", "")
85
+ allowed_tools = frontmatter.get("allowed-tools", "")
86
+
87
+ # Find CLEO operation references
88
+ cleo_ops: list[str] = []
89
+ for m in OPERATION_PATTERN.finditer(content):
90
+ if m.group(1):
91
+ cleo_ops.append(m.group(1))
92
+ elif m.group(2) and m.group(3):
93
+ cleo_ops.append(f"{m.group(2)}.{m.group(3)}")
94
+ cleo_ops = sorted(set(cleo_ops))
95
+
96
+ # Find domain mentions
97
+ domains_mentioned: list[str] = []
98
+ for domain in CANONICAL_DOMAINS:
99
+ if re.search(r"\b" + domain + r"\b", content, re.IGNORECASE):
100
+ domains_mentioned.append(domain)
101
+
102
+ # Find lifecycle stage mentions
103
+ lifecycle_found: list[str] = []
104
+ for stage in LIFECYCLE_STAGES:
105
+ if re.search(r"\b" + re.escape(stage) + r"\b", content, re.IGNORECASE):
106
+ lifecycle_found.append(stage)
107
+
108
+ # Find deprecated verb usage
109
+ deprecated_found: list[str] = []
110
+ for pattern in DEPRECATED_VERBS:
111
+ matches = re.findall(pattern, body, re.IGNORECASE)
112
+ if matches:
113
+ verb = pattern.replace(r"\b", "").replace("(?<!\\btool)\\bget\\b", "get")
114
+ deprecated_found.append(f"{matches[0]} ({len(matches)} occurrence(s))")
115
+
116
+ # Check for direct data manipulation
117
+ direct_data_issues: list[str] = []
118
+ for pattern in DIRECT_DATA_PATTERNS:
119
+ if re.search(pattern, body, re.IGNORECASE):
120
+ direct_data_issues.append(pattern)
121
+
122
+ # Find cleo CLI verb usage
123
+ cli_verbs = list(set(m.group(1).lower() for m in CLEO_CLI_PATTERN.finditer(content)))
124
+
125
+ body_lines = len([l for l in body.split("\n") if l.strip()])
126
+
127
+ return {
128
+ "skill_name": name,
129
+ "skill_path": str(skill_path.resolve()),
130
+ "frontmatter": frontmatter,
131
+ "description": description,
132
+ "allowed_tools": allowed_tools,
133
+ "body_line_count": body_lines,
134
+ "body": body,
135
+ "cleo_operations_referenced": cleo_ops,
136
+ "domains_mentioned": domains_mentioned,
137
+ "lifecycle_stages_mentioned": lifecycle_found,
138
+ "deprecated_verbs_found": deprecated_found,
139
+ "direct_data_manipulation_detected": direct_data_issues,
140
+ "cli_verbs_used": cli_verbs,
141
+ }
142
+
143
+
144
+ def main() -> None:
145
+ parser = argparse.ArgumentParser(
146
+ description="Extract CLEO ecosystem context from a skill for compliance checking"
147
+ )
148
+ parser.add_argument("skill_dir", help="Path to the skill directory")
149
+ parser.add_argument("--output", "-o", default=None, help="Write JSON to file (default: stdout)")
150
+ args = parser.parse_args()
151
+
152
+ skill_path = Path(args.skill_dir).resolve()
153
+ if not skill_path.is_dir():
154
+ print(f"Error: '{args.skill_dir}' is not a directory", file=sys.stderr)
155
+ sys.exit(1)
156
+
157
+ data = extract_skill_data(skill_path)
158
+
159
+ output = json.dumps(data, indent=2)
160
+
161
+ if args.output:
162
+ Path(args.output).write_text(output)
163
+ print(f"Context written to {args.output}", file=sys.stderr)
164
+ else:
165
+ print(output)
166
+
167
+
168
+ if __name__ == "__main__":
169
+ main()