@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.
Files changed (117) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +108 -28
  3. package/THIRD_PARTY_NOTICES.md +92 -16
  4. package/dist/adapters/agent-docs.d.ts +32 -0
  5. package/dist/adapters/agent-docs.d.ts.map +1 -0
  6. package/dist/adapters/agent-docs.js +146 -0
  7. package/dist/adapters/agent-docs.js.map +1 -0
  8. package/dist/adapters/agents-md.d.ts +13 -1
  9. package/dist/adapters/agents-md.d.ts.map +1 -1
  10. package/dist/adapters/agents-md.js +137 -120
  11. package/dist/adapters/agents-md.js.map +1 -1
  12. package/dist/adapters/e2e.d.ts +15 -0
  13. package/dist/adapters/e2e.d.ts.map +1 -0
  14. package/dist/adapters/e2e.js +55 -0
  15. package/dist/adapters/e2e.js.map +1 -0
  16. package/dist/adapters/skills.d.ts +5 -0
  17. package/dist/adapters/skills.d.ts.map +1 -1
  18. package/dist/adapters/skills.js +17 -0
  19. package/dist/adapters/skills.js.map +1 -1
  20. package/dist/adapters/tool-ignores.d.ts +9 -0
  21. package/dist/adapters/tool-ignores.d.ts.map +1 -0
  22. package/dist/adapters/tool-ignores.js +121 -0
  23. package/dist/adapters/tool-ignores.js.map +1 -0
  24. package/dist/cli/capabilities.d.ts +17 -0
  25. package/dist/cli/capabilities.d.ts.map +1 -0
  26. package/dist/cli/capabilities.js +149 -0
  27. package/dist/cli/capabilities.js.map +1 -0
  28. package/dist/cli/doctor.d.ts +16 -0
  29. package/dist/cli/doctor.d.ts.map +1 -1
  30. package/dist/cli/doctor.js +104 -70
  31. package/dist/cli/doctor.js.map +1 -1
  32. package/dist/cli/init.js +1 -1
  33. package/dist/cli/init.js.map +1 -1
  34. package/dist/cli/main.d.ts.map +1 -1
  35. package/dist/cli/main.js +123 -20
  36. package/dist/cli/main.js.map +1 -1
  37. package/dist/cli/new.d.ts +15 -1
  38. package/dist/cli/new.d.ts.map +1 -1
  39. package/dist/cli/new.js +163 -67
  40. package/dist/cli/new.js.map +1 -1
  41. package/dist/cli/wizard.d.ts +5 -0
  42. package/dist/cli/wizard.d.ts.map +1 -1
  43. package/dist/cli/wizard.js +104 -102
  44. package/dist/cli/wizard.js.map +1 -1
  45. package/dist/config/defaults.d.ts +14 -6
  46. package/dist/config/defaults.d.ts.map +1 -1
  47. package/dist/config/defaults.js +38 -174
  48. package/dist/config/defaults.js.map +1 -1
  49. package/dist/config/schema.d.ts +14 -0
  50. package/dist/config/schema.d.ts.map +1 -1
  51. package/dist/config/schema.js.map +1 -1
  52. package/dist/config/validation.d.ts +1 -0
  53. package/dist/config/validation.d.ts.map +1 -1
  54. package/dist/config/validation.js +63 -0
  55. package/dist/config/validation.js.map +1 -1
  56. package/dist/detect/index.d.ts +18 -0
  57. package/dist/detect/index.d.ts.map +1 -0
  58. package/dist/detect/index.js +120 -0
  59. package/dist/detect/index.js.map +1 -0
  60. package/dist/index.d.ts +5 -0
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +5 -0
  63. package/dist/index.js.map +1 -1
  64. package/dist/presets/cli.d.ts.map +1 -1
  65. package/dist/presets/cli.js +7 -0
  66. package/dist/presets/cli.js.map +1 -1
  67. package/dist/presets/generic.d.ts.map +1 -1
  68. package/dist/presets/generic.js +4 -1
  69. package/dist/presets/generic.js.map +1 -1
  70. package/dist/presets/testing.d.ts +1 -1
  71. package/dist/presets/testing.d.ts.map +1 -1
  72. package/dist/presets/testing.js +22 -8
  73. package/dist/presets/testing.js.map +1 -1
  74. package/dist/presets/web.d.ts.map +1 -1
  75. package/dist/presets/web.js +39 -5
  76. package/dist/presets/web.js.map +1 -1
  77. package/dist/registry/catalog.d.ts +13 -0
  78. package/dist/registry/catalog.d.ts.map +1 -0
  79. package/dist/registry/catalog.js +544 -0
  80. package/dist/registry/catalog.js.map +1 -0
  81. package/dist/registry/index.d.ts +84 -0
  82. package/dist/registry/index.d.ts.map +1 -0
  83. package/dist/registry/index.js +309 -0
  84. package/dist/registry/index.js.map +1 -0
  85. package/dist/registry/setup.d.ts +14 -0
  86. package/dist/registry/setup.d.ts.map +1 -0
  87. package/dist/registry/setup.js +25 -0
  88. package/dist/registry/setup.js.map +1 -0
  89. package/dist/registry/types.d.ts +115 -0
  90. package/dist/registry/types.d.ts.map +1 -0
  91. package/dist/registry/types.js +2 -0
  92. package/dist/registry/types.js.map +1 -0
  93. package/package.json +3 -2
  94. package/src/adapters/agent-docs.ts +184 -0
  95. package/src/adapters/agents-md.ts +160 -137
  96. package/src/adapters/e2e.ts +58 -0
  97. package/src/adapters/skills.ts +18 -0
  98. package/src/adapters/tool-ignores.ts +158 -0
  99. package/src/cli/capabilities.ts +176 -0
  100. package/src/cli/doctor.ts +100 -66
  101. package/src/cli/init.ts +1 -1
  102. package/src/cli/main.ts +130 -20
  103. package/src/cli/new.ts +199 -73
  104. package/src/cli/wizard.ts +130 -103
  105. package/src/config/defaults.ts +57 -179
  106. package/src/config/schema.ts +15 -0
  107. package/src/config/validation.ts +56 -0
  108. package/src/detect/index.ts +134 -0
  109. package/src/index.ts +5 -0
  110. package/src/presets/cli.ts +6 -0
  111. package/src/presets/generic.ts +4 -1
  112. package/src/presets/testing.ts +29 -12
  113. package/src/presets/web.ts +39 -5
  114. package/src/registry/catalog.ts +569 -0
  115. package/src/registry/index.ts +395 -0
  116. package/src/registry/setup.ts +40 -0
  117. package/src/registry/types.ts +109 -0
