thachvd-kit 1.0.17 → 1.0.19

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,10 +1,11 @@
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 { spawnSync } = require('child_process');
6
+ const prompts = require('prompts');
7
+ const pc = require('picocolors');
8
+ const packageJson = require('../package.json');
8
9
 
9
10
  const sourceDir = path.resolve(__dirname, '..');
10
11
  const targetDir = process.cwd();
@@ -62,7 +63,7 @@ const INFRA_SKILLS = {
62
63
  vercel: ["deployment-procedures"],
63
64
  };
64
65
 
65
- function resolveSkills(data) {
66
+ function resolveSkills(data) {
66
67
  const skills = new Set();
67
68
  const lang = (data.primary_language || "").toLowerCase();
68
69
 
@@ -87,8 +88,16 @@ function resolveSkills(data) {
87
88
  skills.add("clean-code");
88
89
  skills.add("systematic-debugging");
89
90
 
90
- return Array.from(skills).sort();
91
- }
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
+ }
92
101
 
93
102
  const FRAMEWORK_CHOICES = [
94
103
  'vue', 'react', 'next', 'nuxt', 'svelte', 'angular',
@@ -111,28 +120,58 @@ const IGNORED_SCAN_DIRS = new Set(['.git', 'node_modules', 'vendor', 'dist', 'bu
111
120
 
112
121
  function printHelp() {
113
122
  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
- }
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, and Claude Code
140
+ CLAUDE.md Claude Code entry file that imports AGENTS.md
141
+ GEMINI.md Antigravity entry file that points to AGENTS.md
142
+ .agent/ Shared docs, skills, workflows, agents, and rules
143
+ ~/.codex/skills/ Selected Codex skills copied to global user dir (shows in $ menu)
144
+ .claude/skills/ Selected Claude Code project skills copied from .agent/skills
145
+
146
+ ${pc.bold('Workflow guide:')}
147
+ Idea is fuzzy /brainstorm Explore options, tradeoffs, and recommended direction
148
+ New app from scratch /create Turn an app idea into plan + implementation flow
149
+ Feature is clear /plan Create docs/PLAN-*.md first, no code yet
150
+ Existing app update /enhance Add or change a feature in an existing codebase
151
+ Bug or failing behavior /debug Investigate symptoms, hypotheses, root cause, fix
152
+ UI / UX work ui-ux-pro-max, frontend-specialist, $frontend-design, $webapp-testing
153
+ Run or add tests /test Generate tests, run tests, check coverage
154
+ Preview locally /preview Start, stop, restart, or health-check dev server
155
+ Deploy / infra /deploy Release, hosting, Docker, cloud, environment setup
156
+ Project state /status Summarize stack, progress, preview, pending work
157
+ Multi-domain work /orchestrate Coordinate frontend, backend, data, security, QA
158
+
159
+ ${pc.bold('Common explicit skill prompts:')}
160
+ Use /brainstorm for this feature idea before planning.
161
+ Use /plan for this feature; do not write code yet.
162
+ Use /enhance to implement this planned feature.
163
+ Use $clean-code before editing this module.
164
+ Use $systematic-debugging to investigate this bug.
165
+ Use $webapp-testing to verify the UI with Playwright.
166
+ Use $verification-before-completion before claiming done.
167
+ Use $writing-skills to improve a SKILL.md description.
168
+
169
+ ${pc.bold('Tip:')}
170
+ Skills are copied for Codex/Claude, but you can call them explicitly with $skill-name
171
+ when implicit matching does not fire.
172
+ `);
173
+
174
+ }
136
175
 
137
176
  function formatScanPreviewLines(scanned) {
138
177
  return [
@@ -861,9 +900,10 @@ Shared operating instructions for Codex, Antigravity, and Claude Code.
861
900
 
862
901
  1. Read this file first.
863
902
  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.
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.
867
907
 
868
908
  If any \`.agent/docs/*.md\` file still contains \`TODO: refine\`, update the docs by scanning the project before making product code changes.
869
909
 
@@ -872,10 +912,21 @@ If any \`.agent/docs/*.md\` file still contains \`TODO: refine\`, update the doc
872
912
  - Questions and analysis: answer directly, cite relevant files when useful, and do not edit code.
873
913
  - Simple fix: inspect dependencies, make the smallest change, run focused verification.
874
914
  - 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.
877
-
878
- ## Rules
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
879
930
 
880
931
  - Respond in the user's language; keep code, identifiers, and code comments in English.
881
932
  - State assumptions when the request is ambiguous.
@@ -886,13 +937,16 @@ If any \`.agent/docs/*.md\` file still contains \`TODO: refine\`, update the doc
886
937
 
887
938
  ## Shared Knowledge
888
939
 
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
- }
940
+ - Project docs: \`.agent/docs/\`
941
+ - Specialist agents: \`.agent/agents/\`
942
+ - Skills: \`.agent/skills/\`
943
+ - Codex global skills: \`~/.codex/skills/\` (installed by thachvd-kit init)
944
+ - Claude Code project skills: \`.claude/skills/\`
945
+ - Workflows: \`.agent/workflows/\`
946
+ - Tooling setup: \`.agent/docs/tooling.md\`
947
+ - Antigravity mirror rules: \`.agent/rules/GEMINI.md\`
948
+ `;
949
+ }
896
950
 
897
951
  function generateSharedClaudeMd() {
898
952
  return `@AGENTS.md
@@ -1060,17 +1114,28 @@ function generateConventionsDoc(data) {
1060
1114
  `;
1061
1115
  }
1062
1116
 
1063
- function generateWorkflowDoc() {
1117
+ function generateWorkflowDoc() {
1064
1118
  return `# Agent Workflow
1065
1119
 
1066
1120
  ## Before Every Task
1067
1121
 
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
1122
+ 1. Read AGENTS.md.
1123
+ 2. Read .agent/docs/project.md.
1124
+ 3. Classify the request: question, survey, simple fix, feature, refactor, debug, UI, security, or deploy.
1125
+ 4. Read only the relevant docs under .agent/agents, .agent/skills, .claude/skills, and .agent/workflows.
1126
+
1127
+ ## Skill Selection
1128
+
1129
+ - Prefer native skill discovery when the client exposes it.
1130
+ - Codex global skills live in ~/.codex/skills/ and are visible via the $ menu; thachvd-kit copies selected skills there on init.
1131
+ - Claude Code project skills live in .claude/skills.
1132
+ - Shared fallback skills live in .agent/skills.
1133
+ - Do not load skill bodies by default.
1134
+ - 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.
1135
+ - Use explicit skill names in prompts for predictable behavior, for example $webapp-testing or $clean-code.
1136
+ - Run thachvd-kit --help to see common workflow and skill recommendations.
1137
+
1138
+ ## Implementation Flow
1074
1139
 
1075
1140
  1. State assumptions and success criteria when the task is not trivial.
1076
1141
  2. Inspect dependent files before editing.
@@ -1085,12 +1150,158 @@ function generateWorkflowDoc() {
1085
1150
  - Update .agent/docs/conventions.md when repeated project patterns become clear.
1086
1151
  - Update .agent/docs/project.md when stack, scripts, app root, test tooling, or routing changes.
1087
1152
  - Keep AGENTS.md concise. Put project-specific detail in .agent/docs.
1088
- `;
1089
- }
1153
+ `;
1154
+ }
1155
+
1156
+ function resolveToolingSetup(data) {
1157
+ const primaryLang = data.primary_language || 'javascript';
1158
+ const packageManager = data.package_manager || 'npm';
1159
+ const isPython = primaryLang === 'python';
1160
+ const runner = packageManager === 'pnpm'
1161
+ ? 'pnpm dlx'
1162
+ : packageManager === 'yarn'
1163
+ ? 'yarn dlx'
1164
+ : packageManager === 'bun'
1165
+ ? 'bunx'
1166
+ : 'npx';
1167
+ const playwrightCommand = isPython
1168
+ ? 'pip install playwright && playwright install chromium'
1169
+ : `${runner} playwright install`;
1170
+
1171
+ return { runner, playwrightCommand };
1172
+ }
1173
+
1174
+ function generateToolingDoc(data) {
1175
+ const { runner, playwrightCommand } = resolveToolingSetup(data);
1176
+
1177
+ return `# Optional Tooling
1178
+
1179
+ This kit keeps tool setup explicit. Do not assume these tools are available until you verify them in the current environment.
1180
+
1181
+ ## Playwright
1182
+
1183
+ Use Playwright for browser and UI verification when a task touches web behavior.
1184
+
1185
+ - Check availability: \`${runner} playwright --version\`
1186
+ - Install browsers: \`${playwrightCommand}\`
1187
+ - Codex MCP CLI setup: \`codex mcp add playwright -- npx -y @playwright/mcp\`
1188
+ - Auto setup: \`thachvd-kit\`
1189
+ - Kit helper: \`python .agent/skills/webapp-testing/scripts/playwright_runner.py <url> --screenshot\`
1190
+
1191
+ If the project already has Playwright configured, prefer the project's existing scripts.
1192
+
1193
+ Codex \`config.toml\` example:
1194
+
1195
+ \`\`\`toml
1196
+ [mcp_servers.context7]
1197
+ command = "npx"
1198
+ args = ["-y", "@upstash/context7-mcp"]
1199
+ startup_timeout_sec = 20
1200
+ tool_timeout_sec = 120
1201
+
1202
+ [mcp_servers.playwright]
1203
+ command = "npx"
1204
+ args = ["-y", "@playwright/mcp"]
1205
+ startup_timeout_sec = 20
1206
+ tool_timeout_sec = 120
1207
+ \`\`\`
1208
+
1209
+ ## Codegraph
1210
+
1211
+ Use codegraph for codebase exploration when the environment exposes it. The index is local state and should not be committed.
1212
+
1213
+ - Check for an index: look for \`.codegraph/\`
1214
+ - Check CLI availability: \`codegraph --help\`
1215
+ - Install CLI if needed: \`npm install -g @colbymchenry/codegraph\`
1216
+ - Codex MCP CLI setup: \`codex mcp add codegraph -- codegraph serve --mcp\`
1217
+ - Auto setup: \`thachvd-kit\`
1218
+ - If your Codex environment provides the codegraph CLI, run its project indexing step from the repository root.
1219
+ - Keep \`.codegraph/\` ignored in git.
1220
+
1221
+ Codex \`config.toml\` example:
1222
+
1223
+ \`\`\`toml
1224
+ [mcp_servers.codegraph]
1225
+ command = "codegraph"
1226
+ args = ["serve", "--mcp"]
1227
+ startup_timeout_sec = 20
1228
+ tool_timeout_sec = 120
1229
+ \`\`\`
1230
+
1231
+ 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.
1232
+
1233
+ Gemini CLI / Antigravity \`mcp_config.json\` example:
1234
+
1235
+ \`\`\`json
1236
+ {
1237
+ "mcpServers": {
1238
+ "codegraph": {
1239
+ "command": "codegraph",
1240
+ "args": ["serve", "--mcp"]
1241
+ },
1242
+ "context7": {
1243
+ "command": "npx",
1244
+ "args": ["-y", "@upstash/context7-mcp"]
1245
+ },
1246
+ "playwright": {
1247
+ "command": "npx",
1248
+ "args": ["-y", "@playwright/mcp"]
1249
+ }
1250
+ }
1251
+ }
1252
+ \`\`\`
1253
+
1254
+ Common local paths:
1255
+
1256
+ - Gemini CLI: \`~/.gemini/config/mcp_config.json\`
1257
+ - Antigravity IDE: \`~/.gemini/antigravity-ide/mcp_config.json\`
1258
+ - Older Antigravity setups may use \`~/.gemini/antigravity/mcp_config.json\`
1259
+
1260
+ Claude Code user config example:
1261
+
1262
+ \`\`\`json
1263
+ {
1264
+ "mcpServers": {
1265
+ "codegraph": {
1266
+ "type": "stdio",
1267
+ "command": "codegraph",
1268
+ "args": ["serve", "--mcp"]
1269
+ },
1270
+ "playwright": {
1271
+ "type": "stdio",
1272
+ "command": "npx",
1273
+ "args": ["-y", "@playwright/mcp"]
1274
+ }
1275
+ }
1276
+ }
1277
+ \`\`\`
1278
+
1279
+ Common local path:
1280
+
1281
+ - Claude Code: \`~/.claude.json\`
1282
+
1283
+ ## Native Skill Folders
1284
+
1285
+ 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/\`.
1286
+
1287
+ - Codex global skills: \`~/.codex/skills/\` — installed by \`thachvd-kit init\`, visible in Codex \`$\` menu
1288
+ - Claude Code project skills: \`.claude/skills/\`
1289
+ - Antigravity: reads \`.agent/skills/\` directly (no copy needed)
1290
+ - Keep \`.agent/skills/\` as the full shared source of truth committed with the repo.
1291
+ - Do not commit \`~/.codex/skills/\`; it is local user state.
1292
+
1293
+ Recommended candidates to copy first:
1294
+
1295
+ - \`clean-code\`
1296
+ - \`systematic-debugging\`
1297
+ - \`verification-before-completion\`
1298
+ - Stack-specific skills listed in \`.agent/docs/project.md\`
1299
+ `;
1300
+ }
1090
1301
 
1091
1302
  // --- Agent Folder Copy ---
1092
1303
 
1093
- function copyAgentFolder() {
1304
+ function copyAgentFolder() {
1094
1305
  const srcAgent = path.join(sourceDir, '.agent');
1095
1306
  if (!fs.existsSync(srcAgent)) return { copied: 0, skipped: 0 };
1096
1307
 
@@ -1120,31 +1331,198 @@ function copyAgentFolder() {
1120
1331
  }
1121
1332
  }
1122
1333
 
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?`,
1135
- initial: false
1136
- });
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() {
1334
+ copyDir(srcAgent, destAgent);
1335
+ return { copied, skipped };
1336
+ }
1337
+
1338
+ function copySelectedSkillFolders(data, assumeYes) {
1339
+ const srcSkills = path.join(sourceDir, '.agent', 'skills');
1340
+ if (!fs.existsSync(srcSkills)) return { copied: 0, skipped: 0, missing: 0, skills: [] };
1341
+
1342
+ const selectedSkills = resolveNativeSkills(data);
1343
+
1344
+ // ~/.codex/skills/ — global Codex user skills dir, always visible in the $ menu
1345
+ const codexSkillsDir = homePath('.codex', 'skills');
1346
+ // .claude/skills/ — project-level Claude Code skills
1347
+ const claudeSkillsDir = path.join(targetDir, '.claude', 'skills');
1348
+
1349
+ const destinations = [claudeSkillsDir];
1350
+ if (codexSkillsDir) destinations.push(codexSkillsDir);
1351
+
1352
+ let copied = 0;
1353
+ let skipped = 0;
1354
+ let missing = 0;
1355
+
1356
+ function copyDir(src, dest) {
1357
+ fs.mkdirSync(dest, { recursive: true });
1358
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
1359
+ const srcPath = path.join(src, entry.name);
1360
+ const destPath = path.join(dest, entry.name);
1361
+ if (entry.isDirectory()) {
1362
+ copyDir(srcPath, destPath);
1363
+ } else if (fs.existsSync(destPath) && !assumeYes) {
1364
+ skipped++;
1365
+ } else {
1366
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
1367
+ fs.copyFileSync(srcPath, destPath);
1368
+ copied++;
1369
+ }
1370
+ }
1371
+ }
1372
+
1373
+ for (const skill of selectedSkills) {
1374
+ const srcSkill = path.join(srcSkills, skill);
1375
+ if (!fs.existsSync(srcSkill)) {
1376
+ missing++;
1377
+ continue;
1378
+ }
1379
+ for (const destSkills of destinations) {
1380
+ copyDir(srcSkill, path.join(destSkills, skill));
1381
+ }
1382
+ }
1383
+
1384
+ return { copied, skipped, missing, skills: selectedSkills };
1385
+ }
1386
+
1387
+ async function writeGeneratedFile(relativePath, content, assumeYes) {
1388
+ const outputPath = path.join(targetDir, relativePath);
1389
+ const exists = fs.existsSync(outputPath);
1390
+ if (exists && !assumeYes) return false;
1391
+
1392
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
1393
+ fs.writeFileSync(outputPath, content, 'utf8');
1394
+ return true;
1395
+ }
1396
+
1397
+ function homePath(...parts) {
1398
+ const home = process.env.USERPROFILE || process.env.HOME;
1399
+ return home ? path.join(home, ...parts) : null;
1400
+ }
1401
+
1402
+ function commandExists(command) {
1403
+ const checker = process.platform === 'win32' ? 'where' : 'command';
1404
+ const args = process.platform === 'win32' ? [command] : ['-v', command];
1405
+ const result = spawnSync(checker, args, { encoding: 'utf8', shell: process.platform !== 'win32' });
1406
+ return result.status === 0;
1407
+ }
1408
+
1409
+ function ensureCodegraphCli() {
1410
+ if (commandExists('codegraph')) {
1411
+ return { ok: true, message: 'codegraph CLI already installed' };
1412
+ }
1413
+ if (!commandExists('npm')) {
1414
+ return { ok: false, message: 'npm not found; run: npm install -g @colbymchenry/codegraph' };
1415
+ }
1416
+ const result = spawnSync('npm', ['install', '-g', '@colbymchenry/codegraph'], {
1417
+ encoding: 'utf8',
1418
+ stdio: 'pipe'
1419
+ });
1420
+ if (result.status !== 0) {
1421
+ return { ok: false, message: 'failed to install codegraph; run: npm install -g @colbymchenry/codegraph' };
1422
+ }
1423
+ return { ok: true, message: 'installed codegraph CLI' };
1424
+ }
1425
+
1426
+ function readJsonFile(filePath) {
1427
+ if (!fs.existsSync(filePath)) return {};
1428
+ try {
1429
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
1430
+ } catch {
1431
+ return null;
1432
+ }
1433
+ }
1434
+
1435
+ function writeJsonFile(filePath, data) {
1436
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
1437
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8');
1438
+ }
1439
+
1440
+ function mergeMcpJson(filePath, withType = false, includeContext7 = false) {
1441
+ const data = readJsonFile(filePath);
1442
+ if (data === null) {
1443
+ return { ok: false, message: `${filePath} is not valid JSON; skipped` };
1444
+ }
1445
+ data.mcpServers = data.mcpServers || {};
1446
+ const baseServers = {
1447
+ codegraph: { command: 'codegraph', args: ['serve', '--mcp'] },
1448
+ playwright: { command: 'npx', args: ['-y', '@playwright/mcp'] }
1449
+ };
1450
+ if (includeContext7) {
1451
+ baseServers.context7 = { command: 'npx', args: ['-y', '@upstash/context7-mcp'] };
1452
+ }
1453
+ for (const [name, server] of Object.entries(baseServers)) {
1454
+ if (!data.mcpServers[name]) {
1455
+ data.mcpServers[name] = withType ? { type: 'stdio', ...server } : server;
1456
+ }
1457
+ }
1458
+ writeJsonFile(filePath, data);
1459
+ const tools = Object.keys(baseServers).join(', ');
1460
+ return { ok: true, message: `configured ${tools} MCP in ${filePath}` };
1461
+ }
1462
+
1463
+ function appendMcpTomlIfMissing(filePath) {
1464
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
1465
+ const existing = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : '';
1466
+ const blocks = [];
1467
+ if (!/\[mcp_servers\.context7\]/.test(existing)) {
1468
+ blocks.push(`[mcp_servers.context7]
1469
+ command = "npx"
1470
+ args = ["-y", "@upstash/context7-mcp"]
1471
+ startup_timeout_sec = 20
1472
+ tool_timeout_sec = 120
1473
+ `);
1474
+ }
1475
+ if (!/\[mcp_servers\.codegraph\]/.test(existing)) {
1476
+ blocks.push(`[mcp_servers.codegraph]
1477
+ command = "codegraph"
1478
+ args = ["serve", "--mcp"]
1479
+ startup_timeout_sec = 20
1480
+ tool_timeout_sec = 120
1481
+ `);
1482
+ }
1483
+ if (!/\[mcp_servers\.playwright\]/.test(existing)) {
1484
+ blocks.push(`[mcp_servers.playwright]
1485
+ command = "npx"
1486
+ args = ["-y", "@playwright/mcp"]
1487
+ startup_timeout_sec = 20
1488
+ tool_timeout_sec = 120
1489
+ `);
1490
+ }
1491
+ if (blocks.length === 0) {
1492
+ return { ok: true, message: `already configured ${filePath}` };
1493
+ }
1494
+ const separator = existing && !existing.endsWith('\n') ? '\n\n' : existing ? '\n' : '';
1495
+ fs.writeFileSync(filePath, existing + separator + blocks.join('\n'), 'utf8');
1496
+ return { ok: true, message: `configured context7, codegraph, playwright MCP in ${filePath}` };
1497
+ }
1498
+
1499
+ function setupMcpServers() {
1500
+ const results = [];
1501
+ results.push(ensureCodegraphCli());
1502
+
1503
+ const codexConfig = homePath('.codex', 'config.toml');
1504
+ if (codexConfig) results.push(appendMcpTomlIfMissing(codexConfig));
1505
+
1506
+ const geminiConfig = homePath('.gemini', 'config', 'mcp_config.json');
1507
+ if (geminiConfig) results.push(mergeMcpJson(geminiConfig, false, true));
1508
+
1509
+ const antigravityIdeConfig = homePath('.gemini', 'antigravity-ide', 'mcp_config.json');
1510
+ if (antigravityIdeConfig) results.push(mergeMcpJson(antigravityIdeConfig, false, true));
1511
+
1512
+ const antigravityConfig = homePath('.gemini', 'antigravity', 'mcp_config.json');
1513
+ if (antigravityConfig && fs.existsSync(path.dirname(antigravityConfig))) {
1514
+ results.push(mergeMcpJson(antigravityConfig, false, true));
1515
+ }
1516
+
1517
+ const claudeConfig = homePath('.claude.json');
1518
+ if (claudeConfig) results.push(mergeMcpJson(claudeConfig, true));
1519
+
1520
+ return results;
1521
+ }
1522
+
1523
+ // --- Main CLI ---
1524
+
1525
+ async function main() {
1148
1526
  const rawArgs = process.argv.slice(2);
1149
1527
  const command = rawArgs[0] && !rawArgs[0].startsWith('-') ? rawArgs[0] : 'init';
1150
1528
  const args = command === 'init' ? rawArgs.slice(rawArgs[0] === 'init' ? 1 : 0) : rawArgs.slice(1);
@@ -1165,137 +1543,42 @@ async function main() {
1165
1543
  process.exit(1);
1166
1544
  }
1167
1545
 
1168
- let useScan = args.includes('--scan');
1169
- const assumeYes = args.includes('--yes') || args.includes('-y');
1170
-
1171
- console.log(pc.bold(pc.cyan('='.repeat(50))));
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('='.repeat(50))));
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
- };
1546
+ const assumeYes = args.includes('--yes') || args.includes('-y');
1547
+ const skipMcpSetup = args.includes('--no-setup-mcp') || process.env.THACHVD_KIT_SKIP_MCP_SETUP === '1';
1548
+
1549
+ console.log(pc.bold(pc.cyan('='.repeat(50))));
1550
+ console.log(' ' + pc.bold('thachvd-kit') + ' - AI Project Bootstrap');
1551
+ console.log(' Generates shared agent rules and configures MCP tooling');
1552
+ console.log(pc.bold(pc.cyan('='.repeat(50))));
1553
+
1554
+ console.log(pc.dim(' Scanning current project files for a baseline...\n'));
1555
+ const scanned = scanProject();
1556
+
1557
+ console.log(` ${pc.bold('Detected context:')}`);
1558
+ formatScanPreviewLines(scanned).forEach(([label, value]) => {
1559
+ console.log(` - ${label}: ${value}`);
1560
+ });
1561
+ console.log('');
1562
+ if ((scanned.scan_evidence || []).length > 0) {
1563
+ console.log(` ${pc.bold('Evidence:')}`);
1564
+ scanned.scan_evidence.forEach(line => console.log(` - ${line}`));
1565
+ console.log('');
1566
+ }
1567
+
1568
+ // Combine data
1569
+ const data = {
1570
+ project_name: scanned?.project_name || path.basename(targetDir),
1571
+ description: scanned?.description || 'A software project',
1572
+ project_type: scanned?.project_type || 'web-app',
1573
+ primary_language: scanned?.primary_language || 'typescript',
1574
+ frameworks: scanned?.frameworks || [],
1575
+ database: scanned?.database || 'none',
1576
+ uses_docker: scanned?.uses_docker ?? true,
1577
+ cloud: scanned?.cloud || 'none',
1578
+ package_manager: scanned?.package_manager || 'auto-detect',
1579
+ test_framework: scanned?.test_framework || 'auto-detect',
1580
+ max_file_lines: scanned?.max_file_lines || '300'
1581
+ };
1299
1582
 
1300
1583
  data.database = data.database || scanned?.database || 'none';
1301
1584
  data.uses_docker = typeof data.uses_docker === 'boolean' ? data.uses_docker : (scanned?.uses_docker ?? true);
@@ -1307,11 +1590,13 @@ async function main() {
1307
1590
  data.scan_evidence = scanned?.scan_evidence || [];
1308
1591
  data.app_root = scanned?.app_root || '.';
1309
1592
 
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'))}`);
1593
+ data.infrastructure = [];
1594
+ if (data.uses_docker) data.infrastructure.push('docker');
1595
+ if (data.cloud !== 'none') data.infrastructure.push(data.cloud);
1596
+
1597
+ const shouldSetupMcp = !skipMcpSetup;
1598
+
1599
+ console.log(`\n${pc.bold(pc.cyan('Generating Files'))}`);
1315
1600
 
1316
1601
  // Shared entry files are written after the .agent folder is available.
1317
1602
 
@@ -1319,28 +1604,44 @@ async function main() {
1319
1604
  const { copied, skipped } = copyAgentFolder();
1320
1605
  if (copied > 0) {
1321
1606
  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 = [
1607
+ } else if (skipped > 0) {
1608
+ console.log(` ${pc.yellow('~')} ${pc.bold('.agent/')} already exists - ${skipped} file${skipped !== 1 ? 's' : ''} skipped (no overwrite)`);
1609
+ }
1610
+
1611
+ const nativeSkills = copySelectedSkillFolders(data, assumeYes);
1612
+ if (nativeSkills.copied > 0) {
1613
+ console.log(` ${pc.green('OK')} Copied native skills to ${pc.bold('~/.codex/skills/')} and ${pc.bold('.claude/skills/')} (${nativeSkills.skills.join(', ')})`);
1614
+ } else if (nativeSkills.skipped > 0) {
1615
+ console.log(` ${pc.yellow('~')} Native skills already exist - ${nativeSkills.skipped} file${nativeSkills.skipped !== 1 ? 's' : ''} skipped`);
1616
+ }
1617
+
1618
+
1619
+ const generatedFiles = [
1328
1620
  ['AGENTS.md', generateSharedAgentsMd(data)],
1329
1621
  ['CLAUDE.md', generateSharedClaudeMd()],
1330
1622
  ['GEMINI.md', generateSharedGeminiMd()],
1331
1623
  [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!'))}
1624
+ [path.join('.agent', 'docs', 'architecture.md'), generateArchitectureDoc(data)],
1625
+ [path.join('.agent', 'docs', 'conventions.md'), generateConventionsDoc(data)],
1626
+ [path.join('.agent', 'docs', 'workflow.md'), generateWorkflowDoc()],
1627
+ [path.join('.agent', 'docs', 'tooling.md'), generateToolingDoc(data)]
1628
+ ];
1629
+
1630
+ for (const [relativePath, content] of generatedFiles) {
1631
+ const wrote = await writeGeneratedFile(relativePath, content, assumeYes);
1632
+ if (wrote) console.log(` ${pc.green('OK')} Generated ${pc.bold(relativePath)}`);
1633
+ else console.log(` ${pc.yellow('~')} Skipped ${pc.bold(relativePath)}`);
1634
+ }
1635
+
1636
+ if (shouldSetupMcp) {
1637
+ console.log(`\n${pc.bold(pc.cyan('Configuring MCP'))}`);
1638
+ for (const result of setupMcpServers()) {
1639
+ const marker = result.ok ? pc.green('OK') : pc.yellow('~');
1640
+ console.log(` ${marker} ${result.message}`);
1641
+ }
1642
+ }
1643
+
1644
+ console.log(`\n${pc.bold(pc.green('Done!'))}
1344
1645
 
1345
1646
  ${pc.bold('Project:')} ${data.project_name}
1346
1647
  ${pc.bold('Stack:')} ${data.primary_language} | ${(data.frameworks || []).join(', ') || 'no framework'}
@@ -1348,23 +1649,33 @@ async function main() {
1348
1649
  ${pc.bold('Generated:')}
1349
1650
  - ${pc.bold('AGENTS.md')} shared entry for Codex, Antigravity, and Claude Code
1350
1651
  - ${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
-
1355
- ${pc.bold('Next steps:')}
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('---')}
1652
+ - ${pc.bold('GEMINI.md')} Antigravity entry
1653
+ - ${pc.bold('.agent/docs/')} scan-based project rules
1654
+ - ${pc.bold('.agent/')} skills, workflows, agents, and rules
1655
+ - ${pc.bold('~/.codex/skills/')} selected Codex skills installed globally (visible in $ menu)
1656
+ - ${pc.bold('.claude/skills/')} selected Claude Code native skills
1657
+
1658
+ ${pc.bold('MCP/tooling setup:')}
1659
+ - Codegraph CLI checked or installed; MCP configured for Codex, Gemini/Antigravity, and Claude Code
1660
+ - Context7 MCP configured for Codex and Gemini/Antigravity
1661
+ - Playwright MCP configured for Codex, Gemini/Antigravity, and Claude Code
1662
+ - Browser install hint: ${pc.bold(resolveToolingSetup(data).playwrightCommand)}
1663
+ - Codegraph index hint: run ${pc.bold('codegraph init -i')} when a project index is missing, then keep ${pc.bold('.codegraph/')} uncommitted
1664
+
1665
+ ${pc.bold('Next steps:')}
1666
+ 1. Copy the prompt below into your AI editor and describe what the project does
1667
+ 2. Commit ${pc.bold('AGENTS.md')}, ${pc.bold('CLAUDE.md')}, ${pc.bold('GEMINI.md')}, and ${pc.bold('.agent/')}
1668
+ 3. Open the project in Codex, Antigravity, or Claude Code
1669
+
1670
+ ${pc.bold('Copy this prompt into your AI editor:')}
1671
+ ${pc.dim('---')}
1672
+ Read AGENTS.md and all files under .agent/docs/.
1673
+ Ask me what this project does and any important conventions I want preserved.
1674
+ Then scan this repository.
1675
+ Update AGENTS.md 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.
1676
+ Do not implement product code.
1677
+ Remove TODO: refine only when backed by evidence from the codebase.
1678
+ ${pc.dim('---')}
1368
1679
  `);
1369
1680
  }
1370
1681