@cleocode/skills 2026.5.82 → 2026.5.84

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 (44) hide show
  1. package/README.md +0 -1
  2. package/package.json +1 -1
  3. package/profiles/recommended.json +1 -1
  4. package/skills/_shared/__tests__/lifecycle-protocol-reconcile.test.ts +112 -0
  5. package/skills/_shared/__tests__/loom-adr-links.test.ts +163 -0
  6. package/skills/_shared/__tests__/loom-stage-coverage.test.ts +167 -0
  7. package/skills/ct-adr-recorder/SKILL.md +18 -0
  8. package/skills/ct-consensus-voter/SKILL.md +14 -0
  9. package/skills/ct-contribution/SKILL.md +80 -0
  10. package/skills/ct-epic-architect/SKILL.md +15 -0
  11. package/skills/ct-ivt-looper/SKILL.md +32 -0
  12. package/skills/ct-release-orchestrator/SKILL.md +16 -0
  13. package/skills/ct-research-agent/SKILL.md +15 -0
  14. package/skills/ct-spec-writer/SKILL.md +15 -0
  15. package/skills/ct-task-executor/SKILL.md +15 -0
  16. package/skills/ct-validator/SKILL.md +35 -0
  17. package/skills/manifest.json +81 -9
  18. package/skills/ct-grade-v2-1/MIGRATION.md +0 -28
  19. package/skills/ct-grade-v2-1/SKILL.md +0 -235
  20. package/skills/ct-grade-v2-1/agents/analysis-reporter.md +0 -203
  21. package/skills/ct-grade-v2-1/agents/blind-comparator.md +0 -157
  22. package/skills/ct-grade-v2-1/agents/scenario-runner.md +0 -160
  23. package/skills/ct-grade-v2-1/evals/evals.json +0 -74
  24. package/skills/ct-grade-v2-1/grade-viewer/__pycache__/build_op_stats.cpython-314.pyc +0 -0
  25. package/skills/ct-grade-v2-1/grade-viewer/__pycache__/generate_grade_review.cpython-314.pyc +0 -0
  26. package/skills/ct-grade-v2-1/grade-viewer/build_op_stats.py +0 -174
  27. package/skills/ct-grade-v2-1/grade-viewer/eval-analysis.json +0 -41
  28. package/skills/ct-grade-v2-1/grade-viewer/eval-report.md +0 -37
  29. package/skills/ct-grade-v2-1/grade-viewer/generate_grade_review.py +0 -1023
  30. package/skills/ct-grade-v2-1/grade-viewer/generate_grade_viewer.py +0 -548
  31. package/skills/ct-grade-v2-1/grade-viewer/grade-review-eval.html +0 -613
  32. package/skills/ct-grade-v2-1/grade-viewer/grade-review.html +0 -1532
  33. package/skills/ct-grade-v2-1/grade-viewer/viewer.html +0 -620
  34. package/skills/ct-grade-v2-1/manifest-entry.json +0 -31
  35. package/skills/ct-grade-v2-1/references/ab-testing.md +0 -173
  36. package/skills/ct-grade-v2-1/references/domains-ssot.md +0 -156
  37. package/skills/ct-grade-v2-1/references/grade-spec-v2.md +0 -167
  38. package/skills/ct-grade-v2-1/references/playbook-v2.md +0 -325
  39. package/skills/ct-grade-v2-1/references/token-tracking.md +0 -200
  40. package/skills/ct-grade-v2-1/scripts/generate_report.py +0 -419
  41. package/skills/ct-grade-v2-1/scripts/run_ab_test.py +0 -493
  42. package/skills/ct-grade-v2-1/scripts/run_scenario.py +0 -396
  43. package/skills/ct-grade-v2-1/scripts/setup_run.py +0 -207
  44. package/skills/ct-grade-v2-1/scripts/token_tracker.py +0 -175
package/README.md CHANGED
@@ -72,7 +72,6 @@ yarn add @cleocode/skills
72
72
  | Skill | Purpose | Description |
73
73
  |-------|---------|-------------|
74
74
  | **ct-grade** | Grading | Session quality evaluation |
75
- | **ct-grade-v2-1** | Grading V2 | Enhanced grading with scenarios |
76
75
  | **ct-stickynote** | Notes | Quick ephemeral sticky notes |
77
76
 
78
77
  ### Integration Skills
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cleocode/skills",
3
- "version": "2026.5.82",
3
+ "version": "2026.5.84",
4
4
  "description": "CLEO skill definitions - bundled with CLEO monorepo",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -2,6 +2,6 @@
2
2
  "name": "recommended",
3
3
  "description": "Full LOOM (RCASD-IVTR+C pipeline) skills for epic-driven development",
