thachvd-kit 1.0.6 → 1.0.8
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/README.md +1 -0
- package/bin/cli.js +152 -27
- package/package.json +1 -1
- package/rules/GEMINI.md +13 -10
- package/scripts/init.py +130 -23
package/README.md
CHANGED
|
@@ -18,6 +18,7 @@ npx github:thachvd/thachvd-kit init
|
|
|
18
18
|
The interactive wizard will first ask whether this is a new project or an existing one.
|
|
19
19
|
If it is an existing project, it can scan the current codebase to prefill `PROJECT_CONTEXT.md`, or let you enter everything manually.
|
|
20
20
|
The scan mode uses evidence-based detectors and shows why a framework was inferred, so you can quickly confirm or correct it.
|
|
21
|
+
After init, you can also generate an AI refinement prompt to let your editor/agent deepen the project context.
|
|
21
22
|
|
|
22
23
|
```bash
|
|
23
24
|
thachvd-kit init --scan
|
package/bin/cli.js
CHANGED
|
@@ -126,6 +126,20 @@ ${pc.bold('Options:')}
|
|
|
126
126
|
`);
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
function formatScanPreviewLines(scanned) {
|
|
130
|
+
return [
|
|
131
|
+
['Project type', scanned.project_type || 'unknown'],
|
|
132
|
+
['Primary language', scanned.primary_language || 'unknown'],
|
|
133
|
+
['Frameworks', (scanned.frameworks || []).join(', ') || 'no framework'],
|
|
134
|
+
['Database', scanned.database || 'none'],
|
|
135
|
+
['Package manager', scanned.package_manager || 'auto-detect'],
|
|
136
|
+
['Test framework', scanned.test_framework || 'auto-detect'],
|
|
137
|
+
['Cloud', scanned.cloud || 'none'],
|
|
138
|
+
['Infrastructure', (scanned.infrastructure || []).join(', ') || 'none'],
|
|
139
|
+
['CI/CD', scanned.ci_cd || 'none']
|
|
140
|
+
];
|
|
141
|
+
}
|
|
142
|
+
|
|
129
143
|
async function askQuestions(questions) {
|
|
130
144
|
return prompts(questions, {
|
|
131
145
|
onCancel: () => {
|
|
@@ -365,10 +379,11 @@ const FRAMEWORK_DETECTORS = [
|
|
|
365
379
|
id: 'django',
|
|
366
380
|
kind: 'backend',
|
|
367
381
|
language: 'python',
|
|
368
|
-
projectType: '
|
|
382
|
+
projectType: 'web-app',
|
|
369
383
|
detect: ctx => buildDetection('django', [
|
|
370
384
|
evidenceIf(textMatches(ctx.requirementsText, /\bdjango\b/i) || textMatches(ctx.pyprojectText, /\bdjango\b/i), 'Python deps -> django', 4),
|
|
371
|
-
evidenceIf(ctx.fileExists('manage.py'), 'found manage.py', 2)
|
|
385
|
+
evidenceIf(ctx.fileExists('manage.py'), 'found manage.py', 2),
|
|
386
|
+
evidenceIf(ctx.fileExists('templates'), 'found templates/', 1)
|
|
372
387
|
])
|
|
373
388
|
},
|
|
374
389
|
{
|
|
@@ -384,17 +399,18 @@ const FRAMEWORK_DETECTORS = [
|
|
|
384
399
|
id: 'laravel',
|
|
385
400
|
kind: 'backend',
|
|
386
401
|
language: 'php',
|
|
387
|
-
projectType: '
|
|
402
|
+
projectType: 'web-app',
|
|
388
403
|
detect: ctx => buildDetection('laravel', [
|
|
389
404
|
evidenceIf(hasComposerDependency(ctx.composerDepNames, 'laravel/framework'), 'composer.json -> laravel/framework', 4),
|
|
390
|
-
evidenceIf(ctx.fileExists('artisan'), 'found artisan', 3)
|
|
405
|
+
evidenceIf(ctx.fileExists('artisan'), 'found artisan', 3),
|
|
406
|
+
evidenceIf(ctx.fileExists('resources', 'views'), 'found resources/views', 1)
|
|
391
407
|
])
|
|
392
408
|
},
|
|
393
409
|
{
|
|
394
410
|
id: 'cakephp',
|
|
395
411
|
kind: 'backend',
|
|
396
412
|
language: 'php',
|
|
397
|
-
projectType: '
|
|
413
|
+
projectType: 'web-app',
|
|
398
414
|
detect: ctx => buildDetection('cakephp', [
|
|
399
415
|
evidenceIf(hasComposerDependency(ctx.composerDepNames, 'cakephp/cakephp'), 'composer.json -> cakephp/cakephp', 4),
|
|
400
416
|
evidenceIf(ctx.fileExists('bin', 'cake'), 'found bin/cake', 3),
|
|
@@ -403,18 +419,21 @@ const FRAMEWORK_DETECTORS = [
|
|
|
403
419
|
evidenceIf(ctx.fileExists('app', 'Controller', 'AppController.php'), 'found app/Controller/AppController.php', 2),
|
|
404
420
|
evidenceIf(ctx.fileExists('app', 'Model', 'AppModel.php'), 'found app/Model/AppModel.php', 2),
|
|
405
421
|
evidenceIf(ctx.fileExists('lib', 'Cake'), 'found lib/Cake', 3),
|
|
406
|
-
evidenceIf(ctx.fileExists('cake', 'console', 'cake'), 'found cake/console/cake', 3)
|
|
422
|
+
evidenceIf(ctx.fileExists('cake', 'console', 'cake'), 'found cake/console/cake', 3),
|
|
423
|
+
evidenceIf(ctx.fileExists('app', 'View'), 'found app/View', 2),
|
|
424
|
+
evidenceIf(textMatches(ctx.viewFilesText, /\.ctp$/im), 'found .ctp view templates', 2)
|
|
407
425
|
])
|
|
408
426
|
},
|
|
409
427
|
{
|
|
410
428
|
id: 'symfony',
|
|
411
429
|
kind: 'backend',
|
|
412
430
|
language: 'php',
|
|
413
|
-
projectType: '
|
|
431
|
+
projectType: 'web-app',
|
|
414
432
|
detect: ctx => buildDetection('symfony', [
|
|
415
433
|
evidenceIf(hasComposerDependency(ctx.composerDepNames, 'symfony/framework-bundle'), 'composer.json -> symfony/framework-bundle', 4),
|
|
416
434
|
evidenceIf(ctx.fileExists('bin', 'console'), 'found bin/console', 2),
|
|
417
|
-
evidenceIf(ctx.fileExists('config', 'bundles.php'), 'found config/bundles.php', 1)
|
|
435
|
+
evidenceIf(ctx.fileExists('config', 'bundles.php'), 'found config/bundles.php', 1),
|
|
436
|
+
evidenceIf(ctx.fileExists('templates'), 'found templates/', 1)
|
|
418
437
|
])
|
|
419
438
|
},
|
|
420
439
|
{
|
|
@@ -459,10 +478,11 @@ const FRAMEWORK_DETECTORS = [
|
|
|
459
478
|
id: 'rails',
|
|
460
479
|
kind: 'backend',
|
|
461
480
|
language: 'ruby',
|
|
462
|
-
projectType: '
|
|
481
|
+
projectType: 'web-app',
|
|
463
482
|
detect: ctx => buildDetection('rails', [
|
|
464
483
|
evidenceIf(textMatches(ctx.gemfileText, /^\s*gem\s+["']rails["']/im), 'Gemfile -> rails', 4),
|
|
465
|
-
evidenceIf(ctx.fileExists('bin', 'rails'), 'found bin/rails', 2)
|
|
484
|
+
evidenceIf(ctx.fileExists('bin', 'rails'), 'found bin/rails', 2),
|
|
485
|
+
evidenceIf(ctx.fileExists('app', 'views'), 'found app/views', 1)
|
|
466
486
|
])
|
|
467
487
|
},
|
|
468
488
|
{
|
|
@@ -563,7 +583,10 @@ function scanProject() {
|
|
|
563
583
|
gradleText: `${readTextIfExists('build.gradle')}\n${readTextIfExists('build.gradle.kts')}`,
|
|
564
584
|
cargoText: `${readTextIfExists('Cargo.toml')}\n${readTextIfExists('src-tauri', 'Cargo.toml')}`,
|
|
565
585
|
csprojText: rootCsproj ? readTextIfExists(rootCsproj) : '',
|
|
566
|
-
programCsText: readTextIfExists('Program.cs')
|
|
586
|
+
programCsText: readTextIfExists('Program.cs'),
|
|
587
|
+
viewFilesText: fs.existsSync(path.join(targetDir, 'app', 'View'))
|
|
588
|
+
? fs.readdirSync(path.join(targetDir, 'app', 'View'), { recursive: true }).join('\n')
|
|
589
|
+
: ''
|
|
567
590
|
};
|
|
568
591
|
|
|
569
592
|
const languageDetections = LANGUAGE_DETECTORS
|
|
@@ -605,6 +628,7 @@ function scanProject() {
|
|
|
605
628
|
if (frameworkDetails.some(item => item.projectType === 'desktop')) projectType = 'desktop';
|
|
606
629
|
else if (frameworkDetails.some(item => item.projectType === 'mobile')) projectType = 'mobile';
|
|
607
630
|
else if (frameworkDetails.some(item => item.kind === 'frontend') && frameworkDetails.some(item => item.kind === 'backend')) projectType = 'fullstack';
|
|
631
|
+
else if (frameworkDetails.some(item => item.projectType === 'web-app')) projectType = 'web-app';
|
|
608
632
|
else if (frameworkDetails.some(item => item.kind === 'backend')) projectType = 'api';
|
|
609
633
|
else if (frameworkDetails.some(item => item.kind === 'frontend')) projectType = 'web-app';
|
|
610
634
|
else if (fileExists('package.json') && pkg.bin) projectType = 'cli';
|
|
@@ -745,13 +769,24 @@ ${(data.scan_evidence || []).map(line => '- ' + line).join('\n')}
|
|
|
745
769
|
# Generated by thachvd-kit init wizard on ${new Date().toISOString().split('T')[0]}
|
|
746
770
|
# Edit freely — this is your project's brain.
|
|
747
771
|
|
|
748
|
-
## Project
|
|
749
|
-
|
|
750
|
-
- **Name:** ${data.project_name}
|
|
751
|
-
- **Description:** ${data.description}
|
|
752
|
-
- **Type:** ${data.project_type}
|
|
753
|
-
|
|
754
|
-
---
|
|
772
|
+
## Project
|
|
773
|
+
|
|
774
|
+
- **Name:** ${data.project_name}
|
|
775
|
+
- **Description:** ${data.description}
|
|
776
|
+
- **Type:** ${data.project_type}
|
|
777
|
+
|
|
778
|
+
---
|
|
779
|
+
|
|
780
|
+
## Context Status
|
|
781
|
+
|
|
782
|
+
\`\`\`yaml
|
|
783
|
+
context:
|
|
784
|
+
status: ${data.context_status || 'baseline'}
|
|
785
|
+
needs_ai_refinement: ${data.needs_ai_refinement ? 'true' : 'false'}
|
|
786
|
+
last_refined_at: ${data.last_refined_at || 'not-yet-refined'}
|
|
787
|
+
\`\`\`
|
|
788
|
+
|
|
789
|
+
---
|
|
755
790
|
|
|
756
791
|
## Stack
|
|
757
792
|
|
|
@@ -842,9 +877,73 @@ Stack: ${data.primary_language} | ${(data.frameworks || []).join(', ')}
|
|
|
842
877
|
- \`/test\` — Run test suite
|
|
843
878
|
- \`/deploy\` — Deploy checklist
|
|
844
879
|
|
|
845
|
-
See PROJECT_CONTEXT.md for full agent routing table.
|
|
846
|
-
`;
|
|
847
|
-
}
|
|
880
|
+
See PROJECT_CONTEXT.md for full agent routing table.
|
|
881
|
+
`;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
function generateAiContextRefinementPrompt(data) {
|
|
885
|
+
const evidence = (data.scan_evidence || []).length > 0
|
|
886
|
+
? (data.scan_evidence || []).map(line => `- ${line}`).join('\n')
|
|
887
|
+
: '- No scan evidence was captured during init.';
|
|
888
|
+
|
|
889
|
+
return `# AI Context Refinement
|
|
890
|
+
|
|
891
|
+
Use this prompt in your AI editor after \`thachvd-kit init\`.
|
|
892
|
+
|
|
893
|
+
## Goal
|
|
894
|
+
|
|
895
|
+
Refine \`PROJECT_CONTEXT.md\` so it reflects the real project more accurately than the baseline init scan.
|
|
896
|
+
|
|
897
|
+
## Current Baseline
|
|
898
|
+
|
|
899
|
+
- Project: ${data.project_name}
|
|
900
|
+
- Type: ${data.project_type}
|
|
901
|
+
- Stack: ${data.primary_language} | ${(data.frameworks || []).join(', ') || 'no framework'}
|
|
902
|
+
- Database: ${data.database || 'none'}
|
|
903
|
+
- Infra: ${(data.infrastructure || []).join(', ') || 'none'}
|
|
904
|
+
|
|
905
|
+
## Scan Evidence
|
|
906
|
+
|
|
907
|
+
${evidence}
|
|
908
|
+
|
|
909
|
+
## Instructions For The AI Agent
|
|
910
|
+
|
|
911
|
+
1. Read \`PROJECT_CONTEXT.md\` first.
|
|
912
|
+
2. Inspect the project selectively to improve factual accuracy.
|
|
913
|
+
3. Prioritize:
|
|
914
|
+
- real database/config detection
|
|
915
|
+
- domain/capability inference
|
|
916
|
+
- architecture notes
|
|
917
|
+
- more accurate auto-resolved skills
|
|
918
|
+
- important project-specific constraints
|
|
919
|
+
4. Update \`PROJECT_CONTEXT.md\` directly.
|
|
920
|
+
5. Distinguish facts from inferences when uncertain.
|
|
921
|
+
6. Do not implement product code in this pass. Only refine project context.
|
|
922
|
+
|
|
923
|
+
## Suggested Files To Inspect
|
|
924
|
+
|
|
925
|
+
- Root manifests: \`package.json\`, \`composer.json\`, \`Cargo.toml\`, \`pyproject.toml\`
|
|
926
|
+
- Infrastructure/config: Docker, CI, env, framework config files
|
|
927
|
+
- Database config and models
|
|
928
|
+
- Main app entry points, routing, controllers/services
|
|
929
|
+
- README and existing documentation
|
|
930
|
+
|
|
931
|
+
## Desired Improvements
|
|
932
|
+
|
|
933
|
+
- Correct the \`database\` field if config files reveal the real DB.
|
|
934
|
+
- Add architecture notes that explain the actual structure.
|
|
935
|
+
- Infer domain capabilities such as scraping, browser automation, external API integrations, queues, workers, etc.
|
|
936
|
+
- Expand auto-resolved skills when there is clear evidence in the codebase.
|
|
937
|
+
- Add caveats or assumptions if parts of the stack are still uncertain.
|
|
938
|
+
|
|
939
|
+
## Completion Standard
|
|
940
|
+
|
|
941
|
+
When done, summarize:
|
|
942
|
+
- what was corrected
|
|
943
|
+
- what is still inferred
|
|
944
|
+
- which files provided the strongest evidence
|
|
945
|
+
`;
|
|
946
|
+
}
|
|
848
947
|
|
|
849
948
|
// --- Main CLI ---
|
|
850
949
|
|
|
@@ -925,10 +1024,14 @@ async function main() {
|
|
|
925
1024
|
|
|
926
1025
|
const scanned = useScan ? scanProject() : null;
|
|
927
1026
|
let reviewScannedFields = !useScan;
|
|
1027
|
+
let requestAiRefinement = false;
|
|
928
1028
|
|
|
929
1029
|
if (useScan) {
|
|
930
|
-
console.log(
|
|
931
|
-
|
|
1030
|
+
console.log(` ${pc.bold('Detected context:')}`);
|
|
1031
|
+
formatScanPreviewLines(scanned).forEach(([label, value]) => {
|
|
1032
|
+
console.log(` · ${label}: ${value}`);
|
|
1033
|
+
});
|
|
1034
|
+
console.log('');
|
|
932
1035
|
if ((scanned.scan_evidence || []).length > 0) {
|
|
933
1036
|
console.log(` ${pc.bold('Evidence:')}`);
|
|
934
1037
|
scanned.scan_evidence.forEach(line => console.log(` · ${line}`));
|
|
@@ -1018,6 +1121,20 @@ async function main() {
|
|
|
1018
1121
|
data.max_file_lines = data.max_file_lines || scanned?.max_file_lines || '300';
|
|
1019
1122
|
data.framework_details = scanned?.framework_details || [];
|
|
1020
1123
|
data.scan_evidence = scanned?.scan_evidence || [];
|
|
1124
|
+
|
|
1125
|
+
if (!assumeYes) {
|
|
1126
|
+
const aiRefineAnswer = await askQuestions({
|
|
1127
|
+
type: 'confirm',
|
|
1128
|
+
name: 'ai_refine',
|
|
1129
|
+
message: 'Should the agent refine PROJECT_CONTEXT.md on first session start?',
|
|
1130
|
+
initial: useScan
|
|
1131
|
+
});
|
|
1132
|
+
requestAiRefinement = Boolean(aiRefineAnswer.ai_refine);
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
data.context_status = 'baseline';
|
|
1136
|
+
data.needs_ai_refinement = requestAiRefinement;
|
|
1137
|
+
data.last_refined_at = 'not-yet-refined';
|
|
1021
1138
|
data.infrastructure = [];
|
|
1022
1139
|
if (data.uses_docker) data.infrastructure.push('docker');
|
|
1023
1140
|
if (data.cloud !== 'none') data.infrastructure.push(data.cloud);
|
|
@@ -1052,9 +1169,16 @@ async function main() {
|
|
|
1052
1169
|
const claudeMdPath = path.join(targetDir, 'CLAUDE.md');
|
|
1053
1170
|
fs.writeFileSync(claudeMdPath, generateClaudeMd(data), 'utf8');
|
|
1054
1171
|
console.log(` ${pc.green('✓')} Generated ${pc.bold('CLAUDE.md')}`);
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1172
|
+
|
|
1173
|
+
let aiRefinementPath = null;
|
|
1174
|
+
if (requestAiRefinement) {
|
|
1175
|
+
aiRefinementPath = path.join(targetDir, 'AI_CONTEXT_REFINEMENT.md');
|
|
1176
|
+
fs.writeFileSync(aiRefinementPath, generateAiContextRefinementPrompt(data), 'utf8');
|
|
1177
|
+
console.log(` ${pc.green('✓')} Generated ${pc.bold('AI_CONTEXT_REFINEMENT.md')}`);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
// Summary
|
|
1181
|
+
const autoSkills = resolveSkills(data);
|
|
1058
1182
|
console.log(`\n${pc.bold(pc.green('━━ Done! ━━'))}
|
|
1059
1183
|
|
|
1060
1184
|
${pc.bold('Project:')} ${data.project_name}
|
|
@@ -1067,11 +1191,12 @@ ${autoSkills.map(s => ' · ' + s).join('\n')}
|
|
|
1067
1191
|
${pc.bold('Generated files:')}
|
|
1068
1192
|
· ${pc.bold(projectContextPath)}
|
|
1069
1193
|
· ${pc.bold(claudeMdPath)}
|
|
1194
|
+
${aiRefinementPath ? ` · ${pc.bold(aiRefinementPath)}` : ''}
|
|
1070
1195
|
|
|
1071
1196
|
${pc.bold('Next steps:')}
|
|
1072
1197
|
1. Your kit is installed in ${pc.bold('./.agent')}
|
|
1073
1198
|
2. Review ${pc.bold('PROJECT_CONTEXT.md')} first and add architecture notes
|
|
1074
|
-
3. Instruct the agent to read/update ${pc.bold('PROJECT_CONTEXT.md')} before major tasks
|
|
1199
|
+
3. ${aiRefinementPath ? `Open ${pc.bold('AI_CONTEXT_REFINEMENT.md')} in your AI editor to refine project context` : `Instruct the agent to read/update ${pc.bold('PROJECT_CONTEXT.md')} before major tasks`}
|
|
1075
1200
|
4. Open project in Antigravity or Claude Code — the agent will read context on start
|
|
1076
1201
|
`);
|
|
1077
1202
|
}
|
package/package.json
CHANGED
package/rules/GEMINI.md
CHANGED
|
@@ -10,16 +10,19 @@ trigger: always_on
|
|
|
10
10
|
|
|
11
11
|
## MANDATORY FIRST ACTION
|
|
12
12
|
|
|
13
|
-
**At session start — before anything else:**
|
|
14
|
-
|
|
15
|
-
```
|
|
16
|
-
1. Read PROJECT_CONTEXT.md (if exists) → load stack, agents, constraints
|
|
17
|
-
2. If missing → run @[skills/project-onboarding] to create it
|
|
18
|
-
3.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
13
|
+
**At session start — before anything else:**
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
1. Read PROJECT_CONTEXT.md (if exists) → load stack, agents, constraints
|
|
17
|
+
2. If missing → run @[skills/project-onboarding] to create it
|
|
18
|
+
3. Read PROJECT_CONTEXT context.status and context.needs_ai_refinement
|
|
19
|
+
4. If needs_ai_refinement = true → scan project with AI, update PROJECT_CONTEXT.md, set status=refined and needs_ai_refinement=false
|
|
20
|
+
5. Confirm: "Context loaded: [Project Name] | Stack: [Stack] | Ready."
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
> All routing decisions below depend on PROJECT_CONTEXT.md.
|
|
24
|
+
> Without it, ask user to run: `python scripts/init.py`
|
|
25
|
+
> Baseline context must be refined once when `context.needs_ai_refinement = true`.
|
|
23
26
|
|
|
24
27
|
---
|
|
25
28
|
|
package/scripts/init.py
CHANGED
|
@@ -316,14 +316,16 @@ def scan_project():
|
|
|
316
316
|
{**build_detection("django", [
|
|
317
317
|
evidence_if(text_matches(requirements, r"\bdjango\b") or text_matches(pyproject, r"\bdjango\b"), "Python deps -> django", 4),
|
|
318
318
|
evidence_if(Path("manage.py").exists(), "found manage.py", 2),
|
|
319
|
-
|
|
319
|
+
evidence_if(Path("templates").exists(), "found templates/", 1),
|
|
320
|
+
]), "kind": "backend", "project_type": "web-app", "language": "python"},
|
|
320
321
|
{**build_detection("flask", [
|
|
321
322
|
evidence_if(text_matches(requirements, r"\bflask\b") or text_matches(pyproject, r"\bflask\b"), "Python deps -> flask", 4),
|
|
322
323
|
]), "kind": "backend", "project_type": "api", "language": "python"},
|
|
323
324
|
{**build_detection("laravel", [
|
|
324
325
|
evidence_if(has_composer_dependency(composer_dep_names, "laravel/framework"), "composer.json -> laravel/framework", 4),
|
|
325
326
|
evidence_if(Path("artisan").exists(), "found artisan", 3),
|
|
326
|
-
|
|
327
|
+
evidence_if(Path("resources/views").exists(), "found resources/views", 1),
|
|
328
|
+
]), "kind": "backend", "project_type": "web-app", "language": "php"},
|
|
327
329
|
{**build_detection("cakephp", [
|
|
328
330
|
evidence_if(has_composer_dependency(composer_dep_names, "cakephp/cakephp"), "composer.json -> cakephp/cakephp", 4),
|
|
329
331
|
evidence_if(Path("bin/cake").exists(), "found bin/cake", 3),
|
|
@@ -333,12 +335,15 @@ def scan_project():
|
|
|
333
335
|
evidence_if(Path("app/Model/AppModel.php").exists(), "found app/Model/AppModel.php", 2),
|
|
334
336
|
evidence_if(Path("lib/Cake").exists(), "found lib/Cake", 3),
|
|
335
337
|
evidence_if(Path("cake/console/cake").exists(), "found cake/console/cake", 3),
|
|
336
|
-
|
|
338
|
+
evidence_if(Path("app/View").exists(), "found app/View", 2),
|
|
339
|
+
evidence_if(any(p.suffix.lower() == ".ctp" for p in Path("app/View").rglob("*.ctp")) if Path("app/View").exists() else False, "found .ctp view templates", 2),
|
|
340
|
+
]), "kind": "backend", "project_type": "web-app", "language": "php"},
|
|
337
341
|
{**build_detection("symfony", [
|
|
338
342
|
evidence_if(has_composer_dependency(composer_dep_names, "symfony/framework-bundle"), "composer.json -> symfony/framework-bundle", 4),
|
|
339
343
|
evidence_if(Path("bin/console").exists(), "found bin/console", 2),
|
|
340
344
|
evidence_if(Path("config/bundles.php").exists(), "found config/bundles.php", 1),
|
|
341
|
-
|
|
345
|
+
evidence_if(Path("templates").exists(), "found templates/", 1),
|
|
346
|
+
]), "kind": "backend", "project_type": "web-app", "language": "php"},
|
|
342
347
|
{**build_detection("codeigniter", [
|
|
343
348
|
evidence_if(has_composer_dependency(composer_dep_names, "codeigniter4/framework"), "composer.json -> codeigniter4/framework", 4),
|
|
344
349
|
evidence_if(Path("spark").exists(), "found spark", 2),
|
|
@@ -356,7 +361,8 @@ def scan_project():
|
|
|
356
361
|
{**build_detection("rails", [
|
|
357
362
|
evidence_if(text_matches(gemfile, r'^\s*gem\s+["\']rails["\']'), "Gemfile -> rails", 4),
|
|
358
363
|
evidence_if(Path("bin/rails").exists(), "found bin/rails", 2),
|
|
359
|
-
|
|
364
|
+
evidence_if(Path("app/views").exists(), "found app/views", 1),
|
|
365
|
+
]), "kind": "backend", "project_type": "web-app", "language": "ruby"},
|
|
360
366
|
{**build_detection("spring", [
|
|
361
367
|
evidence_if(text_matches(pom, r"spring-boot") or text_matches(gradle, r"spring-boot"), "Java build -> spring-boot", 4),
|
|
362
368
|
]), "kind": "backend", "project_type": "api", "language": "java"},
|
|
@@ -404,6 +410,7 @@ def scan_project():
|
|
|
404
410
|
if any(item["project_type"] == "desktop" for item in framework_details): project_type = "desktop"
|
|
405
411
|
elif any(item["project_type"] == "mobile" for item in framework_details): project_type = "mobile"
|
|
406
412
|
elif any(item["kind"] == "frontend" for item in framework_details) and any(item["kind"] == "backend" for item in framework_details): project_type = "fullstack"
|
|
413
|
+
elif any(item["project_type"] == "web-app" for item in framework_details): project_type = "web-app"
|
|
407
414
|
elif any(item["kind"] == "backend" for item in framework_details): project_type = "api"
|
|
408
415
|
elif any(item["kind"] == "frontend" for item in framework_details): project_type = "web-app"
|
|
409
416
|
elif Path("package.json").exists() and pkg.get("bin"): project_type = "cli"
|
|
@@ -539,13 +546,24 @@ These detections were inferred from the current codebase during `init --scan`:
|
|
|
539
546
|
# Generated by thachvd-kit init wizard on {datetime.now().strftime('%Y-%m-%d')}
|
|
540
547
|
# Edit freely — this is your project's brain.
|
|
541
548
|
|
|
542
|
-
## Project
|
|
543
|
-
|
|
544
|
-
- **Name:** {data['project_name']}
|
|
545
|
-
- **Description:** {data['description']}
|
|
546
|
-
- **Type:** {data['project_type']}
|
|
547
|
-
|
|
548
|
-
---
|
|
549
|
+
## Project
|
|
550
|
+
|
|
551
|
+
- **Name:** {data['project_name']}
|
|
552
|
+
- **Description:** {data['description']}
|
|
553
|
+
- **Type:** {data['project_type']}
|
|
554
|
+
|
|
555
|
+
---
|
|
556
|
+
|
|
557
|
+
## Context Status
|
|
558
|
+
|
|
559
|
+
```yaml
|
|
560
|
+
context:
|
|
561
|
+
status: {data.get('context_status', 'baseline')}
|
|
562
|
+
needs_ai_refinement: {'true' if data.get('needs_ai_refinement') else 'false'}
|
|
563
|
+
last_refined_at: {data.get('last_refined_at', 'not-yet-refined')}
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
---
|
|
549
567
|
|
|
550
568
|
## Stack
|
|
551
569
|
|
|
@@ -607,7 +625,7 @@ Based on your stack, these skills will be loaded automatically:
|
|
|
607
625
|
{data.get('known_issues', '<!-- Add known issues or pending tasks -->')}
|
|
608
626
|
"""
|
|
609
627
|
|
|
610
|
-
def generate_claude_md(data):
|
|
628
|
+
def generate_claude_md(data):
|
|
611
629
|
"""Generate CLAUDE.md for Claude Code / Codex compatibility."""
|
|
612
630
|
return f"""# CLAUDE.md — {data['project_name']}
|
|
613
631
|
# Generated by thachvd-kit. See PROJECT_CONTEXT.md for full stack info.
|
|
@@ -636,8 +654,82 @@ Stack: {data['primary_language']} | {', '.join(data.get('frameworks', []))}
|
|
|
636
654
|
- `/test` — Run test suite
|
|
637
655
|
- `/deploy` — Deploy checklist
|
|
638
656
|
|
|
639
|
-
See PROJECT_CONTEXT.md for full agent routing table.
|
|
640
|
-
"""
|
|
657
|
+
See PROJECT_CONTEXT.md for full agent routing table.
|
|
658
|
+
"""
|
|
659
|
+
|
|
660
|
+
def generate_ai_context_refinement_prompt(data):
|
|
661
|
+
scan_evidence = data.get("scan_evidence", [])
|
|
662
|
+
evidence_block = "\n".join("- " + line for line in scan_evidence) if scan_evidence else "- No scan evidence was captured during init."
|
|
663
|
+
return f"""# AI Context Refinement
|
|
664
|
+
|
|
665
|
+
Use this prompt in your AI editor after `thachvd-kit init`.
|
|
666
|
+
|
|
667
|
+
## Goal
|
|
668
|
+
|
|
669
|
+
Refine `PROJECT_CONTEXT.md` so it reflects the real project more accurately than the baseline init scan.
|
|
670
|
+
|
|
671
|
+
## Current Baseline
|
|
672
|
+
|
|
673
|
+
- Project: {data['project_name']}
|
|
674
|
+
- Type: {data['project_type']}
|
|
675
|
+
- Stack: {data['primary_language']} | {', '.join(data.get('frameworks', [])) or 'no framework'}
|
|
676
|
+
- Database: {data.get('database', 'none')}
|
|
677
|
+
- Infra: {', '.join(data.get('infrastructure', [])) or 'none'}
|
|
678
|
+
|
|
679
|
+
## Scan Evidence
|
|
680
|
+
|
|
681
|
+
{evidence_block}
|
|
682
|
+
|
|
683
|
+
## Instructions For The AI Agent
|
|
684
|
+
|
|
685
|
+
1. Read `PROJECT_CONTEXT.md` first.
|
|
686
|
+
2. Inspect the project selectively to improve factual accuracy.
|
|
687
|
+
3. Prioritize:
|
|
688
|
+
- real database/config detection
|
|
689
|
+
- domain/capability inference
|
|
690
|
+
- architecture notes
|
|
691
|
+
- more accurate auto-resolved skills
|
|
692
|
+
- important project-specific constraints
|
|
693
|
+
4. Update `PROJECT_CONTEXT.md` directly.
|
|
694
|
+
5. Distinguish facts from inferences when uncertain.
|
|
695
|
+
6. Do not implement product code in this pass. Only refine project context.
|
|
696
|
+
|
|
697
|
+
## Suggested Files To Inspect
|
|
698
|
+
|
|
699
|
+
- Root manifests: `package.json`, `composer.json`, `Cargo.toml`, `pyproject.toml`
|
|
700
|
+
- Infrastructure/config: Docker, CI, env, framework config files
|
|
701
|
+
- Database config and models
|
|
702
|
+
- Main app entry points, routing, controllers/services
|
|
703
|
+
- README and existing documentation
|
|
704
|
+
|
|
705
|
+
## Desired Improvements
|
|
706
|
+
|
|
707
|
+
- Correct the `database` field if config files reveal the real DB.
|
|
708
|
+
- Add architecture notes that explain the actual structure.
|
|
709
|
+
- Infer domain capabilities such as scraping, browser automation, external API integrations, queues, workers, etc.
|
|
710
|
+
- Expand auto-resolved skills when there is clear evidence in the codebase.
|
|
711
|
+
- Add caveats or assumptions if parts of the stack are still uncertain.
|
|
712
|
+
|
|
713
|
+
## Completion Standard
|
|
714
|
+
|
|
715
|
+
When done, summarize:
|
|
716
|
+
- what was corrected
|
|
717
|
+
- what is still inferred
|
|
718
|
+
- which files provided the strongest evidence
|
|
719
|
+
"""
|
|
720
|
+
|
|
721
|
+
def format_scan_preview_lines(scanned):
|
|
722
|
+
return [
|
|
723
|
+
("Project type", scanned.get("project_type", "unknown")),
|
|
724
|
+
("Primary language", scanned.get("primary_language", "unknown")),
|
|
725
|
+
("Frameworks", ", ".join(scanned.get("frameworks", [])) or "no framework"),
|
|
726
|
+
("Database", scanned.get("database", "none")),
|
|
727
|
+
("Package manager", scanned.get("package_manager", "auto-detect")),
|
|
728
|
+
("Test framework", scanned.get("test_framework", "auto-detect")),
|
|
729
|
+
("Cloud", scanned.get("cloud", "none")),
|
|
730
|
+
("Infrastructure", ", ".join(scanned.get("infrastructure", [])) or "none"),
|
|
731
|
+
("CI/CD", scanned.get("ci_cd", "none")),
|
|
732
|
+
]
|
|
641
733
|
|
|
642
734
|
# ─── Main wizard ─────────────────────────────────────────────────────────────
|
|
643
735
|
|
|
@@ -678,8 +770,10 @@ def main():
|
|
|
678
770
|
review_scanned_fields = not use_scan
|
|
679
771
|
if use_scan:
|
|
680
772
|
print(f" {dim('Scan mode: detecting stack from current project files...')}")
|
|
681
|
-
print(f" {bold('
|
|
682
|
-
|
|
773
|
+
print(f" {bold('Detected context:')}")
|
|
774
|
+
for label, value in format_scan_preview_lines(scanned):
|
|
775
|
+
print(f" · {label}: {value}")
|
|
776
|
+
print()
|
|
683
777
|
if scanned.get("scan_evidence"):
|
|
684
778
|
print(f" {bold('Evidence:')}")
|
|
685
779
|
for line in scanned["scan_evidence"]:
|
|
@@ -769,6 +863,9 @@ def main():
|
|
|
769
863
|
data["code_style"] = data.get("code_style", (scanned or {}).get("code_style", "project-standard"))
|
|
770
864
|
data["framework_details"] = data.get("framework_details", (scanned or {}).get("framework_details", []))
|
|
771
865
|
data["scan_evidence"] = data.get("scan_evidence", (scanned or {}).get("scan_evidence", []))
|
|
866
|
+
data["context_status"] = data.get("context_status", "baseline")
|
|
867
|
+
data["needs_ai_refinement"] = data.get("needs_ai_refinement", False)
|
|
868
|
+
data["last_refined_at"] = data.get("last_refined_at", "not-yet-refined")
|
|
772
869
|
data["architecture_notes"] = data.get("architecture_notes", (scanned or {}).get("architecture_notes", "<!-- Add notes -->"))
|
|
773
870
|
data["current_phase"] = data.get("current_phase", (scanned or {}).get("current_phase", "Initial setup"))
|
|
774
871
|
data["next_phase"] = data.get("next_phase", (scanned or {}).get("next_phase", "—"))
|
|
@@ -777,6 +874,9 @@ def main():
|
|
|
777
874
|
if data["uses_docker"]: data["infrastructure"].append("docker")
|
|
778
875
|
if data["cloud"] != "none": data["infrastructure"].append(data["cloud"])
|
|
779
876
|
|
|
877
|
+
if not args.yes:
|
|
878
|
+
data["needs_ai_refinement"] = ask_bool("Should the agent refine PROJECT_CONTEXT.md on first session start?", default=use_scan)
|
|
879
|
+
|
|
780
880
|
# ── Generate files ────────────────────────────────────────────────────
|
|
781
881
|
section("Generating Files")
|
|
782
882
|
|
|
@@ -788,11 +888,17 @@ def main():
|
|
|
788
888
|
# CLAUDE.md (for Claude Code / Codex)
|
|
789
889
|
if args.platform in ("claude", "all"):
|
|
790
890
|
claude_path = Path("CLAUDE.md")
|
|
791
|
-
claude_path.write_text(generate_claude_md(data), encoding="utf-8")
|
|
792
|
-
print(f" {green('✓')} {bold('CLAUDE.md')} generated (Claude Code / Codex)")
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
891
|
+
claude_path.write_text(generate_claude_md(data), encoding="utf-8")
|
|
892
|
+
print(f" {green('✓')} {bold('CLAUDE.md')} generated (Claude Code / Codex)")
|
|
893
|
+
|
|
894
|
+
ai_refine_path = None
|
|
895
|
+
if data.get("needs_ai_refinement"):
|
|
896
|
+
ai_refine_path = Path("AI_CONTEXT_REFINEMENT.md")
|
|
897
|
+
ai_refine_path.write_text(generate_ai_context_refinement_prompt(data), encoding="utf-8")
|
|
898
|
+
print(f" {green('✓')} {bold('AI_CONTEXT_REFINEMENT.md')} generated")
|
|
899
|
+
|
|
900
|
+
# Summary
|
|
901
|
+
auto_skills = resolve_skills(data)
|
|
796
902
|
print(f"""
|
|
797
903
|
{bold(green('━━ Done! ━━'))}
|
|
798
904
|
|
|
@@ -806,10 +912,11 @@ def main():
|
|
|
806
912
|
{bold('Generated files:')}
|
|
807
913
|
· {bold(str(output_path.resolve()))}
|
|
808
914
|
· {bold(str(Path("CLAUDE.md").resolve())) if args.platform in ("claude", "all") else 'CLAUDE.md not generated for this platform'}
|
|
915
|
+
· {bold(str(ai_refine_path.resolve())) if ai_refine_path else 'AI_CONTEXT_REFINEMENT.md not generated'}
|
|
809
916
|
|
|
810
917
|
{bold('Next steps:')}
|
|
811
918
|
1. Review {bold('PROJECT_CONTEXT.md')} first and add architecture notes
|
|
812
|
-
2. Instruct the agent to read/update
|
|
919
|
+
2. {"Open " + bold("AI_CONTEXT_REFINEMENT.md") + " in your AI editor to refine project context" if ai_refine_path else "Instruct the agent to read/update " + bold("PROJECT_CONTEXT.md") + " before major tasks"}
|
|
813
920
|
3. Open project in your AI editor (Antigravity / Claude Code / Codex)
|
|
814
921
|
4. The agent will read PROJECT_CONTEXT.md on session start
|
|
815
922
|
|