@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.
- package/README.md +0 -1
- package/package.json +1 -1
- package/profiles/recommended.json +1 -1
- package/skills/_shared/__tests__/lifecycle-protocol-reconcile.test.ts +112 -0
- package/skills/_shared/__tests__/loom-adr-links.test.ts +163 -0
- package/skills/_shared/__tests__/loom-stage-coverage.test.ts +167 -0
- package/skills/ct-adr-recorder/SKILL.md +18 -0
- package/skills/ct-consensus-voter/SKILL.md +14 -0
- package/skills/ct-contribution/SKILL.md +80 -0
- package/skills/ct-epic-architect/SKILL.md +15 -0
- package/skills/ct-ivt-looper/SKILL.md +32 -0
- package/skills/ct-release-orchestrator/SKILL.md +16 -0
- package/skills/ct-research-agent/SKILL.md +15 -0
- package/skills/ct-spec-writer/SKILL.md +15 -0
- package/skills/ct-task-executor/SKILL.md +15 -0
- package/skills/ct-validator/SKILL.md +35 -0
- package/skills/manifest.json +81 -9
- package/skills/ct-grade-v2-1/MIGRATION.md +0 -28
- package/skills/ct-grade-v2-1/SKILL.md +0 -235
- package/skills/ct-grade-v2-1/agents/analysis-reporter.md +0 -203
- package/skills/ct-grade-v2-1/agents/blind-comparator.md +0 -157
- package/skills/ct-grade-v2-1/agents/scenario-runner.md +0 -160
- package/skills/ct-grade-v2-1/evals/evals.json +0 -74
- package/skills/ct-grade-v2-1/grade-viewer/__pycache__/build_op_stats.cpython-314.pyc +0 -0
- package/skills/ct-grade-v2-1/grade-viewer/__pycache__/generate_grade_review.cpython-314.pyc +0 -0
- package/skills/ct-grade-v2-1/grade-viewer/build_op_stats.py +0 -174
- package/skills/ct-grade-v2-1/grade-viewer/eval-analysis.json +0 -41
- package/skills/ct-grade-v2-1/grade-viewer/eval-report.md +0 -37
- package/skills/ct-grade-v2-1/grade-viewer/generate_grade_review.py +0 -1023
- package/skills/ct-grade-v2-1/grade-viewer/generate_grade_viewer.py +0 -548
- package/skills/ct-grade-v2-1/grade-viewer/grade-review-eval.html +0 -613
- package/skills/ct-grade-v2-1/grade-viewer/grade-review.html +0 -1532
- package/skills/ct-grade-v2-1/grade-viewer/viewer.html +0 -620
- package/skills/ct-grade-v2-1/manifest-entry.json +0 -31
- package/skills/ct-grade-v2-1/references/ab-testing.md +0 -173
- package/skills/ct-grade-v2-1/references/domains-ssot.md +0 -156
- package/skills/ct-grade-v2-1/references/grade-spec-v2.md +0 -167
- package/skills/ct-grade-v2-1/references/playbook-v2.md +0 -325
- package/skills/ct-grade-v2-1/references/token-tracking.md +0 -200
- package/skills/ct-grade-v2-1/scripts/generate_report.py +0 -419
- package/skills/ct-grade-v2-1/scripts/run_ab_test.py +0 -493
- package/skills/ct-grade-v2-1/scripts/run_scenario.py +0 -396
- package/skills/ct-grade-v2-1/scripts/setup_run.py +0 -207
- 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
|
@@ -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).
|