4
4
  "extends": "core",
5
- "skills": ["ct-epic-architect", "ct-research-agent", "ct-spec-writer", "ct-validator", "loom"],
5
+ "skills": ["ct-council", "ct-epic-architect", "ct-research-agent", "ct-spec-writer", "ct-validator", "loom"],
6
6
  "includeProtocols": ["research", "consensus", "specification", "decomposition", "validation", "adr"]
7
7
  }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Lifecycle-protocol reconcile gate (T9672).
3
+ *
4
+ * Asserts the SET equality:
5
+ *
6
+ * SET( cleo lifecycle stages )
7
+ * ==
8
+ * SET( manifest.dispatch_matrix.by_protocol keys )
9
+ * − { "artifact-publish", "provenance", "agent-protocol" } # cross-cutting
10
+ *
11
+ * No normalization, no dashed-alias allowance. This is the strict gate that
12
+ * lands together with the manifest rename `architecture-decision` →
13
+ * `architecture_decision` so that a future drift fails CI.
14
+ *
15
+ * @task T9672
16
+ * @epic T9568
17
+ */
18
+
19
+ import { readFileSync } from 'node:fs';
20
+ import { dirname, resolve } from 'node:path';
21
+ import { fileURLToPath } from 'node:url';
22
+ import { describe, expect, it } from 'vitest';
23
+
24
+ const thisDir = dirname(fileURLToPath(import.meta.url));
25
+ const manifestPath = resolve(thisDir, '../../manifest.json');
26
+
27
+ interface Manifest {
28
+ dispatch_matrix: {
29
+ by_protocol: Record<string, string>;
30
+ by_keyword: Record<string, string>;
31
+ };
32
+ }
33
+
34
+ const manifest: Manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
35
+
36
+ /**
37
+ * The canonical 10 LOOM lifecycle stages emitted by `cleo lifecycle --help`.
38
+ * Underscored form is authoritative — see `packages/core/src/lifecycle/`.
39
+ */
40
+ const LIFECYCLE_STAGES = [
41
+ 'research',
42
+ 'consensus',
43
+ 'architecture_decision',
44
+ 'specification',
45
+ 'decomposition',
46
+ 'implementation',
47
+ 'validation',
48
+ 'testing',
49
+ 'release',
50
+ 'contribution',
51
+ ] as const;
52
+
53
+ /**
54
+ * Cross-cutting protocols that live in `dispatch_matrix.by_protocol` but are
55
+ * not LOOM lifecycle stages.
56
+ */
57
+ const CROSS_CUTTING_PROTOCOLS = new Set([
58
+ 'artifact-publish',
59
+ 'provenance',
60
+ 'agent-protocol',
61
+ ]);
62
+
63
+ describe('lifecycle ↔ protocol reconcile (T9672)', () => {
64
+ const byProtocolKeys = new Set(Object.keys(manifest.dispatch_matrix.by_protocol));
65
+ const stageOnlyKeys = new Set(
66
+ [...byProtocolKeys].filter((k) => !CROSS_CUTTING_PROTOCOLS.has(k)),
67
+ );
68
+ const expected = new Set<string>(LIFECYCLE_STAGES);
69
+
70
+ it('every cleo lifecycle stage is present as a strict (underscored) key in dispatch_matrix.by_protocol', () => {
71
+ for (const stage of LIFECYCLE_STAGES) {
72
+ expect(
73
+ byProtocolKeys.has(stage),
74
+ `lifecycle stage "${stage}" missing from dispatch_matrix.by_protocol — keys: ${[...byProtocolKeys].sort().join(', ')}`,
75
+ ).toBe(true);
76
+ }
77
+ });
78
+
79
+ it('dispatch_matrix.by_protocol contains no surplus stage-like keys', () => {
80
+ for (const key of stageOnlyKeys) {
81
+ expect(
82
+ expected.has(key),
83
+ `dispatch_matrix.by_protocol has key "${key}" which is not a cleo lifecycle stage and not a known cross-cutting protocol`,
84
+ ).toBe(true);
85
+ }
86
+ });
87
+
88
+ it('the dashed legacy form "architecture-decision" is NOT a dispatch_matrix.by_protocol key', () => {
89
+ expect(byProtocolKeys.has('architecture-decision')).toBe(false);
90
+ });
91
+
92
+ it('the dashed legacy form "architecture-decision" IS preserved as a keyword alias', () => {
93
+ const keywordKeys = Object.keys(manifest.dispatch_matrix.by_keyword);
94
+ const adrLine = keywordKeys.find((k) => k.includes('adr') && k.includes('formalize'));
95
+ expect(
96
+ adrLine,
97
+ `expected the ct-adr-recorder keyword dispatch line to exist; keys: ${keywordKeys.join(', ')}`,
98
+ ).toBeDefined();
99
+ expect(
100
+ (adrLine ?? '').includes('architecture-decision'),
101
+ `architecture-decision keyword alias must be retained in ${adrLine}`,
102
+ ).toBe(true);
103
+ });
104
+
105
+ it('the underscored canonical form "architecture_decision" maps to ct-adr-recorder', () => {
106
+ expect(manifest.dispatch_matrix.by_protocol.architecture_decision).toBe('ct-adr-recorder');
107
+ });
108
+
109
+ it('the by_protocol stage-only key set is exactly equal to the lifecycle stage set', () => {
110
+ expect([...stageOnlyKeys].sort()).toEqual([...expected].sort());
111
+ });
112
+ });
@@ -0,0 +1,163 @@
1
+ /**
2
+ * ADR-link gate for LOOM-stage skills (T9665).
3
+ *
4
+ * Enforces two invariants on every canonical LOOM-stage skill in
5
+ * `packages/skills/skills/manifest.json`:
6
+ *
7
+ * 1. The skill entry declares a non-empty `adrRefs[]` array of ADR IDs.
8
+ * 2. Every ADR id in the array resolves to a real file under `.cleo/adrs/`.
9
+ *
10
+ * The mapping of (stage -> required adrRefs minimum) is the authoritative
11
+ * source-of-truth defined in `docs/skills/loom-coverage-matrix.md` under
12
+ * the "ADR Bindings Section". When that doc updates, this test updates.
13
+ *
14
+ * @task T9665
15
+ * @epic T9568
16
+ */
17
+
18
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
19
+ import { dirname, resolve } from 'node:path';
20
+ import { fileURLToPath } from 'node:url';
21
+ import { describe, expect, it } from 'vitest';
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // Setup
25
+ // ---------------------------------------------------------------------------
26
+
27
+ const thisDir = dirname(fileURLToPath(import.meta.url));
28
+ const manifestPath = resolve(thisDir, '../../manifest.json');
29
+ const adrDir = resolve(thisDir, '../../../../../.cleo/adrs');
30
+
31
+ interface SkillEntry {
32
+ name: string;
33
+ loomStage?: string;
34
+ adrRefs?: string[];
35
+ [key: string]: unknown;
36
+ }
37
+
38
+ interface Manifest {
39
+ skills: SkillEntry[];
40
+ }
41
+
42
+ const manifest: Manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
43
+
44
+ /**
45
+ * The canonical 10 LOOM lifecycle stages — underscored form is authoritative.
46
+ * Must match `cleo lifecycle --help` output.
47
+ */
48
+ const CANONICAL_LOOM_STAGES = [
49
+ 'research',
50
+ 'consensus',
51
+ 'architecture_decision',
52
+ 'specification',
53
+ 'decomposition',
54
+ 'implementation',
55
+ 'validation',
56
+ 'testing',
57
+ 'release',
58
+ 'contribution',
59
+ ] as const;
60
+
61
+ /**
62
+ * Required ADR-id minimums per stage. The matrix doc may extend each entry;
63
+ * this gate only enforces the floor.
64
+ */
65
+ const REQUIRED_ADR_REFS: Record<(typeof CANONICAL_LOOM_STAGES)[number], string[]> = {
66
+ research: ['ADR-023', 'ADR-070'],
67
+ consensus: ['ADR-015', 'ADR-023'],
68
+ architecture_decision: ['ADR-053', 'ADR-070'],
69
+ specification: ['ADR-014', 'ADR-023'],
70
+ decomposition: ['ADR-066', 'ADR-073'],
71
+ implementation: ['ADR-070', 'ADR-062'],
72
+ validation: ['ADR-051', 'ADR-023'],
73
+ testing: ['ADR-051', 'ADR-061'],
74
+ release: ['ADR-053', 'ADR-063', 'ADR-065'],
75
+ contribution: ['ADR-015', 'ADR-053'],
76
+ };
77
+
78
+ /**
79
+ * Build a set of ADR-id prefixes from .cleo/adrs/. Each filename starts
80
+ * with `ADR-NNN-...`; we extract the prefix before the second dash.
81
+ */
82
+ function loadAdrIdSet(): Set<string> {
83
+ const files = readdirSync(adrDir);
84
+ const ids = new Set<string>();
85
+ for (const file of files) {
86
+ const match = file.match(/^(ADR-\d{3})-/);
87
+ if (match) ids.add(match[1]);
88
+ }
89
+ return ids;
90
+ }
91
+
92
+ const adrIds = loadAdrIdSet();
93
+ const skillsByStage = new Map<string, SkillEntry>();
94
+ for (const s of manifest.skills) {
95
+ if (typeof s.loomStage === 'string') {
96
+ skillsByStage.set(s.loomStage, s);
97
+ }
98
+ }
99
+
100
+ // ---------------------------------------------------------------------------
101
+ // Gate 1: every LOOM-stage skill declares a non-empty adrRefs[]
102
+ // ---------------------------------------------------------------------------
103
+
104
+ describe('LOOM ADR links — adrRefs[] declared on every LOOM-stage skill', () => {
105
+ for (const stage of CANONICAL_LOOM_STAGES) {
106
+ it(`stage "${stage}" skill carries a non-empty adrRefs[]`, () => {
107
+ const skill = skillsByStage.get(stage);
108
+ expect(skill, `no skill found with loomStage="${stage}"`).toBeDefined();
109
+ expect(
110
+ Array.isArray(skill?.adrRefs),
111
+ `skill ${skill?.name} adrRefs is not an array`,
112
+ ).toBe(true);
113
+ expect(
114
+ (skill?.adrRefs ?? []).length,
115
+ `skill ${skill?.name} adrRefs is empty`,
116
+ ).toBeGreaterThan(0);
117
+ });
118
+ }
119
+ });
120
+
121
+ // ---------------------------------------------------------------------------
122
+ // Gate 2: every adrRef resolves to a real file under .cleo/adrs/
123
+ // ---------------------------------------------------------------------------
124
+
125
+ describe('LOOM ADR links — every adrRefs entry resolves to .cleo/adrs/<id>-*.md', () => {
126
+ it('.cleo/adrs/ exists and was loaded', () => {
127
+ expect(existsSync(adrDir), `.cleo/adrs/ not found at ${adrDir}`).toBe(true);
128
+ expect(adrIds.size).toBeGreaterThan(0);
129
+ });
130
+
131
+ for (const stage of CANONICAL_LOOM_STAGES) {
132
+ const skill = skillsByStage.get(stage);
133
+ const refs = skill?.adrRefs ?? [];
134
+ for (const ref of refs) {
135
+ it(`stage "${stage}" skill ${skill?.name} references real ADR file "${ref}"`, () => {
136
+ expect(
137
+ adrIds.has(ref),
138
+ `${ref} not found under .cleo/adrs/ — known prefixes: ${[...adrIds].sort().join(', ')}`,
139
+ ).toBe(true);
140
+ });
141
+ }
142
+ }
143
+ });
144
+
145
+ // ---------------------------------------------------------------------------
146
+ // Gate 3: required ADR floor per stage is met
147
+ // ---------------------------------------------------------------------------
148
+
149
+ describe('LOOM ADR links — required ADR floor met per stage', () => {
150
+ for (const stage of CANONICAL_LOOM_STAGES) {
151
+ const required = REQUIRED_ADR_REFS[stage];
152
+ const skill = skillsByStage.get(stage);
153
+ const refs = new Set(skill?.adrRefs ?? []);
154
+ for (const requiredAdr of required) {
155
+ it(`stage "${stage}" includes required ADR "${requiredAdr}" in adrRefs`, () => {
156
+ expect(
157
+ refs.has(requiredAdr),
158
+ `skill ${skill?.name} for stage ${stage} missing required ADR ${requiredAdr} (has: ${[...refs].join(', ')})`,
159
+ ).toBe(true);
160
+ });
161
+ }
162
+ }
163
+ });
@@ -0,0 +1,167 @@
1
+ /**
2
+ * LOOM-stage coverage gate (T9664).
3
+ *
4
+ * Enforces that every canonical LOOM lifecycle stage emitted by `cleo lifecycle`
5
+ * has a bound skill in `packages/skills/skills/manifest.json` and that the
6
+ * skill's entry declares a `loomStage` field equal to the lifecycle stage name
7
+ * in underscored canonical form (the `cleo lifecycle` source-of-truth form).
8
+ *
9
+ * Why this exists:
10
+ * - The lifecycle CLI is the runtime source of truth for stage names.
11
+ * - The manifest's `dispatch_matrix.by_protocol` is the dispatch routing table.
12
+ * - Historically those two surfaces drifted (T9568 audit found
13
+ * `architecture-decision` dashed in manifest vs `architecture_decision`
14
+ * underscored in lifecycle CLI).
15
+ * - This test pins the contract so future drift fails CI instead of silently
16
+ * reaching agents at spawn time.
17
+ *
18
+ * @task T9664
19
+ * @epic T9568
20
+ */
21
+
22
+ import { readFileSync } from 'node:fs';
23
+ import { dirname, resolve } from 'node:path';
24
+ import { fileURLToPath } from 'node:url';
25
+ import { describe, expect, it } from 'vitest';
26
+
27
+ // ---------------------------------------------------------------------------
28
+ // Setup
29
+ // ---------------------------------------------------------------------------
30
+
31
+ const thisDir = dirname(fileURLToPath(import.meta.url));
32
+ const manifestPath = resolve(thisDir, '../../manifest.json');
33
+
34
+ interface SkillEntry {
35
+ name: string;
36
+ protocol?: string;
37
+ loomStage?: string;
38
+ status?: string;
39
+ // intentionally permissive — other fields ignored for this gate
40
+ [key: string]: unknown;
41
+ }
42
+
43
+ interface Manifest {
44
+ dispatch_matrix: {
45
+ by_protocol: Record<string, string>;
46
+ };
47
+ skills: SkillEntry[];
48
+ }
49
+
50
+ const manifest: Manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
51
+
52
+ /**
53
+ * The canonical 10 LOOM lifecycle stages emitted by `cleo lifecycle` (see
54
+ * `packages/core/src/lifecycle/`). Underscored form is authoritative.
55
+ * Update this constant ONLY when the lifecycle CLI itself adds or removes
56
+ * a stage — never to silence a drift failure.
57
+ */
58
+ const CANONICAL_LOOM_STAGES = [
59
+ 'research',
60
+ 'consensus',
61
+ 'architecture_decision',
62
+ 'specification',
63
+ 'decomposition',
64
+ 'implementation',
65
+ 'validation',
66
+ 'testing',
67
+ 'release',
68
+ 'contribution',
69
+ ] as const;
70
+
71
+ /**
72
+ * Cross-cutting protocols that live in `dispatch_matrix.by_protocol` but are
73
+ * NOT LOOM lifecycle stages. They route by capability, not by lifecycle
74
+ * position, and are excluded from the 10-stage gate.
75
+ */
76
+ const CROSS_CUTTING_PROTOCOLS = new Set([
77
+ 'artifact-publish',
78
+ 'provenance',
79
+ 'agent-protocol',
80
+ ]);
81
+
82
+ /**
83
+ * Skill-name lookup keyed by the `name` field.
84
+ */
85
+ const skillByName = new Map(manifest.skills.map((s) => [s.name, s]));
86
+
87
+ // ---------------------------------------------------------------------------
88
+ // Gate 1: every canonical stage has a binding in dispatch_matrix.by_protocol
89
+ // ---------------------------------------------------------------------------
90
+
91
+ describe('LOOM stage coverage — dispatch_matrix.by_protocol', () => {
92
+ for (const stage of CANONICAL_LOOM_STAGES) {
93
+ it(`stage "${stage}" is bound to a skill in dispatch_matrix.by_protocol`, () => {
94
+ // We accept either the underscored form (canonical) or the dashed
95
+ // legacy alias (architecture-decision) until T9672 reconciles. After
96
+ // T9672, only the underscored key is required.
97
+ const dashed = stage.replace(/_/g, '-');
98
+ const skillName =
99
+ manifest.dispatch_matrix.by_protocol[stage] ??
100
+ manifest.dispatch_matrix.by_protocol[dashed];
101
+ expect(
102
+ skillName,
103
+ `LOOM stage "${stage}" has no skill binding in manifest.dispatch_matrix.by_protocol (checked both "${stage}" and "${dashed}")`,
104
+ ).toBeTruthy();
105
+ });
106
+ }
107
+ });
108
+
109
+ // ---------------------------------------------------------------------------
110
+ // Gate 2: every protocol-bound skill carries a matching loomStage frontmatter
111
+ // ---------------------------------------------------------------------------
112
+
113
+ describe('LOOM stage coverage — loomStage field on bound skills', () => {
114
+ for (const stage of CANONICAL_LOOM_STAGES) {
115
+ it(`bound skill for "${stage}" declares loomStage === "${stage}"`, () => {
116
+ const dashed = stage.replace(/_/g, '-');
117
+ const skillName =
118
+ manifest.dispatch_matrix.by_protocol[stage] ??
119
+ manifest.dispatch_matrix.by_protocol[dashed];
120
+ const skill = skillName ? skillByName.get(skillName) : undefined;
121
+ expect(skill, `skill "${skillName}" not found in manifest.skills[]`).toBeDefined();
122
+ expect(
123
+ skill?.loomStage,
124
+ `skill "${skillName}" missing loomStage field; expected "${stage}"`,
125
+ ).toBe(stage);
126
+ });
127
+ }
128
+ });
129
+
130
+ // ---------------------------------------------------------------------------
131
+ // Gate 3: every skill that has loomStage uses a canonical (underscored) value
132
+ // ---------------------------------------------------------------------------
133
+
134
+ describe('LOOM stage coverage — loomStage values are canonical', () => {
135
+ const stagesSet = new Set<string>(CANONICAL_LOOM_STAGES);
136
+ const skillsWithLoomStage = manifest.skills.filter((s) => typeof s.loomStage === 'string');
137
+
138
+ it('at least 10 skills carry a loomStage field (one per LOOM stage)', () => {
139
+ expect(skillsWithLoomStage.length).toBeGreaterThanOrEqual(CANONICAL_LOOM_STAGES.length);
140
+ });
141
+
142
+ for (const skill of skillsWithLoomStage) {
143
+ it(`skill "${skill.name}" loomStage value "${skill.loomStage}" is a canonical LOOM stage`, () => {
144
+ expect(stagesSet.has(skill.loomStage as string)).toBe(true);
145
+ });
146
+ }
147
+ });
148
+
149
+ // ---------------------------------------------------------------------------
150
+ // Gate 4: dispatch_matrix.by_protocol keys minus cross-cutting == LOOM stages
151
+ // (the union of the 10 lifecycle stages — checked allowing dashed alias for
152
+ // architecture_decision until T9672 lands the reconcile)
153
+ // ---------------------------------------------------------------------------
154
+
155
+ describe('LOOM stage coverage — dispatch_matrix.by_protocol key set', () => {
156
+ it('by_protocol keys minus cross-cutting protocols cover every canonical LOOM stage', () => {
157
+ const keys = new Set(Object.keys(manifest.dispatch_matrix.by_protocol));
158
+ const stageKeys = [...keys].filter((k) => !CROSS_CUTTING_PROTOCOLS.has(k));
159
+ const normalized = new Set(stageKeys.map((k) => k.replace(/-/g, '_')));
160
+ for (const stage of CANONICAL_LOOM_STAGES) {
161
+ expect(
162
+ normalized.has(stage),
163
+ `LOOM stage "${stage}" not represented in dispatch_matrix.by_protocol (normalized keys: ${[...normalized].join(', ')})`,
164
+ ).toBe(true);
165
+ }
166
+ });
167
+ });
@@ -1,6 +1,11 @@
1
1
  ---
