@cleocode/skills 2026.9.21 → 2026.9.23
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/README.md +46 -63
- package/frontmatter.d.mts +60 -0
- package/frontmatter.mjs +277 -0
- package/index.d.ts +3 -3
- package/index.js +34 -9
- package/package.json +4 -2
- package/profiles/core.json +10 -3
- package/profiles/full.json +10 -6
- package/profiles/recommended.json +29 -3
- package/skills/_shared/manifest-operations.md +19 -40
- package/skills/_shared/skill-chaining-patterns.md +5 -9
- package/skills/_shared/subagent-protocol-base.md +6 -6
- package/skills/_shared/task-system-integration.md +5 -5
- package/skills/ct-adr-recorder/SKILL.md +18 -17
- package/skills/ct-artifact-publisher/SKILL.md +10 -7
- package/skills/ct-cleo/SKILL.md +26 -9
- package/skills/ct-cleo/references/loom-lifecycle.md +1 -1
- package/skills/{ct-memory/SKILL.md → ct-cleo/references/memory.md} +10 -23
- package/skills/ct-cleo/references/orchestrator-constraints.md +5 -5
- package/skills/ct-cleo/references/session-protocol.md +11 -8
- package/skills/{ct-stickynote/SKILL.md → ct-cleo/references/sticky-notes.md} +7 -29
- package/skills/ct-codebase-mapper/SKILL.md +62 -38
- package/skills/ct-consensus-voter/SKILL.md +10 -7
- package/skills/ct-contribution/SKILL.md +7 -5
- package/skills/ct-dev-workflow/SKILL.md +7 -7
- package/skills/ct-documentor/SKILL.md +29 -29
- package/skills/{ct-docs-review/__tests__/skill-docs-review.test.ts → ct-documentor/__tests__/reviewing-reference.test.ts} +4 -3
- package/skills/{ct-docs-write/__tests__/skill-docs-write.test.ts → ct-documentor/__tests__/writing-reference.test.ts} +4 -3
- package/skills/ct-documentor/references/anti-patterns.md +8 -8
- package/skills/{ct-docs-write → ct-documentor}/references/audience-targeting.md +5 -5
- package/skills/ct-documentor/references/chain-orchestration.md +19 -19
- package/skills/{ct-docs-write → ct-documentor}/references/cleo-style-guide.md +1 -1
- package/skills/ct-documentor/references/doc-types-and-templates.md +1 -1
- package/skills/{ct-docs-review → ct-documentor}/references/pr-review-mode.md +1 -1
- package/skills/{ct-docs-review/SKILL.md → ct-documentor/references/reviewing.md} +6 -26
- package/skills/ct-documentor/references/style-coordination.md +5 -5
- package/skills/{ct-docs-review → ct-documentor}/references/style-violations.md +1 -1
- package/skills/{ct-docs-write/SKILL.md → ct-documentor/references/writing.md} +5 -25
- package/skills/ct-epic-architect/SKILL.md +12 -9
- package/skills/ct-epic-architect/references/commands.md +5 -5
- package/skills/ct-epic-architect/references/output-format.md +2 -2
- package/skills/ct-epic-architect/references/patterns.md +2 -2
- package/skills/ct-epic-architect/references/skill-aware-execution.md +3 -3
- package/skills/ct-grade/SKILL.md +3 -4
- package/skills/ct-grade/agents/scenario-runner.md +1 -1
- package/skills/ct-ivt-looper/SKILL.md +23 -22
- package/skills/ct-lead/SKILL.md +52 -13
- package/skills/ct-lead/references/aggregation-protocol.md +44 -21
- package/skills/ct-lead/references/spawn-pattern.md +12 -11
- package/skills/ct-orchestrator/README.md +2 -2
- package/skills/ct-orchestrator/SKILL.md +14 -10
- package/skills/ct-orchestrator/references/SUBAGENT-PROTOCOL-BLOCK.md +4 -4
- package/skills/ct-orchestrator/references/autonomous-operation.md +4 -4
- package/skills/ct-orchestrator/references/orchestrator-compliance.md +3 -3
- package/skills/ct-orchestrator/references/orchestrator-patterns.md +20 -19
- package/skills/ct-orchestrator/references/orchestrator-recovery.md +13 -23
- package/skills/ct-orchestrator/references/orchestrator-spawning.md +9 -10
- package/skills/ct-orchestrator/references/orchestrator-tokens.md +9 -9
- package/skills/ct-provenance-keeper/SKILL.md +10 -7
- package/skills/ct-release-orchestrator/SKILL.md +21 -13
- package/skills/ct-release-orchestrator/references/composition.md +1 -1
- package/skills/ct-release-orchestrator/references/release-types.md +33 -15
- package/skills/ct-research-agent/SKILL.md +14 -12
- package/skills/ct-research-agent/references/triggers-and-routing.md +4 -4
- package/skills/{ct-skill-creator → ct-skill-author}/SKILL.md +9 -6
- package/skills/{ct-skill-validator → ct-skill-author}/agents/ecosystem-checker.md +1 -1
- package/skills/{ct-skill-validator → ct-skill-author}/evals/quality_evals.json +10 -10
- package/skills/{ct-skill-validator → ct-skill-author}/evals/trigger_queries.json +1 -1
- package/skills/{ct-skill-validator → ct-skill-author}/references/cleo-ecosystem-rules.md +8 -4
- package/skills/{ct-skill-creator → ct-skill-author}/references/provider-deployment.md +10 -6
- package/skills/{ct-skill-validator/SKILL.md → ct-skill-author/references/validating.md} +14 -21
- package/skills/{ct-skill-validator → ct-skill-author}/references/validation-rules.md +2 -2
- package/skills/{ct-skill-validator → ct-skill-author}/scripts/_skill_finder.py +2 -2
- package/skills/{ct-skill-validator → ct-skill-author}/scripts/check_depth.py +2 -5
- package/skills/{ct-skill-validator → ct-skill-author}/scripts/generate_validation_report.py +1 -1
- package/skills/{ct-skill-validator → ct-skill-author}/scripts/run_quality_eval.py +2 -2
- package/skills/ct-spec-writer/SKILL.md +9 -7
- package/skills/ct-task-executor/SKILL.md +19 -16
- package/skills/ct-task-executor/references/common-failures.md +1 -1
- package/skills/ct-validator/SKILL.md +11 -9
- package/skills/ct-validator/references/compliance-reports.md +1 -1
- package/skills/ct-validator/references/validation-modes.md +3 -3
- package/skills/manifest.json +381 -365
- package/dispatch-config.json +0 -983
- package/provider-skills-map.json +0 -97
- package/skills/ct-docs-lookup/SKILL.md +0 -185
- package/skills/ct-docs-lookup/references/ctx7-workflow.md +0 -198
- package/skills/ct-docs-lookup/references/library-id-resolution.md +0 -217
- package/skills/ct-docs-lookup/references/version-specific-docs.md +0 -220
- package/skills/ct-master-tac/SKILL.md +0 -120
- package/skills/ct-master-tac/__tests__/install.test.ts +0 -304
- package/skills/ct-master-tac/bundled/README.md +0 -40
- package/skills/ct-master-tac/bundled/protocols/architecture-decision.cant +0 -90
- package/skills/ct-master-tac/bundled/protocols/artifact-publish.cant +0 -105
- package/skills/ct-master-tac/bundled/protocols/consensus.cant +0 -85
- package/skills/ct-master-tac/bundled/protocols/contribution.cant +0 -92
- package/skills/ct-master-tac/bundled/protocols/decomposition.cant +0 -102
- package/skills/ct-master-tac/bundled/protocols/implementation.cant +0 -77
- package/skills/ct-master-tac/bundled/protocols/provenance.cant +0 -100
- package/skills/ct-master-tac/bundled/protocols/release.cant +0 -107
- package/skills/ct-master-tac/bundled/protocols/research.cant +0 -77
- package/skills/ct-master-tac/bundled/protocols/specification.cant +0 -77
- package/skills/ct-master-tac/bundled/protocols/testing.cant +0 -99
- package/skills/ct-master-tac/bundled/protocols/validation.cant +0 -75
- package/skills/ct-master-tac/bundled/teams/platform.cant +0 -115
- package/skills/ct-master-tac/manifest.json +0 -55
- package/skills/ct-skill-creator/eval-viewer/__pycache__/generate_review.cpython-314.pyc +0 -0
- package/skills/ct-skill-creator/manifest-entry.json +0 -17
- 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-validator/manifest-entry.json +0 -20
- package/skills/ct-stickynote/README.md +0 -14
- package/skills/signaldock-connect/SKILL.md +0 -137
- package/skills/signaldock-connect/assets/agent-card.json +0 -48
- package/skills/signaldock-connect/references/api-endpoints.md +0 -131
- package/skills.json +0 -495
- /package/skills/{ct-docs-review → ct-documentor}/references/inline-comment-patterns.md +0 -0
- /package/skills/{ct-docs-write → ct-documentor}/references/markdown-patterns.md +0 -0
- /package/skills/{ct-skill-creator → ct-skill-author}/agents/analyzer.md +0 -0
- /package/skills/{ct-skill-creator → ct-skill-author}/agents/comparator.md +0 -0
- /package/skills/{ct-skill-creator → ct-skill-author}/agents/grader.md +0 -0
- /package/skills/{ct-skill-creator → ct-skill-author}/assets/eval_review.html +0 -0
- /package/skills/{ct-skill-validator → ct-skill-author}/assets/valid-skill-example.md +0 -0
- /package/skills/{ct-skill-creator → ct-skill-author}/eval-viewer/generate_review.py +0 -0
- /package/skills/{ct-skill-creator → ct-skill-author}/eval-viewer/viewer.html +0 -0
- /package/skills/{ct-skill-creator → ct-skill-author}/references/dynamic-context.md +0 -0
- /package/skills/{ct-skill-creator → ct-skill-author}/references/frontmatter.md +0 -0
- /package/skills/{ct-skill-creator → ct-skill-author}/references/invocation-control.md +0 -0
- /package/skills/{ct-skill-creator → ct-skill-author}/references/output-patterns.md +0 -0
- /package/skills/{ct-skill-creator → ct-skill-author}/references/schemas.md +0 -0
- /package/skills/{ct-skill-creator → ct-skill-author}/references/workflows.md +0 -0
- /package/skills/{ct-skill-creator → ct-skill-author}/scripts/__init__.py +0 -0
- /package/skills/{ct-skill-creator → ct-skill-author}/scripts/aggregate_benchmark.py +0 -0
- /package/skills/{ct-skill-validator → ct-skill-author}/scripts/audit_body.py +0 -0
- /package/skills/{ct-skill-validator → ct-skill-author}/scripts/check_ecosystem.py +0 -0
- /package/skills/{ct-skill-creator → ct-skill-author}/scripts/generate_report.py +0 -0
- /package/skills/{ct-skill-creator → ct-skill-author}/scripts/improve_description.py +0 -0
- /package/skills/{ct-skill-creator → ct-skill-author}/scripts/init_skill.py +0 -0
- /package/skills/{ct-skill-creator → ct-skill-author}/scripts/package_skill.py +0 -0
- /package/skills/{ct-skill-creator → ct-skill-author}/scripts/quick_validate.py +0 -0
- /package/skills/{ct-skill-creator → ct-skill-author}/scripts/run_eval.py +0 -0
- /package/skills/{ct-skill-creator → ct-skill-author}/scripts/run_loop.py +0 -0
- /package/skills/{ct-skill-creator → ct-skill-author}/scripts/utils.py +0 -0
- /package/skills/{ct-skill-validator → ct-skill-author}/scripts/validate.py +0 -0
package/README.md
CHANGED
|
@@ -31,69 +31,52 @@ yarn add @cleocode/skills
|
|
|
31
31
|
|
|
32
32
|
## Included Skills
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
|
47
|
-
|
|
48
|
-
| **ct-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
|
56
|
-
|
|
57
|
-
| **ct-
|
|
58
|
-
| **ct-
|
|
59
|
-
| **ct-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
|
64
|
-
|
|
65
|
-
| **ct-
|
|
66
|
-
| **ct-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|-------|---------|-------------|
|
|
81
|
-
| **ct-cleo** | CLEO | Task management protocol operations |
|
|
82
|
-
|
|
83
|
-
### Specialized Skills
|
|
84
|
-
|
|
85
|
-
Additional skills for specific domains:
|
|
86
|
-
- **better-auth-svelte** - Better-Auth with SvelteKit
|
|
87
|
-
- **drizzle-orm** - Drizzle ORM guidance
|
|
88
|
-
- **expo-production-deploy** - Expo app deployment
|
|
89
|
-
- **flarectl** - Cloudflare CLI management
|
|
90
|
-
- **github-guru** - GitHub workflows
|
|
91
|
-
- **neonctl** - Neon Postgres CLI
|
|
92
|
-
- **payment-provider-oauth** - Payment provider OAuth
|
|
93
|
-
- **railway** - Railway infrastructure
|
|
94
|
-
- **resend** - Resend email API
|
|
95
|
-
- **svelte5-sveltekit** - Svelte 5 development
|
|
96
|
-
- **yt-dlp-webapp** - YouTube download backends
|
|
34
|
+
Tiers and install behaviour come from each SKILL.md's `metadata` (owner decision
|
|
35
|
+
D11157). `packages/skills/skills/manifest.json` is generated from them by
|
|
36
|
+
`node scripts/skills/generate-manifest.mjs`; gates 29-31 keep the manifest,
|
|
37
|
+
installability and every documented `cleo` command honest.
|
|
38
|
+
|
|
39
|
+
### Core (installed to every harness, always relevant)
|
|
40
|
+
|
|
41
|
+
| Skill | Purpose |
|
|
42
|
+
|-------|---------|
|
|
43
|
+
| **ct-cleo** | CLEO task management protocol - session, task, and workflow guidance. |
|
|
44
|
+
| **ct-dev-workflow** | Development workflow orchestration for task-driven development with atomic commits, conventional commit messag… |
|
|
45
|
+
| **ct-documentor** | Documentation coordinator with CLEO style guide compliance. |
|
|
46
|
+
| **ct-lead** | Phase Lead orchestration playbook for spawning and supervising a parallel worker swarm in one wave. |
|
|
47
|
+
| **ct-orchestrator** | Pipeline-aware orchestration skill for managing complex workflows through subagent delegation. |
|
|
48
|
+
| **ct-task-executor** | General implementation task execution for completing assigned CLEO tasks by following instructions and produci… |
|
|
49
|
+
|
|
50
|
+
### On demand (installed; loaded by description or by stage guidance)
|
|
51
|
+
|
|
52
|
+
| Skill | Purpose |
|
|
53
|
+
|-------|---------|
|
|
54
|
+
| **ct-adr-recorder** | Records Architecture Decision Records from accepted consensus verdicts. |
|
|
55
|
+
| **ct-artifact-publisher** | Builds and publishes artifacts to registries (npm, PyPI, cargo, docker, GitHub releases, generic tarballs) fol… |
|
|
56
|
+
| **ct-codebase-mapper** | Orient in an unfamiliar or large codebase with CLEO's code-intelligence graph (cleo nexus) and project map (cl… |
|
|
57
|
+
| **ct-consensus-voter** | Runs structured multi-agent voting for decision tasks with confidence scores, conflict detection, and HITL esc… |
|
|
58
|
+
| **ct-contribution** | Guided workflow for multi-agent consensus contributions. |
|
|
59
|
+
| **ct-council** | Convene "The Council" — a 5-advisor, shuffled gate-based peer-review, chairman-synthesis workflow for reviewin… |
|
|
60
|
+
| **ct-epic-architect** | Epic planning and task decomposition for breaking down large initiatives into atomic, executable tasks. |
|
|
61
|
+
| **ct-ivt-looper** | Runs a project-agnostic autonomous Implement-then-Validate-then-Test compliance loop on any git worktree. |
|
|
62
|
+
| **ct-provenance-keeper** | Generates in-toto v1 attestations, SLSA-level provenance records, SBOMs (CycloneDX or SPDX), and sigstore/cosi… |
|
|
63
|
+
| **ct-release-orchestrator** | Orchestrates the canonical 4-verb release pipeline introduced by SPEC-T9345: cleo release plan, then cleo rele… |
|
|
64
|
+
| **ct-research-agent** | Multi-source research and investigation combining web search, documentation lookup via Context7, and codebase … |
|
|
65
|
+
| **ct-spec-writer** | Technical specification writing using RFC 2119 language for clear, unambiguous requirements. |
|
|
66
|
+
| **ct-validator** | Compliance validation for verifying systems, documents, or code against requirements, schemas, or standards. |
|
|
67
|
+
|
|
68
|
+
### Internal (CLEO development only; never installed to a harness)
|
|
69
|
+
|
|
70
|
+
| Skill | Purpose |
|
|
71
|
+
|-------|---------|
|
|
72
|
+
| **ct-grade** | CLEO session grading and A/B behavioral analysis with token tracking. |
|
|
73
|
+
| **ct-skill-author** | Create, improve and validate CLEO skills. |
|
|
74
|
+
|
|
75
|
+
Merged or retired in T12649: ct-docs-write and ct-docs-review are references of
|
|
76
|
+
ct-documentor; ct-memory and ct-stickynote are references of ct-cleo;
|
|
77
|
+
ct-skill-creator and ct-skill-validator became ct-skill-author; ct-docs-lookup
|
|
78
|
+
(use the Context7 MCP) and ct-master-tac were retired; signaldock-connect moved
|
|
79
|
+
to the SignalDock repository.
|
|
97
80
|
|
|
98
81
|
## Skill Structure
|
|
99
82
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for `@cleocode/skills/frontmatter.mjs` — the SKILL.md frontmatter
|
|
3
|
+
* rules shared by gate 29 and `cleo skills validate` (T12655).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** Tag-like text the Agent Skills standard rejects. */
|
|
7
|
+
export declare const TAG_PATTERN: RegExp;
|
|
8
|
+
/** Allowed `metadata.tier` values. */
|
|
9
|
+
export declare const SKILL_TIERS: readonly ['core', 'on-demand', 'internal'];
|
|
10
|
+
/** Allowed `metadata.install` values. */
|
|
11
|
+
export declare const SKILL_INSTALL_MODES: readonly ['harness', 'internal'];
|
|
12
|
+
/** Allowed `metadata.stability` values. */
|
|
13
|
+
export declare const SKILL_STABILITIES: readonly ['experimental', 'stable', 'deprecated'];
|
|
14
|
+
/** Numeric tier written into manifest entries. */
|
|
15
|
+
export declare const TIER_NUMBER: { readonly core: 0; readonly 'on-demand': 1; readonly internal: 3 };
|
|
16
|
+
/** CAAMP catalogue `category` derived from the tier. */
|
|
17
|
+
export declare const CATEGORY_FOR_TIER: {
|
|
18
|
+
readonly core: 'core';
|
|
19
|
+
readonly 'on-demand': 'recommended';
|
|
20
|
+
readonly internal: 'meta';
|
|
21
|
+
};
|
|
22
|
+
/** Top-level keys a SKILL.md may not declare (derived from `metadata.tier`). */
|
|
23
|
+
export declare const DERIVED_TOP_LEVEL_KEYS: readonly ['tier', 'core', 'category'];
|
|
24
|
+
/** Top-level keys that moved under `metadata:`. */
|
|
25
|
+
export declare const MOVED_TO_METADATA_KEYS: readonly ['loomStage'];
|
|
26
|
+
/** Maximum `description` length accepted by harness skill loaders. */
|
|
27
|
+
export declare const MAX_DESCRIPTION_LENGTH: 1024;
|
|
28
|
+
|
|
29
|
+
/** Parsed SKILL.md frontmatter. */
|
|
30
|
+
export interface ParsedFrontmatter {
|
|
31
|
+
/** No parse errors. */
|
|
32
|
+
ok: boolean;
|
|
33
|
+
/** Top-level scalars (block scalars folded to one line). */
|
|
34
|
+
fields: Record<string, string>;
|
|
35
|
+
/** The nested `metadata:` map. */
|
|
36
|
+
metadata: Record<string, string>;
|
|
37
|
+
/** Lists nested under `metadata:` (e.g. `covers`). */
|
|
38
|
+
metadataLists: Record<string, string[]>;
|
|
39
|
+
/** Top-level block and inline lists. */
|
|
40
|
+
lists: Record<string, string[]>;
|
|
41
|
+
/** Every top-level key in order, duplicates included. */
|
|
42
|
+
keys: string[];
|
|
43
|
+
/** Parse errors. */
|
|
44
|
+
errors: string[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Parse SKILL.md text into its frontmatter. */
|
|
48
|
+
export declare function parseFrontmatter(text: string): ParsedFrontmatter;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Validate one bundled skill's frontmatter against the CLEO contract.
|
|
52
|
+
*
|
|
53
|
+
* @param fm - Parsed frontmatter plus `name`, the skill's directory name.
|
|
54
|
+
* @param ctx - `loomStages` enables the `metadata.loomStage` binding check.
|
|
55
|
+
* @returns Human-readable problems (empty when valid).
|
|
56
|
+
*/
|
|
57
|
+
export declare function validateFrontmatter(
|
|
58
|
+
fm: ParsedFrontmatter & { name: string },
|
|
59
|
+
ctx?: { loomStages?: Map<string, string> },
|
|
60
|
+
): string[];
|
package/frontmatter.mjs
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SKILL.md frontmatter rules for CLEO's bundled skills — the one copy shared
|
|
3
|
+
* by the arch gates (`scripts/skills/lib/skill-frontmatter.mjs`, gate 29) and
|
|
4
|
+
* the runtime (`cleo skills validate`, T12655), so the two cannot disagree.
|
|
5
|
+
*
|
|
6
|
+
* Pure: no fs, no dependencies. The gates run on a bare checkout (no
|
|
7
|
+
* `pnpm install`), and the frontmatter grammar used by skills is a flat map
|
|
8
|
+
* plus one nested `metadata:` map and folded/literal block scalars. Anything
|
|
9
|
+
* outside that grammar is reported as an error rather than guessed at.
|
|
10
|
+
*
|
|
11
|
+
* @module @cleocode/skills/frontmatter
|
|
12
|
+
* @task T12648
|
|
13
|
+
* @task T12655
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** Tag-like text the Agent Skills standard rejects (same test as CAAMP's validateSkill). */
|
|
17
|
+
export const TAG_PATTERN = /<[^>]+>/;
|
|
18
|
+
|
|
19
|
+
/** Allowed `metadata.tier` values — the skill's delivery class (D11157). */
|
|
20
|
+
export const SKILL_TIERS = /** @type {const} */ (['core', 'on-demand', 'internal']);
|
|
21
|
+
|
|
22
|
+
/** Allowed `metadata.install` values. */
|
|
23
|
+
export const SKILL_INSTALL_MODES = /** @type {const} */ (['harness', 'internal']);
|
|
24
|
+
|
|
25
|
+
/** Allowed `metadata.stability` values. */
|
|
26
|
+
export const SKILL_STABILITIES = /** @type {const} */ (['experimental', 'stable', 'deprecated']);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Numeric tier written into manifest entries for existing numeric readers
|
|
30
|
+
* (CAAMP catalogue, `packages/skills/index.js`), derived from the class.
|
|
31
|
+
*/
|
|
32
|
+
export const TIER_NUMBER = /** @type {const} */ ({ core: 0, 'on-demand': 1, internal: 3 });
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* CAAMP catalogue `category` written into manifest entries, derived from the
|
|
36
|
+
* class so the two cannot disagree (T12653).
|
|
37
|
+
*/
|
|
38
|
+
export const CATEGORY_FOR_TIER = /** @type {const} */ ({
|
|
39
|
+
core: 'core',
|
|
40
|
+
'on-demand': 'recommended',
|
|
41
|
+
internal: 'meta',
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Top-level keys a SKILL.md may not declare, because the manifest derives
|
|
46
|
+
* them from `metadata.tier` (T12648, T12653).
|
|
47
|
+
*/
|
|
48
|
+
export const DERIVED_TOP_LEVEL_KEYS = /** @type {const} */ (['tier', 'core', 'category']);
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Top-level keys that moved under `metadata:` (T12649). Each maps to its
|
|
52
|
+
* `metadata.<key>` home.
|
|
53
|
+
*/
|
|
54
|
+
export const MOVED_TO_METADATA_KEYS = /** @type {const} */ (['loomStage']);
|
|
55
|
+
|
|
56
|
+
/** Maximum `description` length accepted by harness skill loaders. */
|
|
57
|
+
export const MAX_DESCRIPTION_LENGTH = 1024;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Parse the scalar on the right of `key:`.
|
|
61
|
+
*
|
|
62
|
+
* @param {string} raw - Text after the colon.
|
|
63
|
+
* @returns {string} Unquoted scalar ('' for an empty value).
|
|
64
|
+
*/
|
|
65
|
+
function scalar(raw) {
|
|
66
|
+
const v = raw.trim();
|
|
67
|
+
if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
|
|
68
|
+
return v.slice(1, -1);
|
|
69
|
+
}
|
|
70
|
+
return v;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Parse SKILL.md text into its frontmatter.
|
|
75
|
+
*
|
|
76
|
+
* @param {string} text - Full SKILL.md content.
|
|
77
|
+
* @returns {{
|
|
78
|
+
* ok: boolean,
|
|
79
|
+
* fields: Record<string, string>,
|
|
80
|
+
* metadata: Record<string, string>,
|
|
81
|
+
* metadataLists: Record<string, string[]>,
|
|
82
|
+
* lists: Record<string, string[]>,
|
|
83
|
+
* keys: string[],
|
|
84
|
+
* errors: string[],
|
|
85
|
+
* }} `fields` holds top-level scalars (block scalars folded to one line;
|
|
86
|
+
* nested maps omitted), `lists` top-level block (`- item`) and inline
|
|
87
|
+
* (`[a, b]`) lists, `keys` every top-level key in order (duplicates
|
|
88
|
+
* included), `metadata` the nested `metadata:` map and `metadataLists`
|
|
89
|
+
* its nested lists (`metadata.covers`).
|
|
90
|
+
*/
|
|
91
|
+
export function parseFrontmatter(text) {
|
|
92
|
+
const errors = [];
|
|
93
|
+
// T12649: a CRLF checkout (Windows autocrlf) must parse identically.
|
|
94
|
+
const lines = text.replace(/\r\n?/g, '\n').split('\n');
|
|
95
|
+
if (lines[0] !== '---') {
|
|
96
|
+
return {
|
|
97
|
+
ok: false,
|
|
98
|
+
fields: {},
|
|
99
|
+
metadata: {},
|
|
100
|
+
metadataLists: {},
|
|
101
|
+
lists: {},
|
|
102
|
+
keys: [],
|
|
103
|
+
errors: ['no frontmatter block'],
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const end = lines.indexOf('---', 1);
|
|
107
|
+
if (end === -1) {
|
|
108
|
+
return {
|
|
109
|
+
ok: false,
|
|
110
|
+
fields: {},
|
|
111
|
+
metadata: {},
|
|
112
|
+
metadataLists: {},
|
|
113
|
+
lists: {},
|
|
114
|
+
keys: [],
|
|
115
|
+
errors: ['unterminated frontmatter'],
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const body = lines.slice(1, end);
|
|
119
|
+
/** @type {Record<string, string>} */
|
|
120
|
+
const fields = {};
|
|
121
|
+
/** @type {Record<string, string>} */
|
|
122
|
+
const metadata = {};
|
|
123
|
+
/** @type {Record<string, string[]>} */
|
|
124
|
+
const lists = {};
|
|
125
|
+
/** @type {Record<string, string[]>} */
|
|
126
|
+
const metadataLists = {};
|
|
127
|
+
const keys = [];
|
|
128
|
+
|
|
129
|
+
for (let i = 0; i < body.length; i++) {
|
|
130
|
+
const line = body[i];
|
|
131
|
+
if (line.trim() === '' || line.trimStart().startsWith('#')) continue;
|
|
132
|
+
if (/^\s/.test(line) || line.startsWith('- ')) continue; // continuation of a nested value
|
|
133
|
+
const m = /^([A-Za-z][\w-]*):(.*)$/.exec(line);
|
|
134
|
+
if (!m) {
|
|
135
|
+
errors.push(`unparseable frontmatter line ${i + 2}: ${line}`);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const [, key, rest] = m;
|
|
139
|
+
keys.push(key);
|
|
140
|
+
const value = rest.trim();
|
|
141
|
+
|
|
142
|
+
if (key === 'metadata' && value === '') {
|
|
143
|
+
// A blank line inside the block does not end it; the next unindented
|
|
144
|
+
// key does (T12649).
|
|
145
|
+
// A nested list (` covers:` then ` - item`) is collected into
|
|
146
|
+
// metadataLists (T12124).
|
|
147
|
+
let listKey = null;
|
|
148
|
+
for (let j = i + 1; j < body.length; j++) {
|
|
149
|
+
if (body[j].trim() === '') continue;
|
|
150
|
+
if (!/^\s+\S/.test(body[j])) break;
|
|
151
|
+
const item = /^\s+-\s+(.*)$/.exec(body[j]);
|
|
152
|
+
if (item && listKey) {
|
|
153
|
+
metadataLists[listKey].push(scalar(item[1]));
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const mm = /^\s+([A-Za-z][\w-]*):(.*)$/.exec(body[j]);
|
|
157
|
+
if (!mm) continue;
|
|
158
|
+
listKey = null;
|
|
159
|
+
if (mm[2].trim() === '') {
|
|
160
|
+
listKey = mm[1];
|
|
161
|
+
metadataLists[listKey] = [];
|
|
162
|
+
} else {
|
|
163
|
+
metadata[mm[1]] = scalar(mm[2]);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (value === '>' || value === '>-' || value === '|' || value === '|-') {
|
|
169
|
+
const parts = [];
|
|
170
|
+
for (let j = i + 1; j < body.length && (/^\s+\S/.test(body[j]) || body[j] === ''); j++) {
|
|
171
|
+
parts.push(body[j].trim());
|
|
172
|
+
}
|
|
173
|
+
fields[key] = parts.filter(Boolean).join(' ');
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (value === '') {
|
|
177
|
+
// A block list (`key:` then ` - item` lines) — T12649.
|
|
178
|
+
const items = [];
|
|
179
|
+
for (let j = i + 1; j < body.length && /^\s+-\s/.test(body[j]); j++) {
|
|
180
|
+
items.push(scalar(body[j].replace(/^\s+-\s+/, '')));
|
|
181
|
+
}
|
|
182
|
+
lists[key] = items;
|
|
183
|
+
} else if (value.startsWith('[') && value.endsWith(']')) {
|
|
184
|
+
lists[key] = value
|
|
185
|
+
.slice(1, -1)
|
|
186
|
+
.split(',')
|
|
187
|
+
.map((s) => scalar(s))
|
|
188
|
+
.filter(Boolean);
|
|
189
|
+
}
|
|
190
|
+
fields[key] = scalar(value);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return { ok: errors.length === 0, fields, metadata, metadataLists, lists, keys, errors };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Validate one skill's frontmatter against the SSoT contract.
|
|
198
|
+
*
|
|
199
|
+
* @param {ReturnType<typeof parseFrontmatter> & { name: string }} fm - Parsed frontmatter
|
|
200
|
+
* plus `name`, the skill's directory name.
|
|
201
|
+
* @param {{ loomStages?: Map<string, string> }} [ctx] - `loomStages` (from
|
|
202
|
+
* {@link loadLoomStages}) enables the `metadata.loomStage` binding check.
|
|
203
|
+
* @returns {string[]} Human-readable problems (empty when valid).
|
|
204
|
+
*/
|
|
205
|
+
export function validateFrontmatter(fm, ctx = {}) {
|
|
206
|
+
const problems = [...fm.errors];
|
|
207
|
+
const seen = new Set();
|
|
208
|
+
for (const k of fm.keys) {
|
|
209
|
+
if (seen.has(k)) problems.push(`duplicate top-level key '${k}' (invalid YAML)`);
|
|
210
|
+
seen.add(k);
|
|
211
|
+
}
|
|
212
|
+
if (fm.fields.name !== fm.name) {
|
|
213
|
+
problems.push(`name '${fm.fields.name ?? ''}' does not equal its directory '${fm.name}'`);
|
|
214
|
+
}
|
|
215
|
+
const description = fm.fields.description ?? '';
|
|
216
|
+
if (description === '') problems.push('description is missing');
|
|
217
|
+
if (description.length > MAX_DESCRIPTION_LENGTH) {
|
|
218
|
+
problems.push(`description is ${description.length} chars (max ${MAX_DESCRIPTION_LENGTH})`);
|
|
219
|
+
}
|
|
220
|
+
// T12655: the Agent Skills standard (CAAMP validateSkill) rejects tag-like
|
|
221
|
+
// text; `epic-<TID>` in a description passed this gate and failed
|
|
222
|
+
// `cleo skills validate`. Write placeholders as `{TID}`.
|
|
223
|
+
for (const field of ['name', 'description']) {
|
|
224
|
+
if (TAG_PATTERN.test(fm.fields[field] ?? '')) {
|
|
225
|
+
problems.push(`${field} contains angle-bracket (XML/HTML tag) text; write placeholders as {name}`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
for (const key of DERIVED_TOP_LEVEL_KEYS) {
|
|
229
|
+
if (key in fm.fields) {
|
|
230
|
+
problems.push(
|
|
231
|
+
`top-level ${key} is not allowed — it is derived from metadata.tier (core|on-demand|internal)`,
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
for (const key of MOVED_TO_METADATA_KEYS) {
|
|
236
|
+
if (key in fm.fields) problems.push(`top-level ${key} moved — declare metadata.${key}`);
|
|
237
|
+
}
|
|
238
|
+
const md = fm.metadata;
|
|
239
|
+
if (md.loomStage !== undefined && ctx.loomStages) {
|
|
240
|
+
const owner = ctx.loomStages.get(md.loomStage);
|
|
241
|
+
if (owner === undefined) {
|
|
242
|
+
problems.push(
|
|
243
|
+
`metadata.loomStage '${md.loomStage}' is not a STAGE_SKILL_MAP stage or a .cant protocol id`,
|
|
244
|
+
);
|
|
245
|
+
} else if (owner !== fm.name) {
|
|
246
|
+
problems.push(
|
|
247
|
+
`metadata.loomStage '${md.loomStage}' is bound to '${owner}', not '${fm.name}'`,
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (!md.version) problems.push('metadata.version is missing');
|
|
252
|
+
else if (!/^\d+\.\d+\.\d+$/.test(md.version)) {
|
|
253
|
+
problems.push(`metadata.version '${md.version}' is not X.Y.Z`);
|
|
254
|
+
}
|
|
255
|
+
if (fm.fields.version !== undefined && fm.fields.version !== md.version) {
|
|
256
|
+
problems.push(
|
|
257
|
+
`top-level version '${fm.fields.version}' disagrees with metadata.version '${md.version ?? ''}'`,
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
if (!SKILL_TIERS.includes(md.tier)) {
|
|
261
|
+
problems.push(`metadata.tier '${md.tier ?? ''}' must be one of ${SKILL_TIERS.join('|')}`);
|
|
262
|
+
}
|
|
263
|
+
if (!SKILL_INSTALL_MODES.includes(md.install)) {
|
|
264
|
+
problems.push(
|
|
265
|
+
`metadata.install '${md.install ?? ''}' must be one of ${SKILL_INSTALL_MODES.join('|')}`,
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
if (md.tier === 'internal' && md.install === 'harness') {
|
|
269
|
+
problems.push('metadata.tier internal cannot have metadata.install harness');
|
|
270
|
+
}
|
|
271
|
+
if (md.stability !== undefined && !SKILL_STABILITIES.includes(md.stability)) {
|
|
272
|
+
problems.push(
|
|
273
|
+
`metadata.stability '${md.stability}' must be one of ${SKILL_STABILITIES.join('|')}`,
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
return problems;
|
|
277
|
+
}
|
package/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// --- Skill Entry (from skills.json) ---
|
|
1
|
+
// --- Skill Entry (derived from skills/manifest.json, T12653) ---
|
|
2
2
|
|
|
3
3
|
export interface SkillEntry {
|
|
4
4
|
name: string;
|
|
@@ -87,7 +87,7 @@ export interface ValidationResult {
|
|
|
87
87
|
|
|
88
88
|
// --- Existing exports (preserved) ---
|
|
89
89
|
|
|
90
|
-
/** All skill entries from skills.json */
|
|
90
|
+
/** All skill entries, derived from skills/manifest.json */
|
|
91
91
|
export declare const skills: SkillEntry[];
|
|
92
92
|
|
|
93
93
|
/** Parsed manifest.json dispatch registry */
|
|
@@ -99,7 +99,7 @@ export declare const shared: Record<string, unknown>;
|
|
|
99
99
|
/** List all skill names */
|
|
100
100
|
export declare function listSkills(): string[];
|
|
101
101
|
|
|
102
|
-
/** Get skill metadata
|
|
102
|
+
/** Get skill metadata by name */
|
|
103
103
|
export declare function getSkill(name: string): SkillEntry | undefined;
|
|
104
104
|
|
|
105
105
|
/** Resolve absolute path to a skill's SKILL.md file */
|
package/index.js
CHANGED
|
@@ -19,23 +19,48 @@ const libraryRoot = LIBRARY_ROOT;
|
|
|
19
19
|
|
|
20
20
|
// --- Core data ---
|
|
21
21
|
|
|
22
|
-
/**
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
/** Parsed manifest.json dispatch registry */
|
|
22
|
+
/**
|
|
23
|
+
* Parsed manifest.json — the only skills index. It is generated from SKILL.md
|
|
24
|
+
* frontmatter by scripts/skills/generate-manifest.mjs; skills.json was
|
|
25
|
+
* removed in T12653.
|
|
26
|
+
*/
|
|
28
27
|
const manifest = JSON.parse(
|
|
29
28
|
fs.readFileSync(path.join(SKILLS_ROOT, 'manifest.json'), 'utf8')
|
|
30
29
|
);
|
|
31
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Map a manifest entry to the catalog SkillEntry shape (the manifest `path`
|
|
33
|
+
* names the directory; the catalog path names SKILL.md). Mirrors
|
|
34
|
+
* catalogEntryFromManifest in @cleocode/caamp.
|
|
35
|
+
* @param {object} m - Manifest skill entry
|
|
36
|
+
* @returns {object} SkillEntry
|
|
37
|
+
*/
|
|
38
|
+
function toSkillEntry(m) {
|
|
39
|
+
return {
|
|
40
|
+
name: m.name,
|
|
41
|
+
description: m.description,
|
|
42
|
+
version: m.version,
|
|
43
|
+
path: m.path.endsWith('SKILL.md') ? m.path : `${m.path}/SKILL.md`,
|
|
44
|
+
references: m.references || [],
|
|
45
|
+
core: m.core === undefined ? m.deliveryTier === 'core' : m.core,
|
|
46
|
+
category: m.category || 'recommended',
|
|
47
|
+
tier: m.tier,
|
|
48
|
+
protocol: m.protocol === undefined ? null : m.protocol,
|
|
49
|
+
dependencies: m.dependencies || [],
|
|
50
|
+
sharedResources: m.sharedResources || [],
|
|
51
|
+
compatibility: m.compatibility || [],
|
|
52
|
+
license: m.license || 'MIT',
|
|
53
|
+
metadata: { deliveryTier: m.deliveryTier, install: m.install },
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
32
57
|
/** Parsed _shared/placeholders.json */
|
|
33
58
|
const shared = JSON.parse(
|
|
34
59
|
fs.readFileSync(path.join(SHARED_ROOT, 'placeholders.json'), 'utf8')
|
|
35
60
|
);
|
|
36
61
|
|
|
37
|
-
/** All skill entries from
|
|
38
|
-
const skills =
|
|
62
|
+
/** All skill entries, derived from manifest.json */
|
|
63
|
+
const skills = manifest.skills.map(toSkillEntry);
|
|
39
64
|
|
|
40
65
|
// --- Existing API (preserved) ---
|
|
41
66
|
|
|
@@ -48,7 +73,7 @@ function listSkills() {
|
|
|
48
73
|
}
|
|
49
74
|
|
|
50
75
|
/**
|
|
51
|
-
* Get skill metadata
|
|
76
|
+
* Get skill metadata by name.
|
|
52
77
|
* @param {string} name - Skill name (e.g. "ct-research-agent")
|
|
53
78
|
* @returns {object|undefined}
|
|
54
79
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cleocode/skills",
|
|
3
|
-
"version": "2026.9.
|
|
3
|
+
"version": "2026.9.23",
|
|
4
4
|
"description": "CLEO skill definitions - bundled with CLEO monorepo",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
"profiles",
|
|
10
10
|
"*.json",
|
|
11
11
|
"*.js",
|
|
12
|
-
"*.d.ts"
|
|
12
|
+
"*.d.ts",
|
|
13
|
+
"*.mjs",
|
|
14
|
+
"*.d.mts"
|
|
13
15
|
],
|
|
14
16
|
"devDependencies": {
|
|
15
17
|
"vitest": "^4.1.4"
|
package/profiles/core.json
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "core",
|
|
3
|
-
"description": "
|
|
3
|
+
"description": "The six core skills (D11157): CLEO protocol, orchestration, phase leads, task execution, dev workflow and documentation",
|
|
4
4
|
"extends": "minimal",
|
|
5
|
-
"skills": [
|
|
6
|
-
|
|
5
|
+
"skills": [
|
|
6
|
+
"ct-orchestrator",
|
|
7
|
+
"ct-lead",
|
|
8
|
+
"ct-dev-workflow",
|
|
9
|
+
"ct-documentor"
|
|
10
|
+
],
|
|
11
|
+
"includeProtocols": [
|
|
12
|
+
"agent-protocol"
|
|
13
|
+
]
|
|
7
14
|
}
|
package/profiles/full.json
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "full",
|
|
3
|
-
"description": "
|
|
3
|
+
"description": "Every skill installed to harnesses. Equal to recommended: internal skills (ct-grade, ct-skill-author) are never installed (D11157)",
|
|
4
4
|
"extends": "recommended",
|
|
5
|
-
"skills": [
|
|
6
|
-
|
|
7
|
-
"
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
"skills": [],
|
|
6
|
+
"includeProtocols": [
|
|
7
|
+
"implementation",
|
|
8
|
+
"contribution",
|
|
9
|
+
"release",
|
|
10
|
+
"testing",
|
|
11
|
+
"artifact-publish",
|
|
12
|
+
"provenance"
|
|
13
|
+
]
|
|
10
14
|
}
|
|
@@ -1,7 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "recommended",
|
|
3
|
-
"description": "
|
|
3
|
+
"description": "Core plus the on-demand skills: every LOOM (RCASD-IVTR+C) stage skill, council and codebase mapping",
|
|
4
4
|
"extends": "core",
|
|
5
|
-
"skills": [
|
|
6
|
-
|
|
5
|
+
"skills": [
|
|
6
|
+
"ct-research-agent",
|
|
7
|
+
"ct-consensus-voter",
|
|
8
|
+
"ct-adr-recorder",
|
|
9
|
+
"ct-spec-writer",
|
|
10
|
+
"ct-epic-architect",
|
|
11
|
+
"ct-validator",
|
|
12
|
+
"ct-ivt-looper",
|
|
13
|
+
"ct-release-orchestrator",
|
|
14
|
+
"ct-contribution",
|
|
15
|
+
"ct-artifact-publisher",
|
|
16
|
+
"ct-provenance-keeper",
|
|
17
|
+
"ct-council",
|
|
18
|
+
"ct-codebase-mapper"
|
|
19
|
+
],
|
|
20
|
+
"includeProtocols": [
|
|
21
|
+
"research",
|
|
22
|
+
"consensus",
|
|
23
|
+
"specification",
|
|
24
|
+
"decomposition",
|
|
25
|
+
"validation",
|
|
26
|
+
"adr",
|
|
27
|
+
"testing",
|
|
28
|
+
"release",
|
|
29
|
+
"contribution",
|
|
30
|
+
"artifact-publish",
|
|
31
|
+
"provenance"
|
|
32
|
+
]
|
|
7
33
|
}
|