kortext 1.0.0 → 2.2.3
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/AGENTS.md +28 -0
- package/README.md +98 -0
- package/agents/backend-developer.md +132 -0
- package/agents/compliance-expert.md +106 -0
- package/agents/copywriter.md +122 -0
- package/agents/db-admin.md +117 -0
- package/agents/delivery-manager.md +121 -0
- package/agents/designer.md +108 -0
- package/agents/devops-engineer.md +141 -0
- package/agents/engineering-manager.md +208 -0
- package/agents/frontend-developer.md +174 -0
- package/agents/growth-expert.md +115 -0
- package/agents/operation-manager.md +122 -0
- package/agents/product-manager.md +114 -0
- package/agents/qa-engineer.md +126 -0
- package/agents/security-engineer.md +119 -0
- package/hooks/audit-logger.sh +25 -0
- package/hooks/auto-locker.sh +74 -0
- package/hooks/auto-unlocker.sh +17 -0
- package/hooks/backlog-sync-guard.sh +12 -0
- package/hooks/branch-guard.sh +32 -0
- package/hooks/commit-msg-guard.sh +43 -0
- package/hooks/git-pre-commit.sh +76 -0
- package/hooks/git-pre-push.sh +17 -0
- package/hooks/handover-guard.sh +33 -0
- package/hooks/kortext-init.sh +194 -0
- package/hooks/kortext-lib.sh +151 -0
- package/hooks/lint-guard.sh +50 -0
- package/hooks/secret-scanner.sh +89 -0
- package/hooks/size-guard.sh +48 -0
- package/hooks/snapshot-guard.sh +39 -0
- package/hooks/write-guard.sh +77 -0
- package/package.json +23 -6
- package/rules/behavior.md +252 -0
- package/rules/branching.md +82 -0
- package/rules/commands.md +112 -0
- package/rules/emergency.md +160 -0
- package/rules/mcp.md +47 -0
- package/rules/models.md +93 -0
- package/scripts/kortext-backlog-add.py +123 -0
- package/scripts/kortext-backlog-health.py +112 -0
- package/scripts/kortext-backlog-sync.py +117 -0
- package/scripts/kortext-bulk-plan.py +63 -0
- package/scripts/kortext-cli.py +376 -0
- package/scripts/kortext-consistency-check.py +74 -0
- package/scripts/kortext-context-check.py +146 -0
- package/scripts/kortext-handover.py +107 -0
- package/scripts/kortext-item-check.py +61 -0
- package/scripts/kortext-item-start.py +70 -0
- package/scripts/kortext-item-transition.py +78 -0
- package/scripts/kortext-lock.py +174 -0
- package/scripts/kortext-session-start.py +127 -0
- package/scripts/lock_kortext.sh +34 -0
- package/settings/.claude-settings.template.json +48 -0
- package/settings/CHANGELOG.md +176 -0
- package/settings/INTEGRATION-MAP.md +276 -0
- package/settings/README.md +255 -0
- package/settings/USER-GUIDE.md +502 -0
- package/settings/VERSION +1 -0
- package/settings/config.md +8 -0
- package/settings/runtime-adapters.md +104 -0
- package/skills/backend-developer/.gitkeep +0 -0
- package/skills/compliance-expert/.gitkeep +0 -0
- package/skills/copywriter/.gitkeep +0 -0
- package/skills/db-admin/.gitkeep +0 -0
- package/skills/delivery-manager/.gitkeep +0 -0
- package/skills/designer/.gitkeep +0 -0
- package/skills/devops-engineer/.gitkeep +0 -0
- package/skills/engineering-manager/.gitkeep +0 -0
- package/skills/frontend-developer/.gitkeep +0 -0
- package/skills/growth-expert/.gitkeep +0 -0
- package/skills/operation-manager/.gitkeep +0 -0
- package/skills/product-manager/.gitkeep +0 -0
- package/skills/qa-engineer/.gitkeep +0 -0
- package/skills/security-engineer/.gitkeep +0 -0
- package/workflows/00-kortext-setup.md +53 -0
- package/workflows/01a-analysis-pipeline.md +117 -0
- package/workflows/01b-onboarding-pipeline.md +110 -0
- package/workflows/02-planning-pipeline.md +146 -0
- package/workflows/02b-spike-workflow.md +120 -0
- package/workflows/03-environment-setup.md +99 -0
- package/workflows/04-development-cycle.md +203 -0
- package/workflows/05-test-cycle.md +123 -0
- package/workflows/06-deployment-cycle.md +224 -0
- package/workflows/07-rollback-pipeline.md +203 -0
- package/workflows/08-hotfix-pipeline.md +196 -0
- package/workflows/09-maintenance-cycle.md +133 -0
- package/workspace/archive/.gitkeep +0 -0
- package/workspace/backups/.gitkeep +0 -0
- package/workspace/memory/backlog/.gitkeep +0 -0
- package/workspace/memory/backlog/BXX-[bug-name].md +2 -0
- package/workspace/memory/backlog/DXX-[debt-name].md +2 -0
- package/workspace/memory/backlog/README.md +31 -0
- package/workspace/memory/backlog/TXX-[task-name].md +2 -0
- package/workspace/memory/backlog/debt-dashboard.md +3 -0
- package/workspace/memory/backlog/epic-dashboard.md +10 -0
- package/workspace/memory/backlog/version-dashboard.md +7 -0
- package/workspace/memory/context/.gitkeep +0 -0
- package/workspace/memory/context/README.md +35 -0
- package/workspace/memory/context/[agent-name]-active.md +2 -0
- package/workspace/memory/decisions.md +17 -0
- package/workspace/memory/handover.md +40 -0
- package/workspace/memory/learned.md +21 -0
- package/workspace/references/access.md +47 -0
- package/workspace/references/api-reference.md +59 -0
- package/workspace/references/blueprint.md +29 -0
- package/workspace/references/content-strategy.md +39 -0
- package/workspace/references/db-schema.md +51 -0
- package/workspace/references/design-system.md +107 -0
- package/workspace/references/dictionary.md +42 -0
- package/workspace/references/file-system.md +42 -0
- package/workspace/references/growth-strategy.md +46 -0
- package/workspace/references/legal-strategy.md +35 -0
- package/workspace/references/required-skills.md +11 -0
- package/workspace/references/security-rules.md +34 -0
- package/workspace/references/tech-stack.md +49 -0
- package/workspace/references/test-strategy.md +48 -0
- package/workspace/reports/analysis-reports.md +40 -0
- package/workspace/reports/content-reports.md +29 -0
- package/workspace/reports/delivery-reports.md +26 -0
- package/workspace/reports/growth-reports.md +9 -0
- package/workspace/reports/legal-reports.md +29 -0
- package/workspace/reports/product-requirements.md +28 -0
- package/workspace/reports/release-notes.md +20 -0
- package/workspace/reports/security-reports.md +27 -0
- package/workspace/reports/status-reports.md +48 -0
- package/workspace/reports/tech-requirements.md +30 -0
- package/workspace/reports/test-reports.md +61 -0
- package/workspace/templates/BXX-[bug-name].md +34 -0
- package/workspace/templates/DXX-[debt-name].md +35 -0
- package/workspace/templates/EXX-[epic-name].md +48 -0
- package/workspace/templates/HXX-[hotfix-name].md +73 -0
- package/workspace/templates/SXX-[spike-name].md +51 -0
- package/workspace/templates/TXX-[task-name].md +34 -0
- package/workspace/templates/[agent-name]-active.md +4 -0
- package/workspace/templates/[original-name]_[YYYY-MM-DD_HHMMSS].md +5 -0
- package/workspace/templates/adr-XXX.md +66 -0
- package/workspace/templates/debt-dashboard.md +10 -0
- package/workspace/templates/epic-dashboard.md +10 -0
- package/workspace/templates/version-dashboard.md +10 -0
package/rules/models.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Model Konfigürasyonu
|
|
2
|
+
|
|
3
|
+
Bu dosya, personanın görev türüne göre hangi model profilini kullanacağını merkezi olarak tanımlar. Persona dosyalarında doğrudan model adı yazılmaz; persona dosyaları yalnızca görev kategorisini belirtir.
|
|
4
|
+
|
|
5
|
+
## Görev Kategorileri
|
|
6
|
+
|
|
7
|
+
| Kategori | Kullanım Alanı | Varsayılan Profil | Fallback Profil |
|
|
8
|
+
| :--- | :--- | :--- | :--- |
|
|
9
|
+
| `deep-research` | Araştırma, analiz, mimari tasarım, strateji, karmaşık kod üretimi, güvenlik değerlendirmesi | `high-reasoning` | `standard-reasoning` |
|
|
10
|
+
| `routine` | Format düzenleme, standart güncelleme, raporlama, basit denetim, düşük riskli rutin işler | `fast-reasoning` | `standard-reasoning` |
|
|
11
|
+
|
|
12
|
+
## Model Profilleri
|
|
13
|
+
|
|
14
|
+
| Profil | Beklenen Özellik | Kullanım |
|
|
15
|
+
| :--- | :--- | :--- |
|
|
16
|
+
| `high-reasoning` | En güçlü muhakeme, uzun bağlam, karmaşık analiz | Kritik teknik kararlar ve çok adımlı üretim işleri |
|
|
17
|
+
| `standard-reasoning` | Dengeli muhakeme ve hız | Fallback veya orta karmaşıklıktaki işler |
|
|
18
|
+
| `fast-reasoning` | Hızlı cevap, düşük maliyet | Rutin raporlama ve basit kontrol işleri |
|
|
19
|
+
|
|
20
|
+
## Rol → Kategori Ataması
|
|
21
|
+
|
|
22
|
+
| Persona | Varsayılan Kategori | Not |
|
|
23
|
+
| :--- | :--- | :--- |
|
|
24
|
+
| `+operation-manager` | `routine` | Orkestrasyon, durum takibi, raporlama |
|
|
25
|
+
| `+product-manager` | `deep-research` | Ürün gereksinimi, kapsam ve önceliklendirme |
|
|
26
|
+
| `+engineering-manager` | `deep-research` | Mimari, teknik karar, code review |
|
|
27
|
+
| `+delivery-manager` | `routine` | Release koordinasyonu ve teslimat raporları |
|
|
28
|
+
| `+backend-developer` | `deep-research` | Backend uygulama ve servis mantığı |
|
|
29
|
+
| `+frontend-developer` | `deep-research` | UI uygulama ve frontend mimarisi |
|
|
30
|
+
| `+db-admin` | `deep-research` | Veri modeli, migration, performans |
|
|
31
|
+
| `+devops-engineer` | `routine` | CI/CD, ortam, deployment operasyonları |
|
|
32
|
+
| `+qa-engineer` | `routine` | Test yürütme, doğrulama, raporlama |
|
|
33
|
+
| `+security-engineer` | `deep-research` | Güvenlik analizi, risk ve zafiyet değerlendirmesi |
|
|
34
|
+
| `+designer` | `deep-research` | Tasarım sistemi, UX/UI değerlendirmesi |
|
|
35
|
+
| `+copywriter` | `deep-research` | İçerik stratejisi, marka dili, mikro metin |
|
|
36
|
+
| `+growth-expert` | `deep-research` | SEO/GEO, analitik, büyüme stratejisi |
|
|
37
|
+
| `+compliance-expert` | `deep-research` | KVKK/GDPR ve yasal değerlendirme |
|
|
38
|
+
|
|
39
|
+
## Sağlayıcı Seçimi
|
|
40
|
+
|
|
41
|
+
Gerçek model ID'leri sağlayıcıya ve kurulu ortama göre değişebilir. Bu nedenle model ID'leri persona dosyalarına yazılmaz.
|
|
42
|
+
|
|
43
|
+
## Resmi Model Kaynakları
|
|
44
|
+
|
|
45
|
+
Model seçimi veya model güncellemesi yapılmadan önce sağlayıcının resmi model dokümantasyonu kontrol edilir.
|
|
46
|
+
|
|
47
|
+
| Sağlayıcı | Resmi Kaynak |
|
|
48
|
+
| :--- | :--- |
|
|
49
|
+
| Anthropic / Claude | https://docs.anthropic.com/en/docs/about-claude/models/overview |
|
|
50
|
+
| Google / Gemini | https://ai.google.dev/gemini-api/docs/models |
|
|
51
|
+
| OpenAI | https://developers.openai.com/api/docs/models |
|
|
52
|
+
|
|
53
|
+
## Güncellik Kuralı
|
|
54
|
+
|
|
55
|
+
- +prime "en güncel", "latest" veya belirli bir sağlayıcıdaki yeni model ailesini isterse ajan resmi model dokümanını kontrol etmeden model önermemelidir.
|
|
56
|
+
- Varsayılan tercih +prime aksi yönde belirtmedikçe en güncel uygun modeldir.
|
|
57
|
+
- Production kullanımında model ID sabitlenmesi gerekiyorsa ilgili snapshot/stable ID `workspace/references/access.md` veya `workspace/references/tech-stack.md` içinde ayrıca belgelenir.
|
|
58
|
+
- Bir sağlayıcının "latest" alias'ı ile sabit snapshot ID'si farklı amaçlara hizmet ediyorsa bu fark raporda açıkça belirtilir.
|
|
59
|
+
|
|
60
|
+
Model seçimi yapılırken:
|
|
61
|
+
|
|
62
|
+
1. Önce görevin kategorisi belirlenir.
|
|
63
|
+
2. Resmi model kaynağı kontrol edilir.
|
|
64
|
+
3. Kategoriye karşılık gelen varsayılan profil seçilir.
|
|
65
|
+
4. Mevcut ortamda bu profile karşılık gelen en güncel uygun model kullanılır.
|
|
66
|
+
5. Model erişilemezse fallback profil kullanılır.
|
|
67
|
+
6. Fallback kullanımı `workspace/reports/status-reports.md` veya ilgili görev raporuna not edilir.
|
|
68
|
+
|
|
69
|
+
## Fallback Politikası
|
|
70
|
+
|
|
71
|
+
- Fallback modeli aynı görevi tamamlamak için yeterliyse işlem devam eder.
|
|
72
|
+
- Fallback kalite riski oluşturuyorsa görev `Blocked` yapılır ve +operation-manager'a eskale edilir.
|
|
73
|
+
- `deep-research` görevlerinde fallback kullanıldıysa sonuç kalitesi özellikle belirtilir.
|
|
74
|
+
- API/LLM kesintilerinde retry ve fallback sırası `rules/emergency.md` içindeki Self-Healing Protocol'e göre yürütülür.
|
|
75
|
+
|
|
76
|
+
## Persona Dosyalarında Kullanım
|
|
77
|
+
|
|
78
|
+
Persona dosyalarının `instructions` bölümünde her görev adımında model adı yerine kategori referansı kullanılır:
|
|
79
|
+
|
|
80
|
+
```markdown
|
|
81
|
+
### 1. Araştırma Protokolü
|
|
82
|
+
**Kategori:** `deep-research`
|
|
83
|
+
|
|
84
|
+
### 2. Rutin Güncelleme
|
|
85
|
+
**Kategori:** `routine`
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Güncelleme Politikası
|
|
89
|
+
|
|
90
|
+
- Model profili veya kategori değişikliklerinde yalnızca bu dosya güncellenir.
|
|
91
|
+
- Gerçek sağlayıcı/model ID eşleşmeleri proje ortamına göre `workspace/references/tech-stack.md` veya `workspace/references/access.md` içinde belgelenir.
|
|
92
|
+
- Model değişikliği önerisi +operation-manager tarafından yapılır.
|
|
93
|
+
- Stratejik model değişikliği +prime onayı gerektirir.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
BACKLOG_ROOT = Path("workspace/memory/backlog")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _atomic_commit(message: str, paths: list[str]) -> None:
|
|
15
|
+
# Kortext atomic operation: dosyaları add edip commit'ler.
|
|
16
|
+
if not shutil.which("git"):
|
|
17
|
+
return
|
|
18
|
+
try:
|
|
19
|
+
subprocess.run(["git", "add", "--", *paths], check=True, capture_output=True)
|
|
20
|
+
subprocess.run(
|
|
21
|
+
["git", "commit", "-m", message, "--", *paths],
|
|
22
|
+
check=True,
|
|
23
|
+
capture_output=True,
|
|
24
|
+
)
|
|
25
|
+
except subprocess.CalledProcessError:
|
|
26
|
+
return
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def slugify(title: str) -> str:
|
|
30
|
+
return "-".join(title.lower().split())
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def dashboard_path(kind: str) -> Path:
|
|
34
|
+
mapping = {
|
|
35
|
+
"version": BACKLOG_ROOT / "version-dashboard.md",
|
|
36
|
+
"epic": BACKLOG_ROOT / "epic-dashboard.md",
|
|
37
|
+
"debt": BACKLOG_ROOT / "debt-dashboard.md",
|
|
38
|
+
}
|
|
39
|
+
return mapping[kind]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def append_row(path: Path, row: str) -> None:
|
|
43
|
+
content = path.read_text() if path.exists() else ""
|
|
44
|
+
if row not in content:
|
|
45
|
+
if content and not content.endswith("\n"):
|
|
46
|
+
content += "\n"
|
|
47
|
+
content += row + "\n"
|
|
48
|
+
path.write_text(content)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def build_item_content(item_type: str, item_id: str, title: str, persona: str, epic: str | None) -> str:
|
|
52
|
+
date_str = datetime.now().strftime("%Y.%m.%d-%H:%M")
|
|
53
|
+
epic_line = f"> **Epic:** {epic}\n" if epic else ""
|
|
54
|
+
return f"""# {item_id}: {title}
|
|
55
|
+
{epic_line}> **Assignee:** [{persona}]
|
|
56
|
+
> **Model:** [selected-ai-model]
|
|
57
|
+
> **Status:** To Do
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Description
|
|
62
|
+
{title} açıklaması.
|
|
63
|
+
|
|
64
|
+
## Acceptance Criteria
|
|
65
|
+
- [ ] Code review
|
|
66
|
+
- [ ] Quality control
|
|
67
|
+
- [ ] Security check
|
|
68
|
+
|
|
69
|
+
## Dependencies
|
|
70
|
+
- [task-id] - [task-name]
|
|
71
|
+
|
|
72
|
+
## Work Log
|
|
73
|
+
- **[{date_str}] ({persona}):** {item_type.capitalize()} oluşturuldu.
|
|
74
|
+
|
|
75
|
+
## Decisions
|
|
76
|
+
- [alınan karar]
|
|
77
|
+
|
|
78
|
+
## Notes
|
|
79
|
+
- [notlar]
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def main() -> None:
|
|
84
|
+
parser = argparse.ArgumentParser(description="Kortext Backlog Add Tool")
|
|
85
|
+
parser.add_argument("--type", choices=["task", "bug", "debt"], required=True)
|
|
86
|
+
parser.add_argument("--id", required=True, help="e.g. T01, B01, D01")
|
|
87
|
+
parser.add_argument("--title", required=True)
|
|
88
|
+
parser.add_argument("--persona", default="+persona")
|
|
89
|
+
parser.add_argument("--epic", help="e.g. E01-auth (required for task and bug)")
|
|
90
|
+
parser.add_argument("--no-commit", action="store_true", help="Atomic git commit'i atla")
|
|
91
|
+
args = parser.parse_args()
|
|
92
|
+
|
|
93
|
+
if args.type in {"task", "bug"} and not args.epic:
|
|
94
|
+
raise SystemExit("Hata: task ve bug türleri için --epic zorunludur.")
|
|
95
|
+
|
|
96
|
+
BACKLOG_ROOT.mkdir(parents=True, exist_ok=True)
|
|
97
|
+
file_path = BACKLOG_ROOT / f"{args.id}-{slugify(args.title)}.md"
|
|
98
|
+
file_path.write_text(build_item_content(args.type, args.id, args.title, args.persona, args.epic))
|
|
99
|
+
|
|
100
|
+
if args.type in {"task", "bug"}:
|
|
101
|
+
dashboard = dashboard_path("epic")
|
|
102
|
+
append_row(
|
|
103
|
+
dashboard,
|
|
104
|
+
f"| {args.id} | [{args.title}](./{file_path.name}) | {args.epic} | {args.persona} | To Do |",
|
|
105
|
+
)
|
|
106
|
+
else:
|
|
107
|
+
dashboard = dashboard_path("debt")
|
|
108
|
+
append_row(
|
|
109
|
+
dashboard,
|
|
110
|
+
f"| {args.id} | [{args.title}](./{file_path.name}) | {args.persona} | To Do |",
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
print(f"✅ {args.type.capitalize()} oluşturuldu: {file_path}")
|
|
114
|
+
|
|
115
|
+
if not args.no_commit:
|
|
116
|
+
_atomic_commit(
|
|
117
|
+
f"chore(kortext): add {args.type} {args.id} [skip ci]",
|
|
118
|
+
[str(file_path), str(dashboard)],
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
if __name__ == "__main__":
|
|
123
|
+
main()
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Kortext Backlog Health
|
|
4
|
+
----------------------
|
|
5
|
+
Backlog'un genel sağlık durumunu raporlar. Blocker itemlar, stale In Progress'ler,
|
|
6
|
+
review bekleyenler ve hazır (başlanabilir) itemlar özetlenir.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import re
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
17
|
+
BACKLOG_DIR = ROOT / "workspace/memory/backlog"
|
|
18
|
+
# In Progress backlog item'larının stale eşiği. Env var ile override edilebilir.
|
|
19
|
+
STALE_HOURS = int(os.environ.get("KORTEXT_STALE_HOURS_BACKLOG", "48"))
|
|
20
|
+
|
|
21
|
+
STATUS_PATTERN = re.compile(r"\*\*Status:\*\*\s*(.+)")
|
|
22
|
+
BLOCKED_BY_PATTERN = re.compile(r"\*\*Blocked By:\*\*\s*(.+)")
|
|
23
|
+
RISK_PATTERN = re.compile(r"\*\*Risk:\*\*\s*(Blocker|High|Critical)", re.IGNORECASE)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def parse_items() -> list[dict]:
|
|
27
|
+
items = []
|
|
28
|
+
if not BACKLOG_DIR.exists():
|
|
29
|
+
return items
|
|
30
|
+
for f in sorted(BACKLOG_DIR.glob("[TBD]*.md")):
|
|
31
|
+
text = f.read_text(errors="ignore")
|
|
32
|
+
status_m = STATUS_PATTERN.search(text)
|
|
33
|
+
blocked_m = BLOCKED_BY_PATTERN.search(text)
|
|
34
|
+
risk_m = RISK_PATTERN.search(text)
|
|
35
|
+
|
|
36
|
+
status = status_m.group(1).strip() if status_m else "?"
|
|
37
|
+
has_blocker = blocked_m is not None and blocked_m.group(1).strip() not in ("", "-", "Yok")
|
|
38
|
+
is_high_risk = risk_m is not None
|
|
39
|
+
mtime = datetime.fromtimestamp(f.stat().st_mtime)
|
|
40
|
+
age_hours = (datetime.now() - mtime).total_seconds() / 3600
|
|
41
|
+
|
|
42
|
+
items.append({
|
|
43
|
+
"id": f.stem,
|
|
44
|
+
"status": status,
|
|
45
|
+
"has_blocker": has_blocker,
|
|
46
|
+
"is_high_risk": is_high_risk,
|
|
47
|
+
"age_hours": round(age_hours, 1),
|
|
48
|
+
"stale": status == "In Progress" and age_hours > STALE_HOURS,
|
|
49
|
+
})
|
|
50
|
+
return items
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def compute_score(items: list[dict]) -> tuple[int, int]:
|
|
54
|
+
"""Basit sağlık skoru: toplam item sayısı üzerinden sorunlu itemları düşür."""
|
|
55
|
+
total = len(items)
|
|
56
|
+
if total == 0:
|
|
57
|
+
return 100, 100
|
|
58
|
+
problems = sum(1 for i in items if i["stale"] or i["has_blocker"])
|
|
59
|
+
score = max(0, round((1 - problems / total) * 100))
|
|
60
|
+
return score, 100
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def main() -> None:
|
|
64
|
+
items = parse_items()
|
|
65
|
+
now = datetime.now().strftime("%d.%m.%Y %H:%M")
|
|
66
|
+
|
|
67
|
+
# Grupla
|
|
68
|
+
todo = [i for i in items if i["status"] == "To Do" and not i["has_blocker"]]
|
|
69
|
+
in_progress = [i for i in items if i["status"] == "In Progress"]
|
|
70
|
+
review = [i for i in items if i["status"] == "Review"]
|
|
71
|
+
blocked = [i for i in items if i["has_blocker"] or i["status"] == "Blocked"]
|
|
72
|
+
stale = [i for i in items if i["stale"]]
|
|
73
|
+
done = [i for i in items if i["status"] == "Done"]
|
|
74
|
+
score, max_score = compute_score(items)
|
|
75
|
+
|
|
76
|
+
print("=" * 60)
|
|
77
|
+
print(f" KORTEXT BACKLOG HEALTH — {now}")
|
|
78
|
+
print("=" * 60)
|
|
79
|
+
print(f"\n Toplam item : {len(items)}")
|
|
80
|
+
print(f" ✅ Başlanabilir : {len(todo)}")
|
|
81
|
+
print(f" 🔄 In Progress : {len(in_progress)}")
|
|
82
|
+
print(f" 👀 Review bekliyor : {len(review)}")
|
|
83
|
+
print(f" 🚫 Bloklu : {len(blocked)}")
|
|
84
|
+
print(f" ⚠️ Stale ({STALE_HOURS}s+) : {len(stale)}")
|
|
85
|
+
print(f" ✔️ Done : {len(done)}")
|
|
86
|
+
|
|
87
|
+
if blocked:
|
|
88
|
+
print(f"\n 🚫 BLOKLANAN İTEMLAR:")
|
|
89
|
+
for i in blocked:
|
|
90
|
+
print(f" • {i['id']} ({i['status']})")
|
|
91
|
+
|
|
92
|
+
if stale:
|
|
93
|
+
print(f"\n ⚠️ STALE İTEMLAR:")
|
|
94
|
+
for i in stale:
|
|
95
|
+
print(f" • {i['id']} — {i['age_hours']} saat önce güncellendi")
|
|
96
|
+
|
|
97
|
+
if review:
|
|
98
|
+
print(f"\n 👀 REVIEW BEKLEYENLER (+prime aksiyonu gerekebilir):")
|
|
99
|
+
for i in review:
|
|
100
|
+
print(f" • {i['id']}")
|
|
101
|
+
|
|
102
|
+
bar_filled = int(score / 5)
|
|
103
|
+
bar = "█" * bar_filled + "░" * (20 - bar_filled)
|
|
104
|
+
print(f"\n Sağlık Skoru: {score}/{max_score} [{bar}]")
|
|
105
|
+
print("=" * 60)
|
|
106
|
+
|
|
107
|
+
if score < 50:
|
|
108
|
+
raise SystemExit(1)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
if __name__ == "__main__":
|
|
112
|
+
main()
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
BACKLOG_ROOT = Path("workspace/memory/backlog")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class Item:
|
|
14
|
+
item_id: str
|
|
15
|
+
title: str
|
|
16
|
+
status: str
|
|
17
|
+
path: Path
|
|
18
|
+
epic: str | None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def parse_item(path: Path) -> Item:
|
|
22
|
+
content = path.read_text()
|
|
23
|
+
header = re.search(r"^# ([TBD]\d+): (.+)$", content, re.M)
|
|
24
|
+
status = re.search(r"> \*\*Status:\*\* (.+)", content)
|
|
25
|
+
epic = re.search(r"> \*\*Epic:\*\* (.+)", content)
|
|
26
|
+
if not header or not status:
|
|
27
|
+
raise ValueError(f"Geçersiz item şablonu: {path}")
|
|
28
|
+
return Item(
|
|
29
|
+
item_id=header.group(1),
|
|
30
|
+
title=header.group(2),
|
|
31
|
+
status=status.group(1).strip(),
|
|
32
|
+
path=path,
|
|
33
|
+
epic=epic.group(1).strip() if epic else None,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def actual_items() -> list[Item]:
|
|
38
|
+
items: list[Item] = []
|
|
39
|
+
for path in BACKLOG_ROOT.glob("*.md"):
|
|
40
|
+
if path.name.endswith("dashboard.md"):
|
|
41
|
+
continue
|
|
42
|
+
if re.match(r"^[TBD]\d+-", path.name):
|
|
43
|
+
items.append(parse_item(path))
|
|
44
|
+
return items
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def expected_epic_rows(items: list[Item]) -> set[str]:
|
|
48
|
+
return {
|
|
49
|
+
f"| {item.item_id} | [{item.title}](./{item.path.name}) | {item.epic} | "
|
|
50
|
+
f"{read_assignee(item.path)} | {item.status} |"
|
|
51
|
+
for item in items
|
|
52
|
+
if item.item_id.startswith(("T", "B"))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def expected_debt_rows(items: list[Item]) -> set[str]:
|
|
57
|
+
return {
|
|
58
|
+
f"| {item.item_id} | [{item.title}](./{item.path.name}) | "
|
|
59
|
+
f"{read_assignee(item.path)} | {item.status} |"
|
|
60
|
+
for item in items
|
|
61
|
+
if item.item_id.startswith("D")
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def read_assignee(path: Path) -> str:
|
|
66
|
+
match = re.search(r"> \*\*Assignee:\*\* \[(.+)\]", path.read_text())
|
|
67
|
+
return match.group(1) if match else "+persona"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def actual_rows(path: Path) -> set[str]:
|
|
71
|
+
if not path.exists():
|
|
72
|
+
return set()
|
|
73
|
+
return {
|
|
74
|
+
line
|
|
75
|
+
for line in path.read_text().splitlines()
|
|
76
|
+
if re.match(r"^\| [TBD]\d+ \|", line)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def compare(label: str, expected: set[str], actual: set[str]) -> list[str]:
|
|
81
|
+
failures: list[str] = []
|
|
82
|
+
for row in sorted(expected - actual):
|
|
83
|
+
failures.append(f"{label}: eksik satır -> {row}")
|
|
84
|
+
for row in sorted(actual - expected):
|
|
85
|
+
failures.append(f"{label}: beklenmeyen satır -> {row}")
|
|
86
|
+
return failures
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def main() -> None:
|
|
90
|
+
items = actual_items()
|
|
91
|
+
failures: list[str] = []
|
|
92
|
+
failures.extend(
|
|
93
|
+
compare(
|
|
94
|
+
"epic-dashboard.md",
|
|
95
|
+
expected_epic_rows(items),
|
|
96
|
+
actual_rows(BACKLOG_ROOT / "epic-dashboard.md"),
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
failures.extend(
|
|
100
|
+
compare(
|
|
101
|
+
"debt-dashboard.md",
|
|
102
|
+
expected_debt_rows(items),
|
|
103
|
+
actual_rows(BACKLOG_ROOT / "debt-dashboard.md"),
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
if failures:
|
|
108
|
+
print("❌ Backlog dashboard drift tespit edildi:")
|
|
109
|
+
for failure in failures:
|
|
110
|
+
print(f"- {failure}")
|
|
111
|
+
raise SystemExit(1)
|
|
112
|
+
|
|
113
|
+
print("✅ Backlog dashboard'ları item dosyalarıyla uyumlu.")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
if __name__ == "__main__":
|
|
117
|
+
main()
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
SCRIPT_ADD = Path(__file__).with_name("kortext-backlog-add.py")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def run_add_task(item_type: str, epic: str | None, item_id: str, title: str, persona: str) -> None:
|
|
14
|
+
cmd = [
|
|
15
|
+
sys.executable,
|
|
16
|
+
str(SCRIPT_ADD),
|
|
17
|
+
"--type",
|
|
18
|
+
item_type,
|
|
19
|
+
"--id",
|
|
20
|
+
item_id,
|
|
21
|
+
"--title",
|
|
22
|
+
title,
|
|
23
|
+
"--persona",
|
|
24
|
+
persona,
|
|
25
|
+
]
|
|
26
|
+
if epic:
|
|
27
|
+
cmd.extend(["--epic", epic])
|
|
28
|
+
subprocess.run(cmd, check=True)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def main() -> None:
|
|
32
|
+
if len(sys.argv) < 2:
|
|
33
|
+
raise SystemExit("Kullanım: kortext-bulk-plan.py <plan.json>")
|
|
34
|
+
|
|
35
|
+
data = json.loads(Path(sys.argv[1]).read_text())
|
|
36
|
+
|
|
37
|
+
for epic in data.get("epics", []):
|
|
38
|
+
epic_id = epic.get("id")
|
|
39
|
+
for item in epic.get("tasks", []):
|
|
40
|
+
print(f"⌛ Oluşturuluyor: {item.get('id')}...")
|
|
41
|
+
run_add_task(
|
|
42
|
+
item.get("type", "task"),
|
|
43
|
+
epic_id,
|
|
44
|
+
item.get("id"),
|
|
45
|
+
item.get("title"),
|
|
46
|
+
item.get("persona", "+persona"),
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
for debt in data.get("debts", []):
|
|
50
|
+
print(f"⌛ Oluşturuluyor: {debt.get('id')}...")
|
|
51
|
+
run_add_task(
|
|
52
|
+
"debt",
|
|
53
|
+
None,
|
|
54
|
+
debt.get("id"),
|
|
55
|
+
debt.get("title"),
|
|
56
|
+
debt.get("persona", "+persona"),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
print("\n🚀 Toplu planlama başarıyla tamamlandı!")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
if __name__ == "__main__":
|
|
63
|
+
main()
|