thachvd-kit 1.0.16 → 1.0.18
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/.agent/agents/frontend-specialist.md +2 -2
- package/.agent/agents/mobile-developer.md +4 -4
- package/.agent/docs/architecture.md +22 -22
- package/.agent/docs/conventions.md +21 -21
- package/.agent/docs/project.md +40 -40
- package/.agent/docs/tooling.md +123 -0
- package/.agent/docs/workflow.md +28 -17
- package/.agent/rules/GEMINI.md +45 -45
- package/.agent/skills/desktop-design/SKILL.md +25 -25
- package/.agent/skills/project-onboarding/SKILL.md +42 -42
- package/.agent/skills/react-frontend/SKILL.md +25 -25
- package/LICENSE +21 -21
- package/README.md +86 -55
- package/agents/frontend-specialist.md +2 -2
- package/agents/mobile-developer.md +4 -4
- package/bin/cli.js +623 -317
- package/kit/PROMPT_RECIPE.md +30 -30
- package/kit/README.md +29 -29
- package/package.json +3 -3
- package/rules/GEMINI.md +45 -45
- package/skills/desktop-design/SKILL.md +25 -25
- package/skills/project-onboarding/SKILL.md +42 -42
- package/skills/react-frontend/SKILL.md +25 -25
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
|
|
6
|
-
const
|
|
7
|
-
const
|
|
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
|
|
116
|
-
thachvd-kit --
|
|
117
|
-
thachvd-kit --
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
--yes
|
|
125
|
-
--
|
|
126
|
-
--
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
.
|
|
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
|
+
.agents/skills/ Selected Codex repo skills copied from .agent/skills
|
|
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 [
|
|
@@ -800,7 +839,7 @@ function scanProject() {
|
|
|
800
839
|
code_style: 'project-standard',
|
|
801
840
|
architecture_notes: '<!-- Add notes -->',
|
|
802
841
|
current_phase: 'Initial setup',
|
|
803
|
-
next_phase: '
|
|
842
|
+
next_phase: '-',
|
|
804
843
|
known_issues: '<!-- None yet -->'
|
|
805
844
|
};
|
|
806
845
|
}
|
|
@@ -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.
|
|
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,20 @@ 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
|
-
##
|
|
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 should discover native repo skills from \`.agents/skills/\`; Claude Code should discover project skills from \`.claude/skills/\`.
|
|
922
|
+
- If native skill discovery is unavailable or stale, fall back to the matching \`.agent/skills/<skill>/SKILL.md\` file.
|
|
923
|
+
- 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.
|
|
924
|
+
- Prefer explicit skill mentions for predictable behavior: \`$clean-code\`, \`$systematic-debugging\`, \`$webapp-testing\`, or any skill listed in \`.agent/docs/project.md\`.
|
|
925
|
+
- Run \`thachvd-kit --help\` to see common workflow and skill recommendations.
|
|
926
|
+
- When creating or improving skills, use \`writing-skills\` first and make the \`description\` field specific enough for implicit invocation.
|
|
927
|
+
|
|
928
|
+
## Rules
|
|
879
929
|
|
|
880
930
|
- Respond in the user's language; keep code, identifiers, and code comments in English.
|
|
881
931
|
- State assumptions when the request is ambiguous.
|
|
@@ -886,13 +936,16 @@ If any \`.agent/docs/*.md\` file still contains \`TODO: refine\`, update the doc
|
|
|
886
936
|
|
|
887
937
|
## Shared Knowledge
|
|
888
938
|
|
|
889
|
-
- Project docs: \`.agent/docs/\`
|
|
890
|
-
- Specialist agents: \`.agent/agents/\`
|
|
891
|
-
- Skills: \`.agent/skills/\`
|
|
892
|
-
-
|
|
893
|
-
-
|
|
894
|
-
|
|
895
|
-
|
|
939
|
+
- Project docs: \`.agent/docs/\`
|
|
940
|
+
- Specialist agents: \`.agent/agents/\`
|
|
941
|
+
- Skills: \`.agent/skills/\`
|
|
942
|
+
- Codex native skills: \`.agents/skills/\`
|
|
943
|
+
- Claude Code project skills: \`.claude/skills/\`
|
|
944
|
+
- Workflows: \`.agent/workflows/\`
|
|
945
|
+
- Tooling setup: \`.agent/docs/tooling.md\`
|
|
946
|
+
- Antigravity mirror rules: \`.agent/rules/GEMINI.md\`
|
|
947
|
+
`;
|
|
948
|
+
}
|
|
896
949
|
|
|
897
950
|
function generateSharedClaudeMd() {
|
|
898
951
|
return `@AGENTS.md
|
|
@@ -903,67 +956,67 @@ This repository uses AGENTS.md as the shared cross-agent entry file. Follow the
|
|
|
903
956
|
`;
|
|
904
957
|
}
|
|
905
958
|
|
|
906
|
-
function generateSharedGeminiMd() {
|
|
907
|
-
return `# GEMINI.md
|
|
908
|
-
|
|
909
|
-
Antigravity entry file for this project.
|
|
910
|
-
|
|
911
|
-
Read AGENTS.md first, then follow the shared docs under .agent/docs/.
|
|
912
|
-
`;
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
function resolveCommands(data) {
|
|
916
|
-
const frameworks = data.frameworks || [];
|
|
917
|
-
const primaryLang = data.primary_language || 'javascript';
|
|
918
|
-
const pkgManager = data.package_manager || 'npm';
|
|
919
|
-
const testFramework = data.test_framework || 'auto-detect';
|
|
920
|
-
|
|
921
|
-
const isPython = primaryLang === 'python';
|
|
922
|
-
const isGo = primaryLang === 'go';
|
|
923
|
-
const isRust = primaryLang === 'rust';
|
|
924
|
-
const isPhp = primaryLang === 'php';
|
|
925
|
-
|
|
926
|
-
let install = 'npm install';
|
|
927
|
-
let dev = 'npm run dev';
|
|
928
|
-
let build = 'npm run build';
|
|
929
|
-
let test = 'npm test';
|
|
930
|
-
let lint = 'npm run lint';
|
|
931
|
-
|
|
932
|
-
if (pkgManager === 'pnpm') { install = 'pnpm install'; dev = 'pnpm dev'; build = 'pnpm build'; test = 'pnpm test'; lint = 'pnpm lint'; }
|
|
933
|
-
else if (pkgManager === 'yarn') { install = 'yarn'; dev = 'yarn dev'; build = 'yarn build'; test = 'yarn test'; lint = 'yarn lint'; }
|
|
934
|
-
else if (pkgManager === 'bun') { install = 'bun install'; dev = 'bun dev'; build = 'bun run build'; test = 'bun test'; lint = 'bun run lint'; }
|
|
935
|
-
else if (isPython) { install = 'pip install -r requirements.txt'; dev = 'python manage.py runserver'; build = '# no build step'; test = 'pytest'; lint = 'ruff check .'; }
|
|
936
|
-
else if (isGo) { install = 'go mod download'; dev = 'go run .'; build = 'go build ./...'; test = 'go test ./...'; lint = 'golangci-lint run'; }
|
|
937
|
-
else if (isRust) { install = 'cargo build'; dev = 'cargo run'; build = 'cargo build --release'; test = 'cargo test'; lint = 'cargo clippy'; }
|
|
938
|
-
else if (isPhp) {
|
|
939
|
-
install = 'composer install';
|
|
940
|
-
build = '# no build step';
|
|
941
|
-
test = testFramework === 'pest' ? './vendor/bin/pest' : './vendor/bin/phpunit';
|
|
942
|
-
const isCakePHP = frameworks.some(f => f.toLowerCase().includes('cakephp'));
|
|
943
|
-
const isSymfony = frameworks.some(f => f.toLowerCase().includes('symfony'));
|
|
944
|
-
const isCodeIgniter = frameworks.some(f => f.toLowerCase().includes('codeigniter'));
|
|
945
|
-
if (isCakePHP) {
|
|
946
|
-
dev = 'bin/cake server';
|
|
947
|
-
lint = './vendor/bin/phpcs';
|
|
948
|
-
} else if (isSymfony) {
|
|
949
|
-
dev = 'bin/console server:run';
|
|
950
|
-
lint = './vendor/bin/phpcs';
|
|
951
|
-
} else if (isCodeIgniter) {
|
|
952
|
-
dev = 'php spark serve';
|
|
953
|
-
lint = './vendor/bin/phpcs';
|
|
954
|
-
} else {
|
|
955
|
-
dev = 'php artisan serve';
|
|
956
|
-
lint = 'php artisan pint';
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
if (testFramework === 'vitest') test = `${pkgManager === 'pnpm' ? 'pnpm' : pkgManager === 'yarn' ? 'yarn' : pkgManager === 'bun' ? 'bun run' : 'npx'} vitest`;
|
|
961
|
-
else if (testFramework === 'jest') test = `${pkgManager === 'pnpm' ? 'pnpm' : pkgManager === 'yarn' ? 'yarn' : 'npx'} jest`;
|
|
962
|
-
|
|
963
|
-
return { install, dev, build, test, lint };
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
function generateProjectDoc(data) {
|
|
959
|
+
function generateSharedGeminiMd() {
|
|
960
|
+
return `# GEMINI.md
|
|
961
|
+
|
|
962
|
+
Antigravity entry file for this project.
|
|
963
|
+
|
|
964
|
+
Read AGENTS.md first, then follow the shared docs under .agent/docs/.
|
|
965
|
+
`;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
function resolveCommands(data) {
|
|
969
|
+
const frameworks = data.frameworks || [];
|
|
970
|
+
const primaryLang = data.primary_language || 'javascript';
|
|
971
|
+
const pkgManager = data.package_manager || 'npm';
|
|
972
|
+
const testFramework = data.test_framework || 'auto-detect';
|
|
973
|
+
|
|
974
|
+
const isPython = primaryLang === 'python';
|
|
975
|
+
const isGo = primaryLang === 'go';
|
|
976
|
+
const isRust = primaryLang === 'rust';
|
|
977
|
+
const isPhp = primaryLang === 'php';
|
|
978
|
+
|
|
979
|
+
let install = 'npm install';
|
|
980
|
+
let dev = 'npm run dev';
|
|
981
|
+
let build = 'npm run build';
|
|
982
|
+
let test = 'npm test';
|
|
983
|
+
let lint = 'npm run lint';
|
|
984
|
+
|
|
985
|
+
if (pkgManager === 'pnpm') { install = 'pnpm install'; dev = 'pnpm dev'; build = 'pnpm build'; test = 'pnpm test'; lint = 'pnpm lint'; }
|
|
986
|
+
else if (pkgManager === 'yarn') { install = 'yarn'; dev = 'yarn dev'; build = 'yarn build'; test = 'yarn test'; lint = 'yarn lint'; }
|
|
987
|
+
else if (pkgManager === 'bun') { install = 'bun install'; dev = 'bun dev'; build = 'bun run build'; test = 'bun test'; lint = 'bun run lint'; }
|
|
988
|
+
else if (isPython) { install = 'pip install -r requirements.txt'; dev = 'python manage.py runserver'; build = '# no build step'; test = 'pytest'; lint = 'ruff check .'; }
|
|
989
|
+
else if (isGo) { install = 'go mod download'; dev = 'go run .'; build = 'go build ./...'; test = 'go test ./...'; lint = 'golangci-lint run'; }
|
|
990
|
+
else if (isRust) { install = 'cargo build'; dev = 'cargo run'; build = 'cargo build --release'; test = 'cargo test'; lint = 'cargo clippy'; }
|
|
991
|
+
else if (isPhp) {
|
|
992
|
+
install = 'composer install';
|
|
993
|
+
build = '# no build step';
|
|
994
|
+
test = testFramework === 'pest' ? './vendor/bin/pest' : './vendor/bin/phpunit';
|
|
995
|
+
const isCakePHP = frameworks.some(f => f.toLowerCase().includes('cakephp'));
|
|
996
|
+
const isSymfony = frameworks.some(f => f.toLowerCase().includes('symfony'));
|
|
997
|
+
const isCodeIgniter = frameworks.some(f => f.toLowerCase().includes('codeigniter'));
|
|
998
|
+
if (isCakePHP) {
|
|
999
|
+
dev = 'bin/cake server';
|
|
1000
|
+
lint = './vendor/bin/phpcs';
|
|
1001
|
+
} else if (isSymfony) {
|
|
1002
|
+
dev = 'bin/console server:run';
|
|
1003
|
+
lint = './vendor/bin/phpcs';
|
|
1004
|
+
} else if (isCodeIgniter) {
|
|
1005
|
+
dev = 'php spark serve';
|
|
1006
|
+
lint = './vendor/bin/phpcs';
|
|
1007
|
+
} else {
|
|
1008
|
+
dev = 'php artisan serve';
|
|
1009
|
+
lint = 'php artisan pint';
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
if (testFramework === 'vitest') test = `${pkgManager === 'pnpm' ? 'pnpm' : pkgManager === 'yarn' ? 'yarn' : pkgManager === 'bun' ? 'bun run' : 'npx'} vitest`;
|
|
1014
|
+
else if (testFramework === 'jest') test = `${pkgManager === 'pnpm' ? 'pnpm' : pkgManager === 'yarn' ? 'yarn' : 'npx'} jest`;
|
|
1015
|
+
|
|
1016
|
+
return { install, dev, build, test, lint };
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
function generateProjectDoc(data) {
|
|
967
1020
|
const commands = resolveCommands(data);
|
|
968
1021
|
const agentRows = resolveAgentTable(data).map(([task, agent, skills]) => `| ${task} | \`${agent}\` | ${skills} |`).join('\n');
|
|
969
1022
|
const skillsList = resolveSkills(data).map(s => `- ${s}`).join('\n');
|
|
@@ -1060,17 +1113,28 @@ function generateConventionsDoc(data) {
|
|
|
1060
1113
|
`;
|
|
1061
1114
|
}
|
|
1062
1115
|
|
|
1063
|
-
function generateWorkflowDoc() {
|
|
1116
|
+
function generateWorkflowDoc() {
|
|
1064
1117
|
return `# Agent Workflow
|
|
1065
1118
|
|
|
1066
1119
|
## Before Every Task
|
|
1067
1120
|
|
|
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
|
-
##
|
|
1121
|
+
1. Read AGENTS.md.
|
|
1122
|
+
2. Read .agent/docs/project.md.
|
|
1123
|
+
3. Classify the request: question, survey, simple fix, feature, refactor, debug, UI, security, or deploy.
|
|
1124
|
+
4. Read only the relevant docs under .agent/agents, .agent/skills, .agents/skills, .claude/skills, and .agent/workflows.
|
|
1125
|
+
|
|
1126
|
+
## Skill Selection
|
|
1127
|
+
|
|
1128
|
+
- Prefer native skill discovery when the client exposes it.
|
|
1129
|
+
- Codex repo skills live in .agents/skills.
|
|
1130
|
+
- Claude Code project skills live in .claude/skills.
|
|
1131
|
+
- Shared fallback skills live in .agent/skills.
|
|
1132
|
+
- Do not load skill bodies by default.
|
|
1133
|
+
- 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.
|
|
1134
|
+
- Use explicit skill names in prompts for predictable behavior, for example $webapp-testing or $clean-code.
|
|
1135
|
+
- Run thachvd-kit --help to see common workflow and skill recommendations.
|
|
1136
|
+
|
|
1137
|
+
## Implementation Flow
|
|
1074
1138
|
|
|
1075
1139
|
1. State assumptions and success criteria when the task is not trivial.
|
|
1076
1140
|
2. Inspect dependent files before editing.
|
|
@@ -1085,12 +1149,159 @@ function generateWorkflowDoc() {
|
|
|
1085
1149
|
- Update .agent/docs/conventions.md when repeated project patterns become clear.
|
|
1086
1150
|
- Update .agent/docs/project.md when stack, scripts, app root, test tooling, or routing changes.
|
|
1087
1151
|
- Keep AGENTS.md concise. Put project-specific detail in .agent/docs.
|
|
1088
|
-
`;
|
|
1089
|
-
}
|
|
1152
|
+
`;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
function resolveToolingSetup(data) {
|
|
1156
|
+
const primaryLang = data.primary_language || 'javascript';
|
|
1157
|
+
const packageManager = data.package_manager || 'npm';
|
|
1158
|
+
const isPython = primaryLang === 'python';
|
|
1159
|
+
const runner = packageManager === 'pnpm'
|
|
1160
|
+
? 'pnpm dlx'
|
|
1161
|
+
: packageManager === 'yarn'
|
|
1162
|
+
? 'yarn dlx'
|
|
1163
|
+
: packageManager === 'bun'
|
|
1164
|
+
? 'bunx'
|
|
1165
|
+
: 'npx';
|
|
1166
|
+
const playwrightCommand = isPython
|
|
1167
|
+
? 'pip install playwright && playwright install chromium'
|
|
1168
|
+
: `${runner} playwright install`;
|
|
1169
|
+
|
|
1170
|
+
return { runner, playwrightCommand };
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
function generateToolingDoc(data) {
|
|
1174
|
+
const { runner, playwrightCommand } = resolveToolingSetup(data);
|
|
1175
|
+
|
|
1176
|
+
return `# Optional Tooling
|
|
1177
|
+
|
|
1178
|
+
This kit keeps tool setup explicit. Do not assume these tools are available until you verify them in the current environment.
|
|
1179
|
+
|
|
1180
|
+
## Playwright
|
|
1181
|
+
|
|
1182
|
+
Use Playwright for browser and UI verification when a task touches web behavior.
|
|
1183
|
+
|
|
1184
|
+
- Check availability: \`${runner} playwright --version\`
|
|
1185
|
+
- Install browsers: \`${playwrightCommand}\`
|
|
1186
|
+
- Codex MCP CLI setup: \`codex mcp add playwright -- npx -y @playwright/mcp\`
|
|
1187
|
+
- Auto setup: \`thachvd-kit\`
|
|
1188
|
+
- Kit helper: \`python .agent/skills/webapp-testing/scripts/playwright_runner.py <url> --screenshot\`
|
|
1189
|
+
|
|
1190
|
+
If the project already has Playwright configured, prefer the project's existing scripts.
|
|
1191
|
+
|
|
1192
|
+
Codex \`config.toml\` example:
|
|
1193
|
+
|
|
1194
|
+
\`\`\`toml
|
|
1195
|
+
[mcp_servers.context7]
|
|
1196
|
+
command = "npx"
|
|
1197
|
+
args = ["-y", "@upstash/context7-mcp"]
|
|
1198
|
+
startup_timeout_sec = 20
|
|
1199
|
+
tool_timeout_sec = 120
|
|
1200
|
+
|
|
1201
|
+
[mcp_servers.playwright]
|
|
1202
|
+
command = "npx"
|
|
1203
|
+
args = ["-y", "@playwright/mcp"]
|
|
1204
|
+
startup_timeout_sec = 20
|
|
1205
|
+
tool_timeout_sec = 120
|
|
1206
|
+
\`\`\`
|
|
1207
|
+
|
|
1208
|
+
## Codegraph
|
|
1209
|
+
|
|
1210
|
+
Use codegraph for codebase exploration when the environment exposes it. The index is local state and should not be committed.
|
|
1211
|
+
|
|
1212
|
+
- Check for an index: look for \`.codegraph/\`
|
|
1213
|
+
- Check CLI availability: \`codegraph --help\`
|
|
1214
|
+
- Install CLI if needed: \`npm install -g @colbymchenry/codegraph\`
|
|
1215
|
+
- Codex MCP CLI setup: \`codex mcp add codegraph -- codegraph serve --mcp\`
|
|
1216
|
+
- Auto setup: \`thachvd-kit\`
|
|
1217
|
+
- If your Codex environment provides the codegraph CLI, run its project indexing step from the repository root.
|
|
1218
|
+
- Keep \`.codegraph/\` ignored in git.
|
|
1219
|
+
|
|
1220
|
+
Codex \`config.toml\` example:
|
|
1221
|
+
|
|
1222
|
+
\`\`\`toml
|
|
1223
|
+
[mcp_servers.codegraph]
|
|
1224
|
+
command = "codegraph"
|
|
1225
|
+
args = ["serve", "--mcp"]
|
|
1226
|
+
startup_timeout_sec = 20
|
|
1227
|
+
tool_timeout_sec = 120
|
|
1228
|
+
\`\`\`
|
|
1229
|
+
|
|
1230
|
+
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.
|
|
1231
|
+
|
|
1232
|
+
Gemini CLI / Antigravity \`mcp_config.json\` example:
|
|
1233
|
+
|
|
1234
|
+
\`\`\`json
|
|
1235
|
+
{
|
|
1236
|
+
"mcpServers": {
|
|
1237
|
+
"codegraph": {
|
|
1238
|
+
"command": "codegraph",
|
|
1239
|
+
"args": ["serve", "--mcp"]
|
|
1240
|
+
},
|
|
1241
|
+
"context7": {
|
|
1242
|
+
"command": "npx",
|
|
1243
|
+
"args": ["-y", "@upstash/context7-mcp"]
|
|
1244
|
+
},
|
|
1245
|
+
"playwright": {
|
|
1246
|
+
"command": "npx",
|
|
1247
|
+
"args": ["-y", "@playwright/mcp"]
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
\`\`\`
|
|
1252
|
+
|
|
1253
|
+
Common local paths:
|
|
1254
|
+
|
|
1255
|
+
- Gemini CLI: \`~/.gemini/config/mcp_config.json\`
|
|
1256
|
+
- Antigravity IDE: \`~/.gemini/antigravity-ide/mcp_config.json\`
|
|
1257
|
+
- Older Antigravity setups may use \`~/.gemini/antigravity/mcp_config.json\`
|
|
1258
|
+
|
|
1259
|
+
Claude Code user config example:
|
|
1260
|
+
|
|
1261
|
+
\`\`\`json
|
|
1262
|
+
{
|
|
1263
|
+
"mcpServers": {
|
|
1264
|
+
"codegraph": {
|
|
1265
|
+
"type": "stdio",
|
|
1266
|
+
"command": "codegraph",
|
|
1267
|
+
"args": ["serve", "--mcp"]
|
|
1268
|
+
},
|
|
1269
|
+
"playwright": {
|
|
1270
|
+
"type": "stdio",
|
|
1271
|
+
"command": "npx",
|
|
1272
|
+
"args": ["-y", "@playwright/mcp"]
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
\`\`\`
|
|
1277
|
+
|
|
1278
|
+
Common local path:
|
|
1279
|
+
|
|
1280
|
+
- Claude Code: \`~/.claude.json\`
|
|
1281
|
+
|
|
1282
|
+
## Native Skill Folders
|
|
1283
|
+
|
|
1284
|
+
The shared kit skills live in \`.agent/skills/\`. Antigravity can use that folder directly, but Codex expects repo skills under \`.agents/skills/\` or user skills under \`~/.agents/skills/\`. Claude Code may require its own native skill location depending on the installed client.
|
|
1285
|
+
|
|
1286
|
+
- Codex repo skills: \`.agents/skills/\`
|
|
1287
|
+
- Codex user skills: \`~/.agents/skills/\`
|
|
1288
|
+
- Codex may also keep installed/system skills under \`$CODEX_HOME/skills\` such as \`~/.codex/skills\`; treat that as local Codex state, not a repo folder to generate or commit.
|
|
1289
|
+
- Claude Code project skills commonly live under \`.claude/skills/\`.
|
|
1290
|
+
- \`thachvd-kit\` copies selected native skills to \`.agents/skills/\` and \`.claude/skills/\` based on the detected stack.
|
|
1291
|
+
- Keep \`.agent/skills/\` as the full shared source of truth committed with the repo.
|
|
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
|
|
|
@@ -1100,16 +1311,16 @@ function copyAgentFolder() {
|
|
|
1100
1311
|
|
|
1101
1312
|
function copyDir(src, dest) {
|
|
1102
1313
|
fs.mkdirSync(dest, { recursive: true });
|
|
1103
|
-
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
1104
|
-
const srcPath = path.join(src, entry.name);
|
|
1105
|
-
const destPath = path.join(dest, entry.name);
|
|
1106
|
-
const relativeSrc = path.relative(srcAgent, srcPath).split(path.sep).join('/');
|
|
1107
|
-
if (relativeSrc === 'docs' || relativeSrc.startsWith('docs/')) {
|
|
1108
|
-
continue;
|
|
1109
|
-
}
|
|
1110
|
-
if (entry.isDirectory()) {
|
|
1111
|
-
copyDir(srcPath, destPath);
|
|
1112
|
-
} else {
|
|
1314
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
1315
|
+
const srcPath = path.join(src, entry.name);
|
|
1316
|
+
const destPath = path.join(dest, entry.name);
|
|
1317
|
+
const relativeSrc = path.relative(srcAgent, srcPath).split(path.sep).join('/');
|
|
1318
|
+
if (relativeSrc === 'docs' || relativeSrc.startsWith('docs/')) {
|
|
1319
|
+
continue;
|
|
1320
|
+
}
|
|
1321
|
+
if (entry.isDirectory()) {
|
|
1322
|
+
copyDir(srcPath, destPath);
|
|
1323
|
+
} else {
|
|
1113
1324
|
if (fs.existsSync(destPath)) {
|
|
1114
1325
|
skipped++;
|
|
1115
1326
|
} else {
|
|
@@ -1120,31 +1331,193 @@ function copyAgentFolder() {
|
|
|
1120
1331
|
}
|
|
1121
1332
|
}
|
|
1122
1333
|
|
|
1123
|
-
copyDir(srcAgent, destAgent);
|
|
1124
|
-
return { copied, skipped };
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
const
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
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
|
+
const destinations = [
|
|
1344
|
+
path.join(targetDir, '.agents', 'skills'),
|
|
1345
|
+
path.join(targetDir, '.claude', 'skills')
|
|
1346
|
+
];
|
|
1347
|
+
let copied = 0;
|
|
1348
|
+
let skipped = 0;
|
|
1349
|
+
let missing = 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
|
+
if (entry.isDirectory()) {
|
|
1357
|
+
copyDir(srcPath, destPath);
|
|
1358
|
+
} else if (fs.existsSync(destPath) && !assumeYes) {
|
|
1359
|
+
skipped++;
|
|
1360
|
+
} else {
|
|
1361
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
1362
|
+
fs.copyFileSync(srcPath, destPath);
|
|
1363
|
+
copied++;
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
for (const skill of selectedSkills) {
|
|
1369
|
+
const srcSkill = path.join(srcSkills, skill);
|
|
1370
|
+
if (!fs.existsSync(srcSkill)) {
|
|
1371
|
+
missing++;
|
|
1372
|
+
continue;
|
|
1373
|
+
}
|
|
1374
|
+
for (const destSkills of destinations) {
|
|
1375
|
+
copyDir(srcSkill, path.join(destSkills, skill));
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
return { copied, skipped, missing, skills: selectedSkills };
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
async function writeGeneratedFile(relativePath, content, assumeYes) {
|
|
1383
|
+
const outputPath = path.join(targetDir, relativePath);
|
|
1384
|
+
const exists = fs.existsSync(outputPath);
|
|
1385
|
+
if (exists && !assumeYes) return false;
|
|
1386
|
+
|
|
1387
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
1388
|
+
fs.writeFileSync(outputPath, content, 'utf8');
|
|
1389
|
+
return true;
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
function homePath(...parts) {
|
|
1393
|
+
const home = process.env.USERPROFILE || process.env.HOME;
|
|
1394
|
+
return home ? path.join(home, ...parts) : null;
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
function commandExists(command) {
|
|
1398
|
+
const checker = process.platform === 'win32' ? 'where' : 'command';
|
|
1399
|
+
const args = process.platform === 'win32' ? [command] : ['-v', command];
|
|
1400
|
+
const result = spawnSync(checker, args, { encoding: 'utf8', shell: process.platform !== 'win32' });
|
|
1401
|
+
return result.status === 0;
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
function ensureCodegraphCli() {
|
|
1405
|
+
if (commandExists('codegraph')) {
|
|
1406
|
+
return { ok: true, message: 'codegraph CLI already installed' };
|
|
1407
|
+
}
|
|
1408
|
+
if (!commandExists('npm')) {
|
|
1409
|
+
return { ok: false, message: 'npm not found; run: npm install -g @colbymchenry/codegraph' };
|
|
1410
|
+
}
|
|
1411
|
+
const result = spawnSync('npm', ['install', '-g', '@colbymchenry/codegraph'], {
|
|
1412
|
+
encoding: 'utf8',
|
|
1413
|
+
stdio: 'pipe'
|
|
1414
|
+
});
|
|
1415
|
+
if (result.status !== 0) {
|
|
1416
|
+
return { ok: false, message: 'failed to install codegraph; run: npm install -g @colbymchenry/codegraph' };
|
|
1417
|
+
}
|
|
1418
|
+
return { ok: true, message: 'installed codegraph CLI' };
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
function readJsonFile(filePath) {
|
|
1422
|
+
if (!fs.existsSync(filePath)) return {};
|
|
1423
|
+
try {
|
|
1424
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
1425
|
+
} catch {
|
|
1426
|
+
return null;
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
function writeJsonFile(filePath, data) {
|
|
1431
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
1432
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
function mergeMcpJson(filePath, withType = false, includeContext7 = false) {
|
|
1436
|
+
const data = readJsonFile(filePath);
|
|
1437
|
+
if (data === null) {
|
|
1438
|
+
return { ok: false, message: `${filePath} is not valid JSON; skipped` };
|
|
1439
|
+
}
|
|
1440
|
+
data.mcpServers = data.mcpServers || {};
|
|
1441
|
+
const baseServers = {
|
|
1442
|
+
codegraph: { command: 'codegraph', args: ['serve', '--mcp'] },
|
|
1443
|
+
playwright: { command: 'npx', args: ['-y', '@playwright/mcp'] }
|
|
1444
|
+
};
|
|
1445
|
+
if (includeContext7) {
|
|
1446
|
+
baseServers.context7 = { command: 'npx', args: ['-y', '@upstash/context7-mcp'] };
|
|
1447
|
+
}
|
|
1448
|
+
for (const [name, server] of Object.entries(baseServers)) {
|
|
1449
|
+
if (!data.mcpServers[name]) {
|
|
1450
|
+
data.mcpServers[name] = withType ? { type: 'stdio', ...server } : server;
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
writeJsonFile(filePath, data);
|
|
1454
|
+
const tools = Object.keys(baseServers).join(', ');
|
|
1455
|
+
return { ok: true, message: `configured ${tools} MCP in ${filePath}` };
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
function appendMcpTomlIfMissing(filePath) {
|
|
1459
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
1460
|
+
const existing = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : '';
|
|
1461
|
+
const blocks = [];
|
|
1462
|
+
if (!/\[mcp_servers\.context7\]/.test(existing)) {
|
|
1463
|
+
blocks.push(`[mcp_servers.context7]
|
|
1464
|
+
command = "npx"
|
|
1465
|
+
args = ["-y", "@upstash/context7-mcp"]
|
|
1466
|
+
startup_timeout_sec = 20
|
|
1467
|
+
tool_timeout_sec = 120
|
|
1468
|
+
`);
|
|
1469
|
+
}
|
|
1470
|
+
if (!/\[mcp_servers\.codegraph\]/.test(existing)) {
|
|
1471
|
+
blocks.push(`[mcp_servers.codegraph]
|
|
1472
|
+
command = "codegraph"
|
|
1473
|
+
args = ["serve", "--mcp"]
|
|
1474
|
+
startup_timeout_sec = 20
|
|
1475
|
+
tool_timeout_sec = 120
|
|
1476
|
+
`);
|
|
1477
|
+
}
|
|
1478
|
+
if (!/\[mcp_servers\.playwright\]/.test(existing)) {
|
|
1479
|
+
blocks.push(`[mcp_servers.playwright]
|
|
1480
|
+
command = "npx"
|
|
1481
|
+
args = ["-y", "@playwright/mcp"]
|
|
1482
|
+
startup_timeout_sec = 20
|
|
1483
|
+
tool_timeout_sec = 120
|
|
1484
|
+
`);
|
|
1485
|
+
}
|
|
1486
|
+
if (blocks.length === 0) {
|
|
1487
|
+
return { ok: true, message: `already configured ${filePath}` };
|
|
1488
|
+
}
|
|
1489
|
+
const separator = existing && !existing.endsWith('\n') ? '\n\n' : existing ? '\n' : '';
|
|
1490
|
+
fs.writeFileSync(filePath, existing + separator + blocks.join('\n'), 'utf8');
|
|
1491
|
+
return { ok: true, message: `configured context7, codegraph, playwright MCP in ${filePath}` };
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
function setupMcpServers() {
|
|
1495
|
+
const results = [];
|
|
1496
|
+
results.push(ensureCodegraphCli());
|
|
1497
|
+
|
|
1498
|
+
const codexConfig = homePath('.codex', 'config.toml');
|
|
1499
|
+
if (codexConfig) results.push(appendMcpTomlIfMissing(codexConfig));
|
|
1500
|
+
|
|
1501
|
+
const geminiConfig = homePath('.gemini', 'config', 'mcp_config.json');
|
|
1502
|
+
if (geminiConfig) results.push(mergeMcpJson(geminiConfig, false, true));
|
|
1503
|
+
|
|
1504
|
+
const antigravityIdeConfig = homePath('.gemini', 'antigravity-ide', 'mcp_config.json');
|
|
1505
|
+
if (antigravityIdeConfig) results.push(mergeMcpJson(antigravityIdeConfig, false, true));
|
|
1506
|
+
|
|
1507
|
+
const antigravityConfig = homePath('.gemini', 'antigravity', 'mcp_config.json');
|
|
1508
|
+
if (antigravityConfig && fs.existsSync(path.dirname(antigravityConfig))) {
|
|
1509
|
+
results.push(mergeMcpJson(antigravityConfig, false, true));
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
const claudeConfig = homePath('.claude.json');
|
|
1513
|
+
if (claudeConfig) results.push(mergeMcpJson(claudeConfig, true));
|
|
1514
|
+
|
|
1515
|
+
return results;
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
// --- Main CLI ---
|
|
1146
1519
|
|
|
1147
|
-
async function main() {
|
|
1520
|
+
async function main() {
|
|
1148
1521
|
const rawArgs = process.argv.slice(2);
|
|
1149
1522
|
const command = rawArgs[0] && !rawArgs[0].startsWith('-') ? rawArgs[0] : 'init';
|
|
1150
1523
|
const args = command === 'init' ? rawArgs.slice(rawArgs[0] === 'init' ? 1 : 0) : rawArgs.slice(1);
|
|
@@ -1165,137 +1538,42 @@ async function main() {
|
|
|
1165
1538
|
process.exit(1);
|
|
1166
1539
|
}
|
|
1167
1540
|
|
|
1168
|
-
|
|
1169
|
-
const
|
|
1170
|
-
|
|
1171
|
-
console.log(pc.bold(pc.cyan('
|
|
1172
|
-
console.log(' ' + pc.bold('thachvd-kit') + '
|
|
1173
|
-
console.log(
|
|
1174
|
-
console.log(pc.bold(pc.cyan('
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
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
|
-
};
|
|
1541
|
+
const assumeYes = args.includes('--yes') || args.includes('-y');
|
|
1542
|
+
const skipMcpSetup = args.includes('--no-setup-mcp') || process.env.THACHVD_KIT_SKIP_MCP_SETUP === '1';
|
|
1543
|
+
|
|
1544
|
+
console.log(pc.bold(pc.cyan('='.repeat(50))));
|
|
1545
|
+
console.log(' ' + pc.bold('thachvd-kit') + ' - AI Project Bootstrap');
|
|
1546
|
+
console.log(' Generates shared agent rules and configures MCP tooling');
|
|
1547
|
+
console.log(pc.bold(pc.cyan('='.repeat(50))));
|
|
1548
|
+
|
|
1549
|
+
console.log(pc.dim(' Scanning current project files for a baseline...\n'));
|
|
1550
|
+
const scanned = scanProject();
|
|
1551
|
+
|
|
1552
|
+
console.log(` ${pc.bold('Detected context:')}`);
|
|
1553
|
+
formatScanPreviewLines(scanned).forEach(([label, value]) => {
|
|
1554
|
+
console.log(` - ${label}: ${value}`);
|
|
1555
|
+
});
|
|
1556
|
+
console.log('');
|
|
1557
|
+
if ((scanned.scan_evidence || []).length > 0) {
|
|
1558
|
+
console.log(` ${pc.bold('Evidence:')}`);
|
|
1559
|
+
scanned.scan_evidence.forEach(line => console.log(` - ${line}`));
|
|
1560
|
+
console.log('');
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
// Combine data
|
|
1564
|
+
const data = {
|
|
1565
|
+
project_name: scanned?.project_name || path.basename(targetDir),
|
|
1566
|
+
description: scanned?.description || 'A software project',
|
|
1567
|
+
project_type: scanned?.project_type || 'web-app',
|
|
1568
|
+
primary_language: scanned?.primary_language || 'typescript',
|
|
1569
|
+
frameworks: scanned?.frameworks || [],
|
|
1570
|
+
database: scanned?.database || 'none',
|
|
1571
|
+
uses_docker: scanned?.uses_docker ?? true,
|
|
1572
|
+
cloud: scanned?.cloud || 'none',
|
|
1573
|
+
package_manager: scanned?.package_manager || 'auto-detect',
|
|
1574
|
+
test_framework: scanned?.test_framework || 'auto-detect',
|
|
1575
|
+
max_file_lines: scanned?.max_file_lines || '300'
|
|
1576
|
+
};
|
|
1299
1577
|
|
|
1300
1578
|
data.database = data.database || scanned?.database || 'none';
|
|
1301
1579
|
data.uses_docker = typeof data.uses_docker === 'boolean' ? data.uses_docker : (scanned?.uses_docker ?? true);
|
|
@@ -1307,40 +1585,58 @@ async function main() {
|
|
|
1307
1585
|
data.scan_evidence = scanned?.scan_evidence || [];
|
|
1308
1586
|
data.app_root = scanned?.app_root || '.';
|
|
1309
1587
|
|
|
1310
|
-
data.infrastructure = [];
|
|
1311
|
-
if (data.uses_docker) data.infrastructure.push('docker');
|
|
1312
|
-
if (data.cloud !== 'none') data.infrastructure.push(data.cloud);
|
|
1313
|
-
|
|
1314
|
-
console.log(`\n${pc.bold(pc.cyan('Generating Files'))}`);
|
|
1315
|
-
|
|
1316
|
-
// Shared entry files are written after the .agent folder is available.
|
|
1588
|
+
data.infrastructure = [];
|
|
1589
|
+
if (data.uses_docker) data.infrastructure.push('docker');
|
|
1590
|
+
if (data.cloud !== 'none') data.infrastructure.push(data.cloud);
|
|
1317
1591
|
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
console.log(` ${pc.green('OK')} Copied ${pc.bold('.agent/')} (${copied} file${copied !== 1 ? 's' : ''} added${skipped > 0 ? `, ${skipped} skipped` : ''})`);
|
|
1322
|
-
} else if (skipped > 0) {
|
|
1323
|
-
console.log(` ${pc.yellow('~')} ${pc.bold('.agent/')} already exists - ${skipped} file${skipped !== 1 ? 's' : ''} skipped (no overwrite)`);
|
|
1324
|
-
}
|
|
1592
|
+
const shouldSetupMcp = !skipMcpSetup;
|
|
1593
|
+
|
|
1594
|
+
console.log(`\n${pc.bold(pc.cyan('Generating Files'))}`);
|
|
1325
1595
|
|
|
1596
|
+
// Shared entry files are written after the .agent folder is available.
|
|
1326
1597
|
|
|
1327
|
-
|
|
1598
|
+
// Copy .agent folder
|
|
1599
|
+
const { copied, skipped } = copyAgentFolder();
|
|
1600
|
+
if (copied > 0) {
|
|
1601
|
+
console.log(` ${pc.green('OK')} Copied ${pc.bold('.agent/')} (${copied} file${copied !== 1 ? 's' : ''} added${skipped > 0 ? `, ${skipped} skipped` : ''})`);
|
|
1602
|
+
} else if (skipped > 0) {
|
|
1603
|
+
console.log(` ${pc.yellow('~')} ${pc.bold('.agent/')} already exists - ${skipped} file${skipped !== 1 ? 's' : ''} skipped (no overwrite)`);
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
const nativeSkills = copySelectedSkillFolders(data, assumeYes);
|
|
1607
|
+
if (nativeSkills.copied > 0) {
|
|
1608
|
+
console.log(` ${pc.green('OK')} Copied native skills to ${pc.bold('.agents/skills/')} and ${pc.bold('.claude/skills/')} (${nativeSkills.skills.join(', ')})`);
|
|
1609
|
+
} else if (nativeSkills.skipped > 0) {
|
|
1610
|
+
console.log(` ${pc.yellow('~')} Native skills already exist - ${nativeSkills.skipped} file${nativeSkills.skipped !== 1 ? 's' : ''} skipped`);
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
|
|
1614
|
+
const generatedFiles = [
|
|
1328
1615
|
['AGENTS.md', generateSharedAgentsMd(data)],
|
|
1329
1616
|
['CLAUDE.md', generateSharedClaudeMd()],
|
|
1330
1617
|
['GEMINI.md', generateSharedGeminiMd()],
|
|
1331
1618
|
[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
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1619
|
+
[path.join('.agent', 'docs', 'architecture.md'), generateArchitectureDoc(data)],
|
|
1620
|
+
[path.join('.agent', 'docs', 'conventions.md'), generateConventionsDoc(data)],
|
|
1621
|
+
[path.join('.agent', 'docs', 'workflow.md'), generateWorkflowDoc()],
|
|
1622
|
+
[path.join('.agent', 'docs', 'tooling.md'), generateToolingDoc(data)]
|
|
1623
|
+
];
|
|
1624
|
+
|
|
1625
|
+
for (const [relativePath, content] of generatedFiles) {
|
|
1626
|
+
const wrote = await writeGeneratedFile(relativePath, content, assumeYes);
|
|
1627
|
+
if (wrote) console.log(` ${pc.green('OK')} Generated ${pc.bold(relativePath)}`);
|
|
1628
|
+
else console.log(` ${pc.yellow('~')} Skipped ${pc.bold(relativePath)}`);
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
if (shouldSetupMcp) {
|
|
1632
|
+
console.log(`\n${pc.bold(pc.cyan('Configuring MCP'))}`);
|
|
1633
|
+
for (const result of setupMcpServers()) {
|
|
1634
|
+
const marker = result.ok ? pc.green('OK') : pc.yellow('~');
|
|
1635
|
+
console.log(` ${marker} ${result.message}`);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
console.log(`\n${pc.bold(pc.green('Done!'))}
|
|
1344
1640
|
|
|
1345
1641
|
${pc.bold('Project:')} ${data.project_name}
|
|
1346
1642
|
${pc.bold('Stack:')} ${data.primary_language} | ${(data.frameworks || []).join(', ') || 'no framework'}
|
|
@@ -1348,25 +1644,35 @@ async function main() {
|
|
|
1348
1644
|
${pc.bold('Generated:')}
|
|
1349
1645
|
- ${pc.bold('AGENTS.md')} shared entry for Codex, Antigravity, and Claude Code
|
|
1350
1646
|
- ${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
|
-
|
|
1647
|
+
- ${pc.bold('GEMINI.md')} Antigravity entry
|
|
1648
|
+
- ${pc.bold('.agent/docs/')} scan-based project rules
|
|
1649
|
+
- ${pc.bold('.agent/')} skills, workflows, agents, and rules
|
|
1650
|
+
- ${pc.bold('.agents/skills/')} selected Codex native skills
|
|
1651
|
+
- ${pc.bold('.claude/skills/')} selected Claude Code native skills
|
|
1652
|
+
|
|
1653
|
+
${pc.bold('MCP/tooling setup:')}
|
|
1654
|
+
- Codegraph CLI checked or installed; MCP configured for Codex, Gemini/Antigravity, and Claude Code
|
|
1655
|
+
- Context7 MCP configured for Codex and Gemini/Antigravity
|
|
1656
|
+
- Playwright MCP configured for Codex, Gemini/Antigravity, and Claude Code
|
|
1657
|
+
- Browser install hint: ${pc.bold(resolveToolingSetup(data).playwrightCommand)}
|
|
1658
|
+
- Codegraph index hint: run ${pc.bold('codegraph init -i')} when a project index is missing, then keep ${pc.bold('.codegraph/')} uncommitted
|
|
1659
|
+
|
|
1355
1660
|
${pc.bold('Next steps:')}
|
|
1356
|
-
1.
|
|
1661
|
+
1. Copy the prompt below into your AI editor and describe what the project does
|
|
1357
1662
|
2. Commit ${pc.bold('AGENTS.md')}, ${pc.bold('CLAUDE.md')}, ${pc.bold('GEMINI.md')}, and ${pc.bold('.agent/')}
|
|
1358
1663
|
3. Open the project in Codex, Antigravity, or Claude Code
|
|
1359
1664
|
|
|
1360
1665
|
${pc.bold('Copy this prompt into your AI editor:')}
|
|
1361
1666
|
${pc.dim('---')}
|
|
1362
1667
|
Read AGENTS.md and all files under .agent/docs/.
|
|
1363
|
-
|
|
1364
|
-
|
|
1668
|
+
Ask me what this project does and any important conventions I want preserved.
|
|
1669
|
+
Then scan this repository.
|
|
1670
|
+
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.
|
|
1365
1671
|
Do not implement product code.
|
|
1366
1672
|
Remove TODO: refine only when backed by evidence from the codebase.
|
|
1367
1673
|
${pc.dim('---')}
|
|
1368
|
-
`);
|
|
1369
|
-
}
|
|
1674
|
+
`);
|
|
1675
|
+
}
|
|
1370
1676
|
|
|
1371
1677
|
main().catch(err => {
|
|
1372
1678
|
console.error(pc.red('\nError during init:'), err);
|