kodu 2.2.0 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -3
- package/bin/kodu.js +40 -0
- package/package.json +12 -67
- package/scripts/install.js +68 -0
- package/scripts/postinstall.js +22 -0
- package/AGENTS.md +0 -214
- package/__tests__/core/fs/fs.service.test.ts +0 -72
- package/__tests__/core/registry/registry.service.test.ts +0 -82
- package/__tests__/shared/cleaner/cleaner.service.test.ts +0 -102
- package/__tests__/shared/git/git.service.test.ts +0 -84
- package/__tests__/shared/runbook/runbook.service.test.ts +0 -104
- package/__tests__/shared/tokenizer/tokenizer.service.test.ts +0 -45
- package/biome.json +0 -50
- package/dist/package.json +0 -96
- package/dist/src/app.module.d.ts +0 -2
- package/dist/src/app.module.js +0 -42
- package/dist/src/app.module.js.map +0 -1
- package/dist/src/commands/clean/clean.command.d.ts +0 -37
- package/dist/src/commands/clean/clean.command.js +0 -240
- package/dist/src/commands/clean/clean.command.js.map +0 -1
- package/dist/src/commands/clean/clean.module.d.ts +0 -2
- package/dist/src/commands/clean/clean.module.js +0 -26
- package/dist/src/commands/clean/clean.module.js.map +0 -1
- package/dist/src/commands/init/init.command.d.ts +0 -10
- package/dist/src/commands/init/init.command.js +0 -96
- package/dist/src/commands/init/init.command.js.map +0 -1
- package/dist/src/commands/init/init.module.d.ts +0 -2
- package/dist/src/commands/init/init.module.js +0 -22
- package/dist/src/commands/init/init.module.js.map +0 -1
- package/dist/src/commands/ops/ops-add.command.d.ts +0 -18
- package/dist/src/commands/ops/ops-add.command.js +0 -102
- package/dist/src/commands/ops/ops-add.command.js.map +0 -1
- package/dist/src/commands/ops/ops-init.command.d.ts +0 -22
- package/dist/src/commands/ops/ops-init.command.js +0 -130
- package/dist/src/commands/ops/ops-init.command.js.map +0 -1
- package/dist/src/commands/ops/ops-list.command.d.ts +0 -12
- package/dist/src/commands/ops/ops-list.command.js +0 -73
- package/dist/src/commands/ops/ops-list.command.js.map +0 -1
- package/dist/src/commands/ops/ops-path.command.d.ts +0 -9
- package/dist/src/commands/ops/ops-path.command.js +0 -52
- package/dist/src/commands/ops/ops-path.command.js.map +0 -1
- package/dist/src/commands/ops/ops-runbook.command.d.ts +0 -12
- package/dist/src/commands/ops/ops-runbook.command.js +0 -81
- package/dist/src/commands/ops/ops-runbook.command.js.map +0 -1
- package/dist/src/commands/ops/ops-status.command.d.ts +0 -11
- package/dist/src/commands/ops/ops-status.command.js +0 -62
- package/dist/src/commands/ops/ops-status.command.js.map +0 -1
- package/dist/src/commands/ops/ops-use.command.d.ts +0 -12
- package/dist/src/commands/ops/ops-use.command.js +0 -76
- package/dist/src/commands/ops/ops-use.command.js.map +0 -1
- package/dist/src/commands/ops/ops.command.d.ts +0 -7
- package/dist/src/commands/ops/ops.command.js +0 -56
- package/dist/src/commands/ops/ops.command.js.map +0 -1
- package/dist/src/commands/ops/ops.helpers.d.ts +0 -2
- package/dist/src/commands/ops/ops.helpers.js +0 -11
- package/dist/src/commands/ops/ops.helpers.js.map +0 -1
- package/dist/src/commands/ops/ops.module.d.ts +0 -2
- package/dist/src/commands/ops/ops.module.js +0 -36
- package/dist/src/commands/ops/ops.module.js.map +0 -1
- package/dist/src/commands/pack/pack.command.d.ts +0 -51
- package/dist/src/commands/pack/pack.command.js +0 -355
- package/dist/src/commands/pack/pack.command.js.map +0 -1
- package/dist/src/commands/pack/pack.module.d.ts +0 -2
- package/dist/src/commands/pack/pack.module.js +0 -27
- package/dist/src/commands/pack/pack.module.js.map +0 -1
- package/dist/src/core/config/config.module.d.ts +0 -2
- package/dist/src/core/config/config.module.js +0 -23
- package/dist/src/core/config/config.module.js.map +0 -1
- package/dist/src/core/config/config.schema.d.ts +0 -19
- package/dist/src/core/config/config.schema.js +0 -56
- package/dist/src/core/config/config.schema.js.map +0 -1
- package/dist/src/core/config/config.service.d.ts +0 -7
- package/dist/src/core/config/config.service.js +0 -49
- package/dist/src/core/config/config.service.js.map +0 -1
- package/dist/src/core/config/prompt.service.d.ts +0 -10
- package/dist/src/core/config/prompt.service.js +0 -80
- package/dist/src/core/config/prompt.service.js.map +0 -1
- package/dist/src/core/file-system/fs.module.d.ts +0 -2
- package/dist/src/core/file-system/fs.module.js +0 -21
- package/dist/src/core/file-system/fs.module.js.map +0 -1
- package/dist/src/core/file-system/fs.service.d.ts +0 -27
- package/dist/src/core/file-system/fs.service.js +0 -203
- package/dist/src/core/file-system/fs.service.js.map +0 -1
- package/dist/src/core/registry/registry.module.d.ts +0 -2
- package/dist/src/core/registry/registry.module.js +0 -22
- package/dist/src/core/registry/registry.module.js.map +0 -1
- package/dist/src/core/registry/registry.schema.d.ts +0 -24
- package/dist/src/core/registry/registry.schema.js +0 -21
- package/dist/src/core/registry/registry.schema.js.map +0 -1
- package/dist/src/core/registry/registry.service.d.ts +0 -16
- package/dist/src/core/registry/registry.service.js +0 -91
- package/dist/src/core/registry/registry.service.js.map +0 -1
- package/dist/src/core/ui/ui.module.d.ts +0 -2
- package/dist/src/core/ui/ui.module.js +0 -22
- package/dist/src/core/ui/ui.module.js.map +0 -1
- package/dist/src/core/ui/ui.service.d.ts +0 -22
- package/dist/src/core/ui/ui.service.js +0 -43
- package/dist/src/core/ui/ui.service.js.map +0 -1
- package/dist/src/main.d.ts +0 -2
- package/dist/src/main.js +0 -16
- package/dist/src/main.js.map +0 -1
- package/dist/src/shared/cleaner/cleaner.service.d.ts +0 -23
- package/dist/src/shared/cleaner/cleaner.service.js +0 -223
- package/dist/src/shared/cleaner/cleaner.service.js.map +0 -1
- package/dist/src/shared/cleaner/cleaner.types.d.ts +0 -21
- package/dist/src/shared/cleaner/cleaner.types.js +0 -3
- package/dist/src/shared/cleaner/cleaner.types.js.map +0 -1
- package/dist/src/shared/constants.d.ts +0 -4
- package/dist/src/shared/constants.js +0 -113
- package/dist/src/shared/constants.js.map +0 -1
- package/dist/src/shared/deps/deps.module.d.ts +0 -2
- package/dist/src/shared/deps/deps.module.js +0 -21
- package/dist/src/shared/deps/deps.module.js.map +0 -1
- package/dist/src/shared/deps/deps.service.d.ts +0 -15
- package/dist/src/shared/deps/deps.service.js +0 -114
- package/dist/src/shared/deps/deps.service.js.map +0 -1
- package/dist/src/shared/git/git.module.d.ts +0 -2
- package/dist/src/shared/git/git.module.js +0 -21
- package/dist/src/shared/git/git.module.js.map +0 -1
- package/dist/src/shared/git/git.service.d.ts +0 -5
- package/dist/src/shared/git/git.service.js +0 -56
- package/dist/src/shared/git/git.service.js.map +0 -1
- package/dist/src/shared/runbook/runbook.module.d.ts +0 -2
- package/dist/src/shared/runbook/runbook.module.js +0 -22
- package/dist/src/shared/runbook/runbook.module.js.map +0 -1
- package/dist/src/shared/runbook/runbook.service.d.ts +0 -20
- package/dist/src/shared/runbook/runbook.service.js +0 -118
- package/dist/src/shared/runbook/runbook.service.js.map +0 -1
- package/dist/src/shared/runbook/runbook.templates.d.ts +0 -6
- package/dist/src/shared/runbook/runbook.templates.js +0 -49
- package/dist/src/shared/runbook/runbook.templates.js.map +0 -1
- package/dist/src/shared/tokenizer/tokenizer.module.d.ts +0 -2
- package/dist/src/shared/tokenizer/tokenizer.module.js +0 -21
- package/dist/src/shared/tokenizer/tokenizer.module.js.map +0 -1
- package/dist/src/shared/tokenizer/tokenizer.service.d.ts +0 -10
- package/dist/src/shared/tokenizer/tokenizer.service.js +0 -36
- package/dist/src/shared/tokenizer/tokenizer.service.js.map +0 -1
- package/dist/tsconfig.build.tsbuildinfo +0 -1
- package/docs/todo.md +0 -7
- package/knip.json +0 -10
- package/kodu.json +0 -63
- package/kodu.schema.json +0 -100
- package/lefthook.yml +0 -11
- package/nest-cli.json +0 -8
- package/registry.schema.json +0 -39
- package/scripts/generate-json-schema.ts +0 -27
- package/skills/ac/SKILL.md +0 -239
- package/skills/al/SKILL.md +0 -98
- package/skills/audit/SKILL.md +0 -205
- package/skills/audit/audit-baseline-template.yml +0 -188
- package/skills/audit/runtime-detect.md +0 -64
- package/skills/audit/stacks/_generic.md +0 -41
- package/skills/audit/stacks/_registry.md +0 -47
- package/skills/audit/stacks/go.md +0 -66
- package/skills/audit/stacks/java.md +0 -44
- package/skills/audit/stacks/node.md +0 -57
- package/skills/audit/stacks/python.md +0 -45
- package/skills/audit/stacks/rust.md +0 -44
- package/skills/audit-api-contracts/SKILL.md +0 -201
- package/skills/audit-architecture/SKILL.md +0 -200
- package/skills/audit-bugs/SKILL.md +0 -226
- package/skills/audit-concurrency/SKILL.md +0 -197
- package/skills/audit-deployment/SKILL.md +0 -218
- package/skills/audit-docs/SKILL.md +0 -209
- package/skills/audit-errors/SKILL.md +0 -216
- package/skills/audit-logging/SKILL.md +0 -197
- package/skills/audit-matrix/SKILL.md +0 -245
- package/skills/audit-meta/SKILL.md +0 -120
- package/skills/audit-naming/SKILL.md +0 -200
- package/skills/audit-owasp/SKILL.md +0 -223
- package/skills/audit-performance/SKILL.md +0 -199
- package/skills/audit-reinvention/SKILL.md +0 -214
- package/skills/audit-secrets/SKILL.md +0 -198
- package/skills/audit-tests/SKILL.md +0 -210
- package/skills/audit-validation/SKILL.md +0 -206
- package/skills/audit-verify/SKILL.md +0 -139
- package/skills/audit-yagni/SKILL.md +0 -188
- package/skills/doc-gen/SKILL.md +0 -490
- package/skills/doc-gen/scripts/doc_gen.py +0 -911
- package/skills/generate-project-docs/SKILL.md +0 -380
- package/skills/implement-project/SKILL.md +0 -409
- package/skills/liteend-init/SKILL.md +0 -84
- package/skills/litefront-init/SKILL.md +0 -96
- package/skills/litefront-prototype/SKILL.md +0 -484
- package/skills/ops/SKILL.md +0 -94
- package/skills/post-call-task-builder/SKILL.md +0 -419
- package/skills/project-setup-standardizer/SKILL.md +0 -285
- package/skills/skills-best-practices/SKILL.md +0 -415
- package/skills/start/SKILL.md +0 -319
- package/skills/tech-blueprint/SKILL.md +0 -890
- package/skills/tech-blueprint/scripts/blueprint_validator.py +0 -417
- package/src/app.module.ts +0 -29
- package/src/commands/clean/clean.command.ts +0 -235
- package/src/commands/clean/clean.module.ts +0 -13
- package/src/commands/init/init.command.ts +0 -92
- package/src/commands/init/init.module.ts +0 -9
- package/src/commands/ops/ops-add.command.ts +0 -83
- package/src/commands/ops/ops-init.command.ts +0 -125
- package/src/commands/ops/ops-list.command.ts +0 -57
- package/src/commands/ops/ops-path.command.ts +0 -38
- package/src/commands/ops/ops-runbook.command.ts +0 -74
- package/src/commands/ops/ops-status.command.ts +0 -47
- package/src/commands/ops/ops-use.command.ts +0 -76
- package/src/commands/ops/ops.command.ts +0 -42
- package/src/commands/ops/ops.helpers.ts +0 -20
- package/src/commands/ops/ops.module.ts +0 -23
- package/src/commands/pack/pack.command.ts +0 -347
- package/src/commands/pack/pack.module.ts +0 -14
- package/src/core/config/config.module.ts +0 -10
- package/src/core/config/config.schema.ts +0 -58
- package/src/core/config/config.service.ts +0 -43
- package/src/core/config/prompt.service.ts +0 -80
- package/src/core/file-system/fs.module.ts +0 -8
- package/src/core/file-system/fs.service.ts +0 -248
- package/src/core/registry/registry.module.ts +0 -9
- package/src/core/registry/registry.schema.ts +0 -46
- package/src/core/registry/registry.service.ts +0 -128
- package/src/core/ui/ui.module.ts +0 -9
- package/src/core/ui/ui.service.ts +0 -39
- package/src/main.ts +0 -12
- package/src/shared/cleaner/cleaner.service.ts +0 -289
- package/src/shared/cleaner/cleaner.types.ts +0 -23
- package/src/shared/constants.ts +0 -118
- package/src/shared/deps/deps.module.ts +0 -8
- package/src/shared/deps/deps.service.ts +0 -175
- package/src/shared/git/git.module.ts +0 -8
- package/src/shared/git/git.service.ts +0 -47
- package/src/shared/runbook/runbook.module.ts +0 -9
- package/src/shared/runbook/runbook.service.ts +0 -164
- package/src/shared/runbook/runbook.templates.ts +0 -66
- package/src/shared/tokenizer/tokenizer.module.ts +0 -8
- package/src/shared/tokenizer/tokenizer.service.ts +0 -30
- package/tsconfig.build.json +0 -7
- package/tsconfig.json +0 -28
|
@@ -1,417 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
blueprint_validator.py — валидатор технических контрактов проекта.
|
|
4
|
-
|
|
5
|
-
Использование:
|
|
6
|
-
python3 blueprint_validator.py validate "ИмяПроекта" [--output PATH] [--update-mode]
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
import argparse
|
|
10
|
-
import re
|
|
11
|
-
import sys
|
|
12
|
-
from pathlib import Path
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
# ─── Цвета и вывод ────────────────────────────────────────────────────────────
|
|
16
|
-
|
|
17
|
-
class C:
|
|
18
|
-
RED = "\033[0;31m"
|
|
19
|
-
GREEN = "\033[0;32m"
|
|
20
|
-
YELLOW = "\033[1;33m"
|
|
21
|
-
RESET = "\033[0m"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def ok(msg: str) -> None: print(f"{C.GREEN}✓{C.RESET} {msg}")
|
|
25
|
-
def err(msg: str) -> None: print(f"{C.RED}✗{C.RESET} {msg}")
|
|
26
|
-
def warn(msg: str) -> None: print(f"{C.YELLOW}⟳{C.RESET} {msg}")
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
# ─── Константы ────────────────────────────────────────────────────────────────
|
|
30
|
-
|
|
31
|
-
REQUIRED_FILES = [
|
|
32
|
-
"IMPLEMENTATION_GUIDE.md",
|
|
33
|
-
"DATABASE_MODEL.md",
|
|
34
|
-
"API_CONTRACTS.md",
|
|
35
|
-
"ARCHITECTURE.md",
|
|
36
|
-
"TESTING_PLAN.md",
|
|
37
|
-
]
|
|
38
|
-
|
|
39
|
-
_FSD_PATH_RE = re.compile(
|
|
40
|
-
r"src/(entities|features|widgets|pages|shared|app)/|"
|
|
41
|
-
r"app/(entities|features|widgets|pages|shared)/"
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
_LIST_TYPE_RE = re.compile(r":\s*\[\w")
|
|
45
|
-
_PAGINATION_KW_RE = re.compile(r"\b(first|after|limit|offset|page|cursor)\b", re.IGNORECASE)
|
|
46
|
-
_SPEC_REF_RE = re.compile(r"(SPEC\.md|VISION\.md)")
|
|
47
|
-
_PRISMA_MODEL_RE = re.compile(r"model\s+(\w+)\s*\{([\s\S]*?)\n\}", re.MULTILINE)
|
|
48
|
-
_MIGRATION_SEC_RE = re.compile(
|
|
49
|
-
r"^#{1,3}\s*(план миграции|изменения бд|migration plan|db changes|database changes)",
|
|
50
|
-
re.MULTILINE | re.IGNORECASE,
|
|
51
|
-
)
|
|
52
|
-
_IMPL_STACK_RE = re.compile(r"^#{1,3}\s*(стек|stack)\b", re.MULTILINE | re.IGNORECASE)
|
|
53
|
-
_IMPL_DONE_RE = re.compile(
|
|
54
|
-
r"^#{1,3}\s*(что уже реализовано|already implemented|what.s already)",
|
|
55
|
-
re.MULTILINE | re.IGNORECASE,
|
|
56
|
-
)
|
|
57
|
-
_IMPL_LAUNCH_RE = re.compile(
|
|
58
|
-
r"^#{1,3}\s*(локальный запуск|local (setup|run|start)|getting started|quick start)",
|
|
59
|
-
re.MULTILINE | re.IGNORECASE,
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
# ─── Вспомогательные функции ──────────────────────────────────────────────────
|
|
64
|
-
|
|
65
|
-
def _read(path: Path) -> str:
|
|
66
|
-
return path.read_text(encoding="utf-8")
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def _extract_code_block(content: str, lang: str) -> str:
|
|
70
|
-
"""Возвращает содержимое первого блока ```lang ... ```."""
|
|
71
|
-
m = re.search(rf"```{re.escape(lang)}\n([\s\S]*?)```", content)
|
|
72
|
-
return m.group(1) if m else ""
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def _extract_prisma_models(prisma_content: str) -> dict[str, str]:
|
|
76
|
-
"""Возвращает {ИмяМодели: тело_модели} из Prisma-схемы."""
|
|
77
|
-
return {m.group(1): m.group(2) for m in _PRISMA_MODEL_RE.finditer(prisma_content)}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
# ─── Проверки ─────────────────────────────────────────────────────────────────
|
|
81
|
-
|
|
82
|
-
def check_files(blueprint_dir: Path) -> list[str]:
|
|
83
|
-
"""Проверка 1: все обязательные файлы существуют."""
|
|
84
|
-
return [
|
|
85
|
-
f"Файл отсутствует: {f}"
|
|
86
|
-
for f in REQUIRED_FILES
|
|
87
|
-
if not (blueprint_dir / f).exists()
|
|
88
|
-
]
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def check_code_blocks(blueprint_dir: Path) -> list[str]:
|
|
92
|
-
"""Проверка 2: DATABASE_MODEL.md содержит ```prisma, API_CONTRACTS.md — ```graphql."""
|
|
93
|
-
errors: list[str] = []
|
|
94
|
-
db = _read(blueprint_dir / "DATABASE_MODEL.md")
|
|
95
|
-
if not _extract_code_block(db, "prisma"):
|
|
96
|
-
errors.append("DATABASE_MODEL.md: отсутствует блок ```prisma")
|
|
97
|
-
api = _read(blueprint_dir / "API_CONTRACTS.md")
|
|
98
|
-
if not _extract_code_block(api, "graphql"):
|
|
99
|
-
errors.append("API_CONTRACTS.md: отсутствует блок ```graphql")
|
|
100
|
-
return errors
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def check_fsd_paths(blueprint_dir: Path) -> list[str]:
|
|
104
|
-
"""Проверка 3: ARCHITECTURE.md не содержит файловых путей FSD."""
|
|
105
|
-
arch = _read(blueprint_dir / "ARCHITECTURE.md")
|
|
106
|
-
matches = _FSD_PATH_RE.findall(arch)
|
|
107
|
-
if matches:
|
|
108
|
-
return [
|
|
109
|
-
"ARCHITECTURE.md: обнаружены файловые пути FSD. "
|
|
110
|
-
"В архитектурном документе должны быть только логические названия "
|
|
111
|
-
"сущностей и компонентов (не файловые пути)."
|
|
112
|
-
]
|
|
113
|
-
return []
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
def check_model_coverage(blueprint_dir: Path) -> list[str]:
|
|
117
|
-
"""Проверка 4: большинство Prisma-моделей упомянуты в API_CONTRACTS.md."""
|
|
118
|
-
db = _read(blueprint_dir / "DATABASE_MODEL.md")
|
|
119
|
-
api = _read(blueprint_dir / "API_CONTRACTS.md")
|
|
120
|
-
|
|
121
|
-
prisma_block = _extract_code_block(db, "prisma")
|
|
122
|
-
if not prisma_block:
|
|
123
|
-
return []
|
|
124
|
-
|
|
125
|
-
model_names = list(_extract_prisma_models(prisma_block).keys())
|
|
126
|
-
if not model_names:
|
|
127
|
-
return []
|
|
128
|
-
|
|
129
|
-
api_lower = api.lower()
|
|
130
|
-
covered = [m for m in model_names if m.lower() in api_lower]
|
|
131
|
-
ratio = len(covered) / len(model_names)
|
|
132
|
-
|
|
133
|
-
if ratio < 0.5:
|
|
134
|
-
missing = [m for m in model_names if m.lower() not in api_lower]
|
|
135
|
-
return [
|
|
136
|
-
f"Кросс-чек БД/API: {len(covered)}/{len(model_names)} Prisma-моделей "
|
|
137
|
-
f"найдены в API_CONTRACTS.md. Отсутствуют: {', '.join(missing)}"
|
|
138
|
-
]
|
|
139
|
-
return []
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
def check_traceability(blueprint_dir: Path) -> list[str]:
|
|
143
|
-
"""Проверка 5: DATABASE_MODEL.md и API_CONTRACTS.md содержат ссылки на SPEC.md/VISION.md."""
|
|
144
|
-
errors: list[str] = []
|
|
145
|
-
for filename in ("DATABASE_MODEL.md", "API_CONTRACTS.md"):
|
|
146
|
-
content = _read(blueprint_dir / filename)
|
|
147
|
-
if not _SPEC_REF_RE.search(content):
|
|
148
|
-
errors.append(
|
|
149
|
-
f"{filename}: отсутствует трассируемость. "
|
|
150
|
-
"Добавьте комментарии со ссылками на бизнес-требования (SPEC.md или VISION.md)"
|
|
151
|
-
)
|
|
152
|
-
return errors
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
def check_pagination(blueprint_dir: Path) -> list[str]:
|
|
156
|
-
"""
|
|
157
|
-
Проверка 6: поля Query/Mutation, возвращающие списки, имеют аргументы пагинации.
|
|
158
|
-
Ищет только внутри type Query / type Mutation / type Subscription.
|
|
159
|
-
Предупреждения, не ошибки (pagination может быть в обёртке-типе).
|
|
160
|
-
"""
|
|
161
|
-
issues: list[str] = []
|
|
162
|
-
api = _read(blueprint_dir / "API_CONTRACTS.md")
|
|
163
|
-
graphql_block = _extract_code_block(api, "graphql")
|
|
164
|
-
if not graphql_block:
|
|
165
|
-
return issues
|
|
166
|
-
|
|
167
|
-
lines = graphql_block.splitlines()
|
|
168
|
-
in_root_op = False
|
|
169
|
-
current_type: str | None = None
|
|
170
|
-
depth = 0
|
|
171
|
-
|
|
172
|
-
for i, line in enumerate(lines):
|
|
173
|
-
stripped = line.strip()
|
|
174
|
-
|
|
175
|
-
# Определить вход в тип Query/Mutation/Subscription
|
|
176
|
-
m = re.match(r"^type\s+(Query|Mutation|Subscription)\b", stripped)
|
|
177
|
-
if m:
|
|
178
|
-
in_root_op = True
|
|
179
|
-
current_type = m.group(1)
|
|
180
|
-
depth = 0
|
|
181
|
-
|
|
182
|
-
# Обновить глубину вложенности
|
|
183
|
-
depth += stripped.count("{") - stripped.count("}")
|
|
184
|
-
if depth <= 0 and in_root_op:
|
|
185
|
-
in_root_op = False
|
|
186
|
-
current_type = None
|
|
187
|
-
continue
|
|
188
|
-
|
|
189
|
-
if not in_root_op:
|
|
190
|
-
continue
|
|
191
|
-
|
|
192
|
-
# Проверить, возвращает ли строка список
|
|
193
|
-
if not _LIST_TYPE_RE.search(stripped):
|
|
194
|
-
continue
|
|
195
|
-
|
|
196
|
-
# Собрать контекст: до 8 предыдущих строк (для многострочных аргументов)
|
|
197
|
-
ctx = "\n".join(lines[max(0, i - 8) : i + 1])
|
|
198
|
-
if not _PAGINATION_KW_RE.search(ctx):
|
|
199
|
-
name_m = re.search(r"(\w+)\s*[\(:]", stripped)
|
|
200
|
-
field_name = name_m.group(1) if name_m else stripped[:50]
|
|
201
|
-
issues.append(
|
|
202
|
-
f"API_CONTRACTS.md [{current_type}]: поле «{field_name}» возвращает список "
|
|
203
|
-
"без аргументов пагинации — добавьте first/after или limit/offset"
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
return issues
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
def check_prisma_timestamps(blueprint_dir: Path) -> list[str]:
|
|
210
|
-
"""
|
|
211
|
-
Проверка 7: каждая Prisma-модель (кроме join-таблиц с '_' в названии)
|
|
212
|
-
содержит createdAt или updatedAt.
|
|
213
|
-
"""
|
|
214
|
-
errors: list[str] = []
|
|
215
|
-
db = _read(blueprint_dir / "DATABASE_MODEL.md")
|
|
216
|
-
prisma_block = _extract_code_block(db, "prisma")
|
|
217
|
-
if not prisma_block:
|
|
218
|
-
return errors
|
|
219
|
-
|
|
220
|
-
for model_name, body in _extract_prisma_models(prisma_block).items():
|
|
221
|
-
# Join-таблицы (содержат '_' в имени) — пропустить
|
|
222
|
-
if "_" in model_name:
|
|
223
|
-
continue
|
|
224
|
-
if "createdAt" not in body and "updatedAt" not in body:
|
|
225
|
-
errors.append(
|
|
226
|
-
f"DATABASE_MODEL.md: модель «{model_name}» не содержит "
|
|
227
|
-
"обязательных полей createdAt/updatedAt"
|
|
228
|
-
)
|
|
229
|
-
|
|
230
|
-
return errors
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
def check_implementation_guide(blueprint_dir: Path) -> list[str]:
|
|
234
|
-
"""Проверка 8: IMPLEMENTATION_GUIDE.md содержит обязательные разделы."""
|
|
235
|
-
guide_path = blueprint_dir / "IMPLEMENTATION_GUIDE.md"
|
|
236
|
-
if not guide_path.exists():
|
|
237
|
-
return [] # уже зафиксировано в check_files
|
|
238
|
-
content = _read(guide_path)
|
|
239
|
-
errors: list[str] = []
|
|
240
|
-
if not _IMPL_STACK_RE.search(content):
|
|
241
|
-
errors.append("IMPLEMENTATION_GUIDE.md: отсутствует раздел «## Стек»")
|
|
242
|
-
if not _IMPL_DONE_RE.search(content):
|
|
243
|
-
errors.append("IMPLEMENTATION_GUIDE.md: отсутствует раздел «## Что уже реализовано»")
|
|
244
|
-
if not _IMPL_LAUNCH_RE.search(content):
|
|
245
|
-
errors.append("IMPLEMENTATION_GUIDE.md: отсутствует раздел «## Локальный запуск»")
|
|
246
|
-
return errors
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
def check_migration_section(blueprint_dir: Path) -> list[str]:
|
|
250
|
-
"""Проверка 8 (--update-mode): DATABASE_MODEL.md содержит раздел «План миграции»."""
|
|
251
|
-
db = _read(blueprint_dir / "DATABASE_MODEL.md")
|
|
252
|
-
if not _MIGRATION_SEC_RE.search(db):
|
|
253
|
-
return [
|
|
254
|
-
"DATABASE_MODEL.md: в режиме обновления требуется описать план миграции. "
|
|
255
|
-
"Добавьте раздел «## План миграции» или «## Изменения БД»"
|
|
256
|
-
]
|
|
257
|
-
return []
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
# ─── Команда validate ──────────────────────────────────────────────────────────
|
|
261
|
-
|
|
262
|
-
def cmd_validate(args: argparse.Namespace) -> int:
|
|
263
|
-
blueprint_dir = Path(args.output) / args.name / "3_TECH_BLUEPRINT"
|
|
264
|
-
|
|
265
|
-
if not blueprint_dir.exists():
|
|
266
|
-
err(f"Папка не найдена: {blueprint_dir}")
|
|
267
|
-
print(f" Ожидается: docs/{args.name}/3_TECH_BLUEPRINT/")
|
|
268
|
-
return 1
|
|
269
|
-
|
|
270
|
-
print(f"\nВалидация технического блюпринта: {args.name}\n")
|
|
271
|
-
|
|
272
|
-
all_errors: list[str] = []
|
|
273
|
-
all_warnings: list[str] = []
|
|
274
|
-
|
|
275
|
-
# ── 1. Наличие файлов ─────────────────────────────────────────────────────
|
|
276
|
-
print(f"{C.YELLOW}── Наличие файлов ──────────────────────────{C.RESET}")
|
|
277
|
-
file_errors = check_files(blueprint_dir)
|
|
278
|
-
for e in file_errors:
|
|
279
|
-
err(e)
|
|
280
|
-
if file_errors:
|
|
281
|
-
print(
|
|
282
|
-
f"\n{C.RED}Критические ошибки: создайте все обязательные файлы "
|
|
283
|
-
f"прежде чем продолжить.{C.RESET}\n"
|
|
284
|
-
)
|
|
285
|
-
return 1
|
|
286
|
-
ok("Все обязательные файлы присутствуют")
|
|
287
|
-
|
|
288
|
-
# ── 2. Блоки кода ─────────────────────────────────────────────────────────
|
|
289
|
-
print(f"\n{C.YELLOW}── Структура контента ──────────────────────{C.RESET}")
|
|
290
|
-
block_errors = check_code_blocks(blueprint_dir)
|
|
291
|
-
for e in block_errors:
|
|
292
|
-
err(e)
|
|
293
|
-
all_errors.extend(block_errors)
|
|
294
|
-
if not block_errors:
|
|
295
|
-
ok("Блоки кода (```prisma, ```graphql) найдены")
|
|
296
|
-
|
|
297
|
-
# ── 3. FSD-пути ───────────────────────────────────────────────────────────
|
|
298
|
-
print(f"\n{C.YELLOW}── FSD Paths Check ─────────────────────────{C.RESET}")
|
|
299
|
-
fsd_errors = check_fsd_paths(blueprint_dir)
|
|
300
|
-
for e in fsd_errors:
|
|
301
|
-
err(e)
|
|
302
|
-
all_errors.extend(fsd_errors)
|
|
303
|
-
if not fsd_errors:
|
|
304
|
-
ok("ARCHITECTURE.md не содержит файловых путей FSD")
|
|
305
|
-
|
|
306
|
-
# ── 4. Кросс-чек моделей ──────────────────────────────────────────────────
|
|
307
|
-
print(f"\n{C.YELLOW}── Кросс-чек БД / GraphQL ──────────────────{C.RESET}")
|
|
308
|
-
if not block_errors:
|
|
309
|
-
coverage_errors = check_model_coverage(blueprint_dir)
|
|
310
|
-
for e in coverage_errors:
|
|
311
|
-
err(e)
|
|
312
|
-
all_errors.extend(coverage_errors)
|
|
313
|
-
if not coverage_errors:
|
|
314
|
-
ok("Большинство Prisma-моделей упомянуты в API_CONTRACTS.md")
|
|
315
|
-
|
|
316
|
-
# ── 5. Трассируемость ─────────────────────────────────────────────────────
|
|
317
|
-
print(f"\n{C.YELLOW}── Трассируемость ──────────────────────────{C.RESET}")
|
|
318
|
-
trace_errors = check_traceability(blueprint_dir)
|
|
319
|
-
for e in trace_errors:
|
|
320
|
-
err(e)
|
|
321
|
-
all_errors.extend(trace_errors)
|
|
322
|
-
if not trace_errors:
|
|
323
|
-
ok("Ссылки на бизнес-требования найдены в DB-модели и API-контрактах")
|
|
324
|
-
|
|
325
|
-
# ── 6. Пагинация GraphQL ──────────────────────────────────────────────────
|
|
326
|
-
print(f"\n{C.YELLOW}── Пагинация GraphQL ───────────────────────{C.RESET}")
|
|
327
|
-
if not block_errors:
|
|
328
|
-
pag_issues = check_pagination(blueprint_dir)
|
|
329
|
-
for issue in pag_issues:
|
|
330
|
-
warn(issue)
|
|
331
|
-
all_warnings.extend(pag_issues)
|
|
332
|
-
if not pag_issues:
|
|
333
|
-
ok("Все списочные поля Query/Mutation имеют аргументы пагинации")
|
|
334
|
-
|
|
335
|
-
# ── 7. Технические поля Prisma ────────────────────────────────────────────
|
|
336
|
-
print(f"\n{C.YELLOW}── Технические поля Prisma ─────────────────{C.RESET}")
|
|
337
|
-
if not block_errors:
|
|
338
|
-
ts_errors = check_prisma_timestamps(blueprint_dir)
|
|
339
|
-
for e in ts_errors:
|
|
340
|
-
err(e)
|
|
341
|
-
all_errors.extend(ts_errors)
|
|
342
|
-
if not ts_errors:
|
|
343
|
-
ok("Все модели (кроме join-таблиц) содержат createdAt/updatedAt")
|
|
344
|
-
|
|
345
|
-
# ── 8. IMPLEMENTATION_GUIDE.md ────────────────────────────────────────────
|
|
346
|
-
print(f"\n{C.YELLOW}── IMPLEMENTATION_GUIDE.md ─────────────────{C.RESET}")
|
|
347
|
-
guide_errors = check_implementation_guide(blueprint_dir)
|
|
348
|
-
for e in guide_errors:
|
|
349
|
-
err(e)
|
|
350
|
-
all_errors.extend(guide_errors)
|
|
351
|
-
if not guide_errors:
|
|
352
|
-
ok("IMPLEMENTATION_GUIDE.md содержит обязательные разделы")
|
|
353
|
-
|
|
354
|
-
# ── 9. Режим обновления ───────────────────────────────────────────────────
|
|
355
|
-
if args.update_mode:
|
|
356
|
-
print(f"\n{C.YELLOW}── Режим обновления (миграция) ─────────────{C.RESET}")
|
|
357
|
-
mig_errors = check_migration_section(blueprint_dir)
|
|
358
|
-
for e in mig_errors:
|
|
359
|
-
err(e)
|
|
360
|
-
all_errors.extend(mig_errors)
|
|
361
|
-
if not mig_errors:
|
|
362
|
-
ok("Раздел с планом миграции найден")
|
|
363
|
-
|
|
364
|
-
# ── Итог ──────────────────────────────────────────────────────────────────
|
|
365
|
-
print()
|
|
366
|
-
if all_errors:
|
|
367
|
-
warn_suffix = f", предупреждений: {len(all_warnings)}" if all_warnings else ""
|
|
368
|
-
print(
|
|
369
|
-
f"{C.RED}Итог: {len(all_errors)} ошибок{warn_suffix}. "
|
|
370
|
-
f"Устраните ошибки перед продолжением.{C.RESET}\n"
|
|
371
|
-
)
|
|
372
|
-
return 1
|
|
373
|
-
|
|
374
|
-
if all_warnings:
|
|
375
|
-
print(
|
|
376
|
-
f"{C.YELLOW}Итог: ошибок нет, предупреждений: {len(all_warnings)}. "
|
|
377
|
-
f"Рекомендуется исправить.{C.RESET}\n"
|
|
378
|
-
)
|
|
379
|
-
else:
|
|
380
|
-
print(
|
|
381
|
-
f"{C.GREEN}✅ Блюпринт «{args.name}» прошёл полную проверку.{C.RESET}\n"
|
|
382
|
-
)
|
|
383
|
-
|
|
384
|
-
return 0
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
# ─── CLI ──────────────────────────────────────────────────────────────────────
|
|
388
|
-
|
|
389
|
-
def build_parser() -> argparse.ArgumentParser:
|
|
390
|
-
parser = argparse.ArgumentParser(
|
|
391
|
-
prog="blueprint_validator.py",
|
|
392
|
-
description="Валидатор технических контрактов (tech-blueprint)",
|
|
393
|
-
)
|
|
394
|
-
sub = parser.add_subparsers(dest="command", required=True)
|
|
395
|
-
|
|
396
|
-
val = sub.add_parser("validate", help="Проверить папку 3_TECH_BLUEPRINT/")
|
|
397
|
-
val.add_argument("name", metavar="ИмяПроекта")
|
|
398
|
-
val.add_argument(
|
|
399
|
-
"--output", default="./blueprint", metavar="PATH",
|
|
400
|
-
help="Корневая папка проектов (по умолчанию: ./blueprint)",
|
|
401
|
-
)
|
|
402
|
-
val.add_argument(
|
|
403
|
-
"--update-mode", action="store_true",
|
|
404
|
-
help="Режим обновления: требует раздел «План миграции» в DATABASE_MODEL.md",
|
|
405
|
-
)
|
|
406
|
-
|
|
407
|
-
return parser
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
def main() -> None:
|
|
411
|
-
parser = build_parser()
|
|
412
|
-
args = parser.parse_args()
|
|
413
|
-
sys.exit(cmd_validate(args))
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
if __name__ == "__main__":
|
|
417
|
-
main()
|
package/src/app.module.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { Module } from '@nestjs/common';
|
|
2
|
-
import { CleanModule } from './commands/clean/clean.module';
|
|
3
|
-
import { InitModule } from './commands/init/init.module';
|
|
4
|
-
import { OpsModule } from './commands/ops/ops.module';
|
|
5
|
-
import { PackModule } from './commands/pack/pack.module';
|
|
6
|
-
import { ConfigModule } from './core/config/config.module';
|
|
7
|
-
import { FsModule } from './core/file-system/fs.module';
|
|
8
|
-
import { RegistryModule } from './core/registry/registry.module';
|
|
9
|
-
import { UiModule } from './core/ui/ui.module';
|
|
10
|
-
import { GitModule } from './shared/git/git.module';
|
|
11
|
-
import { RunbookModule } from './shared/runbook/runbook.module';
|
|
12
|
-
import { TokenizerModule } from './shared/tokenizer/tokenizer.module';
|
|
13
|
-
|
|
14
|
-
@Module({
|
|
15
|
-
imports: [
|
|
16
|
-
ConfigModule,
|
|
17
|
-
UiModule,
|
|
18
|
-
FsModule,
|
|
19
|
-
GitModule,
|
|
20
|
-
TokenizerModule,
|
|
21
|
-
RegistryModule,
|
|
22
|
-
RunbookModule,
|
|
23
|
-
InitModule,
|
|
24
|
-
PackModule,
|
|
25
|
-
CleanModule,
|
|
26
|
-
OpsModule,
|
|
27
|
-
],
|
|
28
|
-
})
|
|
29
|
-
export class AppModule {}
|
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
import { createReadStream } from 'node:fs';
|
|
2
|
-
import { Command, CommandRunner, Option } from 'nest-commander';
|
|
3
|
-
import { ConfigService } from '../../core/config/config.service';
|
|
4
|
-
import { FsService } from '../../core/file-system/fs.service';
|
|
5
|
-
import { UiService } from '../../core/ui/ui.service';
|
|
6
|
-
import { CleanerService } from '../../shared/cleaner/cleaner.service';
|
|
7
|
-
import { GitService } from '../../shared/git/git.service';
|
|
8
|
-
|
|
9
|
-
const SUPPORTED_EXTENSIONS = /\.(ts|tsx|js|jsx|mjs|cjs|html|htm)$/i;
|
|
10
|
-
|
|
11
|
-
type CleanOptions = {
|
|
12
|
-
dryRun?: boolean;
|
|
13
|
-
changed?: boolean;
|
|
14
|
-
staged?: boolean;
|
|
15
|
-
backup?: boolean;
|
|
16
|
-
noJsdoc?: boolean;
|
|
17
|
-
verbose?: boolean;
|
|
18
|
-
stdin?: boolean;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
@Command({ name: 'clean', description: 'Remove comments from code' })
|
|
22
|
-
export class CleanCommand extends CommandRunner {
|
|
23
|
-
constructor(
|
|
24
|
-
private readonly ui: UiService,
|
|
25
|
-
private readonly fsService: FsService,
|
|
26
|
-
private readonly cleaner: CleanerService,
|
|
27
|
-
private readonly config: ConfigService,
|
|
28
|
-
private readonly git: GitService,
|
|
29
|
-
) {
|
|
30
|
-
super();
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
@Option({ flags: '-d, --dry-run', description: 'Show what will be removed' })
|
|
34
|
-
parseDryRun(): boolean {
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
@Option({
|
|
39
|
-
flags: '-c, --changed',
|
|
40
|
-
description: 'Clean only git-changed files (staged + unstaged + untracked)',
|
|
41
|
-
})
|
|
42
|
-
parseChanged(): boolean {
|
|
43
|
-
return true;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
@Option({
|
|
47
|
-
flags: '-s, --staged',
|
|
48
|
-
description: 'Clean only git-staged files',
|
|
49
|
-
})
|
|
50
|
-
parseStaged(): boolean {
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
@Option({
|
|
55
|
-
flags: '-b, --backup',
|
|
56
|
-
description: 'Save originals to .kodu/backup/ before modifying',
|
|
57
|
-
})
|
|
58
|
-
parseBackup(): boolean {
|
|
59
|
-
return true;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
@Option({
|
|
63
|
-
flags: '-n, --no-jsdoc',
|
|
64
|
-
description: 'Remove JSDoc comments (overrides config keepJSDoc)',
|
|
65
|
-
})
|
|
66
|
-
parseNoJsdoc(): boolean {
|
|
67
|
-
return true;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
@Option({
|
|
71
|
-
flags: '-v, --verbose',
|
|
72
|
-
description: 'Show all removed comments in dry-run (not just first 3)',
|
|
73
|
-
})
|
|
74
|
-
parseVerbose(): boolean {
|
|
75
|
-
return true;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
@Option({
|
|
79
|
-
flags: '--stdin',
|
|
80
|
-
description: 'Read from stdin, write cleaned result to stdout',
|
|
81
|
-
})
|
|
82
|
-
parseStdin(): boolean {
|
|
83
|
-
return true;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async run(inputs: string[], options: CleanOptions = {}): Promise<void> {
|
|
87
|
-
if (options.stdin) {
|
|
88
|
-
await this.runStdin(options);
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const spinner = this.ui
|
|
93
|
-
.createSpinner({ text: this.buildSpinnerText(options) })
|
|
94
|
-
.start();
|
|
95
|
-
|
|
96
|
-
try {
|
|
97
|
-
const { cleaner: cleanerConfig, packer } = this.config.getConfig();
|
|
98
|
-
const ignorePatterns = [
|
|
99
|
-
...(packer.ignore ?? []),
|
|
100
|
-
...(cleanerConfig.ignore ?? []),
|
|
101
|
-
];
|
|
102
|
-
const allFiles = await this.fsService.findProjectFiles({
|
|
103
|
-
useGitignore: cleanerConfig.useGitignore,
|
|
104
|
-
ignore: ignorePatterns,
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
const targets = await this.collectTargets(allFiles, inputs, options);
|
|
108
|
-
|
|
109
|
-
if (targets.length === 0) {
|
|
110
|
-
const msg = this.noFilesMessage(options);
|
|
111
|
-
spinner.stop(msg);
|
|
112
|
-
this.ui.log.warn(msg);
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const summary = await this.cleaner.cleanFiles(targets, {
|
|
117
|
-
dryRun: options.dryRun,
|
|
118
|
-
backup: options.backup,
|
|
119
|
-
keepJSDoc: options.noJsdoc ? false : undefined,
|
|
120
|
-
onProgress: (current, total) => {
|
|
121
|
-
spinner.text = `${this.buildSpinnerText(options)} (${current}/${total})`;
|
|
122
|
-
},
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
spinner.success(
|
|
126
|
-
options.dryRun ? 'Analysis complete' : 'Cleaning complete',
|
|
127
|
-
);
|
|
128
|
-
|
|
129
|
-
const bytesSaved = summary.bytesBefore - summary.bytesAfter;
|
|
130
|
-
const tokensSaved = Math.round(bytesSaved / 4);
|
|
131
|
-
|
|
132
|
-
if (options.dryRun) {
|
|
133
|
-
this.ui.log.info(
|
|
134
|
-
`Files affected: ${summary.filesChanged}/${summary.filesProcessed}, comments: ${summary.commentsRemoved}`,
|
|
135
|
-
);
|
|
136
|
-
this.ui.log.info(`Bytes saved: ${bytesSaved} (~${tokensSaved} tokens)`);
|
|
137
|
-
|
|
138
|
-
const limit = options.verbose ? Number.POSITIVE_INFINITY : 3;
|
|
139
|
-
for (const report of summary.reports.filter((r) => r.removed > 0)) {
|
|
140
|
-
const previews = options.verbose
|
|
141
|
-
? report.previews
|
|
142
|
-
: report.previews.slice(0, limit);
|
|
143
|
-
const more =
|
|
144
|
-
!options.verbose && report.previews.length > limit
|
|
145
|
-
? ` +${report.previews.length - limit} more`
|
|
146
|
-
: '';
|
|
147
|
-
this.ui.log.info(
|
|
148
|
-
` ${report.file} (${report.removed}): ${previews.map((p) => `"${p}"`).join(', ')}${more}`,
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
this.ui.log.success(
|
|
155
|
-
`Files cleaned: ${summary.filesChanged}, comments removed: ${summary.commentsRemoved}`,
|
|
156
|
-
);
|
|
157
|
-
this.ui.log.info(`Bytes saved: ${bytesSaved} (~${tokensSaved} tokens)`);
|
|
158
|
-
|
|
159
|
-
if (options.backup && summary.filesChanged > 0) {
|
|
160
|
-
this.ui.log.info('Originals backed up to .kodu/backup/');
|
|
161
|
-
}
|
|
162
|
-
} catch (error) {
|
|
163
|
-
spinner.error('Error during cleaning');
|
|
164
|
-
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
165
|
-
this.ui.log.error(message);
|
|
166
|
-
process.exitCode = 1;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
private async runStdin(options: CleanOptions): Promise<void> {
|
|
171
|
-
try {
|
|
172
|
-
const input = await this.readStdin();
|
|
173
|
-
const cleaned = this.cleaner.cleanContent(
|
|
174
|
-
'stdin.ts',
|
|
175
|
-
input,
|
|
176
|
-
options.noJsdoc ? false : undefined,
|
|
177
|
-
);
|
|
178
|
-
process.stdout.write(cleaned);
|
|
179
|
-
} catch (error) {
|
|
180
|
-
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
181
|
-
this.ui.log.error(message);
|
|
182
|
-
process.exitCode = 1;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
private readStdin(): Promise<string> {
|
|
187
|
-
return new Promise((resolve, reject) => {
|
|
188
|
-
const chunks: Buffer[] = [];
|
|
189
|
-
const stream = createReadStream('/dev/stdin');
|
|
190
|
-
stream.on('data', (chunk) =>
|
|
191
|
-
chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk),
|
|
192
|
-
);
|
|
193
|
-
stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
|
|
194
|
-
stream.on('error', reject);
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
private buildSpinnerText(options: CleanOptions): string {
|
|
199
|
-
if (options.staged) return 'Cleaning staged files...';
|
|
200
|
-
if (options.changed) return 'Cleaning changed files...';
|
|
201
|
-
return options.dryRun ? 'Analysing...' : 'Cleaning...';
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
private noFilesMessage(options: CleanOptions): string {
|
|
205
|
-
if (options.staged) return 'No staged files to clean.';
|
|
206
|
-
if (options.changed) return 'No changed files to clean.';
|
|
207
|
-
return 'No files to clean.';
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
private async collectTargets(
|
|
211
|
-
allFiles: string[],
|
|
212
|
-
inputs: string[],
|
|
213
|
-
options: CleanOptions,
|
|
214
|
-
): Promise<string[]> {
|
|
215
|
-
const supported = allFiles.filter((f) => SUPPORTED_EXTENSIONS.test(f));
|
|
216
|
-
|
|
217
|
-
if (inputs.length > 0) {
|
|
218
|
-
return supported.filter((f) =>
|
|
219
|
-
inputs.some((i) => f === i || f.startsWith(`${i.replace(/\/$/, '')}/`)),
|
|
220
|
-
);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
if (options.staged) {
|
|
224
|
-
const staged = new Set(await this.git.getStagedFiles());
|
|
225
|
-
return supported.filter((f) => staged.has(f));
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (options.changed) {
|
|
229
|
-
const changed = new Set(await this.git.getChangedFiles());
|
|
230
|
-
return supported.filter((f) => changed.has(f));
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
return supported;
|
|
234
|
-
}
|
|
235
|
-
}
|