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.
Files changed (91) hide show
  1. package/CLAUDE.md +1 -1
  2. package/VERSION +1 -1
  3. package/bin/kodelyth-ecc.js +54 -5
  4. package/install.ps1 +30 -13
  5. package/install.sh +11 -4
  6. package/package.json +1 -1
  7. package/rules/common/agent-intent-routing.md +96 -195
  8. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -47
  9. package/.github/ISSUE_TEMPLATE/config.yml +0 -8
  10. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -41
  11. package/.github/ISSUE_TEMPLATE/good_first_issue.md +0 -36
  12. package/.github/ISSUE_TEMPLATE/new_agent.md +0 -52
  13. package/.github/PULL_REQUEST_TEMPLATE.md +0 -53
  14. package/.github/workflows/ci.yml +0 -45
  15. package/.github/workflows/publish.yml +0 -60
  16. package/brand/concepts.svg +0 -109
  17. package/brand/convert.js +0 -161
  18. package/brand/favicon.svg +0 -7
  19. package/brand/fb-cover.svg +0 -34
  20. package/brand/fb-profile.svg +0 -21
  21. package/brand/kodelyth-dark.svg +0 -24
  22. package/brand/kodelyth-light.svg +0 -24
  23. package/brand/kodelyth-mark.svg +0 -20
  24. package/brand/package.json +0 -12
  25. package/docs/dashboard.md +0 -211
  26. package/docs/evolve.md +0 -303
  27. package/docs/mcp-clients.md +0 -167
  28. package/docs/mcp.md +0 -178
  29. package/docs/replay.md +0 -244
  30. package/docs/supply-chain.md +0 -207
  31. package/docs/swarm.md +0 -243
  32. package/skills/skill-comply/tests/test_grader.py +0 -137
  33. package/skills/skill-comply/tests/test_parser.py +0 -90
  34. package/social/card-agents.svg +0 -104
  35. package/social/card-install.svg +0 -58
  36. package/social/card-main.svg +0 -54
  37. package/social/facebook-v150.svg +0 -110
  38. package/social/fb-ad-main.svg +0 -128
  39. package/social/fb-post-features.svg +0 -118
  40. package/social/fb-post-launch.svg +0 -144
  41. package/social/fb-post-platforms.svg +0 -135
  42. package/social/github-social-preview.svg +0 -151
  43. package/social/hype-compound-learning.svg +0 -129
  44. package/social/hype-devil-mode.svg +0 -240
  45. package/social/hype-mcp-server.svg +0 -148
  46. package/social/hype-parallel-agents.svg +0 -162
  47. package/social/hype-stats-hero.svg +0 -138
  48. package/social/og-image.svg +0 -151
  49. package/social/readme-agents.svg +0 -138
  50. package/social/readme-hero.svg +0 -102
  51. package/social/section-agents.svg +0 -69
  52. package/social/section-author.svg +0 -66
  53. package/social/section-dashboard.svg +0 -71
  54. package/social/section-devil.svg +0 -66
  55. package/social/section-hooks.svg +0 -63
  56. package/social/section-install.svg +0 -54
  57. package/social/section-learning.svg +0 -64
  58. package/social/section-mcp.svg +0 -58
  59. package/social/section-memory.svg +0 -69
  60. package/social/section-parallel.svg +0 -84
  61. package/social/section-routing.svg +0 -63
  62. package/social/twitter-threads.md +0 -621
  63. package/social/x-card-agents-grid.svg +0 -120
  64. package/social/x-card-free.svg +0 -71
  65. package/social/x-card-hook.svg +0 -94
  66. package/tests/dashboard/data.test.js +0 -391
  67. package/tests/dashboard/server.test.js +0 -257
  68. package/tests/evolve/analyze.test.js +0 -169
  69. package/tests/evolve/proposals.test.js +0 -173
  70. package/tests/evolve/stats.test.js +0 -167
  71. package/tests/hooks/branch-name-check.test.js +0 -184
  72. package/tests/hooks/smart-suggest.test.js +0 -149
  73. package/tests/hooks/test-reminder.test.js +0 -181
  74. package/tests/mcp/catalog.test.js +0 -98
  75. package/tests/mcp/client.test.js +0 -113
  76. package/tests/mcp/resources-prompts.test.js +0 -70
  77. package/tests/mcp/tools.test.js +0 -159
  78. package/tests/memory/auto-recall.test.js +0 -132
  79. package/tests/memory/instincts.test.js +0 -258
  80. package/tests/memory/store.test.js +0 -203
  81. package/tests/replay/bundle.test.js +0 -181
  82. package/tests/replay/replay.test.js +0 -131
  83. package/tests/router/classify.test.js +0 -164
  84. package/tests/run-all.js +0 -55
  85. package/tests/safety/patterns.test.js +0 -94
  86. package/tests/safety/prompt-injection-guard.test.js +0 -111
  87. package/tests/safety/token-budget.test.js +0 -119
  88. package/tests/supply-chain/manifest.test.js +0 -147
  89. package/tests/supply-chain/sbom.test.js +0 -170
  90. package/tests/supply-chain/verify.test.js +0 -146
  91. 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
- });