@soleri/cli 9.2.0 → 9.3.1
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/.github/workflows/ci.yml +116 -0
- package/dist/commands/hooks.js +36 -7
- package/dist/commands/hooks.js.map +1 -1
- package/dist/commands/install.js.map +1 -1
- package/dist/hook-packs/installer.js +7 -2
- package/dist/hook-packs/installer.js.map +1 -1
- package/dist/hook-packs/installer.ts +98 -26
- package/dist/hook-packs/registry.js +12 -4
- package/dist/hook-packs/registry.js.map +1 -1
- package/dist/hook-packs/registry.ts +21 -7
- package/package.json +1 -1
- package/src/__tests__/hook-packs.test.ts +23 -3
- package/src/__tests__/wizard-e2e.mjs +148 -58
- package/src/commands/hooks.ts +186 -82
- package/src/commands/install.ts +7 -4
- package/src/hook-packs/installer.ts +98 -26
- package/src/hook-packs/registry.ts +21 -7
- package/vitest.config.ts +2 -0
|
@@ -7,8 +7,12 @@ import { fileURLToPath } from 'node:url';
|
|
|
7
7
|
import { homedir } from 'node:os';
|
|
8
8
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
9
|
const __dirname = dirname(__filename);
|
|
10
|
-
function getBuiltinRoot() {
|
|
11
|
-
|
|
10
|
+
function getBuiltinRoot() {
|
|
11
|
+
return __dirname;
|
|
12
|
+
}
|
|
13
|
+
function getLocalRoot() {
|
|
14
|
+
return join(process.cwd(), '.soleri', 'hook-packs');
|
|
15
|
+
}
|
|
12
16
|
function scanPacksDir(root, source) {
|
|
13
17
|
if (!existsSync(root))
|
|
14
18
|
return [];
|
|
@@ -25,7 +29,9 @@ function scanPacksDir(root, source) {
|
|
|
25
29
|
manifest.source = source;
|
|
26
30
|
packs.push(manifest);
|
|
27
31
|
}
|
|
28
|
-
catch {
|
|
32
|
+
catch {
|
|
33
|
+
/* Skip malformed manifests */
|
|
34
|
+
}
|
|
29
35
|
}
|
|
30
36
|
return packs;
|
|
31
37
|
}
|
|
@@ -48,7 +54,9 @@ export function getPack(name) {
|
|
|
48
54
|
manifest.source = 'local';
|
|
49
55
|
return { manifest, dir: localDir };
|
|
50
56
|
}
|
|
51
|
-
catch {
|
|
57
|
+
catch {
|
|
58
|
+
/* Fall through */
|
|
59
|
+
}
|
|
52
60
|
}
|
|
53
61
|
const builtinDir = join(getBuiltinRoot(), name);
|
|
54
62
|
const builtinManifest = join(builtinDir, 'manifest.json');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/hook-packs/registry.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AA4BlC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAEtC,SAAS,cAAc,
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/hook-packs/registry.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AA4BlC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAEtC,SAAS,cAAc;IACrB,OAAO,SAAS,CAAC;AACnB,CAAC;AACD,SAAS,YAAY;IACnB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC;AACtD,CAAC;AAED,SAAS,YAAY,CAAC,IAAY,EAAE,MAA4B;IAC9D,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IACjC,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3D,MAAM,KAAK,GAAuB,EAAE,CAAC;IACrC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE;YAAE,SAAS;QACnC,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC;QAC7D,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC;YAAE,SAAS;QACxC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAqB,CAAC;YACrF,QAAQ,CAAC,MAAM,GAAG,MAAM,CAAC;YACzB,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACvB,CAAC;QAAC,MAAM,CAAC;YACP,8BAA8B;QAChC,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,MAAM,OAAO,GAAG,YAAY,CAAC,cAAc,EAAE,EAAE,UAAU,CAAC,CAAC;IAC3D,MAAM,KAAK,GAAG,YAAY,CAAC,YAAY,EAAE,EAAE,OAAO,CAAC,CAAC;IACpD,MAAM,MAAM,GAAG,IAAI,GAAG,EAA4B,CAAC;IACnD,KAAK,MAAM,IAAI,IAAI,OAAO;QAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACxD,KAAK,MAAM,IAAI,IAAI,KAAK;QAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACtD,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,EAAE,EAAE,IAAI,CAAC,CAAC;IAC5C,MAAM,aAAa,GAAG,IAAI,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC;IACtD,IAAI,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QAC9B,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,aAAa,EAAE,OAAO,CAAC,CAAqB,CAAC;YACtF,QAAQ,CAAC,MAAM,GAAG,OAAO,CAAC;YAC1B,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC;QACrC,CAAC;QAAC,MAAM,CAAC;YACP,kBAAkB;QACpB,CAAC;IACH,CAAC;IACD,MAAM,UAAU,GAAG,IAAI,CAAC,cAAc,EAAE,EAAE,IAAI,CAAC,CAAC;IAChD,MAAM,eAAe,GAAG,IAAI,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;IAC1D,IAAI,CAAC,UAAU,CAAC,eAAe,CAAC;QAAE,OAAO,IAAI,CAAC;IAC9C,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,eAAe,EAAE,OAAO,CAAC,CAAqB,CAAC;QACxF,QAAQ,CAAC,MAAM,GAAG,UAAU,CAAC;QAC7B,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;IAC7C,MAAM,KAAK,GAAG,SAAS,EAAE,CAAC;IAC1B,MAAM,SAAS,GAAa,EAAE,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5B,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC5C,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,EAAE,CAC/C,UAAU,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAC3D,CAAC;gBACF,IAAI,UAAU,EAAE,CAAC;oBACf,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC5B,CAAC;YACH,CAAC;YACD,SAAS;QACX,CAAC;QACD,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAC3C,UAAU,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,IAAI,WAAW,CAAC,CAAC,CACxD,CAAC;QACF,IAAI,UAAU,EAAE,CAAC;YACf,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
|
@@ -35,8 +35,12 @@ export interface HookPackManifest {
|
|
|
35
35
|
const __filename = fileURLToPath(import.meta.url);
|
|
36
36
|
const __dirname = dirname(__filename);
|
|
37
37
|
|
|
38
|
-
function getBuiltinRoot(): string {
|
|
39
|
-
|
|
38
|
+
function getBuiltinRoot(): string {
|
|
39
|
+
return __dirname;
|
|
40
|
+
}
|
|
41
|
+
function getLocalRoot(): string {
|
|
42
|
+
return join(process.cwd(), '.soleri', 'hook-packs');
|
|
43
|
+
}
|
|
40
44
|
|
|
41
45
|
function scanPacksDir(root: string, source: 'built-in' | 'local'): HookPackManifest[] {
|
|
42
46
|
if (!existsSync(root)) return [];
|
|
@@ -50,7 +54,9 @@ function scanPacksDir(root: string, source: 'built-in' | 'local'): HookPackManif
|
|
|
50
54
|
const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8')) as HookPackManifest;
|
|
51
55
|
manifest.source = source;
|
|
52
56
|
packs.push(manifest);
|
|
53
|
-
} catch {
|
|
57
|
+
} catch {
|
|
58
|
+
/* Skip malformed manifests */
|
|
59
|
+
}
|
|
54
60
|
}
|
|
55
61
|
return packs;
|
|
56
62
|
}
|
|
@@ -72,7 +78,9 @@ export function getPack(name: string): { manifest: HookPackManifest; dir: string
|
|
|
72
78
|
const manifest = JSON.parse(readFileSync(localManifest, 'utf-8')) as HookPackManifest;
|
|
73
79
|
manifest.source = 'local';
|
|
74
80
|
return { manifest, dir: localDir };
|
|
75
|
-
} catch {
|
|
81
|
+
} catch {
|
|
82
|
+
/* Fall through */
|
|
83
|
+
}
|
|
76
84
|
}
|
|
77
85
|
const builtinDir = join(getBuiltinRoot(), name);
|
|
78
86
|
const builtinManifest = join(builtinDir, 'manifest.json');
|
|
@@ -81,7 +89,9 @@ export function getPack(name: string): { manifest: HookPackManifest; dir: string
|
|
|
81
89
|
const manifest = JSON.parse(readFileSync(builtinManifest, 'utf-8')) as HookPackManifest;
|
|
82
90
|
manifest.source = 'built-in';
|
|
83
91
|
return { manifest, dir: builtinDir };
|
|
84
|
-
} catch {
|
|
92
|
+
} catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
85
95
|
}
|
|
86
96
|
|
|
87
97
|
export function getInstalledPacks(): string[] {
|
|
@@ -94,14 +104,18 @@ export function getInstalledPacks(): string[] {
|
|
|
94
104
|
const allScripts = pack.scripts.every((script) =>
|
|
95
105
|
existsSync(join(claudeDir, script.targetDir, script.file)),
|
|
96
106
|
);
|
|
97
|
-
if (allScripts) {
|
|
107
|
+
if (allScripts) {
|
|
108
|
+
installed.push(pack.name);
|
|
109
|
+
}
|
|
98
110
|
}
|
|
99
111
|
continue;
|
|
100
112
|
}
|
|
101
113
|
const allPresent = pack.hooks.every((hook) =>
|
|
102
114
|
existsSync(join(claudeDir, `hookify.${hook}.local.md`)),
|
|
103
115
|
);
|
|
104
|
-
if (allPresent) {
|
|
116
|
+
if (allPresent) {
|
|
117
|
+
installed.push(pack.name);
|
|
118
|
+
}
|
|
105
119
|
}
|
|
106
120
|
return installed;
|
|
107
121
|
}
|
package/package.json
CHANGED
|
@@ -27,7 +27,14 @@ describe('hook-packs', () => {
|
|
|
27
27
|
const packs = listPacks();
|
|
28
28
|
expect(packs.length).toBe(6);
|
|
29
29
|
const names = packs.map((p) => p.name).sort();
|
|
30
|
-
expect(names).toEqual([
|
|
30
|
+
expect(names).toEqual([
|
|
31
|
+
'a11y',
|
|
32
|
+
'clean-commits',
|
|
33
|
+
'css-discipline',
|
|
34
|
+
'full',
|
|
35
|
+
'typescript-safety',
|
|
36
|
+
'yolo-safety',
|
|
37
|
+
]);
|
|
31
38
|
});
|
|
32
39
|
|
|
33
40
|
it('should get a specific pack by name', () => {
|
|
@@ -46,7 +53,11 @@ describe('hook-packs', () => {
|
|
|
46
53
|
const pack = getPack('full');
|
|
47
54
|
expect(pack).not.toBeNull();
|
|
48
55
|
expect(pack!.manifest.composedFrom).toEqual([
|
|
49
|
-
'typescript-safety',
|
|
56
|
+
'typescript-safety',
|
|
57
|
+
'a11y',
|
|
58
|
+
'css-discipline',
|
|
59
|
+
'clean-commits',
|
|
60
|
+
'yolo-safety',
|
|
50
61
|
]);
|
|
51
62
|
expect(pack!.manifest.hooks).toHaveLength(8);
|
|
52
63
|
});
|
|
@@ -93,7 +104,16 @@ describe('hook-packs', () => {
|
|
|
93
104
|
expect(result.installed).toHaveLength(8);
|
|
94
105
|
expect(result.skipped).toEqual([]);
|
|
95
106
|
const claudeDir = join(tempHome, '.claude');
|
|
96
|
-
for (const hook of [
|
|
107
|
+
for (const hook of [
|
|
108
|
+
'no-any-types',
|
|
109
|
+
'no-console-log',
|
|
110
|
+
'no-important',
|
|
111
|
+
'no-inline-styles',
|
|
112
|
+
'semantic-html',
|
|
113
|
+
'focus-ring-required',
|
|
114
|
+
'ux-touch-targets',
|
|
115
|
+
'no-ai-attribution',
|
|
116
|
+
]) {
|
|
97
117
|
expect(existsSync(join(claudeDir, `hookify.${hook}.local.md`))).toBe(true);
|
|
98
118
|
}
|
|
99
119
|
expect(result.scripts).toHaveLength(1);
|
|
@@ -70,8 +70,12 @@ function runWizard(name, actions, opts = {}) {
|
|
|
70
70
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
71
71
|
});
|
|
72
72
|
|
|
73
|
-
proc.stdout.on('data', (d) => {
|
|
74
|
-
|
|
73
|
+
proc.stdout.on('data', (d) => {
|
|
74
|
+
buffer += d.toString();
|
|
75
|
+
});
|
|
76
|
+
proc.stderr.on('data', (d) => {
|
|
77
|
+
buffer += d.toString();
|
|
78
|
+
});
|
|
75
79
|
|
|
76
80
|
async function drive() {
|
|
77
81
|
while (actionIndex < actions.length && !state.completed) {
|
|
@@ -120,7 +124,9 @@ function runWizard(name, actions, opts = {}) {
|
|
|
120
124
|
clearInterval(poller);
|
|
121
125
|
proc.kill('SIGTERM');
|
|
122
126
|
setTimeout(() => {
|
|
123
|
-
try {
|
|
127
|
+
try {
|
|
128
|
+
proc.kill('SIGKILL');
|
|
129
|
+
} catch {}
|
|
124
130
|
resolve({
|
|
125
131
|
exitCode: -1,
|
|
126
132
|
output: stripAnsi(buffer) + '\n[TIMEOUT]',
|
|
@@ -157,13 +163,62 @@ function archetypeActions(outDir, { downCount = 0 } = {}) {
|
|
|
157
163
|
// Note: agentId is slugify(label), not the archetype value.
|
|
158
164
|
// e.g., "Full-Stack Assistant" → "full-stack-assistant"
|
|
159
165
|
const ARCHETYPES = [
|
|
160
|
-
{
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
166
|
+
{
|
|
167
|
+
value: 'code-reviewer',
|
|
168
|
+
agentId: 'code-reviewer',
|
|
169
|
+
label: 'Code Reviewer',
|
|
170
|
+
tone: 'mentor',
|
|
171
|
+
totalSkills: 10,
|
|
172
|
+
downCount: 0,
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
value: 'security-auditor',
|
|
176
|
+
agentId: 'security-auditor',
|
|
177
|
+
label: 'Security Auditor',
|
|
178
|
+
tone: 'precise',
|
|
179
|
+
totalSkills: 10,
|
|
180
|
+
downCount: 1,
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
value: 'api-architect',
|
|
184
|
+
agentId: 'api-architect',
|
|
185
|
+
label: 'API Architect',
|
|
186
|
+
tone: 'pragmatic',
|
|
187
|
+
totalSkills: 10,
|
|
188
|
+
downCount: 2,
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
value: 'test-engineer',
|
|
192
|
+
agentId: 'test-engineer',
|
|
193
|
+
label: 'Test Engineer',
|
|
194
|
+
tone: 'mentor',
|
|
195
|
+
totalSkills: 10,
|
|
196
|
+
downCount: 3,
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
value: 'devops-pilot',
|
|
200
|
+
agentId: 'devops-pilot',
|
|
201
|
+
label: 'DevOps Pilot',
|
|
202
|
+
tone: 'pragmatic',
|
|
203
|
+
totalSkills: 10,
|
|
204
|
+
downCount: 4,
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
value: 'database-architect',
|
|
208
|
+
agentId: 'database-architect',
|
|
209
|
+
label: 'Database Architect',
|
|
210
|
+
tone: 'precise',
|
|
211
|
+
totalSkills: 10,
|
|
212
|
+
downCount: 5,
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
value: 'full-stack',
|
|
216
|
+
agentId: 'full-stack-assistant',
|
|
217
|
+
label: 'Full-Stack Assistant',
|
|
218
|
+
tone: 'mentor',
|
|
219
|
+
totalSkills: 11,
|
|
220
|
+
downCount: 6,
|
|
221
|
+
},
|
|
167
222
|
];
|
|
168
223
|
|
|
169
224
|
// ══════════════════════════════════════════════════════════
|
|
@@ -172,43 +227,55 @@ const ARCHETYPES = [
|
|
|
172
227
|
|
|
173
228
|
async function testCancelArchetype() {
|
|
174
229
|
console.log('\n [1/14] Cancel at archetype (Ctrl+C)');
|
|
175
|
-
const r = await runWizard('cancel-arch', [
|
|
176
|
-
|
|
177
|
-
|
|
230
|
+
const r = await runWizard('cancel-arch', [{ waitFor: 'kind of agent', send: CTRL_C }], {
|
|
231
|
+
timeout: 15000,
|
|
232
|
+
});
|
|
178
233
|
assert(r.actionsCompleted >= 1, 'prompt reached', 'cancel-archetype');
|
|
179
234
|
}
|
|
180
235
|
|
|
181
236
|
async function testCancelName() {
|
|
182
237
|
console.log('\n [2/14] Cancel at display name');
|
|
183
|
-
const r = await runWizard(
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
238
|
+
const r = await runWizard(
|
|
239
|
+
'cancel-name',
|
|
240
|
+
[
|
|
241
|
+
{ waitFor: 'kind of agent', send: SPACE + ENTER },
|
|
242
|
+
{ waitFor: 'Display name', send: CTRL_C },
|
|
243
|
+
],
|
|
244
|
+
{ timeout: 15000 },
|
|
245
|
+
);
|
|
187
246
|
assert(r.actionsCompleted >= 2, 'reached name prompt', 'cancel-name');
|
|
188
247
|
}
|
|
189
248
|
|
|
190
249
|
async function testCancelRole() {
|
|
191
250
|
console.log('\n [3/14] Cancel at role');
|
|
192
|
-
const r = await runWizard(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
251
|
+
const r = await runWizard(
|
|
252
|
+
'cancel-role',
|
|
253
|
+
[
|
|
254
|
+
{ waitFor: 'kind of agent', send: SPACE + ENTER },
|
|
255
|
+
{ waitFor: 'Display name', send: ENTER },
|
|
256
|
+
{ waitFor: 'Role', send: CTRL_C },
|
|
257
|
+
],
|
|
258
|
+
{ timeout: 15000 },
|
|
259
|
+
);
|
|
197
260
|
assert(r.actionsCompleted >= 3, 'reached role prompt', 'cancel-role');
|
|
198
261
|
}
|
|
199
262
|
|
|
200
263
|
async function testCancelSkills() {
|
|
201
264
|
console.log('\n [4/14] Cancel at skills');
|
|
202
|
-
const r = await runWizard(
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
265
|
+
const r = await runWizard(
|
|
266
|
+
'cancel-skills',
|
|
267
|
+
[
|
|
268
|
+
{ waitFor: 'kind of agent', send: SPACE + ENTER },
|
|
269
|
+
{ waitFor: 'Display name', send: ENTER },
|
|
270
|
+
{ waitFor: 'Role', send: ENTER },
|
|
271
|
+
{ waitFor: 'Description', send: ENTER },
|
|
272
|
+
{ waitFor: /domain|expertise/i, send: ENTER },
|
|
273
|
+
{ waitFor: /principle|guiding/i, send: ENTER },
|
|
274
|
+
{ waitFor: /tone/i, send: ENTER },
|
|
275
|
+
{ waitFor: /skill/i, send: CTRL_C },
|
|
276
|
+
],
|
|
277
|
+
{ timeout: 15000 },
|
|
278
|
+
);
|
|
212
279
|
assert(r.actionsCompleted >= 8, 'reached skills prompt', 'cancel-skills');
|
|
213
280
|
}
|
|
214
281
|
|
|
@@ -221,20 +288,24 @@ async function testDeclineConfirm() {
|
|
|
221
288
|
const outDir = join(TEST_ROOT, 'decline');
|
|
222
289
|
mkdirSync(outDir, { recursive: true });
|
|
223
290
|
|
|
224
|
-
const r = await runWizard(
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
291
|
+
const r = await runWizard(
|
|
292
|
+
'decline',
|
|
293
|
+
[
|
|
294
|
+
{ waitFor: 'kind of agent', send: SPACE + ENTER },
|
|
295
|
+
{ waitFor: 'Display name', send: ENTER },
|
|
296
|
+
{ waitFor: 'Role', send: ENTER },
|
|
297
|
+
{ waitFor: 'Description', send: ENTER },
|
|
298
|
+
{ waitFor: /domain|expertise/i, send: ENTER },
|
|
299
|
+
{ waitFor: /principle|guiding/i, send: ENTER },
|
|
300
|
+
{ waitFor: /tone/i, send: ENTER },
|
|
301
|
+
{ waitFor: /skill/i, send: ENTER },
|
|
302
|
+
{ waitFor: /greeting/i, send: ENTER },
|
|
303
|
+
{ waitFor: /output|directory/i, send: CTRL_U + outDir + ENTER, delay: 300 },
|
|
304
|
+
{ waitFor: /hook|pack/i, send: ENTER },
|
|
305
|
+
{ waitFor: /create agent/i, send: LEFT + ENTER },
|
|
306
|
+
],
|
|
307
|
+
{ timeout: 15000 },
|
|
308
|
+
);
|
|
238
309
|
|
|
239
310
|
assert(r.actionsCompleted >= 12, `all prompts reached (${r.actionsCompleted}/12)`, 'decline');
|
|
240
311
|
assert(!existsSync(join(outDir, 'code-reviewer', 'package.json')), 'no agent created', 'decline');
|
|
@@ -264,8 +335,11 @@ async function testArchetype(arch, idx) {
|
|
|
264
335
|
const personaPath = join(ad, 'src', 'identity', 'persona.ts');
|
|
265
336
|
if (existsSync(personaPath)) {
|
|
266
337
|
const persona = readFileSync(personaPath, 'utf-8');
|
|
267
|
-
assert(
|
|
268
|
-
`
|
|
338
|
+
assert(
|
|
339
|
+
persona.includes(`'${arch.label}'`) || persona.includes(`"${arch.label}"`),
|
|
340
|
+
`name = ${arch.label}`,
|
|
341
|
+
ctx,
|
|
342
|
+
);
|
|
269
343
|
assert(persona.includes(`tone: '${arch.tone}'`), `tone = ${arch.tone}`, ctx);
|
|
270
344
|
} else {
|
|
271
345
|
assert(false, 'persona.ts exists', ctx);
|
|
@@ -275,9 +349,21 @@ async function testArchetype(arch, idx) {
|
|
|
275
349
|
const skillsDir = join(ad, 'skills');
|
|
276
350
|
if (existsSync(skillsDir)) {
|
|
277
351
|
const skills = readdirSync(skillsDir);
|
|
278
|
-
assert(
|
|
352
|
+
assert(
|
|
353
|
+
skills.length === arch.totalSkills,
|
|
354
|
+
`${arch.totalSkills} skills (got ${skills.length})`,
|
|
355
|
+
ctx,
|
|
356
|
+
);
|
|
279
357
|
// Core skills always present
|
|
280
|
-
for (const core of [
|
|
358
|
+
for (const core of [
|
|
359
|
+
'brainstorming',
|
|
360
|
+
'systematic-debugging',
|
|
361
|
+
'verification-before-completion',
|
|
362
|
+
'health-check',
|
|
363
|
+
'context-resume',
|
|
364
|
+
'writing-plans',
|
|
365
|
+
'executing-plans',
|
|
366
|
+
]) {
|
|
281
367
|
assert(skills.includes(core), `core skill: ${core}`, ctx);
|
|
282
368
|
}
|
|
283
369
|
} else {
|
|
@@ -314,8 +400,9 @@ async function testCustomArchetype() {
|
|
|
314
400
|
const customName = 'GraphQL Guardian';
|
|
315
401
|
const customId = 'graphql-guardian';
|
|
316
402
|
const customRole = 'Validates GraphQL schemas against federation rules';
|
|
317
|
-
const customDesc =
|
|
318
|
-
|
|
403
|
+
const customDesc =
|
|
404
|
+
'This agent checks GraphQL schemas for breaking changes, naming conventions, and federation compatibility across subgraphs.';
|
|
405
|
+
const customGreeting = 'Hey! Drop your GraphQL schema and I will check it for issues.';
|
|
319
406
|
|
|
320
407
|
const r = await runWizard('custom', [
|
|
321
408
|
// Step 1: Select "✦ Create Custom" (9 downs — 9 archetypes before _custom)
|
|
@@ -426,7 +513,11 @@ async function testHookPacks() {
|
|
|
426
513
|
// Validate hooks were installed
|
|
427
514
|
const output = r.output;
|
|
428
515
|
assert(output.includes('a11y') && output.includes('installed'), 'a11y pack installed', ctx);
|
|
429
|
-
assert(
|
|
516
|
+
assert(
|
|
517
|
+
output.includes('typescript-safety') && output.includes('installed'),
|
|
518
|
+
'typescript-safety pack installed',
|
|
519
|
+
ctx,
|
|
520
|
+
);
|
|
430
521
|
|
|
431
522
|
// Check .claude directory has hooks
|
|
432
523
|
const claudeDir = join(ad, '.claude');
|
|
@@ -435,7 +526,9 @@ async function testHookPacks() {
|
|
|
435
526
|
assert(files.length > 0, `.claude/ has hook files (${files.length})`, ctx);
|
|
436
527
|
}
|
|
437
528
|
|
|
438
|
-
console.log(
|
|
529
|
+
console.log(
|
|
530
|
+
` exit=${r.exitCode}, agent=${existsSync(ad)}, hooks=${r.output.includes('installed')}`,
|
|
531
|
+
);
|
|
439
532
|
}
|
|
440
533
|
|
|
441
534
|
// ══════════════════════════════════════════════════════════
|
|
@@ -474,10 +567,7 @@ rmSync(TEST_ROOT, { recursive: true, force: true });
|
|
|
474
567
|
|
|
475
568
|
// Clean up any MCP registrations
|
|
476
569
|
try {
|
|
477
|
-
const claudeJson = join(
|
|
478
|
-
process.env.HOME || process.env.USERPROFILE || '',
|
|
479
|
-
'.claude.json',
|
|
480
|
-
);
|
|
570
|
+
const claudeJson = join(process.env.HOME || process.env.USERPROFILE || '', '.claude.json');
|
|
481
571
|
if (existsSync(claudeJson)) {
|
|
482
572
|
const c = JSON.parse(readFileSync(claudeJson, 'utf-8'));
|
|
483
573
|
let changed = false;
|