package/src/cli/wizard.ts CHANGED
@@ -3,6 +3,10 @@ import pc from 'picocolors';
3
3
  import { PresetType, WebFramework, PackageManager } from '../config/schema.js';
4
4
  import { validateProjectName, readProjectConfig } from '../config/validation.js';
5
5
  import { detectPackageManager } from '../adapters/package-manager.js';
6
+ import { detectProject } from '../detect/index.js';
7
+ import { presetPlatform } from '../config/defaults.js';
8
+ import { CATALOG, PLATFORMS, defaultCapabilities, isNodeStack, supports } from '../registry/index.js';
9
+ import type { CapabilityCategory, Platform, ProfileFlags, Stack } from '../registry/index.js';
6
10
  import { createProject } from './new.js';
7
11
  import { initCurrentDirectory } from './init.js';
8
12
 
@@ -23,12 +27,14 @@ export interface WizardNewAnswers {
23
27
  preset: PresetType;
24
28
  framework: WebFramework;
25
29
  packageManager: PackageManager;
30
+ profile: ProfileFlags;
26
31
  }
27
32
 
28
33
  export interface WizardInitAnswers {
29
34
  preset: PresetType;
30
35
  framework: WebFramework;
31
36
  packageManager: PackageManager;
37
+ profile: ProfileFlags;
32
38
  }
33
39
 
