thachvd-kit 1.0.15 → 1.0.17

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/scripts/init.py DELETED
@@ -1,1047 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- thachvd-kit — Project Init Wizard
4
- Generates PROJECT_CONTEXT.md and optional platform config files.
5
-
6
- Usage:
7
- python scripts/init.py # Interactive wizard
8
- python scripts/init.py --platform all # Also generate CLAUDE.md + AGENTS.md
9
- python scripts/init.py --platform claude # Only CLAUDE.md
10
- python scripts/init.py --platform codex # Only AGENTS.md
11
- """
12
-
13
- import sys
14
- import os
15
- import argparse
16
- import json
17
- import re
18
- from pathlib import Path
19
- from datetime import datetime
20
-
21
- # ─── Color helpers ──────────────────────────────────────────────────────────
22
-
23
- def c(text, code): return f"\033[{code}m{text}\033[0m" if sys.stdout.isatty() else text
24
- def bold(t): return c(t, "1")
25
- def green(t): return c(t, "32")
26
- def cyan(t): return c(t, "36")
27
- def yellow(t): return c(t, "33")
28
- def dim(t): return c(t, "2")
29
-
30
- # ─── Input helpers ───────────────────────────────────────────────────────────
31
-
32
- def ask(prompt, default="", choices=None):
33
- """Prompt user for input with optional default and choices."""
34
- display_default = f" [{dim(default)}]" if default else ""
35
- if choices:
36
- print(f"\n {cyan('Options:')} {', '.join(choices)}")
37
- value = input(f" {bold('→')} {prompt}{display_default}: ").strip()
38
- return value if value else default
39
-
40
- def ask_multi(prompt, choices, default=None):
41
- """Prompt for comma-separated selection from choices."""
42
- display_default = ", ".join(default) if isinstance(default, list) else (default or "none")
43
- print(f"\n {cyan('Available:')} {', '.join(f'{i+1}) {c}' for i, c in enumerate(choices))}")
44
- raw = input(f" {bold('→')} {prompt} (numbers or names, comma-separated) [{dim(display_default)}]: ").strip()
45
- if not raw:
46
- return default or []
47
- selected = []
48
- for part in raw.replace(",", " ").split():
49
- part = part.strip()
50
- if part.isdigit():
51
- idx = int(part) - 1
52
- if 0 <= idx < len(choices):
53
- selected.append(choices[idx])
54
- elif part in choices:
55
- selected.append(part)
56
- else:
57
- selected.append(part) # allow free-text
58
- return selected
59
-
60
- def ask_bool(prompt, default=True):
61
- """Yes/no prompt."""
62
- default_str = "Y/n" if default else "y/N"
63
- val = input(f" {bold('→')} {prompt} [{dim(default_str)}]: ").strip().lower()
64
- if not val:
65
- return default
66
- return val.startswith("y")
67
-
68
- def ask_select(prompt, choices, default=None):
69
- default = default or choices[0]
70
- return ask(prompt, default=default, choices=choices)
71
-
72
- def section(title):
73
- print(f"\n{bold(cyan('━━ ' + title + ' '))}")
74
-
75
- # ─── Skill resolver ──────────────────────────────────────────────────────────
76
-
77
- LANG_SKILLS = {
78
- "typescript": ["nodejs-best-practices", "api-design"],
79
- "javascript": ["nodejs-best-practices", "api-design"],
80
- "python": ["python-patterns"],
81
- "go": ["golang-patterns", "golang-testing"],
82
- "php": ["api-design"],
83
- "rust": ["rust-pro"],
84
- "java": ["api-design"],
85
- "ruby": ["api-design"],
86
- "swift": ["mobile-design"],
87
- "kotlin": ["mobile-design"],
88
- "csharp": ["api-design"],
89
- }
90
-
91
- FRAMEWORK_SKILLS = {
92
- "vue": ["frontend-design", "tailwind-patterns"],
93
- "react": ["react-frontend", "frontend-design"],
94
- "next": ["nextjs-react-expert", "frontend-design"],
95
- "nuxt": ["frontend-design"],
96
- "svelte": ["frontend-design"],
97
- "angular": ["frontend-design"],
98
- "laravel": ["laravel-patterns", "laravel-security", "laravel-tdd"],
99
- "cakephp": ["api-design"],
100
- "symfony": ["api-design"],
101
- "codeigniter": ["api-design"],
102
- "express": ["nodejs-best-practices", "api-design"],
103
- "fastapi": ["python-patterns", "api-design"],
104
- "django": ["python-patterns", "api-design"],
105
- "flask": ["python-patterns", "api-design"],
106
- "gin": ["golang-patterns", "golang-testing"],
107
- "fiber": ["golang-patterns", "golang-testing"],
108
- "echo": ["golang-patterns", "golang-testing"],
109
- "nestjs": ["nodejs-best-practices", "api-design"],
110
- "tauri": ["rust-pro", "desktop-design"],
111
- "axum": ["rust-pro", "api-design"],
112
- "actix": ["rust-pro", "api-design"],
113
- "rails": ["api-design"],
114
- "spring": ["api-design"],
115
- "aspnet": ["api-design"],
116
- "flutter": ["mobile-design"],
117
- "react-native": ["mobile-design"],
118
- }
119
-
120
- INFRA_SKILLS = {
121
- "docker": ["docker-patterns"],
122
- "aws": ["deployment-procedures", "docker-patterns"],
123
- "gcp": ["deployment-procedures", "docker-patterns"],
124
- "azure": ["deployment-procedures", "docker-patterns"],
125
- "vercel": ["deployment-procedures"],
126
- }
127
-
128
- def resolve_skills(data):
129
- skills = set()
130
- lang = data.get("primary_language", "").lower()
131
- if lang in LANG_SKILLS:
132
- skills.update(LANG_SKILLS[lang])
133
- for fw in data.get("frameworks", []):
134
- fw_lower = fw.lower()
135
- for key, vals in FRAMEWORK_SKILLS.items():
136
- if key in fw_lower:
137
- skills.update(vals)
138
- for infra in data.get("infrastructure", []):
139
- infra_lower = infra.lower()
140
- for key, vals in INFRA_SKILLS.items():
141
- if key in infra_lower:
142
- skills.update(vals)
143
- skills.update(["clean-code", "systematic-debugging"])
144
- return sorted(skills)
145
-
146
- FRAMEWORKS_ALL = ["vue", "react", "next", "nuxt", "svelte", "angular",
147
- "laravel", "cakephp", "symfony", "codeigniter",
148
- "express", "fastapi", "django", "flask", "gin", "fiber", "echo", "nestjs",
149
- "rails", "spring", "aspnet",
150
- "tauri", "axum", "actix", "flutter", "react-native"]
151
- LANGUAGES_ALL = ["typescript", "javascript", "python", "go", "php", "rust", "java", "ruby", "swift", "kotlin", "csharp", "c", "cpp"]
152
- PROJECT_TYPES_ALL = ["web-app", "api", "mobile", "desktop", "cli", "library", "fullstack"]
153
- DATABASES_ALL = ["postgres", "mysql", "sqlite", "mongodb", "redis", "dynamodb", "supabase", "prisma", "none"]
154
- PACKAGE_MANAGERS_ALL = ["npm", "pnpm", "yarn", "bun", "pip", "uv", "poetry", "go mod", "cargo", "composer", "auto-detect"]
155
- TEST_FRAMEWORKS_ALL = ["jest", "vitest", "pytest", "go test", "phpunit", "pest", "rspec", "junit", "auto-detect"]
156
- CLOUDS_ALL = ["aws", "gcp", "azure", "vercel", "fly", "railway", "digitalocean", "none"]
157
- FRONTEND_FRAMEWORKS = ["vue", "react", "next", "nuxt", "svelte", "angular"]
158
- BACKEND_FRAMEWORKS = ["laravel", "cakephp", "symfony", "codeigniter", "express", "fastapi", "django", "flask", "gin", "fiber", "echo", "nestjs", "rails", "spring", "aspnet", "axum", "actix"]
159
- MOBILE_DESKTOP_FRAMEWORKS = ["tauri", "flutter", "react-native"]
160
- IGNORED_SCAN_DIRS = {".git", "node_modules", "vendor", "dist", "build", "coverage", ".next", ".nuxt", ".turbo", ".cache", "storage", "tmp"}
161
-
162
- def read_text_if_exists(*parts):
163
- path = Path.cwd().joinpath(*parts)
164
- try:
165
- return path.read_text(encoding="utf-8") if path.exists() else ""
166
- except Exception:
167
- return ""
168
-
169
- def read_json_if_exists(*parts):
170
- path = Path.cwd().joinpath(*parts)
171
- try:
172
- return json.loads(path.read_text(encoding="utf-8")) if path.exists() else None
173
- except Exception:
174
- return None
175
-
176
- def detect_package_manager(base_dir=None):
177
- base_dir = base_dir or Path.cwd()
178
- if path_exists(base_dir, "pnpm-lock.yaml"): return "pnpm"
179
- if path_exists(base_dir, "yarn.lock"): return "yarn"
180
- if path_exists(base_dir, "bun.lockb") or path_exists(base_dir, "bun.lock"): return "bun"
181
- if path_exists(base_dir, "package-lock.json"): return "npm"
182
- if path_exists(base_dir, "uv.lock"): return "uv"
183
- if path_exists(base_dir, "poetry.lock"): return "poetry"
184
- if path_exists(base_dir, "requirements.txt") or path_exists(base_dir, "pyproject.toml"): return "pip"
185
- if path_exists(base_dir, "go.mod"): return "go mod"
186
- if path_exists(base_dir, "Cargo.toml"): return "cargo"
187
- if path_exists(base_dir, "composer.json"): return "composer"
188
- return "auto-detect"
189
-
190
- def uniq(values):
191
- return list(dict.fromkeys(v for v in values if v))
192
-
193
- def path_exists(base_dir, *parts):
194
- return base_dir.joinpath(*parts).exists()
195
-
196
- def evidence_if(condition, label, weight=1):
197
- return {"label": label, "weight": weight} if condition else None
198
-
199
- def build_detection(id, evidence, **meta):
200
- hits = [hit for hit in evidence if hit]
201
- score = sum(hit["weight"] for hit in hits)
202
- return {
203
- "id": id,
204
- "score": score,
205
- "evidence": [hit["label"] for hit in hits],
206
- **meta,
207
- }
208
-
209
- def confidence_label(score):
210
- if score >= 6: return "high"
211
- if score >= 3: return "medium"
212
- return "low"
213
-
214
- def has_dependency(dep_names, *names):
215
- return any(name in dep_names for name in names)
216
-
217
- def has_composer_dependency(composer_dep_names, *names):
218
- return any(name in composer_dep_names for name in names)
219
-
220
- def text_matches(text, pattern):
221
- return bool(text and re.search(pattern, text, re.IGNORECASE | re.MULTILINE))
222
-
223
- def detect_app_root(base_dir):
224
- candidates = [base_dir]
225
- queue = [(base_dir, 0)]
226
- while queue:
227
- current, depth = queue.pop(0)
228
- if depth >= 2:
229
- continue
230
- try:
231
- entries = list(current.iterdir())
232
- except Exception:
233
- continue
234
- for entry in entries:
235
- if not entry.is_dir():
236
- continue
237
- if entry.name in IGNORED_SCAN_DIRS:
238
- continue
239
- candidates.append(entry)
240
- queue.append((entry, depth + 1))
241
-
242
- def score_candidate(path):
243
- score = 0
244
- if path_exists(path, "composer.json"): score += 5
245
- if path_exists(path, "package.json"): score += 4
246
- if path_exists(path, "Cargo.toml"): score += 4
247
- if path_exists(path, "go.mod"): score += 4
248
- if path_exists(path, "pyproject.toml") or path_exists(path, "requirements.txt"): score += 4
249
- if path_exists(path, "artisan"): score += 5
250
- if path_exists(path, "bin", "cake") or path_exists(path, "app", "Config", "core.php"): score += 5
251
- if path_exists(path, "src-tauri", "tauri.conf.json") or path_exists(path, "src-tauri", "tauri.conf.json5"): score += 5
252
- if path_exists(path, "manage.py"): score += 4
253
- if path_exists(path, "Gemfile"): score += 4
254
- if path_exists(path, "pom.xml") or path_exists(path, "build.gradle") or path_exists(path, "build.gradle.kts"): score += 4
255
- if path_exists(path, "app", "View") or path_exists(path, "resources", "views") or path_exists(path, "templates") or path_exists(path, "app", "views"): score += 2
256
- return score
257
-
258
- ranked = [{"path": path, "score": score_candidate(path)} for path in candidates]
259
- ranked = [item for item in ranked if item["score"] > 0]
260
- ranked.sort(key=lambda item: (-item["score"], len(str(item["path"]))))
261
- return ranked[0]["path"] if ranked else base_dir
262
-
263
- def repo_has_any(base_dir, relative_paths, max_depth=3):
264
- queue = [(base_dir, 0)]
265
- while queue:
266
- current, depth = queue.pop(0)
267
- for rel_parts in relative_paths:
268
- if path_exists(current, *rel_parts):
269
- return True
270
- if depth >= max_depth:
271
- continue
272
- try:
273
- entries = list(current.iterdir())
274
- except Exception:
275
- continue
276
- for entry in entries:
277
- if not entry.is_dir():
278
- continue
279
- if entry.name in IGNORED_SCAN_DIRS:
280
- continue
281
- queue.append((entry, depth + 1))
282
- return False
283
-
284
- def scan_project():
285
- repo_root = Path.cwd()
286
- app_root = detect_app_root(repo_root)
287
- pkg = read_json_if_exists(app_root, "package.json") or {}
288
- composer = read_json_if_exists(app_root, "composer.json") or {}
289
- root_entries = [p.name for p in app_root.iterdir()] if app_root.exists() else []
290
- root_csproj = next((name for name in root_entries if name.endswith(".csproj")), "")
291
- deps = {}
292
- for key in ("dependencies", "devDependencies", "peerDependencies"):
293
- deps.update(pkg.get(key, {}) or {})
294
- dep_names = set(deps.keys())
295
- composer_deps = {}
296
- for key in ("require", "require-dev"):
297
- composer_deps.update(composer.get(key, {}) or {})
298
- composer_dep_names = set(composer_deps.keys())
299
- requirements = read_text_if_exists(app_root, "requirements.txt")
300
- pyproject = read_text_if_exists(app_root, "pyproject.toml")
301
- go_mod = read_text_if_exists(app_root, "go.mod")
302
- pubspec = read_text_if_exists(app_root, "pubspec.yaml")
303
- gemfile = read_text_if_exists(app_root, "Gemfile")
304
- pom = read_text_if_exists(app_root, "pom.xml")
305
- gradle = f"{read_text_if_exists(app_root, 'build.gradle')}\n{read_text_if_exists(app_root, 'build.gradle.kts')}"
306
- cargo = f"{read_text_if_exists(app_root, 'Cargo.toml')}\n{read_text_if_exists(app_root, 'src-tauri/Cargo.toml')}"
307
- csproj = read_text_if_exists(app_root, root_csproj) if root_csproj else ""
308
- program_cs = read_text_if_exists(app_root, "Program.cs")
309
-
310
- language_detections = [
311
- build_detection("typescript", [
312
- evidence_if(path_exists(app_root, "tsconfig.json"), "found tsconfig.json", 4),
313
- evidence_if(has_dependency(dep_names, "typescript", "ts-node", "tsx"), "package.json -> typescript toolchain", 2),
314
- ]),
315
- build_detection("javascript", [
316
- evidence_if(path_exists(app_root, "package.json"), "found package.json", 2),
317
- evidence_if(bool(dep_names), "package.json -> JavaScript dependency graph", 1),
318
- ]),
319
- build_detection("python", [
320
- evidence_if(path_exists(app_root, "requirements.txt"), "found requirements.txt", 3),
321
- evidence_if(path_exists(app_root, "pyproject.toml"), "found pyproject.toml", 2),
322
- evidence_if(text_matches(pyproject, r"\[tool\.(poetry|uv|hatch|pdm)\]"), "pyproject.toml -> Python tool section", 2),
323
- ]),
324
- build_detection("go", [
325
- evidence_if(path_exists(app_root, "go.mod"), "found go.mod", 4),
326
- ]),
327
- build_detection("php", [
328
- evidence_if(path_exists(app_root, "composer.json"), "found composer.json", 4),
329
- evidence_if(bool(composer_dep_names), "composer.json -> PHP dependency graph", 1),
330
- ]),
331
- build_detection("rust", [
332
- evidence_if(path_exists(app_root, "Cargo.toml"), "found Cargo.toml", 4),
333
- evidence_if(path_exists(app_root, "src-tauri", "Cargo.toml"), "found src-tauri/Cargo.toml", 5),
334
- ]),
335
- build_detection("java", [
336
- evidence_if(path_exists(app_root, "pom.xml"), "found pom.xml", 4),
337
- evidence_if(path_exists(app_root, "build.gradle") or path_exists(app_root, "build.gradle.kts"), "found Gradle build file", 3),
338
- ]),
339
- build_detection("ruby", [
340
- evidence_if(path_exists(app_root, "Gemfile"), "found Gemfile", 4),
341
- evidence_if(path_exists(app_root, "config.ru"), "found config.ru", 1),
342
- ]),
343
- build_detection("csharp", [
344
- evidence_if(any(name.endswith(".sln") or name.endswith(".csproj") for name in root_entries), "found .sln/.csproj", 4),
345
- evidence_if(path_exists(app_root, "Program.cs"), "found Program.cs", 1),
346
- ]),
347
- ]
348
- language_detections = [item for item in language_detections if item["score"] >= 2]
349
- language_detections.sort(key=lambda item: item["score"], reverse=True)
350
-
351
- framework_details = [
352
- {**build_detection("next", [
353
- evidence_if(has_dependency(dep_names, "next"), "package.json -> next", 4),
354
- evidence_if(path_exists(app_root, "next.config.js") or path_exists(app_root, "next.config.mjs") or path_exists(app_root, "next.config.ts"), "found next.config.*", 3),
355
- evidence_if(path_exists(app_root, "app") or path_exists(app_root, "pages"), "found app/ or pages/", 1),
356
- ]), "kind": "frontend", "project_type": "web-app"},
357
- {**build_detection("react", [
358
- evidence_if(has_dependency(dep_names, "react"), "package.json -> react", 4),
359
- evidence_if(has_dependency(dep_names, "react-dom"), "package.json -> react-dom", 2),
360
- ]), "kind": "frontend", "project_type": "web-app"},
361
- {**build_detection("vue", [
362
- evidence_if(has_dependency(dep_names, "vue"), "package.json -> vue", 4),
363
- ]), "kind": "frontend", "project_type": "web-app"},
364
- {**build_detection("nuxt", [
365
- evidence_if(has_dependency(dep_names, "nuxt"), "package.json -> nuxt", 4),
366
- evidence_if(path_exists(app_root, "nuxt.config.ts") or path_exists(app_root, "nuxt.config.js"), "found nuxt.config.*", 3),
367
- ]), "kind": "frontend", "project_type": "web-app"},
368
- {**build_detection("svelte", [
369
- evidence_if(has_dependency(dep_names, "svelte", "@sveltejs/kit"), "package.json -> svelte", 4),
370
- ]), "kind": "frontend", "project_type": "web-app"},
371
- {**build_detection("angular", [
372
- evidence_if(has_dependency(dep_names, "@angular/core"), "package.json -> @angular/core", 4),
373
- evidence_if(path_exists(app_root, "angular.json"), "found angular.json", 3),
374
- ]), "kind": "frontend", "project_type": "web-app"},
375
- {**build_detection("express", [
376
- evidence_if(has_dependency(dep_names, "express"), "package.json -> express", 4),
377
- ]), "kind": "backend", "project_type": "api", "language": "javascript"},
378
- {**build_detection("nestjs", [
379
- evidence_if(has_dependency(dep_names, "@nestjs/core"), "package.json -> @nestjs/core", 4),
380
- ]), "kind": "backend", "project_type": "api", "language": "javascript"},
381
- {**build_detection("fastapi", [
382
- evidence_if(text_matches(requirements, r"\bfastapi\b") or text_matches(pyproject, r"\bfastapi\b"), "Python deps -> fastapi", 4),
383
- ]), "kind": "backend", "project_type": "api", "language": "python"},
384
- {**build_detection("django", [
385
- evidence_if(text_matches(requirements, r"\bdjango\b") or text_matches(pyproject, r"\bdjango\b"), "Python deps -> django", 4),
386
- evidence_if(path_exists(app_root, "manage.py"), "found manage.py", 2),
387
- evidence_if(path_exists(app_root, "templates"), "found templates/", 1),
388
- ]), "kind": "backend", "project_type": "web-app", "language": "python"},
389
- {**build_detection("flask", [
390
- evidence_if(text_matches(requirements, r"\bflask\b") or text_matches(pyproject, r"\bflask\b"), "Python deps -> flask", 4),
391
- ]), "kind": "backend", "project_type": "api", "language": "python"},
392
- {**build_detection("laravel", [
393
- evidence_if(has_composer_dependency(composer_dep_names, "laravel/framework"), "composer.json -> laravel/framework", 4),
394
- evidence_if(path_exists(app_root, "artisan"), "found artisan", 3),
395
- evidence_if(path_exists(app_root, "resources", "views"), "found resources/views", 1),
396
- ]), "kind": "backend", "project_type": "web-app", "language": "php"},
397
- {**build_detection("cakephp", [
398
- evidence_if(has_composer_dependency(composer_dep_names, "cakephp/cakephp"), "composer.json -> cakephp/cakephp", 4),
399
- evidence_if(path_exists(app_root, "bin", "cake"), "found bin/cake", 3),
400
- evidence_if(path_exists(app_root, "config", "app.php"), "found config/app.php", 2),
401
- evidence_if(path_exists(app_root, "app", "Config", "core.php"), "found app/Config/core.php (CakePHP legacy)", 3),
402
- evidence_if(path_exists(app_root, "app", "Controller", "AppController.php"), "found app/Controller/AppController.php", 2),
403
- evidence_if(path_exists(app_root, "app", "Model", "AppModel.php"), "found app/Model/AppModel.php", 2),
404
- evidence_if(path_exists(app_root, "lib", "Cake"), "found lib/Cake", 3),
405
- evidence_if(path_exists(app_root, "cake", "console", "cake"), "found cake/console/cake", 3),
406
- evidence_if(path_exists(app_root, "app", "View"), "found app/View", 2),
407
- evidence_if(any(p.suffix.lower() == ".ctp" for p in app_root.joinpath("app", "View").rglob("*.ctp")) if path_exists(app_root, "app", "View") else False, "found .ctp view templates", 2),
408
- ]), "kind": "backend", "project_type": "web-app", "language": "php"},
409
- {**build_detection("symfony", [
410
- evidence_if(has_composer_dependency(composer_dep_names, "symfony/framework-bundle"), "composer.json -> symfony/framework-bundle", 4),
411
- evidence_if(path_exists(app_root, "bin", "console"), "found bin/console", 2),
412
- evidence_if(path_exists(app_root, "config", "bundles.php"), "found config/bundles.php", 1),
413
- evidence_if(path_exists(app_root, "templates"), "found templates/", 1),
414
- ]), "kind": "backend", "project_type": "web-app", "language": "php"},
415
- {**build_detection("codeigniter", [
416
- evidence_if(has_composer_dependency(composer_dep_names, "codeigniter4/framework"), "composer.json -> codeigniter4/framework", 4),
417
- evidence_if(path_exists(app_root, "spark"), "found spark", 2),
418
- evidence_if(path_exists(app_root, "app", "Config", "App.php"), "found app/Config/App.php", 1),
419
- ]), "kind": "backend", "project_type": "api", "language": "php"},
420
- {**build_detection("gin", [
421
- evidence_if(text_matches(go_mod, r"\bgithub\.com\/gin-gonic\/gin\b"), "go.mod -> github.com/gin-gonic/gin", 4),
422
- ]), "kind": "backend", "project_type": "api", "language": "go"},
423
- {**build_detection("fiber", [
424
- evidence_if(text_matches(go_mod, r"\bgithub\.com\/gofiber\/fiber\b"), "go.mod -> github.com/gofiber/fiber", 4),
425
- ]), "kind": "backend", "project_type": "api", "language": "go"},
426
- {**build_detection("echo", [
427
- evidence_if(text_matches(go_mod, r"\bgithub\.com\/labstack\/echo\b"), "go.mod -> github.com/labstack/echo", 4),
428
- ]), "kind": "backend", "project_type": "api", "language": "go"},
429
- {**build_detection("rails", [
430
- evidence_if(text_matches(gemfile, r'^\s*gem\s+["\']rails["\']'), "Gemfile -> rails", 4),
431
- evidence_if(path_exists(app_root, "bin", "rails"), "found bin/rails", 2),
432
- evidence_if(path_exists(app_root, "app", "views"), "found app/views", 1),
433
- ]), "kind": "backend", "project_type": "web-app", "language": "ruby"},
434
- {**build_detection("spring", [
435
- evidence_if(text_matches(pom, r"spring-boot") or text_matches(gradle, r"spring-boot"), "Java build -> spring-boot", 4),
436
- ]), "kind": "backend", "project_type": "api", "language": "java"},
437
- {**build_detection("aspnet", [
438
- evidence_if(text_matches(csproj, r"Microsoft\.NET\.Sdk\.Web"), ".csproj -> Microsoft.NET.Sdk.Web", 4),
439
- evidence_if(text_matches(program_cs, r"WebApplication\.CreateBuilder"), "Program.cs -> WebApplication.CreateBuilder", 2),
440
- ]), "kind": "backend", "project_type": "api", "language": "csharp"},
441
- {**build_detection("tauri", [
442
- evidence_if(has_dependency(dep_names, "@tauri-apps/api"), "package.json -> @tauri-apps/api", 4),
443
- evidence_if(path_exists(app_root, "src-tauri", "tauri.conf.json") or path_exists(app_root, "src-tauri", "tauri.conf.json5"), "found src-tauri/tauri.conf.*", 4),
444
- evidence_if(path_exists(app_root, "src-tauri", "Cargo.toml"), "found src-tauri/Cargo.toml", 2),
445
- ]), "kind": "desktop", "project_type": "desktop", "language": "rust"},
446
- {**build_detection("axum", [
447
- evidence_if(text_matches(cargo, r"\baxum\b"), "Cargo.toml -> axum", 4),
448
- ]), "kind": "backend", "project_type": "api", "language": "rust"},
449
- {**build_detection("actix", [
450
- evidence_if(text_matches(cargo, r"\bactix-web\b"), "Cargo.toml -> actix-web", 4),
451
- ]), "kind": "backend", "project_type": "api", "language": "rust"},
452
- {**build_detection("flutter", [
453
- evidence_if(text_matches(pubspec, r"^\s*flutter\s*:"), "pubspec.yaml -> flutter", 4),
454
- ]), "kind": "mobile", "project_type": "mobile"},
455
- {**build_detection("react-native", [
456
- evidence_if(has_dependency(dep_names, "react-native", "expo"), "package.json -> react-native/expo", 4),
457
- ]), "kind": "mobile", "project_type": "mobile"},
458
- ]
459
- framework_details = [item for item in framework_details if item["score"] >= 3]
460
- framework_details.sort(key=lambda item: item["score"], reverse=True)
461
- for item in framework_details:
462
- item["confidence"] = confidence_label(item["score"])
463
-
464
- framework_language_scores = {}
465
- for item in framework_details:
466
- if item.get("language"):
467
- framework_language_scores[item["language"]] = framework_language_scores.get(item["language"], 0) + item["score"]
468
- primary_language = max(framework_language_scores, key=framework_language_scores.get) if framework_language_scores else (language_detections[0]["id"] if language_detections else "typescript")
469
- frameworks = [item["id"] for item in framework_details if item["id"] in FRAMEWORKS_ALL]
470
- frameworks = uniq(frameworks)
471
- infrastructure = []
472
- project_type = ""
473
- database = "none"
474
- test_framework = "auto-detect"
475
- cloud = "none"
476
- ci_cd = "none"
477
-
478
- if any(item["project_type"] == "desktop" for item in framework_details): project_type = "desktop"
479
- elif any(item["project_type"] == "mobile" for item in framework_details): project_type = "mobile"
480
- elif any(item["kind"] == "frontend" for item in framework_details) and any(item["kind"] == "backend" for item in framework_details): project_type = "fullstack"
481
- elif any(item["project_type"] == "web-app" for item in framework_details): project_type = "web-app"
482
- elif any(item["kind"] == "backend" for item in framework_details): project_type = "api"
483
- elif any(item["kind"] == "frontend" for item in framework_details): project_type = "web-app"
484
- elif path_exists(app_root, "package.json") and pkg.get("bin"): project_type = "cli"
485
- elif primary_language in ["php", "python", "go", "java", "ruby", "csharp", "rust"]: project_type = "api"
486
-
487
- if has_dependency(dep_names, "pg", "postgres", "@prisma/client") or "postgres" in json.dumps(pkg).lower(): database = "postgres"
488
- elif has_dependency(dep_names, "mysql", "mysql2"): database = "mysql"
489
- elif has_dependency(dep_names, "sqlite3", "better-sqlite3", "rusqlite"): database = "sqlite"
490
- elif has_dependency(dep_names, "mongodb", "mongoose"): database = "mongodb"
491
- elif has_dependency(dep_names, "redis", "ioredis"): database = "redis"
492
- elif has_dependency(dep_names, "@aws-sdk/client-dynamodb"): database = "dynamodb"
493
-
494
- if has_dependency(dep_names, "vitest"): test_framework = "vitest"
495
- elif has_dependency(dep_names, "jest"): test_framework = "jest"
496
- elif has_dependency(dep_names, "pytest") or text_matches(requirements, r"\bpytest\b") or text_matches(pyproject, r"\bpytest\b"): test_framework = "pytest"
497
- elif has_composer_dependency(composer_dep_names, "phpunit/phpunit"): test_framework = "phpunit"
498
- elif has_composer_dependency(composer_dep_names, "pestphp/pest"): test_framework = "pest"
499
- elif path_exists(app_root, "go.mod"): test_framework = "go test"
500
- elif text_matches(gemfile, r'^\s*gem\s+["\']rspec["\']'): test_framework = "rspec"
501
-
502
- if repo_has_any(repo_root, [("Dockerfile",), ("docker-compose.yml",), ("docker-compose.yaml",)]):
503
- infrastructure.append("docker")
504
-
505
- if repo_has_any(repo_root, [("vercel.json",)]):
506
- cloud = "vercel"
507
- infrastructure.append("vercel")
508
- elif repo_has_any(repo_root, [("fly.toml",)]):
509
- cloud = "fly"
510
- infrastructure.append("fly")
511
- elif repo_has_any(repo_root, [("railway.json",), ("railway.toml",)]):
512
- cloud = "railway"
513
- infrastructure.append("railway")
514
-
515
- if repo_has_any(repo_root, [(".github", "workflows")], max_depth=2): ci_cd = "github-actions"
516
- elif repo_has_any(repo_root, [(".gitlab-ci.yml",)], max_depth=2): ci_cd = "gitlab-ci"
517
- elif repo_has_any(repo_root, [(".circleci", "config.yml")], max_depth=2): ci_cd = "circleci"
518
- elif repo_has_any(repo_root, [("Jenkinsfile",)], max_depth=2): ci_cd = "jenkins"
519
-
520
- scan_evidence = []
521
- if language_detections:
522
- scan_evidence.append(f"language:{language_detections[0]['id']} => {'; '.join(language_detections[0]['evidence'])}")
523
- scan_evidence.extend(f"{item['id']} [{item['confidence']}] => {'; '.join(item['evidence'])}" for item in framework_details)
524
-
525
- return {
526
- "project_name": Path.cwd().name,
527
- "app_root": str(app_root.relative_to(repo_root)) if app_root != repo_root else ".",
528
- "description": pkg.get("description") or composer.get("description", "A software project"),
529
- "project_type": project_type if project_type in PROJECT_TYPES_ALL else "web-app",
530
- "primary_language": primary_language if primary_language in LANGUAGES_ALL else "typescript",
531
- "frameworks": frameworks,
532
- "framework_details": framework_details,
533
- "scan_evidence": scan_evidence,
534
- "database": database if database in DATABASES_ALL else "none",
535
- "package_manager": detect_package_manager(app_root),
536
- "uses_docker": "docker" in infrastructure,
537
- "cloud": cloud,
538
- "infrastructure": uniq(infrastructure),
539
- "ci_cd": ci_cd,
540
- "test_framework": test_framework if test_framework in TEST_FRAMEWORKS_ALL else "auto-detect",
541
- "max_file_lines": "300",
542
- "code_style": "project-standard",
543
- "architecture_notes": "<!-- Add notes -->",
544
- "current_phase": "Initial setup",
545
- "next_phase": "—",
546
- "known_issues": "<!-- None yet -->",
547
- }
548
-
549
- def resolve_agent_table(data):
550
- rows = []
551
- frameworks = [f.lower() for f in data.get("frameworks", [])]
552
- lang = data.get("primary_language", "").lower()
553
-
554
- # Determine frontend agent
555
- frontend_fws = [f for f in frameworks if f in FRONTEND_FRAMEWORKS]
556
- if frontend_fws:
557
- fe_skills = []
558
- for fw in frontend_fws:
559
- fe_skills.extend(FRAMEWORK_SKILLS.get(fw, []))
560
- rows.append(("Frontend UI", "frontend-specialist", ", ".join(set(fe_skills[:3]))))
561
-
562
- # Backend
563
- be_skills = list(LANG_SKILLS.get(lang, ["api-design"]))
564
- backend_fws = [f for f in frameworks if f in BACKEND_FRAMEWORKS]
565
- for fw in backend_fws:
566
- be_skills.extend(FRAMEWORK_SKILLS.get(fw, []))
567
- rows.append(("Backend API", "backend-specialist", ", ".join(set(be_skills[:3]))))
568
-
569
- # Mobile/Desktop
570
- mobile_fws = [f for f in frameworks if f in MOBILE_DESKTOP_FRAMEWORKS]
571
- if mobile_fws:
572
- mobile_skills = []
573
- for fw in mobile_fws:
574
- mobile_skills.extend(FRAMEWORK_SKILLS.get(fw, []))
575
- rows.append(("Mobile / Desktop", "mobile-developer", ", ".join(set(mobile_skills[:3]))))
576
-
577
- # DevOps
578
- if data.get("uses_docker") or data.get("cloud"):
579
- rows.append(("DevOps / Infra", "devops-engineer", "docker-patterns, deployment-procedures"))
580
-
581
- rows.append(("Debug", "debugger", "systematic-debugging"))
582
- rows.append(("Security", "security-auditor", "vulnerability-scanner"))
583
- rows.append(("Planning", "project-planner", "brainstorming, plan-writing"))
584
- return rows
585
-
586
- # ─── Generators ──────────────────────────────────────────────────────────────
587
-
588
- def generate_project_context(data):
589
- frameworks = data.get("frameworks", [])
590
- infrastructure = data.get("infrastructure", [])
591
- agent_table = resolve_agent_table(data)
592
- auto_skills = resolve_skills(data)
593
- scan_evidence = data.get("scan_evidence", [])
594
-
595
- agent_rows = "\n".join(
596
- f"| {task} | `{agent}` | {skills} |"
597
- for task, agent, skills in agent_table
598
- )
599
-
600
- scan_evidence_block = ""
601
- if scan_evidence:
602
- scan_evidence_block = f"""
603
- ---
604
-
605
- ## Scan Evidence
606
-
607
- These detections were inferred from the current codebase during `init --scan`:
608
-
609
- ```
610
- {chr(10).join('- ' + line for line in scan_evidence)}
611
- ```
612
- """
613
-
614
- return f"""# PROJECT_CONTEXT.md
615
- # Generated by thachvd-kit init wizard on {datetime.now().strftime('%Y-%m-%d')}
616
- # Edit freely — this is your project's brain.
617
-
618
- ## Project
619
-
620
- - **Name:** {data['project_name']}
621
- - **Description:** {data['description']}
622
- - **Type:** {data['project_type']}
623
-
624
- ---
625
-
626
- ## Context Status
627
-
628
- ```yaml
629
- context:
630
- status: {data.get('context_status', 'baseline')}
631
- needs_ai_refinement: {'true' if data.get('needs_ai_refinement') else 'false'}
632
- last_refined_at: {data.get('last_refined_at', 'not-yet-refined')}
633
- ```
634
-
635
- ---
636
-
637
- ## Stack
638
-
639
- ```yaml
640
- stack:
641
- app_root: {data.get('app_root', '.')}
642
- primary_language: {data['primary_language']}
643
- frameworks: [{', '.join(frameworks)}]
644
- database: {data.get('database', 'none')}
645
- infrastructure: [{', '.join(infrastructure)}]
646
- package_manager: {data.get('package_manager', 'auto-detect')}
647
- test_framework: {data.get('test_framework', 'auto-detect')}
648
- ```
649
-
650
- {scan_evidence_block}
651
-
652
- ---
653
-
654
- ## Agent Routing
655
-
656
- | Task Type | Agent | Primary Skills |
657
- |-----------|-------|----------------|
658
- {agent_rows}
659
-
660
- ---
661
-
662
- ## Auto-Resolved Skills
663
-
664
- Based on your stack, these skills will be loaded automatically:
665
-
666
- ```
667
- {chr(10).join('- ' + s for s in auto_skills)}
668
- ```
669
-
670
- ---
671
-
672
- ## Project Conventions
673
-
674
- - **Max file size:** {data.get('max_file_lines', '300')} lines (then modularize)
675
- - **Code style:** {data.get('code_style', 'project-standard')}
676
-
677
- ---
678
-
679
- ## Architecture Notes
680
-
681
- {data.get('architecture_notes', '<!-- Add architecture notes, key decisions, patterns used -->')}
682
-
683
- ---
684
-
685
- ## Current Phase
686
-
687
- - **Done:** {data.get('done_phases', '—')}
688
- - **In Progress:** {data.get('current_phase', 'Initial setup')}
689
- - **Next:** {data.get('next_phase', '—')}
690
-
691
- ---
692
-
693
- ## Known Issues / Tasks
694
-
695
- {data.get('known_issues', '<!-- Add known issues or pending tasks -->')}
696
- """
697
-
698
- def generate_claude_md(data):
699
- """Generate CLAUDE.md for Claude Code."""
700
- return f"""# CLAUDE.md — {data['project_name']}
701
- # Generated by thachvd-kit. See PROJECT_CONTEXT.md for full stack info.
702
- # Platform: Claude Code
703
-
704
- ## Project
705
- {data['description']}
706
- Stack: {data['primary_language']} | {', '.join(data.get('frameworks', []))}
707
-
708
- ## Rules
709
- - Read PROJECT_CONTEXT.md first for stack details and agent routing.
710
- - If PROJECT_CONTEXT.md is missing or stale, create/update it before major work.
711
- - Follow skills in .claude/skills/ (or skills/ if using Antigravity).
712
- - Tests are mandatory. Verify before claiming done.
713
- - Max {data.get('max_file_lines', '300')} lines per file.
714
-
715
- ## Key Skills
716
- - Plan: `plan-writing` → `executing-plans`
717
- - Review: `requesting-code-review` / `receiving-code-review`
718
- - Quality: `clean-code`, `verification-before-completion`
719
- - Workflow: `subagent-driven-development`, `finishing-a-development-branch`
720
-
721
- ## Commands
722
- - `/plan` — Create implementation plan
723
- - `/debug` — Systematic debugging
724
- - `/test` — Run test suite
725
- - `/deploy` — Deploy checklist
726
-
727
- See PROJECT_CONTEXT.md for full agent routing table.
728
- """
729
-
730
- def generate_agents_md(data):
731
- """Generate AGENTS.md for Codex and AGENTS-compatible editors."""
732
- return f"""# AGENTS.md — {data['project_name']}
733
- # Generated by thachvd-kit. See PROJECT_CONTEXT.md for full stack info.
734
- # Platform: Codex / AGENTS-compatible editors
735
-
736
- ## Project
737
- {data['description']}
738
- Stack: {data['primary_language']} | {', '.join(data.get('frameworks', [])) or 'no framework'}
739
-
740
- ## Mandatory First Action
741
- - Read `PROJECT_CONTEXT.md` before making plans or code changes.
742
- - If `PROJECT_CONTEXT.md` is missing or stale, create/update it before major work.
743
- - If `context.needs_ai_refinement: true`, refine `PROJECT_CONTEXT.md` first, then set:
744
- - `context.status: refined`
745
- - `context.needs_ai_refinement: false`
746
- - `context.last_refined_at: <today>`
747
-
748
- ## Working Rules
749
- - Use `.agent/agents`, `.agent/skills`, `.agent/rules`, and `.agent/workflows` as the local project kit.
750
- - Match the task against the Agent Routing table in `PROJECT_CONTEXT.md`.
751
- - Load relevant skills before editing code.
752
- - Tests are mandatory. Verify before claiming done.
753
- - Keep files under {data.get('max_file_lines', '300')} lines when practical, then split into modules.
754
- - Update dependent files together when a change crosses boundaries.
755
-
756
- ## Preferred Workflow
757
- - Plan work from `PROJECT_CONTEXT.md` first.
758
- - For feature work, use: `plan-writing` -> `executing-plans` -> `verification-before-completion`.
759
- - For reviews, use: `requesting-code-review` / `receiving-code-review`.
760
- - For debugging, use: `systematic-debugging`.
761
-
762
- ## Key Paths
763
- - Project context: `PROJECT_CONTEXT.md`
764
- - Agent kit: `.agent/`
765
- - Skills: `.agent/skills/`
766
- - Workflows: `.agent/workflows/`
767
-
768
- See `PROJECT_CONTEXT.md` for stack details, routing, and auto-resolved skills.
769
- """
770
-
771
- def generate_ai_context_refinement_prompt(data):
772
- scan_evidence = data.get("scan_evidence", [])
773
- evidence_block = "\n".join("- " + line for line in scan_evidence) if scan_evidence else "- No scan evidence was captured during init."
774
- return f"""# AI Context Refinement
775
-
776
- Use this prompt in your AI editor after `thachvd-kit init`.
777
-
778
- ## Goal
779
-
780
- Refine `PROJECT_CONTEXT.md` so it reflects the real project more accurately than the baseline init scan.
781
-
782
- ## Current Baseline
783
-
784
- - Project: {data['project_name']}
785
- - Type: {data['project_type']}
786
- - Stack: {data['primary_language']} | {', '.join(data.get('frameworks', [])) or 'no framework'}
787
- - Database: {data.get('database', 'none')}
788
- - Infra: {', '.join(data.get('infrastructure', [])) or 'none'}
789
-
790
- ## Scan Evidence
791
-
792
- {evidence_block}
793
-
794
- ## Instructions For The AI Agent
795
-
796
- 1. Read `PROJECT_CONTEXT.md` first.
797
- 2. Inspect the project selectively to improve factual accuracy.
798
- 3. Prioritize:
799
- - real database/config detection
800
- - domain/capability inference
801
- - architecture notes
802
- - more accurate auto-resolved skills
803
- - important project-specific constraints
804
- 4. Update `PROJECT_CONTEXT.md` directly.
805
- 5. Distinguish facts from inferences when uncertain.
806
- 6. Do not implement product code in this pass. Only refine project context.
807
-
808
- ## Suggested Files To Inspect
809
-
810
- - Root manifests: `package.json`, `composer.json`, `Cargo.toml`, `pyproject.toml`
811
- - Infrastructure/config: Docker, CI, env, framework config files
812
- - Database config and models
813
- - Main app entry points, routing, controllers/services
814
- - README and existing documentation
815
-
816
- ## Desired Improvements
817
-
818
- - Correct the `database` field if config files reveal the real DB.
819
- - Add architecture notes that explain the actual structure.
820
- - Infer domain capabilities such as scraping, browser automation, external API integrations, queues, workers, etc.
821
- - Expand auto-resolved skills when there is clear evidence in the codebase.
822
- - Add caveats or assumptions if parts of the stack are still uncertain.
823
-
824
- ## Completion Standard
825
-
826
- When done, summarize:
827
- - what was corrected
828
- - what is still inferred
829
- - which files provided the strongest evidence
830
- """
831
-
832
- def format_scan_preview_lines(scanned):
833
- return [
834
- ("App root", scanned.get("app_root", ".")),
835
- ("Project type", scanned.get("project_type", "unknown")),
836
- ("Primary language", scanned.get("primary_language", "unknown")),
837
- ("Frameworks", ", ".join(scanned.get("frameworks", [])) or "no framework"),
838
- ("Database", scanned.get("database", "none")),
839
- ("Package manager", scanned.get("package_manager", "auto-detect")),
840
- ("Test framework", scanned.get("test_framework", "auto-detect")),
841
- ("Cloud", scanned.get("cloud", "none")),
842
- ("Infrastructure", ", ".join(scanned.get("infrastructure", [])) or "none"),
843
- ("CI/CD", scanned.get("ci_cd", "none")),
844
- ]
845
-
846
- # ─── Main wizard ─────────────────────────────────────────────────────────────
847
-
848
- def main():
849
- parser = argparse.ArgumentParser(description="thachvd-kit project init wizard")
850
- parser.add_argument("--platform", choices=["antigravity", "claude", "codex", "all"], default="antigravity",
851
- help="Which platform config to generate (default: antigravity)")
852
- parser.add_argument("--yes", "-y", action="store_true", help="Use defaults without prompting")
853
- parser.add_argument("--scan", action="store_true", help="Scan the current project to prefill or auto-generate PROJECT_CONTEXT.md")
854
- args = parser.parse_args()
855
-
856
- print(f"""
857
- {bold(cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'))}
858
- {bold('thachvd-kit')} — Project Init Wizard
859
- Generates PROJECT_CONTEXT.md for smart agent routing{' (scan-assisted)' if args.scan else ''}
860
- {bold(cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'))}
861
- """)
862
-
863
- # Check if PROJECT_CONTEXT.md already exists
864
- output_path = Path("PROJECT_CONTEXT.md")
865
- if output_path.exists() and not args.yes:
866
- overwrite = ask_bool(f"{yellow('PROJECT_CONTEXT.md already exists. Overwrite?')}", default=False)
867
- if not overwrite:
868
- print(f"\n {yellow('Aborted.')} Existing PROJECT_CONTEXT.md unchanged.\n")
869
- sys.exit(0)
870
-
871
- use_scan = args.scan
872
- if not args.scan and not args.yes:
873
- section("Setup Mode")
874
- project_setup = ask_select("Project already exists or are you starting fresh",
875
- ["existing", "new"], default="existing")
876
- if project_setup == "existing":
877
- existing_mode = ask_select("How should thachvd-kit build PROJECT_CONTEXT.md",
878
- ["scan", "manual"], default="scan")
879
- use_scan = existing_mode == "scan"
880
-
881
- scanned = scan_project() if use_scan else None
882
- review_scanned_fields = not use_scan
883
- if use_scan:
884
- print(f" {dim('Scan mode: detecting stack from current project files...')}")
885
- print(f" {bold('Detected context:')}")
886
- for label, value in format_scan_preview_lines(scanned):
887
- print(f" · {label}: {value}")
888
- print()
889
- if scanned.get("scan_evidence"):
890
- print(f" {bold('Evidence:')}")
891
- for line in scanned["scan_evidence"]:
892
- print(f" · {line}")
893
- print()
894
- if not args.yes:
895
- scan_action = ask_select("Use detected context as-is or review fields manually",
896
- ["accept", "review"], default="accept")
897
- review_scanned_fields = scan_action == "review"
898
- elif not args.yes:
899
- print(f" {dim('Manual mode: asking for project context explicitly.')}\n")
900
-
901
- if args.yes and scanned:
902
- data = dict(scanned)
903
- else:
904
- data = {}
905
-
906
- if not args.yes and review_scanned_fields:
907
- # ── Project Info ──────────────────────────────────────────────────────
908
- section("Project Info")
909
- data["project_name"] = ask("Project name", default=(scanned or {}).get("project_name", Path.cwd().name))
910
- data["description"] = ask("Short description", default=(scanned or {}).get("description", "A software project"))
911
- data["project_type"] = ask("Project type",
912
- default=(scanned or {}).get("project_type", "web-app"),
913
- choices=PROJECT_TYPES_ALL)
914
-
915
- # ── Tech Stack ────────────────────────────────────────────────────────
916
- section("Tech Stack")
917
-
918
- data["primary_language"] = ask("Primary language",
919
- default=(scanned or {}).get("primary_language", "typescript"),
920
- choices=LANGUAGES_ALL)
921
-
922
- frameworks = ask_multi("Frameworks (select all that apply)", FRAMEWORKS_ALL, default=(scanned or {}).get("frameworks", None))
923
- data["frameworks"] = frameworks or []
924
-
925
- data["database"] = ask("Database / ORM", default=(scanned or {}).get("database", "postgres"), choices=DATABASES_ALL)
926
-
927
- data["package_manager"] = ask("Package manager", default=(scanned or {}).get("package_manager", "auto-detect"), choices=PACKAGE_MANAGERS_ALL)
928
-
929
- # ── Infrastructure ────────────────────────────────────────────────────
930
- section("Infrastructure")
931
-
932
- data["uses_docker"] = ask_bool("Uses Docker / Docker Compose?", default=(scanned or {}).get("uses_docker", True))
933
- cloud = ask("Cloud provider", default=(scanned or {}).get("cloud", "none"), choices=CLOUDS_ALL)
934
- data["cloud"] = cloud
935
-
936
- infrastructure = []
937
- if data["uses_docker"]: infrastructure.append("docker")
938
- if cloud != "none": infrastructure.append(cloud)
939
- data["infrastructure"] = infrastructure
940
-
941
- CI_CD = ["github-actions", "gitlab-ci", "circleci", "jenkins", "none"]
942
- data["ci_cd"] = ask("CI/CD", default=(scanned or {}).get("ci_cd", "github-actions"), choices=CI_CD)
943
-
944
- # ── Testing ───────────────────────────────────────────────────────────
945
- section("Testing")
946
-
947
- data["test_framework"] = ask("Test framework", default=(scanned or {}).get("test_framework", "auto-detect"), choices=TEST_FRAMEWORKS_ALL)
948
-
949
- # ── Conventions ───────────────────────────────────────────────────────
950
- section("Coding Conventions")
951
-
952
- data["max_file_lines"] = ask("Max lines per file before splitting", default=(scanned or {}).get("max_file_lines", "300"))
953
- data["code_style"] = ask("Code style / linter", default=(scanned or {}).get("code_style", "project-standard"),
954
- choices=["eslint", "prettier", "ruff", "gofmt", "pint", "standard", "project-standard"])
955
-
956
- # ── Optional fields ───────────────────────────────────────────────────
957
- section("Architecture (Optional — press Enter to skip)")
958
-
959
- data["architecture_notes"] = ask("Key architecture notes", default=(scanned or {}).get("architecture_notes", "<!-- Add notes -->"))
960
- data["current_phase"] = ask("Current phase", default=(scanned or {}).get("current_phase", "Initial setup"))
961
- data["next_phase"] = ask("Next planned phase", default=(scanned or {}).get("next_phase", "—"))
962
- data["known_issues"] = ask("Known issues / tasks", default=(scanned or {}).get("known_issues", "<!-- None yet -->"))
963
-
964
- data["project_name"] = data.get("project_name", (scanned or {}).get("project_name", Path.cwd().name))
965
- data["description"] = data.get("description", (scanned or {}).get("description", "A software project"))
966
- data["project_type"] = data.get("project_type", (scanned or {}).get("project_type", "web-app"))
967
- data["primary_language"] = data.get("primary_language", (scanned or {}).get("primary_language", "typescript"))
968
- data["frameworks"] = data.get("frameworks", (scanned or {}).get("frameworks", []))
969
- data["database"] = data.get("database", (scanned or {}).get("database", "none"))
970
- data["package_manager"] = data.get("package_manager", (scanned or {}).get("package_manager", "auto-detect"))
971
- data["uses_docker"] = data.get("uses_docker", (scanned or {}).get("uses_docker", True))
972
- data["cloud"] = data.get("cloud", (scanned or {}).get("cloud", "none"))
973
- data["test_framework"] = data.get("test_framework", (scanned or {}).get("test_framework", "auto-detect"))
974
- data["max_file_lines"] = data.get("max_file_lines", (scanned or {}).get("max_file_lines", "300"))
975
- data["code_style"] = data.get("code_style", (scanned or {}).get("code_style", "project-standard"))
976
- data["framework_details"] = data.get("framework_details", (scanned or {}).get("framework_details", []))
977
- data["scan_evidence"] = data.get("scan_evidence", (scanned or {}).get("scan_evidence", []))
978
- data["app_root"] = data.get("app_root", (scanned or {}).get("app_root", "."))
979
- data["context_status"] = data.get("context_status", "baseline")
980
- data["needs_ai_refinement"] = data.get("needs_ai_refinement", False)
981
- data["last_refined_at"] = data.get("last_refined_at", "not-yet-refined")
982
- data["architecture_notes"] = data.get("architecture_notes", (scanned or {}).get("architecture_notes", "<!-- Add notes -->"))
983
- data["current_phase"] = data.get("current_phase", (scanned or {}).get("current_phase", "Initial setup"))
984
- data["next_phase"] = data.get("next_phase", (scanned or {}).get("next_phase", "—"))
985
- data["known_issues"] = data.get("known_issues", (scanned or {}).get("known_issues", "<!-- None yet -->"))
986
- data["infrastructure"] = []
987
- if data["uses_docker"]: data["infrastructure"].append("docker")
988
- if data["cloud"] != "none": data["infrastructure"].append(data["cloud"])
989
-
990
- if not args.yes:
991
- data["needs_ai_refinement"] = ask_bool("Should the agent refine PROJECT_CONTEXT.md on first session start?", default=use_scan)
992
-
993
- # ── Generate files ────────────────────────────────────────────────────
994
- section("Generating Files")
995
-
996
- # PROJECT_CONTEXT.md (always)
997
- context_content = generate_project_context(data)
998
- output_path.write_text(context_content, encoding="utf-8")
999
- print(f" {green('✓')} {bold('PROJECT_CONTEXT.md')} generated")
1000
-
1001
- # CLAUDE.md (for Claude Code)
1002
- if args.platform in ("claude", "all"):
1003
- claude_path = Path("CLAUDE.md")
1004
- claude_path.write_text(generate_claude_md(data), encoding="utf-8")
1005
- print(f" {green('✓')} {bold('CLAUDE.md')} generated (Claude Code)")
1006
-
1007
- # AGENTS.md (for Codex / AGENTS-compatible editors)
1008
- if args.platform in ("codex", "all"):
1009
- agents_path = Path("AGENTS.md")
1010
- agents_path.write_text(generate_agents_md(data), encoding="utf-8")
1011
- print(f" {green('✓')} {bold('AGENTS.md')} generated (Codex)")
1012
-
1013
- ai_refine_path = None
1014
- if data.get("needs_ai_refinement"):
1015
- ai_refine_path = Path("AI_CONTEXT_REFINEMENT.md")
1016
- ai_refine_path.write_text(generate_ai_context_refinement_prompt(data), encoding="utf-8")
1017
- print(f" {green('✓')} {bold('AI_CONTEXT_REFINEMENT.md')} generated")
1018
-
1019
- # Summary
1020
- auto_skills = resolve_skills(data)
1021
- print(f"""
1022
- {bold(green('━━ Done! ━━'))}
1023
-
1024
- {bold('Project:')} {data['project_name']}
1025
- {bold('Stack:')} {data['primary_language']} | {', '.join(data['frameworks']) or 'no framework'}
1026
- {bold('Infra:')} {', '.join(data['infrastructure']) or 'none'}
1027
-
1028
- {bold('Auto-resolved skills:')}
1029
- {chr(10).join(f' · {s}' for s in auto_skills)}
1030
-
1031
- {bold('Generated files:')}
1032
- · {bold(str(output_path.resolve()))}
1033
- · {bold(str(Path("CLAUDE.md").resolve())) if args.platform in ("claude", "all") else 'CLAUDE.md not generated for this platform'}
1034
- · {bold(str(Path("AGENTS.md").resolve())) if args.platform in ("codex", "all") else 'AGENTS.md not generated for this platform'}
1035
- · {bold(str(ai_refine_path.resolve())) if ai_refine_path else 'AI_CONTEXT_REFINEMENT.md not generated'}
1036
-
1037
- {bold('Next steps:')}
1038
- 1. Review {bold('PROJECT_CONTEXT.md')} first and add architecture notes
1039
- 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"}
1040
- 3. Open project in your AI editor (Antigravity / Claude Code / Codex)
1041
- 4. The agent will read PROJECT_CONTEXT.md on session start
1042
-
1043
- {dim(' Tip: Run with --platform all to also generate CLAUDE.md and AGENTS.md')}
1044
- """)
1045
-
1046
- if __name__ == "__main__":
1047
- main()