@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
|
@@ -0,0 +1,447 @@
|
|
|
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
|
+
import { parseOrgConfig } from '../parser.js';
|
|
7
|
+
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const BUSINESS_DIR = path.resolve(__dirname, '../../../Business');
|
|
10
|
+
const ORG_TEMPLATES_DIR = path.join(BUSINESS_DIR, 'org-templates');
|
|
11
|
+
const SKILLS_DIR = path.resolve(__dirname, '../../skills');
|
|
12
|
+
|
|
13
|
+
// ─── parseOrgConfig unit tests ────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
describe('parseOrgConfig', () => {
|
|
16
|
+
const minimalTemplate = `---
|
|
17
|
+
name: test-org
|
|
18
|
+
description: Test organization template
|
|
19
|
+
version: "1.0.0"
|
|
20
|
+
tier: business
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
# Organization: Test Template
|
|
24
|
+
|
|
25
|
+
## Teams
|
|
26
|
+
|
|
27
|
+
| Team | Lead | Domain Packs | Members |
|
|
28
|
+
|------|------|-------------|---------|
|
|
29
|
+
| Engineering | CTO | — | eng-team |
|
|
30
|
+
| Product | VP Product | @rune-pro/product | product-team |
|
|
31
|
+
|
|
32
|
+
## Roles
|
|
33
|
+
|
|
34
|
+
| Role | Permissions | Approval Authority |
|
|
35
|
+
|------|------------|-------------------|
|
|
36
|
+
| admin | all | Can override any gate |
|
|
37
|
+
| contributor | write | Requires admin approval |
|
|
38
|
+
|
|
39
|
+
## Policies
|
|
40
|
+
|
|
41
|
+
### Code Review
|
|
42
|
+
- **Minimum reviewers**: 2
|
|
43
|
+
- **Self-merge allowed**: No
|
|
44
|
+
|
|
45
|
+
### Security
|
|
46
|
+
- **Dependency audit frequency**: Weekly
|
|
47
|
+
- **Secret rotation**: Monthly
|
|
48
|
+
|
|
49
|
+
### Deployment
|
|
50
|
+
- **Staging required**: Yes
|
|
51
|
+
- **Production deploy window**: Weekdays 09:00-16:00
|
|
52
|
+
|
|
53
|
+
## Approval Flows
|
|
54
|
+
|
|
55
|
+
### Feature Launch
|
|
56
|
+
\`\`\`
|
|
57
|
+
contributor proposes → admin approves → deploy
|
|
58
|
+
\`\`\`
|
|
59
|
+
|
|
60
|
+
### Budget Approval
|
|
61
|
+
\`\`\`
|
|
62
|
+
< $5,000: admin approves
|
|
63
|
+
> $5,000: board approves
|
|
64
|
+
\`\`\`
|
|
65
|
+
|
|
66
|
+
## Governance Level
|
|
67
|
+
|
|
68
|
+
**Moderate** — Balanced speed and safety.
|
|
69
|
+
|
|
70
|
+
- sentinel: enforce mode
|
|
71
|
+
- preflight: full checks
|
|
72
|
+
`;
|
|
73
|
+
|
|
74
|
+
test('parses frontmatter correctly', () => {
|
|
75
|
+
const result = parseOrgConfig(minimalTemplate);
|
|
76
|
+
assert.strictEqual(result.name, 'test-org');
|
|
77
|
+
assert.strictEqual(result.description, 'Test organization template');
|
|
78
|
+
assert.strictEqual(result.version, '1.0.0');
|
|
79
|
+
assert.strictEqual(result.tier, 'business');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('parses teams table', () => {
|
|
83
|
+
const result = parseOrgConfig(minimalTemplate);
|
|
84
|
+
assert.strictEqual(result.teams.length, 2);
|
|
85
|
+
assert.strictEqual(result.teams[0].team, 'Engineering');
|
|
86
|
+
assert.strictEqual(result.teams[0].lead, 'CTO');
|
|
87
|
+
assert.strictEqual(result.teams[1].team, 'Product');
|
|
88
|
+
assert.strictEqual(result.teams[1].domain_packs, '@rune-pro/product');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('parses roles table', () => {
|
|
92
|
+
const result = parseOrgConfig(minimalTemplate);
|
|
93
|
+
assert.strictEqual(result.roles.length, 2);
|
|
94
|
+
assert.strictEqual(result.roles[0].role, 'admin');
|
|
95
|
+
assert.strictEqual(result.roles[0].permissions, 'all');
|
|
96
|
+
assert.strictEqual(result.roles[1].role, 'contributor');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('parses policies into structured map', () => {
|
|
100
|
+
const result = parseOrgConfig(minimalTemplate);
|
|
101
|
+
assert.ok(result.policies.code_review, 'Expected code_review policy');
|
|
102
|
+
assert.ok(result.policies.security, 'Expected security policy');
|
|
103
|
+
assert.ok(result.policies.deployment, 'Expected deployment policy');
|
|
104
|
+
|
|
105
|
+
const cr = result.policies.code_review;
|
|
106
|
+
assert.strictEqual(cr.length, 2);
|
|
107
|
+
assert.strictEqual(cr[0].key, 'minimum_reviewers');
|
|
108
|
+
assert.strictEqual(cr[0].value, '2');
|
|
109
|
+
assert.strictEqual(cr[1].key, 'self-merge_allowed');
|
|
110
|
+
assert.strictEqual(cr[1].value, 'No');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('parses security policies', () => {
|
|
114
|
+
const result = parseOrgConfig(minimalTemplate);
|
|
115
|
+
const sec = result.policies.security;
|
|
116
|
+
assert.strictEqual(sec.length, 2);
|
|
117
|
+
assert.strictEqual(sec[0].key, 'dependency_audit_frequency');
|
|
118
|
+
assert.strictEqual(sec[0].value, 'Weekly');
|
|
119
|
+
assert.strictEqual(sec[1].key, 'secret_rotation');
|
|
120
|
+
assert.strictEqual(sec[1].value, 'Monthly');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('parses deployment policies', () => {
|
|
124
|
+
const result = parseOrgConfig(minimalTemplate);
|
|
125
|
+
const dep = result.policies.deployment;
|
|
126
|
+
assert.strictEqual(dep.length, 2);
|
|
127
|
+
assert.strictEqual(dep[0].key, 'staging_required');
|
|
128
|
+
assert.strictEqual(dep[0].value, 'Yes');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('parses approval flows', () => {
|
|
132
|
+
const result = parseOrgConfig(minimalTemplate);
|
|
133
|
+
assert.ok(result.approvalFlows.feature_launch, 'Expected feature_launch flow');
|
|
134
|
+
assert.ok(result.approvalFlows.budget_approval, 'Expected budget_approval flow');
|
|
135
|
+
assert.ok(
|
|
136
|
+
result.approvalFlows.feature_launch.includes('contributor proposes'),
|
|
137
|
+
'Expected contributor proposes in feature launch flow',
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('parses governance level', () => {
|
|
142
|
+
const result = parseOrgConfig(minimalTemplate);
|
|
143
|
+
assert.strictEqual(result.governanceLevel.level, 'moderate');
|
|
144
|
+
assert.ok(result.governanceLevel.settings.length >= 2);
|
|
145
|
+
assert.ok(result.governanceLevel.settings[0].includes('sentinel'));
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('handles missing sections gracefully', () => {
|
|
149
|
+
const sparse = `---
|
|
150
|
+
name: sparse
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
# Sparse Org
|
|
154
|
+
|
|
155
|
+
## Teams
|
|
156
|
+
|
|
157
|
+
No table here.
|
|
158
|
+
|
|
159
|
+
## Governance Level
|
|
160
|
+
|
|
161
|
+
**Minimal** — Fast.
|
|
162
|
+
|
|
163
|
+
- basic checks
|
|
164
|
+
`;
|
|
165
|
+
const result = parseOrgConfig(sparse);
|
|
166
|
+
assert.strictEqual(result.name, 'sparse');
|
|
167
|
+
assert.strictEqual(result.teams.length, 0);
|
|
168
|
+
assert.strictEqual(result.roles.length, 0);
|
|
169
|
+
assert.deepStrictEqual(result.policies, {});
|
|
170
|
+
assert.deepStrictEqual(result.approvalFlows, {});
|
|
171
|
+
assert.strictEqual(result.governanceLevel.level, 'minimal');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('handles empty content', () => {
|
|
175
|
+
const result = parseOrgConfig('');
|
|
176
|
+
assert.strictEqual(result.name, '');
|
|
177
|
+
assert.strictEqual(result.teams.length, 0);
|
|
178
|
+
assert.strictEqual(result.roles.length, 0);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test('preserves filePath', () => {
|
|
182
|
+
const result = parseOrgConfig(minimalTemplate, '/test/org.md');
|
|
183
|
+
assert.strictEqual(result.filePath, '/test/org.md');
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// ─── Business org template file validation ────────────────────────
|
|
188
|
+
|
|
189
|
+
// Skip Business tier tests when Business repo is not available (CI)
|
|
190
|
+
const HAS_BUSINESS = existsSync(BUSINESS_DIR);
|
|
191
|
+
|
|
192
|
+
(HAS_BUSINESS ? describe : describe.skip)('Business org-templates file validation', () => {
|
|
193
|
+
const templateFiles = [];
|
|
194
|
+
|
|
195
|
+
if (existsSync(ORG_TEMPLATES_DIR)) {
|
|
196
|
+
for (const file of readdirSync(ORG_TEMPLATES_DIR)) {
|
|
197
|
+
if (file.endsWith('.md')) {
|
|
198
|
+
templateFiles.push({
|
|
199
|
+
name: file.replace('.md', ''),
|
|
200
|
+
path: path.join(ORG_TEMPLATES_DIR, file),
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
test('org-templates directory exists', () => {
|
|
207
|
+
assert.ok(existsSync(ORG_TEMPLATES_DIR), 'Expected Business/org-templates/ to exist');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test('at least 3 org templates exist', () => {
|
|
211
|
+
assert.ok(templateFiles.length >= 3, `Expected >= 3 templates, got ${templateFiles.length}`);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test('required templates exist (startup, mid-size, enterprise)', () => {
|
|
215
|
+
const names = templateFiles.map((t) => t.name);
|
|
216
|
+
assert.ok(names.includes('startup'), 'Missing startup template');
|
|
217
|
+
assert.ok(names.includes('mid-size'), 'Missing mid-size template');
|
|
218
|
+
assert.ok(names.includes('enterprise'), 'Missing enterprise template');
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
for (const template of templateFiles) {
|
|
222
|
+
describe(`org-templates/${template.name}.md`, () => {
|
|
223
|
+
const content = readFileSync(template.path, 'utf-8');
|
|
224
|
+
const parsed = parseOrgConfig(content, template.path);
|
|
225
|
+
|
|
226
|
+
test('has valid frontmatter with name', () => {
|
|
227
|
+
assert.ok(parsed.name, 'Missing name in frontmatter');
|
|
228
|
+
assert.strictEqual(parsed.name, template.name);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test('has description', () => {
|
|
232
|
+
assert.ok(parsed.description, 'Missing description');
|
|
233
|
+
assert.ok(parsed.description.length > 10, 'Description too short');
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test('has version', () => {
|
|
237
|
+
assert.ok(parsed.version, 'Missing version');
|
|
238
|
+
assert.match(parsed.version, /^\d+\.\d+\.\d+$/, 'Version must be semver');
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test('tier is business', () => {
|
|
242
|
+
assert.strictEqual(parsed.tier, 'business');
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test('has Teams section with entries', () => {
|
|
246
|
+
assert.ok(parsed.teams.length > 0, 'Teams table is empty');
|
|
247
|
+
for (const team of parsed.teams) {
|
|
248
|
+
assert.ok(team.team, 'Team row missing team name');
|
|
249
|
+
assert.ok(team.lead, 'Team row missing lead');
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test('has Roles section with entries', () => {
|
|
254
|
+
assert.ok(parsed.roles.length > 0, 'Roles table is empty');
|
|
255
|
+
for (const role of parsed.roles) {
|
|
256
|
+
assert.ok(role.role, 'Role row missing role name');
|
|
257
|
+
assert.ok(role.permissions, 'Role row missing permissions');
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test('has Policies section', () => {
|
|
262
|
+
const policyKeys = Object.keys(parsed.policies);
|
|
263
|
+
assert.ok(policyKeys.length > 0, 'No policies found');
|
|
264
|
+
assert.ok(policyKeys.includes('code_review'), 'Missing Code Review policy');
|
|
265
|
+
assert.ok(policyKeys.includes('security'), 'Missing Security policy');
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test('has code review policy with minimum_reviewers', () => {
|
|
269
|
+
const cr = parsed.policies.code_review;
|
|
270
|
+
assert.ok(cr, 'Missing code_review policy');
|
|
271
|
+
const minReviewers = cr.find((r) => r.key === 'minimum_reviewers');
|
|
272
|
+
assert.ok(minReviewers, 'Missing minimum_reviewers in Code Review policy');
|
|
273
|
+
const count = parseInt(minReviewers.value, 10);
|
|
274
|
+
assert.ok(count >= 1, 'minimum_reviewers must be >= 1');
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test('has security policy with dependency audit', () => {
|
|
278
|
+
const sec = parsed.policies.security;
|
|
279
|
+
assert.ok(sec, 'Missing security policy');
|
|
280
|
+
const audit = sec.find((r) => r.key === 'dependency_audit_frequency');
|
|
281
|
+
assert.ok(audit, 'Missing dependency_audit_frequency in Security policy');
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
test('has Approval Flows section', () => {
|
|
285
|
+
const flowKeys = Object.keys(parsed.approvalFlows);
|
|
286
|
+
assert.ok(flowKeys.length > 0, 'No approval flows found');
|
|
287
|
+
assert.ok(flowKeys.includes('feature_launch'), 'Missing Feature Launch flow');
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test('has valid governance level', () => {
|
|
291
|
+
const validLevels = ['minimal', 'moderate', 'maximum'];
|
|
292
|
+
assert.ok(
|
|
293
|
+
validLevels.includes(parsed.governanceLevel.level),
|
|
294
|
+
`Governance level '${parsed.governanceLevel.level}' not in [${validLevels.join(', ')}]`,
|
|
295
|
+
);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test('governance settings reference sentinel', () => {
|
|
299
|
+
const hasSentinel = parsed.governanceLevel.settings.some((s) => s.toLowerCase().includes('sentinel'));
|
|
300
|
+
assert.ok(hasSentinel, 'Governance settings should reference sentinel mode');
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test('## Governance Level section exists in body', () => {
|
|
304
|
+
assert.ok(content.includes('## Governance Level'), 'Missing ## Governance Level heading');
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test('references rune pack names correctly', () => {
|
|
308
|
+
// All pack references should be @rune-pro/* or @rune-business/*
|
|
309
|
+
const packRefs = content.match(/@rune-(?:pro|business)\/[\w-]+/g) || [];
|
|
310
|
+
for (const ref of packRefs) {
|
|
311
|
+
assert.match(ref, /^@rune-(pro|business)\/[a-z][\w-]*$/, `Invalid pack reference: ${ref}`);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// ─── Governance level scaling tests ───────────────────────────────
|
|
319
|
+
|
|
320
|
+
(HAS_BUSINESS ? describe : describe.skip)('org template governance scaling', () => {
|
|
321
|
+
const templateData = {};
|
|
322
|
+
for (const name of ['startup', 'mid-size', 'enterprise']) {
|
|
323
|
+
const filePath = path.join(ORG_TEMPLATES_DIR, `${name}.md`);
|
|
324
|
+
if (existsSync(filePath)) {
|
|
325
|
+
templateData[name] = parseOrgConfig(readFileSync(filePath, 'utf-8'), filePath);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
test('startup has minimal governance', () => {
|
|
330
|
+
assert.ok(templateData.startup, 'Missing startup template');
|
|
331
|
+
assert.strictEqual(templateData.startup.governanceLevel.level, 'minimal');
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
test('mid-size has moderate governance', () => {
|
|
335
|
+
assert.ok(templateData['mid-size'], 'Missing mid-size template');
|
|
336
|
+
assert.strictEqual(templateData['mid-size'].governanceLevel.level, 'moderate');
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
test('enterprise has maximum governance', () => {
|
|
340
|
+
assert.ok(templateData.enterprise, 'Missing enterprise template');
|
|
341
|
+
assert.strictEqual(templateData.enterprise.governanceLevel.level, 'maximum');
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
test('team count scales: startup < mid-size < enterprise', () => {
|
|
345
|
+
const s = templateData.startup?.teams.length || 0;
|
|
346
|
+
const m = templateData['mid-size']?.teams.length || 0;
|
|
347
|
+
const e = templateData.enterprise?.teams.length || 0;
|
|
348
|
+
assert.ok(s < m, `startup teams (${s}) should be < mid-size (${m})`);
|
|
349
|
+
assert.ok(m <= e, `mid-size teams (${m}) should be <= enterprise (${e})`);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
test('role count scales: startup < mid-size < enterprise', () => {
|
|
353
|
+
const s = templateData.startup?.roles.length || 0;
|
|
354
|
+
const m = templateData['mid-size']?.roles.length || 0;
|
|
355
|
+
const e = templateData.enterprise?.roles.length || 0;
|
|
356
|
+
assert.ok(s < m, `startup roles (${s}) should be < mid-size (${m})`);
|
|
357
|
+
assert.ok(m <= e, `mid-size roles (${m}) should be <= enterprise (${e})`);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
test('startup minimum_reviewers <= mid-size <= enterprise', () => {
|
|
361
|
+
const getMinReviewers = (data) => {
|
|
362
|
+
const cr = data?.policies.code_review;
|
|
363
|
+
if (!cr) return 0;
|
|
364
|
+
const r = cr.find((x) => x.key === 'minimum_reviewers');
|
|
365
|
+
return r ? parseInt(r.value, 10) : 0;
|
|
366
|
+
};
|
|
367
|
+
const s = getMinReviewers(templateData.startup);
|
|
368
|
+
const m = getMinReviewers(templateData['mid-size']);
|
|
369
|
+
const e = getMinReviewers(templateData.enterprise);
|
|
370
|
+
assert.ok(s <= m, `startup reviewers (${s}) should be <= mid-size (${m})`);
|
|
371
|
+
assert.ok(m <= e, `mid-size reviewers (${m}) should be <= enterprise (${e})`);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
test('enterprise has more policy categories than startup', () => {
|
|
375
|
+
const sKeys = Object.keys(templateData.startup?.policies || {}).length;
|
|
376
|
+
const eKeys = Object.keys(templateData.enterprise?.policies || {}).length;
|
|
377
|
+
assert.ok(eKeys >= sKeys, `enterprise policies (${eKeys}) should be >= startup (${sKeys})`);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
test('enterprise has more approval flows than startup', () => {
|
|
381
|
+
const sFlows = Object.keys(templateData.startup?.approvalFlows || {}).length;
|
|
382
|
+
const eFlows = Object.keys(templateData.enterprise?.approvalFlows || {}).length;
|
|
383
|
+
assert.ok(eFlows >= sFlows, `enterprise flows (${eFlows}) should be >= startup (${sFlows})`);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
test('enterprise governance settings are more comprehensive', () => {
|
|
387
|
+
const sSettings = templateData.startup?.governanceLevel.settings.length || 0;
|
|
388
|
+
const eSettings = templateData.enterprise?.governanceLevel.settings.length || 0;
|
|
389
|
+
assert.ok(eSettings >= sSettings, `enterprise settings (${eSettings}) should be >= startup (${sSettings})`);
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// ─── Sentinel + preflight org integration ─────────────────────────
|
|
394
|
+
|
|
395
|
+
describe('sentinel/preflight org policy integration', () => {
|
|
396
|
+
const sentinelPath = path.join(SKILLS_DIR, 'sentinel', 'SKILL.md');
|
|
397
|
+
const preflightPath = path.join(SKILLS_DIR, 'preflight', 'SKILL.md');
|
|
398
|
+
|
|
399
|
+
test('sentinel references .rune/org/org.md', () => {
|
|
400
|
+
const content = readFileSync(sentinelPath, 'utf-8');
|
|
401
|
+
assert.ok(
|
|
402
|
+
content.includes('.rune/org/org.md'),
|
|
403
|
+
'sentinel should reference .rune/org/org.md for org policy loading',
|
|
404
|
+
);
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
test('sentinel has Organization Policy Enforcement step', () => {
|
|
408
|
+
const content = readFileSync(sentinelPath, 'utf-8');
|
|
409
|
+
assert.ok(
|
|
410
|
+
content.includes('Organization Policy Enforcement'),
|
|
411
|
+
'sentinel should have Organization Policy Enforcement step',
|
|
412
|
+
);
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
test('sentinel handles missing org config gracefully', () => {
|
|
416
|
+
const content = readFileSync(sentinelPath, 'utf-8');
|
|
417
|
+
assert.ok(content.includes('no org config'), 'sentinel should handle missing org config');
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
test('preflight references .rune/org/org.md', () => {
|
|
421
|
+
const content = readFileSync(preflightPath, 'utf-8');
|
|
422
|
+
assert.ok(content.includes('.rune/org/org.md'), 'preflight should reference .rune/org/org.md for org requirements');
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
test('preflight has Organization Approval Requirements step', () => {
|
|
426
|
+
const content = readFileSync(preflightPath, 'utf-8');
|
|
427
|
+
assert.ok(
|
|
428
|
+
content.includes('Organization Approval Requirements'),
|
|
429
|
+
'preflight should have Organization Approval Requirements step',
|
|
430
|
+
);
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
test('preflight handles missing org config gracefully', () => {
|
|
434
|
+
const content = readFileSync(preflightPath, 'utf-8');
|
|
435
|
+
assert.ok(content.includes('no org config'), 'preflight should handle missing org config');
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
test('sentinel step is numbered 4.86 (between contract 4.85 and six-gate 4.9)', () => {
|
|
439
|
+
const content = readFileSync(sentinelPath, 'utf-8');
|
|
440
|
+
assert.ok(content.includes('Step 4.86'), 'sentinel org policy step should be numbered 4.86');
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
test('preflight step is numbered 4.6 (between domain hooks 4.5 and composite score 4.8)', () => {
|
|
444
|
+
const content = readFileSync(preflightPath, 'utf-8');
|
|
445
|
+
assert.ok(content.includes('Step 4.6'), 'preflight org requirements step should be numbered 4.6');
|
|
446
|
+
});
|
|
447
|
+
});
|