thachvd-kit 1.0.7 → 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/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: 'api',
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: 'api',
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: 'api',
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: 'api',
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: 'api',
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
 
@@ -992,8 +1027,11 @@ async function main() {
992
1027
  let requestAiRefinement = false;
993
1028
 
994
1029
  if (useScan) {
995
- console.log(`${pc.bold(' Scan result:')} ${scanned.primary_language} | ${scanned.frameworks.join(', ') || 'no framework'}`);
996
- console.log(` ${pc.bold('Detected infra:')} ${scanned.infrastructure.join(', ') || 'none'}\n`);
1030
+ console.log(` ${pc.bold('Detected context:')}`);
1031
+ formatScanPreviewLines(scanned).forEach(([label, value]) => {
1032
+ console.log(` · ${label}: ${value}`);
1033
+ });
1034
+ console.log('');
997
1035
  if ((scanned.scan_evidence || []).length > 0) {
998
1036
  console.log(` ${pc.bold('Evidence:')}`);
999
1037
  scanned.scan_evidence.forEach(line => console.log(` · ${line}`));
@@ -1083,6 +1121,20 @@ async function main() {
1083
1121
  data.max_file_lines = data.max_file_lines || scanned?.max_file_lines || '300';
1084
1122
  data.framework_details = scanned?.framework_details || [];
1085
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';
1086
1138
  data.infrastructure = [];
1087
1139
  if (data.uses_docker) data.infrastructure.push('docker');
1088
1140
  if (data.cloud !== 'none') data.infrastructure.push(data.cloud);
@@ -1118,16 +1170,6 @@ async function main() {
1118
1170
  fs.writeFileSync(claudeMdPath, generateClaudeMd(data), 'utf8');
1119
1171
  console.log(` ${pc.green('✓')} Generated ${pc.bold('CLAUDE.md')}`);
1120
1172
 
1121
- if (!assumeYes) {
1122
- const aiRefineAnswer = await askQuestions({
1123
- type: 'confirm',
1124
- name: 'ai_refine',
1125
- message: 'Do you want an AI refinement prompt for PROJECT_CONTEXT.md?',
1126
- initial: useScan
1127
- });
1128
- requestAiRefinement = Boolean(aiRefineAnswer.ai_refine);
1129
- }
1130
-
1131
1173
  let aiRefinementPath = null;
1132
1174
  if (requestAiRefinement) {
1133
1175
  aiRefinementPath = path.join(targetDir, 'AI_CONTEXT_REFINEMENT.md');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thachvd-kit",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "Universal Agent Kit for Antigravity, Claude Code, and OpenCode",
5
5
  "bin": {
6
6
  "thachvd-kit": "./bin/cli.js"
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. Confirm: "Context loaded: [Project Name] | Stack: [Stack] | Ready."
19
- ```
20
-
21
- > All routing decisions below depend on PROJECT_CONTEXT.md.
22
- > Without it, ask user to run: `python scripts/init.py`
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
- ]), "kind": "backend", "project_type": "api", "language": "python"},
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
- ]), "kind": "backend", "project_type": "api", "language": "php"},
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
- ]), "kind": "backend", "project_type": "api", "language": "php"},
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
- ]), "kind": "backend", "project_type": "api", "language": "php"},
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
- ]), "kind": "backend", "project_type": "api", "language": "ruby"},
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
 
@@ -699,6 +717,19 @@ When done, summarize:
699
717
  - what is still inferred
700
718
  - which files provided the strongest evidence
701
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
+ ]
702
733
 
703
734
  # ─── Main wizard ─────────────────────────────────────────────────────────────
704
735
 
@@ -739,8 +770,10 @@ def main():
739
770
  review_scanned_fields = not use_scan
740
771
  if use_scan:
741
772
  print(f" {dim('Scan mode: detecting stack from current project files...')}")
742
- print(f" {bold('Scan result:')} {scanned['primary_language']} | {', '.join(scanned['frameworks']) or 'no framework'}")
743
- print(f" {bold('Detected infra:')} {', '.join(scanned['infrastructure']) or 'none'}\n")
773
+ print(f" {bold('Detected context:')}")
774
+ for label, value in format_scan_preview_lines(scanned):
775
+ print(f" · {label}: {value}")
776
+ print()
744
777
  if scanned.get("scan_evidence"):
745
778
  print(f" {bold('Evidence:')}")
746
779
  for line in scanned["scan_evidence"]:
@@ -830,6 +863,9 @@ def main():
830
863
  data["code_style"] = data.get("code_style", (scanned or {}).get("code_style", "project-standard"))
831
864
  data["framework_details"] = data.get("framework_details", (scanned or {}).get("framework_details", []))
832
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")
833
869
  data["architecture_notes"] = data.get("architecture_notes", (scanned or {}).get("architecture_notes", "<!-- Add notes -->"))
834
870
  data["current_phase"] = data.get("current_phase", (scanned or {}).get("current_phase", "Initial setup"))
835
871
  data["next_phase"] = data.get("next_phase", (scanned or {}).get("next_phase", "—"))
@@ -838,6 +874,9 @@ def main():
838
874
  if data["uses_docker"]: data["infrastructure"].append("docker")
839
875
  if data["cloud"] != "none": data["infrastructure"].append(data["cloud"])
840
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
+
841
880
  # ── Generate files ────────────────────────────────────────────────────
842
881
  section("Generating Files")
843
882
 
@@ -853,7 +892,7 @@ def main():
853
892
  print(f" {green('✓')} {bold('CLAUDE.md')} generated (Claude Code / Codex)")
854
893
 
855
894
  ai_refine_path = None
856
- if not args.yes and ask_bool("Generate AI context refinement prompt?", default=use_scan):
895
+ if data.get("needs_ai_refinement"):
857
896
  ai_refine_path = Path("AI_CONTEXT_REFINEMENT.md")
858
897
  ai_refine_path.write_text(generate_ai_context_refinement_prompt(data), encoding="utf-8")
859
898
  print(f" {green('✓')} {bold('AI_CONTEXT_REFINEMENT.md')} generated")