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/.agent/agents/frontend-specialist.md +2 -2
- package/.agent/agents/mobile-developer.md +4 -4
- package/.agent/docs/architecture.md +22 -0
- package/.agent/docs/conventions.md +21 -0
- package/.agent/docs/project.md +40 -0
- package/.agent/docs/workflow.md +24 -0
- package/.agent/rules/GEMINI.md +31 -193
- package/.agent/skills/desktop-design/SKILL.md +25 -25
- package/.agent/skills/project-onboarding/SKILL.md +42 -62
- package/.agent/skills/react-frontend/SKILL.md +25 -25
- package/LICENSE +21 -21
- package/README.md +37 -71
- package/agents/frontend-specialist.md +2 -2
- package/agents/mobile-developer.md +4 -4
- package/bin/cli.js +1276 -1409
- package/kit/PROMPT_RECIPE.md +30 -52
- package/kit/README.md +29 -33
- package/package.json +51 -51
- package/rules/GEMINI.md +43 -207
- package/skills/desktop-design/SKILL.md +25 -25
- package/skills/project-onboarding/SKILL.md +42 -62
- package/skills/react-frontend/SKILL.md +25 -25
- package/kit/PROJECT_CONTEXT.template.md +0 -35
- package/scripts/init.py +0 -1047
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()
|