thachvd-kit 1.0.14 → 1.0.16

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 (203) hide show
  1. package/.agent/agents/backend-specialist.md +263 -0
  2. package/.agent/agents/code-archaeologist.md +106 -0
  3. package/.agent/agents/database-architect.md +226 -0
  4. package/.agent/agents/debugger.md +225 -0
  5. package/.agent/agents/devops-engineer.md +242 -0
  6. package/.agent/agents/documentation-writer.md +104 -0
  7. package/.agent/agents/explorer-agent.md +73 -0
  8. package/.agent/agents/frontend-specialist.md +593 -0
  9. package/.agent/agents/game-developer.md +162 -0
  10. package/.agent/agents/mobile-developer.md +377 -0
  11. package/.agent/agents/orchestrator.md +416 -0
  12. package/.agent/agents/penetration-tester.md +188 -0
  13. package/.agent/agents/performance-optimizer.md +187 -0
  14. package/.agent/agents/product-manager.md +112 -0
  15. package/.agent/agents/product-owner.md +95 -0
  16. package/.agent/agents/project-planner.md +406 -0
  17. package/.agent/agents/qa-automation-engineer.md +103 -0
  18. package/.agent/agents/security-auditor.md +170 -0
  19. package/.agent/agents/seo-specialist.md +111 -0
  20. package/.agent/agents/test-engineer.md +158 -0
  21. package/.agent/docs/architecture.md +22 -0
  22. package/.agent/docs/conventions.md +21 -0
  23. package/.agent/docs/project.md +40 -0
  24. package/.agent/docs/workflow.md +24 -0
  25. package/.agent/rules/GEMINI.md +45 -0
  26. package/.agent/skills/api-design/SKILL.md +156 -0
  27. package/.agent/skills/api-patterns/SKILL.md +81 -0
  28. package/.agent/skills/api-patterns/api-style.md +42 -0
  29. package/.agent/skills/api-patterns/auth.md +24 -0
  30. package/.agent/skills/api-patterns/documentation.md +26 -0
  31. package/.agent/skills/api-patterns/graphql.md +41 -0
  32. package/.agent/skills/api-patterns/rate-limiting.md +31 -0
  33. package/.agent/skills/api-patterns/response.md +37 -0
  34. package/.agent/skills/api-patterns/rest.md +40 -0
  35. package/.agent/skills/api-patterns/scripts/api_validator.py +211 -0
  36. package/.agent/skills/api-patterns/security-testing.md +122 -0
  37. package/.agent/skills/api-patterns/trpc.md +41 -0
  38. package/.agent/skills/api-patterns/versioning.md +22 -0
  39. package/.agent/skills/app-builder/SKILL.md +75 -0
  40. package/.agent/skills/app-builder/agent-coordination.md +71 -0
  41. package/.agent/skills/app-builder/feature-building.md +53 -0
  42. package/.agent/skills/app-builder/project-detection.md +34 -0
  43. package/.agent/skills/app-builder/scaffolding.md +118 -0
  44. package/.agent/skills/app-builder/tech-stack.md +40 -0
  45. package/.agent/skills/app-builder/templates/SKILL.md +39 -0
  46. package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +76 -0
  47. package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +92 -0
  48. package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +88 -0
  49. package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +88 -0
  50. package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +83 -0
  51. package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +90 -0
  52. package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +90 -0
  53. package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +122 -0
  54. package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +122 -0
  55. package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +169 -0
  56. package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +134 -0
  57. package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +83 -0
  58. package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +119 -0
  59. package/.agent/skills/architecture/SKILL.md +55 -0
  60. package/.agent/skills/architecture/context-discovery.md +43 -0
  61. package/.agent/skills/architecture/examples.md +94 -0
  62. package/.agent/skills/architecture/pattern-selection.md +68 -0
  63. package/.agent/skills/architecture/patterns-reference.md +50 -0
  64. package/.agent/skills/architecture/trade-off-analysis.md +77 -0
  65. package/.agent/skills/bash-linux/SKILL.md +199 -0
  66. package/.agent/skills/behavioral-modes/SKILL.md +242 -0
  67. package/.agent/skills/brainstorming/SKILL.md +163 -0
  68. package/.agent/skills/brainstorming/dynamic-questioning.md +350 -0
  69. package/.agent/skills/clean-code/SKILL.md +201 -0
  70. package/.agent/skills/code-review-checklist/SKILL.md +109 -0
  71. package/.agent/skills/database-design/SKILL.md +52 -0
  72. package/.agent/skills/database-design/database-selection.md +43 -0
  73. package/.agent/skills/database-design/indexing.md +39 -0
  74. package/.agent/skills/database-design/migrations.md +48 -0
  75. package/.agent/skills/database-design/optimization.md +36 -0
  76. package/.agent/skills/database-design/orm-selection.md +30 -0
  77. package/.agent/skills/database-design/schema-design.md +56 -0
  78. package/.agent/skills/database-design/scripts/schema_validator.py +172 -0
  79. package/.agent/skills/deployment-procedures/SKILL.md +241 -0
  80. package/.agent/skills/desktop-design/SKILL.md +25 -0
  81. package/.agent/skills/dispatching-parallel-agents/SKILL.md +112 -0
  82. package/.agent/skills/doc.md +177 -0
  83. package/.agent/skills/docker-patterns/SKILL.md +232 -0
  84. package/.agent/skills/documentation-templates/SKILL.md +194 -0
  85. package/.agent/skills/executing-plans/SKILL.md +61 -0
  86. package/.agent/skills/finishing-a-development-branch/SKILL.md +135 -0
  87. package/.agent/skills/frontend-design/SKILL.md +418 -0
  88. package/.agent/skills/frontend-design/animation-guide.md +331 -0
  89. package/.agent/skills/frontend-design/color-system.md +311 -0
  90. package/.agent/skills/frontend-design/decision-trees.md +418 -0
  91. package/.agent/skills/frontend-design/motion-graphics.md +306 -0
  92. package/.agent/skills/frontend-design/scripts/accessibility_checker.py +183 -0
  93. package/.agent/skills/frontend-design/scripts/ux_audit.py +722 -0
  94. package/.agent/skills/frontend-design/typography-system.md +345 -0
  95. package/.agent/skills/frontend-design/ux-psychology.md +1116 -0
  96. package/.agent/skills/frontend-design/visual-effects.md +383 -0
  97. package/.agent/skills/game-development/2d-games/SKILL.md +119 -0
  98. package/.agent/skills/game-development/3d-games/SKILL.md +135 -0
  99. package/.agent/skills/game-development/SKILL.md +167 -0
  100. package/.agent/skills/game-development/game-art/SKILL.md +185 -0
  101. package/.agent/skills/game-development/game-audio/SKILL.md +190 -0
  102. package/.agent/skills/game-development/game-design/SKILL.md +129 -0
  103. package/.agent/skills/game-development/mobile-games/SKILL.md +108 -0
  104. package/.agent/skills/game-development/multiplayer/SKILL.md +132 -0
  105. package/.agent/skills/game-development/pc-games/SKILL.md +144 -0
  106. package/.agent/skills/game-development/vr-ar/SKILL.md +123 -0
  107. package/.agent/skills/game-development/web-games/SKILL.md +150 -0
  108. package/.agent/skills/geo-fundamentals/SKILL.md +156 -0
  109. package/.agent/skills/geo-fundamentals/scripts/geo_checker.py +289 -0
  110. package/.agent/skills/golang-patterns/SKILL.md +226 -0
  111. package/.agent/skills/golang-testing/SKILL.md +182 -0
  112. package/.agent/skills/i18n-localization/SKILL.md +154 -0
  113. package/.agent/skills/i18n-localization/scripts/i18n_checker.py +241 -0
  114. package/.agent/skills/intelligent-routing/SKILL.md +335 -0
  115. package/.agent/skills/laravel-patterns/SKILL.md +224 -0
  116. package/.agent/skills/laravel-security/SKILL.md +171 -0
  117. package/.agent/skills/laravel-tdd/SKILL.md +149 -0
  118. package/.agent/skills/lint-and-validate/SKILL.md +45 -0
  119. package/.agent/skills/lint-and-validate/scripts/lint_runner.py +184 -0
  120. package/.agent/skills/lint-and-validate/scripts/type_coverage.py +173 -0
  121. package/.agent/skills/mcp-builder/SKILL.md +176 -0
  122. package/.agent/skills/mobile-design/SKILL.md +394 -0
  123. package/.agent/skills/mobile-design/decision-trees.md +516 -0
  124. package/.agent/skills/mobile-design/mobile-backend.md +491 -0
  125. package/.agent/skills/mobile-design/mobile-color-system.md +420 -0
  126. package/.agent/skills/mobile-design/mobile-debugging.md +122 -0
  127. package/.agent/skills/mobile-design/mobile-design-thinking.md +357 -0
  128. package/.agent/skills/mobile-design/mobile-navigation.md +458 -0
  129. package/.agent/skills/mobile-design/mobile-performance.md +767 -0
  130. package/.agent/skills/mobile-design/mobile-testing.md +356 -0
  131. package/.agent/skills/mobile-design/mobile-typography.md +433 -0
  132. package/.agent/skills/mobile-design/platform-android.md +666 -0
  133. package/.agent/skills/mobile-design/platform-ios.md +561 -0
  134. package/.agent/skills/mobile-design/scripts/mobile_audit.py +670 -0
  135. package/.agent/skills/mobile-design/touch-psychology.md +537 -0
  136. package/.agent/skills/nextjs-react-expert/1-async-eliminating-waterfalls.md +312 -0
  137. package/.agent/skills/nextjs-react-expert/2-bundle-bundle-size-optimization.md +240 -0
  138. package/.agent/skills/nextjs-react-expert/3-server-server-side-performance.md +490 -0
  139. package/.agent/skills/nextjs-react-expert/4-client-client-side-data-fetching.md +264 -0
  140. package/.agent/skills/nextjs-react-expert/5-rerender-re-render-optimization.md +581 -0
  141. package/.agent/skills/nextjs-react-expert/6-rendering-rendering-performance.md +432 -0
  142. package/.agent/skills/nextjs-react-expert/7-js-javascript-performance.md +684 -0
  143. package/.agent/skills/nextjs-react-expert/8-advanced-advanced-patterns.md +150 -0
  144. package/.agent/skills/nextjs-react-expert/SKILL.md +286 -0
  145. package/.agent/skills/nextjs-react-expert/scripts/convert_rules.py +222 -0
  146. package/.agent/skills/nextjs-react-expert/scripts/react_performance_checker.py +252 -0
  147. package/.agent/skills/nodejs-best-practices/SKILL.md +333 -0
  148. package/.agent/skills/parallel-agents/SKILL.md +175 -0
  149. package/.agent/skills/performance-profiling/SKILL.md +143 -0
  150. package/.agent/skills/performance-profiling/scripts/lighthouse_audit.py +76 -0
  151. package/.agent/skills/plan-writing/SKILL.md +152 -0
  152. package/.agent/skills/powershell-windows/SKILL.md +167 -0
  153. package/.agent/skills/project-onboarding/SKILL.md +42 -0
  154. package/.agent/skills/python-patterns/SKILL.md +441 -0
  155. package/.agent/skills/react-frontend/SKILL.md +25 -0
  156. package/.agent/skills/receiving-code-review/SKILL.md +120 -0
  157. package/.agent/skills/red-team-tactics/SKILL.md +199 -0
  158. package/.agent/skills/requesting-code-review/SKILL.md +67 -0
  159. package/.agent/skills/rust-pro/SKILL.md +176 -0
  160. package/.agent/skills/seo-fundamentals/SKILL.md +129 -0
  161. package/.agent/skills/seo-fundamentals/scripts/seo_checker.py +219 -0
  162. package/.agent/skills/server-management/SKILL.md +161 -0
  163. package/.agent/skills/subagent-driven-development/SKILL.md +76 -0
  164. package/.agent/skills/systematic-debugging/SKILL.md +109 -0
  165. package/.agent/skills/tailwind-patterns/SKILL.md +269 -0
  166. package/.agent/skills/tdd-workflow/SKILL.md +149 -0
  167. package/.agent/skills/testing-patterns/SKILL.md +178 -0
  168. package/.agent/skills/testing-patterns/scripts/test_runner.py +219 -0
  169. package/.agent/skills/using-git-worktrees/SKILL.md +122 -0
  170. package/.agent/skills/verification-before-completion/SKILL.md +99 -0
  171. package/.agent/skills/vulnerability-scanner/SKILL.md +276 -0
  172. package/.agent/skills/vulnerability-scanner/checklists.md +121 -0
  173. package/.agent/skills/vulnerability-scanner/scripts/security_scan.py +458 -0
  174. package/.agent/skills/web-design-guidelines/SKILL.md +57 -0
  175. package/.agent/skills/webapp-testing/SKILL.md +187 -0
  176. package/.agent/skills/webapp-testing/scripts/playwright_runner.py +173 -0
  177. package/.agent/skills/writing-skills/SKILL.md +110 -0
  178. package/.agent/workflows/brainstorm.md +113 -0
  179. package/.agent/workflows/create.md +59 -0
  180. package/.agent/workflows/debug.md +103 -0
  181. package/.agent/workflows/deploy.md +176 -0
  182. package/.agent/workflows/enhance.md +63 -0
  183. package/.agent/workflows/orchestrate.md +237 -0
  184. package/.agent/workflows/plan.md +89 -0
  185. package/.agent/workflows/preview.md +81 -0
  186. package/.agent/workflows/status.md +86 -0
  187. package/.agent/workflows/test.md +144 -0
  188. package/.agent/workflows/ui-ux-pro-max.md +296 -0
  189. package/LICENSE +21 -21
  190. package/README.md +37 -71
  191. package/agents/frontend-specialist.md +2 -2
  192. package/agents/mobile-developer.md +4 -4
  193. package/bin/cli.js +1239 -1326
  194. package/kit/PROMPT_RECIPE.md +30 -52
  195. package/kit/README.md +29 -33
  196. package/package.json +51 -51
  197. package/rules/GEMINI.md +43 -207
  198. package/skills/desktop-design/SKILL.md +25 -25
  199. package/skills/project-onboarding/SKILL.md +42 -62
  200. package/skills/react-frontend/SKILL.md +25 -25
  201. package/PROJECT_CONTEXT.template.md +0 -68
  202. package/kit/PROJECT_CONTEXT.template.md +0 -35
  203. package/scripts/init.py +0 -1047
package/bin/cli.js CHANGED
@@ -1,60 +1,60 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const fs = require('fs');
4
- const path = require('path');
5
- const prompts = require('prompts');
6
- const pc = require('picocolors');
7
- const packageJson = require('../package.json');
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const prompts = require('prompts');
6
+ const pc = require('picocolors');
7
+ const packageJson = require('../package.json');
8
8
 
9
9
  const sourceDir = path.resolve(__dirname, '..');
10
10
  const targetDir = process.cwd();
11
11
 
12
12
  // --- Skill Resolver ---
13
13
 
