thachvd-kit 1.0.24 → 1.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/cli.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const fs = require('fs');
4
- const path = require('path');
5
- const { spawnSync } = require('child_process');
6
- const prompts = require('prompts');
7
- const pc = require('picocolors');
8
- const packageJson = require('../package.json');
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { spawnSync } = require('child_process');
6
+ const prompts = require('prompts');
7
+ const pc = require('picocolors');
8
+ const packageJson = require('../package.json');
9
9
 
10
10
  const sourceDir = path.resolve(__dirname, '..');
11
11
  const targetDir = process.cwd();
@@ -63,7 +63,7 @@ const INFRA_SKILLS = {
63
63
  vercel: ["deployment-procedures"],
64
64
  };
65
65
 
66
- function resolveSkills(data) {
66
+ function resolveSkills(data) {
67
67
  const skills = new Set();
68
68
  const lang = (data.primary_language || "").toLowerCase();
69
69
 
@@ -88,16 +88,16 @@ function resolveSkills(data) {
88
88
  skills.add("clean-code");
89
89
  skills.add("systematic-debugging");
90
90
 
91
- return Array.from(skills).sort();
92
- }
93
-
94
- function resolveNativeSkills(data) {
95
- return Array.from(new Set([
96
- ...resolveSkills(data),
97
- 'verification-before-completion',
98
- 'webapp-testing'
99
- ])).sort();
100
- }
91
+ return Array.from(skills).sort();
92
+ }
93
+
94
+ function resolveNativeSkills(data) {
95
+ return Array.from(new Set([
96
+ ...resolveSkills(data),
97
+ 'verification-before-completion',
98
+ 'webapp-testing'
99
+ ])).sort();
100
+ }
101
101
 
102
102
  const FRAMEWORK_CHOICES = [
103
103
  'vue', 'react', 'next', 'nuxt', 'svelte', 'angular',
@@ -120,59 +120,59 @@ const IGNORED_SCAN_DIRS = new Set(['.git', 'node_modules', 'vendor', 'dist', 'bu
120
120
 
121
121
  function printHelp() {
122
122
  console.log(`
123
- ${pc.bold('Usage:')}
124
- thachvd-kit [--yes]
125
- thachvd-kit init [--yes]
126
- thachvd-kit --version
127
- thachvd-kit --help
128
-
129
- ${pc.bold('Commands:')}
130
- init Bootstrap shared AI-agent rules and tooling
131
-
132
- ${pc.bold('Options:')}
133
- --yes Overwrite generated files when they already exist
134
- --no-setup-mcp Skip local Codegraph and Playwright MCP setup
135
- --version Show the installed CLI version
136
- --help Show this help message
137
-
138
- ${pc.bold('What gets generated:')}
139
- AGENTS.md Shared instructions for Codex, Antigravity, Claude Code, and Cursor
140
- CLAUDE.md Claude Code entry file that imports AGENTS.md
141
- GEMINI.md Antigravity entry file that points to AGENTS.md
142
- .cursorrules Cursor entry file that points to AGENTS.md
143
- .agent/ Shared docs, skills, workflows, agents, and rules
144
- ~/.codex/skills/ Selected Codex skills copied to global user dir (shows in $ menu)
145
- .claude/skills/ Selected Claude Code project skills copied from .agent/skills
146
-
147
- ${pc.bold('Workflow guide:')}
148
- Idea is fuzzy /brainstorm Explore options, tradeoffs, and recommended direction
149
- New app from scratch /create Turn an app idea into plan + implementation flow
150
- Feature is clear /plan Create docs/PLAN-*.md first, no code yet
151
- Existing app update /enhance Add or change a feature in an existing codebase
152
- Bug or failing behavior /debug Investigate symptoms, hypotheses, root cause, fix
153
- UI / UX work ui-ux-pro-max, frontend-specialist, $frontend-design, $webapp-testing
154
- Run or add tests /test Generate tests, run tests, check coverage
155
- Preview locally /preview Start, stop, restart, or health-check dev server
156
- Deploy / infra /deploy Release, hosting, Docker, cloud, environment setup
157
- Project state /status Summarize stack, progress, preview, pending work
158
- Multi-domain work /orchestrate Coordinate frontend, backend, data, security, QA
159
-
160
- ${pc.bold('Common explicit skill prompts:')}
161
- Use /brainstorm for this feature idea before planning.
162
- Use /plan for this feature; do not write code yet.
163
- Use /enhance to implement this planned feature.
164
- Use $clean-code before editing this module.
165
- Use $systematic-debugging to investigate this bug.
166
- Use $webapp-testing to verify the UI with Playwright.
167
- Use $verification-before-completion before claiming done.
168
- Use $writing-skills to improve a SKILL.md description.
169
-
170
- ${pc.bold('Tip:')}
171
- Skills are copied for Codex/Claude, but you can call them explicitly with $skill-name
172
- when implicit matching does not fire.
173
- `);
174
-
175
- }
123
+ ${pc.bold('Usage:')}
124
+ thachvd-kit [--yes]
125
+ thachvd-kit init [--yes]
126
+ thachvd-kit --version
127
+ thachvd-kit --help
128
+
129
+ ${pc.bold('Commands:')}
130
+ init Bootstrap shared AI-agent rules and tooling
131
+
132
+ ${pc.bold('Options:')}
133
+ --yes Overwrite generated files when they already exist
134
+ --no-setup-mcp Skip local Codegraph and Playwright MCP setup
135
+ --version Show the installed CLI version
136
+ --help Show this help message
137
+
138
+ ${pc.bold('What gets generated:')}
139
+ AGENTS.md Shared instructions for Codex, Antigravity, Claude Code, and Cursor
140
+ CLAUDE.md Claude Code entry file that imports AGENTS.md
141
+ GEMINI.md Antigravity entry file that points to AGENTS.md
142
+ .cursorrules Cursor entry file that points to AGENTS.md
143
+ .agent/ Shared docs, skills, workflows, agents, and rules
144
+ ~/.codex/skills/ Selected Codex skills copied to global user dir (shows in $ menu)
145
+ .claude/skills/ Selected Claude Code project skills copied from .agent/skills
146
+
147
+ ${pc.bold('Workflow guide:')}
148
+ Idea is fuzzy /brainstorm Explore options, tradeoffs, and recommended direction
149
+ New app from scratch /create Turn an app idea into plan + implementation flow
150
+ Feature is clear /plan Create docs/PLAN-*.md first, no code yet
151
+ Existing app update /enhance Add or change a feature in an existing codebase
152
+ Bug or failing behavior /debug Investigate symptoms, hypotheses, root cause, fix
153
+ UI / UX work ui-ux-pro-max, frontend-specialist, $frontend-design, $webapp-testing
154
+ Run or add tests /test Generate tests, run tests, check coverage
155
+ Preview locally /preview Start, stop, restart, or health-check dev server
156
+ Deploy / infra /deploy Release, hosting, Docker, cloud, environment setup
157
+ Project state /status Summarize stack, progress, preview, pending work
158
+ Multi-domain work /orchestrate Coordinate frontend, backend, data, security, QA
159
+
160
+ ${pc.bold('Common explicit skill prompts:')}
161
+ Use /brainstorm for this feature idea before planning.
162
+ Use /plan for this feature; do not write code yet.
163
+ Use /enhance to implement this planned feature.
164
+ Use $clean-code before editing this module.
165
+ Use $systematic-debugging to investigate this bug.
166
+ Use $webapp-testing to verify the UI with Playwright.
167
+ Use $verification-before-completion before claiming done.
168
+ Use $writing-skills to improve a SKILL.md description.
169
+
170
+ ${pc.bold('Tip:')}
171
+ Skills are copied for Codex/Claude, but you can call them explicitly with $skill-name
172
+ when implicit matching does not fire.
173
+ `);
174
+
175
+ }
176
176
 
177
177
  function formatScanPreviewLines(scanned) {
178
178
  return [
@@ -886,855 +886,872 @@ function resolveAgentTable(data) {
886
886
  rows.push(["Debug", "debugger", "systematic-debugging"]);
887
887
  rows.push(["Security", "security-auditor", "vulnerability-scanner"]);
888
888
  rows.push(["Planning", "project-planner", "brainstorming, plan-writing"]);
889
-
890
889
  return rows;
891
- }
892
- // --- File Generators ---
893
-
894
- function generateSharedAgentsMd(data) {
895
- return `# AGENTS.md
896
-
897
- Shared operating instructions for Codex, Antigravity, Claude Code, and Cursor.
898
-
899
- ## Startup
900
-
901
- 1. Read this file first.
902
- 2. Read \`.agent/docs/project.md\` for detected stack, commands, and routing.
903
- 3. Read \`.agent/docs/workflow.md\` for the task flow before editing.
904
- 4. Read \`.agent/docs/architecture.md\` and \`.agent/docs/conventions.md\` before planning code changes.
905
- 5. Read \`.agent/docs/tooling.md\` when the task needs Playwright, codegraph, MCP, or native Codex/Claude skill setup.
906
- 6. Select and load only the agent, skill, or workflow files that match the current task.
907
-
908
- If any \`.agent/docs/*.md\` file still contains \`TODO: refine\`, update the docs by scanning the project before making product code changes.
909
-
910
- ## Task Flow
911
-
912
- - Questions and analysis: answer directly, cite relevant files when useful, and do not edit code.
913
- - Simple fix: inspect dependencies, make the smallest change, run focused verification.
914
- - Feature or refactor: define success criteria, make a short plan, implement, then verify.
915
- - Multi-domain work: use \`.agent/workflows/orchestrate.md\` and route to the relevant specialist docs.
916
- - UI work: read \`.agent/agents/frontend-specialist.md\` and applicable design skills before editing.
917
-
918
- ## Skill Loading
919
-
920
- - Treat \`.agent/skills/\` as the shared source of truth for all kit skills.
921
- - Codex exposes skills from \`~/.codex/skills/\` in the \`$\` menu; \`thachvd-kit\` copies selected skills there on init.
922
- - Claude Code project skills live in \`.claude/skills/\`.
923
- - If a skill is missing from the global dir, fall back to the matching \`.agent/skills/<skill>/SKILL.md\` file.
924
- - Do not load skill bodies by default; load a skill only when the user mentions it, the task clearly matches its description, or \`.agent/docs/project.md\` routes the task to it.
925
- - Prefer explicit skill mentions for predictable behavior: \`$clean-code\`, \`$systematic-debugging\`, \`$webapp-testing\`, or any skill listed in \`.agent/docs/project.md\`.
926
- - Run \`thachvd-kit --help\` to see common workflow and skill recommendations.
927
- - When creating or improving skills, use \`writing-skills\` first and make the \`description\` field specific enough for implicit invocation.
928
-
929
- ## Rules
930
-
931
- - Respond in the user's language; keep code, identifiers, and code comments in English.
932
- - State assumptions when the request is ambiguous.
933
- - Prefer the existing project style over new abstractions.
934
- - Keep changes surgical and remove only dead code introduced by your change.
935
- - **MCP First**: Prioritize using MCP server tools (e.g., \`codegraph\` for codebase search/symbol tracking, \`context7\` for API/docs queries, and \`playwright\` for browser/UI testing and verification) to explore, search, and verify rather than recursively listing directories, running expensive shell commands/grep, or reading large files. This minimizes token usage and maintains a cleaner context.
936
- - Tests or equivalent verification are mandatory before claiming done.
937
- - Keep files under ${data.max_file_lines || '300'} lines unless the project already has a different standard in \`.agent/docs/conventions.md\`.
938
-
939
- ## Shared Knowledge
940
-
941
- - Project docs: \`.agent/docs/\`
942
- - Specialist agents: \`.agent/agents/\`
943
- - Skills: \`.agent/skills/\`
944
- - Codex global skills: \`~/.codex/skills/\` (installed by thachvd-kit init)
945
- - Claude Code project skills: \`.claude/skills/\`
946
- - Workflows: \`.agent/workflows/\`
947
- - Tooling setup: \`.agent/docs/tooling.md\`
948
- - Antigravity mirror rules: \`.agent/rules/GEMINI.md\`
949
- - Cursor entry rules: \`.cursorrules\`
950
- `;
951
- }
952
-
953
- function generateSharedClaudeMd(data) {
954
- return `@AGENTS.md
955
-
956
- ${generateSharedAgentsMd(data)}
957
-
958
- ## Claude Code
959
-
960
- 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.
961
- `;
962
- }
963
-
964
- function generateSharedGeminiMd() {
965
- return `# GEMINI.md
966
-
967
- Antigravity entry file for this project.
968
-
969
- Read AGENTS.md first, then follow the shared docs under .agent/docs/.
970
- `;
971
- }
972
-
973
- function generateSharedCursorrules(data) {
974
- return `# Cursor Rules
975
-
976
- This repository uses AGENTS.md as the shared cross-agent entry file. Follow the instructions in AGENTS.md and keep Cursor-specific notes here only when they cannot apply to Codex, Antigravity, or Claude Code.
977
-
978
- Read AGENTS.md first, then follow the shared docs under .agent/docs/.
979
-
980
- ---
981
-
982
- ${generateSharedAgentsMd(data)}
983
- `;
984
- }
985
-
986
- function resolveCommands(data) {
987
- const frameworks = data.frameworks || [];
988
- const primaryLang = data.primary_language || 'javascript';
989
- const pkgManager = data.package_manager || 'npm';
990
- const testFramework = data.test_framework || 'auto-detect';
991
-
992
- const isPython = primaryLang === 'python';
993
- const isGo = primaryLang === 'go';
994
- const isRust = primaryLang === 'rust';
995
- const isPhp = primaryLang === 'php';
996
-
997
- let install = 'npm install';
998
- let dev = 'npm run dev';
999
- let build = 'npm run build';
1000
- let test = 'npm test';
1001
- let lint = 'npm run lint';
1002
-
1003
- if (pkgManager === 'pnpm') { install = 'pnpm install'; dev = 'pnpm dev'; build = 'pnpm build'; test = 'pnpm test'; lint = 'pnpm lint'; }
1004
- else if (pkgManager === 'yarn') { install = 'yarn'; dev = 'yarn dev'; build = 'yarn build'; test = 'yarn test'; lint = 'yarn lint'; }
1005
- else if (pkgManager === 'bun') { install = 'bun install'; dev = 'bun dev'; build = 'bun run build'; test = 'bun test'; lint = 'bun run lint'; }
1006
- else if (isPython) { install = 'pip install -r requirements.txt'; dev = 'python manage.py runserver'; build = '# no build step'; test = 'pytest'; lint = 'ruff check .'; }
1007
- else if (isGo) { install = 'go mod download'; dev = 'go run .'; build = 'go build ./...'; test = 'go test ./...'; lint = 'golangci-lint run'; }
1008
- else if (isRust) { install = 'cargo build'; dev = 'cargo run'; build = 'cargo build --release'; test = 'cargo test'; lint = 'cargo clippy'; }
1009
- else if (isPhp) {
1010
- install = 'composer install';
1011
- build = '# no build step';
1012
- test = testFramework === 'pest' ? './vendor/bin/pest' : './vendor/bin/phpunit';
1013
- const isCakePHP = frameworks.some(f => f.toLowerCase().includes('cakephp'));
1014
- const isSymfony = frameworks.some(f => f.toLowerCase().includes('symfony'));
1015
- const isCodeIgniter = frameworks.some(f => f.toLowerCase().includes('codeigniter'));
1016
- if (isCakePHP) {
1017
- dev = 'bin/cake server';
1018
- lint = './vendor/bin/phpcs';
1019
- } else if (isSymfony) {
1020
- dev = 'bin/console server:run';
1021
- lint = './vendor/bin/phpcs';
1022
- } else if (isCodeIgniter) {
1023
- dev = 'php spark serve';
1024
- lint = './vendor/bin/phpcs';
1025
- } else {
1026
- dev = 'php artisan serve';
1027
- lint = 'php artisan pint';
1028
- }
1029
- }
1030
-
1031
- if (testFramework === 'vitest') test = `${pkgManager === 'pnpm' ? 'pnpm' : pkgManager === 'yarn' ? 'yarn' : pkgManager === 'bun' ? 'bun run' : 'npx'} vitest`;
1032
- else if (testFramework === 'jest') test = `${pkgManager === 'pnpm' ? 'pnpm' : pkgManager === 'yarn' ? 'yarn' : 'npx'} jest`;
1033
-
1034
- return { install, dev, build, test, lint };
1035
- }
1036
-
1037
- function generateProjectDoc(data) {
1038
- const commands = resolveCommands(data);
1039
- const agentRows = resolveAgentTable(data).map(([task, agent, skills]) => `| ${task} | \`${agent}\` | ${skills} |`).join('\n');
1040
- const skillsList = resolveSkills(data).map(s => `- ${s}`).join('\n');
1041
- const evidence = (data.scan_evidence || []).length > 0
1042
- ? (data.scan_evidence || []).map(line => `- ${line}`).join('\n')
1043
- : '- TODO: refine scan evidence by inspecting the project.';
1044
-
1045
- return `# Project Rules
1046
-
1047
- Generated by thachvd-kit on ${new Date().toISOString().split('T')[0]}.
1048
-
1049
- ## Summary
1050
-
1051
- - Name: ${data.project_name}
1052
- - Description: ${data.description}
1053
- - Type: ${data.project_type}
1054
- - App root: ${data.app_root || '.'}
1055
-
1056
- ## Stack
1057
-
1058
- - Language: ${data.primary_language}
1059
- - Frameworks: ${(data.frameworks || []).join(', ') || 'none'}
1060
- - Database: ${data.database || 'none'}
1061
- - Infrastructure: ${(data.infrastructure || []).join(', ') || 'none'}
1062
- - Package manager: ${data.package_manager || 'auto-detect'}
1063
- - Test framework: ${data.test_framework || 'auto-detect'}
1064
-
1065
- ## Commands
1066
-
1067
- - Install: \`${commands.install}\`
1068
- - Dev: \`${commands.dev}\`
1069
- - Build: \`${commands.build}\`
1070
- - Test: \`${commands.test}\`
1071
- - Lint: \`${commands.lint}\`
1072
-
1073
- ## Agent Routing
1074
-
1075
- | Task Type | Agent | Primary Skills |
1076
- |-----------|-------|----------------|
1077
- ${agentRows}
1078
-
1079
- ## Auto-Resolved Skills
1080
-
1081
- ${skillsList}
1082
-
1083
- ## Scan Evidence
1084
-
1085
- ${evidence}
1086
- `;
1087
- }
1088
-
1089
- function generateArchitectureDoc(data) {
1090
- const appRootLine = data.app_root && data.app_root !== '.'
1091
- ? `- App root is currently detected as \`${data.app_root}\`.`
1092
- : '- App root is the repository root unless refined below.';
1093
- const evidence = (data.scan_evidence || []).length > 0
1094
- ? (data.scan_evidence || []).map(line => `- ${line}`).join('\n')
1095
- : '- TODO: refine architecture by scanning source directories.';
1096
-
1097
- return `# Architecture Notes
1098
-
1099
- ## Current Map
1100
-
1101
- ${appRootLine}
1102
- - TODO: refine major directories and responsibilities.
1103
- - TODO: refine main entry points, routing, state boundaries, and integration boundaries.
1104
-
1105
- ## Detected Evidence
1106
-
1107
- ${evidence}
1108
-
1109
- ## AI Maintenance Rule
1110
-
1111
- 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.
1112
- `;
1113
- }
1114
-
1115
- function generateConventionsDoc(data) {
1116
- return `# Coding Conventions
1117
-
1118
- ## Current Standards
1119
-
1120
- - Maximum file length: ${data.max_file_lines || '300'} lines unless the existing project standard is stricter.
1121
- - Code comments and identifiers should be written in English.
1122
- - Keep changes scoped to the user request.
1123
- - Prefer existing local patterns over introducing new abstractions.
1124
- - TODO: refine naming, formatting, folder, API, state, styling, and testing conventions from the real codebase.
1125
-
1126
- ## Verification
1127
-
1128
- - Run the focused test or lint command that matches the touched area.
1129
- - If no automated check exists, document the manual verification performed.
1130
- - For UI/web changes, prioritize using Playwright MCP to automatically verify client-side behavior and capture screenshots.
1131
- - Do not claim completion without verification evidence.
1132
- `;
1133
- }
1134
-
1135
- function generateWorkflowDoc() {
1136
- return `# Agent Workflow
1137
-
1138
- ## Before Every Task
1139
-
1140
- 1. Read AGENTS.md.
1141
- 2. Read .agent/docs/project.md.
1142
- 3. Classify the request: question, survey, simple fix, feature, refactor, debug, UI, security, or deploy.
1143
- 4. Read only the relevant docs under .agent/agents, .agent/skills, .claude/skills, and .agent/workflows.
1144
- 5. Check if MCP servers (like \`codegraph\`) are active/available and prioritize using them as the primary entry point to search and locate files, symbols, and code blocks.
1145
-
1146
- ## Skill Selection
1147
-
1148
- - Prefer native skill discovery when the client exposes it.
1149
- - Codex global skills live in ~/.codex/skills/ and are visible via the $ menu; thachvd-kit copies selected skills there on init.
1150
- - Claude Code project skills live in .claude/skills.
1151
- - Shared fallback skills live in .agent/skills.
1152
- - Do not load skill bodies by default.
1153
- - Load a skill when the user mentions it, the task clearly matches its SKILL.md description, or .agent/docs/project.md routes the task to it.
1154
- - Use explicit skill names in prompts for predictable behavior, for example $webapp-testing or $clean-code.
1155
- - Run thachvd-kit --help to see common workflow and skill recommendations.
1156
-
1157
- ## Implementation Flow
1158
-
1159
- 1. State assumptions and success criteria when the task is not trivial.
1160
- 2. Inspect dependent files before editing.
1161
- 3. Make the smallest coherent change.
1162
- 4. Add or update focused tests when behavior changes.
1163
- 5. Run verification (prioritize using Playwright MCP for web/UI changes to automate verification and capture screenshots).
1164
- 6. Summarize changed files and verification evidence.
1165
-
1166
- ## When To Update Docs
1167
-
1168
- - Update .agent/docs/architecture.md when structure, boundaries, or entry points change.
1169
- - Update .agent/docs/conventions.md when repeated project patterns become clear.
1170
- - Update .agent/docs/project.md when stack, scripts, app root, test tooling, or routing changes.
1171
- - Keep AGENTS.md concise. Put project-specific detail in .agent/docs.
1172
- `;
1173
- }
1174
-
1175
- function resolveToolingSetup(data) {
1176
- const primaryLang = data.primary_language || 'javascript';
1177
- const packageManager = data.package_manager || 'npm';
1178
- const isPython = primaryLang === 'python';
1179
- const runner = packageManager === 'pnpm'
1180
- ? 'pnpm dlx'
1181
- : packageManager === 'yarn'
1182
- ? 'yarn dlx'
1183
- : packageManager === 'bun'
1184
- ? 'bunx'
1185
- : 'npx';
1186
- const playwrightCommand = isPython
1187
- ? 'pip install playwright && playwright install chromium'
1188
- : `${runner} playwright install`;
1189
-
1190
- return { runner, playwrightCommand };
1191
- }
1192
-
1193
- function generateToolingDoc(data) {
1194
- const { runner, playwrightCommand } = resolveToolingSetup(data);
1195
-
1196
- return `# Optional Tooling
1197
-
1198
- This kit keeps tool setup explicit. Do not assume these tools are available until you verify them in the current environment.
1199
-
1200
- ## Playwright
1201
-
1202
- Use Playwright for browser and UI verification when a task touches web behavior.
1203
-
1204
- - Check availability: \`${runner} playwright --version\`
1205
- - Install browsers: \`${playwrightCommand}\`
1206
- - Codex MCP CLI setup: \`codex mcp add playwright -- npx -y @playwright/mcp\`
1207
- - Auto setup: \`thachvd-kit\`
1208
- - Kit helper: \`python .agent/skills/webapp-testing/scripts/playwright_runner.py <url> --screenshot\`
1209
-
1210
- If the project already has Playwright configured, prefer the project's existing scripts.
1211
-
1212
- Codex \`config.toml\` example:
1213
-
1214
- \`\`\`toml
1215
- [mcp_servers.context7]
1216
- command = "npx"
1217
- args = ["-y", "@upstash/context7-mcp"]
1218
- startup_timeout_sec = 20
1219
- tool_timeout_sec = 120
1220
-
1221
- [mcp_servers.filesystem]
1222
- command = "npx"
1223
- args = ["-y", "@modelcontextprotocol/server-filesystem", "<projectPath>"]
1224
- startup_timeout_sec = 20
1225
- tool_timeout_sec = 120
1226
-
1227
- [mcp_servers.playwright]
1228
- command = "npx"
1229
- args = ["-y", "@playwright/mcp"]
1230
- startup_timeout_sec = 20
1231
- tool_timeout_sec = 120
1232
- \`\`\`
1233
-
1234
- ## Codegraph
1235
-
1236
- Use codegraph for codebase exploration when the environment exposes it. The index is local state and should not be committed.
1237
-
1238
- - Check for an index: look for \`.codegraph/\`
1239
- - Check CLI availability: \`codegraph --help\`
1240
- - Install CLI if needed: \`npm install -g @colbymchenry/codegraph\`
1241
- - Codex MCP CLI setup: \`codex mcp add codegraph -- codegraph serve --mcp\`
1242
- - Auto setup: \`thachvd-kit\`
1243
- - If your Codex environment provides the codegraph CLI, run its project indexing step from the repository root.
1244
- - Keep \`.codegraph/\` ignored in git.
1245
-
1246
- Codex \`config.toml\` example:
1247
-
1248
- \`\`\`toml
1249
- [mcp_servers.codegraph]
1250
- command = "codegraph"
1251
- args = ["serve", "--mcp"]
1252
- startup_timeout_sec = 20
1253
- tool_timeout_sec = 120
1254
- \`\`\`
1255
-
1256
- Codex reads MCP servers from \`~/.codex/config.toml\` by default. For repo-local setup, use \`.codex/config.toml\`; Codex loads project config only for trusted projects.
1257
-
1258
- Gemini CLI / Antigravity \`mcp_config.json\` example:
1259
-
1260
- \`\`\`json
1261
- {
1262
- "mcpServers": {
1263
- "codegraph": {
1264
- "command": "codegraph",
1265
- "args": ["serve", "--mcp"]
1266
- },
1267
- "context7": {
1268
- "command": "npx",
1269
- "args": ["-y", "@upstash/context7-mcp"]
1270
- },
1271
- "filesystem": {
1272
- "command": "npx",
1273
- "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/project"]
1274
- },
1275
- "playwright": {
1276
- "command": "npx",
1277
- "args": ["-y", "@playwright/mcp"]
1278
- }
1279
- }
1280
- }
1281
- \`\`\`
1282
-
1283
- Common local paths:
1284
-
1285
- - Gemini CLI: \`~/.gemini/config/mcp_config.json\`
1286
- - Antigravity IDE: \`~/.gemini/antigravity-ide/mcp_config.json\`
1287
- - Older Antigravity setups may use \`~/.gemini/antigravity/mcp_config.json\`
1288
-
1289
- Claude Code user config example:
1290
-
1291
- \`\`\`json
1292
- {
1293
- "mcpServers": {
1294
- "codegraph": {
1295
- "type": "stdio",
1296
- "command": "codegraph",
1297
- "args": ["serve", "--mcp"]
1298
- },
1299
- "context7": {
1300
- "type": "stdio",
1301
- "command": "npx",
1302
- "args": ["-y", "@upstash/context7-mcp"]
1303
- },
1304
- "filesystem": {
1305
- "type": "stdio",
1306
- "command": "npx",
1307
- "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/project"]
1308
- },
1309
- "playwright": {
1310
- "type": "stdio",
1311
- "command": "npx",
1312
- "args": ["-y", "@playwright/mcp"]
1313
- }
1314
- }
1315
- }
1316
- \`\`\`
1317
-
1318
- Common local path:
1319
-
1320
- - Claude Code: \`~/.claude.json\` or \`~/.claude/settings.json\`
1321
-
1322
- ## Native Skill Folders
1323
-
1324
- The shared kit skills live in \`.agent/skills/\`. Antigravity reads that folder directly. Codex shows skills from \`~/.codex/skills/\` in the \`$\` menu. Claude Code reads project skills from \`.claude/skills/\`.
1325
-
1326
- - Codex global skills: \`~/.codex/skills/\` — installed by \`thachvd-kit init\`, visible in Codex \`$\` menu
1327
- - Claude Code project skills: \`.claude/skills/\`
1328
- - Antigravity: reads \`.agent/skills/\` directly (no copy needed)
1329
- - Keep \`.agent/skills/\` as the full shared source of truth committed with the repo.
1330
- - Do not commit \`~/.codex/skills/\`; it is local user state.
1331
-
1332
- Recommended candidates to copy first:
1333
-
1334
- - \`clean-code\`
1335
- - \`systematic-debugging\`
1336
- - \`verification-before-completion\`
1337
- - Stack-specific skills listed in \`.agent/docs/project.md\`
1338
- `;
1339
- }
1340
-
1341
- // --- Agent Folder Copy ---
1342
-
1343
- function copyAgentFolder() {
1344
- const srcAgent = path.join(sourceDir, '.agent');
1345
- if (!fs.existsSync(srcAgent)) return { copied: 0, skipped: 0 };
1346
-
1347
- const destAgent = path.join(targetDir, '.agent');
1348
- let copied = 0;
1349
- let skipped = 0;
1350
-
1351
- function copyDir(src, dest) {
1352
- fs.mkdirSync(dest, { recursive: true });
1353
- for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
1354
- const srcPath = path.join(src, entry.name);
1355
- const destPath = path.join(dest, entry.name);
1356
- const relativeSrc = path.relative(srcAgent, srcPath).split(path.sep).join('/');
1357
- if (relativeSrc === 'docs' || relativeSrc.startsWith('docs/')) {
1358
- continue;
1359
- }
1360
- if (entry.isDirectory()) {
1361
- copyDir(srcPath, destPath);
1362
- } else {
1363
- if (fs.existsSync(destPath)) {
1364
- skipped++;
1365
- } else {
1366
- fs.copyFileSync(srcPath, destPath);
1367
- copied++;
1368
- }
1369
- }
1370
- }
1371
- }
1372
-
1373
- copyDir(srcAgent, destAgent);
1374
- return { copied, skipped };
1375
- }
1376
-
1377
- function copySelectedSkillFolders(data, assumeYes) {
1378
- const srcSkills = path.join(sourceDir, '.agent', 'skills');
1379
- if (!fs.existsSync(srcSkills)) return { copied: 0, skipped: 0, missing: 0, skills: [] };
1380
-
1381
- const selectedSkills = resolveNativeSkills(data);
1382
-
1383
- // ~/.codex/skills/ — global Codex user skills dir, always visible in the $ menu
1384
- const codexSkillsDir = homePath('.codex', 'skills');
1385
- // .claude/skills/ — project-level Claude Code skills
1386
- const claudeSkillsDir = path.join(targetDir, '.claude', 'skills');
1387
-
1388
- const destinations = [claudeSkillsDir];
1389
- if (codexSkillsDir) destinations.push(codexSkillsDir);
1390
-
1391
- let copied = 0;
1392
- let skipped = 0;
1393
- let missing = 0;
1394
-
1395
- function copyDir(src, dest) {
1396
- fs.mkdirSync(dest, { recursive: true });
1397
- for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
1398
- const srcPath = path.join(src, entry.name);
1399
- const destPath = path.join(dest, entry.name);
1400
- if (entry.isDirectory()) {
1401
- copyDir(srcPath, destPath);
1402
- } else if (fs.existsSync(destPath) && !assumeYes) {
1403
- skipped++;
1404
- } else {
1405
- fs.mkdirSync(path.dirname(destPath), { recursive: true });
1406
- fs.copyFileSync(srcPath, destPath);
1407
- copied++;
1408
- }
1409
- }
1410
- }
1411
-
1412
- for (const skill of selectedSkills) {
1413
- const srcSkill = path.join(srcSkills, skill);
1414
- if (!fs.existsSync(srcSkill)) {
1415
- missing++;
1416
- continue;
1417
- }
1418
- for (const destSkills of destinations) {
1419
- copyDir(srcSkill, path.join(destSkills, skill));
1420
- }
1421
- }
1422
-
1423
- return { copied, skipped, missing, skills: selectedSkills };
1424
- }
1425
-
1426
- async function writeGeneratedFile(relativePath, content, assumeYes) {
1427
- const outputPath = path.join(targetDir, relativePath);
1428
- const exists = fs.existsSync(outputPath);
1429
- if (exists && !assumeYes) return false;
1430
-
1431
- fs.mkdirSync(path.dirname(outputPath), { recursive: true });
1432
- fs.writeFileSync(outputPath, content, 'utf8');
1433
- return true;
1434
- }
1435
-
1436
- function homePath(...parts) {
1437
- const home = process.env.USERPROFILE || process.env.HOME;
1438
- return home ? path.join(home, ...parts) : null;
1439
- }
1440
-
1441
- function commandExists(command) {
1442
- const checker = process.platform === 'win32' ? 'where' : 'command';
1443
- const args = process.platform === 'win32' ? [command] : ['-v', command];
1444
- const result = spawnSync(checker, args, { encoding: 'utf8', shell: process.platform !== 'win32' });
1445
- return result.status === 0;
1446
- }
1447
-
1448
- function ensureCodegraphCli() {
1449
- if (commandExists('codegraph')) {
1450
- return { ok: true, message: 'codegraph CLI already installed' };
1451
- }
1452
- if (!commandExists('npm')) {
1453
- return { ok: false, message: 'npm not found; run: npm install -g @colbymchenry/codegraph' };
1454
- }
1455
- const result = spawnSync('npm', ['install', '-g', '@colbymchenry/codegraph'], {
1456
- encoding: 'utf8',
1457
- stdio: 'pipe'
1458
- });
1459
- if (result.status !== 0) {
1460
- return { ok: false, message: 'failed to install codegraph; run: npm install -g @colbymchenry/codegraph' };
1461
- }
1462
- return { ok: true, message: 'installed codegraph CLI' };
1463
- }
1464
-
1465
- function readJsonFile(filePath) {
1466
- if (!fs.existsSync(filePath)) return {};
1467
- try {
1468
- return JSON.parse(fs.readFileSync(filePath, 'utf8'));
1469
- } catch {
1470
- return null;
1471
- }
1472
- }
1473
-
1474
- function writeJsonFile(filePath, data) {
1475
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
1476
- fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8');
1477
- }
1478
-
1479
- function mergeMcpJson(filePath, withType = false) {
1480
- const data = readJsonFile(filePath);
1481
- if (data === null) {
1482
- return { ok: false, message: `${filePath} is not valid JSON; skipped` };
1483
- }
1484
- data.mcpServers = data.mcpServers || {};
1485
- const npxCmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';
1486
- const baseServers = {
1487
- codegraph: { command: 'codegraph', args: ['serve', '--mcp'] },
1488
- playwright: { command: npxCmd, args: ['-y', '@playwright/mcp'] },
1489
- context7: { command: npxCmd, args: ['-y', '@upstash/context7-mcp'] }
1490
- };
1491
- if (data.permissions && Array.isArray(data.permissions.allow)) {
1492
- const requiredPermissions = [
1493
- "mcp__context7__resolve-library-id",
1494
- "mcp__context7__query-docs"
1495
- ];
1496
- for (const perm of requiredPermissions) {
1497
- if (!data.permissions.allow.includes(perm)) {
1498
- data.permissions.allow.push(perm);
1499
- }
1500
- }
1501
- }
1502
- for (const [name, server] of Object.entries(baseServers)) {
1503
- if (!data.mcpServers[name]) {
1504
- data.mcpServers[name] = withType ? { type: 'stdio', ...server } : server;
1505
- }
1506
- }
1507
- writeJsonFile(filePath, data);
1508
- const tools = Object.keys(baseServers).join(', ');
1509
- return { ok: true, message: `configured ${tools} MCP in ${filePath}` };
1510
- }
1511
-
1512
- function appendMcpTomlIfMissing(filePath) {
1513
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
1514
- const existing = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : '';
1515
- const blocks = [];
1516
- const npxCmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';
1517
- if (!/\[mcp_servers\.context7\]/.test(existing)) {
1518
- blocks.push(`[mcp_servers.context7]
1519
- command = "${npxCmd}"
1520
- args = ["-y", "@upstash/context7-mcp"]
1521
- startup_timeout_sec = 20
1522
- tool_timeout_sec = 120
1523
- `);
1524
- }
1525
- if (!/\[mcp_servers\.codegraph\]/.test(existing)) {
1526
- blocks.push(`[mcp_servers.codegraph]
1527
- command = "codegraph"
1528
- args = ["serve", "--mcp"]
1529
- startup_timeout_sec = 20
1530
- tool_timeout_sec = 120
1531
- `);
1532
- }
1533
- if (!/\[mcp_servers\.playwright\]/.test(existing)) {
1534
- blocks.push(`[mcp_servers.playwright]
1535
- command = "${npxCmd}"
1536
- args = ["-y", "@playwright/mcp"]
1537
- startup_timeout_sec = 20
1538
- tool_timeout_sec = 120
1539
- `);
1540
- }
1541
- if (blocks.length === 0) {
1542
- return { ok: true, message: `already configured ${filePath}` };
1543
- }
1544
- const separator = existing && !existing.endsWith('\n') ? '\n\n' : existing ? '\n' : '';
1545
- fs.writeFileSync(filePath, existing + separator + blocks.join('\n'), 'utf8');
1546
- return { ok: true, message: `configured context7, codegraph, playwright MCP in ${filePath}` };
1547
- }
1548
-
1549
- function setupMcpServers() {
1550
- const results = [];
1551
- results.push(ensureCodegraphCli());
1552
-
1553
- const codexConfig = homePath('.codex', 'config.toml');
1554
- if (codexConfig) results.push(appendMcpTomlIfMissing(codexConfig));
1555
-
1556
- const geminiConfig = homePath('.gemini', 'config', 'mcp_config.json');
1557
- if (geminiConfig) results.push(mergeMcpJson(geminiConfig, false));
1558
-
1559
- const antigravityIdeConfig = homePath('.gemini', 'antigravity-ide', 'mcp_config.json');
1560
- if (antigravityIdeConfig) results.push(mergeMcpJson(antigravityIdeConfig, false));
1561
-
1562
- const antigravityConfig = homePath('.gemini', 'antigravity', 'mcp_config.json');
1563
- if (antigravityConfig && fs.existsSync(path.dirname(antigravityConfig))) {
1564
- results.push(mergeMcpJson(antigravityConfig, false));
1565
- }
1566
-
1567
- const claudeConfig = homePath('.claude.json');
1568
- if (claudeConfig) results.push(mergeMcpJson(claudeConfig, true));
1569
-
1570
- const claudeSettings = homePath('.claude', 'settings.json');
1571
- if (claudeSettings) results.push(mergeMcpJson(claudeSettings, true));
1572
-
1573
- return results;
1574
- }
1575
-
1576
- // --- Main CLI ---
1577
-
1578
- async function main() {
1579
- const rawArgs = process.argv.slice(2);
1580
- const command = rawArgs[0] && !rawArgs[0].startsWith('-') ? rawArgs[0] : 'init';
1581
- const args = command === 'init' ? rawArgs.slice(rawArgs[0] === 'init' ? 1 : 0) : rawArgs.slice(1);
1582
-
1583
- if (rawArgs.includes('--version') || rawArgs.includes('-v')) {
1584
- console.log(packageJson.version);
1585
- return;
1586
- }
1587
-
1588
- if (rawArgs.includes('--help') || rawArgs.includes('-h')) {
1589
- printHelp();
1590
- return;
1591
- }
1592
-
1593
- if (command !== 'init') {
1594
- console.error(pc.red(`Unknown command: ${command}`));
1595
- printHelp();
1596
- process.exit(1);
1597
- }
1598
-
1599
- const assumeYes = args.includes('--yes') || args.includes('-y');
1600
- const skipMcpSetup = args.includes('--no-setup-mcp') || process.env.THACHVD_KIT_SKIP_MCP_SETUP === '1';
1601
-
1602
- console.log(pc.bold(pc.cyan('='.repeat(50))));
1603
- console.log(' ' + pc.bold('thachvd-kit') + ' - AI Project Bootstrap');
1604
- console.log(' Generates shared agent rules and configures MCP tooling');
1605
- console.log(pc.bold(pc.cyan('='.repeat(50))));
1606
-
1607
- console.log(pc.dim(' Scanning current project files for a baseline...\n'));
1608
- const scanned = scanProject();
1609
-
1610
- console.log(` ${pc.bold('Detected context:')}`);
1611
- formatScanPreviewLines(scanned).forEach(([label, value]) => {
1612
- console.log(` - ${label}: ${value}`);
1613
- });
1614
- console.log('');
1615
- if ((scanned.scan_evidence || []).length > 0) {
1616
- console.log(` ${pc.bold('Evidence:')}`);
1617
- scanned.scan_evidence.forEach(line => console.log(` - ${line}`));
1618
- console.log('');
1619
- }
1620
-
1621
- // Combine data
1622
- const data = {
1623
- project_name: scanned?.project_name || path.basename(targetDir),
1624
- description: scanned?.description || 'A software project',
1625
- project_type: scanned?.project_type || 'web-app',
1626
- primary_language: scanned?.primary_language || 'typescript',
1627
- frameworks: scanned?.frameworks || [],
1628
- database: scanned?.database || 'none',
1629
- uses_docker: scanned?.uses_docker ?? true,
1630
- cloud: scanned?.cloud || 'none',
1631
- package_manager: scanned?.package_manager || 'auto-detect',
1632
- test_framework: scanned?.test_framework || 'auto-detect',
1633
- max_file_lines: scanned?.max_file_lines || '300'
1634
- };
1635
-
1636
- data.database = data.database || scanned?.database || 'none';
1637
- data.uses_docker = typeof data.uses_docker === 'boolean' ? data.uses_docker : (scanned?.uses_docker ?? true);
1638
- data.cloud = data.cloud || scanned?.cloud || 'none';
1639
- data.package_manager = data.package_manager || scanned?.package_manager || 'auto-detect';
1640
- data.test_framework = data.test_framework || scanned?.test_framework || 'auto-detect';
1641
- data.max_file_lines = data.max_file_lines || scanned?.max_file_lines || '300';
1642
- data.framework_details = scanned?.framework_details || [];
1643
- data.scan_evidence = scanned?.scan_evidence || [];
1644
- data.app_root = scanned?.app_root || '.';
1645
-
1646
- data.infrastructure = [];
1647
- if (data.uses_docker) data.infrastructure.push('docker');
1648
- if (data.cloud !== 'none') data.infrastructure.push(data.cloud);
1649
-
1650
- const shouldSetupMcp = !skipMcpSetup;
1651
-
1652
- console.log(`\n${pc.bold(pc.cyan('Generating Files'))}`);
1653
-
1654
- // Shared entry files are written after the .agent folder is available.
1655
-
1656
- // Copy .agent folder
1657
- const { copied, skipped } = copyAgentFolder();
1658
- if (copied > 0) {
1659
- console.log(` ${pc.green('OK')} Copied ${pc.bold('.agent/')} (${copied} file${copied !== 1 ? 's' : ''} added${skipped > 0 ? `, ${skipped} skipped` : ''})`);
1660
- } else if (skipped > 0) {
1661
- console.log(` ${pc.yellow('~')} ${pc.bold('.agent/')} already exists - ${skipped} file${skipped !== 1 ? 's' : ''} skipped (no overwrite)`);
1662
- }
1663
-
1664
- const nativeSkills = copySelectedSkillFolders(data, assumeYes);
1665
- if (nativeSkills.copied > 0) {
1666
- console.log(` ${pc.green('OK')} Copied native skills to ${pc.bold('~/.codex/skills/')} and ${pc.bold('.claude/skills/')} (${nativeSkills.skills.join(', ')})`);
1667
- } else if (nativeSkills.skipped > 0) {
1668
- console.log(` ${pc.yellow('~')} Native skills already exist - ${nativeSkills.skipped} file${nativeSkills.skipped !== 1 ? 's' : ''} skipped`);
1669
- }
1670
-
1671
-
1672
- const generatedFiles = [
1673
- ['AGENTS.md', generateSharedAgentsMd(data)],
1674
- ['CLAUDE.md', generateSharedClaudeMd(data)],
1675
- ['GEMINI.md', generateSharedGeminiMd()],
1676
- ['.cursorrules', generateSharedCursorrules(data)],
1677
- [path.join('.agent', 'docs', 'project.md'), generateProjectDoc(data)],
1678
- [path.join('.agent', 'docs', 'architecture.md'), generateArchitectureDoc(data)],
1679
- [path.join('.agent', 'docs', 'conventions.md'), generateConventionsDoc(data)],
1680
- [path.join('.agent', 'docs', 'workflow.md'), generateWorkflowDoc()],
1681
- [path.join('.agent', 'docs', 'tooling.md'), generateToolingDoc(data)]
1682
- ];
1683
-
1684
- for (const [relativePath, content] of generatedFiles) {
1685
- const wrote = await writeGeneratedFile(relativePath, content, assumeYes);
1686
- if (wrote) console.log(` ${pc.green('OK')} Generated ${pc.bold(relativePath)}`);
1687
- else console.log(` ${pc.yellow('~')} Skipped ${pc.bold(relativePath)}`);
1688
- }
1689
-
1690
- if (shouldSetupMcp) {
1691
- console.log(`\n${pc.bold(pc.cyan('Configuring MCP'))}`);
1692
- for (const result of setupMcpServers()) {
1693
- const marker = result.ok ? pc.green('OK') : pc.yellow('~');
1694
- console.log(` ${marker} ${result.message}`);
1695
- }
1696
- }
1697
-
1698
- console.log(`\n${pc.bold(pc.green('Done!'))}
1699
-
1700
- ${pc.bold('Project:')} ${data.project_name}
1701
- ${pc.bold('Stack:')} ${data.primary_language} | ${(data.frameworks || []).join(', ') || 'no framework'}
1702
-
1703
- ${pc.bold('Generated:')}
1704
- - ${pc.bold('AGENTS.md')} shared entry for Codex, Antigravity, Claude Code, and Cursor
1705
- - ${pc.bold('CLAUDE.md')} Claude Code entry that imports AGENTS.md
1706
- - ${pc.bold('GEMINI.md')} Antigravity entry
1707
- - ${pc.bold('.cursorrules')} Cursor entry
1708
- - ${pc.bold('.agent/docs/')} scan-based project rules
1709
- - ${pc.bold('.agent/')} skills, workflows, agents, and rules
1710
- - ${pc.bold('~/.codex/skills/')} selected Codex skills installed globally (visible in $ menu)
1711
- - ${pc.bold('.claude/skills/')} selected Claude Code native skills
1712
-
1713
- ${pc.bold('MCP/tooling setup:')}
1714
- - Codegraph CLI checked or installed; MCP configured for Codex, Gemini/Antigravity, and Claude Code
1715
- - Context7 MCP configured for Codex and Gemini/Antigravity
1716
- - Playwright MCP configured for Codex, Gemini/Antigravity, and Claude Code
1717
- - Browser install hint: ${pc.bold(resolveToolingSetup(data).playwrightCommand)}
1718
- - Codegraph index hint: run ${pc.bold('codegraph init -i')} when a project index is missing, then keep ${pc.bold('.codegraph/')} uncommitted
1719
-
1720
- ${pc.bold('Next steps:')}
1721
- 1. Copy the prompt below into your AI editor and describe what the project does
1722
- 2. Commit ${pc.bold('AGENTS.md')}, ${pc.bold('CLAUDE.md')}, ${pc.bold('GEMINI.md')}, ${pc.bold('.cursorrules')}, and ${pc.bold('.agent/')}
1723
- 3. Open the project in Codex, Antigravity, Claude Code, or Cursor
1724
-
1725
- ${pc.bold('Copy this prompt into your AI editor:')}
1726
- ${pc.dim('---')}
1727
- Read AGENTS.md and all files under .agent/docs/.
1728
- Ask me what this project does and any important conventions I want preserved.
1729
- Then scan this repository.
1730
- Update AGENTS.md, .cursorrules and .agent/docs/project.md, .agent/docs/architecture.md, .agent/docs/conventions.md, .agent/docs/workflow.md, and .agent/docs/tooling.md with factual project-specific rules.
1731
- Do not implement product code.
1732
- Remove TODO: refine only when backed by evidence from the codebase.
1733
- ${pc.dim('---')}
1734
- `);
1735
- }
1736
-
1737
- main().catch(err => {
1738
- console.error(pc.red('\nError during init:'), err);
1739
- process.exit(1);
1740
- });
890
+
1741
891
  return rows;
892
+ }
893
+ // --- File Generators ---
894
+
895
+ function generateSharedAgentsMd(data) {
896
+ return `# AGENTS.md
897
+
898
+ Shared operating instructions for Codex, Antigravity, Claude Code, and Cursor.
899
+
900
+ ## Startup
901
+
902
+ 1. Read this file first.
903
+ 2. Read \`.agent/docs/project.md\` for detected stack, commands, and routing.
904
+ 3. Read \`.agent/docs/workflow.md\` for the task flow before editing.
905
+ 4. Read \`.agent/docs/architecture.md\` and \`.agent/docs/conventions.md\` before planning code changes.
906
+ 5. Read \`.agent/docs/tooling.md\` when the task needs Playwright, codegraph, MCP, or native Codex/Claude skill setup.
907
+ 6. Select and load only the agent, skill, or workflow files that match the current task.
908
+
909
+ If any \`.agent/docs/*.md\` file still contains \`TODO: refine\`, update the docs by scanning the project before making product code changes.
910
+
911
+ ## Task Flow
912
+
913
+ - Questions and analysis: answer directly, cite relevant files when useful, and do not edit code.
914
+ - Simple fix: inspect dependencies, make the smallest change, run focused verification.
915
+ - Feature or refactor: define success criteria, make a short plan, implement, then verify.
916
+ - Multi-domain work: use \`.agent/workflows/orchestrate.md\` and route to the relevant specialist docs.
917
+ - UI work: read \`.agent/agents/frontend-specialist.md\` and applicable design skills before editing.
918
+
919
+ ## Skill Loading
920
+
921
+ - Treat \`.agent/skills/\` as the shared source of truth for all kit skills.
922
+ - Codex exposes skills from \`~/.codex/skills/\` in the \`$\` menu; \`thachvd-kit\` copies selected skills there on init.
923
+ - Claude Code project skills live in \`.claude/skills/\`.
924
+ - If a skill is missing from the global dir, fall back to the matching \`.agent/skills/<skill>/SKILL.md\` file.
925
+ - Do not load skill bodies by default; load a skill only when the user mentions it, the task clearly matches its description, or \`.agent/docs/project.md\` routes the task to it.
926
+ - Prefer explicit skill mentions for predictable behavior: \`$clean-code\`, \`$systematic-debugging\`, \`$webapp-testing\`, or any skill listed in \`.agent/docs/project.md\`.
927
+ - Run \`thachvd-kit --help\` to see common workflow and skill recommendations.
928
+ - When creating or improving skills, use \`writing-skills\` first and make the \`description\` field specific enough for implicit invocation.
929
+
930
+ ## Rules
931
+
932
+ - Respond in the user's language; keep code, identifiers, and code comments in English.
933
+ - State assumptions when the request is ambiguous.
934
+ - Prefer the existing project style over new abstractions.
935
+ - Keep changes surgical and remove only dead code introduced by your change.
936
+ - **MCP First**: Prioritize using MCP server tools (e.g., \`codegraph\` for codebase search/symbol tracking, \`context7\` for API/docs queries, and \`playwright\` for browser/UI testing and verification) to explore, search, and verify rather than recursively listing directories, running expensive shell commands/grep, or reading large files. This minimizes token usage and maintains a cleaner context.
937
+ - Tests or equivalent verification are mandatory before claiming done.
938
+ - Keep files under ${data.max_file_lines || '300'} lines unless the project already has a different standard in \`.agent/docs/conventions.md\`.
939
+
940
+ ## Shared Knowledge
941
+
942
+ - Project docs: \`.agent/docs/\`
943
+ - Specialist agents: \`.agent/agents/\`
944
+ - Skills: \`.agent/skills/\`
945
+ - Codex global skills: \`~/.codex/skills/\` (installed by thachvd-kit init)
946
+ - Claude Code project skills: \`.claude/skills/\`
947
+ - Workflows: \`.agent/workflows/\`
948
+ - Tooling setup: \`.agent/docs/tooling.md\`
949
+ - Antigravity mirror rules: \`.agent/rules/GEMINI.md\`
950
+ - Cursor entry rules: \`.cursorrules\`
951
+ `;
952
+ }
953
+
954
+ function generateSharedClaudeMd() {
955
+ return `@AGENTS.md
956
+
957
+ ## Claude Code
958
+
959
+ 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.
960
+ `;
961
+ }
962
+
963
+ function generateSharedGeminiMd() {
964
+ return `# GEMINI.md
965
+
966
+ Antigravity entry file for this project.
967
+
968
+ Read AGENTS.md first, then follow the shared docs under .agent/docs/.
969
+ `;
970
+ }
971
+
972
+ function generateSharedCursorrules(data) {
973
+ return `# Cursor Rules
974
+
975
+ This repository uses AGENTS.md as the shared cross-agent entry file. Follow the instructions in AGENTS.md and keep Cursor-specific notes here only when they cannot apply to Codex, Antigravity, or Claude Code.
976
+
977
+ Read AGENTS.md first, then follow the shared docs under .agent/docs/.
978
+
979
+ ---
980
+
981
+ ${generateSharedAgentsMd(data)}
982
+ `;
983
+ }
984
+
985
+ function resolveCommands(data) {
986
+ const frameworks = data.frameworks || [];
987
+ const primaryLang = data.primary_language || 'javascript';
988
+ const pkgManager = data.package_manager || 'npm';
989
+ const testFramework = data.test_framework || 'auto-detect';
990
+
991
+ const isPython = primaryLang === 'python';
992
+ const isGo = primaryLang === 'go';
993
+ const isRust = primaryLang === 'rust';
994
+ const isPhp = primaryLang === 'php';
995
+
996
+ let install = 'npm install';
997
+ let dev = 'npm run dev';
998
+ let build = 'npm run build';
999
+ let test = 'npm test';
1000
+ let lint = 'npm run lint';
1001
+
1002
+ if (pkgManager === 'pnpm') { install = 'pnpm install'; dev = 'pnpm dev'; build = 'pnpm build'; test = 'pnpm test'; lint = 'pnpm lint'; }
1003
+ else if (pkgManager === 'yarn') { install = 'yarn'; dev = 'yarn dev'; build = 'yarn build'; test = 'yarn test'; lint = 'yarn lint'; }
1004
+ else if (pkgManager === 'bun') { install = 'bun install'; dev = 'bun dev'; build = 'bun run build'; test = 'bun test'; lint = 'bun run lint'; }
1005
+ else if (isPython) { install = 'pip install -r requirements.txt'; dev = 'python manage.py runserver'; build = '# no build step'; test = 'pytest'; lint = 'ruff check .'; }
1006
+ else if (isGo) { install = 'go mod download'; dev = 'go run .'; build = 'go build ./...'; test = 'go test ./...'; lint = 'golangci-lint run'; }
1007
+ else if (isRust) { install = 'cargo build'; dev = 'cargo run'; build = 'cargo build --release'; test = 'cargo test'; lint = 'cargo clippy'; }
1008
+ else if (isPhp) {
1009
+ install = 'composer install';
1010
+ build = '# no build step';
1011
+ test = testFramework === 'pest' ? './vendor/bin/pest' : './vendor/bin/phpunit';
1012
+ const isCakePHP = frameworks.some(f => f.toLowerCase().includes('cakephp'));
1013
+ const isSymfony = frameworks.some(f => f.toLowerCase().includes('symfony'));
1014
+ const isCodeIgniter = frameworks.some(f => f.toLowerCase().includes('codeigniter'));
1015
+ if (isCakePHP) {
1016
+ dev = 'bin/cake server';
1017
+ lint = './vendor/bin/phpcs';
1018
+ } else if (isSymfony) {
1019
+ dev = 'bin/console server:run';
1020
+ lint = './vendor/bin/phpcs';
1021
+ } else if (isCodeIgniter) {
1022
+ dev = 'php spark serve';
1023
+ lint = './vendor/bin/phpcs';
1024
+ } else {
1025
+ dev = 'php artisan serve';
1026
+ lint = 'php artisan pint';
1027
+ }
1028
+ }
1029
+
1030
+ if (testFramework === 'vitest') test = `${pkgManager === 'pnpm' ? 'pnpm' : pkgManager === 'yarn' ? 'yarn' : pkgManager === 'bun' ? 'bun run' : 'npx'} vitest`;
1031
+ else if (testFramework === 'jest') test = `${pkgManager === 'pnpm' ? 'pnpm' : pkgManager === 'yarn' ? 'yarn' : 'npx'} jest`;
1032
+
1033
+ return { install, dev, build, test, lint };
1034
+ }
1035
+
1036
+ function generateProjectDoc(data) {
1037
+ const commands = resolveCommands(data);
1038
+ const agentRows = resolveAgentTable(data).map(([task, agent, skills]) => `| ${task} | \`${agent}\` | ${skills} |`).join('\n');
1039
+ const skillsList = resolveSkills(data).map(s => `- ${s}`).join('\n');
1040
+ const evidence = (data.scan_evidence || []).length > 0
1041
+ ? (data.scan_evidence || []).map(line => `- ${line}`).join('\n')
1042
+ : '- TODO: refine scan evidence by inspecting the project.';
1043
+
1044
+ return `# Project Rules
1045
+
1046
+ Generated by thachvd-kit on ${new Date().toISOString().split('T')[0]}.
1047
+
1048
+ ## Summary
1049
+
1050
+ - Name: ${data.project_name}
1051
+ - Description: ${data.description}
1052
+ - Type: ${data.project_type}
1053
+ - App root: ${data.app_root || '.'}
1054
+
1055
+ ## Stack
1056
+
1057
+ - Language: ${data.primary_language}
1058
+ - Frameworks: ${(data.frameworks || []).join(', ') || 'none'}
1059
+ - Database: ${data.database || 'none'}
1060
+ - Infrastructure: ${(data.infrastructure || []).join(', ') || 'none'}
1061
+ - Package manager: ${data.package_manager || 'auto-detect'}
1062
+ - Test framework: ${data.test_framework || 'auto-detect'}
1063
+
1064
+ ## Commands
1065
+
1066
+ - Install: \`${commands.install}\`
1067
+ - Dev: \`${commands.dev}\`
1068
+ - Build: \`${commands.build}\`
1069
+ - Test: \`${commands.test}\`
1070
+ - Lint: \`${commands.lint}\`
1071
+
1072
+ ## Available MCP Tools
1073
+
1074
+ Use these tools as first-class search and verification methods — prefer them over shell commands or file reads.
1075
+
1076
+ | MCP | When to Use |
1077
+ |-----|-------------|
1078
+ | \`codegraph\` | Explore symbols, trace call chains, find usages — use BEFORE reading files |
1079
+ | \`context7\` | Look up library/framework docs, API signatures, migration guides |
1080
+ | \`playwright\` | Verify UI behavior, take screenshots, test forms and navigation |
1081
+
1082
+ > Check \`.agent/docs/tooling.md\` for setup details and additional MCP servers.
1083
+
1084
+ ## Agent Routing
1085
+
1086
+ | Task Type | Agent | Primary Skills |
1087
+ |-----------|-------|----------------|
1088
+ ${agentRows}
1089
+
1090
+ ## Auto-Resolved Skills
1091
+
1092
+ ${skillsList}
1093
+
1094
+ ## Scan Evidence
1095
+
1096
+ ${evidence}
1097
+ `;
1098
+ }
1099
+
1100
+ function generateArchitectureDoc(data) {
1101
+ const appRootLine = data.app_root && data.app_root !== '.'
1102
+ ? `- App root is currently detected as \`${data.app_root}\`.`
1103
+ : '- App root is the repository root unless refined below.';
1104
+ const evidence = (data.scan_evidence || []).length > 0
1105
+ ? (data.scan_evidence || []).map(line => `- ${line}`).join('\n')
1106
+ : '- TODO: refine architecture by scanning source directories.';
1107
+
1108
+ return `# Architecture Notes
1109
+
1110
+ ## Current Map
1111
+
1112
+ ${appRootLine}
1113
+ - TODO: refine major directories and responsibilities.
1114
+ - TODO: refine main entry points, routing, state boundaries, and integration boundaries.
1115
+
1116
+ ## Detected Evidence
1117
+
1118
+ ${evidence}
1119
+
1120
+ ## AI Maintenance Rule
1121
+
1122
+ 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.
1123
+ `;
1124
+ }
1125
+
1126
+ function generateConventionsDoc(data) {
1127
+ return `# Coding Conventions
1128
+
1129
+ ## Current Standards
1130
+
1131
+ - Maximum file length: ${data.max_file_lines || '300'} lines unless the existing project standard is stricter.
1132
+ - Code comments and identifiers should be written in English.
1133
+ - Keep changes scoped to the user request.
1134
+ - Prefer existing local patterns over introducing new abstractions.
1135
+ - TODO: refine naming, formatting, folder, API, state, styling, and testing conventions from the real codebase.
1136
+
1137
+ ## Verification
1138
+
1139
+ - Run the focused test or lint command that matches the touched area.
1140
+ - If no automated check exists, document the manual verification performed.
1141
+ - For UI/web changes, prioritize using Playwright MCP to automatically verify client-side behavior and capture screenshots.
1142
+ - Do not claim completion without verification evidence.
1143
+ `;
1144
+ }
1145
+
1146
+ function generateWorkflowDoc() {
1147
+ return `# Agent Workflow
1148
+
1149
+ ## Before Every Task
1150
+
1151
+ 1. Read AGENTS.md.
1152
+ 2. Read .agent/docs/project.md.
1153
+ 3. Classify the request: question, survey, simple fix, feature, refactor, debug, UI, security, or deploy.
1154
+ 4. Read only the relevant docs under .agent/agents, .agent/skills, .claude/skills, and .agent/workflows.
1155
+ 5. Check if MCP servers (like \`codegraph\`) are active/available and prioritize using them as the primary entry point to search and locate files, symbols, and code blocks.
1156
+
1157
+ ## Skill Selection
1158
+
1159
+ - Prefer native skill discovery when the client exposes it.
1160
+ - Codex global skills live in ~/.codex/skills/ and are visible via the $ menu; thachvd-kit copies selected skills there on init.
1161
+ - Claude Code project skills live in .claude/skills.
1162
+ - Shared fallback skills live in .agent/skills.
1163
+ - Do not load skill bodies by default.
1164
+ - Load a skill when the user mentions it, the task clearly matches its SKILL.md description, or .agent/docs/project.md routes the task to it.
1165
+ - Use explicit skill names in prompts for predictable behavior, for example $webapp-testing or $clean-code.
1166
+ - Run thachvd-kit --help to see common workflow and skill recommendations.
1167
+
1168
+ ## Implementation Flow
1169
+
1170
+ 1. State assumptions and success criteria when the task is not trivial.
1171
+ 2. Inspect dependent files before editing.
1172
+ 3. Make the smallest coherent change.
1173
+ 4. Add or update focused tests when behavior changes.
1174
+ 5. Run verification (prioritize using Playwright MCP for web/UI changes to automate verification and capture screenshots).
1175
+ 6. Summarize changed files and verification evidence.
1176
+
1177
+ ## When To Update Docs
1178
+
1179
+ - Update .agent/docs/architecture.md when structure, boundaries, or entry points change.
1180
+ - Update .agent/docs/conventions.md when repeated project patterns become clear.
1181
+ - Update .agent/docs/project.md when stack, scripts, app root, test tooling, or routing changes.
1182
+ - Keep AGENTS.md concise. Put project-specific detail in .agent/docs.
1183
+ `;
1184
+ }
1185
+
1186
+ function resolveToolingSetup(data) {
1187
+ const primaryLang = data.primary_language || 'javascript';
1188
+ const packageManager = data.package_manager || 'npm';
1189
+ const isPython = primaryLang === 'python';
1190
+ const runner = packageManager === 'pnpm'
1191
+ ? 'pnpm dlx'
1192
+ : packageManager === 'yarn'
1193
+ ? 'yarn dlx'
1194
+ : packageManager === 'bun'
1195
+ ? 'bunx'
1196
+ : 'npx';
1197
+ const playwrightCommand = isPython
1198
+ ? 'pip install playwright && playwright install chromium'
1199
+ : `${runner} playwright install`;
1200
+
1201
+ return { runner, playwrightCommand };
1202
+ }
1203
+
1204
+ function generateToolingDoc(data) {
1205
+ const { runner, playwrightCommand } = resolveToolingSetup(data);
1206
+
1207
+ return `# Optional Tooling
1208
+
1209
+ This kit keeps tool setup explicit. Do not assume these tools are available until you verify them in the current environment.
1210
+
1211
+ ## Playwright
1212
+
1213
+ Use Playwright for browser and UI verification when a task touches web behavior.
1214
+
1215
+ - Check availability: \`${runner} playwright --version\`
1216
+ - Install browsers: \`${playwrightCommand}\`
1217
+ - Codex MCP CLI setup: \`codex mcp add playwright -- playwright-mcp\`
1218
+ - Auto setup: \`thachvd-kit\`
1219
+ - Kit helper: \`python .agent/skills/webapp-testing/scripts/playwright_runner.py <url> --screenshot\`
1220
+
1221
+ If the project already has Playwright configured, prefer the project's existing scripts.
1222
+
1223
+ Codex \`config.toml\` example:
1224
+
1225
+ \`\`\`toml
1226
+ [mcp_servers.context7]
1227
+ command = "context7-mcp"
1228
+ startup_timeout_sec = 20
1229
+ tool_timeout_sec = 120
1230
+
1231
+ [mcp_servers.filesystem]
1232
+ command = "npx"
1233
+ args = ["-y", "@modelcontextprotocol/server-filesystem", "<projectPath>"]
1234
+ startup_timeout_sec = 20
1235
+ tool_timeout_sec = 120
1236
+
1237
+ [mcp_servers.playwright]
1238
+ command = "playwright-mcp"
1239
+ startup_timeout_sec = 20
1240
+ tool_timeout_sec = 120
1241
+ \`\`\`
1242
+
1243
+ ## Codegraph
1244
+
1245
+ Use codegraph for codebase exploration when the environment exposes it. The index is local state and should not be committed.
1246
+
1247
+ - Check for an index: look for \`.codegraph/\`
1248
+ - Check CLI availability: \`codegraph --help\`
1249
+ - Install CLI if needed: \`npm install -g @colbymchenry/codegraph\`
1250
+ - Codex MCP CLI setup: \`codex mcp add codegraph -- codegraph serve --mcp\`
1251
+ - Auto setup: \`thachvd-kit\`
1252
+ - If your Codex environment provides the codegraph CLI, run its project indexing step from the repository root.
1253
+ - Keep \`.codegraph/\` ignored in git.
1254
+
1255
+ Codex \`config.toml\` example:
1256
+
1257
+ \`\`\`toml
1258
+ [mcp_servers.codegraph]
1259
+ command = "codegraph"
1260
+ args = ["serve", "--mcp"]
1261
+ startup_timeout_sec = 20
1262
+ tool_timeout_sec = 120
1263
+ \`\`\`
1264
+
1265
+ Codex reads MCP servers from \`~/.codex/config.toml\` by default. For repo-local setup, use \`.codex/config.toml\`; Codex loads project config only for trusted projects.
1266
+
1267
+ Gemini CLI / Antigravity \`mcp_config.json\` example:
1268
+
1269
+ \`\`\`json
1270
+ {
1271
+ "mcpServers": {
1272
+ "codegraph": {
1273
+ "command": "codegraph",
1274
+ "args": ["serve", "--mcp"]
1275
+ },
1276
+ "context7": {
1277
+ "command": "context7-mcp"
1278
+ },
1279
+ "filesystem": {
1280
+ "command": "npx",
1281
+ "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/project"]
1282
+ },
1283
+ "playwright": {
1284
+ "command": "playwright-mcp"
1285
+ }
1286
+ }
1287
+ }
1288
+ \`\`\`
1289
+
1290
+ Common local paths:
1291
+
1292
+ - Gemini CLI: \`~/.gemini/config/mcp_config.json\`
1293
+ - Antigravity IDE: \`~/.gemini/antigravity-ide/mcp_config.json\`
1294
+ - Older Antigravity setups may use \`~/.gemini/antigravity/mcp_config.json\`
1295
+
1296
+ Claude Code user config example:
1297
+
1298
+ \`\`\`json
1299
+ {
1300
+ "mcpServers": {
1301
+ "codegraph": {
1302
+ "type": "stdio",
1303
+ "command": "codegraph",
1304
+ "args": ["serve", "--mcp"]
1305
+ },
1306
+ "context7": {
1307
+ "type": "stdio",
1308
+ "command": "context7-mcp"
1309
+ },
1310
+ "filesystem": {
1311
+ "type": "stdio",
1312
+ "command": "npx",
1313
+ "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/project"]
1314
+ },
1315
+ "playwright": {
1316
+ "type": "stdio",
1317
+ "command": "playwright-mcp"
1318
+ }
1319
+ }
1320
+ }
1321
+ \`\`\`
1322
+
1323
+ Common local path:
1324
+
1325
+ - Claude Code: \`~/.claude.json\` or \`~/.claude/settings.json\`
1326
+
1327
+ ## Native Skill Folders
1328
+
1329
+ The shared kit skills live in \`.agent/skills/\`. Antigravity reads that folder directly. Codex shows skills from \`~/.codex/skills/\` in the \`$\` menu. Claude Code reads project skills from \`.claude/skills/\`.
1330
+
1331
+ - Codex global skills: \`~/.codex/skills/\` — installed by \`thachvd-kit init\`, visible in Codex \`$\` menu
1332
+ - Claude Code project skills: \`.claude/skills/\`
1333
+ - Antigravity: reads \`.agent/skills/\` directly (no copy needed)
1334
+ - Keep \`.agent/skills/\` as the full shared source of truth committed with the repo.
1335
+ - Do not commit \`~/.codex/skills/\`; it is local user state.
1336
+
1337
+ Recommended candidates to copy first:
1338
+
1339
+ - \`clean-code\`
1340
+ - \`systematic-debugging\`
1341
+ - \`verification-before-completion\`
1342
+ - Stack-specific skills listed in \`.agent/docs/project.md\`
1343
+ `;
1344
+ }
1345
+
1346
+ // --- Agent Folder Copy ---
1347
+
1348
+ function copyAgentFolder() {
1349
+ const srcAgent = path.join(sourceDir, '.agent');
1350
+ if (!fs.existsSync(srcAgent)) return { copied: 0, skipped: 0 };
1351
+
1352
+ const destAgent = path.join(targetDir, '.agent');
1353
+ let copied = 0;
1354
+
1355
+ function copyDir(src, dest) {
1356
+ fs.mkdirSync(dest, { recursive: true });
1357
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
1358
+ const srcPath = path.join(src, entry.name);
1359
+ const destPath = path.join(dest, entry.name);
1360
+ const relativeSrc = path.relative(srcAgent, srcPath).split(path.sep).join('/');
1361
+ if (relativeSrc === 'docs' || relativeSrc.startsWith('docs/')) {
1362
+ continue;
1363
+ }
1364
+ if (entry.isDirectory()) {
1365
+ copyDir(srcPath, destPath);
1366
+ } else {
1367
+ fs.copyFileSync(srcPath, destPath);
1368
+ copied++;
1369
+ }
1370
+ }
1371
+ }
1372
+
1373
+ copyDir(srcAgent, destAgent);
1374
+ return { copied, skipped: 0 };
1375
+ }
1376
+
1377
+ function copySelectedSkillFolders(data, assumeYes) {
1378
+ const srcSkills = path.join(sourceDir, '.agent', 'skills');
1379
+ if (!fs.existsSync(srcSkills)) return { copied: 0, skipped: 0, missing: 0, skills: [] };
1380
+
1381
+ const selectedSkills = resolveNativeSkills(data);
1382
+
1383
+ // ~/.codex/skills/ — global Codex user skills dir, always visible in the $ menu
1384
+ const codexSkillsDir = homePath('.codex', 'skills');
1385
+ // .claude/skills/ — project-level Claude Code skills
1386
+ const claudeSkillsDir = path.join(targetDir, '.claude', 'skills');
1387
+
1388
+ const destinations = [claudeSkillsDir];
1389
+ if (codexSkillsDir) destinations.push(codexSkillsDir);
1390
+
1391
+ let copied = 0;
1392
+ let missing = 0;
1393
+
1394
+ function copyDir(src, dest) {
1395
+ fs.mkdirSync(dest, { recursive: true });
1396
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
1397
+ const srcPath = path.join(src, entry.name);
1398
+ const destPath = path.join(dest, entry.name);
1399
+ if (entry.isDirectory()) {
1400
+ copyDir(srcPath, destPath);
1401
+ } else {
1402
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
1403
+ fs.copyFileSync(srcPath, destPath);
1404
+ copied++;
1405
+ }
1406
+ }
1407
+ }
1408
+
1409
+ for (const skill of selectedSkills) {
1410
+ const srcSkill = path.join(srcSkills, skill);
1411
+ if (!fs.existsSync(srcSkill)) {
1412
+ missing++;
1413
+ continue;
1414
+ }
1415
+ for (const destSkills of destinations) {
1416
+ copyDir(srcSkill, path.join(destSkills, skill));
1417
+ }
1418
+ }
1419
+
1420
+ return { copied, skipped: 0, missing, skills: selectedSkills };
1421
+ }
1422
+
1423
+ async function writeGeneratedFile(relativePath, content, assumeYes) {
1424
+ const outputPath = path.join(targetDir, relativePath);
1425
+ const exists = fs.existsSync(outputPath);
1426
+ if (exists && !assumeYes) {
1427
+ const response = await prompts({
1428
+ type: 'confirm',
1429
+ name: 'overwrite',
1430
+ message: `File ${pc.bold(relativePath)} already exists. Do you want to overwrite it?`,
1431
+ initial: false
1432
+ });
1433
+ if (response.overwrite === undefined) {
1434
+ console.log(pc.yellow('\nCancelled.'));
1435
+ process.exit(130);
1436
+ }
1437
+ if (!response.overwrite) return false;
1438
+ }
1439
+
1440
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
1441
+ fs.writeFileSync(outputPath, content, 'utf8');
1442
+ return true;
1443
+ }
1444
+
1445
+ function homePath(...parts) {
1446
+ const home = process.env.USERPROFILE || process.env.HOME;
1447
+ return home ? path.join(home, ...parts) : null;
1448
+ }
1449
+
1450
+ function commandExists(command) {
1451
+ const checker = process.platform === 'win32' ? 'where' : 'command';
1452
+ const args = process.platform === 'win32' ? [command] : ['-v', command];
1453
+ const result = spawnSync(checker, args, { encoding: 'utf8', shell: process.platform !== 'win32' });
1454
+ return result.status === 0;
1455
+ }
1456
+
1457
+ function ensureGlobalNpmPackage(binaryName, packageName) {
1458
+ if (commandExists(binaryName)) {
1459
+ return { ok: true, message: `${binaryName} already installed` };
1460
+ }
1461
+ if (!commandExists('npm')) {
1462
+ return { ok: false, message: `npm not found; run: npm install -g ${packageName}` };
1463
+ }
1464
+ const result = spawnSync('npm', ['install', '-g', packageName], {
1465
+ encoding: 'utf8',
1466
+ stdio: 'pipe'
1467
+ });
1468
+ if (result.status !== 0) {
1469
+ return { ok: false, message: `failed to install ${binaryName}; run: npm install -g ${packageName}` };
1470
+ }
1471
+ return { ok: true, message: `installed ${binaryName}` };
1472
+ }
1473
+
1474
+ function ensureCodegraphCli() {
1475
+ return ensureGlobalNpmPackage('codegraph', '@colbymchenry/codegraph');
1476
+ }
1477
+
1478
+ function ensureContext7Cli() {
1479
+ return ensureGlobalNpmPackage('context7-mcp', '@upstash/context7-mcp');
1480
+ }
1481
+
1482
+ function ensurePlaywrightMcpCli() {
1483
+ return ensureGlobalNpmPackage('playwright-mcp', '@playwright/mcp');
1484
+ }
1485
+
1486
+ function readJsonFile(filePath) {
1487
+ if (!fs.existsSync(filePath)) return {};
1488
+ try {
1489
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
1490
+ } catch {
1491
+ return null;
1492
+ }
1493
+ }
1494
+
1495
+ function writeJsonFile(filePath, data) {
1496
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
1497
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8');
1498
+ }
1499
+
1500
+ function mergeMcpJson(filePath, withType = false) {
1501
+ const data = readJsonFile(filePath);
1502
+ if (data === null) {
1503
+ return { ok: false, message: `${filePath} is not valid JSON; skipped` };
1504
+ }
1505
+ data.mcpServers = data.mcpServers || {};
1506
+ const baseServers = {
1507
+ codegraph: { command: 'codegraph', args: ['serve', '--mcp'] },
1508
+ playwright: { command: 'playwright-mcp' },
1509
+ context7: { command: 'context7-mcp' }
1510
+ };
1511
+ if (data.permissions && Array.isArray(data.permissions.allow)) {
1512
+ const requiredPermissions = [
1513
+ "mcp__context7__resolve-library-id",
1514
+ "mcp__context7__query-docs"
1515
+ ];
1516
+ for (const perm of requiredPermissions) {
1517
+ if (!data.permissions.allow.includes(perm)) {
1518
+ data.permissions.allow.push(perm);
1519
+ }
1520
+ }
1521
+ }
1522
+ for (const [name, server] of Object.entries(baseServers)) {
1523
+ if (!data.mcpServers[name]) {
1524
+ data.mcpServers[name] = withType ? { type: 'stdio', ...server } : server;
1525
+ }
1526
+ }
1527
+ writeJsonFile(filePath, data);
1528
+ const tools = Object.keys(baseServers).join(', ');
1529
+ return { ok: true, message: `configured ${tools} MCP in ${filePath}` };
1530
+ }
1531
+
1532
+ function appendMcpTomlIfMissing(filePath) {
1533
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
1534
+ const existing = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : '';
1535
+ const blocks = [];
1536
+ if (!/\[mcp_servers\.context7\]/.test(existing)) {
1537
+ blocks.push(`[mcp_servers.context7]
1538
+ command = "context7-mcp"
1539
+ startup_timeout_sec = 20
1540
+ tool_timeout_sec = 120
1541
+ `);
1542
+ }
1543
+ if (!/\[mcp_servers\.codegraph\]/.test(existing)) {
1544
+ blocks.push(`[mcp_servers.codegraph]
1545
+ command = "codegraph"
1546
+ args = ["serve", "--mcp"]
1547
+ startup_timeout_sec = 20
1548
+ tool_timeout_sec = 120
1549
+ `);
1550
+ }
1551
+ if (!/\[mcp_servers\.playwright\]/.test(existing)) {
1552
+ blocks.push(`[mcp_servers.playwright]
1553
+ command = "playwright-mcp"
1554
+ startup_timeout_sec = 20
1555
+ tool_timeout_sec = 120
1556
+ `);
1557
+ }
1558
+ if (blocks.length === 0) { return { ok: true, message: `already configured ${filePath}` };
1559
+ }
1560
+ const separator = existing && !existing.endsWith('\n') ? '\n\n' : existing ? '\n' : '';
1561
+ fs.writeFileSync(filePath, existing + separator + blocks.join('\n'), 'utf8');
1562
+ return { ok: true, message: `configured context7, codegraph, playwright MCP in ${filePath}` };
1563
+ }
1564
+
1565
+ function setupMcpServers() {
1566
+ const results = [];
1567
+ results.push(ensureCodegraphCli());
1568
+ results.push(ensureContext7Cli());
1569
+ results.push(ensurePlaywrightMcpCli());
1570
+
1571
+ const codexConfig = homePath('.codex', 'config.toml');
1572
+ if (codexConfig) results.push(appendMcpTomlIfMissing(codexConfig));
1573
+
1574
+ const geminiConfig = homePath('.gemini', 'config', 'mcp_config.json');
1575
+ if (geminiConfig) results.push(mergeMcpJson(geminiConfig, false));
1576
+
1577
+ const antigravityIdeConfig = homePath('.gemini', 'antigravity-ide', 'mcp_config.json');
1578
+ if (antigravityIdeConfig) results.push(mergeMcpJson(antigravityIdeConfig, false));
1579
+
1580
+ const antigravityConfig = homePath('.gemini', 'antigravity', 'mcp_config.json');
1581
+ if (antigravityConfig && fs.existsSync(path.dirname(antigravityConfig))) {
1582
+ results.push(mergeMcpJson(antigravityConfig, false));
1583
+ }
1584
+
1585
+ const claudeConfig = homePath('.claude.json');
1586
+ if (claudeConfig) results.push(mergeMcpJson(claudeConfig, true));
1587
+
1588
+ const claudeSettings = homePath('.claude', 'settings.json');
1589
+ if (claudeSettings) results.push(mergeMcpJson(claudeSettings, true));
1590
+
1591
+ return results;
1592
+ }
1593
+
1594
+ // --- Main CLI ---
1595
+
1596
+ async function main() {
1597
+ const rawArgs = process.argv.slice(2);
1598
+ const command = rawArgs[0] && !rawArgs[0].startsWith('-') ? rawArgs[0] : 'init';
1599
+ const args = command === 'init' ? rawArgs.slice(rawArgs[0] === 'init' ? 1 : 0) : rawArgs.slice(1);
1600
+
1601
+ if (rawArgs.includes('--version') || rawArgs.includes('-v')) {
1602
+ console.log(packageJson.version);
1603
+ return;
1604
+ }
1605
+
1606
+ if (rawArgs.includes('--help') || rawArgs.includes('-h')) {
1607
+ printHelp();
1608
+ return;
1609
+ }
1610
+
1611
+ if (command !== 'init') {
1612
+ console.error(pc.red(`Unknown command: ${command}`));
1613
+ printHelp();
1614
+ process.exit(1);
1615
+ }
1616
+
1617
+ const assumeYes = args.includes('--yes') || args.includes('-y');
1618
+ const skipMcpSetup = args.includes('--no-setup-mcp') || process.env.THACHVD_KIT_SKIP_MCP_SETUP === '1';
1619
+
1620
+ console.log(pc.bold(pc.cyan('='.repeat(50))));
1621
+ console.log(' ' + pc.bold('thachvd-kit') + ' - AI Project Bootstrap');
1622
+ console.log(' Generates shared agent rules and configures MCP tooling');
1623
+ console.log(pc.bold(pc.cyan('='.repeat(50))));
1624
+
1625
+ console.log(pc.dim(' Scanning current project files for a baseline...\n'));
1626
+ const scanned = scanProject();
1627
+
1628
+ console.log(` ${pc.bold('Detected context:')}`);
1629
+ formatScanPreviewLines(scanned).forEach(([label, value]) => {
1630
+ console.log(` - ${label}: ${value}`);
1631
+ });
1632
+ console.log('');
1633
+ if ((scanned.scan_evidence || []).length > 0) {
1634
+ console.log(` ${pc.bold('Evidence:')}`);
1635
+ scanned.scan_evidence.forEach(line => console.log(` - ${line}`));
1636
+ console.log('');
1637
+ }
1638
+
1639
+ // Combine data
1640
+ const data = {
1641
+ project_name: scanned?.project_name || path.basename(targetDir),
1642
+ description: scanned?.description || 'A software project',
1643
+ project_type: scanned?.project_type || 'web-app',
1644
+ primary_language: scanned?.primary_language || 'typescript',
1645
+ frameworks: scanned?.frameworks || [],
1646
+ database: scanned?.database || 'none',
1647
+ uses_docker: scanned?.uses_docker ?? true,
1648
+ cloud: scanned?.cloud || 'none',
1649
+ package_manager: scanned?.package_manager || 'auto-detect',
1650
+ test_framework: scanned?.test_framework || 'auto-detect',
1651
+ max_file_lines: scanned?.max_file_lines || '300'
1652
+ };
1653
+
1654
+ data.database = data.database || scanned?.database || 'none';
1655
+ data.uses_docker = typeof data.uses_docker === 'boolean' ? data.uses_docker : (scanned?.uses_docker ?? true);
1656
+ data.cloud = data.cloud || scanned?.cloud || 'none';
1657
+ data.package_manager = data.package_manager || scanned?.package_manager || 'auto-detect';
1658
+ data.test_framework = data.test_framework || scanned?.test_framework || 'auto-detect';
1659
+ data.max_file_lines = data.max_file_lines || scanned?.max_file_lines || '300';
1660
+ data.framework_details = scanned?.framework_details || [];
1661
+ data.scan_evidence = scanned?.scan_evidence || [];
1662
+ data.app_root = scanned?.app_root || '.';
1663
+
1664
+ data.infrastructure = [];
1665
+ if (data.uses_docker) data.infrastructure.push('docker');
1666
+ if (data.cloud !== 'none') data.infrastructure.push(data.cloud);
1667
+
1668
+ const shouldSetupMcp = !skipMcpSetup;
1669
+
1670
+ console.log(`\n${pc.bold(pc.cyan('Generating Files'))}`);
1671
+
1672
+ // Shared entry files are written after the .agent folder is available.
1673
+
1674
+ // Copy .agent folder
1675
+ const { copied, skipped } = copyAgentFolder();
1676
+ if (copied > 0) {
1677
+ console.log(` ${pc.green('OK')} Copied ${pc.bold('.agent/')} (${copied} file${copied !== 1 ? 's' : ''} added${skipped > 0 ? `, ${skipped} skipped` : ''})`);
1678
+ } else if (skipped > 0) {
1679
+ console.log(` ${pc.yellow('~')} ${pc.bold('.agent/')} already exists - ${skipped} file${skipped !== 1 ? 's' : ''} skipped (no overwrite)`);
1680
+ }
1681
+
1682
+ const nativeSkills = copySelectedSkillFolders(data, assumeYes);
1683
+ if (nativeSkills.copied > 0) {
1684
+ console.log(` ${pc.green('OK')} Copied native skills to ${pc.bold('~/.codex/skills/')} and ${pc.bold('.claude/skills/')} (${nativeSkills.skills.join(', ')})`);
1685
+ } else if (nativeSkills.skipped > 0) {
1686
+ console.log(` ${pc.yellow('~')} Native skills already exist - ${nativeSkills.skipped} file${nativeSkills.skipped !== 1 ? 's' : ''} skipped`);
1687
+ }
1688
+
1689
+
1690
+ const generatedFiles = [
1691
+ ['AGENTS.md', generateSharedAgentsMd(data)],
1692
+ ['CLAUDE.md', generateSharedClaudeMd()],
1693
+ ['GEMINI.md', generateSharedGeminiMd()],
1694
+ ['.cursorrules', generateSharedCursorrules(data)],
1695
+ [path.join('.agent', 'docs', 'project.md'), generateProjectDoc(data)],
1696
+ [path.join('.agent', 'docs', 'architecture.md'), generateArchitectureDoc(data)],
1697
+ [path.join('.agent', 'docs', 'conventions.md'), generateConventionsDoc(data)],
1698
+ [path.join('.agent', 'docs', 'workflow.md'), generateWorkflowDoc()],
1699
+ [path.join('.agent', 'docs', 'tooling.md'), generateToolingDoc(data)]
1700
+ ];
1701
+
1702
+ for (const [relativePath, content] of generatedFiles) {
1703
+ const wrote = await writeGeneratedFile(relativePath, content, assumeYes);
1704
+ if (wrote) console.log(` ${pc.green('OK')} Generated ${pc.bold(relativePath)}`);
1705
+ else console.log(` ${pc.yellow('~')} Skipped ${pc.bold(relativePath)}`);
1706
+ }
1707
+
1708
+ if (shouldSetupMcp) {
1709
+ console.log(`\n${pc.bold(pc.cyan('Configuring MCP'))}`);
1710
+ for (const result of setupMcpServers()) {
1711
+ const marker = result.ok ? pc.green('OK') : pc.yellow('~');
1712
+ console.log(` ${marker} ${result.message}`);
1713
+ }
1714
+ }
1715
+
1716
+ console.log(`\n${pc.bold(pc.green('Done!'))}
1717
+
1718
+ ${pc.bold('Project:')} ${data.project_name}
1719
+ ${pc.bold('Stack:')} ${data.primary_language} | ${(data.frameworks || []).join(', ') || 'no framework'}
1720
+
1721
+ ${pc.bold('Generated:')}
1722
+ - ${pc.bold('AGENTS.md')} shared entry for Codex, Antigravity, Claude Code, and Cursor
1723
+ - ${pc.bold('CLAUDE.md')} Claude Code entry that imports AGENTS.md
1724
+ - ${pc.bold('GEMINI.md')} Antigravity entry
1725
+ - ${pc.bold('.cursorrules')} Cursor entry
1726
+ - ${pc.bold('.agent/docs/')} scan-based project rules
1727
+ - ${pc.bold('.agent/')} skills, workflows, agents, and rules
1728
+ - ${pc.bold('~/.codex/skills/')} selected Codex skills installed globally (visible in $ menu)
1729
+ - ${pc.bold('.claude/skills/')} selected Claude Code native skills
1730
+
1731
+ ${pc.bold('MCP/tooling setup:')}
1732
+ - Codegraph CLI checked or installed; MCP configured for Codex, Gemini/Antigravity, and Claude Code
1733
+ - Context7 MCP configured for Codex and Gemini/Antigravity
1734
+ - Playwright MCP configured for Codex, Gemini/Antigravity, and Claude Code
1735
+ - Browser install hint: ${pc.bold(resolveToolingSetup(data).playwrightCommand)}
1736
+ - Codegraph index hint: run ${pc.bold('codegraph init -i')} when a project index is missing, then keep ${pc.bold('.codegraph/')} uncommitted
1737
+
1738
+ ${pc.bold('Next steps:')}
1739
+ 1. Copy the prompt below into your AI editor and describe what the project does
1740
+ 2. Commit ${pc.bold('AGENTS.md')}, ${pc.bold('CLAUDE.md')}, ${pc.bold('GEMINI.md')}, ${pc.bold('.cursorrules')}, and ${pc.bold('.agent/')}
1741
+ 3. Open the project in Codex, Antigravity, Claude Code, or Cursor
1742
+
1743
+ ${pc.bold('Copy this prompt into your AI editor:')}
1744
+ ${pc.dim('---')}
1745
+ Read CLAUDE.md (if using Claude Code), AGENTS.md, and all files under .agent/docs/.
1746
+ Ask me what this project does and any important conventions I want preserved.
1747
+ Then scan this repository.
1748
+ Update CLAUDE.md, AGENTS.md, .cursorrules and .agent/docs/project.md, .agent/docs/architecture.md, .agent/docs/conventions.md, .agent/docs/workflow.md, and .agent/docs/tooling.md with factual project-specific rules.
1749
+ Do not implement product code.
1750
+ Remove TODO: refine only when backed by evidence from the codebase.
1751
+ ${pc.dim('---')}
1752
+ `);
1753
+ }
1754
+
1755
+ main().catch(err => {
1756
+ console.error(pc.red('\nError during init:'), err);
1757
+ process.exit(1);
1758
+ });
1742
1759