2
2
  name: ct-adr-recorder
3
3
  description: "Records Architecture Decision Records from accepted consensus verdicts. Use when promoting a consensus outcome to a formal ADR: drafts the document in the proposed-then-accepted HITL lifecycle, links to the originating consensus manifest, persists the decision to the canonical SQLite decisions table, and triggers downstream invalidation when an accepted ADR is later superseded. Triggers on phrases like 'write ADR', 'record architecture decision', 'formalize this decision', 'lock in the choice', 'create ADR-XXX', or when a consensus task reaches completed status and needs formalization."
4
+ protocol: architecture_decision
5
+ loomStage: architecture_decision
6
+ adrRefs:
7
+ - ADR-053
8
+ - ADR-070
4
9
  ---
5
10
 
6
11
  # ADR Recorder
@@ -173,3 +178,16 @@ Exit code 0 = valid. Exit code 65 = `HANDOFF_REQUIRED`. Exit code 18 = `CASCADE_
173
178
  6. Superseding an accepted ADR MUST trigger the downstream cascade over linked specs, decomps, and impls.
174
179
  7. Agents MUST NOT retry the HITL handoff on a loop; wait for the human reviewer.
175
180
  8. Always validate via `cleo check protocol --protocolType architecture-decision` before exiting.
181
+
182
+ ## See also / References
183
+
184
+ This skill binds to the **architecture_decision** LOOM lifecycle stage (underscored canonical form — `cleo lifecycle` source of truth). Governing ADRs:
185
+
186
+ - [ADR-053 — playbook runtime](../../../../.cleo/adrs/ADR-053-playbook-runtime.md) — defines the lifecycle state machine; the ADR stage is one of its 10 nodes.
187
+ - [ADR-070 — three-tier orchestration](../../../../.cleo/adrs/ADR-070-three-tier-orchestration.md) — defines the Orchestrator HITL gate that owns the `proposed → accepted` ADR transition.
188
+
189
+ ### Naming Note (T9672)
190
+
191
+ The stage's canonical name is **`architecture_decision`** (underscored) because the `cleo lifecycle` CLI emits it that way. Historically `packages/skills/skills/manifest.json` `dispatch_matrix.by_protocol` used the dashed form `architecture-decision` — T9672 reconciled that key to the underscored form. The dashed form is retained only as a keyword alias under `dispatch_matrix.by_keyword` so legacy dispatch paths continue to resolve. Frontmatter on this skill (`protocol: architecture_decision`, `loomStage: architecture_decision`) uses the underscored form.
192
+
193
+ LOOM coverage matrix: [docs/skills/loom-coverage-matrix.md](../../../../docs/skills/loom-coverage-matrix.md).
@@ -1,6 +1,11 @@
1
1
  ---
2
2
  name: ct-consensus-voter
3
3
  description: "Runs structured multi-agent voting for decision tasks with confidence scores, conflict detection, and HITL escalation when the threshold is not met. Use when two or more agents must vote on options: architecture choices, tool selection, policy decisions, when a task carries agent_type:analysis, or on phrases like 'reach consensus', 'vote on options', 'resolve the debate', 'pick the best approach'. Produces a voting matrix JSON, enforces the 0.5 threshold, flags ties within 0.1 confidence as contested and escalates to human tiebreak."
4
+ protocol: consensus
5
+ loomStage: consensus
6
+ adrRefs:
7
+ - ADR-015
8
+ - ADR-023
4
9
  ---
5
10
 
6
11
  # Consensus Voter
@@ -156,3 +161,12 @@ This skill typically hands off to ct-adr-recorder on a `PROVEN` verdict so the d
156
161
  6. Manifest entry MUST set `agent_type: "analysis"` and include the verdict.
157
162
  7. On PROVEN, hand off to ct-adr-recorder; on CONTESTED or INSUFFICIENT_EVIDENCE, hand off to HITL.
158
163
  8. Always validate via `cleo check protocol --protocolType consensus`.
164
+
165
+ ## See also / References
166
+
167
+ This skill binds to the **consensus** LOOM lifecycle stage. Governing ADRs:
168
+
169
+ - [ADR-015 — multi-contributor architecture](../../../../.cleo/adrs/ADR-015-multi-contributor-architecture.md) — defines the consensus framework that this skill implements.
170
+ - [ADR-023 — protocol validation dispatch](../../../../.cleo/adrs/ADR-023-protocol-validation-dispatch.md) — defines how consensus output is validated before downstream stages consume it.
171
+
172
+ LOOM coverage matrix: [docs/skills/loom-coverage-matrix.md](../../../../docs/skills/loom-coverage-matrix.md).
@@ -11,6 +11,10 @@ tier: 3
11
11
  core: false
12
12
  category: meta
13
13
  protocol: contribution
14
+ loomStage: contribution
15
+ adrRefs:
16
+ - ADR-015
17
+ - ADR-053
14
18
  dependencies: []
15
19
  sharedResources:
16
20
  - subagent-protocol-base
@@ -519,3 +523,79 @@ jq -s '[.[] | select(.epicId == "T2204")] | .[0]' .cleo/contributions/CONTRIBUTI
519
523
  | [contribution.schema.json](../../schemas/contribution.schema.json) | **Authoritative** for JSON Schema |
520
524
  | [CONTRIBUTION-PROTOCOL-GUIDE.md](../../docs/guides/CONTRIBUTION-PROTOCOL-GUIDE.md) | Usage guide with examples |
521
525
  | [CONSENSUS-FRAMEWORK-SPEC.md](../../docs/specs/CONSENSUS-FRAMEWORK-SPEC.md) | Consensus voting thresholds |
526
+
527
+ ---
528
+
529
+ ## LOOM Stage Binding (T9670)
530
+
531
+ `ct-contribution` is bound to LOOM lifecycle stage **`contribution`** — the terminal node of the RCASD-IVTR+C pipeline. Use this skill to formalize an Epic's contribution back to canon after its work has converged.
532
+
533
+ ### Stage-Transition Contract
534
+
535
+ The contribution stage is entered from one of two upstream stages depending on the Epic's `kind`:
536
+
537
+ | Upstream stage | Epic kind | Entry condition |
538
+ |---|---|---|
539
+ | **`release`** | most epics (work, bug, experiment) | Release tag pushed; release manifest recorded |
540
+ | **`testing`** | epics whose `kind` is `release` or that gate on IVTR | IVT loop converged; `ivtLoopConverged: true` recorded |
541
+ | **`specification`** | spec-only epics (no code) | Specification accepted; HITL signoff recorded |
542
+
543
+ ```
544
+ research → consensus → architecture_decision → specification → decomposition
545
+ ↓
546
+ implementation
547
+ ↓
548
+ validation
549
+ ↓
550
+ testing ← (some epics return here)
551
+ ↓
552
+ release
553
+ ↓
554
+ contribution ← (this skill)
555
+ ```
556
+
557
+ The transition is enforced by the playbook runtime defined in **ADR-053**. The runtime is a deterministic state machine; `contribution` is its terminal accepting state. Once entered, the Epic is closed in canon.
558
+
559
+ ### Acceptance-Gate Evidence
560
+
561
+ The contribution stage's completion gate is satisfied by emitting **at least one** of the following ADR-051 evidence atoms, recorded via `cleo verify <epicId> --gate contribution --evidence "<atoms>"`:
562
+
563
+ | Atom kind | Format | Meaning |
564
+ |---|---|---|
565
+ | `decision:` | `decision:D-<slug>` | A BRAIN decision id that records the contribution outcome. |
566
+ | `files:` | `files:path/a.md,path/b.md` | A list of contribution-format JSON / markdown deliverables produced by `/contribution submit`. |
567
+ | `note:` | `note:<freeform>` | Owner-attested closure rationale; preferred when the contribution is non-textual (e.g. a tag push referenced by SHA in the note). |
568
+
569
+ Example:
570
+
571
+ ```bash
572
+ cleo verify T9568 --gate contribution \
573
+ --evidence "decision:D-loom-coverage-001;files:.cleo/contributions/T9568-final.json"
574
+ cleo complete T9568
575
+ ```
576
+
577
+ The gate validator (ADR-051 §2.4) rejects an empty evidence string with `E_EVIDENCE_MISSING`. Stale evidence (modified files after `verify` but before `complete`) fails with `E_EVIDENCE_STALE`.
578
+
579
+ ### Open Follow-Up
580
+
581
+ A future ADR dedicated to the contribution stage's lifecycle gates (covering automated rollup signals from `cleo saga rollup`, multi-Epic contribution aggregation, and the contribution → "saga close" promotion path) is on the roadmap. File via:
582
+
583
+ ```bash
584
+ cleo add --kind work --type task --severity P2 \
585
+ --title "T-LOOM-GAP-ADR-CONTRIBUTION: dedicated ADR for contribution stage gates" \
586
+ --relates T9670 \
587
+ --acceptance "ADR drafted under .cleo/adrs/|Cross-referenced from ct-contribution SKILL.md|Validator gate updated"
588
+ ```
589
+
590
+ Until that ADR lands, contribution gates derive from ADR-015 (multi-contributor architecture) and ADR-053 (playbook runtime) — both already referenced in this skill's `adrRefs`.
591
+
592
+ ---
593
+
594
+ ## See also / References
595
+
596
+ This skill binds to the **contribution** LOOM lifecycle stage (the final stage of the RCASD-IVTR+C pipeline). Governing ADRs:
597
+
598
+ - [ADR-015 — multi-contributor architecture](../../../../.cleo/adrs/ADR-015-multi-contributor-architecture.md) — defines the multi-contributor consensus mechanics that this stage formalizes for an Epic's downstream return path.
599
+ - [ADR-053 — playbook runtime](../../../../.cleo/adrs/ADR-053-playbook-runtime.md) — defines the lifecycle state machine; contribution is its terminal node.
600
+
601
+ LOOM coverage matrix: [docs/skills/loom-coverage-matrix.md](../../../../docs/skills/loom-coverage-matrix.md).
@@ -6,6 +6,10 @@ tier: 1
6
6
  core: false
7
7
  category: recommended
8
8
  protocol: decomposition
9
+ loomStage: decomposition
10
+ adrRefs:
11
+ - ADR-066
12
+ - ADR-073
9
13
  dependencies: []
10
14
  sharedResources:
11
15
  - subagent-protocol-base
@@ -329,3 +333,14 @@ Recommendation: [Your recommendation]
329
333
  | 6 | Validation | Escape `$` as `\$`, check fields |
330
334
 
331
335
  **Shell Escaping**: Always `\$` in `--notes`/`--description`. See [shell-escaping.md](references/shell-escaping.md).
336
+
337
+ ---
338
+
339
+ ## See also / References
340
+
341
+ This skill binds to the **decomposition** LOOM lifecycle stage. Governing ADRs:
342
+
343
+ - [ADR-066 — task taxonomy consolidation](../../../../.cleo/adrs/ADR-066-task-taxonomy-consolidation.md) — defines the Type/Kind/Severity axes the decomposer must populate on every leaf task.
344
+ - [ADR-073 — above-epic naming](../../../../.cleo/adrs/ADR-073-above-epic-naming.md) — defines the Saga/Epic/Task/Subtask hierarchy that decomposition produces.
345
+
346
+ LOOM coverage matrix: [docs/skills/loom-coverage-matrix.md](../../../../docs/skills/loom-coverage-matrix.md).