14
- const LANG_SKILLS = {
15
- javascript: ["nodejs-best-practices", "api-design"],
16
- typescript: ["nodejs-best-practices", "api-design"],
17
- python: ["python-patterns"],
18
- go: ["golang-patterns", "golang-testing"],
19
- php: ["api-design"],
20
- rust: ["rust-pro"],
21
- java: ["api-design"],
22
- ruby: ["api-design"],
23
- swift: ["mobile-design"],
24
- kotlin: ["mobile-design"],
25
- csharp: ["api-design"],
26
- };
27
-
28
- const FRAMEWORK_SKILLS = {
29
- vue: ["frontend-design", "tailwind-patterns"],
30
- react: ["react-frontend", "frontend-design"],
31
- next: ["nextjs-react-expert", "frontend-design"],
32
- nuxt: ["frontend-design"],
33
- svelte: ["frontend-design"],
34
- angular: ["frontend-design"],
35
- laravel: ["laravel-patterns", "laravel-security", "laravel-tdd"],
36
- cakephp: ["api-design"],
37
- symfony: ["api-design"],
38
- codeigniter: ["api-design"],
39
- express: ["nodejs-best-practices", "api-design"],
40
- fastapi: ["python-patterns", "api-design"],
41
- django: ["python-patterns", "api-design"],
42
- flask: ["python-patterns", "api-design"],
43
- gin: ["golang-patterns", "golang-testing"],
44
- fiber: ["golang-patterns", "golang-testing"],
45
- echo: ["golang-patterns", "golang-testing"],
46
- nestjs: ["nodejs-best-practices", "api-design"],
47
- rails: ["api-design"],
48
- spring: ["api-design"],
49
- aspnet: ["api-design"],
50
- tauri: ["rust-pro", "desktop-design"],
51
- axum: ["rust-pro", "api-design"],
52
- actix: ["rust-pro", "api-design"],
53
- flutter: ["mobile-design"],
54
- "react-native": ["mobile-design"],
55
- };
56
-
57
- const INFRA_SKILLS = {
14
+ const LANG_SKILLS = {
15
+ javascript: ["nodejs-best-practices", "api-design"],
16
+ typescript: ["nodejs-best-practices", "api-design"],
17
+ python: ["python-patterns"],
18
+ go: ["golang-patterns", "golang-testing"],
19
+ php: ["api-design"],
20
+ rust: ["rust-pro"],
21
+ java: ["api-design"],
22
+ ruby: ["api-design"],
23
+ swift: ["mobile-design"],
24
+ kotlin: ["mobile-design"],
25
+ csharp: ["api-design"],
26
+ };
27
+
28
+ const FRAMEWORK_SKILLS = {
29
+ vue: ["frontend-design", "tailwind-patterns"],
30
+ react: ["react-frontend", "frontend-design"],
31
+ next: ["nextjs-react-expert", "frontend-design"],
32
+ nuxt: ["frontend-design"],
33
+ svelte: ["frontend-design"],
34
+ angular: ["frontend-design"],
35
+ laravel: ["laravel-patterns", "laravel-security", "laravel-tdd"],
36
+ cakephp: ["api-design"],
37
+ symfony: ["api-design"],
38
+ codeigniter: ["api-design"],
39
+ express: ["nodejs-best-practices", "api-design"],
40
+ fastapi: ["python-patterns", "api-design"],
41
+ django: ["python-patterns", "api-design"],
42
+ flask: ["python-patterns", "api-design"],
43
+ gin: ["golang-patterns", "golang-testing"],
44
+ fiber: ["golang-patterns", "golang-testing"],
45
+ echo: ["golang-patterns", "golang-testing"],
46
+ nestjs: ["nodejs-best-practices", "api-design"],
47
+ rails: ["api-design"],
48
+ spring: ["api-design"],
49
+ aspnet: ["api-design"],
50
+ tauri: ["rust-pro", "desktop-design"],
51
+ axum: ["rust-pro", "api-design"],
52
+ actix: ["rust-pro", "api-design"],
53
+ flutter: ["mobile-design"],
54
+ "react-native": ["mobile-design"],
55
+ };
56
+
57
+ const INFRA_SKILLS = {
58
58
  docker: ["docker-patterns"],
59
59
  aws: ["deployment-procedures", "docker-patterns"],
60
60
  gcp: ["deployment-procedures", "docker-patterns"],
@@ -87,716 +87,723 @@ function resolveSkills(data) {
87
87
  skills.add("clean-code");
88
88
  skills.add("systematic-debugging");
89
89
 
90
- return Array.from(skills).sort();
91
- }
92
-
93
- const FRAMEWORK_CHOICES = [
94
- 'vue', 'react', 'next', 'nuxt', 'svelte', 'angular',
95
- 'laravel', 'cakephp', 'symfony', 'codeigniter',
96
- 'express', 'fastapi', 'django', 'flask', 'gin', 'fiber', 'echo', 'nestjs',
97
- 'rails', 'spring', 'aspnet',
98
- 'tauri', 'axum', 'actix', 'flutter', 'react-native'
99
- ];
100
-
101
- const LANGUAGE_CHOICES = ['typescript', 'javascript', 'python', 'go', 'php', 'rust', 'java', 'ruby', 'swift', 'kotlin', 'csharp', 'cpp'];
102
- const PROJECT_TYPE_CHOICES = ['web-app', 'api', 'mobile', 'desktop', 'cli', 'fullstack'];
103
- const DATABASE_CHOICES = ['postgres', 'mysql', 'sqlite', 'mongodb', 'redis', 'dynamodb', 'none'];
104
- const PACKAGE_MANAGER_CHOICES = ['npm', 'pnpm', 'yarn', 'bun', 'pip', 'uv', 'go mod', 'cargo', 'composer', 'auto-detect'];
105
- const TEST_FRAMEWORK_CHOICES = ['jest', 'vitest', 'pytest', 'go test', 'phpunit', 'pest', 'rspec', 'junit', 'auto-detect'];
106
- const CLOUD_CHOICES = ['aws', 'gcp', 'azure', 'vercel', 'fly', 'railway', 'none'];
107
- const FRONTEND_FRAMEWORKS = ['vue', 'react', 'next', 'nuxt', 'svelte', 'angular'];
108
- const BACKEND_FRAMEWORKS = ['laravel', 'cakephp', 'symfony', 'codeigniter', 'express', 'fastapi', 'django', 'flask', 'gin', 'fiber', 'echo', 'nestjs', 'rails', 'spring', 'aspnet', 'axum', 'actix'];
109
- const MOBILE_DESKTOP_FRAMEWORKS = ['tauri', 'flutter', 'react-native'];
110
- const IGNORED_SCAN_DIRS = new Set(['.git', 'node_modules', 'vendor', 'dist', 'build', 'coverage', '.next', '.nuxt', '.turbo', '.cache', 'storage', 'tmp']);
111
-
112
- function printHelp() {
113
- console.log(`
114
- ${pc.bold('Usage:')}
115
- thachvd-kit init [--scan] [--yes]
116
- thachvd-kit --version
117
- thachvd-kit --help
118
-
119
- ${pc.bold('Commands:')}
120
- init Run the project init wizard
121
-
122
- ${pc.bold('Options:')}
123
- --scan Scan the current project to prefill PROJECT_CONTEXT.md
124
- --yes Use detected/default values without prompting
125
- --version Show the installed CLI version
126
- --help Show this help message
127
- `);
128
- }
129
-
130
- function formatScanPreviewLines(scanned) {
131
- return [
132
- ['App root', scanned.app_root || '.'],
133
- ['Project type', scanned.project_type || 'unknown'],
134
- ['Primary language', scanned.primary_language || 'unknown'],
135
- ['Frameworks', (scanned.frameworks || []).join(', ') || 'no framework'],
136
- ['Database', scanned.database || 'none'],
137
- ['Package manager', scanned.package_manager || 'auto-detect'],
138
- ['Test framework', scanned.test_framework || 'auto-detect'],
139
- ['Cloud', scanned.cloud || 'none'],
140
- ['Infrastructure', (scanned.infrastructure || []).join(', ') || 'none'],
141
- ['CI/CD', scanned.ci_cd || 'none']
142
- ];
143
- }
144
-
145
- async function askQuestions(questions) {
146
- return prompts(questions, {
147
- onCancel: () => {
148
- console.log(pc.yellow('\nCancelled.'));
149
- process.exit(130);
150
- }
151
- });
152
- }
153
-
154
- function fileExists(...parts) {
155
- return fs.existsSync(path.join(targetDir, ...parts));
156
- }
157
-
158
- function fileExistsAt(baseDir, ...parts) {
159
- return fs.existsSync(path.join(baseDir, ...parts));
160
- }
161
-
162
- function readJsonIfExists(...parts) {
163
- const fullPath = path.join(targetDir, ...parts);
164
- if (!fs.existsSync(fullPath)) return null;
165
- try {
166
- return JSON.parse(fs.readFileSync(fullPath, 'utf8'));
167
- } catch {
168
- return null;
169
- }
170
- }
171
-
172
- function readJsonIfExistsAt(baseDir, ...parts) {
173
- const fullPath = path.join(baseDir, ...parts);
174
- if (!fs.existsSync(fullPath)) return null;
175
- try {
176
- return JSON.parse(fs.readFileSync(fullPath, 'utf8'));
177
- } catch {
178
- return null;
179
- }
180
- }
181
-
182
- function readTextIfExists(...parts) {
183
- const fullPath = path.join(targetDir, ...parts);
184
- if (!fs.existsSync(fullPath)) return '';
185
- try {
186
- return fs.readFileSync(fullPath, 'utf8');
187
- } catch {
188
- return '';
189
- }
190
- }
191
-
192
- function readTextIfExistsAt(baseDir, ...parts) {
193
- const fullPath = path.join(baseDir, ...parts);
194
- if (!fs.existsSync(fullPath)) return '';
195
- try {
196
- return fs.readFileSync(fullPath, 'utf8');
197
- } catch {
198
- return '';
199
- }
200
- }
201
-
202
- function detectPackageManager(baseDir = targetDir) {
203
- if (fileExistsAt(baseDir, 'pnpm-lock.yaml')) return 'pnpm';
204
- if (fileExistsAt(baseDir, 'yarn.lock')) return 'yarn';
205
- if (fileExistsAt(baseDir, 'bun.lockb') || fileExistsAt(baseDir, 'bun.lock')) return 'bun';
206
- if (fileExistsAt(baseDir, 'package-lock.json')) return 'npm';
207
- if (fileExistsAt(baseDir, 'uv.lock')) return 'uv';
208
- if (fileExistsAt(baseDir, 'poetry.lock')) return 'pip';
209
- if (fileExistsAt(baseDir, 'requirements.txt') || fileExistsAt(baseDir, 'pyproject.toml')) return 'pip';
210
- if (fileExistsAt(baseDir, 'go.mod')) return 'go mod';
211
- if (fileExistsAt(baseDir, 'Cargo.toml')) return 'cargo';
212
- if (fileExistsAt(baseDir, 'composer.json')) return 'composer';
213
- return 'auto-detect';
214
- }
215
-
216
- function uniq(values) {
217
- return Array.from(new Set(values.filter(Boolean)));
218
- }
219
-
220
- function detectAppRoot(baseDir) {
221
- const candidates = [baseDir];
222
- const queue = [{ dir: baseDir, depth: 0 }];
223
-
224
- while (queue.length > 0) {
225
- const { dir, depth } = queue.shift();
226
- if (depth >= 2) continue;
227
- let entries = [];
228
- try {
229
- entries = fs.readdirSync(dir, { withFileTypes: true });
230
- } catch {
231
- continue;
232
- }
233
- for (const entry of entries) {
234
- if (!entry.isDirectory()) continue;
235
- if (IGNORED_SCAN_DIRS.has(entry.name)) continue;
236
- const nextDir = path.join(dir, entry.name);
237
- candidates.push(nextDir);
238
- queue.push({ dir: nextDir, depth: depth + 1 });
239
- }
240
- }
241
-
242
- const scoreCandidate = dir => {
243
- let score = 0;
244
- if (fileExistsAt(dir, 'composer.json')) score += 5;
245
- if (fileExistsAt(dir, 'package.json')) score += 4;
246
- if (fileExistsAt(dir, 'Cargo.toml')) score += 4;
247
- if (fileExistsAt(dir, 'go.mod')) score += 4;
248
- if (fileExistsAt(dir, 'pyproject.toml') || fileExistsAt(dir, 'requirements.txt')) score += 4;
249
- if (fileExistsAt(dir, 'artisan')) score += 5;
250
- if (fileExistsAt(dir, 'bin', 'cake') || fileExistsAt(dir, 'app', 'Config', 'core.php')) score += 5;
251
- if (fileExistsAt(dir, 'src-tauri', 'tauri.conf.json') || fileExistsAt(dir, 'src-tauri', 'tauri.conf.json5')) score += 5;
252
- if (fileExistsAt(dir, 'manage.py')) score += 4;
253
- if (fileExistsAt(dir, 'Gemfile')) score += 4;
254
- if (fileExistsAt(dir, 'pom.xml') || fileExistsAt(dir, 'build.gradle') || fileExistsAt(dir, 'build.gradle.kts')) score += 4;
255
- if (fileExistsAt(dir, 'app', 'View') || fileExistsAt(dir, 'resources', 'views') || fileExistsAt(dir, 'templates') || fileExistsAt(dir, 'app', 'views')) score += 2;
256
- return score;
257
- };
258
-
259
- const ranked = candidates
260
- .map(dir => ({ dir, score: scoreCandidate(dir) }))
261
- .filter(item => item.score > 0)
262
- .sort((a, b) => (b.score - a.score) || (a.dir.length - b.dir.length));
263
-
264
- return ranked[0]?.dir || baseDir;
265
- }
266
-
267
- function repoHasAny(baseDir, relativePaths, maxDepth = 3) {
268
- const queue = [{ dir: baseDir, depth: 0 }];
269
- while (queue.length > 0) {
270
- const { dir, depth } = queue.shift();
271
- for (const relativePath of relativePaths) {
272
- if (fileExistsAt(dir, ...relativePath)) return true;
273
- }
274
- if (depth >= maxDepth) continue;
275
- let entries = [];
276
- try {
277
- entries = fs.readdirSync(dir, { withFileTypes: true });
278
- } catch {
279
- continue;
280
- }
281
- for (const entry of entries) {
282
- if (!entry.isDirectory()) continue;
283
- if (IGNORED_SCAN_DIRS.has(entry.name)) continue;
284
- queue.push({ dir: path.join(dir, entry.name), depth: depth + 1 });
285
- }
286
- }
287
- return false;
288
- }
289
-
290
- function evidenceIf(condition, label, weight = 1) {
291
- return condition ? { label, weight } : null;
292
- }
293
-
294
- function buildDetection(id, evidence, meta = {}) {
295
- const hits = evidence.filter(Boolean);
296
- const score = hits.reduce((sum, hit) => sum + hit.weight, 0);
297
- return {
298
- id,
299
- score,
300
- evidence: hits.map(hit => hit.label),
301
- ...meta
302
- };
303
- }
304
-
305
- function confidenceLabel(score) {
306
- if (score >= 6) return 'high';
307
- if (score >= 3) return 'medium';
308
- return 'low';
309
- }
310
-
311
- function hasDependency(depNames, ...names) {
312
- return names.some(name => depNames.has(name));
313
- }
314
-
315
- function hasComposerDependency(composerDepNames, ...names) {
316
- return names.some(name => composerDepNames.has(name));
317
- }
318
-
319
- function hasRootFile(rootEntries, ...filenames) {
320
- return filenames.some(name => rootEntries.has(name));
321
- }
322
-
323
- function textMatches(text, pattern) {
324
- return Boolean(text && pattern.test(text));
325
- }
326
-
327
- const LANGUAGE_DETECTORS = [
328
- {
329
- id: 'typescript',
330
- detect: ctx => buildDetection('typescript', [
331
- evidenceIf(ctx.fileExists('tsconfig.json'), 'found tsconfig.json', 4),
332
- evidenceIf(hasDependency(ctx.depNames, 'typescript', 'ts-node', 'tsx'), 'package.json -> typescript toolchain', 2)
333
- ])
334
- },
335
- {
336
- id: 'javascript',
337
- detect: ctx => buildDetection('javascript', [
338
- evidenceIf(ctx.fileExists('package.json'), 'found package.json', 2),
339
- evidenceIf(ctx.depNames.size > 0, 'package.json -> JavaScript dependency graph', 1)
340
- ])
341
- },
342
- {
343
- id: 'python',
344
- detect: ctx => buildDetection('python', [
345
- evidenceIf(ctx.fileExists('requirements.txt'), 'found requirements.txt', 3),
346
- evidenceIf(ctx.fileExists('pyproject.toml'), 'found pyproject.toml', 2),
347
- evidenceIf(textMatches(ctx.pyprojectText, /\[tool\.(poetry|uv|hatch|pdm)\]/i), 'pyproject.toml -> Python tool section', 2)
348
- ])
349
- },
350
- {
351
- id: 'go',
352
- detect: ctx => buildDetection('go', [
353
- evidenceIf(ctx.fileExists('go.mod'), 'found go.mod', 4)
354
- ])
355
- },
356
- {
357
- id: 'php',
358
- detect: ctx => buildDetection('php', [
359
- evidenceIf(ctx.fileExists('composer.json'), 'found composer.json', 4),
360
- evidenceIf(ctx.composerDepNames.size > 0, 'composer.json -> PHP dependency graph', 1)
361
- ])
362
- },
363
- {
364
- id: 'rust',
365
- detect: ctx => buildDetection('rust', [
366
- evidenceIf(ctx.fileExists('Cargo.toml'), 'found Cargo.toml', 4),
367
- evidenceIf(ctx.fileExists('src-tauri', 'Cargo.toml'), 'found src-tauri/Cargo.toml', 5)
368
- ])
369
- },
370
- {
371
- id: 'java',
372
- detect: ctx => buildDetection('java', [
373
- evidenceIf(ctx.fileExists('pom.xml'), 'found pom.xml', 4),
374
- evidenceIf(ctx.fileExists('build.gradle') || ctx.fileExists('build.gradle.kts'), 'found Gradle build file', 3)
375
- ])
376
- },
377
- {
378
- id: 'ruby',
379
- detect: ctx => buildDetection('ruby', [
380
- evidenceIf(ctx.fileExists('Gemfile'), 'found Gemfile', 4),
381
- evidenceIf(ctx.fileExists('config.ru'), 'found config.ru', 1)
382
- ])
383
- },
384
- {
385
- id: 'csharp',
386
- detect: ctx => buildDetection('csharp', [
387
- evidenceIf(ctx.rootEntries.some(name => name.endsWith('.sln') || name.endsWith('.csproj')), 'found .sln/.csproj', 4),
388
- evidenceIf(ctx.fileExists('Program.cs'), 'found Program.cs', 1)
389
- ])
390
- }
391
- ];
392
-
393
- const FRAMEWORK_DETECTORS = [
394
- {
395
- id: 'next',
396
- kind: 'frontend',
397
- projectType: 'web-app',
398
- detect: ctx => buildDetection('next', [
399
- evidenceIf(hasDependency(ctx.depNames, 'next'), 'package.json -> next', 4),
400
- evidenceIf(ctx.fileExists('next.config.js') || ctx.fileExists('next.config.mjs') || ctx.fileExists('next.config.ts'), 'found next.config.*', 3),
401
- evidenceIf(ctx.fileExists('app') || ctx.fileExists('pages'), 'found app/ or pages/', 1)
402
- ])
403
- },
404
- {
405
- id: 'react',
406
- kind: 'frontend',
407
- projectType: 'web-app',
408
- detect: ctx => buildDetection('react', [
409
- evidenceIf(hasDependency(ctx.depNames, 'react'), 'package.json -> react', 4),
410
- evidenceIf(hasDependency(ctx.depNames, 'react-dom'), 'package.json -> react-dom', 2)
411
- ])
412
- },
413
- {
414
- id: 'vue',
415
- kind: 'frontend',
416
- projectType: 'web-app',
417
- detect: ctx => buildDetection('vue', [
418
- evidenceIf(hasDependency(ctx.depNames, 'vue'), 'package.json -> vue', 4)
419
- ])
420
- },
421
- {
422
- id: 'nuxt',
423
- kind: 'frontend',
424
- projectType: 'web-app',
425
- detect: ctx => buildDetection('nuxt', [
426
- evidenceIf(hasDependency(ctx.depNames, 'nuxt'), 'package.json -> nuxt', 4),
427
- evidenceIf(ctx.fileExists('nuxt.config.ts') || ctx.fileExists('nuxt.config.js'), 'found nuxt.config.*', 3)
428
- ])
429
- },
430
- {
431
- id: 'svelte',
432
- kind: 'frontend',
433
- projectType: 'web-app',
434
- detect: ctx => buildDetection('svelte', [
435
- evidenceIf(hasDependency(ctx.depNames, 'svelte', '@sveltejs/kit'), 'package.json -> svelte', 4)
436
- ])
437
- },
438
- {
439
- id: 'angular',
440
- kind: 'frontend',
441
- projectType: 'web-app',
442
- detect: ctx => buildDetection('angular', [
443
- evidenceIf(hasDependency(ctx.depNames, '@angular/core'), 'package.json -> @angular/core', 4),
444
- evidenceIf(ctx.fileExists('angular.json'), 'found angular.json', 3)
445
- ])
446
- },
447
- {
448
- id: 'express',
449
- kind: 'backend',
450
- language: 'javascript',
451
- projectType: 'api',
452
- detect: ctx => buildDetection('express', [
453
- evidenceIf(hasDependency(ctx.depNames, 'express'), 'package.json -> express', 4)
454
- ])
455
- },
456
- {
457
- id: 'nestjs',
458
- kind: 'backend',
459
- language: 'javascript',
460
- projectType: 'api',
461
- detect: ctx => buildDetection('nestjs', [
462
- evidenceIf(hasDependency(ctx.depNames, '@nestjs/core'), 'package.json -> @nestjs/core', 4)
463
- ])
464
- },
465
- {
466
- id: 'fastapi',
467
- kind: 'backend',
468
- language: 'python',
469
- projectType: 'api',
470
- detect: ctx => buildDetection('fastapi', [
471
- evidenceIf(textMatches(ctx.requirementsText, /\bfastapi\b/i) || textMatches(ctx.pyprojectText, /\bfastapi\b/i), 'Python deps -> fastapi', 4)
472
- ])
473
- },
474
- {
475
- id: 'django',
476
- kind: 'backend',
477
- language: 'python',
478
- projectType: 'web-app',
479
- detect: ctx => buildDetection('django', [
480
- evidenceIf(textMatches(ctx.requirementsText, /\bdjango\b/i) || textMatches(ctx.pyprojectText, /\bdjango\b/i), 'Python deps -> django', 4),
481
- evidenceIf(ctx.fileExists('manage.py'), 'found manage.py', 2),
482
- evidenceIf(ctx.fileExists('templates'), 'found templates/', 1)
483
- ])
484
- },
485
- {
486
- id: 'flask',
487
- kind: 'backend',
488
- language: 'python',
489
- projectType: 'api',
490
- detect: ctx => buildDetection('flask', [
491
- evidenceIf(textMatches(ctx.requirementsText, /\bflask\b/i) || textMatches(ctx.pyprojectText, /\bflask\b/i), 'Python deps -> flask', 4)
492
- ])
493
- },
494
- {
495
- id: 'laravel',
496
- kind: 'backend',
497
- language: 'php',
498
- projectType: 'web-app',
499
- detect: ctx => buildDetection('laravel', [
500
- evidenceIf(hasComposerDependency(ctx.composerDepNames, 'laravel/framework'), 'composer.json -> laravel/framework', 4),
501
- evidenceIf(ctx.fileExists('artisan'), 'found artisan', 3),
502
- evidenceIf(ctx.fileExists('resources', 'views'), 'found resources/views', 1)
503
- ])
504
- },
505
- {
506
- id: 'cakephp',
507
- kind: 'backend',
508
- language: 'php',
509
- projectType: 'web-app',
510
- detect: ctx => buildDetection('cakephp', [
511
- evidenceIf(hasComposerDependency(ctx.composerDepNames, 'cakephp/cakephp'), 'composer.json -> cakephp/cakephp', 4),
512
- evidenceIf(ctx.fileExists('bin', 'cake'), 'found bin/cake', 3),
513
- evidenceIf(ctx.fileExists('config', 'app.php'), 'found config/app.php', 2),
514
- evidenceIf(ctx.fileExists('app', 'Config', 'core.php'), 'found app/Config/core.php (CakePHP legacy)', 3),
515
- evidenceIf(ctx.fileExists('app', 'Controller', 'AppController.php'), 'found app/Controller/AppController.php', 2),
516
- evidenceIf(ctx.fileExists('app', 'Model', 'AppModel.php'), 'found app/Model/AppModel.php', 2),
517
- evidenceIf(ctx.fileExists('lib', 'Cake'), 'found lib/Cake', 3),
518
- evidenceIf(ctx.fileExists('cake', 'console', 'cake'), 'found cake/console/cake', 3),
519
- evidenceIf(ctx.fileExists('app', 'View'), 'found app/View', 2),
520
- evidenceIf(textMatches(ctx.viewFilesText, /\.ctp$/im), 'found .ctp view templates', 2)
521
- ])
522
- },
523
- {
524
- id: 'symfony',
525
- kind: 'backend',
526
- language: 'php',
527
- projectType: 'web-app',
528
- detect: ctx => buildDetection('symfony', [
529
- evidenceIf(hasComposerDependency(ctx.composerDepNames, 'symfony/framework-bundle'), 'composer.json -> symfony/framework-bundle', 4),
530
- evidenceIf(ctx.fileExists('bin', 'console'), 'found bin/console', 2),
531
- evidenceIf(ctx.fileExists('config', 'bundles.php'), 'found config/bundles.php', 1),
532
- evidenceIf(ctx.fileExists('templates'), 'found templates/', 1)
533
- ])
534
- },
535
- {
536
- id: 'codeigniter',
537
- kind: 'backend',
538
- language: 'php',
539
- projectType: 'api',
540
- detect: ctx => buildDetection('codeigniter', [
541
- evidenceIf(hasComposerDependency(ctx.composerDepNames, 'codeigniter4/framework'), 'composer.json -> codeigniter4/framework', 4),
542
- evidenceIf(ctx.fileExists('spark'), 'found spark', 2),
543
- evidenceIf(ctx.fileExists('app', 'Config', 'App.php'), 'found app/Config/App.php', 1)
544
- ])
545
- },
546
- {
547
- id: 'gin',
548
- kind: 'backend',
549
- language: 'go',
550
- projectType: 'api',
551
- detect: ctx => buildDetection('gin', [
552
- evidenceIf(textMatches(ctx.goModText, /\bgithub\.com\/gin-gonic\/gin\b/i), 'go.mod -> github.com/gin-gonic/gin', 4)
553
- ])
554
- },
555
- {
556
- id: 'fiber',
557
- kind: 'backend',
558
- language: 'go',
559
- projectType: 'api',
560
- detect: ctx => buildDetection('fiber', [
561
- evidenceIf(textMatches(ctx.goModText, /\bgithub\.com\/gofiber\/fiber\b/i), 'go.mod -> github.com/gofiber/fiber', 4)
562
- ])
563
- },
564
- {
565
- id: 'echo',
566
- kind: 'backend',
567
- language: 'go',
568
- projectType: 'api',
569
- detect: ctx => buildDetection('echo', [
570
- evidenceIf(textMatches(ctx.goModText, /\bgithub\.com\/labstack\/echo\b/i), 'go.mod -> github.com/labstack/echo', 4)
571
- ])
572
- },
573
- {
574
- id: 'rails',
575
- kind: 'backend',
576
- language: 'ruby',
577
- projectType: 'web-app',
578
- detect: ctx => buildDetection('rails', [
579
- evidenceIf(textMatches(ctx.gemfileText, /^\s*gem\s+["']rails["']/im), 'Gemfile -> rails', 4),
580
- evidenceIf(ctx.fileExists('bin', 'rails'), 'found bin/rails', 2),
581
- evidenceIf(ctx.fileExists('app', 'views'), 'found app/views', 1)
582
- ])
583
- },
584
- {
585
- id: 'spring',
586
- kind: 'backend',
587
- language: 'java',
588
- projectType: 'api',
589
- detect: ctx => buildDetection('spring', [
590
- evidenceIf(textMatches(ctx.pomText, /spring-boot/i) || textMatches(ctx.gradleText, /spring-boot/i), 'Java build -> spring-boot', 4)
591
- ])
592
- },
593
- {
594
- id: 'aspnet',
595
- kind: 'backend',
596
- language: 'csharp',
597
- projectType: 'api',
598
- detect: ctx => buildDetection('aspnet', [
599
- evidenceIf(textMatches(ctx.csprojText, /Microsoft\.NET\.Sdk\.Web/i), '.csproj -> Microsoft.NET.Sdk.Web', 4),
600
- evidenceIf(textMatches(ctx.programCsText, /WebApplication\.CreateBuilder/i), 'Program.cs -> WebApplication.CreateBuilder', 2)
601
- ])
602
- },
603
- {
604
- id: 'tauri',
605
- kind: 'desktop',
606
- language: 'rust',
607
- projectType: 'desktop',
608
- detect: ctx => buildDetection('tauri', [
609
- evidenceIf(hasDependency(ctx.depNames, '@tauri-apps/api'), 'package.json -> @tauri-apps/api', 4),
610
- evidenceIf(ctx.fileExists('src-tauri', 'tauri.conf.json') || ctx.fileExists('src-tauri', 'tauri.conf.json5'), 'found src-tauri/tauri.conf.*', 4),
611
- evidenceIf(ctx.fileExists('src-tauri', 'Cargo.toml'), 'found src-tauri/Cargo.toml', 2)
612
- ])
613
- },
614
- {
615
- id: 'axum',
616
- kind: 'backend',
617
- language: 'rust',
618
- projectType: 'api',
619
- detect: ctx => buildDetection('axum', [
620
- evidenceIf(textMatches(ctx.cargoText, /\baxum\b/i), 'Cargo.toml -> axum', 4)
621
- ])
622
- },
623
- {
624
- id: 'actix',
625
- kind: 'backend',
626
- language: 'rust',
627
- projectType: 'api',
628
- detect: ctx => buildDetection('actix', [
629
- evidenceIf(textMatches(ctx.cargoText, /\bactix-web\b/i), 'Cargo.toml -> actix-web', 4)
630
- ])
631
- },
632
- {
633
- id: 'flutter',
634
- kind: 'mobile',
635
- projectType: 'mobile',
636
- detect: ctx => buildDetection('flutter', [
637
- evidenceIf(textMatches(ctx.pubspecText, /^\s*flutter\s*:/im), 'pubspec.yaml -> flutter', 4)
638
- ])
639
- },
640
- {
641
- id: 'react-native',
642
- kind: 'mobile',
643
- projectType: 'mobile',
644
- detect: ctx => buildDetection('react-native', [
645
- evidenceIf(hasDependency(ctx.depNames, 'react-native', 'expo'), 'package.json -> react-native/expo', 4)
646
- ])
647
- }
648
- ];
649
-
650
- function scanProject() {
651
- const appRoot = detectAppRoot(targetDir);
652
- const pkg = readJsonIfExistsAt(appRoot, 'package.json') || {};
653
- const composer = readJsonIfExistsAt(appRoot, 'composer.json') || {};
654
- const rootEntries = fs.existsSync(appRoot) ? fs.readdirSync(appRoot) : [];
655
- const rootCsproj = rootEntries.find(name => name.endsWith('.csproj')) || '';
656
- const deps = {
657
- ...(pkg.dependencies || {}),
658
- ...(pkg.devDependencies || {}),
659
- ...(pkg.peerDependencies || {})
660
- };
661
- const depNames = new Set(Object.keys(deps));
662
- const composerDeps = {
663
- ...(composer.require || {}),
664
- ...(composer['require-dev'] || {})
665
- };
666
- const composerDepNames = new Set(Object.keys(composerDeps));
667
- const ctx = {
668
- pkg,
669
- composer,
670
- depNames,
671
- composerDepNames,
672
- rootEntries,
673
- fileExists: (...parts) => fileExistsAt(appRoot, ...parts),
674
- requirementsText: readTextIfExistsAt(appRoot, 'requirements.txt'),
675
- pyprojectText: readTextIfExistsAt(appRoot, 'pyproject.toml'),
676
- goModText: readTextIfExistsAt(appRoot, 'go.mod'),
677
- pubspecText: readTextIfExistsAt(appRoot, 'pubspec.yaml'),
678
- gemfileText: readTextIfExistsAt(appRoot, 'Gemfile'),
679
- pomText: readTextIfExistsAt(appRoot, 'pom.xml'),
680
- gradleText: `${readTextIfExistsAt(appRoot, 'build.gradle')}\n${readTextIfExistsAt(appRoot, 'build.gradle.kts')}`,
681
- cargoText: `${readTextIfExistsAt(appRoot, 'Cargo.toml')}\n${readTextIfExistsAt(appRoot, 'src-tauri', 'Cargo.toml')}`,
682
- csprojText: rootCsproj ? readTextIfExistsAt(appRoot, rootCsproj) : '',
683
- programCsText: readTextIfExistsAt(appRoot, 'Program.cs'),
684
- viewFilesText: fs.existsSync(path.join(appRoot, 'app', 'View'))
685
- ? fs.readdirSync(path.join(appRoot, 'app', 'View'), { recursive: true }).join('\n')
686
- : ''
687
- };
688
-
689
- const languageDetections = LANGUAGE_DETECTORS
690
- .map(detector => detector.detect(ctx))
691
- .filter(result => result.score >= 2)
692
- .sort((a, b) => b.score - a.score);
693
-
694
- const frameworkDetails = FRAMEWORK_DETECTORS
695
- .map(detector => {
696
- const result = detector.detect(ctx);
697
- if (result.score < 3) return null;
698
- return {
699
- ...result,
700
- kind: detector.kind,
701
- language: detector.language,
702
- projectType: detector.projectType,
703
- confidence: confidenceLabel(result.score)
704
- };
705
- })
706
- .filter(Boolean)
707
- .sort((a, b) => b.score - a.score);
708
-
709
- const frameworkLanguageScores = frameworkDetails.reduce((acc, item) => {
710
- if (!item.language) return acc;
711
- acc[item.language] = (acc[item.language] || 0) + item.score;
712
- return acc;
713
- }, {});
714
- const primaryLanguage = Object.keys(frameworkLanguageScores).sort((a, b) => frameworkLanguageScores[b] - frameworkLanguageScores[a])[0]
715
- || languageDetections[0]?.id
716
- || 'typescript';
717
- const frameworks = uniq(frameworkDetails.map(item => item.id)).filter(name => FRAMEWORK_CHOICES.includes(name));
718
- const infrastructure = [];
719
- let projectType = '';
720
- let database = 'none';
721
- let testFramework = 'auto-detect';
722
- let cloud = 'none';
723
- let ciCd = 'none';
724
-
725
- if (frameworkDetails.some(item => item.projectType === 'desktop')) projectType = 'desktop';
726
- else if (frameworkDetails.some(item => item.projectType === 'mobile')) projectType = 'mobile';
727
- else if (frameworkDetails.some(item => item.kind === 'frontend') && frameworkDetails.some(item => item.kind === 'backend')) projectType = 'fullstack';
728
- else if (frameworkDetails.some(item => item.projectType === 'web-app')) projectType = 'web-app';
729
- else if (frameworkDetails.some(item => item.kind === 'backend')) projectType = 'api';
730
- else if (frameworkDetails.some(item => item.kind === 'frontend')) projectType = 'web-app';
731
- else if (fileExistsAt(appRoot, 'package.json') && pkg.bin) projectType = 'cli';
732
- else if (['php', 'python', 'go', 'java', 'ruby', 'csharp', 'rust'].includes(primaryLanguage)) projectType = 'api';
733
-
734
- if (hasDependency(depNames, 'pg', 'postgres', '@prisma/client') || /postgres/i.test(JSON.stringify(pkg))) database = 'postgres';
735
- else if (hasDependency(depNames, 'mysql', 'mysql2')) database = 'mysql';
736
- else if (hasDependency(depNames, 'sqlite3', 'better-sqlite3', 'rusqlite')) database = 'sqlite';
737
- else if (hasDependency(depNames, 'mongodb', 'mongoose')) database = 'mongodb';
738
- else if (hasDependency(depNames, 'redis', 'ioredis')) database = 'redis';
739
- else if (hasDependency(depNames, '@aws-sdk/client-dynamodb')) database = 'dynamodb';
740
-
741
- if (hasDependency(depNames, 'vitest')) testFramework = 'vitest';
742
- else if (hasDependency(depNames, 'jest')) testFramework = 'jest';
743
- else if (hasDependency(depNames, 'pytest') || textMatches(ctx.requirementsText, /\bpytest\b/i) || textMatches(ctx.pyprojectText, /\bpytest\b/i)) testFramework = 'pytest';
744
- else if (hasComposerDependency(composerDepNames, 'phpunit/phpunit')) testFramework = 'phpunit';
745
- else if (hasComposerDependency(composerDepNames, 'pestphp/pest')) testFramework = 'pest';
746
- else if (fileExistsAt(appRoot, 'go.mod')) testFramework = 'go test';
747
- else if (textMatches(ctx.gemfileText, /^\s*gem\s+["']rspec["']/im)) testFramework = 'rspec';
748
-
749
- if (repoHasAny(targetDir, [['Dockerfile'], ['docker-compose.yml'], ['docker-compose.yaml']])) {
750
- infrastructure.push('docker');
751
- }
752
-
753
- if (repoHasAny(targetDir, [['vercel.json']])) {
754
- cloud = 'vercel';
755
- infrastructure.push('vercel');
756
- } else if (repoHasAny(targetDir, [['fly.toml']])) {
757
- cloud = 'fly';
758
- infrastructure.push('fly');
759
- } else if (repoHasAny(targetDir, [['railway.json'], ['railway.toml']])) {
760
- cloud = 'railway';
761
- infrastructure.push('railway');
762
- }
763
-
764
- if (repoHasAny(targetDir, [['.github', 'workflows'], ['.gitlab-ci.yml'], ['.circleci', 'config.yml'], ['Jenkinsfile']], 2)) {
765
- if (repoHasAny(targetDir, [['.github', 'workflows']], 2)) ciCd = 'github-actions';
766
- else if (repoHasAny(targetDir, [['.gitlab-ci.yml']], 2)) ciCd = 'gitlab-ci';
767
- else if (repoHasAny(targetDir, [['.circleci', 'config.yml']], 2)) ciCd = 'circleci';
768
- else if (repoHasAny(targetDir, [['Jenkinsfile']], 2)) ciCd = 'jenkins';
769
- }
770
-
771
- const scanEvidence = [
772
- ...(languageDetections[0] ? [`language:${languageDetections[0].id} => ${languageDetections[0].evidence.join('; ')}`] : []),
773
- ...frameworkDetails.map(item => `${item.id} [${item.confidence}] => ${item.evidence.join('; ')}`)
774
- ];
775
-
776
- return {
777
- project_name: path.basename(targetDir),
778
- app_root: path.relative(targetDir, appRoot) || '.',
779
- description: pkg.description || composer.description || 'A software project',
780
- project_type: PROJECT_TYPE_CHOICES.includes(projectType) ? projectType : 'web-app',
781
- primary_language: LANGUAGE_CHOICES.includes(primaryLanguage) ? primaryLanguage : 'typescript',
782
- frameworks,
783
- framework_details: frameworkDetails,
784
- scan_evidence: scanEvidence,
785
- database: DATABASE_CHOICES.includes(database) ? database : 'none',
786
- package_manager: PACKAGE_MANAGER_CHOICES.includes(detectPackageManager(appRoot)) ? detectPackageManager(appRoot) : 'auto-detect',
787
- uses_docker: infrastructure.includes('docker'),
788
- cloud,
789
- infrastructure: uniq(infrastructure),
790
- ci_cd: ciCd,
791
- test_framework: TEST_FRAMEWORK_CHOICES.includes(testFramework) ? testFramework : 'auto-detect',
792
- max_file_lines: '300',
793
- code_style: 'project-standard',
794
- architecture_notes: '<!-- Add notes -->',
795
- current_phase: 'Initial setup',
796
- next_phase: '—',
797
- known_issues: '<!-- None yet -->'
798
- };
799
- }
90
+ return Array.from(skills).sort();
91
+ }
92
+
93
+ const FRAMEWORK_CHOICES = [
94
+ 'vue', 'react', 'next', 'nuxt', 'svelte', 'angular',
95
+ 'laravel', 'cakephp', 'symfony', 'codeigniter',
96
+ 'express', 'fastapi', 'django', 'flask', 'gin', 'fiber', 'echo', 'nestjs',
97
+ 'rails', 'spring', 'aspnet',
98
+ 'tauri', 'axum', 'actix', 'flutter', 'react-native'
99
+ ];
100
+
101
+ const LANGUAGE_CHOICES = ['typescript', 'javascript', 'python', 'go', 'php', 'rust', 'java', 'ruby', 'swift', 'kotlin', 'csharp', 'cpp'];
102
+ const PROJECT_TYPE_CHOICES = ['web-app', 'api', 'mobile', 'desktop', 'cli', 'fullstack'];
103
+ const DATABASE_CHOICES = ['postgres', 'mysql', 'sqlite', 'mongodb', 'redis', 'dynamodb', 'none'];
104
+ const PACKAGE_MANAGER_CHOICES = ['npm', 'pnpm', 'yarn', 'bun', 'pip', 'uv', 'go mod', 'cargo', 'composer', 'auto-detect'];
105
+ const TEST_FRAMEWORK_CHOICES = ['jest', 'vitest', 'pytest', 'go test', 'phpunit', 'pest', 'rspec', 'junit', 'auto-detect'];
106
+ const CLOUD_CHOICES = ['aws', 'gcp', 'azure', 'vercel', 'fly', 'railway', 'none'];
107
+ const FRONTEND_FRAMEWORKS = ['vue', 'react', 'next', 'nuxt', 'svelte', 'angular'];
108
+ const BACKEND_FRAMEWORKS = ['laravel', 'cakephp', 'symfony', 'codeigniter', 'express', 'fastapi', 'django', 'flask', 'gin', 'fiber', 'echo', 'nestjs', 'rails', 'spring', 'aspnet', 'axum', 'actix'];
109
+ const MOBILE_DESKTOP_FRAMEWORKS = ['tauri', 'flutter', 'react-native'];
110
+ const IGNORED_SCAN_DIRS = new Set(['.git', 'node_modules', 'vendor', 'dist', 'build', 'coverage', '.next', '.nuxt', '.turbo', '.cache', 'storage', 'tmp']);
111
+
112
+ function printHelp() {
113
+ console.log(`
114
+ ${pc.bold('Usage:')}
115
+ thachvd-kit init [--scan] [--yes]
116
+ thachvd-kit --version
117
+ thachvd-kit --help
118
+
119
+ ${pc.bold('Commands:')}
120
+ init Run the project init wizard
121
+
122
+ ${pc.bold('Options:')}
123
+ --scan Scan the current project to auto-detect stack
124
+ --yes Use detected/default values without prompting
125
+ --version Show the installed CLI version
126
+ --help Show this help message
127
+
128
+ ${pc.bold('What gets generated:')}
129
+ AGENTS.md Shared instructions for Codex, Antigravity, and Claude Code
130
+ CLAUDE.md Claude Code entry file that imports AGENTS.md
131
+ GEMINI.md Antigravity entry file that points to AGENTS.md
132
+ .agent/ Shared docs, skills, workflows, agents, and rules
133
+ `);
134
+
135
+ }
136
+
137
+ function formatScanPreviewLines(scanned) {
138
+ return [
139
+ ['App root', scanned.app_root || '.'],
140
+ ['Project type', scanned.project_type || 'unknown'],
141
+ ['Primary language', scanned.primary_language || 'unknown'],
142
+ ['Frameworks', (scanned.frameworks || []).join(', ') || 'no framework'],
143
+ ['Database', scanned.database || 'none'],
144
+ ['Package manager', scanned.package_manager || 'auto-detect'],
145
+ ['Test framework', scanned.test_framework || 'auto-detect'],
146
+ ['Cloud', scanned.cloud || 'none'],
147
+ ['Infrastructure', (scanned.infrastructure || []).join(', ') || 'none'],
148
+ ['CI/CD', scanned.ci_cd || 'none']
149
+ ];
150
+ }
151
+
152
+ async function askQuestions(questions) {
153
+ return prompts(questions, {
154
+ onCancel: () => {
155
+ console.log(pc.yellow('\nCancelled.'));
156
+ process.exit(130);
157
+ }
158
+ });
159
+ }
160
+
161
+ function fileExists(...parts) {
162
+ return fs.existsSync(path.join(targetDir, ...parts));
163
+ }
164
+
165
+ function fileExistsAt(baseDir, ...parts) {
166
+ return fs.existsSync(path.join(baseDir, ...parts));
167
+ }
168
+
169
+ function readJsonIfExists(...parts) {
170
+ const fullPath = path.join(targetDir, ...parts);
171
+ if (!fs.existsSync(fullPath)) return null;
172
+ try {
173
+ return JSON.parse(fs.readFileSync(fullPath, 'utf8'));
174
+ } catch {
175
+ return null;
176
+ }
177
+ }
178
+
179
+ function readJsonIfExistsAt(baseDir, ...parts) {
180
+ const fullPath = path.join(baseDir, ...parts);
181
+ if (!fs.existsSync(fullPath)) return null;
182
+ try {
183
+ return JSON.parse(fs.readFileSync(fullPath, 'utf8'));
184
+ } catch {
185
+ return null;
186
+ }
187
+ }
188
+
189
+ function readTextIfExists(...parts) {
190
+ const fullPath = path.join(targetDir, ...parts);
191
+ if (!fs.existsSync(fullPath)) return '';
192
+ try {
193
+ return fs.readFileSync(fullPath, 'utf8');
194
+ } catch {
195
+ return '';
196
+ }
197
+ }
198
+
199
+ function readTextIfExistsAt(baseDir, ...parts) {
200
+ const fullPath = path.join(baseDir, ...parts);
201
+ if (!fs.existsSync(fullPath)) return '';
202
+ try {
203
+ return fs.readFileSync(fullPath, 'utf8');
204
+ } catch {
205
+ return '';
206
+ }
207
+ }
208
+
209
+ function detectPackageManager(baseDir = targetDir) {
210
+ if (fileExistsAt(baseDir, 'pnpm-lock.yaml')) return 'pnpm';
211
+ if (fileExistsAt(baseDir, 'yarn.lock')) return 'yarn';
212
+ if (fileExistsAt(baseDir, 'bun.lockb') || fileExistsAt(baseDir, 'bun.lock')) return 'bun';
213
+ if (fileExistsAt(baseDir, 'package-lock.json')) return 'npm';
214
+ if (fileExistsAt(baseDir, 'uv.lock')) return 'uv';
215
+ if (fileExistsAt(baseDir, 'poetry.lock')) return 'pip';
216
+ if (fileExistsAt(baseDir, 'requirements.txt') || fileExistsAt(baseDir, 'pyproject.toml')) return 'pip';
217
+ if (fileExistsAt(baseDir, 'go.mod')) return 'go mod';
218
+ if (fileExistsAt(baseDir, 'Cargo.toml')) return 'cargo';
219
+ if (fileExistsAt(baseDir, 'composer.json')) return 'composer';
220
+ return 'auto-detect';
221
+ }
222
+
223
+ function uniq(values) {
224
+ return Array.from(new Set(values.filter(Boolean)));
225
+ }
226
+
227
+ function detectAppRoot(baseDir) {
228
+ const candidates = [baseDir];
229
+ const queue = [{ dir: baseDir, depth: 0 }];
230
+
231
+ while (queue.length > 0) {
232
+ const { dir, depth } = queue.shift();
233
+ if (depth >= 2) continue;
234
+ let entries = [];
235
+ try {
236
+ entries = fs.readdirSync(dir, { withFileTypes: true });
237
+ } catch {
238
+ continue;
239
+ }
240
+ for (const entry of entries) {
241
+ if (!entry.isDirectory()) continue;
242
+ if (IGNORED_SCAN_DIRS.has(entry.name)) continue;
243
+ const nextDir = path.join(dir, entry.name);
244
+ candidates.push(nextDir);
245
+ queue.push({ dir: nextDir, depth: depth + 1 });
246
+ }
247
+ }
248
+
249
+ const scoreCandidate = dir => {
250
+ let score = 0;
251
+ if (fileExistsAt(dir, 'composer.json')) score += 5;
252
+ if (fileExistsAt(dir, 'package.json')) score += 4;
253
+ if (fileExistsAt(dir, 'Cargo.toml')) score += 4;
254
+ if (fileExistsAt(dir, 'go.mod')) score += 4;
255
+ if (fileExistsAt(dir, 'pyproject.toml') || fileExistsAt(dir, 'requirements.txt')) score += 4;
256
+ if (fileExistsAt(dir, 'artisan')) score += 5;
257
+ if (fileExistsAt(dir, 'bin', 'cake') || fileExistsAt(dir, 'app', 'Config', 'core.php')) score += 5;
258
+ if (fileExistsAt(dir, 'src-tauri', 'tauri.conf.json') || fileExistsAt(dir, 'src-tauri', 'tauri.conf.json5')) score += 5;
259
+ if (fileExistsAt(dir, 'manage.py')) score += 4;
260
+ if (fileExistsAt(dir, 'Gemfile')) score += 4;
261
+ if (fileExistsAt(dir, 'pom.xml') || fileExistsAt(dir, 'build.gradle') || fileExistsAt(dir, 'build.gradle.kts')) score += 4;
262
+ if (fileExistsAt(dir, 'app', 'View') || fileExistsAt(dir, 'resources', 'views') || fileExistsAt(dir, 'templates') || fileExistsAt(dir, 'app', 'views')) score += 2;
263
+ return score;
264
+ };
265
+
266
+ const ranked = candidates
267
+ .map(dir => ({ dir, score: scoreCandidate(dir) }))
268
+ .filter(item => item.score > 0)
269
+ .sort((a, b) => (b.score - a.score) || (a.dir.length - b.dir.length));
270
+
271
+ return ranked[0]?.dir || baseDir;
272
+ }
273
+
274
+ function repoHasAny(baseDir, relativePaths, maxDepth = 3) {
275
+ const queue = [{ dir: baseDir, depth: 0 }];
276
+ while (queue.length > 0) {
277
+ const { dir, depth } = queue.shift();
278
+ for (const relativePath of relativePaths) {
279
+ if (fileExistsAt(dir, ...relativePath)) return true;
280
+ }
281
+ if (depth >= maxDepth) continue;
282
+ let entries = [];
283
+ try {
284
+ entries = fs.readdirSync(dir, { withFileTypes: true });
285
+ } catch {
286
+ continue;
287
+ }
288
+ for (const entry of entries) {
289
+ if (!entry.isDirectory()) continue;
290
+ if (IGNORED_SCAN_DIRS.has(entry.name)) continue;
291
+ queue.push({ dir: path.join(dir, entry.name), depth: depth + 1 });
292
+ }
293
+ }
294
+ return false;
295
+ }
296
+
297
+ function evidenceIf(condition, label, weight = 1) {
298
+ return condition ? { label, weight } : null;
299
+ }
300
+
301
+ function buildDetection(id, evidence, meta = {}) {
302
+ const hits = evidence.filter(Boolean);
303
+ const score = hits.reduce((sum, hit) => sum + hit.weight, 0);
304
+ return {
305
+ id,
306
+ score,
307
+ evidence: hits.map(hit => hit.label),
308
+ ...meta
309
+ };
310
+ }
311
+
312
+ function confidenceLabel(score) {
313
+ if (score >= 6) return 'high';
314
+ if (score >= 3) return 'medium';
315
+ return 'low';
316
+ }
317
+
318
+ function hasDependency(depNames, ...names) {
319
+ return names.some(name => depNames.has(name));
320
+ }
321
+
322
+ function hasComposerDependency(composerDepNames, ...names) {
323
+ return names.some(name => composerDepNames.has(name));
324
+ }
325
+
326
+ function hasRootFile(rootEntries, ...filenames) {
327
+ return filenames.some(name => rootEntries.has(name));
328
+ }
329
+
330
+ function textMatches(text, pattern) {
331
+ return Boolean(text && pattern.test(text));
332
+ }
333
+
334
+ const LANGUAGE_DETECTORS = [
335
+ {
336
+ id: 'typescript',
337
+ detect: ctx => buildDetection('typescript', [
338
+ evidenceIf(ctx.fileExists('tsconfig.json'), 'found tsconfig.json', 4),
339
+ evidenceIf(hasDependency(ctx.depNames, 'typescript', 'ts-node', 'tsx'), 'package.json -> typescript toolchain', 2)
340
+ ])
341
+ },
342
+ {
343
+ id: 'javascript',
344
+ detect: ctx => buildDetection('javascript', [
345
+ evidenceIf(ctx.fileExists('package.json'), 'found package.json', 2),
346
+ evidenceIf(ctx.depNames.size > 0, 'package.json -> JavaScript dependency graph', 1)
347
+ ])
348
+ },
349
+ {
350
+ id: 'python',
351
+ detect: ctx => buildDetection('python', [
352
+ evidenceIf(ctx.fileExists('requirements.txt'), 'found requirements.txt', 3),
353
+ evidenceIf(ctx.fileExists('pyproject.toml'), 'found pyproject.toml', 2),
354
+ evidenceIf(textMatches(ctx.pyprojectText, /\[tool\.(poetry|uv|hatch|pdm)\]/i), 'pyproject.toml -> Python tool section', 2)
355
+ ])
356
+ },
357
+ {
358
+ id: 'go',
359
+ detect: ctx => buildDetection('go', [
360
+ evidenceIf(ctx.fileExists('go.mod'), 'found go.mod', 4)
361
+ ])
362
+ },
363
+ {
364
+ id: 'php',
365
+ detect: ctx => buildDetection('php', [
366
+ evidenceIf(ctx.fileExists('composer.json'), 'found composer.json', 4),
367
+ evidenceIf(ctx.composerDepNames.size > 0, 'composer.json -> PHP dependency graph', 1)
368
+ ])
369
+ },
370
+ {
371
+ id: 'rust',
372
+ detect: ctx => buildDetection('rust', [
373
+ evidenceIf(ctx.fileExists('Cargo.toml'), 'found Cargo.toml', 4),
374
+ evidenceIf(ctx.fileExists('src-tauri', 'Cargo.toml'), 'found src-tauri/Cargo.toml', 5)
375
+ ])
376
+ },
377
+ {
378
+ id: 'java',
379
+ detect: ctx => buildDetection('java', [
380
+ evidenceIf(ctx.fileExists('pom.xml'), 'found pom.xml', 4),
381
+ evidenceIf(ctx.fileExists('build.gradle') || ctx.fileExists('build.gradle.kts'), 'found Gradle build file', 3)
382
+ ])
383
+ },
384
+ {
385
+ id: 'ruby',
386
+ detect: ctx => buildDetection('ruby', [
387
+ evidenceIf(ctx.fileExists('Gemfile'), 'found Gemfile', 4),
388
+ evidenceIf(ctx.fileExists('config.ru'), 'found config.ru', 1)
389
+ ])
390
+ },
391
+ {
392
+ id: 'csharp',
393
+ detect: ctx => buildDetection('csharp', [
394
+ evidenceIf(ctx.rootEntries.some(name => name.endsWith('.sln') || name.endsWith('.csproj')), 'found .sln/.csproj', 4),
395
+ evidenceIf(ctx.fileExists('Program.cs'), 'found Program.cs', 1)
396
+ ])
397
+ }
398
+ ];
399
+
400
+ const FRAMEWORK_DETECTORS = [
401
+ {
402
+ id: 'next',
403
+ kind: 'frontend',
404
+ projectType: 'web-app',
405
+ detect: ctx => buildDetection('next', [
406
+ evidenceIf(hasDependency(ctx.depNames, 'next'), 'package.json -> next', 4),
407
+ evidenceIf(ctx.fileExists('next.config.js') || ctx.fileExists('next.config.mjs') || ctx.fileExists('next.config.ts'), 'found next.config.*', 3),
408
+ evidenceIf(ctx.fileExists('app') || ctx.fileExists('pages'), 'found app/ or pages/', 1)
409
+ ])
410
+ },
411
+ {
412
+ id: 'react',
413
+ kind: 'frontend',
414
+ projectType: 'web-app',
415
+ detect: ctx => buildDetection('react', [
416
+ evidenceIf(hasDependency(ctx.depNames, 'react'), 'package.json -> react', 4),
417
+ evidenceIf(hasDependency(ctx.depNames, 'react-dom'), 'package.json -> react-dom', 2)
418
+ ])
419
+ },
420
+ {
421
+ id: 'vue',
422
+ kind: 'frontend',
423
+ projectType: 'web-app',
424
+ detect: ctx => buildDetection('vue', [
425
+ evidenceIf(hasDependency(ctx.depNames, 'vue'), 'package.json -> vue', 4)
426
+ ])
427
+ },
428
+ {
429
+ id: 'nuxt',
430
+ kind: 'frontend',
431
+ projectType: 'web-app',
432
+ detect: ctx => buildDetection('nuxt', [
433
+ evidenceIf(hasDependency(ctx.depNames, 'nuxt'), 'package.json -> nuxt', 4),
434
+ evidenceIf(ctx.fileExists('nuxt.config.ts') || ctx.fileExists('nuxt.config.js'), 'found nuxt.config.*', 3)
435
+ ])
436
+ },
437
+ {
438
+ id: 'svelte',
439
+ kind: 'frontend',
440
+ projectType: 'web-app',
441
+ detect: ctx => buildDetection('svelte', [
442
+ evidenceIf(hasDependency(ctx.depNames, 'svelte', '@sveltejs/kit'), 'package.json -> svelte', 4)
443
+ ])
444
+ },
445
+ {
446
+ id: 'angular',
447
+ kind: 'frontend',
448
+ projectType: 'web-app',
449
+ detect: ctx => buildDetection('angular', [
450
+ evidenceIf(hasDependency(ctx.depNames, '@angular/core'), 'package.json -> @angular/core', 4),
451
+ evidenceIf(ctx.fileExists('angular.json'), 'found angular.json', 3)
452
+ ])
453
+ },
454
+ {
455
+ id: 'express',
456
+ kind: 'backend',
457
+ language: 'javascript',
458
+ projectType: 'api',
459
+ detect: ctx => buildDetection('express', [
460
+ evidenceIf(hasDependency(ctx.depNames, 'express'), 'package.json -> express', 4)
461
+ ])
462
+ },
463
+ {
464
+ id: 'nestjs',
465
+ kind: 'backend',
466
+ language: 'javascript',
467
+ projectType: 'api',
468
+ detect: ctx => buildDetection('nestjs', [
469
+ evidenceIf(hasDependency(ctx.depNames, '@nestjs/core'), 'package.json -> @nestjs/core', 4)
470
+ ])
471
+ },
472
+ {
473
+ id: 'fastapi',
474
+ kind: 'backend',
475
+ language: 'python',
476
+ projectType: 'api',
477
+ detect: ctx => buildDetection('fastapi', [
478
+ evidenceIf(textMatches(ctx.requirementsText, /\bfastapi\b/i) || textMatches(ctx.pyprojectText, /\bfastapi\b/i), 'Python deps -> fastapi', 4)
479
+ ])
480
+ },
481
+ {
482
+ id: 'django',
483
+ kind: 'backend',
484
+ language: 'python',
485
+ projectType: 'web-app',
486
+ detect: ctx => buildDetection('django', [
487
+ evidenceIf(textMatches(ctx.requirementsText, /\bdjango\b/i) || textMatches(ctx.pyprojectText, /\bdjango\b/i), 'Python deps -> django', 4),
488
+ evidenceIf(ctx.fileExists('manage.py'), 'found manage.py', 2),
489
+ evidenceIf(ctx.fileExists('templates'), 'found templates/', 1)
490
+ ])
491
+ },
492
+ {
493
+ id: 'flask',
494
+ kind: 'backend',
495
+ language: 'python',
496
+ projectType: 'api',
497
+ detect: ctx => buildDetection('flask', [
498
+ evidenceIf(textMatches(ctx.requirementsText, /\bflask\b/i) || textMatches(ctx.pyprojectText, /\bflask\b/i), 'Python deps -> flask', 4)
499
+ ])
500
+ },
501
+ {
502
+ id: 'laravel',
503
+ kind: 'backend',
504
+ language: 'php',
505
+ projectType: 'web-app',
506
+ detect: ctx => buildDetection('laravel', [
507
+ evidenceIf(hasComposerDependency(ctx.composerDepNames, 'laravel/framework'), 'composer.json -> laravel/framework', 4),
508
+ evidenceIf(ctx.fileExists('artisan'), 'found artisan', 3),
509
+ evidenceIf(ctx.fileExists('resources', 'views'), 'found resources/views', 1)
510
+ ])
511
+ },
512
+ {
513
+ id: 'cakephp',
514
+ kind: 'backend',
515
+ language: 'php',
516
+ projectType: 'web-app',
517
+ detect: ctx => buildDetection('cakephp', [
518
+ evidenceIf(hasComposerDependency(ctx.composerDepNames, 'cakephp/cakephp'), 'composer.json -> cakephp/cakephp', 4),
519
+ evidenceIf(ctx.fileExists('bin', 'cake'), 'found bin/cake', 3),
520
+ evidenceIf(ctx.fileExists('config', 'app.php'), 'found config/app.php', 2),
521
+ evidenceIf(ctx.fileExists('app', 'Config', 'core.php'), 'found app/Config/core.php (CakePHP legacy)', 3),
522
+ evidenceIf(ctx.fileExists('app', 'Controller', 'AppController.php'), 'found app/Controller/AppController.php', 2),
523
+ evidenceIf(ctx.fileExists('app', 'Model', 'AppModel.php'), 'found app/Model/AppModel.php', 2),
524
+ evidenceIf(ctx.fileExists('lib', 'Cake'), 'found lib/Cake', 3),
525
+ evidenceIf(ctx.fileExists('cake', 'console', 'cake'), 'found cake/console/cake', 3),
526
+ evidenceIf(ctx.fileExists('app', 'View'), 'found app/View', 2),
527
+ evidenceIf(textMatches(ctx.viewFilesText, /\.ctp$/im), 'found .ctp view templates', 2)
528
+ ])
529
+ },
530
+ {
531
+ id: 'symfony',
532
+ kind: 'backend',
533
+ language: 'php',
534
+ projectType: 'web-app',
535
+ detect: ctx => buildDetection('symfony', [
536
+ evidenceIf(hasComposerDependency(ctx.composerDepNames, 'symfony/framework-bundle'), 'composer.json -> symfony/framework-bundle', 4),
537
+ evidenceIf(ctx.fileExists('bin', 'console'), 'found bin/console', 2),
538
+ evidenceIf(ctx.fileExists('config', 'bundles.php'), 'found config/bundles.php', 1),
539
+ evidenceIf(ctx.fileExists('templates'), 'found templates/', 1)
540
+ ])
541
+ },
542
+ {
543
+ id: 'codeigniter',
544
+ kind: 'backend',
545
+ language: 'php',
546
+ projectType: 'api',
547
+ detect: ctx => buildDetection('codeigniter', [
548
+ evidenceIf(hasComposerDependency(ctx.composerDepNames, 'codeigniter4/framework'), 'composer.json -> codeigniter4/framework', 4),
549
+ evidenceIf(ctx.fileExists('spark'), 'found spark', 2),
550
+ evidenceIf(ctx.fileExists('app', 'Config', 'App.php'), 'found app/Config/App.php', 1)
551
+ ])
552
+ },
553
+ {
554
+ id: 'gin',
555
+ kind: 'backend',
556
+ language: 'go',
557
+ projectType: 'api',
558
+ detect: ctx => buildDetection('gin', [
559
+ evidenceIf(textMatches(ctx.goModText, /\bgithub\.com\/gin-gonic\/gin\b/i), 'go.mod -> github.com/gin-gonic/gin', 4)
560
+ ])
561
+ },
562
+ {
563
+ id: 'fiber',
564
+ kind: 'backend',
565
+ language: 'go',
566
+ projectType: 'api',
567
+ detect: ctx => buildDetection('fiber', [
568
+ evidenceIf(textMatches(ctx.goModText, /\bgithub\.com\/gofiber\/fiber\b/i), 'go.mod -> github.com/gofiber/fiber', 4)
569
+ ])
570
+ },
571
+ {
572
+ id: 'echo',
573
+ kind: 'backend',
574
+ language: 'go',
575
+ projectType: 'api',
576
+ detect: ctx => buildDetection('echo', [
577
+ evidenceIf(textMatches(ctx.goModText, /\bgithub\.com\/labstack\/echo\b/i), 'go.mod -> github.com/labstack/echo', 4)
578
+ ])
579
+ },
580
+ {
581
+ id: 'rails',
582
+ kind: 'backend',
583
+ language: 'ruby',
584
+ projectType: 'web-app',
585
+ detect: ctx => buildDetection('rails', [
586
+ evidenceIf(textMatches(ctx.gemfileText, /^\s*gem\s+["']rails["']/im), 'Gemfile -> rails', 4),
587
+ evidenceIf(ctx.fileExists('bin', 'rails'), 'found bin/rails', 2),
588
+ evidenceIf(ctx.fileExists('app', 'views'), 'found app/views', 1)
589
+ ])
590
+ },
591
+ {
592
+ id: 'spring',
593
+ kind: 'backend',
594
+ language: 'java',
595
+ projectType: 'api',
596
+ detect: ctx => buildDetection('spring', [
597
+ evidenceIf(textMatches(ctx.pomText, /spring-boot/i) || textMatches(ctx.gradleText, /spring-boot/i), 'Java build -> spring-boot', 4)
598
+ ])
599
+ },
600
+ {
601
+ id: 'aspnet',
602
+ kind: 'backend',
603
+ language: 'csharp',
604
+ projectType: 'api',
605
+ detect: ctx => buildDetection('aspnet', [
606
+ evidenceIf(textMatches(ctx.csprojText, /Microsoft\.NET\.Sdk\.Web/i), '.csproj -> Microsoft.NET.Sdk.Web', 4),
607
+ evidenceIf(textMatches(ctx.programCsText, /WebApplication\.CreateBuilder/i), 'Program.cs -> WebApplication.CreateBuilder', 2)
608
+ ])
609
+ },
610
+ {
611
+ id: 'tauri',
612
+ kind: 'desktop',
613
+ language: 'rust',
614
+ projectType: 'desktop',
615
+ detect: ctx => buildDetection('tauri', [
616
+ evidenceIf(hasDependency(ctx.depNames, '@tauri-apps/api'), 'package.json -> @tauri-apps/api', 4),
617
+ evidenceIf(ctx.fileExists('src-tauri', 'tauri.conf.json') || ctx.fileExists('src-tauri', 'tauri.conf.json5'), 'found src-tauri/tauri.conf.*', 4),
618
+ evidenceIf(ctx.fileExists('src-tauri', 'Cargo.toml'), 'found src-tauri/Cargo.toml', 2)
619
+ ])
620
+ },
621
+ {
622
+ id: 'axum',
623
+ kind: 'backend',
624
+ language: 'rust',
625
+ projectType: 'api',
626
+ detect: ctx => buildDetection('axum', [
627
+ evidenceIf(textMatches(ctx.cargoText, /\baxum\b/i), 'Cargo.toml -> axum', 4)
628
+ ])
629
+ },
630
+ {
631
+ id: 'actix',
632
+ kind: 'backend',
633
+ language: 'rust',
634
+ projectType: 'api',
635
+ detect: ctx => buildDetection('actix', [
636
+ evidenceIf(textMatches(ctx.cargoText, /\bactix-web\b/i), 'Cargo.toml -> actix-web', 4)
637
+ ])
638
+ },
639
+ {
640
+ id: 'flutter',
641
+ kind: 'mobile',
642
+ projectType: 'mobile',
643
+ detect: ctx => buildDetection('flutter', [
644
+ evidenceIf(textMatches(ctx.pubspecText, /^\s*flutter\s*:/im), 'pubspec.yaml -> flutter', 4)
645
+ ])
646
+ },
647
+ {
648
+ id: 'react-native',
649
+ kind: 'mobile',
650
+ projectType: 'mobile',
651
+ detect: ctx => buildDetection('react-native', [
652
+ evidenceIf(hasDependency(ctx.depNames, 'react-native', 'expo'), 'package.json -> react-native/expo', 4)
653
+ ])
654
+ }
655
+ ];
656
+
657
+ function scanProject() {
658
+ const appRoot = detectAppRoot(targetDir);
659
+ const pkg = readJsonIfExistsAt(appRoot, 'package.json') || {};
660
+ const composer = readJsonIfExistsAt(appRoot, 'composer.json') || {};
661
+ const rootEntries = fs.existsSync(appRoot) ? fs.readdirSync(appRoot) : [];
662
+ const rootCsproj = rootEntries.find(name => name.endsWith('.csproj')) || '';
663
+ const deps = {
664
+ ...(pkg.dependencies || {}),
665
+ ...(pkg.devDependencies || {}),
666
+ ...(pkg.peerDependencies || {})
667
+ };
668
+ const depNames = new Set(Object.keys(deps));
669
+ const composerDeps = {
670
+ ...(composer.require || {}),
671
+ ...(composer['require-dev'] || {})
672
+ };
673
+ const composerDepNames = new Set(Object.keys(composerDeps));
674
+ const ctx = {
675
+ pkg,
676
+ composer,
677
+ depNames,
678
+ composerDepNames,
679
+ rootEntries,
680
+ fileExists: (...parts) => fileExistsAt(appRoot, ...parts),
681
+ requirementsText: readTextIfExistsAt(appRoot, 'requirements.txt'),
682
+ pyprojectText: readTextIfExistsAt(appRoot, 'pyproject.toml'),
683
+ goModText: readTextIfExistsAt(appRoot, 'go.mod'),
684
+ pubspecText: readTextIfExistsAt(appRoot, 'pubspec.yaml'),
685
+ gemfileText: readTextIfExistsAt(appRoot, 'Gemfile'),
686
+ pomText: readTextIfExistsAt(appRoot, 'pom.xml'),
687
+ gradleText: `${readTextIfExistsAt(appRoot, 'build.gradle')}\n${readTextIfExistsAt(appRoot, 'build.gradle.kts')}`,
688
+ cargoText: `${readTextIfExistsAt(appRoot, 'Cargo.toml')}\n${readTextIfExistsAt(appRoot, 'src-tauri', 'Cargo.toml')}`,
689
+ csprojText: rootCsproj ? readTextIfExistsAt(appRoot, rootCsproj) : '',
690
+ programCsText: readTextIfExistsAt(appRoot, 'Program.cs'),
691
+ viewFilesText: fs.existsSync(path.join(appRoot, 'app', 'View'))
692
+ ? fs.readdirSync(path.join(appRoot, 'app', 'View'), { recursive: true }).join('\n')
693
+ : ''
694
+ };
695
+
696
+ const languageDetections = LANGUAGE_DETECTORS
697
+ .map(detector => detector.detect(ctx))
698
+ .filter(result => result.score >= 2)
699
+ .sort((a, b) => b.score - a.score);
700
+
701
+ const frameworkDetails = FRAMEWORK_DETECTORS
702
+ .map(detector => {
703
+ const result = detector.detect(ctx);
704
+ if (result.score < 3) return null;
705
+ return {
706
+ ...result,
707
+ kind: detector.kind,
708
+ language: detector.language,
709
+ projectType: detector.projectType,
710
+ confidence: confidenceLabel(result.score)
711
+ };
712
+ })
713
+ .filter(Boolean)
714
+ .sort((a, b) => b.score - a.score);
715
+
716
+ const frameworkLanguageScores = frameworkDetails.reduce((acc, item) => {
717
+ if (!item.language) return acc;
718
+ acc[item.language] = (acc[item.language] || 0) + item.score;
719
+ return acc;
720
+ }, {});
721
+ const primaryLanguage = Object.keys(frameworkLanguageScores).sort((a, b) => frameworkLanguageScores[b] - frameworkLanguageScores[a])[0]
722
+ || languageDetections[0]?.id
723
+ || 'typescript';
724
+ const frameworks = uniq(frameworkDetails.map(item => item.id)).filter(name => FRAMEWORK_CHOICES.includes(name));
725
+ const infrastructure = [];
726
+ let projectType = '';
727
+ let database = 'none';
728
+ let testFramework = 'auto-detect';
729
+ let cloud = 'none';
730
+ let ciCd = 'none';
731
+
732
+ if (frameworkDetails.some(item => item.projectType === 'desktop')) projectType = 'desktop';
733
+ else if (frameworkDetails.some(item => item.projectType === 'mobile')) projectType = 'mobile';
734
+ else if (frameworkDetails.some(item => item.kind === 'frontend') && frameworkDetails.some(item => item.kind === 'backend')) projectType = 'fullstack';
735
+ else if (frameworkDetails.some(item => item.projectType === 'web-app')) projectType = 'web-app';
736
+ else if (frameworkDetails.some(item => item.kind === 'backend')) projectType = 'api';
737
+ else if (frameworkDetails.some(item => item.kind === 'frontend')) projectType = 'web-app';
738
+ else if (fileExistsAt(appRoot, 'package.json') && pkg.bin) projectType = 'cli';
739
+ else if (['php', 'python', 'go', 'java', 'ruby', 'csharp', 'rust'].includes(primaryLanguage)) projectType = 'api';
740
+
741
+ if (hasDependency(depNames, 'pg', 'postgres', '@prisma/client') || /postgres/i.test(JSON.stringify(pkg))) database = 'postgres';
742
+ else if (hasDependency(depNames, 'mysql', 'mysql2')) database = 'mysql';
743
+ else if (hasDependency(depNames, 'sqlite3', 'better-sqlite3', 'rusqlite')) database = 'sqlite';
744
+ else if (hasDependency(depNames, 'mongodb', 'mongoose')) database = 'mongodb';
745
+ else if (hasDependency(depNames, 'redis', 'ioredis')) database = 'redis';
746
+ else if (hasDependency(depNames, '@aws-sdk/client-dynamodb')) database = 'dynamodb';
747
+
748
+ if (hasDependency(depNames, 'vitest')) testFramework = 'vitest';
749
+ else if (hasDependency(depNames, 'jest')) testFramework = 'jest';
750
+ else if (hasDependency(depNames, 'pytest') || textMatches(ctx.requirementsText, /\bpytest\b/i) || textMatches(ctx.pyprojectText, /\bpytest\b/i)) testFramework = 'pytest';
751
+ else if (hasComposerDependency(composerDepNames, 'phpunit/phpunit')) testFramework = 'phpunit';
752
+ else if (hasComposerDependency(composerDepNames, 'pestphp/pest')) testFramework = 'pest';
753
+ else if (fileExistsAt(appRoot, 'go.mod')) testFramework = 'go test';
754
+ else if (textMatches(ctx.gemfileText, /^\s*gem\s+["']rspec["']/im)) testFramework = 'rspec';
755
+
756
+ if (repoHasAny(targetDir, [['Dockerfile'], ['docker-compose.yml'], ['docker-compose.yaml']])) {
757
+ infrastructure.push('docker');
758
+ }
759
+
760
+ if (repoHasAny(targetDir, [['vercel.json']])) {
761
+ cloud = 'vercel';
762
+ infrastructure.push('vercel');
763
+ } else if (repoHasAny(targetDir, [['fly.toml']])) {
764
+ cloud = 'fly';
765
+ infrastructure.push('fly');
766
+ } else if (repoHasAny(targetDir, [['railway.json'], ['railway.toml']])) {
767
+ cloud = 'railway';
768
+ infrastructure.push('railway');
769
+ }
770
+
771
+ if (repoHasAny(targetDir, [['.github', 'workflows'], ['.gitlab-ci.yml'], ['.circleci', 'config.yml'], ['Jenkinsfile']], 2)) {
772
+ if (repoHasAny(targetDir, [['.github', 'workflows']], 2)) ciCd = 'github-actions';
773
+ else if (repoHasAny(targetDir, [['.gitlab-ci.yml']], 2)) ciCd = 'gitlab-ci';
774
+ else if (repoHasAny(targetDir, [['.circleci', 'config.yml']], 2)) ciCd = 'circleci';
775
+ else if (repoHasAny(targetDir, [['Jenkinsfile']], 2)) ciCd = 'jenkins';
776
+ }
777
+
778
+ const scanEvidence = [
779
+ ...(languageDetections[0] ? [`language:${languageDetections[0].id} => ${languageDetections[0].evidence.join('; ')}`] : []),
780
+ ...frameworkDetails.map(item => `${item.id} [${item.confidence}] => ${item.evidence.join('; ')}`)
781
+ ];
782
+
783
+ return {
784
+ project_name: path.basename(targetDir),
785
+ app_root: path.relative(targetDir, appRoot) || '.',
786
+ description: pkg.description || composer.description || 'A software project',
787
+ project_type: PROJECT_TYPE_CHOICES.includes(projectType) ? projectType : 'web-app',
788
+ primary_language: LANGUAGE_CHOICES.includes(primaryLanguage) ? primaryLanguage : 'typescript',
789
+ frameworks,
790
+ framework_details: frameworkDetails,
791
+ scan_evidence: scanEvidence,
792
+ database: DATABASE_CHOICES.includes(database) ? database : 'none',
793
+ package_manager: PACKAGE_MANAGER_CHOICES.includes(detectPackageManager(appRoot)) ? detectPackageManager(appRoot) : 'auto-detect',
794
+ uses_docker: infrastructure.includes('docker'),
795
+ cloud,
796
+ infrastructure: uniq(infrastructure),
797
+ ci_cd: ciCd,
798
+ test_framework: TEST_FRAMEWORK_CHOICES.includes(testFramework) ? testFramework : 'auto-detect',
799
+ max_file_lines: '300',
800
+ code_style: 'project-standard',
801
+ architecture_notes: '<!-- Add notes -->',
802
+ current_phase: 'Initial setup',
803
+ next_phase: '—',
804
+ known_issues: '<!-- None yet -->'
805
+ };
806
+ }
800
807
 
801
808
  function resolveAgentTable(data) {
802
809
  const rows = [];
@@ -804,7 +811,7 @@ function resolveAgentTable(data) {
804
811
  const lang = (data.primary_language || "").toLowerCase();
805
812
 
806
813
  // Frontend
807
- const frontendFws = frameworks.filter(f => FRONTEND_FRAMEWORKS.includes(f));
814
+ const frontendFws = frameworks.filter(f => FRONTEND_FRAMEWORKS.includes(f));
808
815
  if (frontendFws.length > 0) {
809
816
  let feSkills = [];
810
817
  frontendFws.forEach(fw => {
@@ -815,14 +822,14 @@ function resolveAgentTable(data) {
815
822
 
816
823
  // Backend
817
824
  let beSkills = [...(LANG_SKILLS[lang] || ["api-design"])];
818
- const backendFws = frameworks.filter(f => BACKEND_FRAMEWORKS.includes(f));
825
+ const backendFws = frameworks.filter(f => BACKEND_FRAMEWORKS.includes(f));
819
826
  backendFws.forEach(fw => {
820
827
  beSkills.push(...(FRAMEWORK_SKILLS[fw] || []));
821
828
  });
822
829
  rows.push(["Backend API", "backend-specialist", Array.from(new Set(beSkills)).slice(0, 3).join(", ")]);
823
830
 
824
831
  // Mobile/Desktop
825
- const mobileFws = frameworks.filter(f => MOBILE_DESKTOP_FRAMEWORKS.includes(f));
832
+ const mobileFws = frameworks.filter(f => MOBILE_DESKTOP_FRAMEWORKS.includes(f));
826
833
  if (mobileFws.length > 0) {
827
834
  let mobileSkills = [];
828
835
  mobileFws.forEach(fw => {
@@ -845,617 +852,523 @@ function resolveAgentTable(data) {
845
852
 
846
853
  // --- File Generators ---
847
854
 
848
- function generateProjectContext(data) {
849
- const agentTable = resolveAgentTable(data);
850
- const autoSkills = resolveSkills(data);
851
- const scanEvidenceBlock = (data.scan_evidence || []).length > 0
852
- ? `
853
- ---
854
-
855
- ## Scan Evidence
856
-
857
- These detections were inferred from the current codebase during \`init --scan\`:
858
-
859
- \`\`\`text
860
- ${(data.scan_evidence || []).map(line => '- ' + line).join('\n')}
861
- \`\`\`
862
- `
863
- : '';
864
-
865
- const agentRows = agentTable.map(([task, agent, skills]) => `| ${task} | \`${agent}\` | ${skills} |`).join('\n');
866
- const autoSkillsList = autoSkills.map(s => `- ${s}`).join('\n');
867
-
868
- return `# PROJECT_CONTEXT.md
869
- # Generated by thachvd-kit init wizard on ${new Date().toISOString().split('T')[0]}
870
- # Edit freely — this is your project's brain.
871
-
872
- ## Project
873
-
874
- - **Name:** ${data.project_name}
875
- - **Description:** ${data.description}
876
- - **Type:** ${data.project_type}
877
-
878
- ---
879
-
880
- ## Context Status
881
-
882
- \`\`\`yaml
883
- context:
884
- status: ${data.context_status || 'baseline'}
885
- needs_ai_refinement: ${data.needs_ai_refinement ? 'true' : 'false'}
886
- last_refined_at: ${data.last_refined_at || 'not-yet-refined'}
887
- \`\`\`
888
-
889
- ---
890
-
891
- ## Stack
855
+ function generateSharedAgentsMd(data) {
856
+ return `# AGENTS.md
892
857
 
893
- \`\`\`yaml
894
- stack:
895
- app_root: ${data.app_root || '.'}
896
- primary_language: ${data.primary_language}
897
- frameworks: [${(data.frameworks || []).join(', ')}]
898
- database: ${data.database || 'none'}
899
- infrastructure: [${(data.infrastructure || []).join(', ')}]
900
- package_manager: ${data.package_manager || 'auto-detect'}
901
- test_framework: ${data.test_framework || 'auto-detect'}
902
- \`\`\`
903
-
904
- ${scanEvidenceBlock}
905
-
906
- ---
907
-
908
- ## Agent Routing
858
+ Shared operating instructions for Codex, Antigravity, and Claude Code.
909
859
 
910
- | Task Type | Agent | Primary Skills |
911
- |-----------|-------|----------------|
912
- ${agentRows}
860
+ ## Startup
913
861
 
914
- ---
862
+ 1. Read this file first.
863
+ 2. Read \`.agent/docs/project.md\` for detected stack, commands, and routing.
864
+ 3. Read \`.agent/docs/workflow.md\` for the task flow before editing.
865
+ 4. Read \`.agent/docs/architecture.md\` and \`.agent/docs/conventions.md\` before planning code changes.
866
+ 5. Load only the agent, skill, or workflow files that match the current task.
915
867
 
916
- ## Auto-Resolved Skills
868
+ If any \`.agent/docs/*.md\` file still contains \`TODO: refine\`, update the docs by scanning the project before making product code changes.
917
869
 
918
- Based on your stack, these skills will be loaded automatically:
870
+ ## Task Flow
919
871
 
920
- \`\`\`text
921
- ${autoSkillsList}
922
- \`\`\`
872
+ - Questions and analysis: answer directly, cite relevant files when useful, and do not edit code.
873
+ - Simple fix: inspect dependencies, make the smallest change, run focused verification.
874
+ - Feature or refactor: define success criteria, make a short plan, implement, then verify.
875
+ - Multi-domain work: use \`.agent/workflows/orchestrate.md\` and route to the relevant specialist docs.
876
+ - UI work: read \`.agent/agents/frontend-specialist.md\` and applicable design skills before editing.
923
877
 
924
- ---
878
+ ## Rules
925
879
 
926
- ## Project Conventions
880
+ - Respond in the user's language; keep code, identifiers, and code comments in English.
881
+ - State assumptions when the request is ambiguous.
882
+ - Prefer the existing project style over new abstractions.
883
+ - Keep changes surgical and remove only dead code introduced by your change.
884
+ - Tests or equivalent verification are mandatory before claiming done.
885
+ - Keep files under ${data.max_file_lines || '300'} lines unless the project already has a different standard in \`.agent/docs/conventions.md\`.
927
886
 
928
- - **Max file size:** ${data.max_file_lines || '300'} lines (then modularize)
929
- - **Code style:** ${data.code_style || 'project-standard'}
887
+ ## Shared Knowledge
930
888
 
931
- ---
932
-
933
- ## Architecture Notes
934
-
935
- <!-- Add architecture notes, key decisions, patterns used -->
889
+ - Project docs: \`.agent/docs/\`
890
+ - Specialist agents: \`.agent/agents/\`
891
+ - Skills: \`.agent/skills/\`
892
+ - Workflows: \`.agent/workflows/\`
893
+ - Antigravity mirror rules: \`.agent/rules/GEMINI.md\`
894
+ `;
895
+ }
936
896
 
937
- ---
897
+ function generateSharedClaudeMd() {
898
+ return `@AGENTS.md
938
899
 
939
- ## Current Phase
900
+ ## Claude Code
940
901
 
941
- - **Done:** —
942
- - **In Progress:** Initial setup
943
- - **Next:** —
902
+ This repository uses AGENTS.md as the shared cross-agent entry file. Follow the imported instructions and keep Claude-specific notes here only when they cannot apply to Codex or Antigravity.
903
+ `;
904
+ }
944
905
 
906
+ function generateSharedGeminiMd() {
907
+ return `# GEMINI.md
945
908
 
946
- ---
947
-
948
- ## Known Issues / Tasks
949
-
950
- <!-- Add known issues or pending tasks -->
951
- `;
952
- }
953
-
954
-
955
- function generateClaudeMd(data) {
956
- return `# CLAUDE.md — ${data.project_name}
957
- # Generated by thachvd-kit. See PROJECT_CONTEXT.md for full stack info.
958
- # Platform: Claude Code
959
-
960
- ## Project
961
- ${data.description}
962
- Stack: ${data.primary_language} | ${(data.frameworks || []).join(', ')}
963
-
964
- ## Rules
965
- - Read PROJECT_CONTEXT.md first for stack details and agent routing.
966
- - If PROJECT_CONTEXT.md is missing or stale, create/update it before major work.
967
- - Follow skills in .agent/skills/ (or .claude/skills/).
968
- - Tests are mandatory. Verify before claiming done.
969
- - Max ${data.max_file_lines || '300'} lines per file.
909
+ Antigravity entry file for this project.
970
910
 
971
- ## Key Skills
972
- - Plan: \`plan-writing\` → \`executing-plans\`
973
- - Review: \`requesting-code-review\` / \`receiving-code-review\`
974
- - Quality: \`clean-code\`, \`verification-before-completion\`
975
- - Workflow: \`subagent-driven-development\`, \`finishing-a-development-branch\`
976
-
977
- ## Commands
978
- - \`/plan\` — Create implementation plan
979
- - \`/debug\` — Systematic debugging
980
- - \`/test\` — Run test suite
981
- - \`/deploy\` — Deploy checklist
982
-
983
- See PROJECT_CONTEXT.md for full agent routing table.
911
+ Read AGENTS.md first, then follow the shared docs under .agent/docs/.
984
912
  `;
985
913
  }
986
914
 
987
- function generateAgentsMd(data) {
988
- const frameworks = (data.frameworks || []);
915
+ function resolveCommands(data) {
916
+ const frameworks = data.frameworks || [];
989
917
  const primaryLang = data.primary_language || 'javascript';
990
918
  const pkgManager = data.package_manager || 'npm';
991
919
  const testFramework = data.test_framework || 'auto-detect';
992
- const database = data.database || 'none';
993
- const cloud = data.cloud || 'none';
994
- const infrastructure = (data.infrastructure || []);
995
- const appRoot = data.app_root || '.';
996
- const maxFileLines = data.max_file_lines || '300';
997
920
 
998
- // Build dev commands section based on detected package manager and framework
999
- const isNode = ['javascript', 'typescript'].includes(primaryLang);
1000
921
  const isPython = primaryLang === 'python';
1001
922
  const isGo = primaryLang === 'go';
1002
923
  const isRust = primaryLang === 'rust';
1003
924
  const isPhp = primaryLang === 'php';
1004
925
 
1005
- let installCmd = 'npm install';
1006
- let devCmd = 'npm run dev';
1007
- let buildCmd = 'npm run build';
1008
- let testCmd = 'npm test';
1009
- let lintCmd = 'npm run lint';
1010
-
1011
- if (pkgManager === 'pnpm') { installCmd = 'pnpm install'; devCmd = 'pnpm dev'; buildCmd = 'pnpm build'; testCmd = 'pnpm test'; lintCmd = 'pnpm lint'; }
1012
- else if (pkgManager === 'yarn') { installCmd = 'yarn'; devCmd = 'yarn dev'; buildCmd = 'yarn build'; testCmd = 'yarn test'; lintCmd = 'yarn lint'; }
1013
- else if (pkgManager === 'bun') { installCmd = 'bun install'; devCmd = 'bun dev'; buildCmd = 'bun run build'; testCmd = 'bun test'; lintCmd = 'bun run lint'; }
1014
- else if (isPython) { installCmd = 'pip install -r requirements.txt'; devCmd = 'python manage.py runserver'; buildCmd = '# no build step'; testCmd = 'pytest'; lintCmd = 'ruff check .'; }
1015
- else if (isGo) { installCmd = 'go mod download'; devCmd = 'go run .'; buildCmd = 'go build ./...'; testCmd = 'go test ./...'; lintCmd = 'golangci-lint run'; }
1016
- else if (isRust) { installCmd = 'cargo build'; devCmd = 'cargo run'; buildCmd = 'cargo build --release'; testCmd = 'cargo test'; lintCmd = 'cargo clippy'; }
926
+ let install = 'npm install';
927
+ let dev = 'npm run dev';
928
+ let build = 'npm run build';
929
+ let test = 'npm test';
930
+ let lint = 'npm run lint';
931
+
932
+ if (pkgManager === 'pnpm') { install = 'pnpm install'; dev = 'pnpm dev'; build = 'pnpm build'; test = 'pnpm test'; lint = 'pnpm lint'; }
933
+ else if (pkgManager === 'yarn') { install = 'yarn'; dev = 'yarn dev'; build = 'yarn build'; test = 'yarn test'; lint = 'yarn lint'; }
934
+ else if (pkgManager === 'bun') { install = 'bun install'; dev = 'bun dev'; build = 'bun run build'; test = 'bun test'; lint = 'bun run lint'; }
935
+ else if (isPython) { install = 'pip install -r requirements.txt'; dev = 'python manage.py runserver'; build = '# no build step'; test = 'pytest'; lint = 'ruff check .'; }
936
+ else if (isGo) { install = 'go mod download'; dev = 'go run .'; build = 'go build ./...'; test = 'go test ./...'; lint = 'golangci-lint run'; }
937
+ else if (isRust) { install = 'cargo build'; dev = 'cargo run'; build = 'cargo build --release'; test = 'cargo test'; lint = 'cargo clippy'; }
1017
938
  else if (isPhp) {
1018
- installCmd = 'composer install';
1019
- buildCmd = '# no build step';
1020
- testCmd = testFramework === 'pest' ? './vendor/bin/pest' : './vendor/bin/phpunit';
939
+ install = 'composer install';
940
+ build = '# no build step';
941
+ test = testFramework === 'pest' ? './vendor/bin/pest' : './vendor/bin/phpunit';
1021
942
  const isCakePHP = frameworks.some(f => f.toLowerCase().includes('cakephp'));
1022
943
  const isSymfony = frameworks.some(f => f.toLowerCase().includes('symfony'));
1023
944
  const isCodeIgniter = frameworks.some(f => f.toLowerCase().includes('codeigniter'));
1024
945
  if (isCakePHP) {
1025
- devCmd = 'bin/cake server';
1026
- lintCmd = './vendor/bin/phpcs';
946
+ dev = 'bin/cake server';
947
+ lint = './vendor/bin/phpcs';
1027
948
  } else if (isSymfony) {
1028
- devCmd = 'bin/console server:run';
1029
- lintCmd = './vendor/bin/phpcs';
949
+ dev = 'bin/console server:run';
950
+ lint = './vendor/bin/phpcs';
1030
951
  } else if (isCodeIgniter) {
1031
- devCmd = 'php spark serve';
1032
- lintCmd = './vendor/bin/phpcs';
952
+ dev = 'php spark serve';
953
+ lint = './vendor/bin/phpcs';
1033
954
  } else {
1034
- // Default: Laravel
1035
- devCmd = 'php artisan serve';
1036
- lintCmd = 'php artisan pint';
955
+ dev = 'php artisan serve';
956
+ lint = 'php artisan pint';
1037
957
  }
1038
958
  }
1039
959
 
1040
- if (testFramework === 'vitest') testCmd = `${pkgManager === 'pnpm' ? 'pnpm' : pkgManager === 'yarn' ? 'yarn' : pkgManager === 'bun' ? 'bun run' : 'npx'} vitest`;
1041
- else if (testFramework === 'jest') testCmd = `${pkgManager === 'pnpm' ? 'pnpm' : pkgManager === 'yarn' ? 'yarn' : 'npx'} jest`;
1042
-
1043
- // Build stack overview section
1044
- const stackLines = [
1045
- `- **Language:** ${primaryLang}`,
1046
- frameworks.length > 0 ? `- **Frameworks:** ${frameworks.join(', ')}` : null,
1047
- database !== 'none' ? `- **Database:** ${database}` : null,
1048
- pkgManager !== 'auto-detect' ? `- **Package Manager:** ${pkgManager}` : null,
1049
- testFramework !== 'auto-detect' ? `- **Test Framework:** ${testFramework}` : null,
1050
- cloud !== 'none' ? `- **Cloud:** ${cloud}` : null,
1051
- infrastructure.length > 0 ? `- **Infrastructure:** ${infrastructure.join(', ')}` : null,
1052
- ].filter(Boolean).join('\n');
1053
-
1054
- // Build architecture notes hint
1055
- const archNotes = data.architecture_notes && !data.architecture_notes.includes('<!-- Add')
1056
- ? `\n${data.architecture_notes}`
1057
- : '';
1058
-
1059
- // Agent routing table
1060
- const agentTable = resolveAgentTable(data);
1061
- const agentRows = agentTable.map(([task, agent, skills]) => `| ${task} | \`${agent}\` | ${skills} |`).join('\n');
1062
-
1063
- // Auto-resolved skills
1064
- const autoSkills = resolveSkills(data);
1065
- const skillsList = autoSkills.map(s => `- ${s}`).join('\n');
1066
-
1067
- return `# ${data.project_name}
1068
-
1069
- ${data.description}
1070
-
1071
- ## Project Overview
1072
-
1073
- ${stackLines}
1074
-
1075
- ---
1076
-
1077
- ## Core Architecture
1078
-
1079
- <!-- AI: Scan the project and replace this section with actual directory structure and key module descriptions -->
1080
- ${appRoot !== '.' ? `- **App Root:** \`${appRoot}\`` : ''}
1081
-
1082
- ---
1083
-
1084
- ## Development Workflow
1085
-
1086
- ### Prerequisites
1087
- - ${primaryLang.charAt(0).toUpperCase() + primaryLang.slice(1)} toolchain installed
1088
- ${pkgManager !== 'auto-detect' ? `- \`${pkgManager}\` (Package Manager)` : ''}
1089
-
1090
- ### Key Commands
1091
- - **Install Dependencies:** \`${installCmd}\`
1092
- - **Development Server:** \`${devCmd}\`
1093
- - **Build for Production:** \`${buildCmd}\`
1094
- - **Run Tests:** \`${testCmd}\`
1095
- - **Linting:** \`${lintCmd}\`
1096
-
1097
- ---
1098
-
1099
- ## Coding Conventions
1100
-
1101
- <!-- AI: Scan the project and replace this section with actual conventions (API patterns, state management, styling, naming, routing) -->
1102
-
1103
- ---
1104
-
1105
- ## Performance & Optimization
1106
-
1107
- <!-- AI: Scan vite.config / webpack / build config and replace with actual optimizations used -->
1108
-
1109
- ---
1110
-
1111
- ## Coding Rules
960
+ if (testFramework === 'vitest') test = `${pkgManager === 'pnpm' ? 'pnpm' : pkgManager === 'yarn' ? 'yarn' : pkgManager === 'bun' ? 'bun run' : 'npx'} vitest`;
961
+ else if (testFramework === 'jest') test = `${pkgManager === 'pnpm' ? 'pnpm' : pkgManager === 'yarn' ? 'yarn' : 'npx'} jest`;
1112
962
 
1113
- **Don't assume. Don't hide confusion. Surface tradeoffs.**
1114
-
1115
- Before implementing:
1116
- - State your assumptions explicitly. If uncertain, ask.
1117
- - If multiple interpretations exist, present them — don't pick silently.
1118
- - If a simpler approach exists, say so. Push back when warranted.
1119
- - If something is unclear, stop. Name what's confusing. Ask.
1120
-
1121
- ### 1. Simplicity First
1122
-
1123
- **Minimum code that solves the problem. Nothing speculative.**
1124
-
1125
- - No features beyond what was asked.
1126
- - No abstractions for single-use code.
1127
- - No "flexibility" or "configurability" that wasn't requested.
1128
- - No error handling for impossible scenarios.
1129
- - If you write 200 lines and it could be 50, rewrite it.
1130
-
1131
- Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
1132
-
1133
- ### 2. Surgical Changes
1134
-
1135
- **Touch only what you must. Clean up only your own mess.**
1136
-
1137
- When editing existing code:
1138
- - Don't "improve" adjacent code, comments, or formatting.
1139
- - Don't refactor things that aren't broken.
1140
- - Match existing style, even if you'd do it differently.
1141
- - If you notice unrelated dead code, mention it — don't delete it.
1142
-
1143
- When your changes create orphans:
1144
- - Remove imports/variables/functions that YOUR changes made unused.
1145
- - Don't remove pre-existing dead code unless asked.
1146
-
1147
- The test: Every changed line should trace directly to the user's request.
1148
-
1149
- ### 3. Goal-Driven Execution
1150
-
1151
- **Define success criteria. Loop until verified.**
1152
-
1153
- Transform tasks into verifiable goals:
1154
- - "Add validation" → "Write tests for invalid inputs, then make them pass"
1155
- - "Fix the bug" → "Write a test that reproduces it, then make it pass"
1156
- - "Refactor X" → "Ensure tests pass before and after"
1157
-
1158
- For multi-step tasks, state a brief plan:
1159
- \`\`\`
1160
- 1. [Step] → verify: [check]
1161
- 2. [Step] → verify: [check]
1162
- 3. [Step] → verify: [check]
1163
- \`\`\`
1164
-
1165
- Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
1166
-
1167
- ### 4. Universal Standards
1168
-
1169
- - Respond in user's language; code/comments in English.
1170
- - \`clean-code\` applies to all code, all languages.
1171
- - Tests are mandatory.
1172
- - Verify with evidence before claiming done.
1173
- - Identify dependent files before modifying any.
1174
- - Keep files under ${maxFileLines} lines; split into modules if exceeded.
1175
- `;
963
+ return { install, dev, build, test, lint };
1176
964
  }
1177
965
 
1178
- function generateAgentsMdRefinementPrompt(data) {
1179
- const evidence = (data.scan_evidence || []).length > 0
1180
- ? (data.scan_evidence || []).map(line => `- ${line}`).join('\n')
1181
- : '- No scan evidence was captured during init.';
1182
-
1183
- return `# AGENTS.md Refinement Prompt
1184
-
1185
- Run this in your AI editor right after \`thachvd-kit init --scan\`.
1186
- The goal is to replace the placeholder sections in \`AGENTS.md\` with real project knowledge.
1187
-
1188
- ## Baseline Detected
1189
-
1190
- - **Project:** ${data.project_name}
1191
- - **Stack:** ${data.primary_language} | ${(data.frameworks || []).join(', ') || 'no framework'}
1192
- - **Database:** ${data.database || 'none'}
1193
- - **Package Manager:** ${data.package_manager || 'auto-detect'}
1194
-
1195
- ## Scan Evidence
1196
-
1197
- ${evidence}
1198
-
1199
- ## Your Task
1200
-
1201
- Update **only** the placeholder sections in \`AGENTS.md\` (marked with \`<!-- AI: ... --\`>):
1202
-
1203
- ### 1. Core Architecture
1204
- Scan the \`src/\` (or equivalent) directory. For each major folder, write one bullet:
1205
- \`- **\`src/folder/\`**: What it contains and its role\`
1206
- Keep it concise — one line per folder.
1207
-
1208
- ### 2. Coding Conventions
1209
- Inspect actual code patterns and write conventions grouped by topic:
1210
- - API integration patterns (how HTTP calls are made)
1211
- - State management patterns
1212
- - Styling conventions
1213
- - Component/file naming rules
1214
- - Routing and auth patterns
1215
-
1216
- ### 3. Performance & Optimization
1217
- Check \`vite.config.js\`, \`webpack.config.js\`, \`package.json\` scripts, or build configs. List actual optimizations:
1218
- - Code splitting, lazy loading, chunking
1219
- - Compression, caching strategies
1220
- - PWA, image optimization, etc.
1221
-
1222
- ## Rules
1223
-
1224
- - Replace placeholders with real content. Remove the \`<!-- AI: ... -->\` comments.
1225
- - Do NOT change \`## Project Overview\`, \`## Development Workflow\`, or \`## Coding Rules\` sections.
1226
- - Do NOT implement any product code.
1227
- - If a section is not applicable, write \`N/A\` and remove the placeholder.
1228
-
1229
- ## Files To Inspect
1230
-
1231
- - \`src/\` or \`app/\` directory structure
1232
- - Build config: \`vite.config.*\`, \`webpack.config.*\`, \`next.config.*\`
1233
- - Entry points: \`main.js\`, \`app.js\`, \`index.ts\`
1234
- - Router definition
1235
- - Example API service files
1236
- - Example store/state files
1237
- - README if present
1238
- `;
1239
- }
966
+ function generateProjectDoc(data) {
967
+ const commands = resolveCommands(data);
968
+ const agentRows = resolveAgentTable(data).map(([task, agent, skills]) => `| ${task} | \`${agent}\` | ${skills} |`).join('\n');
969
+ const skillsList = resolveSkills(data).map(s => `- ${s}`).join('\n');
970
+ const evidence = (data.scan_evidence || []).length > 0
971
+ ? (data.scan_evidence || []).map(line => `- ${line}`).join('\n')
972
+ : '- TODO: refine scan evidence by inspecting the project.';
1240
973
 
1241
- // --- Main CLI ---
974
+ return `# Project Rules
1242
975
 
1243
- async function main() {
1244
- const rawArgs = process.argv.slice(2);
1245
- const command = rawArgs[0] && !rawArgs[0].startsWith('-') ? rawArgs[0] : 'init';
1246
- const args = command === 'init' ? rawArgs.slice(rawArgs[0] === 'init' ? 1 : 0) : rawArgs.slice(1);
1247
-
1248
- if (rawArgs.includes('--version') || rawArgs.includes('-v')) {
1249
- console.log(packageJson.version);
1250
- return;
1251
- }
1252
-
1253
- if (rawArgs.includes('--help') || rawArgs.includes('-h')) {
1254
- printHelp();
1255
- return;
1256
- }
1257
-
1258
- if (command !== 'init') {
1259
- console.error(pc.red(`Unknown command: ${command}`));
1260
- printHelp();
1261
- process.exit(1);
1262
- }
1263
-
1264
- let useScan = args.includes('--scan');
1265
- const assumeYes = args.includes('--yes') || args.includes('-y');
1266
-
1267
- console.log(pc.bold(pc.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')));
1268
- console.log(' ' + pc.bold('thachvd-kit') + ' — Project Init Wizard');
1269
- console.log(` Generates AGENTS.md${useScan ? ' (scan-assisted)' : ''}`);
1270
- console.log(pc.bold(pc.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')) + '\n');
1271
-
1272
- let projectSetup = 'new';
1273
- if (!useScan && !assumeYes) {
1274
- const modeAnswer = await askQuestions([
1275
- {
1276
- type: 'select',
1277
- name: 'project_setup',
1278
- message: 'Project already exists or are you starting fresh?',
1279
- choices: [
1280
- { title: 'Existing project', value: 'existing' },
1281
- { title: 'New project', value: 'new' }
1282
- ]
1283
- },
1284
- {
1285
- type: prev => prev === 'existing' ? 'select' : null,
1286
- name: 'existing_mode',
1287
- message: 'How should thachvd-kit build PROJECT_CONTEXT.md?',
1288
- choices: [
1289
- { title: 'Scan current project files', value: 'scan' },
1290
- { title: 'Enter context manually', value: 'manual' }
1291
- ],
1292
- initial: 0
976
+ Generated by thachvd-kit on ${new Date().toISOString().split('T')[0]}.
977
+
978
+ ## Summary
979
+
980
+ - Name: ${data.project_name}
981
+ - Description: ${data.description}
982
+ - Type: ${data.project_type}
983
+ - App root: ${data.app_root || '.'}
984
+
985
+ ## Stack
986
+
987
+ - Language: ${data.primary_language}
988
+ - Frameworks: ${(data.frameworks || []).join(', ') || 'none'}
989
+ - Database: ${data.database || 'none'}
990
+ - Infrastructure: ${(data.infrastructure || []).join(', ') || 'none'}
991
+ - Package manager: ${data.package_manager || 'auto-detect'}
992
+ - Test framework: ${data.test_framework || 'auto-detect'}
993
+
994
+ ## Commands
995
+
996
+ - Install: \`${commands.install}\`
997
+ - Dev: \`${commands.dev}\`
998
+ - Build: \`${commands.build}\`
999
+ - Test: \`${commands.test}\`
1000
+ - Lint: \`${commands.lint}\`
1001
+
1002
+ ## Agent Routing
1003
+
1004
+ | Task Type | Agent | Primary Skills |
1005
+ |-----------|-------|----------------|
1006
+ ${agentRows}
1007
+
1008
+ ## Auto-Resolved Skills
1009
+
1010
+ ${skillsList}
1011
+
1012
+ ## Scan Evidence
1013
+
1014
+ ${evidence}
1015
+ `;
1016
+ }
1017
+
1018
+ function generateArchitectureDoc(data) {
1019
+ const appRootLine = data.app_root && data.app_root !== '.'
1020
+ ? `- App root is currently detected as \`${data.app_root}\`.`
1021
+ : '- App root is the repository root unless refined below.';
1022
+ const evidence = (data.scan_evidence || []).length > 0
1023
+ ? (data.scan_evidence || []).map(line => `- ${line}`).join('\n')
1024
+ : '- TODO: refine architecture by scanning source directories.';
1025
+
1026
+ return `# Architecture Notes
1027
+
1028
+ ## Current Map
1029
+
1030
+ ${appRootLine}
1031
+ - TODO: refine major directories and responsibilities.
1032
+ - TODO: refine main entry points, routing, state boundaries, and integration boundaries.
1033
+
1034
+ ## Detected Evidence
1035
+
1036
+ ${evidence}
1037
+
1038
+ ## AI Maintenance Rule
1039
+
1040
+ Before non-trivial implementation work, inspect this file. If it is stale or still contains TODO items relevant to the task, update it from the code before editing product code.
1041
+ `;
1042
+ }
1043
+
1044
+ function generateConventionsDoc(data) {
1045
+ return `# Coding Conventions
1046
+
1047
+ ## Current Standards
1048
+
1049
+ - Maximum file length: ${data.max_file_lines || '300'} lines unless the existing project standard is stricter.
1050
+ - Code comments and identifiers should be written in English.
1051
+ - Keep changes scoped to the user request.
1052
+ - Prefer existing local patterns over introducing new abstractions.
1053
+ - TODO: refine naming, formatting, folder, API, state, styling, and testing conventions from the real codebase.
1054
+
1055
+ ## Verification
1056
+
1057
+ - Run the focused test or lint command that matches the touched area.
1058
+ - If no automated check exists, document the manual verification performed.
1059
+ - Do not claim completion without verification evidence.
1060
+ `;
1061
+ }
1062
+
1063
+ function generateWorkflowDoc() {
1064
+ return `# Agent Workflow
1065
+
1066
+ ## Before Every Task
1067
+
1068
+ 1. Read AGENTS.md.
1069
+ 2. Read .agent/docs/project.md.
1070
+ 3. Classify the request: question, survey, simple fix, feature, refactor, debug, UI, security, or deploy.
1071
+ 4. Read only the relevant docs under .agent/agents, .agent/skills, and .agent/workflows.
1072
+
1073
+ ## Implementation Flow
1074
+
1075
+ 1. State assumptions and success criteria when the task is not trivial.
1076
+ 2. Inspect dependent files before editing.
1077
+ 3. Make the smallest coherent change.
1078
+ 4. Add or update focused tests when behavior changes.
1079
+ 5. Run verification.
1080
+ 6. Summarize changed files and verification evidence.
1081
+
1082
+ ## When To Update Docs
1083
+
1084
+ - Update .agent/docs/architecture.md when structure, boundaries, or entry points change.
1085
+ - Update .agent/docs/conventions.md when repeated project patterns become clear.
1086
+ - Update .agent/docs/project.md when stack, scripts, app root, test tooling, or routing changes.
1087
+ - Keep AGENTS.md concise. Put project-specific detail in .agent/docs.
1088
+ `;
1089
+ }
1090
+
1091
+ // --- Agent Folder Copy ---
1092
+
1093
+ function copyAgentFolder() {
1094
+ const srcAgent = path.join(sourceDir, '.agent');
1095
+ if (!fs.existsSync(srcAgent)) return { copied: 0, skipped: 0 };
1096
+
1097
+ const destAgent = path.join(targetDir, '.agent');
1098
+ let copied = 0;
1099
+ let skipped = 0;
1100
+
1101
+ function copyDir(src, dest) {
1102
+ fs.mkdirSync(dest, { recursive: true });
1103
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
1104
+ const srcPath = path.join(src, entry.name);
1105
+ const destPath = path.join(dest, entry.name);
1106
+ const relativeSrc = path.relative(srcAgent, srcPath).split(path.sep).join('/');
1107
+ if (relativeSrc === 'docs' || relativeSrc.startsWith('docs/')) {
1108
+ continue;
1293
1109
  }
1294
- ]);
1295
- projectSetup = modeAnswer.project_setup || 'new';
1296
- useScan = modeAnswer.existing_mode === 'scan';
1297
- }
1298
-
1299
- if (useScan) {
1300
- console.log(pc.dim(' Scan mode: detecting stack from current project files...\n'));
1301
- } else if (projectSetup === 'new') {
1302
- console.log(pc.dim(' Manual mode: asking for project context explicitly.\n'));
1303
- }
1304
-
1305
- if (fs.existsSync(path.join(targetDir, 'PROJECT_CONTEXT.md')) && !assumeYes) {
1306
- const res = await askQuestions({
1307
- type: 'confirm',
1308
- name: 'overwrite',
1309
- message: 'PROJECT_CONTEXT.md already exists in this folder. Overwrite?',
1110
+ if (entry.isDirectory()) {
1111
+ copyDir(srcPath, destPath);
1112
+ } else {
1113
+ if (fs.existsSync(destPath)) {
1114
+ skipped++;
1115
+ } else {
1116
+ fs.copyFileSync(srcPath, destPath);
1117
+ copied++;
1118
+ }
1119
+ }
1120
+ }
1121
+ }
1122
+
1123
+ copyDir(srcAgent, destAgent);
1124
+ return { copied, skipped };
1125
+ }
1126
+
1127
+ async function writeGeneratedFile(relativePath, content, assumeYes) {
1128
+ const outputPath = path.join(targetDir, relativePath);
1129
+ const exists = fs.existsSync(outputPath);
1130
+ if (exists && !assumeYes) {
1131
+ const res = await askQuestions({
1132
+ type: 'confirm',
1133
+ name: 'overwrite',
1134
+ message: `${relativePath} already exists. Overwrite?`,
1310
1135
  initial: false
1311
1136
  });
1312
- if (!res.overwrite) {
1313
- console.log(pc.yellow('\nAborted. Existing project context unchanged.'));
1314
- process.exit(0);
1315
- }
1316
- }
1317
-
1318
- const scanned = useScan ? scanProject() : null;
1319
- let reviewScannedFields = !useScan;
1320
- let requestAiRefinement = false;
1321
-
1322
- if (useScan) {
1323
- console.log(` ${pc.bold('Detected context:')}`);
1324
- formatScanPreviewLines(scanned).forEach(([label, value]) => {
1325
- console.log(` · ${label}: ${value}`);
1326
- });
1327
- console.log('');
1328
- if ((scanned.scan_evidence || []).length > 0) {
1329
- console.log(` ${pc.bold('Evidence:')}`);
1330
- scanned.scan_evidence.forEach(line => console.log(` · ${line}`));
1331
- console.log('');
1332
- }
1333
-
1334
- if (!assumeYes) {
1335
- const scanAction = await askQuestions({
1336
- type: 'select',
1337
- name: 'scan_action',
1338
- message: 'Use detected context as-is or review fields manually?',
1339
- choices: [
1340
- { title: 'Use detected context', value: 'accept' },
1341
- { title: 'Review detected fields', value: 'review' }
1342
- ],
1343
- initial: 0
1344
- });
1345
- reviewScannedFields = scanAction.scan_action === 'review';
1346
- }
1347
- }
1348
-
1349
- const { project_name, description, project_type } = await askQuestions([
1350
- { type: assumeYes || !reviewScannedFields ? null : 'text', name: 'project_name', message: 'Project name:', initial: scanned?.project_name || path.basename(targetDir) },
1351
- { type: assumeYes || !reviewScannedFields ? null : 'text', name: 'description', message: 'Short description:', initial: scanned?.description || 'A software project' },
1352
- {
1353
- type: assumeYes || !reviewScannedFields ? null : 'select', name: 'project_type', message: 'Project type:',
1354
- choices: PROJECT_TYPE_CHOICES.map(value => ({ title: value, value })),
1355
- initial: Math.max(PROJECT_TYPE_CHOICES.indexOf(scanned?.project_type || 'web-app'), 0)
1356
- }
1357
- ]);
1358
-
1359
- const { primary_language } = await askQuestions({
1360
- type: assumeYes || !reviewScannedFields ? null : 'select',
1361
- name: 'primary_language',
1362
- message: 'Primary language:',
1363
- choices: LANGUAGE_CHOICES.map(l => ({ title: l, value: l })),
1364
- initial: Math.max(LANGUAGE_CHOICES.indexOf(scanned?.primary_language || 'typescript'), 0)
1365
- });
1366
-
1367
- const { frameworks } = await askQuestions({
1368
- type: assumeYes || !reviewScannedFields ? null : 'multiselect',
1369
- name: 'frameworks',
1370
- message: 'Frameworks (Space to select, Enter to confirm):',
1371
- choices: FRAMEWORK_CHOICES.map(f => ({ title: f, value: f, selected: (scanned?.frameworks || []).includes(f) })),
1372
- min: 0
1373
- });
1374
-
1375
- const dbAndInfra = await askQuestions([
1376
- {
1377
- type: assumeYes || !reviewScannedFields ? null : 'select', name: 'database', message: 'Database / ORM:',
1378
- choices: DATABASE_CHOICES.map(d => ({ title: d, value: d })),
1379
- initial: Math.max(DATABASE_CHOICES.indexOf(scanned?.database || 'none'), 0)
1380
- },
1381
- { type: assumeYes || !reviewScannedFields ? null : 'confirm', name: 'uses_docker', message: 'Uses Docker / Docker Compose?', initial: scanned?.uses_docker ?? true },
1382
- {
1383
- type: assumeYes || !reviewScannedFields ? null : 'select', name: 'cloud', message: 'Cloud Provider:',
1384
- choices: CLOUD_CHOICES.map(c => ({ title: c, value: c })),
1385
- initial: Math.max(CLOUD_CHOICES.indexOf(scanned?.cloud || 'none'), 0)
1386
- }
1387
- ]);
1388
-
1389
- const { package_manager, test_framework, max_file_lines } = await askQuestions([
1390
- {
1391
- type: assumeYes || !reviewScannedFields ? null : 'select', name: 'package_manager', message: 'Package Manager:',
1392
- choices: PACKAGE_MANAGER_CHOICES.map(p => ({ title: p, value: p })),
1393
- initial: Math.max(PACKAGE_MANAGER_CHOICES.indexOf(scanned?.package_manager || 'auto-detect'), 0)
1394
- },
1395
- { type: assumeYes || !reviewScannedFields ? null : 'text', name: 'test_framework', message: 'Test Framework (jest, phpunit, etc):', initial: scanned?.test_framework || 'auto-detect' },
1396
- { type: assumeYes || !reviewScannedFields ? null : 'text', name: 'max_file_lines', message: 'Max lines per file:', initial: scanned?.max_file_lines || '300' }
1397
- ]);
1398
-
1399
- // Combine data
1400
- const data = {
1401
- project_name: project_name || scanned?.project_name || path.basename(targetDir),
1402
- description: description || scanned?.description || 'A software project',
1403
- project_type: project_type || scanned?.project_type || 'web-app',
1404
- primary_language: primary_language || scanned?.primary_language || 'typescript',
1405
- frameworks: frameworks || scanned?.frameworks || [],
1406
- ...dbAndInfra, package_manager, test_framework, max_file_lines
1407
- };
1408
-
1409
- data.database = data.database || scanned?.database || 'none';
1410
- data.uses_docker = typeof data.uses_docker === 'boolean' ? data.uses_docker : (scanned?.uses_docker ?? true);
1411
- data.cloud = data.cloud || scanned?.cloud || 'none';
1412
- data.package_manager = data.package_manager || scanned?.package_manager || 'auto-detect';
1413
- data.test_framework = data.test_framework || scanned?.test_framework || 'auto-detect';
1414
- data.max_file_lines = data.max_file_lines || scanned?.max_file_lines || '300';
1415
- data.framework_details = scanned?.framework_details || [];
1416
- data.scan_evidence = scanned?.scan_evidence || [];
1417
- data.app_root = scanned?.app_root || '.';
1418
-
1419
- data.infrastructure = [];
1420
- if (data.uses_docker) data.infrastructure.push('docker');
1421
- if (data.cloud !== 'none') data.infrastructure.push(data.cloud);
1422
-
1423
- console.log(`\n${pc.bold(pc.cyan('━━ Generating Files ━━'))}`);
1424
-
1425
- // Write AGENTS.md — the only output file
1426
- const agentsMdPath = path.join(targetDir, 'AGENTS.md');
1427
- const agentsMdExists = fs.existsSync(agentsMdPath);
1428
- if (agentsMdExists && !assumeYes) {
1429
- const res = await askQuestions({
1430
- type: 'confirm',
1431
- name: 'overwrite',
1432
- message: 'AGENTS.md already exists. Overwrite?',
1433
- initial: false
1434
- });
1435
- if (!res.overwrite) {
1436
- console.log(pc.yellow('\nAborted. Existing AGENTS.md unchanged.'));
1437
- process.exit(0);
1438
- }
1439
- }
1440
-
1441
- fs.writeFileSync(agentsMdPath, generateAgentsMd(data), 'utf8');
1442
- console.log(` ${pc.green('✓')} Generated ${pc.bold('AGENTS.md')}`);
1443
-
1444
- console.log(`\n${pc.bold(pc.green('━━ Done! ━━'))}
1445
-
1446
- ${pc.bold('Project:')} ${data.project_name}
1447
- ${pc.bold('Stack:')} ${data.primary_language} | ${(data.frameworks || []).join(', ') || 'no framework'}
1448
-
1449
- ${pc.bold('Generated:')} ${pc.bold(agentsMdPath)}
1450
-
1137
+ if (!res.overwrite) return false;
1138
+ }
1139
+
1140
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
1141
+ fs.writeFileSync(outputPath, content, 'utf8');
1142
+ return true;
1143
+ }
1144
+
1145
+ // --- Main CLI ---
1146
+
1147
+ async function main() {
1148
+ const rawArgs = process.argv.slice(2);
1149
+ const command = rawArgs[0] && !rawArgs[0].startsWith('-') ? rawArgs[0] : 'init';
1150
+ const args = command === 'init' ? rawArgs.slice(rawArgs[0] === 'init' ? 1 : 0) : rawArgs.slice(1);
1151
+
1152
+ if (rawArgs.includes('--version') || rawArgs.includes('-v')) {
1153
+ console.log(packageJson.version);
1154
+ return;
1155
+ }
1156
+
1157
+ if (rawArgs.includes('--help') || rawArgs.includes('-h')) {
1158
+ printHelp();
1159
+ return;
1160
+ }
1161
+
1162
+ if (command !== 'init') {
1163
+ console.error(pc.red(`Unknown command: ${command}`));
1164
+ printHelp();
1165
+ process.exit(1);
1166
+ }
1167
+
1168
+ let useScan = args.includes('--scan');
1169
+ const assumeYes = args.includes('--yes') || args.includes('-y');
1170
+
1171
+ console.log(pc.bold(pc.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')));
1172
+ console.log(' ' + pc.bold('thachvd-kit') + ' — Project Init Wizard');
1173
+ console.log(` Generates AGENTS.md + .agent/ folder${useScan ? ' (scan-assisted)' : ''}`);
1174
+ console.log(pc.bold(pc.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')) + '\n');
1175
+
1176
+ let projectSetup = 'new';
1177
+ if (!useScan && !assumeYes) {
1178
+ const modeAnswer = await askQuestions([
1179
+ {
1180
+ type: 'select',
1181
+ name: 'project_setup',
1182
+ message: 'Project already exists or are you starting fresh?',
1183
+ choices: [
1184
+ { title: 'Existing project', value: 'existing' },
1185
+ { title: 'New project', value: 'new' }
1186
+ ]
1187
+ },
1188
+ {
1189
+ type: prev => prev === 'existing' ? 'select' : null,
1190
+ name: 'existing_mode',
1191
+ message: 'How should thachvd-kit build project rules?',
1192
+ choices: [
1193
+ { title: 'Scan current project files', value: 'scan' },
1194
+ { title: 'Enter context manually', value: 'manual' }
1195
+ ],
1196
+ initial: 0
1197
+ }
1198
+ ]);
1199
+ projectSetup = modeAnswer.project_setup || 'new';
1200
+ useScan = modeAnswer.existing_mode === 'scan';
1201
+ }
1202
+
1203
+ if (useScan) {
1204
+ console.log(pc.dim(' Scan mode: detecting stack from current project files...\n'));
1205
+ } else if (projectSetup === 'new') {
1206
+ console.log(pc.dim(' Manual mode: asking for project context explicitly.\n'));
1207
+ }
1208
+
1209
+ const scanned = useScan ? scanProject() : null;
1210
+ let reviewScannedFields = !useScan;
1211
+ let requestAiRefinement = false;
1212
+
1213
+ if (useScan) {
1214
+ console.log(` ${pc.bold('Detected context:')}`);
1215
+ formatScanPreviewLines(scanned).forEach(([label, value]) => {
1216
+ console.log(` · ${label}: ${value}`);
1217
+ });
1218
+ console.log('');
1219
+ if ((scanned.scan_evidence || []).length > 0) {
1220
+ console.log(` ${pc.bold('Evidence:')}`);
1221
+ scanned.scan_evidence.forEach(line => console.log(` · ${line}`));
1222
+ console.log('');
1223
+ }
1224
+
1225
+ if (!assumeYes) {
1226
+ const scanAction = await askQuestions({
1227
+ type: 'select',
1228
+ name: 'scan_action',
1229
+ message: 'Use detected context as-is or review fields manually?',
1230
+ choices: [
1231
+ { title: 'Use detected context', value: 'accept' },
1232
+ { title: 'Review detected fields', value: 'review' }
1233
+ ],
1234
+ initial: 0
1235
+ });
1236
+ reviewScannedFields = scanAction.scan_action === 'review';
1237
+ }
1238
+ }
1239
+
1240
+ const { project_name, description, project_type } = await askQuestions([
1241
+ { type: assumeYes || !reviewScannedFields ? null : 'text', name: 'project_name', message: 'Project name:', initial: scanned?.project_name || path.basename(targetDir) },
1242
+ { type: assumeYes || !reviewScannedFields ? null : 'text', name: 'description', message: 'Short description:', initial: scanned?.description || 'A software project' },
1243
+ {
1244
+ type: assumeYes || !reviewScannedFields ? null : 'select', name: 'project_type', message: 'Project type:',
1245
+ choices: PROJECT_TYPE_CHOICES.map(value => ({ title: value, value })),
1246
+ initial: Math.max(PROJECT_TYPE_CHOICES.indexOf(scanned?.project_type || 'web-app'), 0)
1247
+ }
1248
+ ]);
1249
+
1250
+ const { primary_language } = await askQuestions({
1251
+ type: assumeYes || !reviewScannedFields ? null : 'select',
1252
+ name: 'primary_language',
1253
+ message: 'Primary language:',
1254
+ choices: LANGUAGE_CHOICES.map(l => ({ title: l, value: l })),
1255
+ initial: Math.max(LANGUAGE_CHOICES.indexOf(scanned?.primary_language || 'typescript'), 0)
1256
+ });
1257
+
1258
+ const { frameworks } = await askQuestions({
1259
+ type: assumeYes || !reviewScannedFields ? null : 'multiselect',
1260
+ name: 'frameworks',
1261
+ message: 'Frameworks (Space to select, Enter to confirm):',
1262
+ choices: FRAMEWORK_CHOICES.map(f => ({ title: f, value: f, selected: (scanned?.frameworks || []).includes(f) })),
1263
+ min: 0
1264
+ });
1265
+
1266
+ const dbAndInfra = await askQuestions([
1267
+ {
1268
+ type: assumeYes || !reviewScannedFields ? null : 'select', name: 'database', message: 'Database / ORM:',
1269
+ choices: DATABASE_CHOICES.map(d => ({ title: d, value: d })),
1270
+ initial: Math.max(DATABASE_CHOICES.indexOf(scanned?.database || 'none'), 0)
1271
+ },
1272
+ { type: assumeYes || !reviewScannedFields ? null : 'confirm', name: 'uses_docker', message: 'Uses Docker / Docker Compose?', initial: scanned?.uses_docker ?? true },
1273
+ {
1274
+ type: assumeYes || !reviewScannedFields ? null : 'select', name: 'cloud', message: 'Cloud Provider:',
1275
+ choices: CLOUD_CHOICES.map(c => ({ title: c, value: c })),
1276
+ initial: Math.max(CLOUD_CHOICES.indexOf(scanned?.cloud || 'none'), 0)
1277
+ }
1278
+ ]);
1279
+
1280
+ const { package_manager, test_framework, max_file_lines } = await askQuestions([
1281
+ {
1282
+ type: assumeYes || !reviewScannedFields ? null : 'select', name: 'package_manager', message: 'Package Manager:',
1283
+ choices: PACKAGE_MANAGER_CHOICES.map(p => ({ title: p, value: p })),
1284
+ initial: Math.max(PACKAGE_MANAGER_CHOICES.indexOf(scanned?.package_manager || 'auto-detect'), 0)
1285
+ },
1286
+ { type: assumeYes || !reviewScannedFields ? null : 'text', name: 'test_framework', message: 'Test Framework (jest, phpunit, etc):', initial: scanned?.test_framework || 'auto-detect' },
1287
+ { type: assumeYes || !reviewScannedFields ? null : 'text', name: 'max_file_lines', message: 'Max lines per file:', initial: scanned?.max_file_lines || '300' }
1288
+ ]);
1289
+
1290
+ // Combine data
1291
+ const data = {
1292
+ project_name: project_name || scanned?.project_name || path.basename(targetDir),
1293
+ description: description || scanned?.description || 'A software project',
1294
+ project_type: project_type || scanned?.project_type || 'web-app',
1295
+ primary_language: primary_language || scanned?.primary_language || 'typescript',
1296
+ frameworks: frameworks || scanned?.frameworks || [],
1297
+ ...dbAndInfra, package_manager, test_framework, max_file_lines
1298
+ };
1299
+
1300
+ data.database = data.database || scanned?.database || 'none';
1301
+ data.uses_docker = typeof data.uses_docker === 'boolean' ? data.uses_docker : (scanned?.uses_docker ?? true);
1302
+ data.cloud = data.cloud || scanned?.cloud || 'none';
1303
+ data.package_manager = data.package_manager || scanned?.package_manager || 'auto-detect';
1304
+ data.test_framework = data.test_framework || scanned?.test_framework || 'auto-detect';
1305
+ data.max_file_lines = data.max_file_lines || scanned?.max_file_lines || '300';
1306
+ data.framework_details = scanned?.framework_details || [];
1307
+ data.scan_evidence = scanned?.scan_evidence || [];
1308
+ data.app_root = scanned?.app_root || '.';
1309
+
1310
+ data.infrastructure = [];
1311
+ if (data.uses_docker) data.infrastructure.push('docker');
1312
+ if (data.cloud !== 'none') data.infrastructure.push(data.cloud);
1313
+
1314
+ console.log(`\n${pc.bold(pc.cyan('Generating Files'))}`);
1315
+
1316
+ // Shared entry files are written after the .agent folder is available.
1317
+
1318
+ // Copy .agent folder
1319
+ const { copied, skipped } = copyAgentFolder();
1320
+ if (copied > 0) {
1321
+ console.log(` ${pc.green('OK')} Copied ${pc.bold('.agent/')} (${copied} file${copied !== 1 ? 's' : ''} added${skipped > 0 ? `, ${skipped} skipped` : ''})`);
1322
+ } else if (skipped > 0) {
1323
+ console.log(` ${pc.yellow('~')} ${pc.bold('.agent/')} already exists - ${skipped} file${skipped !== 1 ? 's' : ''} skipped (no overwrite)`);
1324
+ }
1325
+
1326
+
1327
+ const generatedFiles = [
1328
+ ['AGENTS.md', generateSharedAgentsMd(data)],
1329
+ ['CLAUDE.md', generateSharedClaudeMd()],
1330
+ ['GEMINI.md', generateSharedGeminiMd()],
1331
+ [path.join('.agent', 'docs', 'project.md'), generateProjectDoc(data)],
1332
+ [path.join('.agent', 'docs', 'architecture.md'), generateArchitectureDoc(data)],
1333
+ [path.join('.agent', 'docs', 'conventions.md'), generateConventionsDoc(data)],
1334
+ [path.join('.agent', 'docs', 'workflow.md'), generateWorkflowDoc()]
1335
+ ];
1336
+
1337
+ for (const [relativePath, content] of generatedFiles) {
1338
+ const wrote = await writeGeneratedFile(relativePath, content, assumeYes);
1339
+ if (wrote) console.log(` ${pc.green('OK')} Generated ${pc.bold(relativePath)}`);
1340
+ else console.log(` ${pc.yellow('~')} Skipped ${pc.bold(relativePath)}`);
1341
+ }
1342
+
1343
+ console.log(`\n${pc.bold(pc.green('Done!'))}
1344
+
1345
+ ${pc.bold('Project:')} ${data.project_name}
1346
+ ${pc.bold('Stack:')} ${data.primary_language} | ${(data.frameworks || []).join(', ') || 'no framework'}
1347
+
1348
+ ${pc.bold('Generated:')}
1349
+ - ${pc.bold('AGENTS.md')} shared entry for Codex, Antigravity, and Claude Code
1350
+ - ${pc.bold('CLAUDE.md')} Claude Code entry that imports AGENTS.md
1351
+ - ${pc.bold('GEMINI.md')} Antigravity entry
1352
+ - ${pc.bold('.agent/docs/')} scan-based project rules
1353
+ - ${pc.bold('.agent/')} skills, workflows, agents, and rules
1354
+
1451
1355
  ${pc.bold('Next steps:')}
1452
- 1. ${useScan ? `Open ${pc.bold('AGENTS.md')} — paste its content into your AI editor to fill in the placeholders` : `Edit ${pc.bold('AGENTS.md')} and fill in Core Architecture, Coding Conventions, Performance sections`}
1453
- 2. Commit ${pc.bold('AGENTS.md')} to the repo
1454
- 3. Open project in Claude Code / Codex / Antigravity — agent reads AGENTS.md automatically
1356
+ 1. Review ${pc.bold('.agent/docs/')} and replace any TODO: refine items with real project facts
1357
+ 2. Commit ${pc.bold('AGENTS.md')}, ${pc.bold('CLAUDE.md')}, ${pc.bold('GEMINI.md')}, and ${pc.bold('.agent/')}
1358
+ 3. Open the project in Codex, Antigravity, or Claude Code
1359
+
1360
+ ${pc.bold('Copy this prompt into your AI editor:')}
1361
+ ${pc.dim('---')}
1362
+ Read AGENTS.md and all files under .agent/docs/.
1363
+ Scan this repository.
1364
+ Update .agent/docs/project.md, .agent/docs/architecture.md, and .agent/docs/conventions.md with factual project-specific rules.
1365
+ Do not implement product code.
1366
+ Remove TODO: refine only when backed by evidence from the codebase.
1367
+ ${pc.dim('---')}
1455
1368
  `);
1456
1369
  }
1457
-
1458
- main().catch(err => {
1459
- console.error(pc.red('\nError during init:'), err);
1460
- process.exit(1);
1461
- });
1370
+
1371
+ main().catch(err => {
1372
+ console.error(pc.red('\nError during init:'), err);
1373
+ process.exit(1);
1374
+ });