@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
@@ -0,0 +1,134 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { readJsonFile, readTextFile } from '../utils/fs.js';
4
+ import { PackageManager } from '../config/schema.js';
5
+ import { getPackageManagerAdapter } from '../adapters/package-manager.js';
6
+ import { Platform, ProjectCommands, Stack } from '../registry/types.js';
7
+
8
+ export interface DetectedProject {
9
+ /** null when nothing marks the platform (empty directory). */
10
+ platform: Platform | null;
11
+ stack: Stack;
12
+ }
13
+
14
+ const WEB_FRAMEWORKS = [
15
+ 'next',
16
+ 'vite',
17
+ 'astro',
18
+ 'react',
19
+ 'react-dom',
20
+ 'vue',
21
+ 'nuxt',
22
+ 'svelte',
23
+ '@sveltejs/kit',
24
+ '@angular/core',
25
+ 'solid-js',
26
+ '@remix-run/react',
27
+ 'preact'
28
+ ];
29
+
30
+ function entries(dir: string): string[] {
31
+ try {
32
+ return fs.readdirSync(dir);
33
+ } catch {
34
+ return [];
35
+ }
36
+ }
37
+
38
+ const hasXcodeProject = (dir: string) => entries(dir).some((name) => name.endsWith('.xcodeproj'));
39
+
40
+ function gradleFiles(dir: string): string[] {
41
+ return ['build.gradle', 'build.gradle.kts', 'settings.gradle', 'settings.gradle.kts']
42
+ .map((name) => readTextFile(path.join(dir, name)))
43
+ .filter((text): text is string => text !== null);
44
+ }
45
+
46
+ /** Read the project's markers (pubspec.yaml, Package.swift, gradle, package.json, ...). */
47
+ export function detectProject(dir: string): DetectedProject {
48
+ const exists = (file: string) => fs.existsSync(path.join(dir, file));
49
+ const pkg = readJsonFile<Record<string, any>>(path.join(dir, 'package.json'));
50
+ const deps = { ...pkg?.dependencies, ...pkg?.devDependencies };
51
+
52
+ if (exists('pubspec.yaml')) return { platform: 'mobile-native', stack: 'flutter' };
53
+ const expoApp = /"expo"\s*:/.test(readTextFile(path.join(dir, 'app.json')) ?? '');
54
+ if (deps['react-native'] || deps.expo || expoApp)
55
+ return { platform: 'mobile-rn', stack: 'react-native' };
56
+ if (exists('Package.swift') || hasXcodeProject(dir) || hasXcodeProject(path.join(dir, 'ios'))) {
57
+ const manifest = readTextFile(path.join(dir, 'Package.swift')) ?? '';
58
+ const apple = !exists('Package.swift') || /\.(iOS|macOS|tvOS|watchOS|visionOS)\(/.test(manifest);
59
+ return { platform: apple ? 'mobile-native' : 'cli', stack: 'swift' };
60
+ }
61
+ const androidGradle = gradleFiles(path.join(dir, 'android'));
62
+ const rootGradle = gradleFiles(dir);
63
+ if (androidGradle.length > 0 || rootGradle.some((text) => /com\.android|android\s*\{/.test(text)))
64
+ return { platform: 'mobile-native', stack: 'kotlin' };
65
+ if (pkg) {
66
+ const web = WEB_FRAMEWORKS.some((name) => deps[name]);
67
+ return { platform: web ? 'web' : 'cli', stack: 'node' };
68
+ }
69
+ if (rootGradle.length > 0) return { platform: 'backend', stack: 'kotlin' };
70
+ if (exists('Cargo.toml')) return { platform: 'cli', stack: 'rust' };
71
+ if (exists('go.mod')) return { platform: 'backend', stack: 'go' };
72
+ if (exists('pyproject.toml') || exists('requirements.txt'))
73
+ return { platform: 'backend', stack: 'python' };
74
+ return { platform: null, stack: 'unknown' };
75
+ }
76
+
77
+ /**
78
+ * The project's own commands: package scripts for Node projects, the host
79
+ * toolchain otherwise. Commands that would be guesses are left out.
80
+ */
81
+ export function projectCommands(
82
+ dir: string,
83
+ stack: Stack,
84
+ options: { packageManager?: PackageManager; scripts?: Record<string, string> } = {}
85
+ ): ProjectCommands {
86
+ const exists = (file: string) => fs.existsSync(path.join(dir, file));
87
+ switch (stack) {
88
+ case 'node':
89
+ case 'react-native': {
90
+ const scripts =
91
+ options.scripts ??
92
+ readJsonFile<Record<string, any>>(path.join(dir, 'package.json'))?.scripts ??
93
+ {};
94
+ const pm = getPackageManagerAdapter(options.packageManager);
95
+ const run = (name: string) => (Object.hasOwn(scripts, name) ? pm.runCmd(name) : undefined);
96
+ return {
97
+ dev: run('dev'),
98
+ build: run('build'),
99
+ typecheck: run('typecheck'),
100
+ lint: run('lint'),
101
+ test: run('test'),
102
+ unitTest: run('test:unit'),
103
+ e2e: run('test:e2e')
104
+ };
105
+ }
106
+ case 'flutter':
107
+ return { lint: 'flutter analyze', test: 'flutter test' };
108
+ case 'swift':
109
+ return exists('Package.swift')
110
+ ? { build: 'swift build', test: 'swift test' }
111
+ : {
112
+ build: 'xcodebuild build -scheme <Scheme>',
113
+ test: "xcodebuild test -scheme <Scheme> -destination 'platform=iOS Simulator,name=<Device>'"
114
+ };
115
+ case 'kotlin': {
116
+ const nested = fs.existsSync(path.join(dir, 'android'));
117
+ const android = nested || gradleFiles(dir).some((text) => /com\.android|android\s*\{/.test(text));
118
+ const gradlew = nested ? 'cd android && ./gradlew' : './gradlew';
119
+ return {
120
+ build: `${gradlew} build`,
121
+ ...(android ? { lint: `${gradlew} lint` } : {}),
122
+ test: `${gradlew} test`
123
+ };
124
+ }
125
+ case 'rust':
126
+ return { build: 'cargo build', typecheck: 'cargo check', lint: 'cargo clippy', test: 'cargo test' };
127
+ case 'go':
128
+ return { build: 'go build ./...', lint: 'go vet ./...', test: 'go test ./...' };
129
+ case 'python':
130
+ return { test: exists('uv.lock') ? 'uv run pytest' : 'pytest' };
131
+ case 'unknown':
132
+ return {};
133
+ }
134
+ }
package/src/index.ts CHANGED
@@ -14,3 +14,8 @@ export * from './cli/new.js';
14
14
  export * from './cli/init.js';
15
15
  export * from './cli/doctor.js';
16
16
  export * from './cli/wizard.js';
17
+ export * from './registry/index.js';
18
+ export * from './detect/index.js';
19
+ export * from './adapters/agent-docs.js';
20
+ export * from './adapters/e2e.js';
21
+ export * from './cli/capabilities.js';
@@ -4,6 +4,7 @@ import { PresetContext, PresetHandler } from './types.js';
4
4
  import { readPackageManifest } from '../config/validation.js';
5
5
  import { addTestingDependencies, setupTesting, VITEST_VERSION } from './testing.js';
6
6
  import { setupAdr } from './adr.js';
7
+ import { activeIds } from '../registry/index.js';
7
8
 
8
9
  export const cliPreset: PresetHandler = {
9
10
  name: 'cli',
@@ -14,6 +15,11 @@ export const cliPreset: PresetHandler = {
14
15
 
15
16
  const existing = readPackageManifest(targetDir);
16
17
  if (existing) {
18
+ // An existing project keeps its own runner unless Vitest is a selected capability.
19
+ if (!activeIds(ctx.config).has('vitest')) {
20
+ if (ctx.config.docs.adr) setupAdr(targetDir, { dryRun, silent });
21
+ return;
22
+ }
17
23
  const hadVitestScript = Object.values(existing.scripts || {}).some(
18
24
  (script) => typeof script === 'string' && /^vitest(?:\s|$)/.test(script)
19
25
  );
@@ -3,6 +3,7 @@ import { fileExists, writeJsonFile } from '../utils/fs.js';
3
3
  import { PresetContext, PresetHandler } from './types.js';
4
4
  import { addTestingDependencies, setupTesting } from './testing.js';
5
5
  import { setupAdr } from './adr.js';
6
+ import { activeIds } from '../registry/index.js';
6
7
 
7
8
  export const genericPreset: PresetHandler = {
8
9
  name: 'generic',
@@ -10,8 +11,10 @@ export const genericPreset: PresetHandler = {
10
11
  async scaffold(ctx: PresetContext): Promise<void> {
11
12
  const { targetDir, projectName, dryRun, silent } = ctx;
12
13
 
14
+ // Only a Vitest harness needs a root package.json; Flutter, Swift, Kotlin,
15
+ // Rust, Go and Python projects stay free of Node files.
13
16
  const pkgPath = path.join(targetDir, 'package.json');
14
- if (!fileExists(pkgPath)) {
17
+ if (!fileExists(pkgPath) && activeIds(ctx.config).has('vitest')) {
15
18
  const pkg = {
16
19
  name: projectName,
17
20
  version: '0.1.0',
@@ -1,17 +1,24 @@
1
+ import fs from 'node:fs';
1
2
  import path from 'node:path';
2
3
  import { safeWriteFile, fileExists } from '../utils/fs.js';
3
4
  import { PresetContext } from './types.js';
4
5
 
5
6
  export const VITEST_VERSION = '^4.1.11';
6
- export function addTestingDependencies(pkg: Record<string, any>, playwright = false): void {
7
+ export function addTestingDependencies(
8
+ pkg: Record<string, any>,
9
+ playwright = false,
10
+ vitest = true
11
+ ): void {
7
12
  pkg.scripts ??= {};
8
13
  pkg.devDependencies ??= {};
9
- if (!pkg.dependencies?.vitest && !pkg.devDependencies.vitest)
10
- pkg.devDependencies.vitest = VITEST_VERSION;
11
- const customTest = pkg.scripts.test && !/^vitest(?:\s|$)/.test(pkg.scripts.test);
12
- const testScript = customTest ? 'test:unit' : 'test';
13
- pkg.scripts[testScript] ??= 'vitest run';
14
- pkg.scripts[customTest ? 'test:unit:watch' : 'test:watch'] ??= 'vitest';
14
+ if (vitest) {
15
+ if (!pkg.dependencies?.vitest && !pkg.devDependencies.vitest)
16
+ pkg.devDependencies.vitest = VITEST_VERSION;
17
+ const customTest = pkg.scripts.test && !/^vitest(?:\s|$)/.test(pkg.scripts.test);
18
+ const testScript = customTest ? 'test:unit' : 'test';
19
+ pkg.scripts[testScript] ??= 'vitest run';
20
+ pkg.scripts[customTest ? 'test:unit:watch' : 'test:watch'] ??= 'vitest';
21
+ }
15
22
  if (playwright) {
16
23
  pkg.scripts['test:e2e'] ??= 'playwright test';
17
24
  if (!pkg.dependencies?.['@playwright/test'] && !pkg.devDependencies['@playwright/test'])
@@ -39,9 +46,19 @@ export function setupTesting(ctx: PresetContext, preserveViteTestConfig = false)
39
46
  `import { defineConfig } from 'vitest/config';\n\nexport default defineConfig({\n test: { include: ['tests/**/*.test.{js,ts}'] }\n});\n`,
40
47
  { dryRun, silent }
41
48
  );
42
- safeWriteFile(
43
- path.join(targetDir, 'tests', 'aginit.test.js'),
44
- `import { it, expect } from 'vitest';
49
+ // A project that already has tests needs no placeholder.
50
+ let hasTests = false;
51
+ try {
52
+ hasTests = fs
53
+ .readdirSync(path.join(targetDir, 'tests'))
54
+ .some((name) => /\.(test|spec)\.[cm]?[jt]sx?$/.test(name) && name !== 'aginit.test.js');
55
+ } catch {
56
+ // no tests/ directory yet
57
+ }
58
+ if (!hasTests)
59
+ safeWriteFile(
60
+ path.join(targetDir, 'tests', 'aginit.test.js'),
61
+ `import { it, expect } from 'vitest';
45
62
  import { readFileSync } from 'node:fs';
46
63
 
47
64
  // Starter harness: add application behavior tests as the project takes shape.
@@ -51,6 +68,6 @@ it('has a valid project manifest', () => {
51
68
  expect(pkg.name.length).toBeGreaterThan(0);
52
69
  });
53
70
  `,
54
- { dryRun, silent }
55
- );
71
+ { dryRun, silent }
72
+ );
56
73
  }
@@ -1,5 +1,5 @@
1
1
  import path from 'node:path';
2
- import { fileExists, safeWriteFile, writeJsonFile } from '../utils/fs.js';
2
+ import { fileExists, readTextFile, safeWriteFile, writeJsonFile } from '../utils/fs.js';
3
3
  import { readPackageManifest } from '../config/validation.js';
4
4
  import { runExecutable } from '../utils/shell.js';
5
5
  import { PresetContext, PresetHandler } from './types.js';
@@ -7,6 +7,35 @@ import { setupBrowserTesting } from '../adapters/browser.js';
7
7
  import { setupAdr } from './adr.js';
8
8
  import { scaffoldCommand } from '../adapters/package-manager.js';
9
9
  import { addTestingDependencies, setupTesting } from './testing.js';
10
+ import { activeIds } from '../registry/index.js';
11
+
12
+ /** Plain tsc cannot resolve framework virtual modules or single-file components,
13
+ * so those frameworks get their own checker (and the package that provides it). */
14
+ function typecheckFor(
15
+ pkg: Record<string, any>,
16
+ targetDir: string,
17
+ freshNext: boolean
18
+ ): { script: string; checker?: [string, string] } {
19
+ const has = (name: string) => Boolean(pkg.dependencies?.[name] || pkg.devDependencies?.[name]);
20
+ if (freshNext) return { script: 'next typegen && tsc --noEmit' };
21
+ if (has('astro')) return { script: 'astro check', checker: ['@astrojs/check', '^0.9.10'] };
22
+ if (has('nuxt')) return { script: 'nuxi typecheck', checker: ['vue-tsc', '^3.3.13'] };
23
+ if (has('@sveltejs/kit'))
24
+ return {
25
+ script: 'svelte-kit sync && svelte-check --tsconfig ./tsconfig.json',
26
+ checker: ['svelte-check', '^4.7.6']
27
+ };
28
+ if (has('svelte')) return { script: 'svelte-check', checker: ['svelte-check', '^4.7.6'] };
29
+ // A solution-style tsconfig (Vite templates) has no files of its own: only build mode checks
30
+ // the referenced projects.
31
+ const build = /"references"\s*:/.test(readTextFile(path.join(targetDir, 'tsconfig.json')) || '');
32
+ if (has('vue'))
33
+ return {
34
+ script: build ? 'vue-tsc -b --noEmit' : 'vue-tsc --noEmit',
35
+ checker: ['vue-tsc', '^3.3.13']
36
+ };
37
+ return { script: build ? 'tsc -b --noEmit' : 'tsc --noEmit' };
38
+ }
10
39
 
11
40
  export const webPreset: PresetHandler = {
12
41
  name: 'web',
@@ -53,10 +82,15 @@ export const webPreset: PresetHandler = {
53
82
  const hadVitestScript = Object.values(pkg.scripts || {}).some(
54
83
  (script) => typeof script === 'string' && /^vitest(?:\s|$)/.test(script)
55
84
  );
56
- addTestingDependencies(pkg, config.browser.playwright);
85
+ const vitest = activeIds(config).has('vitest');
86
+ addTestingDependencies(pkg, config.browser.playwright, vitest);
57
87
  if (fileExists(path.join(targetDir, 'tsconfig.json'))) {
58
- pkg.scripts.typecheck ??=
59
- fresh && framework === 'next' ? 'next typegen && tsc --noEmit' : 'tsc --noEmit';
88
+ if (pkg.scripts.typecheck === undefined) {
89
+ const { script, checker } = typecheckFor(pkg, targetDir, fresh && framework === 'next');
90
+ pkg.scripts.typecheck = script;
91
+ if (checker && !pkg.dependencies?.[checker[0]] && !pkg.devDependencies[checker[0]])
92
+ pkg.devDependencies[checker[0]] = checker[1];
93
+ }
60
94
  if (!pkg.dependencies?.typescript && !pkg.devDependencies.typescript)
61
95
  pkg.devDependencies.typescript = '^5.6.3';
62
96
  }
@@ -92,7 +126,7 @@ export const webPreset: PresetHandler = {
92
126
  'allowBuilds:\n esbuild: true\n',
93
127
  { dryRun, silent }
94
128
  );
95
- setupTesting(ctx, hadVitestScript);
129
+ if (vitest) setupTesting(ctx, hadVitestScript);
96
130
  if (config.browser.playwright) setupBrowserTesting(targetDir, { dryRun, silent });
97
131
  if (config.docs.adr) setupAdr(targetDir, { dryRun, silent });
98
132
  }