kodelyth-ecc 1.8.0 → 1.8.2
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/CLAUDE.md +1 -1
- package/VERSION +1 -1
- package/bin/kodelyth-ecc.js +54 -5
- package/install.ps1 +30 -13
- package/install.sh +11 -4
- package/package.json +1 -1
- package/rules/common/agent-intent-routing.md +96 -195
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -47
- package/.github/ISSUE_TEMPLATE/config.yml +0 -8
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -41
- package/.github/ISSUE_TEMPLATE/good_first_issue.md +0 -36
- package/.github/ISSUE_TEMPLATE/new_agent.md +0 -52
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -53
- package/.github/workflows/ci.yml +0 -45
- package/.github/workflows/publish.yml +0 -60
- package/brand/concepts.svg +0 -109
- package/brand/convert.js +0 -161
- package/brand/favicon.svg +0 -7
- package/brand/fb-cover.svg +0 -34
- package/brand/fb-profile.svg +0 -21
- package/brand/kodelyth-dark.svg +0 -24
- package/brand/kodelyth-light.svg +0 -24
- package/brand/kodelyth-mark.svg +0 -20
- package/brand/package.json +0 -12
- package/docs/dashboard.md +0 -211
- package/docs/evolve.md +0 -303
- package/docs/mcp-clients.md +0 -167
- package/docs/mcp.md +0 -178
- package/docs/replay.md +0 -244
- package/docs/supply-chain.md +0 -207
- package/docs/swarm.md +0 -243
- package/skills/skill-comply/tests/test_grader.py +0 -137
- package/skills/skill-comply/tests/test_parser.py +0 -90
- package/social/card-agents.svg +0 -104
- package/social/card-install.svg +0 -58
- package/social/card-main.svg +0 -54
- package/social/facebook-v150.svg +0 -110
- package/social/fb-ad-main.svg +0 -128
- package/social/fb-post-features.svg +0 -118
- package/social/fb-post-launch.svg +0 -144
- package/social/fb-post-platforms.svg +0 -135
- package/social/github-social-preview.svg +0 -151
- package/social/hype-compound-learning.svg +0 -129
- package/social/hype-devil-mode.svg +0 -240
- package/social/hype-mcp-server.svg +0 -148
- package/social/hype-parallel-agents.svg +0 -162
- package/social/hype-stats-hero.svg +0 -138
- package/social/og-image.svg +0 -151
- package/social/readme-agents.svg +0 -138
- package/social/readme-hero.svg +0 -102
- package/social/section-agents.svg +0 -69
- package/social/section-author.svg +0 -66
- package/social/section-dashboard.svg +0 -71
- package/social/section-devil.svg +0 -66
- package/social/section-hooks.svg +0 -63
- package/social/section-install.svg +0 -54
- package/social/section-learning.svg +0 -64
- package/social/section-mcp.svg +0 -58
- package/social/section-memory.svg +0 -69
- package/social/section-parallel.svg +0 -84
- package/social/section-routing.svg +0 -63
- package/social/twitter-threads.md +0 -621
- package/social/x-card-agents-grid.svg +0 -120
- package/social/x-card-free.svg +0 -71
- package/social/x-card-hook.svg +0 -94
- package/tests/dashboard/data.test.js +0 -391
- package/tests/dashboard/server.test.js +0 -257
- package/tests/evolve/analyze.test.js +0 -169
- package/tests/evolve/proposals.test.js +0 -173
- package/tests/evolve/stats.test.js +0 -167
- package/tests/hooks/branch-name-check.test.js +0 -184
- package/tests/hooks/smart-suggest.test.js +0 -149
- package/tests/hooks/test-reminder.test.js +0 -181
- package/tests/mcp/catalog.test.js +0 -98
- package/tests/mcp/client.test.js +0 -113
- package/tests/mcp/resources-prompts.test.js +0 -70
- package/tests/mcp/tools.test.js +0 -159
- package/tests/memory/auto-recall.test.js +0 -132
- package/tests/memory/instincts.test.js +0 -258
- package/tests/memory/store.test.js +0 -203
- package/tests/replay/bundle.test.js +0 -181
- package/tests/replay/replay.test.js +0 -131
- package/tests/router/classify.test.js +0 -164
- package/tests/run-all.js +0 -55
- package/tests/safety/patterns.test.js +0 -94
- package/tests/safety/prompt-injection-guard.test.js +0 -111
- package/tests/safety/token-budget.test.js +0 -119
- package/tests/supply-chain/manifest.test.js +0 -147
- package/tests/supply-chain/sbom.test.js +0 -170
- package/tests/supply-chain/verify.test.js +0 -146
- package/tests/swarm/build-plan.test.js +0 -188
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
// Tests for scripts/supply-chain/manifest.js
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
const test = require('node:test');
|
|
5
|
-
const assert = require('node:assert/strict');
|
|
6
|
-
const fs = require('node:fs');
|
|
7
|
-
const os = require('node:os');
|
|
8
|
-
const path = require('node:path');
|
|
9
|
-
const crypto = require('node:crypto');
|
|
10
|
-
|
|
11
|
-
const M = require('../../scripts/supply-chain/manifest.js');
|
|
12
|
-
|
|
13
|
-
function makeFakeRepo(layout) {
|
|
14
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'kodelyth-manifest-'));
|
|
15
|
-
for (const [rel, content] of Object.entries(layout)) {
|
|
16
|
-
const abs = path.join(dir, rel);
|
|
17
|
-
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
18
|
-
const data = typeof content === 'string' ? content : JSON.stringify(content);
|
|
19
|
-
fs.writeFileSync(abs, data);
|
|
20
|
-
}
|
|
21
|
-
return dir;
|
|
22
|
-
}
|
|
23
|
-
function cleanup(dir) {
|
|
24
|
-
try { fs.rmSync(dir, { recursive: true, force: true }); } catch { /* */ }
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
test('schema constants exposed', () => {
|
|
28
|
-
assert.equal(M.MANIFEST_SCHEMA, 'kodelyth.content-manifest/v1');
|
|
29
|
-
assert.equal(M.MANIFEST_VERSION, 1);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test('hashFile: sha256 of known content', () => {
|
|
33
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'kodelyth-h-'));
|
|
34
|
-
const f = path.join(dir, 'a.txt');
|
|
35
|
-
fs.writeFileSync(f, 'hello');
|
|
36
|
-
try {
|
|
37
|
-
const expected = crypto.createHash('sha256').update('hello').digest('hex');
|
|
38
|
-
assert.equal(M.hashFile(f), expected);
|
|
39
|
-
} finally { cleanup(dir); }
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test('walkFiles: includes shipped root files when present', () => {
|
|
43
|
-
const dir = makeFakeRepo({
|
|
44
|
-
'package.json': { name: 'fake', version: '0.1.0' },
|
|
45
|
-
'README.md': '# fake',
|
|
46
|
-
'CHANGELOG.md': '## 0.1.0',
|
|
47
|
-
'agents/a.md': 'agent a',
|
|
48
|
-
'skills/s/SKILL.md': '# skill s',
|
|
49
|
-
});
|
|
50
|
-
try {
|
|
51
|
-
const files = M.walkFiles(dir);
|
|
52
|
-
const rels = files.map(f => path.relative(dir, f).split(path.sep).join('/'));
|
|
53
|
-
assert.ok(rels.includes('package.json'));
|
|
54
|
-
assert.ok(rels.includes('README.md'));
|
|
55
|
-
assert.ok(rels.includes('CHANGELOG.md'));
|
|
56
|
-
assert.ok(rels.includes('agents/a.md'));
|
|
57
|
-
assert.ok(rels.includes('skills/s/SKILL.md'));
|
|
58
|
-
} finally { cleanup(dir); }
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test('walkFiles: skips node_modules and .git', () => {
|
|
62
|
-
const dir = makeFakeRepo({
|
|
63
|
-
'package.json': { name: 'fake', version: '0.1.0' },
|
|
64
|
-
'agents/a.md': 'a',
|
|
65
|
-
'agents/node_modules/x.js': 'should-skip',
|
|
66
|
-
'scripts/.git/HEAD': 'should-skip',
|
|
67
|
-
});
|
|
68
|
-
try {
|
|
69
|
-
const files = M.walkFiles(dir);
|
|
70
|
-
const rels = files.map(f => path.relative(dir, f).split(path.sep).join('/'));
|
|
71
|
-
assert.ok(rels.includes('agents/a.md'));
|
|
72
|
-
assert.ok(!rels.some(r => r.includes('node_modules')));
|
|
73
|
-
assert.ok(!rels.some(r => r.includes('.git')));
|
|
74
|
-
} finally { cleanup(dir); }
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test('walkFiles: deterministic ordering', () => {
|
|
78
|
-
const dir = makeFakeRepo({
|
|
79
|
-
'package.json': { name: 'fake', version: '0.1.0' },
|
|
80
|
-
'agents/z.md': 'z',
|
|
81
|
-
'agents/a.md': 'a',
|
|
82
|
-
'agents/m.md': 'm',
|
|
83
|
-
});
|
|
84
|
-
try {
|
|
85
|
-
const a = M.walkFiles(dir);
|
|
86
|
-
const b = M.walkFiles(dir);
|
|
87
|
-
assert.deepEqual(a, b);
|
|
88
|
-
// Sorted alphabetically by absolute path → relative path.
|
|
89
|
-
const rels = a.map(f => path.relative(dir, f));
|
|
90
|
-
const sorted = [...rels].sort();
|
|
91
|
-
assert.deepEqual(rels, sorted);
|
|
92
|
-
} finally { cleanup(dir); }
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
test('generateManifest: rejects missing rootDir', () => {
|
|
96
|
-
assert.throws(() => M.generateManifest({}), /rootDir is required/);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test('generateManifest: produces stable digest across runs', () => {
|
|
100
|
-
const dir = makeFakeRepo({
|
|
101
|
-
'package.json': { name: 'fake', version: '0.1.0' },
|
|
102
|
-
'agents/a.md': 'agent a',
|
|
103
|
-
});
|
|
104
|
-
try {
|
|
105
|
-
const m1 = M.generateManifest({ rootDir: dir, timestamp: 'fixed' });
|
|
106
|
-
const m2 = M.generateManifest({ rootDir: dir, timestamp: 'fixed' });
|
|
107
|
-
assert.equal(m1.digest, m2.digest);
|
|
108
|
-
assert.deepEqual(m1.files, m2.files);
|
|
109
|
-
} finally { cleanup(dir); }
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
test('generateManifest: digest changes when content changes', () => {
|
|
113
|
-
const dir = makeFakeRepo({
|
|
114
|
-
'package.json': { name: 'fake', version: '0.1.0' },
|
|
115
|
-
'agents/a.md': 'v1',
|
|
116
|
-
});
|
|
117
|
-
try {
|
|
118
|
-
const m1 = M.generateManifest({ rootDir: dir });
|
|
119
|
-
fs.writeFileSync(path.join(dir, 'agents/a.md'), 'v2');
|
|
120
|
-
const m2 = M.generateManifest({ rootDir: dir });
|
|
121
|
-
assert.notEqual(m1.digest, m2.digest);
|
|
122
|
-
} finally { cleanup(dir); }
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
test('generateManifest: every entry has path/size/sha256', () => {
|
|
126
|
-
const dir = makeFakeRepo({
|
|
127
|
-
'package.json': { name: 'fake', version: '0.1.0' },
|
|
128
|
-
'agents/a.md': 'hello world',
|
|
129
|
-
});
|
|
130
|
-
try {
|
|
131
|
-
const m = M.generateManifest({ rootDir: dir });
|
|
132
|
-
assert.ok(m.files.length >= 2);
|
|
133
|
-
for (const f of m.files) {
|
|
134
|
-
assert.ok(typeof f.path === 'string');
|
|
135
|
-
assert.ok(typeof f.size === 'number');
|
|
136
|
-
assert.match(f.sha256, /^[0-9a-f]{64}$/);
|
|
137
|
-
}
|
|
138
|
-
} finally { cleanup(dir); }
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
test('generateManifest: real repo produces large manifest', () => {
|
|
142
|
-
const repoRoot = path.resolve(__dirname, '..', '..');
|
|
143
|
-
const m = M.generateManifest({ rootDir: repoRoot });
|
|
144
|
-
assert.equal(m.package, 'kodelyth-ecc');
|
|
145
|
-
assert.ok(m.file_count > 100, `expected many files, got ${m.file_count}`);
|
|
146
|
-
assert.match(m.digest, /^[0-9a-f]{64}$/);
|
|
147
|
-
});
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
// Tests for scripts/supply-chain/sbom.js
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
const test = require('node:test');
|
|
5
|
-
const assert = require('node:assert/strict');
|
|
6
|
-
const fs = require('node:fs');
|
|
7
|
-
const os = require('node:os');
|
|
8
|
-
const path = require('node:path');
|
|
9
|
-
|
|
10
|
-
const SBOM = require('../../scripts/supply-chain/sbom.js');
|
|
11
|
-
const { generateSBOM, buildComponentFromLockEntry, _internals } = SBOM;
|
|
12
|
-
|
|
13
|
-
// ── helpers: build a fake mini-package on disk ───────────────────────────────
|
|
14
|
-
|
|
15
|
-
function makeFakePkg(layout) {
|
|
16
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'kodelyth-sbom-'));
|
|
17
|
-
for (const [rel, content] of Object.entries(layout)) {
|
|
18
|
-
const abs = path.join(dir, rel);
|
|
19
|
-
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
20
|
-
fs.writeFileSync(abs, typeof content === 'string' ? content : JSON.stringify(content, null, 2));
|
|
21
|
-
}
|
|
22
|
-
return dir;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function cleanup(dir) {
|
|
26
|
-
try { fs.rmSync(dir, { recursive: true, force: true }); } catch { /* */ }
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// ── SBOM_FORMAT / SBOM_SPEC_VERSION ──────────────────────────────────────────
|
|
30
|
-
|
|
31
|
-
test('SBOM constants exposed', () => {
|
|
32
|
-
assert.equal(SBOM.SBOM_FORMAT, 'CycloneDX');
|
|
33
|
-
assert.equal(SBOM.SBOM_SPEC_VERSION, '1.5');
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
// ── generateSBOM ─────────────────────────────────────────────────────────────
|
|
37
|
-
|
|
38
|
-
test('generateSBOM: rejects missing rootDir', () => {
|
|
39
|
-
assert.throws(() => generateSBOM({}), /rootDir is required/);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test('generateSBOM: with no lockfile produces a valid root-only BOM', () => {
|
|
43
|
-
const dir = makeFakePkg({
|
|
44
|
-
'package.json': { name: 'fake-pkg', version: '0.1.0', license: 'MIT' },
|
|
45
|
-
});
|
|
46
|
-
try {
|
|
47
|
-
const bom = generateSBOM({ rootDir: dir, timestamp: '2026-05-10T17:00:00Z' });
|
|
48
|
-
assert.equal(bom.bomFormat, 'CycloneDX');
|
|
49
|
-
assert.equal(bom.specVersion, '1.5');
|
|
50
|
-
assert.equal(bom.metadata.component.name, 'fake-pkg');
|
|
51
|
-
assert.equal(bom.metadata.component.version, '0.1.0');
|
|
52
|
-
assert.equal(bom.metadata.component.purl, 'pkg:npm/fake-pkg@0.1.0');
|
|
53
|
-
assert.deepEqual(bom.components, []);
|
|
54
|
-
assert.deepEqual(bom.dependencies, []);
|
|
55
|
-
assert.equal(bom.metadata.timestamp, '2026-05-10T17:00:00Z');
|
|
56
|
-
assert.match(bom.serialNumber, /^urn:uuid:[0-9a-f-]+$/);
|
|
57
|
-
} finally { cleanup(dir); }
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test('generateSBOM: with lockfile emits library components for each package', () => {
|
|
61
|
-
const dir = makeFakePkg({
|
|
62
|
-
'package.json': { name: 'fake-pkg', version: '0.1.0', dependencies: { foo: '^1.0.0' } },
|
|
63
|
-
'package-lock.json': {
|
|
64
|
-
name: 'fake-pkg',
|
|
65
|
-
version: '0.1.0',
|
|
66
|
-
lockfileVersion: 3,
|
|
67
|
-
packages: {
|
|
68
|
-
'': {
|
|
69
|
-
name: 'fake-pkg',
|
|
70
|
-
version: '0.1.0',
|
|
71
|
-
dependencies: { foo: '^1.0.0' },
|
|
72
|
-
},
|
|
73
|
-
'node_modules/foo': {
|
|
74
|
-
version: '1.2.3',
|
|
75
|
-
license: 'MIT',
|
|
76
|
-
integrity: 'sha512-' + Buffer.from('a'.repeat(64), 'hex').toString('base64'),
|
|
77
|
-
},
|
|
78
|
-
'node_modules/foo/node_modules/@scope/bar': {
|
|
79
|
-
version: '2.0.0',
|
|
80
|
-
license: 'Apache-2.0',
|
|
81
|
-
},
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
});
|
|
85
|
-
try {
|
|
86
|
-
const bom = generateSBOM({ rootDir: dir });
|
|
87
|
-
assert.equal(bom.components.length, 2);
|
|
88
|
-
const foo = bom.components.find(c => c.name === 'foo');
|
|
89
|
-
assert.ok(foo);
|
|
90
|
-
assert.equal(foo.version, '1.2.3');
|
|
91
|
-
assert.equal(foo.purl, 'pkg:npm/foo@1.2.3');
|
|
92
|
-
assert.equal(foo.scope, 'required');
|
|
93
|
-
assert.equal(foo.licenses[0].license.id, 'MIT');
|
|
94
|
-
assert.equal(foo.hashes[0].alg, 'SHA-512');
|
|
95
|
-
|
|
96
|
-
const scoped = bom.components.find(c => c.name === '@scope/bar');
|
|
97
|
-
assert.ok(scoped);
|
|
98
|
-
assert.equal(scoped.purl, 'pkg:npm/@scope/bar@2.0.0');
|
|
99
|
-
|
|
100
|
-
// Direct deps wired in dependencies graph.
|
|
101
|
-
assert.equal(bom.dependencies.length, 1);
|
|
102
|
-
assert.equal(bom.dependencies[0].ref, 'fake-pkg@0.1.0');
|
|
103
|
-
assert.deepEqual(bom.dependencies[0].dependsOn, ['foo@1.2.3']);
|
|
104
|
-
} finally { cleanup(dir); }
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test('generateSBOM: dev dependencies marked as scope=optional', () => {
|
|
108
|
-
const dir = makeFakePkg({
|
|
109
|
-
'package.json': { name: 'fake-pkg', version: '0.1.0' },
|
|
110
|
-
'package-lock.json': {
|
|
111
|
-
lockfileVersion: 3,
|
|
112
|
-
packages: {
|
|
113
|
-
'': { name: 'fake-pkg', version: '0.1.0' },
|
|
114
|
-
'node_modules/dev-only': { version: '1.0.0', dev: true },
|
|
115
|
-
},
|
|
116
|
-
},
|
|
117
|
-
});
|
|
118
|
-
try {
|
|
119
|
-
const bom = generateSBOM({ rootDir: dir });
|
|
120
|
-
const c = bom.components.find(x => x.name === 'dev-only');
|
|
121
|
-
assert.equal(c.scope, 'optional');
|
|
122
|
-
} finally { cleanup(dir); }
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
test('generateSBOM: real repo lockfile produces parseable BOM', () => {
|
|
126
|
-
const repoRoot = path.resolve(__dirname, '..', '..');
|
|
127
|
-
const bom = generateSBOM({ rootDir: repoRoot });
|
|
128
|
-
assert.equal(bom.metadata.component.name, 'kodelyth-ecc');
|
|
129
|
-
assert.ok(bom.components.length > 0, 'should have at least one component from real lockfile');
|
|
130
|
-
// Every component must have a purl.
|
|
131
|
-
for (const c of bom.components.slice(0, 10)) {
|
|
132
|
-
assert.match(c.purl, /^pkg:npm\//);
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
// ── helpers ──────────────────────────────────────────────────────────────────
|
|
137
|
-
|
|
138
|
-
test('buildComponentFromLockEntry: skips entries without name+version', () => {
|
|
139
|
-
assert.equal(buildComponentFromLockEntry('node_modules/x', {}), null);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
test('_internals.npmPurl: scoped packages keep slash', () => {
|
|
143
|
-
assert.equal(_internals.npmPurl('@scope/x', '1.0.0'), 'pkg:npm/@scope/x@1.0.0');
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
test('_internals.parseIntegrity: handles sha512 base64', () => {
|
|
147
|
-
const sri = 'sha512-' + Buffer.from('a'.repeat(64), 'hex').toString('base64');
|
|
148
|
-
const out = _internals.parseIntegrity(sri);
|
|
149
|
-
assert.equal(out[0].alg, 'SHA-512');
|
|
150
|
-
assert.match(out[0].content, /^[0-9a-f]+$/);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
test('_internals.parseIntegrity: skips unknown algorithms', () => {
|
|
154
|
-
const out = _internals.parseIntegrity('md5-AAAA');
|
|
155
|
-
assert.deepEqual(out, []);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
test('_internals.deriveSerial: stable for same inputs', () => {
|
|
159
|
-
const a = _internals.deriveSerial({ name: 'x', version: '1.0.0' }, '2026-05-10T00:00:00Z');
|
|
160
|
-
const b = _internals.deriveSerial({ name: 'x', version: '1.0.0' }, '2026-05-10T00:00:00Z');
|
|
161
|
-
assert.equal(a, b);
|
|
162
|
-
assert.match(a, /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
test('_internals.pkgNameFromPath: deepest scoped path wins', () => {
|
|
166
|
-
assert.equal(
|
|
167
|
-
_internals.pkgNameFromPath('node_modules/foo/node_modules/@scope/bar'),
|
|
168
|
-
'@scope/bar'
|
|
169
|
-
);
|
|
170
|
-
});
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
// Tests for scripts/supply-chain/verify.js
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
const test = require('node:test');
|
|
5
|
-
const assert = require('node:assert/strict');
|
|
6
|
-
const fs = require('node:fs');
|
|
7
|
-
const os = require('node:os');
|
|
8
|
-
const path = require('node:path');
|
|
9
|
-
|
|
10
|
-
const M = require('../../scripts/supply-chain/manifest.js');
|
|
11
|
-
const V = require('../../scripts/supply-chain/verify.js');
|
|
12
|
-
|
|
13
|
-
function makeFakeRepo(layout) {
|
|
14
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'kodelyth-verify-'));
|
|
15
|
-
for (const [rel, content] of Object.entries(layout)) {
|
|
16
|
-
const abs = path.join(dir, rel);
|
|
17
|
-
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
18
|
-
fs.writeFileSync(abs, typeof content === 'string' ? content : JSON.stringify(content));
|
|
19
|
-
}
|
|
20
|
-
return dir;
|
|
21
|
-
}
|
|
22
|
-
function cleanup(dir) { try { fs.rmSync(dir, { recursive: true, force: true }); } catch { /* */ } }
|
|
23
|
-
|
|
24
|
-
const FAKE_LAYOUT = {
|
|
25
|
-
'package.json': { name: 'fake', version: '0.1.0' },
|
|
26
|
-
'agents/a.md': 'agent a v1',
|
|
27
|
-
'skills/s/SKILL.md': '# s',
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
// ── argument validation ──────────────────────────────────────────────────────
|
|
31
|
-
|
|
32
|
-
test('verifyAgainstManifest: rejects missing rootDir', () => {
|
|
33
|
-
assert.throws(() => V.verifyAgainstManifest({ manifest: { schema: M.MANIFEST_SCHEMA, files: [] } }),
|
|
34
|
-
/rootDir is required/);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
test('verifyAgainstManifest: rejects missing manifest', () => {
|
|
38
|
-
assert.throws(() => V.verifyAgainstManifest({ rootDir: '/tmp' }), /manifest is required/);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
test('verifyAgainstManifest: rejects unsupported manifest schema', () => {
|
|
42
|
-
assert.throws(
|
|
43
|
-
() => V.verifyAgainstManifest({ rootDir: '/tmp', manifest: { schema: 'wrong', files: [] } }),
|
|
44
|
-
/unsupported manifest schema/
|
|
45
|
-
);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
// ── happy path ───────────────────────────────────────────────────────────────
|
|
49
|
-
|
|
50
|
-
test('verifyAgainstManifest: clean repo returns ok=true with all files', () => {
|
|
51
|
-
const dir = makeFakeRepo(FAKE_LAYOUT);
|
|
52
|
-
try {
|
|
53
|
-
const manifest = M.generateManifest({ rootDir: dir });
|
|
54
|
-
const r = V.verifyAgainstManifest({ rootDir: dir, manifest });
|
|
55
|
-
assert.equal(r.ok, true);
|
|
56
|
-
assert.equal(r.summary.modified, 0);
|
|
57
|
-
assert.equal(r.summary.missing, 0);
|
|
58
|
-
assert.equal(r.summary.extra, 0);
|
|
59
|
-
assert.equal(r.summary.ok, manifest.file_count);
|
|
60
|
-
} finally { cleanup(dir); }
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
// ── modified file ────────────────────────────────────────────────────────────
|
|
64
|
-
|
|
65
|
-
test('verifyAgainstManifest: detects modified file with sha mismatch', () => {
|
|
66
|
-
const dir = makeFakeRepo(FAKE_LAYOUT);
|
|
67
|
-
try {
|
|
68
|
-
const manifest = M.generateManifest({ rootDir: dir });
|
|
69
|
-
fs.writeFileSync(path.join(dir, 'agents/a.md'), 'agent a v2');
|
|
70
|
-
const r = V.verifyAgainstManifest({ rootDir: dir, manifest });
|
|
71
|
-
assert.equal(r.ok, false);
|
|
72
|
-
assert.equal(r.summary.modified, 1);
|
|
73
|
-
assert.equal(r.details.modified[0].path, 'agents/a.md');
|
|
74
|
-
assert.match(r.details.modified[0].expected_sha256, /^[0-9a-f]{64}$/);
|
|
75
|
-
assert.match(r.details.modified[0].actual_sha256, /^[0-9a-f]{64}$/);
|
|
76
|
-
assert.notEqual(
|
|
77
|
-
r.details.modified[0].expected_sha256,
|
|
78
|
-
r.details.modified[0].actual_sha256
|
|
79
|
-
);
|
|
80
|
-
} finally { cleanup(dir); }
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
// ── missing file ─────────────────────────────────────────────────────────────
|
|
84
|
-
|
|
85
|
-
test('verifyAgainstManifest: detects missing file', () => {
|
|
86
|
-
const dir = makeFakeRepo(FAKE_LAYOUT);
|
|
87
|
-
try {
|
|
88
|
-
const manifest = M.generateManifest({ rootDir: dir });
|
|
89
|
-
fs.unlinkSync(path.join(dir, 'agents/a.md'));
|
|
90
|
-
const r = V.verifyAgainstManifest({ rootDir: dir, manifest });
|
|
91
|
-
assert.equal(r.ok, false);
|
|
92
|
-
assert.equal(r.summary.missing, 1);
|
|
93
|
-
assert.equal(r.details.missing[0].path, 'agents/a.md');
|
|
94
|
-
} finally { cleanup(dir); }
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
// ── extra file ───────────────────────────────────────────────────────────────
|
|
98
|
-
|
|
99
|
-
test('verifyAgainstManifest: extra files reported but do not fail verify', () => {
|
|
100
|
-
const dir = makeFakeRepo(FAKE_LAYOUT);
|
|
101
|
-
try {
|
|
102
|
-
const manifest = M.generateManifest({ rootDir: dir });
|
|
103
|
-
fs.writeFileSync(path.join(dir, 'agents/extra.md'), 'unexpected');
|
|
104
|
-
const r = V.verifyAgainstManifest({ rootDir: dir, manifest });
|
|
105
|
-
assert.equal(r.ok, true, 'extras alone should not fail verify');
|
|
106
|
-
assert.equal(r.summary.extra, 1);
|
|
107
|
-
assert.ok(r.details.extra.includes('agents/extra.md'));
|
|
108
|
-
} finally { cleanup(dir); }
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
// ── combined ─────────────────────────────────────────────────────────────────
|
|
112
|
-
|
|
113
|
-
test('verifyAgainstManifest: counts modified, missing, extra independently', () => {
|
|
114
|
-
const dir = makeFakeRepo(FAKE_LAYOUT);
|
|
115
|
-
try {
|
|
116
|
-
const manifest = M.generateManifest({ rootDir: dir });
|
|
117
|
-
fs.writeFileSync(path.join(dir, 'agents/a.md'), 'tampered');
|
|
118
|
-
fs.unlinkSync(path.join(dir, 'skills/s/SKILL.md'));
|
|
119
|
-
fs.writeFileSync(path.join(dir, 'agents/new.md'), 'fresh');
|
|
120
|
-
const r = V.verifyAgainstManifest({ rootDir: dir, manifest });
|
|
121
|
-
assert.equal(r.ok, false);
|
|
122
|
-
assert.equal(r.summary.modified, 1);
|
|
123
|
-
assert.equal(r.summary.missing, 1);
|
|
124
|
-
assert.equal(r.summary.extra, 1);
|
|
125
|
-
} finally { cleanup(dir); }
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
// ── round-trip integration ───────────────────────────────────────────────────
|
|
129
|
-
|
|
130
|
-
test('verifyAgainstManifest: round-trips clean across copy', () => {
|
|
131
|
-
const dir1 = makeFakeRepo(FAKE_LAYOUT);
|
|
132
|
-
const dir2 = fs.mkdtempSync(path.join(os.tmpdir(), 'kodelyth-verify-2-'));
|
|
133
|
-
try {
|
|
134
|
-
// Copy files to dir2.
|
|
135
|
-
for (const f of M.walkFiles(dir1)) {
|
|
136
|
-
const rel = path.relative(dir1, f);
|
|
137
|
-
const dest = path.join(dir2, rel);
|
|
138
|
-
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
139
|
-
fs.copyFileSync(f, dest);
|
|
140
|
-
}
|
|
141
|
-
const manifest = M.generateManifest({ rootDir: dir1 });
|
|
142
|
-
const r = V.verifyAgainstManifest({ rootDir: dir2, manifest });
|
|
143
|
-
assert.equal(r.ok, true);
|
|
144
|
-
assert.equal(r.summary.ok, manifest.file_count);
|
|
145
|
-
} finally { cleanup(dir1); cleanup(dir2); }
|
|
146
|
-
});
|
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
// Tests for scripts/swarm/build-plan.js — pure builder, no tmux/git side effects.
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
const test = require('node:test');
|
|
5
|
-
const assert = require('node:assert/strict');
|
|
6
|
-
const path = require('path');
|
|
7
|
-
|
|
8
|
-
const B = require('../../scripts/swarm/build-plan');
|
|
9
|
-
const O = require('../../scripts/lib/tmux-worktree-orchestrator.js');
|
|
10
|
-
|
|
11
|
-
// ── pickAgents ───────────────────────────────────────────────────────────────
|
|
12
|
-
|
|
13
|
-
test('pickAgents: explicit list wins over count + signals', () => {
|
|
14
|
-
const picked = B.pickAgents({
|
|
15
|
-
count: 4,
|
|
16
|
-
task: 'security audit oauth flow',
|
|
17
|
-
explicit: ['code-architect', 'release-captain'],
|
|
18
|
-
});
|
|
19
|
-
assert.deepEqual(picked, ['code-architect', 'release-captain']);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
test('pickAgents: signal-driven for security task', () => {
|
|
23
|
-
const picked = B.pickAgents({ count: 4, task: 'security audit oauth flow for OWASP top 10' });
|
|
24
|
-
assert.ok(picked.includes('security-reviewer'), `expected security-reviewer in ${picked}`);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test('pickAgents: signal-driven for performance task', () => {
|
|
28
|
-
const picked = B.pickAgents({ count: 4, task: 'investigate p99 latency spike on the payments API' });
|
|
29
|
-
assert.ok(picked.includes('performance-optimizer'));
|
|
30
|
-
assert.ok(picked.includes('api-guardian'));
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test('pickAgents: signal-driven for incident task picks incident-commander + debug-detective', () => {
|
|
34
|
-
const picked = B.pickAgents({ count: 4, task: 'production is down, P0 incident, why is auth service failing' });
|
|
35
|
-
assert.ok(picked.includes('incident-commander'));
|
|
36
|
-
assert.ok(picked.includes('debug-detective'));
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test('pickAgents: includes baseline anchors when room remains', () => {
|
|
40
|
-
const picked = B.pickAgents({ count: 4, task: 'investigate p99 latency on payments API' });
|
|
41
|
-
// After signals (perf, api), should fill with code-reviewer + pair-programmer.
|
|
42
|
-
assert.ok(picked.includes('code-reviewer') || picked.includes('pair-programmer'));
|
|
43
|
-
assert.equal(picked.length, 4);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test('pickAgents: count clamped to upper bound 12 even with many signals', () => {
|
|
47
|
-
// A task with many signals so the picker has plenty of candidates.
|
|
48
|
-
const richTask = 'security audit with perf load-test architecture API tests refactor docs release incident debug database';
|
|
49
|
-
const picked = B.pickAgents({ count: 99, task: richTask });
|
|
50
|
-
assert.ok(picked.length <= 12, `expected ≤12, got ${picked.length}`);
|
|
51
|
-
assert.ok(picked.length > 8, `expected >8 with rich signals, got ${picked.length}`);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test('pickAgents: count clamps to available pool when signals empty', () => {
|
|
55
|
-
// No matching signals + no signal agent rotation past 8 = bounded by pool size.
|
|
56
|
-
const picked = B.pickAgents({ count: 99, task: 'any' });
|
|
57
|
-
assert.ok(picked.length >= 8 && picked.length <= 12);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test('pickAgents: count of 1 produces 1 agent', () => {
|
|
61
|
-
assert.equal(B.pickAgents({ count: 1, task: 'any' }).length, 1);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
test('pickAgents: falsy count falls back to default of 4', () => {
|
|
65
|
-
assert.equal(B.pickAgents({ count: 0, task: 'any' }).length, 4);
|
|
66
|
-
assert.equal(B.pickAgents({ count: undefined, task: 'any' }).length, 4);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
test('pickAgents: deduplicates if signal + anchor + rotation overlap', () => {
|
|
70
|
-
const picked = B.pickAgents({ count: 6, task: 'do a code review of payment refactor with security implications' });
|
|
71
|
-
assert.equal(new Set(picked).size, picked.length, 'no duplicate agents');
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
// ── shapeTaskForAgent ────────────────────────────────────────────────────────
|
|
75
|
-
|
|
76
|
-
test('shapeTaskForAgent: includes agent name + required sections', () => {
|
|
77
|
-
const out = B.shapeTaskForAgent('security-reviewer', 'audit oauth flow');
|
|
78
|
-
assert.match(out, /# security-reviewer — swarm task/);
|
|
79
|
-
assert.match(out, /audit oauth flow/);
|
|
80
|
-
assert.match(out, /## Required handoff sections/);
|
|
81
|
-
assert.match(out, /1\. Summary/);
|
|
82
|
-
assert.match(out, /2\. Files Changed/);
|
|
83
|
-
assert.match(out, /3\. Validation/);
|
|
84
|
-
assert.match(out, /4\. Remaining Risks/);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test('shapeTaskForAgent: handles empty task gracefully', () => {
|
|
88
|
-
const out = B.shapeTaskForAgent('code-reviewer', '');
|
|
89
|
-
assert.match(out, /no task description provided/);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
// ── buildWorker ──────────────────────────────────────────────────────────────
|
|
93
|
-
|
|
94
|
-
test('buildWorker: claude harness produces a launcher with task placeholder', () => {
|
|
95
|
-
const w = B.buildWorker({ agent: 'security-reviewer', task: 'foo', harness: 'claude' });
|
|
96
|
-
assert.equal(w.name, 'security-reviewer');
|
|
97
|
-
assert.match(w.launcherCommand, /\{task_file_sh\}/);
|
|
98
|
-
assert.match(w.launcherCommand, /claude/);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
test('buildWorker: codex harness uses orchestrate-codex-worker.sh', () => {
|
|
102
|
-
const w = B.buildWorker({ agent: 'security-reviewer', task: 'foo', harness: 'codex' });
|
|
103
|
-
assert.match(w.launcherCommand, /orchestrate-codex-worker\.sh/);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
test('buildWorker: unknown harness throws helpful error', () => {
|
|
107
|
-
assert.throws(
|
|
108
|
-
() => B.buildWorker({ agent: 'x', task: 'y', harness: 'bogus' }),
|
|
109
|
-
/No launcher template for harness "bogus"/
|
|
110
|
-
);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
test('buildWorker: custom launcherCommand overrides harness', () => {
|
|
114
|
-
const w = B.buildWorker({ agent: 'x', task: 'y', harness: 'claude', launcherCommand: 'my-custom {task_file}' });
|
|
115
|
-
assert.equal(w.launcherCommand, 'my-custom {task_file}');
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
// ── buildSwarmPlan (full plan-config) ────────────────────────────────────────
|
|
119
|
-
|
|
120
|
-
test('buildSwarmPlan: rejects empty task', () => {
|
|
121
|
-
assert.throws(() => B.buildSwarmPlan({ task: '' }), /--task is required/);
|
|
122
|
-
assert.throws(() => B.buildSwarmPlan({}), /--task is required/);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
test('buildSwarmPlan: produces a plan-config the orchestrator can consume', () => {
|
|
126
|
-
const planConfig = B.buildSwarmPlan({
|
|
127
|
-
task: 'audit oauth flow for security',
|
|
128
|
-
count: 4,
|
|
129
|
-
harness: 'echo',
|
|
130
|
-
repoRoot: process.cwd(),
|
|
131
|
-
});
|
|
132
|
-
assert.ok(planConfig.workers.length === 4);
|
|
133
|
-
assert.ok(planConfig.sessionName.startsWith('swarm-'));
|
|
134
|
-
assert.ok(planConfig._meta.pickedAgents.includes('security-reviewer'));
|
|
135
|
-
|
|
136
|
-
// Verify orchestrator accepts it without throwing.
|
|
137
|
-
const plan = O.buildOrchestrationPlan(planConfig);
|
|
138
|
-
assert.equal(plan.workerPlans.length, 4);
|
|
139
|
-
assert.ok(plan.workerPlans.every(w => w.launchCommand.includes('demo')));
|
|
140
|
-
assert.ok(plan.workerPlans.every(w => w.taskFilePath.includes('.orchestration')));
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
test('buildSwarmPlan: explicit agents preserved in worker order', () => {
|
|
144
|
-
const planConfig = B.buildSwarmPlan({
|
|
145
|
-
task: 'ship v2.0',
|
|
146
|
-
agents: ['release-captain', 'security-reviewer', 'e2e-runner'],
|
|
147
|
-
harness: 'echo',
|
|
148
|
-
});
|
|
149
|
-
assert.deepEqual(
|
|
150
|
-
planConfig.workers.map(w => w.name),
|
|
151
|
-
['release-captain', 'security-reviewer', 'e2e-runner']
|
|
152
|
-
);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
test('buildSwarmPlan: passes through orchestrator-only fields', () => {
|
|
156
|
-
const planConfig = B.buildSwarmPlan({
|
|
157
|
-
task: 'test',
|
|
158
|
-
count: 2,
|
|
159
|
-
harness: 'echo',
|
|
160
|
-
sessionName: 'my-custom-session',
|
|
161
|
-
baseRef: 'main',
|
|
162
|
-
replaceExisting: true,
|
|
163
|
-
seedPaths: ['scripts'],
|
|
164
|
-
coordinationRoot: '/tmp/coord',
|
|
165
|
-
worktreeRoot: '/tmp/worktrees',
|
|
166
|
-
});
|
|
167
|
-
assert.equal(planConfig.sessionName, 'my-custom-session');
|
|
168
|
-
assert.equal(planConfig.baseRef, 'main');
|
|
169
|
-
assert.equal(planConfig.replaceExisting, true);
|
|
170
|
-
assert.deepEqual(planConfig.seedPaths, ['scripts']);
|
|
171
|
-
assert.equal(planConfig.coordinationRoot, '/tmp/coord');
|
|
172
|
-
assert.equal(planConfig.worktreeRoot, '/tmp/worktrees');
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
test('buildSwarmPlan + orchestrator: worker tasks contain agent-shaped prompt', () => {
|
|
176
|
-
const planConfig = B.buildSwarmPlan({
|
|
177
|
-
task: 'audit oauth flow',
|
|
178
|
-
count: 2,
|
|
179
|
-
harness: 'echo',
|
|
180
|
-
agents: ['security-reviewer', 'code-reviewer'],
|
|
181
|
-
});
|
|
182
|
-
const plan = O.buildOrchestrationPlan(planConfig);
|
|
183
|
-
for (const w of plan.workerPlans) {
|
|
184
|
-
assert.match(w.task, /Required handoff sections/);
|
|
185
|
-
assert.match(w.task, /audit oauth flow/);
|
|
186
|
-
assert.match(w.task, new RegExp(w.workerName));
|
|
187
|
-
}
|
|
188
|
-
});
|