@rune-kit/rune 2.6.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -6
- package/compiler/__tests__/executive-dashboards.test.js +285 -0
- package/compiler/__tests__/inject.test.js +128 -0
- package/compiler/__tests__/orchestrators.test.js +151 -0
- package/compiler/__tests__/org-templates.test.js +447 -0
- package/compiler/__tests__/parser.test.js +201 -147
- package/compiler/__tests__/skill-index.test.js +218 -218
- package/compiler/__tests__/status.test.js +336 -0
- package/compiler/__tests__/templates.test.js +245 -0
- package/compiler/__tests__/visualizer.test.js +325 -0
- package/compiler/adapters/antigravity.js +71 -71
- package/compiler/bin/rune.js +444 -355
- package/compiler/doctor.js +272 -1
- package/compiler/emitter.js +939 -678
- package/compiler/parser.js +498 -267
- package/compiler/status.js +342 -0
- package/compiler/visualizer.js +622 -0
- package/package.json +1 -1
- package/skills/autopsy/SKILL.md +48 -1
- package/skills/completion-gate/SKILL.md +51 -3
- package/skills/context-engine/SKILL.md +141 -2
- package/skills/cook/SKILL.md +177 -4
- package/skills/debug/SKILL.md +50 -1
- package/skills/docs/SKILL.md +28 -3
- package/skills/fix/SKILL.md +26 -1
- package/skills/mcp-builder/SKILL.md +53 -1
- package/skills/onboard/SKILL.md +51 -1
- package/skills/perf/SKILL.md +34 -1
- package/skills/plan/SKILL.md +27 -1
- package/skills/preflight/SKILL.md +35 -1
- package/skills/research/SKILL.md +24 -1
- package/skills/retro/SKILL.md +95 -1
- package/skills/review/SKILL.md +45 -1
- package/skills/scope-guard/SKILL.md +1 -0
- package/skills/scout/SKILL.md +22 -1
- package/skills/sentinel/SKILL.md +35 -1
- package/skills/sentinel/references/policy-driven-constraints.md +424 -0
- package/skills/sentinel-env/SKILL.md +0 -2
- package/skills/session-bridge/SKILL.md +57 -2
- package/skills/session-bridge/references/evolutionary-memory-patterns.md +312 -0
- package/skills/team/SKILL.md +15 -1
- package/skills/verification/SKILL.md +0 -2
package/README.md
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
<a href="https://rune-kit.github.io/rune"><img src="https://img.shields.io/badge/Landing_Page-rune--kit.github.io-blue?style=for-the-badge" alt="Landing Page"></a>
|
|
13
13
|
<a href="https://rune-kit.github.io/rune#pricing"><img src="https://img.shields.io/badge/Pro_%2449-lifetime-blueviolet?style=for-the-badge" alt="Rune Pro $49"></a>
|
|
14
14
|
<a href="https://rune-kit.github.io/rune#pricing"><img src="https://img.shields.io/badge/Business_%24149-lifetime-orange?style=for-the-badge" alt="Rune Business $149"></a>
|
|
15
|
+
<a href="https://t.me/xlabs_updates"><img src="https://img.shields.io/badge/Telegram-Updates-26A5E4?style=for-the-badge&logo=telegram&logoColor=white" alt="Telegram Updates"></a>
|
|
15
16
|
</p>
|
|
16
17
|
|
|
17
18
|
<p align="center">
|
|
@@ -82,15 +83,30 @@ _Methodology: Claude Code CLI headless mode (`claude -p --output-format json`),
|
|
|
82
83
|
|
|
83
84
|
---
|
|
84
85
|
|
|
85
|
-
## What's New (v2.
|
|
86
|
+
## What's New (v2.8.0)
|
|
86
87
|
|
|
87
|
-
- **
|
|
88
|
+
- **Anti-Loop Intelligence** — 7 core skills enriched with execution loop detection, saturation analysis, error pattern matching, artifact folding, budget-aware progression, and recovery policy routing
|
|
89
|
+
- **cook v2.1.0** — observation/effect ratio tracking (detects stuck agents reading without writing) + budget-aware phase progression with hard caps on replans, quality retries, and session tool calls
|
|
90
|
+
- **completion-gate v1.8.0** — execution loop audit: classifies tool calls as observation vs effect, flags imbalanced ratios and repeating sequences in gate reports
|
|
91
|
+
- **scout v0.3.0** — info saturation detection: tracks entity discovery rate and content similarity to stop scanning when diminishing returns detected
|
|
92
|
+
- **research v0.4.0** — diminishing returns detection: monitors new-entity ratio and result overlap across searches to skip redundant queries
|
|
93
|
+
- **context-engine v0.9.0** — artifact folding: large tool outputs (>4000 chars or >120 lines) saved to `.rune/artifacts/` with compact preview in context
|
|
94
|
+
- **debug v1.0.0** — known error pattern catalog: 8 error archetypes (STATELESS_LOSS, MODULE_NOT_FOUND, TYPE_MISMATCH, ASYNC_DEADLOCK, etc.) with recovery hints + error fingerprinting for dedup
|
|
95
|
+
- **fix v0.8.0** — recovery policy matrix: classifies errors into 8 types (INPUT_REQUIRED→PROMPT_USER, TIMEOUT→RETRY, POLICY_BLOCKED→ABORT, etc.) before attempting fixes
|
|
96
|
+
- **Source attribution cleanup** — removed all enrichment credit lines from skill files to reduce context noise
|
|
97
|
+
|
|
98
|
+
### Previous (v2.7.0)
|
|
99
|
+
|
|
100
|
+
- **Deep Knowledge** — 8 core skills enriched with battle-tested patterns: context compaction, structured cumulative memory, milestone analysis, multi-provider adapters, AI-driven interview, prompt-as-API-contract, token budget tracking, incremental stream processing
|
|
101
|
+
- **946 Tests** — compiler + signals + hooks + scripts + status + visualizer validation
|
|
102
|
+
|
|
103
|
+
### Previous (v2.6.0)
|
|
104
|
+
|
|
105
|
+
- **Mesh Signals** — event-driven skill communication via frontmatter. Skills declare `emit` and `listen` signals; compiler builds a signal graph in `skill-index.json`. 17 signals across 15 core skills
|
|
88
106
|
- **Signal Validation** — `scripts/validate-signals.js` checks orphan listeners (hard error), unlistened emitters (warning), signal naming conventions
|
|
89
107
|
- **Mesh Contract** (v2.5.0) — `.rune/contract.md` project-level invariants enforced by cook + sentinel as hard gates
|
|
90
|
-
- **Compiled Intent Mesh** (v2.5.0) — compile-time `skill-index.json` with intent keywords, mesh graph, chain predictions
|
|
91
108
|
- **Tier Override** — Pro/Business packs override Free packs with skill-level merging
|
|
92
109
|
- **Scripts Bundling** — compiler copies `scripts/` directories, resolves `{scripts_dir}` placeholders
|
|
93
|
-
- **566 Tests** — compiler + signals + hooks + scripts validation
|
|
94
110
|
|
|
95
111
|
### Signal Graph
|
|
96
112
|
|
|
@@ -447,11 +463,11 @@ See [docs/MULTI-PLATFORM.md](docs/MULTI-PLATFORM.md) for the full architecture.
|
|
|
447
463
|
Core Skills: 61 (L0: 1 │ L1: 5 │ L2: 28 │ L3: 27)
|
|
448
464
|
Extension Packs: 14 free + 4 pro + 4 business
|
|
449
465
|
Mesh Connections: 200+ cross-references
|
|
450
|
-
Mesh Signals:
|
|
466
|
+
Mesh Signals: 57 signals across 66 skills (emit/listen graph)
|
|
451
467
|
Connections/Skill: 3.4 avg
|
|
452
468
|
Platforms: 8 (Claude Code, Cursor, Windsurf, Antigravity, Codex, OpenCode, OpenClaw, Generic)
|
|
453
469
|
Compiler: ~1400 LOC (parser + 8 transforms + 8 adapters + CLI)
|
|
454
|
-
Tests:
|
|
470
|
+
Tests: 946 (compiler + signals + status + visualizer + hooks + scripts)
|
|
455
471
|
Pack Depth: 22 packs total (14 free + 4 pro + 4 business, all free packs rated Deep)
|
|
456
472
|
```
|
|
457
473
|
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { describe, test } from 'node:test';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const BUSINESS_DIR = path.resolve(__dirname, '../../../Business');
|
|
9
|
+
const REPORT_TEMPLATES_DIR = path.join(BUSINESS_DIR, 'report-templates');
|
|
10
|
+
const SKILLS_DIR = path.resolve(__dirname, '../../skills');
|
|
11
|
+
|
|
12
|
+
// ─── HTML report template validation ──────────────────────────────
|
|
13
|
+
|
|
14
|
+
// Skip Business tier tests when Business repo is not available (CI)
|
|
15
|
+
const HAS_BUSINESS = existsSync(BUSINESS_DIR);
|
|
16
|
+
|
|
17
|
+
(HAS_BUSINESS ? describe : describe.skip)('Business report-templates directory', () => {
|
|
18
|
+
test('report-templates directory exists', () => {
|
|
19
|
+
assert.ok(existsSync(REPORT_TEMPLATES_DIR), 'Expected Business/report-templates/ to exist');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('retro-business.html exists', () => {
|
|
23
|
+
assert.ok(
|
|
24
|
+
existsSync(path.join(REPORT_TEMPLATES_DIR, 'retro-business.html')),
|
|
25
|
+
'Expected retro-business.html template',
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('autopsy-executive.html exists', () => {
|
|
30
|
+
assert.ok(
|
|
31
|
+
existsSync(path.join(REPORT_TEMPLATES_DIR, 'autopsy-executive.html')),
|
|
32
|
+
'Expected autopsy-executive.html template',
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('retro-business.html template', () => {
|
|
38
|
+
const filePath = path.join(REPORT_TEMPLATES_DIR, 'retro-business.html');
|
|
39
|
+
if (!existsSync(filePath)) return;
|
|
40
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
41
|
+
|
|
42
|
+
test('is valid HTML with doctype', () => {
|
|
43
|
+
assert.ok(content.startsWith('<!DOCTYPE html>'), 'Must start with DOCTYPE');
|
|
44
|
+
assert.ok(content.includes('<html'), 'Must have html tag');
|
|
45
|
+
assert.ok(content.includes('</html>'), 'Must close html tag');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('is self-contained (no external CSS/JS)', () => {
|
|
49
|
+
assert.ok(!content.includes('href="http'), 'No external CSS links');
|
|
50
|
+
assert.ok(!content.includes('src="http'), 'No external JS scripts');
|
|
51
|
+
assert.ok(!content.includes('cdn.'), 'No CDN references');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('has inline styles', () => {
|
|
55
|
+
assert.ok(content.includes('<style>'), 'Must have inline style block');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('has print-friendly styles', () => {
|
|
59
|
+
assert.ok(content.includes('@media print'), 'Must have print media query');
|
|
60
|
+
assert.ok(content.includes('@page'), 'Must have @page rule for PDF export');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('has KPI card placeholders', () => {
|
|
64
|
+
assert.ok(content.includes('{{sprint_velocity}}'), 'Missing sprint_velocity placeholder');
|
|
65
|
+
assert.ok(content.includes('{{mrr}}'), 'Missing MRR placeholder');
|
|
66
|
+
assert.ok(content.includes('{{sla_pct}}'), 'Missing SLA placeholder');
|
|
67
|
+
assert.ok(content.includes('{{compliance_score}}'), 'Missing compliance_score placeholder');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('has domain performance sections', () => {
|
|
71
|
+
assert.ok(content.includes('Engineering'), 'Missing Engineering domain');
|
|
72
|
+
assert.ok(content.includes('Revenue'), 'Missing Revenue domain');
|
|
73
|
+
assert.ok(content.includes('Support'), 'Missing Support domain');
|
|
74
|
+
assert.ok(content.includes('Finance'), 'Missing Finance domain');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('has team health table', () => {
|
|
78
|
+
assert.ok(content.includes('Team Health'), 'Missing Team Health section');
|
|
79
|
+
assert.ok(content.includes('{{team_name}}'), 'Missing team_name placeholder');
|
|
80
|
+
assert.ok(content.includes('{{team_lead}}'), 'Missing team_lead placeholder');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('has compliance status section', () => {
|
|
84
|
+
assert.ok(content.includes('Compliance Status'), 'Missing Compliance Status section');
|
|
85
|
+
assert.ok(content.includes('{{framework}}'), 'Missing framework placeholder');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('has key insights section (wins + risks)', () => {
|
|
89
|
+
assert.ok(content.includes('Key Insights'), 'Missing Key Insights section');
|
|
90
|
+
assert.ok(content.includes('Wins'), 'Missing Wins subsection');
|
|
91
|
+
assert.ok(content.includes('Risks'), 'Missing Risks subsection');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('has Rune Business footer', () => {
|
|
95
|
+
assert.ok(content.includes('Rune Business'), 'Missing Rune Business branding');
|
|
96
|
+
assert.ok(content.includes('retro --business'), 'Missing retro --business reference');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('has trend delta placeholders for KPIs', () => {
|
|
100
|
+
assert.ok(content.includes('{{velocity_delta}}'), 'Missing velocity trend');
|
|
101
|
+
assert.ok(content.includes('{{mrr_delta}}'), 'Missing MRR trend');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('uses CSS custom properties for theming', () => {
|
|
105
|
+
assert.ok(content.includes(':root'), 'Must use CSS custom properties');
|
|
106
|
+
assert.ok(content.includes('--bg'), 'Missing --bg variable');
|
|
107
|
+
assert.ok(content.includes('--accent'), 'Missing --accent variable');
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('autopsy-executive.html template', () => {
|
|
112
|
+
const filePath = path.join(REPORT_TEMPLATES_DIR, 'autopsy-executive.html');
|
|
113
|
+
if (!existsSync(filePath)) return;
|
|
114
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
115
|
+
|
|
116
|
+
test('is valid HTML with doctype', () => {
|
|
117
|
+
assert.ok(content.startsWith('<!DOCTYPE html>'), 'Must start with DOCTYPE');
|
|
118
|
+
assert.ok(content.includes('<html'), 'Must have html tag');
|
|
119
|
+
assert.ok(content.includes('</html>'), 'Must close html tag');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('is self-contained (no external CSS/JS)', () => {
|
|
123
|
+
assert.ok(!content.includes('href="http'), 'No external CSS links');
|
|
124
|
+
assert.ok(!content.includes('src="http'), 'No external JS scripts');
|
|
125
|
+
assert.ok(!content.includes('cdn.'), 'No CDN references');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('has print-friendly styles', () => {
|
|
129
|
+
assert.ok(content.includes('@media print'), 'Must have print media query');
|
|
130
|
+
assert.ok(content.includes('@page'), 'Must have @page rule');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('has SVG health ring', () => {
|
|
134
|
+
assert.ok(content.includes('<svg'), 'Must have inline SVG');
|
|
135
|
+
assert.ok(content.includes('<circle'), 'Must have SVG circle for health ring');
|
|
136
|
+
assert.ok(content.includes('stroke-dasharray'), 'Must use dasharray for progress ring');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('has health score placeholder', () => {
|
|
140
|
+
assert.ok(content.includes('{{health_score}}'), 'Missing health_score placeholder');
|
|
141
|
+
assert.ok(content.includes('{{risk_tier}}'), 'Missing risk_tier placeholder');
|
|
142
|
+
assert.ok(content.includes('{{score_color}}'), 'Missing score_color placeholder');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('has six health dimensions', () => {
|
|
146
|
+
assert.ok(content.includes('Complexity'), 'Missing Complexity dimension');
|
|
147
|
+
assert.ok(content.includes('Test Coverage'), 'Missing Test Coverage dimension');
|
|
148
|
+
assert.ok(content.includes('Documentation'), 'Missing Documentation dimension');
|
|
149
|
+
assert.ok(content.includes('Dependencies'), 'Missing Dependencies dimension');
|
|
150
|
+
assert.ok(content.includes('Code Smells'), 'Missing Code Smells dimension');
|
|
151
|
+
assert.ok(content.includes('Maintenance'), 'Missing Maintenance dimension');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('has module health table', () => {
|
|
155
|
+
assert.ok(content.includes('Module Health'), 'Missing Module Health section');
|
|
156
|
+
assert.ok(content.includes('{{module_name}}'), 'Missing module_name placeholder');
|
|
157
|
+
assert.ok(content.includes('{{module_score}}'), 'Missing module_score placeholder');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test('has surgery queue section', () => {
|
|
161
|
+
assert.ok(content.includes('Surgery Queue'), 'Missing Surgery Queue section');
|
|
162
|
+
assert.ok(content.includes('{{surgery_module}}'), 'Missing surgery_module placeholder');
|
|
163
|
+
assert.ok(content.includes('{{surgery_pattern}}'), 'Missing surgery_pattern placeholder');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test('has risk assessment section', () => {
|
|
167
|
+
assert.ok(content.includes('Risk Assessment'), 'Missing Risk Assessment section');
|
|
168
|
+
assert.ok(content.includes('Git Archaeology'), 'Missing Git Archaeology section');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test('has cross-domain impact table', () => {
|
|
172
|
+
assert.ok(content.includes('Cross-Domain Impact'), 'Missing Cross-Domain Impact section');
|
|
173
|
+
assert.ok(content.includes('{{domain_name}}'), 'Missing domain_name placeholder');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('has recommended actions', () => {
|
|
177
|
+
assert.ok(content.includes('Recommended Actions'), 'Missing Recommended Actions section');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test('has Rune Business footer', () => {
|
|
181
|
+
assert.ok(content.includes('Rune Business'), 'Missing Rune Business branding');
|
|
182
|
+
assert.ok(content.includes('autopsy --executive'), 'Missing autopsy --executive reference');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test('has board-ready label', () => {
|
|
186
|
+
assert.ok(content.includes('Board-ready'), 'Should mention board-ready format');
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// ─── Skill integration validation ─────────────────────────────────
|
|
191
|
+
|
|
192
|
+
describe('retro skill --business integration', () => {
|
|
193
|
+
const retroPath = path.join(SKILLS_DIR, 'retro', 'SKILL.md');
|
|
194
|
+
const content = readFileSync(retroPath, 'utf-8');
|
|
195
|
+
|
|
196
|
+
test('retro has --business trigger', () => {
|
|
197
|
+
assert.ok(content.includes('retro --business'), 'retro should document --business mode');
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test('retro --business references HTML report', () => {
|
|
201
|
+
assert.ok(content.includes('.html'), 'retro --business should reference HTML output');
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test('retro --business references cross-domain data', () => {
|
|
205
|
+
assert.ok(
|
|
206
|
+
content.includes('Revenue') || content.includes('revenue'),
|
|
207
|
+
'retro --business should reference revenue domain',
|
|
208
|
+
);
|
|
209
|
+
assert.ok(
|
|
210
|
+
content.includes('Support') || content.includes('support'),
|
|
211
|
+
'retro --business should reference support domain',
|
|
212
|
+
);
|
|
213
|
+
assert.ok(
|
|
214
|
+
content.includes('Finance') || content.includes('finance'),
|
|
215
|
+
'retro --business should reference finance domain',
|
|
216
|
+
);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test('retro --business references org config', () => {
|
|
220
|
+
assert.ok(content.includes('.rune/org/org.md'), 'retro --business should reference org config');
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test('retro --business has graceful degradation', () => {
|
|
224
|
+
assert.ok(
|
|
225
|
+
content.includes('Graceful Degradation') || content.includes('graceful'),
|
|
226
|
+
'retro --business should handle missing Business pack gracefully',
|
|
227
|
+
);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test('retro --business references report template', () => {
|
|
231
|
+
assert.ok(content.includes('retro-business.html'), 'retro should reference the HTML template file');
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
describe('autopsy skill --executive integration', () => {
|
|
236
|
+
const autopsyPath = path.join(SKILLS_DIR, 'autopsy', 'SKILL.md');
|
|
237
|
+
const content = readFileSync(autopsyPath, 'utf-8');
|
|
238
|
+
|
|
239
|
+
test('autopsy has --executive mode', () => {
|
|
240
|
+
assert.ok(content.includes('--executive'), 'autopsy should document --executive mode');
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test('autopsy --executive references HTML report', () => {
|
|
244
|
+
assert.ok(
|
|
245
|
+
content.includes('EXECUTIVE-HEALTH.html'),
|
|
246
|
+
'autopsy --executive should reference EXECUTIVE-HEALTH.html output',
|
|
247
|
+
);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test('autopsy --executive references SVG health ring', () => {
|
|
251
|
+
assert.ok(
|
|
252
|
+
content.includes('SVG') || content.includes('svg'),
|
|
253
|
+
'autopsy --executive should reference SVG health ring',
|
|
254
|
+
);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test('autopsy --executive has color coding table', () => {
|
|
258
|
+
assert.ok(
|
|
259
|
+
content.includes('Color Coding') || content.includes('color'),
|
|
260
|
+
'autopsy --executive should define color coding',
|
|
261
|
+
);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test('autopsy --executive references org config', () => {
|
|
265
|
+
assert.ok(content.includes('.rune/org/org.md'), 'autopsy --executive should reference org config for team mapping');
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test('autopsy --executive references report template', () => {
|
|
269
|
+
assert.ok(content.includes('autopsy-executive.html'), 'autopsy should reference the HTML template file');
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test('autopsy --executive has cross-domain impact', () => {
|
|
273
|
+
assert.ok(
|
|
274
|
+
content.includes('Cross-Domain') || content.includes('cross-domain'),
|
|
275
|
+
'autopsy --executive should map to business domains',
|
|
276
|
+
);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test('autopsy --executive has graceful degradation', () => {
|
|
280
|
+
assert.ok(
|
|
281
|
+
content.includes('Graceful Degradation') || content.includes('graceful'),
|
|
282
|
+
'autopsy --executive should handle missing Business pack',
|
|
283
|
+
);
|
|
284
|
+
});
|
|
285
|
+
});
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { describe, test } from 'node:test';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const SKILLS_DIR = path.resolve(__dirname, '../../skills');
|
|
9
|
+
const PRO_DIR = path.resolve(__dirname, '../../../Pro/extensions');
|
|
10
|
+
const BUSINESS_DIR = path.resolve(__dirname, '../../../Business/extensions');
|
|
11
|
+
|
|
12
|
+
// Skip Pro/Business tier tests when repos are not available (CI)
|
|
13
|
+
const HAS_PRO = existsSync(PRO_DIR);
|
|
14
|
+
|
|
15
|
+
// Collect known Free core skill names
|
|
16
|
+
const knownSkills = new Set();
|
|
17
|
+
if (existsSync(SKILLS_DIR)) {
|
|
18
|
+
for (const entry of readdirSync(SKILLS_DIR, { withFileTypes: true })) {
|
|
19
|
+
if (entry.isDirectory() && existsSync(path.join(SKILLS_DIR, entry.name, 'SKILL.md'))) {
|
|
20
|
+
knownSkills.add(entry.name);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
(HAS_PRO ? describe : describe.skip)('inject.json format validation', () => {
|
|
26
|
+
const packDirs = [];
|
|
27
|
+
|
|
28
|
+
// Collect all extension dirs that might have inject.json
|
|
29
|
+
for (const extDir of [PRO_DIR, BUSINESS_DIR]) {
|
|
30
|
+
if (!existsSync(extDir)) continue;
|
|
31
|
+
for (const entry of readdirSync(extDir, { withFileTypes: true })) {
|
|
32
|
+
if (!entry.isDirectory()) continue;
|
|
33
|
+
const injectPath = path.join(extDir, entry.name, 'inject.json');
|
|
34
|
+
if (existsSync(injectPath)) {
|
|
35
|
+
packDirs.push({ name: entry.name, dir: path.join(extDir, entry.name), injectPath });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
test('at least one inject.json exists in Pro packs', () => {
|
|
41
|
+
assert.ok(packDirs.length > 0, 'Expected at least one pack with inject.json');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
for (const pack of packDirs) {
|
|
45
|
+
describe(`${pack.name}/inject.json`, () => {
|
|
46
|
+
let config;
|
|
47
|
+
|
|
48
|
+
test('is valid JSON', () => {
|
|
49
|
+
const raw = readFileSync(pack.injectPath, 'utf-8');
|
|
50
|
+
config = JSON.parse(raw);
|
|
51
|
+
assert.ok(config, 'inject.json should parse as valid JSON');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('has injections array', () => {
|
|
55
|
+
if (!config) return;
|
|
56
|
+
assert.ok(Array.isArray(config.injections), 'must have injections array');
|
|
57
|
+
assert.ok(config.injections.length > 0, 'must have at least one injection rule');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('has description field', () => {
|
|
61
|
+
if (!config) return;
|
|
62
|
+
assert.ok(config.description, 'should have a description field');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('each rule has required fields', () => {
|
|
66
|
+
if (!config) return;
|
|
67
|
+
for (const rule of config.injections) {
|
|
68
|
+
assert.ok(rule.skill, `rule must have "skill" field: ${JSON.stringify(rule)}`);
|
|
69
|
+
assert.ok(rule.ref, `rule must have "ref" field: ${JSON.stringify(rule)}`);
|
|
70
|
+
assert.ok(rule.context, `rule must have "context" field: ${JSON.stringify(rule)}`);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('target skills exist in Free core', () => {
|
|
75
|
+
if (!config) return;
|
|
76
|
+
for (const rule of config.injections) {
|
|
77
|
+
assert.ok(knownSkills.has(rule.skill), `target skill "${rule.skill}" not found in Free core skills`);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('reference files exist', () => {
|
|
82
|
+
if (!config) return;
|
|
83
|
+
for (const rule of config.injections) {
|
|
84
|
+
const refPath = path.join(pack.dir, rule.ref);
|
|
85
|
+
assert.ok(existsSync(refPath), `reference file "${rule.ref}" not found at ${refPath}`);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('reference files are .md', () => {
|
|
90
|
+
if (!config) return;
|
|
91
|
+
for (const rule of config.injections) {
|
|
92
|
+
assert.ok(rule.ref.endsWith('.md'), `reference "${rule.ref}" should be a .md file`);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('no duplicate skill targets within same pack', () => {
|
|
97
|
+
if (!config) return;
|
|
98
|
+
const seen = new Set();
|
|
99
|
+
for (const rule of config.injections) {
|
|
100
|
+
assert.ok(!seen.has(rule.skill), `duplicate injection target "${rule.skill}" in ${pack.name}`);
|
|
101
|
+
seen.add(rule.skill);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
(HAS_PRO ? describe : describe.skip)('injection rule coverage', () => {
|
|
109
|
+
const expectedPacks = ['pro-product', 'pro-data-science', 'pro-sales', 'pro-support'];
|
|
110
|
+
|
|
111
|
+
for (const packName of expectedPacks) {
|
|
112
|
+
test(`${packName} has inject.json`, () => {
|
|
113
|
+
const injectPath = path.join(PRO_DIR, packName, 'inject.json');
|
|
114
|
+
assert.ok(existsSync(injectPath), `${packName} should have inject.json`);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
test('total injection rules across all packs', () => {
|
|
119
|
+
let totalRules = 0;
|
|
120
|
+
for (const packName of expectedPacks) {
|
|
121
|
+
const injectPath = path.join(PRO_DIR, packName, 'inject.json');
|
|
122
|
+
if (!existsSync(injectPath)) continue;
|
|
123
|
+
const config = JSON.parse(readFileSync(injectPath, 'utf-8'));
|
|
124
|
+
totalRules += (config.injections || []).length;
|
|
125
|
+
}
|
|
126
|
+
assert.ok(totalRules >= 8, `should have at least 8 injection rules total, got ${totalRules}`);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { describe, test } from 'node:test';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { parseSkill } from '../parser.js';
|
|
7
|
+
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const BUSINESS_SKILLS_DIR = path.resolve(__dirname, '../../../Business/skills');
|
|
10
|
+
|
|
11
|
+
// Signal naming pattern
|
|
12
|
+
const SIGNAL_NAME_PATTERN = /^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)+$/;
|
|
13
|
+
|
|
14
|
+
const ORCHESTRATORS = ['launch-product', 'quarterly-review', 'compliance-audit', 'customer-lifecycle'];
|
|
15
|
+
|
|
16
|
+
// Skip Business tier tests when Business repo is not available (CI)
|
|
17
|
+
const HAS_BUSINESS = existsSync(BUSINESS_SKILLS_DIR);
|
|
18
|
+
|
|
19
|
+
(HAS_BUSINESS ? describe : describe.skip)('Business orchestrators', () => {
|
|
20
|
+
test('skills directory exists', () => {
|
|
21
|
+
assert.ok(existsSync(BUSINESS_SKILLS_DIR), `${BUSINESS_SKILLS_DIR} should exist`);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
for (const name of ORCHESTRATORS) {
|
|
25
|
+
const skillPath = path.join(BUSINESS_SKILLS_DIR, name, 'SKILL.md');
|
|
26
|
+
|
|
27
|
+
describe(name, () => {
|
|
28
|
+
test('SKILL.md exists', () => {
|
|
29
|
+
assert.ok(existsSync(skillPath), `${skillPath} should exist`);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Skip remaining tests if file doesn't exist (CI without Business repo)
|
|
33
|
+
if (!existsSync(skillPath)) return;
|
|
34
|
+
|
|
35
|
+
const content = readFileSync(skillPath, 'utf-8');
|
|
36
|
+
const parsed = parseSkill(content, skillPath);
|
|
37
|
+
|
|
38
|
+
test('has correct name', () => {
|
|
39
|
+
assert.strictEqual(parsed.name, name);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('is L1 layer', () => {
|
|
43
|
+
assert.strictEqual(parsed.layer, 'L1', `${name} must be L1 orchestrator`);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('uses opus model', () => {
|
|
47
|
+
assert.strictEqual(parsed.model, 'opus', `${name} should use opus for complex orchestration`);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('has description', () => {
|
|
51
|
+
assert.ok(parsed.description.length > 50, `${name} should have a meaningful description`);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('is context fork', () => {
|
|
55
|
+
assert.strictEqual(parsed.contextFork, true, `${name} should fork context`);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('has emit signals', () => {
|
|
59
|
+
assert.ok(parsed.signals, `${name} must have signals`);
|
|
60
|
+
assert.ok(parsed.signals.emit.length > 0, `${name} must emit at least one signal`);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('has listen signals', () => {
|
|
64
|
+
assert.ok(parsed.signals.listen.length > 0, `${name} must listen to at least one signal`);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('all signals follow naming convention', () => {
|
|
68
|
+
for (const signal of [...parsed.signals.emit, ...parsed.signals.listen]) {
|
|
69
|
+
assert.ok(SIGNAL_NAME_PATTERN.test(signal), `signal "${signal}" must match lowercase.dot.notation`);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('has HARD-GATE block', () => {
|
|
74
|
+
assert.ok(parsed.hardGates.length > 0, `${name} must have at least one HARD-GATE`);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('has cross-references to other skills', () => {
|
|
78
|
+
assert.ok(parsed.crossRefs.length >= 3, `${name} should reference at least 3 other skills`);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('has Workflow Phases section', () => {
|
|
82
|
+
assert.ok(parsed.sections.has('Workflow Phases'), `${name} must have ## Workflow Phases`);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('has Signal Map section', () => {
|
|
86
|
+
assert.ok(parsed.sections.has('Signal Map'), `${name} must have ## Signal Map`);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('has Cross-Domain Connections section', () => {
|
|
90
|
+
assert.ok(parsed.sections.has('Cross-Domain Connections'), `${name} must have ## Cross-Domain Connections`);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('has Connections section', () => {
|
|
94
|
+
assert.ok(parsed.sections.has('Connections'), `${name} must have ## Connections`);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('has Constraints section', () => {
|
|
98
|
+
assert.ok(parsed.sections.has('Constraints'), `${name} must have ## Constraints`);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('references multiple domain packs (cross-domain)', () => {
|
|
102
|
+
const body = parsed.body;
|
|
103
|
+
const domains = ['finance', 'legal', 'hr', 'product', 'sales', 'support', 'enterprise-search', 'data-science'];
|
|
104
|
+
const referencedDomains = domains.filter((d) => body.toLowerCase().includes(d));
|
|
105
|
+
assert.ok(
|
|
106
|
+
referencedDomains.length >= 3,
|
|
107
|
+
`${name} must reference at least 3 domains, found: ${referencedDomains.join(', ')}`,
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('emits business.* signals', () => {
|
|
112
|
+
const businessSignals = parsed.signals.emit.filter((s) => s.startsWith('business.'));
|
|
113
|
+
assert.ok(businessSignals.length > 0, `${name} must emit at least one business.* signal`);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('body is substantial (300-500+ lines)', () => {
|
|
117
|
+
const lineCount = content.split('\n').length;
|
|
118
|
+
assert.ok(lineCount >= 100, `${name} should be at least 100 lines, got ${lineCount}`);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
(HAS_BUSINESS ? describe : describe.skip)('orchestrator signal consistency', () => {
|
|
125
|
+
// Verify orchestrators don't declare signals that overlap with each other
|
|
126
|
+
const allEmitted = new Set();
|
|
127
|
+
const duplicates = [];
|
|
128
|
+
|
|
129
|
+
for (const name of ORCHESTRATORS) {
|
|
130
|
+
const skillPath = path.join(BUSINESS_SKILLS_DIR, name, 'SKILL.md');
|
|
131
|
+
if (!existsSync(skillPath)) continue;
|
|
132
|
+
|
|
133
|
+
const content = readFileSync(skillPath, 'utf-8');
|
|
134
|
+
const parsed = parseSkill(content, skillPath);
|
|
135
|
+
|
|
136
|
+
for (const signal of parsed.signals.emit) {
|
|
137
|
+
if (allEmitted.has(signal)) {
|
|
138
|
+
duplicates.push({ signal, skill: name });
|
|
139
|
+
}
|
|
140
|
+
allEmitted.add(signal);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
test('no duplicate emit signals across orchestrators', () => {
|
|
145
|
+
assert.strictEqual(
|
|
146
|
+
duplicates.length,
|
|
147
|
+
0,
|
|
148
|
+
`Duplicate signals: ${duplicates.map((d) => `${d.signal} (${d.skill})`).join(', ')}`,
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
});
|