From 075f063ce4d5231e52a796f204c3d11162678b5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D1=82=D0=BA=D0=BE=D0=B2=D1=81=D0=BA=D0=B8?= =?UTF-8?q?=D1=85=20=D0=95=D0=B2=D0=B3=D0=B5=D0=BD=D0=B8=D0=B9?= Date: Thu, 5 Nov 2026 18:16:09 +0500 Subject: [PATCH] =?UTF-8?q?analysis:=20flow=5Fstats.py=20=D0=B8=20=D1=8D?= =?UTF-8?q?=D1=82=D0=B0=D0=BB=D0=BE=D0=BD=20=D0=BC=D0=B5=D1=82=D1=80=D0=B8?= =?UTF-8?q?=D0=BA=20dx-flow=20=E2=80=94=20=D0=B6=D1=83=D1=80=D0=BD=D0=B0?= =?UTF-8?q?=D0=BB=20=D1=81=D0=B0=D0=BC=D0=BE=D0=B4=D0=BE=D1=81=D1=82=D0=B0?= =?UTF-8?q?=D1=82=D0=BE=D1=87=D0=B5=D0=BD=20=D0=B4=D0=BB=D1=8F=20=D0=B4?= =?UTF-8?q?=D0=B5=D0=BC=D0=BE=D0=BD=D0=B0-=D0=B0=D0=BD=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D1=82=D0=B8=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dx-flow.md | 125 ++++++++++++++++++++++++++ scripts/flow_stats.py | 202 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 327 insertions(+) create mode 100644 dx-flow.md create mode 100644 scripts/flow_stats.py diff --git a/dx-flow.md b/dx-flow.md new file mode 100644 index 0000000..79753df --- /dev/null +++ b/dx-flow.md @@ -0,0 +1,125 @@ +# Конвейер разработки на агентах: этап → исполнение → проверки (dx-flow) + +> ЖИВАЯ карточка процесса: совершенствуется ретроспективами (dx-retro-1), +> оценки приёмов пересматриваются по новой фактуре, дописывается +> доказательной базой следующих проектов. Первая доказательная база — Kit.RAG +> (2026-10-30…11-01: 3 дня, 90 коммитов, 39 workflow-прогонов ~0,7 млрд +> токенов, ~26 ревизий; гейт 104→607 монотонно, находки ревизий 53→1–2 +> low, ускорение модуль к модулю ×3–5). См. [readme.md](../../readme.md). + +## Цикл этапа (проверен на 3 модулях) + +``` +ТЗ этапа (/stage: «Среда и гейты» B1–B8 + RAG-темы с якорями) + → ИСПОЛНЕНИЕ: субагенты-workflow параллельно по зонам (или агент напрямую) + → ИНТЕГРАТОР: полный гейт (build+test решения), точечные — у исполнителей + → РЕВИЗИЯ СВЕЖИМ КОНТЕКСТОМ: независимый агент без контекста исполнителя; + сверяет код↔канон, якоря перепроверяет живым запросом (dx-dev-8), + числа пересчитывает сам (руками/своим парсером) + → ПРАВКИ по находкам (применить/отклонить с обоснованием) + → КОНТРОЛЬНЫЙ ГЕЙТ (правки ничего не сломали) + → КАСКАД статусов (одно место: readme модуля; plan — строка; handoff — + хвосты/грабли) → GIT-КОММИТ (рецепт — в коммите этапа, B5/D3) +``` + +## Роли проверок + +| Роль | Что проверяет | Ключевое свойство | +|---|---|---| +| Исполнитель | свою зону (точечные тесты) | не видит чужих зон | +| Интегратор | сборка целиком, швы (DI/slnx/фикстуры — dx-dev-3) | единственный полный гейт | +| Ревизор («проверка разработчиком со стороны» + «архитектором») | код↔канон, псевдо-цитаты, якоря, числа | СВЕЖИЙ контекст — главный источник качества; ловил high, невидимые исполнителю | +| Главная сессия | эскалации/развилки, инциденты вне зон | решает владелец | + +Архитектурная проверка решений = протокол dx-dev-1 (до утверждения) + +ревизия шага 0 (module_reqs/спеки как документы, B7). + +## Приёмы (оценки по ретроспективе Kit.RAG; пересматриваются) + +| Приём | Оценка | Комментарий | +|---|---|---| +| Ревизия свежим контекстом + контрольный гейт | ядро, не трогать | находки 53→1–2 low; ловила вне-зонные правки, псевдо-цитаты | +| Авточемод с эскалациями на развилках | высокоэффективно | темп ×3–5 без потери контуров контроля; вводить ПОСЛЕ обкатки конвейера | +| Workflow-исполнители этапов | высокоэффективно | цена: капы/redo-прогоны (6/39), кэш-переигрывания — лечится маркером «ТЗ vN» | +| Honest-skip вместо молчаливого пропуска | высоко | все skip'ы позже сняты живыми прогонами | +| TCP-стабы внешних сервисов + детерминированные векторы | высоко | приёмочные стабильны и доказательны, не зависят от живой LLM | +| docChanges-реестр → легализация одним Изм. на замыкании | высоко (клапан) | симптом дрейфа канона — следить за возрастом | +| Метрики замыкания с независимым пересчётом | высоко | «не замерено честно» — списком, не замалчивать | +| Повторные полные гейты ×3–4 на этап | условно | после введения файл-лока основания нет — точечные + контрольный | +| Дублирование статусов в 3+ документах | НЕ окупилось | ~68% документационных коммитов; одно место + ссылки | + +## Анти-паттерны (пережито, не повторять) + +- Канон утверждается до схождения решений → смена курса после начала + кода (Изм. 5: переделка этапа, 53 находки). Лечение: dx-dev-1 до + утверждения. +- Вне-процессные правки «по ходу» с самовольной атрибуцией → откат, + инцидент. Лечение: изменения — через записи канона/эскалации. +- Закрытый этап «доезжает» потом дважды. Лечение: замыкание = контрольный + гейт + хвосты в handoff, не «потом». + +## Открытые хвосты (совершенствуется) + +- Страж-навык d-proc-guard (авто-проверка чек-листа при генерации ТЗ) — + решение на замыкании И4 по метрикам. +- Фильтр `--tag env:*` для search — среда из текста запроса в фильтр. +- Каноническая нарезка (чанк = законченная единица) — в очереди + легализации; снимет и недетерминизм LLM-нарезки. +- Формы исполнения вне workflow (короткие этапы) — накопить фактуру. + +## Scorecard: оценки, по которым совершенствуется процесс + +**Механика влияния на знания.** Замер (замыкание итерации, dx-retro-2) +→ светофор 🟢/🟡/🔴 → порог-триггер → изменение КОНКРЕТНОГО артефакта +знаний (карточка `_env` / правило dx / приём в dx-flow / строка dx-map / +наполнение дерева) → коммит std1-ext с указанием источника-замера → +переиндексация корпуса. Если правило соблюдается, а метрика хуже цели — +пересматривается ОЦЕНКА правила/приёма (понижается, хвост в «Открытых +хвостах»). Правки знаний — только через ретроспективу (владелец в +контуре утверждения), никогда «по ходу». + +**Фиксация.** Замер — факт в отчёте замыкания итерации проекта (формат: +`итерация | метрика | значение | светофор | триггер | действие`), не +редактируется. Светофор и цели — здесь, с датой и источником. Действие — +git-коммит std1-ext («ретроспектива ИN, метрика M»). **Версионность** — +git: вся история правил и оценок = история коммитов; откат = `git +revert` правки знаний с обоснованием; замеры-факты не откатываются; +корпус пересобирается из git (индекс — производное, не версионируется). + +### Метрики (база / цель / триггер / текущий светофор 2026-11-02) + +| # | Метрика | База (И1/И2) | Цель | Триггер (порог действия) | Сейчас | +|---|---|---|---|---|---| +| А1 | Находки ревизии/этап, медиана | ~7 (пик 53) / ~2 | ≤2, high=0 | high на этапе → усилить B-блок ТЗ и фокус ревизии | 🟡 (И3-э1: 2 high) | +| А2 | Псевдо-якоря/псевдо-цитаты | 0 / 0 (И3-э1: 2) | 0 | >0 → приём в dx-flow + формулировка dx-dev-8 | 🟡 | +| А3 | Дефекты после замыкания этапа | И1: этап доезжал ×2 | 0 | >0 → ужесточить замыкание (контрольный + хвосты в handoff) | 🟡 | +| А4 | Повторы известных граблей | И2: 3 (кодировка/лок/байты) | 0 | 1-й повтор → инструмент (dx-retro-3); 2-й → эскалация владельцу | 🟡 (проверка И3: инструменты введены) | +| Б1 | Доля документационных коммитов | ~70% / ~56% | ≤40% | >50% две итерации подряд → упрощать каскад (одно место статусов) | 🟡 | +| Б2 | Redo-прогоны workflow/этап | 4 / 2 | ≤1 | >2 → стабильность скриптов (маркер «ТЗ vN», прогон сниппетов) | 🟡 | +| Б3 | Wall-clock и токены этапа | база И1/И2 зафиксирована | тренд ↓ на однотипных этапах | рост ×2 → разбор причин | 🟢 (ускорение ×3–5) | +| В1 | Возврат правил: батарея top-3 | 16/16 | 100% | <100% → чинить корпус/нарезку, не батарею | 🟢 | +| В2 | Темы с первого захода | 2/4 до env-фикса | ≥75% | <75% → тюнинг запросов / ускорить `--tag env:*` | 🟡 (контрольный прогон /decision) | +| В3 | Дыры корпуса (темы без находок) | механизм заведён | закрытие в ту же итерацию | каждая → кандидат наполнения дерева | 🟢 | +| В4 | Возраст docChanges на легализации | до 4 этапов | ≤1 итерации | >1 итерации → выносить легализацию чаще | 🟡 | +| Г1 | Honest-skip'ы сняты к замыканию | И1: все сняты живыми | 100% | остаток → в хвосты handoff | 🟢 | +| Г2 | Эскалации закрыты владельцем | не замерялось | ≤1 дня | дольше → выносить в повестку явно | 🟢 (эмпирически быстро) | +| Г3 | Инциденты среды: новые → карточка | правило заведено | в тот же день | дольше → помнить о dx-retro-3 | 🟢 | + +**Пересмотр целей** — не чаще каждой второй ретроспективы, коммитом с +обоснованием. Добавление метрики — через ретроспективу (какие решения +она изменит? не изменяет — не заводить). + +### История светофоров (полная — git log карточки) + +- 2026-11-02 — базовая фиксация: А1–А4/Б1/Б2/В2/В4 🟡, Б3/В1/В3/Г* 🟢; + источники — ретроспектива Kit.RAG (И1/И2) и живой прогон /decision. + +## Правило обновления карточки + +Ретроспектива проекта (dx-retro-1) дописывает: новые приёмы с оценкой и +прецедентом-числом, пересматривает существующие оценки, закрывает хвосты, +обновляет светофоры со ссылкой на замер. Числа обязательны +(«быстрее/лучше» без чисел не принимается, dx-retro-2). Коммит в +std1-ext с указанием источника-ретроспективы + переиндексация корпуса. +Откат оценки/правила — git revert с обоснованием; замеры-факты +последующих итераций не откатываются. diff --git a/scripts/flow_stats.py b/scripts/flow_stats.py new file mode 100644 index 0000000..a967910 --- /dev/null +++ b/scripts/flow_stats.py @@ -0,0 +1,202 @@ +# -*- coding: utf-8 -*- +"""Агрегатор журнала опыта kit-dev-flow (общий git-репозиторий статистики). + +Читает карточки прогонов runs/**/*.md (YAML-шапка между первыми двумя +строками '---'), считает агрегаты по всем проектам команды и печатает +markdown-отчёт. Точка опыта: --dir > env KIT_FLOW_EXP_DIR > +~/.zcode/kit-knowledge.json (flow_exp_dir) > I:\\Kit\\DB.FlowExp_WorkStatistics. + +Примеры: + python flow_stats.py # всё + python flow_stats.py --kind этап-модуля # класс текущего шага + python flow_stats.py --days 30 --last 40 + python flow_stats.py --write-summary # обновить _summary.md +""" +import argparse +import datetime as dt +import glob +import json +import os +import sys + +DEFAULT_DIR = r"I:\Kit\DB.FlowExp_WorkStatistics" +NUM_FIELDS = ("agents", "revisions", "escalations", "duration_min") +STR_FIELDS = ("date", "project", "step", "kind", "form", "preset", + "model", "result", "run_id") + + +def resolve_dir(cli_dir): + if cli_dir: + return cli_dir + env = os.environ.get("KIT_FLOW_EXP_DIR") + if env: + return env + cfg_path = os.path.join(os.path.expanduser("~"), + ".zcode", "kit-knowledge.json") + try: + with open(cfg_path, encoding="utf-8") as fh: + cfg = json.load(fh) + if cfg.get("flow_exp_dir"): + return cfg["flow_exp_dir"] + except (OSError, ValueError): + pass + return DEFAULT_DIR + + +def parse_card(path): + """YAML-шапка карточки: ключ: значение (одна строка на ключ).""" + with open(path, encoding="utf-8") as fh: + lines = fh.read().splitlines() + if not lines or lines[0].strip() != "---": + return None + meta, end = {}, None + for i, line in enumerate(lines[1:], start=1): + s = line.strip() + if s == "---": + end = i + break + if ":" in s and not s.startswith("#"): + key, _, val = s.partition(":") + val = val.split(" #")[0].strip().strip('"').strip("'") + if key.strip() in STR_FIELDS or key.strip() in NUM_FIELDS: + meta[key.strip()] = val + if end is None or not meta.get("date"): + return None + lessons, in_lessons = [], False + for line in lines[end + 1:]: + bs = line.strip() + if bs.startswith("## Уроки"): + in_lessons = True + continue + if in_lessons and bs.startswith("## "): + in_lessons = False + elif in_lessons and bs.startswith("- "): + lessons.append(bs[2:].strip()) + meta["_lessons"] = lessons + return meta + + +def num(meta, field): + try: + return float(meta.get(field, "")) + except (TypeError, ValueError): + return None + + +def load_cards(root, kind, project, days): + cards = [] + for path in glob.glob(os.path.join(root, "runs", "**", "*.md"), + recursive=True): + meta = parse_card(path) + if not meta: + continue + if kind and meta.get("kind") != kind: + continue + if project and meta.get("project") != project: + continue + try: + d = dt.date.fromisoformat(meta["date"][:10]) + except ValueError: + d = None + if days and d and (dt.date.today() - d).days > days: + continue + cards.append(meta) + cards.sort(key=lambda m: m.get("date", "")) + return cards + + +def avg(values): + vals = [v for v in values if v is not None] + return (sum(vals) / len(vals)) if vals else None + + +def fmt(v, suffix=""): + return "%.1f%s" % (v, suffix) if v is not None else "—" + + +def group_stats(cards, field): + """{значение поля: [всего, success]} по карточкам с непустым полем.""" + stats = {} + for m in cards: + key = m.get(field) or "—" + s = stats.setdefault(key, [0, 0]) + s[0] += 1 + if m.get("result") == "success": + s[1] += 1 + return stats + + +def render(cards, title): + out = ["# Статистика kit-dev-flow — " + title, ""] + out.append("Карточек: %d (проектов: %d)" % ( + len(cards), len({m.get("project") for m in cards if m.get("project")}))) + out.append("") + for field, label in [("form", "форма"), ("preset", "пресет"), + ("kind", "класс шага")]: + groups = group_stats(cards, field) + if len(groups) < 2 and field != "form": + continue + out.append("| %s | прогонов | success |" % label) + out.append("|---|---|---|") + for key, (total, ok) in sorted(groups.items(), + key=lambda kv: -kv[1][0]): + out.append("| %s | %d | %d%% |" % (key, total, ok * 100 // total)) + out.append("") + out.append("Средние: агентов %s, ревизий %s, эскалаций %s, " + "длительность %s мин." % ( + fmt(avg([num(m, "agents") for m in cards])), + fmt(avg([num(m, "revisions") for m in cards])), + fmt(avg([num(m, "escalations") for m in cards])), + fmt(avg([num(m, "duration_min") for m in cards])))) + out.append("") + fails = [m for m in cards if m.get("result") in ("fail", "partial")] + out.append("Провальных/частичных: %d. Уроки из последних:" % len(fails)) + shown = 0 + for m in reversed(fails): + if shown >= 8: + break + for lesson in m.get("_lessons", []): + out.append("- (%s, %s, %s) %s" % ( + m.get("project", "?"), m.get("step", "?"), + m.get("form", "?"), lesson)) + shown += 1 + if shown >= 8: + break + if not shown: + out.append("- (нет провальных карточек в выборке)") + return "\n".join(out) + "\n" + + +def main(): + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument("--dir", help="локальный клон репозитория опыта") + ap.add_argument("--kind", help="фильтр: класс шага (kind карточки)") + ap.add_argument("--project", help="фильтр: проект (по умолчанию все)") + ap.add_argument("--days", type=int, help="только последние N дней") + ap.add_argument("--last", type=int, help="только последние N карточек") + ap.add_argument("--write-summary", action="store_true", + help="записать отчёт в _summary.md репозитория") + args = ap.parse_args() + + root = resolve_dir(args.dir) + if not os.path.isdir(root): + print("ОШИБКА: каталог журнала не найден: %s" % root, file=sys.stderr) + print("Настрой flow_exp_dir в ~/.zcode/kit-knowledge.json " + "или склонируй репозиторий опыта.", file=sys.stderr) + sys.exit(1) + + cards = load_cards(root, args.kind, args.project, args.days) + if args.last: + cards = cards[-args.last:] + title = args.kind or ("проект " + args.project if args.project else "все проекты") + report = render(cards, title) + if args.write_summary: + path = os.path.join(root, "_summary.md") + with open(path, "w", encoding="utf-8", newline="\n") as fh: + fh.write(report) + print("Записан отчёт: %s" % path) + print(report) + + +if __name__ == "__main__": + main()