34
40
  export async function promptNewProject(defaults?: {
@@ -36,6 +42,7 @@ export async function promptNewProject(defaults?: {
36
42
  preset?: PresetType;
37
43
  framework?: WebFramework;
38
44
  packageManager?: PackageManager;
45
+ profile?: ProfileFlags;
39
46
  }): Promise<WizardNewAnswers> {
40
47
  let projectName: string = defaults?.projectName || '';
41
48
  if (!projectName) {
@@ -54,27 +61,10 @@ export async function promptNewProject(defaults?: {
54
61
  projectName = String(assertNotCancelled(res)).trim();
55
62
  }
56
63
 
57
- let preset = defaults?.preset;
58
- if (!preset) {
59
- const res = await p.select({
60
- message: 'Select a project preset:',
61
- initialValue: 'web',
62
- options: [
63
- {
64
- value: 'web',
65
- label: 'Web Application',
66
- hint: 'React, Next.js, Vite, or agnostic + Vitest/Playwright'
67
- },
68
- { value: 'cli', label: 'CLI Tool', hint: 'TypeScript, Commander, Picocolors + Vitest' },
69
- {
70
- value: 'generic',
71
- label: 'Generic / Existing Codebase',
72
- hint: 'Codebase graph + agent intake guide'
73
- }
74
- ]
75
- });
76
- preset = assertNotCancelled(res) as PresetType;
77
- }
64
+ const platform = defaults?.profile?.platform ??
65
+ await promptPlatform(presetPlatform(defaults?.preset ?? 'web'));
66
+ const preset = defaults?.preset ??
67
+ (platform === 'web' ? 'web' : platform === 'cli' ? 'cli' : 'generic');
78
68
 
79
69
  let framework: WebFramework = defaults?.framework || 'none';
80
70
  if (preset === 'web' && defaults?.framework === undefined) {
@@ -84,8 +74,8 @@ export async function promptNewProject(defaults?: {
84
74
  options: [
85
75
  {
86
76
  value: 'none',
87
- label: 'Framework-agnostic (Recommended)',
88
- hint: 'Clean TypeScript + Vitest, zero bundler lock-in'
77
+ label: 'None',
78
+ hint: 'TypeScript baseline'
89
79
  },
90
80
  {
91
81
  value: 'vite',
@@ -100,28 +90,17 @@ export async function promptNewProject(defaults?: {
100
90
 
101
91
  let packageManager = defaults?.packageManager;
102
92
  if (!packageManager) {
103
- const res = await p.select({
104
- message: 'Select package manager:',
105
- initialValue: 'pnpm',
106
- options: [
107
- {
108
- value: 'pnpm',
109
- label: 'pnpm (Recommended)',
110
- hint: 'Fast, disk-efficient & compliant workspace'
111
- },
112
- { value: 'npm', label: 'npm', hint: 'Standard Node.js package manager' },
113
- { value: 'yarn', label: 'yarn', hint: 'Yarn package manager' },
114
- { value: 'bun', label: 'bun', hint: 'Fast all-in-one JavaScript runtime & toolkit' }
115
- ]
116
- });
117
- packageManager = assertNotCancelled(res) as PackageManager;
93
+ packageManager = platform === 'mobile-native' ? 'pnpm' : await promptPackageManager();
118
94
  }
119
95
 
96
+ const profile = await promptProfile(platform, 'unknown', framework, true,
97
+ { ...defaults?.profile, platform });
120
98
  return {
121
99
  projectName,
122
100
  preset,
123
101
  framework,
124
- packageManager
102
+ packageManager,
103
+ profile
125
104
  };
126
105
  }
127
106
 
@@ -130,72 +109,110 @@ export async function promptInitProject(defaults?: {
130
109
  preset?: PresetType;
131
110
  framework?: WebFramework;
132
111
  packageManager?: PackageManager;
112
+ profile?: ProfileFlags;
133
113
  }): Promise<WizardInitAnswers> {
134
114
  const targetDir = defaults?.targetDir || process.cwd();
115
+ const detected = detectProject(targetDir);
116
+ const preset = defaults?.preset ?? 'generic';
117
+ const framework = defaults?.framework ?? 'existing';
118
+ const platform =
119
+ defaults?.profile?.platform ??
120
+ (preset !== 'generic' ? presetPlatform(preset) : detected.platform ?? 'cli');
121
+ const stack = detected.stack;
122
+ p.note(
123
+ stack === 'unknown'
124
+ ? 'No stack detected. Choose a platform below.'
125
+ : `Detected ${stack}. Existing project files are preserved.`,
126
+ 'Project'
127
+ );
128
+ const profile = await promptProfile(
129
+ platform, stack, framework,
130
+ preset !== 'generic' || detected.stack === 'unknown',
131
+ defaults?.profile
132
+ );
133
+ const packageManager =
134
+ defaults?.packageManager ?? detectPackageManager(targetDir) ??
135
+ (isNodeStack(stack) || (stack === 'unknown' && profile.platform !== 'mobile-native')
136
+ ? await promptPackageManager()
137
+ : 'pnpm');
138
+ return { preset, framework, packageManager, profile };
139
+ }
135
140
 
136
- let preset = defaults?.preset;
137
- if (!preset) {
138
- const res = await p.select({
139
- message: 'Select project preset to configure in this directory:',
140
- initialValue: 'web',
141
- options: [
142
- { value: 'web', label: 'Web Application', hint: 'Impeccable UI, agent-browser, Vitest' },
143
- { value: 'cli', label: 'CLI Tool', hint: 'TypeScript, Vitest' },
144
- {
145
- value: 'generic',
146
- label: 'Generic Repository',
147
- hint: 'Codebase graph + agent intake guide'
148
- }
149
- ]
150
- });
151
- preset = assertNotCancelled(res) as PresetType;
152
- }
141
+ async function promptPackageManager(): Promise<PackageManager> {
142
+ return assertNotCancelled(await p.select({
143
+ message: 'Package manager',
144
+ initialValue: 'pnpm',
145
+ options: ['pnpm', 'npm', 'yarn', 'bun'].map((value) => ({ value, label: value }))
146
+ })) as PackageManager;
147
+ }
153
148
 
154
- let framework: WebFramework = defaults?.framework || 'existing';
155
- if (preset === 'web' && defaults?.framework === undefined) {
156
- const res = await p.select({
157
- message: 'Select web framework setting:',
158
- initialValue: 'existing',
149
+ async function promptPlatform(initialValue: Platform): Promise<Platform> {
150
+ const labels: Record<Platform, string> = {
151
+ web: 'Web',
152
+ 'mobile-native': 'Native mobile',
153
+ 'mobile-rn': 'React Native / Expo',
154
+ backend: 'Backend',
155
+ cli: 'CLI / library'
156
+ };
157
+ return assertNotCancelled(await p.select({
158
+ message: 'Platform',
159
+ initialValue,
160
+ options: PLATFORMS.map((value) => ({ value, label: labels[value] }))
161
+ }));
162
+ }
163
+
164
+ async function promptProfile(
165
+ initialPlatform: Platform,
166
+ stack: Stack,
167
+ framework: WebFramework,
168
+ harness: boolean,
169
+ flags: ProfileFlags = {}
170
+ ): Promise<ProfileFlags> {
171
+ const platform = flags.platform ?? await promptPlatform(initialPlatform);
172
+ const resolvedStack = stack === 'unknown' && platform !== 'mobile-native' ? 'node' : stack;
173
+ const defaults = defaultCapabilities({ platform, stack: resolvedStack, framework, harness });
174
+ const choose = async (category: CapabilityCategory, message: string, allowNone = false) => {
175
+ const entries = CATALOG.filter((c) =>
176
+ c.category === category && !c.addon && supports(c, platform) &&
177
+ (!c.requiresNodeInProjectRoot || isNodeStack(resolvedStack))
178
+ );
179
+ return assertNotCancelled(await p.select({
180
+ message,
181
+ initialValue:
182
+ [...defaults].reverse().find((id) => entries.some((c) => c.id === id)) ??
183
+ (allowNone ? 'none' : entries[0]?.id),
159
184
  options: [
160
- {
161
- value: 'existing',
162
- label: 'Existing Web Setup (Recommended)',
163
- hint: 'Preserve existing bundler and dependencies'
164
- },
165
- { value: 'none', label: 'Framework-agnostic', hint: 'Baseline TypeScript' },
166
- { value: 'vite', label: 'Vite', hint: 'Vite React setup' },
167
- { value: 'next', label: 'Next.js', hint: 'Next.js setup' }
185
+ ...entries.map((c) => ({ value: c.id, label: c.name, hint: c.description })),
186
+ ...(allowNone ? [{ value: 'none', label: 'None' }] : [])
168
187
  ]
169
- });
170
- framework = assertNotCancelled(res) as WebFramework;
171
- }
172
-
173
- // Auto-detect and preserve existing package manager without prompting
174
- let packageManager = defaults?.packageManager;
175
- if (!packageManager) {
176
- const detected = detectPackageManager(targetDir);
177
- if (detected) {
178
- packageManager = detected;
179
- } else {
180
- const res = await p.select({
181
- message: 'Select package manager for this project:',
182
- initialValue: 'pnpm',
183
- options: [
184
- { value: 'pnpm', label: 'pnpm (Recommended)', hint: 'Fast, disk-efficient workspace' },
185
- { value: 'npm', label: 'npm', hint: 'Standard Node.js package manager' },
186
- { value: 'yarn', label: 'yarn', hint: 'Yarn package manager' },
187
- { value: 'bun', label: 'bun', hint: 'Fast runtime and package manager' }
188
- ]
189
- });
190
- packageManager = assertNotCancelled(res) as PackageManager;
191
- }
192
- }
193
-
194
- return {
195
- preset,
196
- framework,
197
- packageManager
188
+ }));
198
189
  };
190
+ const methodology = flags.methodology ?? await choose('methodology', 'Methodology');
191
+ const design = flags.design ?? await choose('design', 'Design', true);
192
+ const testing = flags.testing ?? await choose('testing-engine', 'Testing engine');
193
+ const uiDriver = flags.uiDriver ?? await choose('ui-driver', 'UI driver', true);
194
+ const addons = flags.addons ?? assertNotCancelled(await p.multiselect({
195
+ message: 'Add-ons',
196
+ required: false,
197
+ options: CATALOG.filter((c) => c.addon && supports(c, platform) &&
198
+ (!c.requiresNodeInProjectRoot || isNodeStack(resolvedStack)))
199
+ .map((c) => ({ value: c.id, label: c.name, hint: c.description }))
200
+ }));
201
+ return { ...flags, platform, methodology, design, testing: testing as ProfileFlags['testing'], uiDriver, addons };
202
+ }
203
+
204
+ async function confirmSetup(answers: WizardInitAnswers): Promise<boolean> {
205
+ const profile = answers.profile;
206
+ const name = (id: string | undefined) => CATALOG.find((c) => c.id === id)?.name ?? id;
207
+ p.note([
208
+ `Platform: ${profile.platform}`,
209
+ `Methodology: ${name(profile.methodology)}`,
210
+ `Design: ${name(profile.design)}`,
211
+ `Testing: ${name(profile.testing)}`,
212
+ `UI driver: ${name(profile.uiDriver)}`,
213
+ `Add-ons: ${profile.addons?.map(name).join(', ') || 'None'}`
214
+ ].join('\n'), 'Setup');
215
+ return assertNotCancelled(await p.confirm({ message: 'Apply this setup?', initialValue: true }));
199
216
  }
200
217
 
201
218
  export async function runInteractiveWizard(): Promise<void> {
@@ -204,16 +221,16 @@ export async function runInteractiveWizard(): Promise<void> {
204
221
  const action = assertNotCancelled(
205
222
  await p.select({
206
223
  message: 'What would you like to do?',
207
- initialValue: 'new',
224
+ initialValue: detectProject(process.cwd()).platform ? 'init' : 'new',
208
225
  options: [
209
226
  {
210
227
  value: 'new',
211
- label: 'Create a brand new AI-first project',
228
+ label: 'New project',
212
229
  hint: 'Scaffold in a new directory'
213
230
  },
214
231
  {
215
232
  value: 'init',
216
- label: 'Initialize AI capabilities in current directory',
233
+ label: 'Current directory',
217
234
  hint: 'Add AGENTS.md, skills & Graft safely'
218
235
  }
219
236
  ]
@@ -223,11 +240,16 @@ export async function runInteractiveWizard(): Promise<void> {
223
240
  let complete: boolean;
224
241
  if (action === 'new') {
225
242
  const answers = await promptNewProject();
243
+ if (!(await confirmSetup(answers))) {
244
+ p.outro('Setup cancelled.');
245
+ return;
246
+ }
226
247
  p.outro(pc.cyan(`Scaffolding "${answers.projectName}"...`));
227
248
  complete = await createProject(answers.projectName, {
228
249
  preset: answers.preset,
229
250
  framework: answers.framework,
230
- packageManager: answers.packageManager
251
+ packageManager: answers.packageManager,
252
+ profile: answers.profile
231
253
  });
232
254
  } else {
233
255
  const saved = readProjectConfig(process.cwd());
@@ -236,11 +258,16 @@ export async function runInteractiveWizard(): Promise<void> {
236
258
  complete = await initCurrentDirectory();
237
259
  } else {
238
260
  const answers = await promptInitProject();
261
+ if (!(await confirmSetup(answers))) {
262
+ p.outro('Setup cancelled.');
263
+ return;
264
+ }
239
265
  p.outro(pc.cyan('Configuring current directory...'));
240
266
  complete = await initCurrentDirectory({
241
267
  preset: answers.preset,
242
268
  framework: answers.framework,
243
- packageManager: answers.packageManager
269
+ packageManager: answers.packageManager,
270
+ profile: answers.profile
244
271
  });
245
272
  }
246
273
  }
@@ -5,199 +5,77 @@ import {
5
5
  PackageManager,
6
6
  SkillSourceConfig
7
7
  } from './schema.js';
8
+ import {
9
+ applyResolvedProfile,
10
+ isNodeStack,
11
+ MATT_POCOCK_SKILLS,
12
+ Platform,
13
+ ProfileFlags,
14
+ resolveCapabilities,
15
+ Stack
16
+ } from '../registry/index.js';
17
+
18
+ export { MATT_POCOCK_SKILLS };
8
19
 
9
20
  export interface DefaultConfigOptions {
10
21
  framework?: WebFramework;
11
22
  packageManager?: PackageManager;
23
+ /** Defaults to the preset's platform (generic: cli). */
24
+ platform?: Platform;
25
+ /** Defaults to node: every preset scaffolds a Node project unless a stack is detected. */
26
+ stack?: Stack;
27
+ flags?: ProfileFlags;
28
+ /** Skip skill sources (`--no-skills`). */
29
+ skills?: boolean;
30
+ /** false when the preset will not scaffold a Vitest harness. */
31
+ harness?: boolean;
32
+ }
33
+
34
+ export function presetPlatform(preset: PresetType): Platform {
35
+ return preset === 'web' ? 'web' : 'cli';
12
36
  }
13
37
 
14
38
  /**
15
- * The stable skill set from mattpocock/skills installed by default:
16
- * every skill in the `engineering` and `productivity` categories.
17
- *
18
- * The `misc` (rarely used) and `in-progress` categories are deliberately
19
- * excluded. Re-evaluate when the upstream repository reshuffles categories.
39
+ * A fresh config: the preset's scaffolding settings plus the capabilities the
40
+ * registry enables for the platform, stack and profile flags.
20
41
  */
21
- export const MATT_POCOCK_SKILLS = [
22
- // engineering
23
- 'ask-matt',
24
- 'code-review',
25
- 'codebase-design',
26
- 'diagnosing-bugs',
27
- 'domain-modeling',
28
- 'grill-with-docs',
29
- 'implement',
30
- 'implement-spec',
31
- 'improve-codebase-architecture',
32
- 'pr',
33
- 'prototype',
34
- 'research',
35
- 'retro',
36
- 'setup-matt-pocock-skills',
37
- 'tdd',
38
- 'to-spec',
39
- 'to-tickets',
40
- 'triage',
41
- 'wayfinder',
42
- 'wizard',
43
- // productivity
44
- 'grill-me',
45
- 'grilling',
46
- 'handoff',
47
- 'teach',
48
- 'to-questionnaire',
49
- 'wait-what',
50
- 'writing-for-agents'
51
- ] as const;
52
-
53
42
  export function getDefaultConfig(
54
43
  name: string,
55
44
  preset: PresetType,
56
45
  options: DefaultConfigOptions = {}
57
46
  ): AginitConfig {
58
47
  const packageManager = options.packageManager || 'pnpm';
59
-
60
- switch (preset) {
61
- case 'web': {
62
- const framework = options.framework || 'none';
63
- const hasWebTarget = framework === 'vite' || framework === 'next';
64
-
65
- return {
66
- schemaVersion: '1.0.0',
67
- name,
68
- preset: 'web',
69
- framework,
70
- packageManager,
71
- agents: {
72
- primary: 'antigravity',
73
- secondary: 'codex',
74
- claudeCode: true
75
- },
76
- skills: {
77
- sources: [
78
- {
79
- package: 'mattpocock/skills',
80
- skills: [...MATT_POCOCK_SKILLS]
81
- },
82
- {
83
- package: 'blader/humanizer',
84
- skills: ['humanizer']
85
- },
86
- {
87
- package: 'dietrichgebert/ponytail',
88
- skills: ['ponytail']
89
- },
90
- {
91
- package: 'pbakaus/impeccable',
92
- skills: ['impeccable']
93
- },
94
- {
95
- package: 'vercel-labs/agent-browser',
96
- skills: ['agent-browser']
97
- }
98
- ]
99
- },
100
- codebase: {
101
- graft: true
102
- },
103
- browser: {
104
- visualAgent: true,
105
- playwright: hasWebTarget
106
- },
107
- docs: {
108
- adr: true
109
- },
110
- github: {
111
- ci: true
112
- }
113
- };
114
- }
115
-
116
- case 'cli':
117
- return {
118
- schemaVersion: '1.0.0',
119
- name,
120
- preset: 'cli',
121
- packageManager,
122
- agents: {
123
- primary: 'antigravity',
124
- secondary: 'codex',
125
- claudeCode: true
126
- },
127
- skills: {
128
- sources: [
129
- {
130
- package: 'mattpocock/skills',
131
- skills: [...MATT_POCOCK_SKILLS]
132
- },
133
- {
134
- package: 'blader/humanizer',
135
- skills: ['humanizer']
136
- },
137
- {
138
- package: 'dietrichgebert/ponytail',
139
- skills: ['ponytail']
140
- }
141
- ]
142
- },
143
- codebase: {
144
- graft: true
145
- },
146
- browser: {
147
- visualAgent: false,
148
- playwright: false
149
- },
150
- docs: {
151
- adr: true
152
- },
153
- github: {
154
- ci: true
155
- }
156
- };
157
-
158
- case 'generic':
159
- default:
160
- return {
161
- schemaVersion: '1.0.0',
162
- name,
163
- preset: 'generic',
164
- packageManager,
165
- agents: {
166
- primary: 'antigravity',
167
- secondary: 'codex',
168
- claudeCode: true
169
- },
170
- skills: {
171
- sources: [
172
- {
173
- package: 'mattpocock/skills',
174
- skills: [...MATT_POCOCK_SKILLS]
175
- },
176
- {
177
- package: 'blader/humanizer',
178
- skills: ['humanizer']
179
- },
180
- {
181
- package: 'dietrichgebert/ponytail',
182
- skills: ['ponytail']
183
- }
184
- ]
185
- },
186
- codebase: {
187
- graft: true
188
- },
189
- browser: {
190
- visualAgent: false,
191
- playwright: false
192
- },
193
- docs: {
194
- adr: true
195
- },
196
- github: {
197
- ci: false
198
- }
199
- };
200
- }
48
+ const platform = options.platform ?? presetPlatform(preset);
49
+ const stack = options.stack ?? 'node';
50
+ const framework = preset === 'web' ? options.framework || 'none' : undefined;
51
+ const base: AginitConfig = {
52
+ schemaVersion: '1.0.0',
53
+ name,
54
+ preset,
55
+ ...(framework ? { framework } : {}),
56
+ // Only Node projects have a package manager; the others use their host toolchain.
57
+ ...(isNodeStack(stack) ? { packageManager } : {}),
58
+ platform,
59
+ stack,
60
+ agents: {
61
+ primary: 'antigravity',
62
+ secondary: 'codex',
63
+ claudeCode: true
64
+ },
65
+ skills: { sources: [] },
66
+ codebase: { graft: true },
67
+ browser: { visualAgent: false, playwright: false },
68
+ docs: { adr: true },
69
+ github: { ci: preset !== 'generic' }
70
+ };
71
+ const profile = resolveCapabilities({
72
+ platform,
73
+ stack,
74
+ framework,
75
+ harness: options.harness,
76
+ flags: options.flags
77
+ });
78
+ return applyResolvedProfile(base, profile, { skills: options.skills !== false });
201
79
  }
202
80
 
203
81
  /** The skill selections that older versions of aginit wrote as defaults. */
@@ -1,3 +1,5 @@
1
+ import type { CustomCapabilityConfig, Platform, Stack } from '../registry/types.js';
2
+
1
3
  export type PresetType = 'web' | 'cli' | 'generic';
2
4
  export type WebFramework = 'none' | 'vite' | 'next' | 'existing';
3
5
  export type PackageManager = 'pnpm' | 'npm' | 'yarn' | 'bun';
@@ -44,6 +46,19 @@ export interface AginitConfig {
44
46
  github: {
45
47
  ci: boolean;
46
48
  };
49
+ /** Target platform. Missing in pre-v2 configs; detected from the project then. */
50
+ platform?: Platform;
51
+ /** Host toolchain. Missing in pre-v2 configs; detected from the project then. */
52
+ stack?: Stack;
53
+ /**
54
+ * Enabled capability ids from the registry (src/registry/catalog.ts).
55
+ * Missing in pre-v2 configs; inferred from skills.sources and the switches above.
56
+ */
57
+ capabilities?: string[];
58
+ /** Skill repositories added on top of the registry; always enabled unless disabled. */
59
+ customCapabilities?: CustomCapabilityConfig[];
60
+ /** Capability ids to keep off, even when a preset or platform default would enable them. */
61
+ disabledCapabilities?: string[];
47
62
  }
48
63
 
49
64
  // Backward-compatible alias
@@ -1,10 +1,33 @@
1
1
  import path from 'node:path';
2
2
  import { fileExists, readJsonFile } from '../utils/fs.js';
3
3
  import { AginitConfig, AGINIT_CONFIG_FILENAME, LEGACY_CONFIG_FILENAME } from './schema.js';
4
+ import { CATALOG } from '../registry/catalog.js';
5
+ import { PLATFORMS } from '../registry/index.js';
4
6
 
5
7
  export const PRESETS = ['web', 'cli', 'generic'] as const;
6
8
  export const FRAMEWORKS = ['none', 'vite', 'next', 'existing'] as const;
7
9
  export const SUPPORTED_PACKAGE_MANAGERS = ['pnpm', 'npm', 'yarn', 'bun'] as const;
10
+ export const STACKS = [
11
+ 'node',
12
+ 'react-native',
13
+ 'flutter',
14
+ 'swift',
15
+ 'kotlin',
16
+ 'rust',
17
+ 'python',
18
+ 'go',
19
+ 'unknown'
20
+ ] as const;
21
+ const CATEGORIES = [
22
+ 'methodology',
23
+ 'discipline',
24
+ 'design',
25
+ 'codebase-graph',
26
+ 'ui-driver',
27
+ 'testing-engine',
28
+ 'context-guard',
29
+ 'custom'
30
+ ] as const;
8
31
  export function isRecord(value: unknown): value is Record<string, any> {
9
32
  return typeof value === 'object' && value !== null && !Array.isArray(value);
10
33
  }
@@ -55,6 +78,39 @@ export function validateConfig(value: unknown): AginitConfig {
55
78
  validateUpstreamArgument(source.package, 'skill package');
56
79
  for (const skill of source.skills) validateUpstreamArgument(skill, 'skill name');
57
80
  }
81
+ if (value.platform !== undefined && !PLATFORMS.includes(value.platform)) fail('platform');
82
+ if (value.stack !== undefined && !STACKS.includes(value.stack)) fail('stack');
83
+ for (const key of ['capabilities', 'disabledCapabilities']) {
84
+ if (value[key] === undefined) continue;
85
+ if (!Array.isArray(value[key])) fail(key);
86
+ for (const id of value[key]) validateUpstreamArgument(id, `${key} entry`);
87
+ }
88
+ if (value.customCapabilities !== undefined) {
89
+ if (!Array.isArray(value.customCapabilities)) fail('customCapabilities');
90
+ const seen = new Set<string>();
91
+ for (const custom of value.customCapabilities) {
92
+ if (!isRecord(custom)) fail('customCapabilities entry');
93
+ validateUpstreamArgument(custom.id, 'custom capability id');
94
+ if (seen.has(custom.id) || CATALOG.some((entry) => entry.id === custom.id))
95
+ fail(`customCapabilities: duplicate id "${custom.id}"`);
96
+ seen.add(custom.id);
97
+ if (typeof custom.repo !== 'string' || !/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(custom.repo))
98
+ fail(`customCapabilities.${custom.id}.repo (expected owner/repo)`);
99
+ if (custom.skills !== undefined) {
100
+ if (!Array.isArray(custom.skills)) fail(`customCapabilities.${custom.id}.skills`);
101
+ for (const skill of custom.skills) validateUpstreamArgument(skill, 'skill name');
102
+ }
103
+ if (custom.category !== undefined && !CATEGORIES.includes(custom.category))
104
+ fail(`customCapabilities.${custom.id}.category`);
105
+ if (
106
+ custom.binary !== undefined &&
107
+ (!isRecord(custom.binary) ||
108
+ typeof custom.binary.install !== 'string' ||
109
+ !/^[a-zA-Z0-9._-]+$/.test(String(custom.binary.bin)))
110
+ )
111
+ fail(`customCapabilities.${custom.id}.binary`);
112
+ }
113
+ }
58
114
  for (const [section, keys] of Object.entries({
59
115
  codebase: ['graft'],
60
116
  browser: ['visualAgent', 'playwright'],