@antomanc/aginit 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -0
- package/README.md +108 -28
- package/THIRD_PARTY_NOTICES.md +92 -16
- package/dist/adapters/agent-docs.d.ts +32 -0
- package/dist/adapters/agent-docs.d.ts.map +1 -0
- package/dist/adapters/agent-docs.js +146 -0
- package/dist/adapters/agent-docs.js.map +1 -0
- package/dist/adapters/agents-md.d.ts +13 -1
- package/dist/adapters/agents-md.d.ts.map +1 -1
- package/dist/adapters/agents-md.js +137 -120
- package/dist/adapters/agents-md.js.map +1 -1
- package/dist/adapters/e2e.d.ts +15 -0
- package/dist/adapters/e2e.d.ts.map +1 -0
- package/dist/adapters/e2e.js +55 -0
- package/dist/adapters/e2e.js.map +1 -0
- package/dist/adapters/skills.d.ts +5 -0
- package/dist/adapters/skills.d.ts.map +1 -1
- package/dist/adapters/skills.js +17 -0
- package/dist/adapters/skills.js.map +1 -1
- package/dist/adapters/tool-ignores.d.ts +9 -0
- package/dist/adapters/tool-ignores.d.ts.map +1 -0
- package/dist/adapters/tool-ignores.js +121 -0
- package/dist/adapters/tool-ignores.js.map +1 -0
- package/dist/cli/capabilities.d.ts +17 -0
- package/dist/cli/capabilities.d.ts.map +1 -0
- package/dist/cli/capabilities.js +149 -0
- package/dist/cli/capabilities.js.map +1 -0
- package/dist/cli/doctor.d.ts +16 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +104 -70
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/init.js +1 -1
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +123 -20
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/new.d.ts +15 -1
- package/dist/cli/new.d.ts.map +1 -1
- package/dist/cli/new.js +163 -67
- package/dist/cli/new.js.map +1 -1
- package/dist/cli/wizard.d.ts +5 -0
- package/dist/cli/wizard.d.ts.map +1 -1
- package/dist/cli/wizard.js +104 -102
- package/dist/cli/wizard.js.map +1 -1
- package/dist/config/defaults.d.ts +14 -6
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +38 -174
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/schema.d.ts +14 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/config/validation.d.ts +1 -0
- package/dist/config/validation.d.ts.map +1 -1
- package/dist/config/validation.js +63 -0
- package/dist/config/validation.js.map +1 -1
- package/dist/detect/index.d.ts +18 -0
- package/dist/detect/index.d.ts.map +1 -0
- package/dist/detect/index.js +120 -0
- package/dist/detect/index.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/presets/cli.d.ts.map +1 -1
- package/dist/presets/cli.js +7 -0
- package/dist/presets/cli.js.map +1 -1
- package/dist/presets/generic.d.ts.map +1 -1
- package/dist/presets/generic.js +4 -1
- package/dist/presets/generic.js.map +1 -1
- package/dist/presets/testing.d.ts +1 -1
- package/dist/presets/testing.d.ts.map +1 -1
- package/dist/presets/testing.js +22 -8
- package/dist/presets/testing.js.map +1 -1
- package/dist/presets/web.d.ts.map +1 -1
- package/dist/presets/web.js +39 -5
- package/dist/presets/web.js.map +1 -1
- package/dist/registry/catalog.d.ts +13 -0
- package/dist/registry/catalog.d.ts.map +1 -0
- package/dist/registry/catalog.js +544 -0
- package/dist/registry/catalog.js.map +1 -0
- package/dist/registry/index.d.ts +84 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +309 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/registry/setup.d.ts +14 -0
- package/dist/registry/setup.d.ts.map +1 -0
- package/dist/registry/setup.js +25 -0
- package/dist/registry/setup.js.map +1 -0
- package/dist/registry/types.d.ts +115 -0
- package/dist/registry/types.d.ts.map +1 -0
- package/dist/registry/types.js +2 -0
- package/dist/registry/types.js.map +1 -0
- package/package.json +3 -2
- package/src/adapters/agent-docs.ts +184 -0
- package/src/adapters/agents-md.ts +160 -137
- package/src/adapters/e2e.ts +58 -0
- package/src/adapters/skills.ts +18 -0
- package/src/adapters/tool-ignores.ts +158 -0
- package/src/cli/capabilities.ts +176 -0
- package/src/cli/doctor.ts +100 -66
- package/src/cli/init.ts +1 -1
- package/src/cli/main.ts +130 -20
- package/src/cli/new.ts +199 -73
- package/src/cli/wizard.ts +130 -103
- package/src/config/defaults.ts +57 -179
- package/src/config/schema.ts +15 -0
- package/src/config/validation.ts +56 -0
- package/src/detect/index.ts +134 -0
- package/src/index.ts +5 -0
- package/src/presets/cli.ts +6 -0
- package/src/presets/generic.ts +4 -1
- package/src/presets/testing.ts +29 -12
- package/src/presets/web.ts +39 -5
- package/src/registry/catalog.ts +569 -0
- package/src/registry/index.ts +395 -0
- package/src/registry/setup.ts +40 -0
- package/src/registry/types.ts +109 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import type { AginitConfig, SkillSourceConfig, WebFramework } from '../config/schema.js';
|
|
2
|
+
import { CATALOG } from './catalog.js';
|
|
3
|
+
import {
|
|
4
|
+
CapabilityCategory,
|
|
5
|
+
CapabilityDescriptor,
|
|
6
|
+
CustomCapabilityConfig,
|
|
7
|
+
DoctorCheck,
|
|
8
|
+
Platform,
|
|
9
|
+
Stack
|
|
10
|
+
} from './types.js';
|
|
11
|
+
|
|
12
|
+
export * from './types.js';
|
|
13
|
+
export { CATALOG, MATT_POCOCK_SKILLS, SUPERPOWERS_SKILLS } from './catalog.js';
|
|
14
|
+
|
|
15
|
+
export const PLATFORMS: Platform[] = ['web', 'mobile-native', 'mobile-rn', 'backend', 'cli'];
|
|
16
|
+
export const TESTING_CHOICES = ['e2e', 'playwright', 'vitest', 'native'] as const;
|
|
17
|
+
export type TestingChoice = (typeof TESTING_CHOICES)[number];
|
|
18
|
+
|
|
19
|
+
/** Categories where picking one capability replaces the previous pick. */
|
|
20
|
+
const EXCLUSIVE: CapabilityCategory[] = ['methodology', 'design', 'ui-driver'];
|
|
21
|
+
|
|
22
|
+
/** Profile options from the CLI (`--platform`, `--methodology`, ...). */
|
|
23
|
+
export interface ProfileFlags {
|
|
24
|
+
platform?: Platform;
|
|
25
|
+
methodology?: string;
|
|
26
|
+
design?: string;
|
|
27
|
+
testing?: TestingChoice;
|
|
28
|
+
uiDriver?: string;
|
|
29
|
+
addons?: string[];
|
|
30
|
+
addSkills?: string[];
|
|
31
|
+
without?: string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function hasProfileFlags(flags: ProfileFlags): boolean {
|
|
35
|
+
return Object.values(flags).some((value) =>
|
|
36
|
+
Array.isArray(value) ? value.length > 0 : value !== undefined
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Add a capability to the built-in catalog (programmatic API and tests). */
|
|
41
|
+
export function registerCapability(descriptor: CapabilityDescriptor): void {
|
|
42
|
+
if (CATALOG.some((entry) => entry.id === descriptor.id))
|
|
43
|
+
throw new Error(`Capability "${descriptor.id}" is already registered.`);
|
|
44
|
+
CATALOG.push(descriptor);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function customToDescriptor(custom: CustomCapabilityConfig): CapabilityDescriptor {
|
|
48
|
+
return {
|
|
49
|
+
id: custom.id,
|
|
50
|
+
name: custom.name ?? custom.id,
|
|
51
|
+
description: custom.description ?? `Skills from ${custom.repo}`,
|
|
52
|
+
category: custom.category ?? 'custom',
|
|
53
|
+
author: custom.repo.split('/')[0] ?? custom.repo,
|
|
54
|
+
repoUrl: `https://github.com/${custom.repo}`,
|
|
55
|
+
license: 'see upstream',
|
|
56
|
+
supportedPlatforms: custom.supportedPlatforms ?? ['all'],
|
|
57
|
+
requiresNodeInProjectRoot: false,
|
|
58
|
+
install: [
|
|
59
|
+
{ kind: 'skills-sh', repo: custom.repo, skills: custom.skills ?? [] },
|
|
60
|
+
...(custom.binary ? [{ kind: 'binary' as const, ...custom.binary }] : [])
|
|
61
|
+
]
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The built-in catalog plus the project's custom capabilities. */
|
|
66
|
+
export function catalogFor(config?: Pick<AginitConfig, 'customCapabilities'> | null): CapabilityDescriptor[] {
|
|
67
|
+
return [...CATALOG, ...(config?.customCapabilities ?? []).map(customToDescriptor)];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function findCapability(
|
|
71
|
+
id: string,
|
|
72
|
+
config?: Pick<AginitConfig, 'customCapabilities'> | null
|
|
73
|
+
): CapabilityDescriptor | undefined {
|
|
74
|
+
return catalogFor(config).find((entry) => entry.id === id);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function isNodeStack(stack: Stack): boolean {
|
|
78
|
+
return stack === 'node' || stack === 'react-native';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function supports(capability: CapabilityDescriptor, platform: Platform): boolean {
|
|
82
|
+
return (
|
|
83
|
+
capability.supportedPlatforms.includes('all') || capability.supportedPlatforms.includes(platform)
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
interface ProfileContext {
|
|
88
|
+
platform: Platform;
|
|
89
|
+
stack: Stack;
|
|
90
|
+
framework?: WebFramework;
|
|
91
|
+
/** false when aginit will not scaffold Vitest (a generic preset over an existing package.json). */
|
|
92
|
+
harness?: boolean;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function testingIds(choice: TestingChoice | undefined, ctx: ProfileContext): string[] {
|
|
96
|
+
const node = isNodeStack(ctx.stack);
|
|
97
|
+
const unit = node && ctx.platform !== 'mobile-rn' && ctx.harness !== false ? 'vitest' : 'native';
|
|
98
|
+
switch (choice) {
|
|
99
|
+
case undefined:
|
|
100
|
+
return ctx.platform === 'web' &&
|
|
101
|
+
node &&
|
|
102
|
+
(ctx.framework === 'vite' || ctx.framework === 'next')
|
|
103
|
+
? ['vitest', 'playwright']
|
|
104
|
+
: [unit];
|
|
105
|
+
case 'vitest':
|
|
106
|
+
return ['vitest'];
|
|
107
|
+
case 'playwright':
|
|
108
|
+
return ['vitest', 'playwright'];
|
|
109
|
+
case 'e2e':
|
|
110
|
+
return [unit, 'e2e'];
|
|
111
|
+
case 'native':
|
|
112
|
+
return ['native'];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Capabilities enabled by default for a platform and stack. */
|
|
117
|
+
export function defaultCapabilities(ctx: ProfileContext): string[] {
|
|
118
|
+
const picked: CapabilityDescriptor[] = [];
|
|
119
|
+
for (const capability of CATALOG) {
|
|
120
|
+
if (!capability.default || capability.addon) continue;
|
|
121
|
+
if (capability.category === 'testing-engine') continue;
|
|
122
|
+
if (!supports(capability, ctx.platform)) continue;
|
|
123
|
+
if (capability.requiresNodeInProjectRoot && !isNodeStack(ctx.stack)) continue;
|
|
124
|
+
if (
|
|
125
|
+
EXCLUSIVE.includes(capability.category) &&
|
|
126
|
+
picked.some((p) => p.category === capability.category)
|
|
127
|
+
)
|
|
128
|
+
continue;
|
|
129
|
+
picked.push(capability);
|
|
130
|
+
}
|
|
131
|
+
return [...picked.map((c) => c.id), ...testingIds(undefined, ctx)];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const unique = (ids: string[]) => [...new Set(ids)];
|
|
135
|
+
|
|
136
|
+
/** Capability id for a `--add-skill owner/repo` value. */
|
|
137
|
+
export function customCapabilityFromRepo(repo: string): CustomCapabilityConfig {
|
|
138
|
+
if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repo))
|
|
139
|
+
throw new Error(`Invalid skill repository "${repo}": expected owner/repo.`);
|
|
140
|
+
return { id: repo, repo };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface ResolveInput extends ProfileContext {
|
|
144
|
+
/** Previously saved capabilities; flags are applied on top of them. */
|
|
145
|
+
base?: string[];
|
|
146
|
+
/** The platform the base was resolved for; a different platform re-derives defaults. */
|
|
147
|
+
basePlatform?: Platform;
|
|
148
|
+
flags?: ProfileFlags;
|
|
149
|
+
custom?: CustomCapabilityConfig[];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export interface ResolvedProfile {
|
|
153
|
+
capabilities: string[];
|
|
154
|
+
customCapabilities: CustomCapabilityConfig[];
|
|
155
|
+
disabledCapabilities: string[];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Turn a platform, a stack and the profile flags into the list of capability
|
|
160
|
+
* ids to save. Explicit flags replace the pick in their category; everything
|
|
161
|
+
* else keeps the saved choice (or the platform default on a fresh project).
|
|
162
|
+
*/
|
|
163
|
+
export function resolveCapabilities(input: ResolveInput, disabled: string[] = []): ResolvedProfile {
|
|
164
|
+
const flags = input.flags ?? {};
|
|
165
|
+
const custom = [...(input.custom ?? [])];
|
|
166
|
+
const byId = (id: string) =>
|
|
167
|
+
CATALOG.find((c) => c.id === id) ?? custom.map(customToDescriptor).find((c) => c.id === id);
|
|
168
|
+
const require = (id: string, category?: CapabilityCategory, allowNone = false): string | null => {
|
|
169
|
+
if (allowNone && id === 'none') return null;
|
|
170
|
+
const capability = byId(id);
|
|
171
|
+
const valid = CATALOG.filter((c) => !category || c.category === category).map((c) => c.id);
|
|
172
|
+
if (!capability || (category && capability.category !== category))
|
|
173
|
+
throw new Error(
|
|
174
|
+
`Unknown ${category ?? 'capability'} "${id}". Valid: ${[...valid, ...(allowNone ? ['none'] : [])].join(', ')}`
|
|
175
|
+
);
|
|
176
|
+
return capability.id;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const ctx: ProfileContext = input;
|
|
180
|
+
let ids: string[];
|
|
181
|
+
if (!input.base) ids = defaultCapabilities(ctx);
|
|
182
|
+
else if (input.basePlatform && input.basePlatform !== input.platform) {
|
|
183
|
+
// A new platform brings its own UI defaults; methodology and add-ons carry over.
|
|
184
|
+
const methodology = input.base.find((id) => byId(id)?.category === 'methodology');
|
|
185
|
+
ids = defaultCapabilities(ctx);
|
|
186
|
+
if (methodology)
|
|
187
|
+
ids = [...ids.filter((id) => byId(id)?.category !== 'methodology'), methodology];
|
|
188
|
+
ids.push(...input.base.filter((id) => byId(id)?.addon));
|
|
189
|
+
} else ids = [...input.base];
|
|
190
|
+
|
|
191
|
+
const replace = (category: CapabilityCategory, id: string | null) => {
|
|
192
|
+
ids = ids.filter((existing) => {
|
|
193
|
+
const capability = byId(existing);
|
|
194
|
+
return !(capability?.category === category && !capability.addon);
|
|
195
|
+
});
|
|
196
|
+
if (id) ids.push(id);
|
|
197
|
+
};
|
|
198
|
+
if (flags.methodology) replace('methodology', require(flags.methodology, 'methodology'));
|
|
199
|
+
if (flags.design) replace('design', require(flags.design, 'design', true));
|
|
200
|
+
if (flags.uiDriver) {
|
|
201
|
+
const pick =
|
|
202
|
+
flags.uiDriver === 'auto'
|
|
203
|
+
? (defaultCapabilities(ctx).find((id) => byId(id)?.category === 'ui-driver') ?? null)
|
|
204
|
+
: require(flags.uiDriver, 'ui-driver', true);
|
|
205
|
+
replace('ui-driver', pick);
|
|
206
|
+
}
|
|
207
|
+
if (flags.testing) {
|
|
208
|
+
if (!TESTING_CHOICES.includes(flags.testing))
|
|
209
|
+
throw new Error(`Unknown testing engine "${flags.testing}". Valid: ${TESTING_CHOICES.join(', ')}`);
|
|
210
|
+
ids = ids.filter((id) => byId(id)?.category !== 'testing-engine');
|
|
211
|
+
ids.push(...testingIds(flags.testing, ctx));
|
|
212
|
+
}
|
|
213
|
+
for (const addon of flags.addons ?? []) ids.push(require(addon)!);
|
|
214
|
+
for (const repo of flags.addSkills ?? []) {
|
|
215
|
+
const entry = customCapabilityFromRepo(repo);
|
|
216
|
+
if (!custom.some((c) => c.id === entry.id)) custom.push(entry);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const without = flags.without ?? [];
|
|
220
|
+
for (const id of without) if (!byId(id)) require(id);
|
|
221
|
+
const disabledIds = unique([...disabled.filter((id) => !(flags.addons ?? []).includes(id)), ...without]);
|
|
222
|
+
ids = unique(ids).filter((id) => !disabledIds.includes(id));
|
|
223
|
+
|
|
224
|
+
for (const id of ids) {
|
|
225
|
+
const capability = byId(id)!;
|
|
226
|
+
if (capability.requiresNodeInProjectRoot && !isNodeStack(input.stack))
|
|
227
|
+
throw new Error(
|
|
228
|
+
`${capability.name} needs a package.json at the project root, but this is a ${input.stack} project. ` +
|
|
229
|
+
'Use --testing e2e (isolated in .agents/e2e/) or --testing native.'
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
capabilities: ids,
|
|
234
|
+
customCapabilities: custom.filter((c) => !disabledIds.includes(c.id)),
|
|
235
|
+
disabledCapabilities: disabledIds
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const samePackage = (a: string, b: string) => a.toLowerCase() === b.toLowerCase();
|
|
240
|
+
|
|
241
|
+
function skillSteps(capability: CapabilityDescriptor) {
|
|
242
|
+
return capability.install.flatMap((step) => (step.kind === 'skills-sh' ? [step] : []));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** Skill sources that install the given capabilities. */
|
|
246
|
+
export function sourcesFor(capabilities: CapabilityDescriptor[]): SkillSourceConfig[] {
|
|
247
|
+
return mergeSources([], capabilities);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function mergeSources(
|
|
251
|
+
sources: SkillSourceConfig[],
|
|
252
|
+
capabilities: CapabilityDescriptor[]
|
|
253
|
+
): SkillSourceConfig[] {
|
|
254
|
+
const merged = sources.map((source) => ({ ...source, skills: [...source.skills] }));
|
|
255
|
+
for (const step of capabilities.flatMap(skillSteps)) {
|
|
256
|
+
const existing = merged.find((source) => samePackage(source.package, step.repo));
|
|
257
|
+
if (!existing) merged.push({ package: step.repo, skills: [...step.skills] });
|
|
258
|
+
else if (existing.skills.length > 0)
|
|
259
|
+
existing.skills =
|
|
260
|
+
step.skills.length === 0 ? [] : unique([...existing.skills, ...step.skills]);
|
|
261
|
+
}
|
|
262
|
+
return merged;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Capabilities a config written before v2 (no `capabilities` field) uses:
|
|
267
|
+
* the ones whose skills it installs, plus Graft and its test harness.
|
|
268
|
+
*/
|
|
269
|
+
export function inferCapabilities(config: AginitConfig): string[] {
|
|
270
|
+
const ids: string[] = [];
|
|
271
|
+
if (config.codebase.graft) ids.push('graft');
|
|
272
|
+
for (const capability of CATALOG) {
|
|
273
|
+
const steps = skillSteps(capability);
|
|
274
|
+
if (
|
|
275
|
+
steps.length > 0 &&
|
|
276
|
+
steps.some((step) => config.skills.sources.some((s) => samePackage(s.package, step.repo)))
|
|
277
|
+
)
|
|
278
|
+
ids.push(capability.id);
|
|
279
|
+
}
|
|
280
|
+
// Every pre-v2 preset scaffolded a Vitest harness.
|
|
281
|
+
ids.push(config.stack && !isNodeStack(config.stack) ? 'native' : 'vitest');
|
|
282
|
+
if (config.browser.playwright) ids.push('playwright');
|
|
283
|
+
return ids;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** The capabilities a project config turns on. */
|
|
287
|
+
export function activeCapabilities(config: AginitConfig): CapabilityDescriptor[] {
|
|
288
|
+
const disabled = new Set(config.disabledCapabilities ?? []);
|
|
289
|
+
const ids = unique([
|
|
290
|
+
...(config.capabilities ?? inferCapabilities(config)),
|
|
291
|
+
...(config.customCapabilities ?? []).map((c) => c.id)
|
|
292
|
+
]);
|
|
293
|
+
const catalog = catalogFor(config);
|
|
294
|
+
return ids
|
|
295
|
+
.filter((id) => !disabled.has(id))
|
|
296
|
+
.map((id) => catalog.find((c) => c.id === id))
|
|
297
|
+
.filter((c): c is CapabilityDescriptor => c !== undefined)
|
|
298
|
+
.filter((capability) => {
|
|
299
|
+
// The legacy switches still turn their capability off.
|
|
300
|
+
if (capability.id === 'graft' && config.codebase.graft === false) return false;
|
|
301
|
+
if (capability.id === 'playwright' && !config.browser.playwright) return false;
|
|
302
|
+
// Skills only install from configured sources (`--no-skills` empties them).
|
|
303
|
+
const steps = skillSteps(capability).filter((step) => !step.optional);
|
|
304
|
+
return (
|
|
305
|
+
steps.length === 0 ||
|
|
306
|
+
steps.some((step) => config.skills.sources.some((s) => samePackage(s.package, step.repo)))
|
|
307
|
+
);
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export function activeIds(config: AginitConfig): Set<string> {
|
|
312
|
+
return new Set(activeCapabilities(config).map((c) => c.id));
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** Keep the pre-v2 switches in step with the capability list. */
|
|
316
|
+
function mirrorLegacySwitches(config: AginitConfig, ids: string[]): void {
|
|
317
|
+
config.codebase = { ...config.codebase, graft: ids.includes('graft') };
|
|
318
|
+
config.browser = {
|
|
319
|
+
...config.browser,
|
|
320
|
+
visualAgent: ids.includes('agent-browser'),
|
|
321
|
+
playwright: ids.includes('playwright')
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** A fresh config section for a resolved profile. */
|
|
326
|
+
export function applyResolvedProfile(
|
|
327
|
+
config: AginitConfig,
|
|
328
|
+
profile: ResolvedProfile,
|
|
329
|
+
options: { skills: boolean; previous?: string[] }
|
|
330
|
+
): AginitConfig {
|
|
331
|
+
const catalog = catalogFor({ customCapabilities: profile.customCapabilities });
|
|
332
|
+
const pick = (ids: string[]) =>
|
|
333
|
+
ids.map((id) => catalog.find((c) => c.id === id)).filter((c): c is CapabilityDescriptor => !!c);
|
|
334
|
+
const all = [...profile.capabilities, ...profile.customCapabilities.map((c) => c.id)];
|
|
335
|
+
const next: AginitConfig = {
|
|
336
|
+
...config,
|
|
337
|
+
capabilities: profile.capabilities,
|
|
338
|
+
...(profile.customCapabilities.length ? { customCapabilities: profile.customCapabilities } : {}),
|
|
339
|
+
...(profile.disabledCapabilities.length
|
|
340
|
+
? { disabledCapabilities: profile.disabledCapabilities }
|
|
341
|
+
: {})
|
|
342
|
+
};
|
|
343
|
+
if (!profile.customCapabilities.length) delete next.customCapabilities;
|
|
344
|
+
if (!profile.disabledCapabilities.length) delete next.disabledCapabilities;
|
|
345
|
+
if (!options.skills) next.skills = { ...config.skills, sources: [] };
|
|
346
|
+
else if (!options.previous) next.skills = { ...config.skills, sources: sourcesFor(pick(all)) };
|
|
347
|
+
else {
|
|
348
|
+
// Incremental: keep hand-edited sources, drop removed capabilities, add new ones.
|
|
349
|
+
const removed = pick(options.previous.filter((id) => !all.includes(id)));
|
|
350
|
+
const removedRepos = removed.flatMap(skillSteps).map((step) => step.repo);
|
|
351
|
+
const kept = config.skills.sources.filter(
|
|
352
|
+
(source) => !removedRepos.some((repo) => samePackage(repo, source.package))
|
|
353
|
+
);
|
|
354
|
+
const added = pick(all.filter((id) => !options.previous!.includes(id)));
|
|
355
|
+
next.skills = { ...config.skills, sources: mergeSources(kept, added) };
|
|
356
|
+
}
|
|
357
|
+
mirrorLegacySwitches(next, all);
|
|
358
|
+
return next;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export interface DerivedCheck {
|
|
362
|
+
capability: CapabilityDescriptor;
|
|
363
|
+
check: DoctorCheck;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Doctor checks for a capability: its declared ones plus one per skill and
|
|
368
|
+
* binary. Optional skills are checked only when their source is configured.
|
|
369
|
+
*/
|
|
370
|
+
export function doctorChecks(
|
|
371
|
+
capability: CapabilityDescriptor,
|
|
372
|
+
sources?: SkillSourceConfig[]
|
|
373
|
+
): DoctorCheck[] {
|
|
374
|
+
const derived: DoctorCheck[] = [];
|
|
375
|
+
for (const step of capability.install) {
|
|
376
|
+
if (step.kind === 'skills-sh') {
|
|
377
|
+
if (step.optional && sources && !sources.some((s) => samePackage(s.package, step.repo)))
|
|
378
|
+
continue;
|
|
379
|
+
for (const skill of step.skills)
|
|
380
|
+
derived.push({
|
|
381
|
+
kind: 'file',
|
|
382
|
+
path: `.agents/skills/${skill}/SKILL.md`,
|
|
383
|
+
fix: `npx skills add ${step.repo} --skill ${skill}`
|
|
384
|
+
});
|
|
385
|
+
} else if (step.kind === 'binary') {
|
|
386
|
+
derived.push({ kind: 'command', bin: step.bin, args: ['--version'], fix: step.install });
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return [...derived, ...(capability.doctor ?? [])];
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/** Where e2e lives: the project root for Node projects, an isolated workspace otherwise. */
|
|
393
|
+
export function e2eDirFor(stack: Stack): string {
|
|
394
|
+
return isNodeStack(stack) ? '' : '.agents/e2e';
|
|
395
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { AginitConfig } from '../config/schema.js';
|
|
2
|
+
import { initGraft } from '../adapters/graft.js';
|
|
3
|
+
import { setupAgentBrowser } from '../adapters/agent-browser.js';
|
|
4
|
+
import { setupE2e } from '../adapters/e2e.js';
|
|
5
|
+
import { claudeCodeEnabled } from '../adapters/skills.js';
|
|
6
|
+
import { Platform, SetupId, Stack } from './types.js';
|
|
7
|
+
|
|
8
|
+
export interface SetupContext {
|
|
9
|
+
targetDir: string;
|
|
10
|
+
config: AginitConfig;
|
|
11
|
+
platform: Platform;
|
|
12
|
+
stack: Stack;
|
|
13
|
+
dryRun: boolean;
|
|
14
|
+
silent: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Upstream setup commands, keyed by the `setup` install step that names them. */
|
|
18
|
+
export const SETUPS: Record<SetupId, (ctx: SetupContext) => Promise<boolean>> = {
|
|
19
|
+
graft: (ctx) =>
|
|
20
|
+
initGraft(ctx.targetDir, {
|
|
21
|
+
dryRun: ctx.dryRun,
|
|
22
|
+
silent: ctx.silent,
|
|
23
|
+
claude: claudeCodeEnabled(ctx.config)
|
|
24
|
+
}),
|
|
25
|
+
'agent-browser': (ctx) =>
|
|
26
|
+
setupAgentBrowser({ dryRun: ctx.dryRun, silent: ctx.silent, cwd: ctx.targetDir }),
|
|
27
|
+
e2e: (ctx) =>
|
|
28
|
+
setupE2e(ctx.targetDir, {
|
|
29
|
+
stack: ctx.stack,
|
|
30
|
+
platform: ctx.platform,
|
|
31
|
+
dryRun: ctx.dryRun,
|
|
32
|
+
silent: ctx.silent
|
|
33
|
+
})
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const SETUP_LABELS: Record<SetupId, string> = {
|
|
37
|
+
graft: 'Configuring codebase intelligence (Graft)',
|
|
38
|
+
'agent-browser': 'Provisioning agent-browser runtime',
|
|
39
|
+
e2e: 'Scaffolding e2e tests'
|
|
40
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
export type Platform = 'web' | 'mobile-native' | 'mobile-rn' | 'backend' | 'cli';
|
|
2
|
+
export type Stack =
|
|
3
|
+
| 'node'
|
|
4
|
+
| 'react-native'
|
|
5
|
+
| 'flutter'
|
|
6
|
+
| 'swift'
|
|
7
|
+
| 'kotlin'
|
|
8
|
+
| 'rust'
|
|
9
|
+
| 'python'
|
|
10
|
+
| 'go'
|
|
11
|
+
| 'unknown';
|
|
12
|
+
|
|
13
|
+
export type CapabilityCategory =
|
|
14
|
+
| 'methodology' // pocock, superpowers, minimal
|
|
15
|
+
| 'discipline' // ponytail, karpathy-guidelines, humanizer
|
|
16
|
+
| 'design' // impeccable
|
|
17
|
+
| 'codebase-graph' // graft
|
|
18
|
+
| 'ui-driver' // agent-browser, agent-device, chrome-devtools-mcp, browserskill
|
|
19
|
+
| 'testing-engine' // e2e, playwright, vitest, native
|
|
20
|
+
| 'context-guard' // context-mode, worktrunk
|
|
21
|
+
| 'custom'; // user-defined via customCapabilities / --add-skill
|
|
22
|
+
|
|
23
|
+
/** The micro-docs aginit generates under docs/agents/. */
|
|
24
|
+
export type MicroDoc = 'testing' | 'ui-verification' | 'workflows';
|
|
25
|
+
|
|
26
|
+
export type InstallStep =
|
|
27
|
+
/**
|
|
28
|
+
* `npx skills add <repo> --skill <skills...>` into .agents/skills/ (empty list = every skill).
|
|
29
|
+
* The capability is active only while its source is configured, unless the step is
|
|
30
|
+
* `optional` (a helper skill for a tool that works without it, like e2e).
|
|
31
|
+
*/
|
|
32
|
+
| { kind: 'skills-sh'; repo: string; skills: string[]; optional?: boolean }
|
|
33
|
+
/** A CLI the capability drives: init and doctor check it and print `install` when it is missing. */
|
|
34
|
+
| { kind: 'binary'; bin: string; install: string }
|
|
35
|
+
/** Upstream setup that needs code (see src/registry/setup.ts). */
|
|
36
|
+
| { kind: 'setup'; setup: SetupId };
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Setups that run an upstream init command. vitest and playwright have none:
|
|
40
|
+
* the presets add them to package.json when they are active.
|
|
41
|
+
*/
|
|
42
|
+
export type SetupId = 'graft' | 'agent-browser' | 'e2e';
|
|
43
|
+
|
|
44
|
+
export type DoctorCheck =
|
|
45
|
+
| { kind: 'file'; path: string; fix: string }
|
|
46
|
+
| { kind: 'command'; bin: string; args: string[]; fix: string };
|
|
47
|
+
|
|
48
|
+
/** What a generated doc needs to know about the project. */
|
|
49
|
+
export interface GuideContext {
|
|
50
|
+
platform: Platform;
|
|
51
|
+
stack: Stack;
|
|
52
|
+
/** Whether a capability is active. */
|
|
53
|
+
has(id: string): boolean;
|
|
54
|
+
/** The project's own commands (package scripts or the stack's host toolchain). */
|
|
55
|
+
commands: ProjectCommands;
|
|
56
|
+
/** Directory that holds the e2e workspace ('' for the project root). */
|
|
57
|
+
e2eDir: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface ProjectCommands {
|
|
61
|
+
build?: string;
|
|
62
|
+
typecheck?: string;
|
|
63
|
+
lint?: string;
|
|
64
|
+
test?: string;
|
|
65
|
+
unitTest?: string;
|
|
66
|
+
e2e?: string;
|
|
67
|
+
dev?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface CapabilityDescriptor {
|
|
71
|
+
id: string;
|
|
72
|
+
name: string;
|
|
73
|
+
description: string;
|
|
74
|
+
category: CapabilityCategory;
|
|
75
|
+
author: string;
|
|
76
|
+
repoUrl: string;
|
|
77
|
+
license: string;
|
|
78
|
+
supportedPlatforms: Array<Platform | 'all'>;
|
|
79
|
+
/** Needs a package.json at the project root (vitest, playwright). */
|
|
80
|
+
requiresNodeInProjectRoot: boolean;
|
|
81
|
+
/** Enabled by default on supported platforms (exclusive categories pick one default). */
|
|
82
|
+
default?: boolean;
|
|
83
|
+
/** Optional extra: selected through --addons, never replaced by a category flag. */
|
|
84
|
+
addon?: boolean;
|
|
85
|
+
install: InstallStep[];
|
|
86
|
+
/** Checks beyond the ones derived from `install` (skill files, binaries). */
|
|
87
|
+
doctor?: DoctorCheck[];
|
|
88
|
+
/** A direct row in the AGENTS.md Task Routing table. */
|
|
89
|
+
routing?: { task: string; read: string };
|
|
90
|
+
/** Pre-refuted shortcuts for the AGENTS.md Anti-Rationalization table. */
|
|
91
|
+
rebuttals?: Array<{ excuse: string; rule: string }>;
|
|
92
|
+
/** Section contributed to a docs/agents/ micro-doc. */
|
|
93
|
+
guide?: { doc: MicroDoc; render: (ctx: GuideContext) => string };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** JSON form of a capability users add in aginit.config.json (`customCapabilities`). */
|
|
97
|
+
export interface CustomCapabilityConfig {
|
|
98
|
+
id: string;
|
|
99
|
+
/** owner/repo installed with the Skills CLI. */
|
|
100
|
+
repo: string;
|
|
101
|
+
/** Skills to install from the repo; omitted or empty installs every skill. */
|
|
102
|
+
skills?: string[];
|
|
103
|
+
name?: string;
|
|
104
|
+
description?: string;
|
|
105
|
+
category?: CapabilityCategory;
|
|
106
|
+
supportedPlatforms?: Array<Platform | 'all'>;
|
|
107
|
+
/** A CLI the skill drives: checked by doctor, never installed by aginit. */
|
|
108
|
+
binary?: { bin: string; install: string };
|
|
109
|
+
}
|