@soleri/forge 9.15.0 → 9.16.7
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/dist/compose-claude-md.js +5 -2
- package/dist/compose-claude-md.js.map +1 -1
- package/dist/scaffold-filetree.js +8 -1
- package/dist/scaffold-filetree.js.map +1 -1
- package/dist/skills/soleri-agent-dev/SKILL.md +1 -0
- package/dist/skills/soleri-agent-guide/SKILL.md +1 -0
- package/dist/skills/soleri-agent-issues/SKILL.md +1 -0
- package/dist/skills/soleri-agent-mode/SKILL.md +173 -0
- package/dist/skills/soleri-agent-persona/SKILL.md +1 -0
- package/dist/skills/soleri-brain-debrief/SKILL.md +1 -0
- package/dist/skills/soleri-brainstorming/SKILL.md +1 -0
- package/dist/skills/soleri-build-skill/SKILL.md +2 -0
- package/dist/skills/soleri-code-patrol/SKILL.md +1 -0
- package/dist/skills/soleri-context-resume/SKILL.md +1 -0
- package/dist/skills/soleri-curator/SKILL.md +66 -0
- package/dist/skills/soleri-deep-review/SKILL.md +1 -0
- package/dist/skills/soleri-deliver-and-ship/SKILL.md +1 -0
- package/dist/skills/soleri-discovery-phase/SKILL.md +1 -0
- package/dist/skills/soleri-dream/SKILL.md +31 -1
- package/dist/skills/soleri-env-setup/SKILL.md +1 -0
- package/dist/skills/soleri-executing-plans/SKILL.md +1 -0
- package/dist/skills/soleri-finishing-a-development-branch/SKILL.md +1 -0
- package/dist/skills/soleri-fix-and-learn/SKILL.md +1 -0
- package/dist/skills/soleri-health-check/SKILL.md +1 -0
- package/dist/skills/soleri-intake/SKILL.md +100 -0
- package/dist/skills/soleri-knowledge-harvest/SKILL.md +1 -0
- package/dist/skills/soleri-loop/SKILL.md +69 -0
- package/dist/skills/soleri-mcp-doctor/SKILL.md +1 -0
- package/dist/skills/soleri-onboard-me/SKILL.md +1 -0
- package/dist/skills/soleri-orchestrate/SKILL.md +70 -0
- package/dist/skills/soleri-parallel-execute/SKILL.md +1 -0
- package/dist/skills/soleri-research-scout/SKILL.md +1 -0
- package/dist/skills/soleri-retrospective/SKILL.md +1 -0
- package/dist/skills/soleri-second-opinion/SKILL.md +1 -0
- package/dist/skills/soleri-subagent-driven-development/SKILL.md +1 -0
- package/dist/skills/soleri-systematic-debugging/SKILL.md +1 -0
- package/dist/skills/soleri-test-driven-development/SKILL.md +1 -0
- package/dist/skills/soleri-using-git-worktrees/SKILL.md +1 -0
- package/dist/skills/soleri-vault-capture/SKILL.md +6 -5
- package/dist/skills/soleri-vault-curate/SKILL.md +1 -0
- package/dist/skills/soleri-vault-navigator/SKILL.md +1 -0
- package/dist/skills/soleri-vault-smells/SKILL.md +1 -0
- package/dist/skills/soleri-verification-before-completion/SKILL.md +1 -0
- package/dist/skills/soleri-writing-plans/SKILL.md +6 -3
- package/dist/skills/soleri-yolo-mode/SKILL.md +1 -0
- package/dist/templates/claude-md-template.js +2 -29
- package/dist/templates/claude-md-template.js.map +1 -1
- package/dist/templates/package-json.js +2 -0
- package/dist/templates/package-json.js.map +1 -1
- package/dist/templates/setup-script.js +6 -63
- package/dist/templates/setup-script.js.map +1 -1
- package/dist/templates/shared-rules.js +11 -4
- package/dist/templates/shared-rules.js.map +1 -1
- package/dist/templates/skills.d.ts +13 -0
- package/dist/templates/skills.js +55 -3
- package/dist/templates/skills.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/scaffold-filetree.test.ts +1 -1
- package/src/__tests__/scaffolder.test.ts +108 -112
- package/src/compose-claude-md.ts +5 -1
- package/src/scaffold-filetree.ts +8 -1
- package/src/skills/soleri-agent-dev/SKILL.md +1 -0
- package/src/skills/soleri-agent-guide/SKILL.md +1 -0
- package/src/skills/soleri-agent-issues/SKILL.md +1 -0
- package/src/skills/soleri-agent-mode/SKILL.md +173 -0
- package/src/skills/soleri-agent-persona/SKILL.md +1 -0
- package/src/skills/soleri-brain-debrief/SKILL.md +1 -0
- package/src/skills/soleri-brainstorming/SKILL.md +1 -0
- package/src/skills/soleri-build-skill/SKILL.md +2 -0
- package/src/skills/soleri-code-patrol/SKILL.md +1 -0
- package/src/skills/soleri-context-resume/SKILL.md +1 -0
- package/src/skills/soleri-curator/SKILL.md +66 -0
- package/src/skills/soleri-deep-review/SKILL.md +1 -0
- package/src/skills/soleri-deliver-and-ship/SKILL.md +1 -0
- package/src/skills/soleri-discovery-phase/SKILL.md +1 -0
- package/src/skills/soleri-dream/SKILL.md +31 -1
- package/src/skills/soleri-env-setup/SKILL.md +1 -0
- package/src/skills/soleri-executing-plans/SKILL.md +1 -0
- package/src/skills/soleri-finishing-a-development-branch/SKILL.md +1 -0
- package/src/skills/soleri-fix-and-learn/SKILL.md +1 -0
- package/src/skills/soleri-health-check/SKILL.md +1 -0
- package/src/skills/soleri-intake/SKILL.md +100 -0
- package/src/skills/soleri-knowledge-harvest/SKILL.md +1 -0
- package/src/skills/soleri-loop/SKILL.md +69 -0
- package/src/skills/soleri-mcp-doctor/SKILL.md +1 -0
- package/src/skills/soleri-onboard-me/SKILL.md +1 -0
- package/src/skills/soleri-orchestrate/SKILL.md +70 -0
- package/src/skills/soleri-parallel-execute/SKILL.md +1 -0
- package/src/skills/soleri-research-scout/SKILL.md +1 -0
- package/src/skills/soleri-retrospective/SKILL.md +1 -0
- package/src/skills/soleri-second-opinion/SKILL.md +1 -0
- package/src/skills/soleri-subagent-driven-development/SKILL.md +1 -0
- package/src/skills/soleri-systematic-debugging/SKILL.md +1 -0
- package/src/skills/soleri-test-driven-development/SKILL.md +1 -0
- package/src/skills/soleri-using-git-worktrees/SKILL.md +1 -0
- package/src/skills/soleri-vault-capture/SKILL.md +6 -5
- package/src/skills/soleri-vault-curate/SKILL.md +1 -0
- package/src/skills/soleri-vault-navigator/SKILL.md +1 -0
- package/src/skills/soleri-vault-smells/SKILL.md +1 -0
- package/src/skills/soleri-verification-before-completion/SKILL.md +1 -0
- package/src/skills/soleri-writing-plans/SKILL.md +6 -3
- package/src/skills/soleri-yolo-mode/SKILL.md +1 -0
- package/src/templates/claude-md-template.ts +2 -50
- package/src/templates/package-json.ts +2 -0
- package/src/templates/setup-script.ts +6 -63
- package/src/templates/shared-rules.ts +11 -4
- package/src/templates/skills.ts +63 -3
- package/vitest.config.ts +2 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, it, expect,
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
2
2
|
import {
|
|
3
3
|
mkdirSync,
|
|
4
4
|
rmSync,
|
|
@@ -8,49 +8,66 @@ import {
|
|
|
8
8
|
statSync,
|
|
9
9
|
lstatSync,
|
|
10
10
|
} from 'node:fs';
|
|
11
|
-
import { join } from 'node:path';
|
|
11
|
+
import { join, dirname } from 'node:path';
|
|
12
12
|
import { tmpdir } from 'node:os';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
|
|
15
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const SOURCE_SKILLS_DIR = join(__dirname, '..', 'skills');
|
|
13
17
|
import { scaffold, previewScaffold, listAgents } from '../scaffolder.js';
|
|
14
18
|
import type { AgentConfig } from '../types.js';
|
|
15
19
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
mkdirSync(tempDir, { recursive: true });
|
|
38
|
-
testConfig.outputDir = tempDir;
|
|
39
|
-
});
|
|
20
|
+
const baseConfig: AgentConfig = {
|
|
21
|
+
id: 'atlas',
|
|
22
|
+
name: 'Atlas',
|
|
23
|
+
role: 'Data Engineering Advisor',
|
|
24
|
+
description:
|
|
25
|
+
'Atlas provides guidance on data pipelines, ETL patterns, and data quality practices.',
|
|
26
|
+
domains: ['data-pipelines', 'data-quality', 'etl'],
|
|
27
|
+
principles: [
|
|
28
|
+
'Data quality is non-negotiable',
|
|
29
|
+
'Idempotent pipelines always',
|
|
30
|
+
'Schema evolution over breaking changes',
|
|
31
|
+
],
|
|
32
|
+
greeting: 'Atlas here. I help with data engineering patterns and best practices.',
|
|
33
|
+
outputDir: '', // set in beforeAll
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function makeTempDir(suffix: string): string {
|
|
37
|
+
const dir = join(tmpdir(), `forge-test-${suffix}-${Date.now()}`);
|
|
38
|
+
mkdirSync(dir, { recursive: true });
|
|
39
|
+
return dir;
|
|
40
|
+
}
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
describe('Scaffolder', () => {
|
|
43
|
+
// Two scaffolds shared across ALL inner describes — base and telegram
|
|
44
|
+
let baseDir: string;
|
|
45
|
+
let baseResult: ReturnType<typeof scaffold>;
|
|
46
|
+
let telegramDir: string;
|
|
47
|
+
let telegramResult: ReturnType<typeof scaffold>;
|
|
48
|
+
|
|
49
|
+
beforeAll(() => {
|
|
50
|
+
baseDir = makeTempDir('base');
|
|
51
|
+
baseResult = scaffold({ ...baseConfig, outputDir: baseDir });
|
|
52
|
+
|
|
53
|
+
telegramDir = makeTempDir('telegram');
|
|
54
|
+
telegramResult = scaffold({ ...baseConfig, telegram: true, outputDir: telegramDir });
|
|
55
|
+
}, 60_000);
|
|
56
|
+
|
|
57
|
+
afterAll(() => {
|
|
58
|
+
rmSync(baseDir, { recursive: true, force: true });
|
|
59
|
+
rmSync(telegramDir, { recursive: true, force: true });
|
|
43
60
|
});
|
|
44
61
|
|
|
45
62
|
describe('previewScaffold', () => {
|
|
46
63
|
it('should return preview without creating files', () => {
|
|
47
|
-
const preview = previewScaffold(
|
|
64
|
+
const preview = previewScaffold({ ...baseConfig, outputDir: baseDir });
|
|
48
65
|
|
|
49
|
-
expect(preview.agentDir).toBe(join(
|
|
66
|
+
expect(preview.agentDir).toBe(join(baseDir, 'atlas'));
|
|
50
67
|
expect(preview.persona.name).toBe('Atlas');
|
|
51
68
|
expect(preview.persona.role).toBe('Data Engineering Advisor');
|
|
52
69
|
expect(preview.domains).toEqual(['data-pipelines', 'data-quality', 'etl']);
|
|
53
|
-
expect(preview.files.length).
|
|
70
|
+
expect(preview.files.length).toBe(19);
|
|
54
71
|
|
|
55
72
|
const paths = preview.files.map((f) => f.path);
|
|
56
73
|
expect(paths).toContain('README.md');
|
|
@@ -64,7 +81,7 @@ describe('Scaffolder', () => {
|
|
|
64
81
|
expect(paths).not.toContain('src/facades/data-pipelines.facade.ts');
|
|
65
82
|
|
|
66
83
|
// Should have domain facades + core facade in preview (3 domains + semantic + agent core)
|
|
67
|
-
expect(preview.facades.length).
|
|
84
|
+
expect(preview.facades.length).toBe(13);
|
|
68
85
|
expect(preview.facades[0].name).toBe('atlas_data_pipelines');
|
|
69
86
|
|
|
70
87
|
// Agent-specific facade has 5 ops
|
|
@@ -76,24 +93,29 @@ describe('Scaffolder', () => {
|
|
|
76
93
|
const vaultFacade = preview.facades.find((f) => f.name === 'atlas_vault')!;
|
|
77
94
|
expect(vaultFacade).toBeDefined();
|
|
78
95
|
|
|
79
|
-
// Should NOT create any files
|
|
80
|
-
expect(existsSync(join(
|
|
96
|
+
// Should NOT create any files beyond what beforeAll scaffolded
|
|
97
|
+
expect(existsSync(join(baseDir, 'atlas'))).toBe(true); // beforeAll created this
|
|
81
98
|
});
|
|
82
99
|
});
|
|
83
100
|
|
|
84
101
|
describe('scaffold', () => {
|
|
85
102
|
it('should create a complete agent project', () => {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
expect(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
103
|
+
expect(baseResult.success).toBe(true);
|
|
104
|
+
expect(baseResult.agentDir).toBe(join(baseDir, 'atlas'));
|
|
105
|
+
expect(baseResult.domains).toEqual(['data-pipelines', 'data-quality', 'etl']);
|
|
106
|
+
|
|
107
|
+
// Split: base scaffold files (stable) + one SKILL.md per source skill (dynamic)
|
|
108
|
+
const skillFiles = baseResult.filesCreated.filter((f) => f.startsWith('skills/'));
|
|
109
|
+
const baseFiles = baseResult.filesCreated.filter((f) => !f.startsWith('skills/'));
|
|
110
|
+
const sourceSkillCount = readdirSync(SOURCE_SKILLS_DIR, { withFileTypes: true }).filter((e) =>
|
|
111
|
+
e.isDirectory(),
|
|
112
|
+
).length;
|
|
113
|
+
expect(skillFiles.length).toBe(sourceSkillCount);
|
|
114
|
+
expect(baseFiles.length).toBeGreaterThan(0);
|
|
92
115
|
});
|
|
93
116
|
|
|
94
117
|
it('should create expected directories (no facades/ or llm/ dirs)', () => {
|
|
95
|
-
|
|
96
|
-
const agentDir = join(tempDir, 'atlas');
|
|
118
|
+
const agentDir = join(baseDir, 'atlas');
|
|
97
119
|
|
|
98
120
|
expect(existsSync(join(agentDir, 'src', 'intelligence', 'data'))).toBe(true);
|
|
99
121
|
expect(existsSync(join(agentDir, 'src', 'identity'))).toBe(true);
|
|
@@ -104,8 +126,7 @@ describe('Scaffolder', () => {
|
|
|
104
126
|
});
|
|
105
127
|
|
|
106
128
|
it('should create valid package.json with @soleri/core ^2.0.0', () => {
|
|
107
|
-
|
|
108
|
-
const pkg = JSON.parse(readFileSync(join(tempDir, 'atlas', 'package.json'), 'utf-8'));
|
|
129
|
+
const pkg = JSON.parse(readFileSync(join(baseDir, 'atlas', 'package.json'), 'utf-8'));
|
|
109
130
|
|
|
110
131
|
expect(pkg.name).toBe('atlas');
|
|
111
132
|
expect(pkg.type).toBe('module');
|
|
@@ -118,9 +139,8 @@ describe('Scaffolder', () => {
|
|
|
118
139
|
});
|
|
119
140
|
|
|
120
141
|
it('should create persona with correct config', () => {
|
|
121
|
-
scaffold(testConfig);
|
|
122
142
|
const persona = readFileSync(
|
|
123
|
-
join(
|
|
143
|
+
join(baseDir, 'atlas', 'src', 'identity', 'persona.ts'),
|
|
124
144
|
'utf-8',
|
|
125
145
|
);
|
|
126
146
|
|
|
@@ -130,8 +150,7 @@ describe('Scaffolder', () => {
|
|
|
130
150
|
});
|
|
131
151
|
|
|
132
152
|
it('should create seeded intelligence data files', () => {
|
|
133
|
-
|
|
134
|
-
const dataDir = join(tempDir, 'atlas', 'src', 'intelligence', 'data');
|
|
153
|
+
const dataDir = join(baseDir, 'atlas', 'src', 'intelligence', 'data');
|
|
135
154
|
const files = readdirSync(dataDir);
|
|
136
155
|
|
|
137
156
|
expect(files).toContain('data-pipelines.json');
|
|
@@ -146,8 +165,7 @@ describe('Scaffolder', () => {
|
|
|
146
165
|
});
|
|
147
166
|
|
|
148
167
|
it('should create entry point using runtime factories from @soleri/core', () => {
|
|
149
|
-
|
|
150
|
-
const entry = readFileSync(join(tempDir, 'atlas', 'src', 'index.ts'), 'utf-8');
|
|
168
|
+
const entry = readFileSync(join(baseDir, 'atlas', 'src', 'index.ts'), 'utf-8');
|
|
151
169
|
|
|
152
170
|
// v5.0 runtime factory pattern
|
|
153
171
|
expect(entry).toContain('createAgentRuntime');
|
|
@@ -167,16 +185,14 @@ describe('Scaffolder', () => {
|
|
|
167
185
|
});
|
|
168
186
|
|
|
169
187
|
it('should create .mcp.json for client config', () => {
|
|
170
|
-
|
|
171
|
-
const mcp = JSON.parse(readFileSync(join(tempDir, 'atlas', '.mcp.json'), 'utf-8'));
|
|
188
|
+
const mcp = JSON.parse(readFileSync(join(baseDir, 'atlas', '.mcp.json'), 'utf-8'));
|
|
172
189
|
|
|
173
190
|
expect(mcp.mcpServers.atlas).toBeDefined();
|
|
174
191
|
expect(mcp.mcpServers.atlas.command).toBe('node');
|
|
175
192
|
});
|
|
176
193
|
|
|
177
194
|
it('should create activation files', () => {
|
|
178
|
-
|
|
179
|
-
const activationDir = join(tempDir, 'atlas', 'src', 'activation');
|
|
195
|
+
const activationDir = join(baseDir, 'atlas', 'src', 'activation');
|
|
180
196
|
const files = readdirSync(activationDir);
|
|
181
197
|
|
|
182
198
|
expect(files).toContain('claude-md-content.ts');
|
|
@@ -185,8 +201,7 @@ describe('Scaffolder', () => {
|
|
|
185
201
|
});
|
|
186
202
|
|
|
187
203
|
it('should create activation files with correct content', () => {
|
|
188
|
-
|
|
189
|
-
const activationDir = join(tempDir, 'atlas', 'src', 'activation');
|
|
204
|
+
const activationDir = join(baseDir, 'atlas', 'src', 'activation');
|
|
190
205
|
|
|
191
206
|
const claudeMd = readFileSync(join(activationDir, 'claude-md-content.ts'), 'utf-8');
|
|
192
207
|
expect(claudeMd).toContain('atlas:mode');
|
|
@@ -203,8 +218,7 @@ describe('Scaffolder', () => {
|
|
|
203
218
|
});
|
|
204
219
|
|
|
205
220
|
it('should create README.md with agent-specific content', () => {
|
|
206
|
-
|
|
207
|
-
const readme = readFileSync(join(tempDir, 'atlas', 'README.md'), 'utf-8');
|
|
221
|
+
const readme = readFileSync(join(baseDir, 'atlas', 'README.md'), 'utf-8');
|
|
208
222
|
|
|
209
223
|
expect(readme).toContain('# Atlas');
|
|
210
224
|
expect(readme).toContain('Data Engineering Advisor');
|
|
@@ -213,8 +227,7 @@ describe('Scaffolder', () => {
|
|
|
213
227
|
});
|
|
214
228
|
|
|
215
229
|
it('should create executable setup.sh', () => {
|
|
216
|
-
|
|
217
|
-
const setupPath = join(tempDir, 'atlas', 'scripts', 'setup.sh');
|
|
230
|
+
const setupPath = join(baseDir, 'atlas', 'scripts', 'setup.sh');
|
|
218
231
|
const setup = readFileSync(setupPath, 'utf-8');
|
|
219
232
|
|
|
220
233
|
expect(setup).toContain('AGENT_NAME="atlas"');
|
|
@@ -228,9 +241,8 @@ describe('Scaffolder', () => {
|
|
|
228
241
|
});
|
|
229
242
|
|
|
230
243
|
it('should generate facade tests using runtime factories', () => {
|
|
231
|
-
scaffold(testConfig);
|
|
232
244
|
const facadesTest = readFileSync(
|
|
233
|
-
join(
|
|
245
|
+
join(baseDir, 'atlas', 'src', '__tests__', 'facades.test.ts'),
|
|
234
246
|
'utf-8',
|
|
235
247
|
);
|
|
236
248
|
|
|
@@ -255,18 +267,17 @@ describe('Scaffolder', () => {
|
|
|
255
267
|
});
|
|
256
268
|
|
|
257
269
|
it('should fail if directory already exists', () => {
|
|
258
|
-
|
|
259
|
-
const
|
|
270
|
+
// beforeAll already scaffolded to baseDir — attempt on same dir should fail
|
|
271
|
+
const duplicate = scaffold({ ...baseConfig, outputDir: baseDir });
|
|
260
272
|
|
|
261
|
-
expect(
|
|
262
|
-
expect(
|
|
273
|
+
expect(duplicate.success).toBe(false);
|
|
274
|
+
expect(duplicate.summary).toContain('already exists');
|
|
263
275
|
});
|
|
264
276
|
});
|
|
265
277
|
|
|
266
278
|
describe('skills', () => {
|
|
267
|
-
it('should create skills directory with
|
|
268
|
-
|
|
269
|
-
const skillsDir = join(tempDir, 'atlas', 'skills');
|
|
279
|
+
it('should create skills directory with SKILL.md files', () => {
|
|
280
|
+
const skillsDir = join(baseDir, 'atlas', 'skills');
|
|
270
281
|
|
|
271
282
|
expect(existsSync(skillsDir)).toBe(true);
|
|
272
283
|
|
|
@@ -274,7 +285,10 @@ describe('Scaffolder', () => {
|
|
|
274
285
|
.filter((e) => e.isDirectory())
|
|
275
286
|
.map((e) => e.name);
|
|
276
287
|
|
|
277
|
-
|
|
288
|
+
const expectedCount = readdirSync(SOURCE_SKILLS_DIR, { withFileTypes: true }).filter((e) =>
|
|
289
|
+
e.isDirectory(),
|
|
290
|
+
).length;
|
|
291
|
+
expect(skillDirs.length).toBe(expectedCount);
|
|
278
292
|
|
|
279
293
|
// Verify each skill dir has a SKILL.md
|
|
280
294
|
for (const dir of skillDirs) {
|
|
@@ -283,8 +297,7 @@ describe('Scaffolder', () => {
|
|
|
283
297
|
});
|
|
284
298
|
|
|
285
299
|
it('should include core expected skill names', () => {
|
|
286
|
-
|
|
287
|
-
const skillsDir = join(tempDir, 'atlas', 'skills');
|
|
300
|
+
const skillsDir = join(baseDir, 'atlas', 'skills');
|
|
288
301
|
const skillDirs = readdirSync(skillsDir).sort();
|
|
289
302
|
|
|
290
303
|
// Check essential skills exist (not an exhaustive list — skills are added over time)
|
|
@@ -301,8 +314,7 @@ describe('Scaffolder', () => {
|
|
|
301
314
|
});
|
|
302
315
|
|
|
303
316
|
it('should have YAML frontmatter in all skills', () => {
|
|
304
|
-
|
|
305
|
-
const skillsDir = join(tempDir, 'atlas', 'skills');
|
|
317
|
+
const skillsDir = join(baseDir, 'atlas', 'skills');
|
|
306
318
|
const skillDirs = readdirSync(skillsDir);
|
|
307
319
|
|
|
308
320
|
for (const dir of skillDirs) {
|
|
@@ -313,8 +325,7 @@ describe('Scaffolder', () => {
|
|
|
313
325
|
});
|
|
314
326
|
|
|
315
327
|
it('should substitute YOUR_AGENT_core with agent ID in all skills', () => {
|
|
316
|
-
|
|
317
|
-
const skillsDir = join(tempDir, 'atlas', 'skills');
|
|
328
|
+
const skillsDir = join(baseDir, 'atlas', 'skills');
|
|
318
329
|
const allSkills = readdirSync(skillsDir);
|
|
319
330
|
|
|
320
331
|
for (const name of allSkills) {
|
|
@@ -328,8 +339,7 @@ describe('Scaffolder', () => {
|
|
|
328
339
|
});
|
|
329
340
|
|
|
330
341
|
it('should have valid content in superpowers-adapted skills', () => {
|
|
331
|
-
|
|
332
|
-
const skillsDir = join(tempDir, 'atlas', 'skills');
|
|
342
|
+
const skillsDir = join(baseDir, 'atlas', 'skills');
|
|
333
343
|
const superpowersSkills = ['soleri-brainstorming', 'soleri-executing-plans'];
|
|
334
344
|
|
|
335
345
|
for (const name of superpowersSkills) {
|
|
@@ -342,8 +352,7 @@ describe('Scaffolder', () => {
|
|
|
342
352
|
});
|
|
343
353
|
|
|
344
354
|
it('should have no YOUR_AGENT_core placeholder remaining in any skill', () => {
|
|
345
|
-
|
|
346
|
-
const skillsDir = join(tempDir, 'atlas', 'skills');
|
|
355
|
+
const skillsDir = join(baseDir, 'atlas', 'skills');
|
|
347
356
|
const allSkills = readdirSync(skillsDir);
|
|
348
357
|
|
|
349
358
|
for (const name of allSkills) {
|
|
@@ -353,19 +362,17 @@ describe('Scaffolder', () => {
|
|
|
353
362
|
});
|
|
354
363
|
|
|
355
364
|
it('should include skills in preview', () => {
|
|
356
|
-
const preview = previewScaffold(
|
|
365
|
+
const preview = previewScaffold({ ...baseConfig, outputDir: baseDir });
|
|
357
366
|
const paths = preview.files.map((f) => f.path);
|
|
358
367
|
expect(paths).toContain('skills/');
|
|
359
368
|
});
|
|
360
369
|
|
|
361
370
|
it('should mention skills in scaffold summary', () => {
|
|
362
|
-
|
|
363
|
-
expect(result.summary).toContain('built-in skills');
|
|
371
|
+
expect(baseResult.summary).toContain('built-in skills');
|
|
364
372
|
});
|
|
365
373
|
|
|
366
374
|
it('should sync generated skills into .claude/skills/', () => {
|
|
367
|
-
|
|
368
|
-
const agentDir = join(tempDir, 'atlas');
|
|
375
|
+
const agentDir = join(baseDir, 'atlas');
|
|
369
376
|
const claudeSkillsDir = join(agentDir, '.claude', 'skills');
|
|
370
377
|
|
|
371
378
|
expect(existsSync(claudeSkillsDir)).toBe(true);
|
|
@@ -394,9 +401,7 @@ describe('Scaffolder', () => {
|
|
|
394
401
|
|
|
395
402
|
describe('listAgents', () => {
|
|
396
403
|
it('should list scaffolded agents', () => {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
const agents = listAgents(tempDir);
|
|
404
|
+
const agents = listAgents(baseDir);
|
|
400
405
|
expect(agents).toHaveLength(1);
|
|
401
406
|
expect(agents[0].id).toBe('atlas');
|
|
402
407
|
expect(agents[0].domains).toEqual(['data-pipelines', 'data-quality', 'etl']);
|
|
@@ -409,14 +414,8 @@ describe('Scaffolder', () => {
|
|
|
409
414
|
});
|
|
410
415
|
|
|
411
416
|
describe('telegram scaffolding', () => {
|
|
412
|
-
const telegramConfig: AgentConfig = {
|
|
413
|
-
...testConfig,
|
|
414
|
-
telegram: true,
|
|
415
|
-
};
|
|
416
|
-
|
|
417
417
|
it('should include telegram files in preview', () => {
|
|
418
|
-
|
|
419
|
-
const preview = previewScaffold(telegramConfig);
|
|
418
|
+
const preview = previewScaffold({ ...baseConfig, telegram: true, outputDir: telegramDir });
|
|
420
419
|
const paths = preview.files.map((f) => f.path);
|
|
421
420
|
expect(paths).toContain('src/telegram-bot.ts');
|
|
422
421
|
expect(paths).toContain('src/telegram-config.ts');
|
|
@@ -425,27 +424,28 @@ describe('Scaffolder', () => {
|
|
|
425
424
|
});
|
|
426
425
|
|
|
427
426
|
it('should not include telegram files without flag', () => {
|
|
428
|
-
const preview = previewScaffold(
|
|
427
|
+
const preview = previewScaffold({ ...baseConfig, outputDir: baseDir });
|
|
429
428
|
const paths = preview.files.map((f) => f.path);
|
|
430
429
|
expect(paths).not.toContain('src/telegram-bot.ts');
|
|
431
430
|
});
|
|
432
431
|
|
|
433
432
|
it('should generate telegram source files', () => {
|
|
434
|
-
telegramConfig.outputDir = tempDir;
|
|
435
|
-
const result = scaffold(telegramConfig);
|
|
436
433
|
// Build may fail (grammy not installed) but files should be created
|
|
437
|
-
expect(
|
|
438
|
-
expect(
|
|
439
|
-
expect(
|
|
440
|
-
expect(
|
|
434
|
+
expect(telegramResult.filesCreated).toContain('src/telegram-bot.ts');
|
|
435
|
+
expect(telegramResult.filesCreated).toContain('src/telegram-config.ts');
|
|
436
|
+
expect(telegramResult.filesCreated).toContain('src/telegram-agent.ts');
|
|
437
|
+
expect(telegramResult.filesCreated).toContain('src/telegram-supervisor.ts');
|
|
441
438
|
|
|
442
439
|
// Verify file contents reference the agent
|
|
443
|
-
const botContent = readFileSync(
|
|
440
|
+
const botContent = readFileSync(
|
|
441
|
+
join(telegramDir, 'atlas', 'src', 'telegram-bot.ts'),
|
|
442
|
+
'utf-8',
|
|
443
|
+
);
|
|
444
444
|
expect(botContent).toContain('Atlas');
|
|
445
445
|
expect(botContent).toContain('grammy');
|
|
446
446
|
|
|
447
447
|
const configContent = readFileSync(
|
|
448
|
-
join(
|
|
448
|
+
join(telegramDir, 'atlas', 'src', 'telegram-config.ts'),
|
|
449
449
|
'utf-8',
|
|
450
450
|
);
|
|
451
451
|
expect(configContent).toContain('.atlas');
|
|
@@ -453,20 +453,16 @@ describe('Scaffolder', () => {
|
|
|
453
453
|
});
|
|
454
454
|
|
|
455
455
|
it('should include grammy dependency in package.json', () => {
|
|
456
|
-
|
|
457
|
-
scaffold(telegramConfig);
|
|
458
|
-
const pkg = JSON.parse(readFileSync(join(tempDir, 'atlas', 'package.json'), 'utf-8'));
|
|
456
|
+
const pkg = JSON.parse(readFileSync(join(telegramDir, 'atlas', 'package.json'), 'utf-8'));
|
|
459
457
|
expect(pkg.dependencies.grammy).toBeDefined();
|
|
460
458
|
expect(pkg.scripts['telegram:start']).toBeDefined();
|
|
461
459
|
expect(pkg.scripts['telegram:dev']).toBeDefined();
|
|
462
460
|
});
|
|
463
461
|
|
|
464
462
|
it('should not include grammy without telegram flag', () => {
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
expect(pkg.dependencies.grammy).toBeUndefined();
|
|
469
|
-
}
|
|
463
|
+
// baseResult was scaffolded without telegram flag — use it for this assertion
|
|
464
|
+
const pkg = JSON.parse(readFileSync(join(baseDir, 'atlas', 'package.json'), 'utf-8'));
|
|
465
|
+
expect(pkg.dependencies.grammy).toBeUndefined();
|
|
470
466
|
});
|
|
471
467
|
});
|
|
472
468
|
});
|
package/src/compose-claude-md.ts
CHANGED
|
@@ -407,7 +407,11 @@ function composeWorkflowIndex(workflowsDir: string): string | null {
|
|
|
407
407
|
const content = readFileSync(promptPath, 'utf-8');
|
|
408
408
|
// Extract first non-heading, non-empty line as description
|
|
409
409
|
const descLine = content.split('\n').find((line) => line.trim() && !line.startsWith('#'));
|
|
410
|
-
if (descLine)
|
|
410
|
+
if (descLine) {
|
|
411
|
+
const trimmed = descLine.trim();
|
|
412
|
+
description =
|
|
413
|
+
trimmed.length > 120 ? trimmed.slice(0, 117).replace(/\s+\S*$/, '') + '...' : trimmed;
|
|
414
|
+
}
|
|
411
415
|
}
|
|
412
416
|
|
|
413
417
|
lines.push(`| \`${dir.name}\` | ${description} |`);
|
package/src/scaffold-filetree.ts
CHANGED
|
@@ -581,8 +581,15 @@ See [Hook Packs documentation](https://soleri.dev/docs/guides/pack-authoring/) f
|
|
|
581
581
|
};
|
|
582
582
|
writeFile(agentDir, '.mcp.json', JSON.stringify(mcpJson, null, 2) + '\n', filesCreated);
|
|
583
583
|
|
|
584
|
-
// ─── 3a. Write settings.local.json (Claude Code hooks) ─────
|
|
584
|
+
// ─── 3a. Write settings.local.json (Claude Code hooks + pre-approved permissions) ─────
|
|
585
585
|
const settingsLocal = {
|
|
586
|
+
permissions: {
|
|
587
|
+
allow: [
|
|
588
|
+
// Pre-approve all facades for this agent so users skip approval prompts
|
|
589
|
+
// on routine ops (session_start, vault search, orchestrate_plan, etc.)
|
|
590
|
+
`mcp__${config.id}__*`,
|
|
591
|
+
],
|
|
592
|
+
},
|
|
586
593
|
hooks: {
|
|
587
594
|
Stop: [
|
|
588
595
|
{
|