Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 32 additions & 31 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@
"claimом",
"clippy",
"CLIS",
"Crcs",
"crcs",
"Crcs",
"dalek",
"deadbe",
"deadbeefdeadbeefdead",
Expand All @@ -55,6 +55,7 @@
"Kratos",
"libc",
"libmt",
"livelock",
"mktemp",
"mktree",
"msvc",
Expand Down Expand Up @@ -241,45 +242,18 @@
"збілджений",
"збілдити",
"змержити",
"квіз",
"квізом",
"квізу",
"квізів",
"колапсить",
"лаповане",
"лапованому",
"лаповані",
"лінкованого",
"лінковані",
"Мілстоун",
"мілстоун",
"мілстоунами",
"мілстоуни",
"мілстоунів",
"недовірений",
"нідедлайн",
"парсять",
"перегенеровую",
"Позамежі",
"почастинно",
"репозиторний",
"розсинхронізований",
"сабсет",
"фейлиться",
"форки",
"фронтматер",
"Ідемпотентно",
"ін'єктується",
"ін'єктуються",
"інвалідація",
"інвалідаціями",
"інвалідована",
"інвалідований",
"інвалідованого",
"інвалідовує",
"інвалідовуються",
"інвалідування",
"Інвалідувати",
"інвалідаціями",
"інвалідує",
"інвентаря",
"інжект",
Expand All @@ -304,11 +278,16 @@
"ітеративно",
"Ітеративно",
"каскадує",
"квіз",
"квізів",
"квізом",
"квізу",
"кворумних",
"Кворумних",
"кворумні",
"Кворумні",
"кворумної",
"колапсить",
"коміт",
"коміта",
"коміти",
Expand Down Expand Up @@ -336,6 +315,11 @@
"курування",
"лайфсайклу",
"ландингу",
"лаповане",
"лаповані",
"лапованому",
"лінковані",
"лінкованого",
"лінкує",
"лінкується",
"лістенер",
Expand Down Expand Up @@ -363,6 +347,11 @@
"метасимволів",
"мідфлайт",
"Мікроменеджер",
"мілстоун",
"Мілстоун",
"мілстоунами",
"мілстоуни",
"мілстоунів",
"Мітигація",
"мокати",
"монорепа",
Expand All @@ -380,6 +369,7 @@
"невалідних",
"невалідному",
"недетермінізм",
"недовірений",
"неефемерні",
"незакоміче",
"незакомічених",
Expand All @@ -396,6 +386,7 @@
"неучасників",
"низьковажільне",
"низьковажільні",
"нідедлайн",
"ноди",
"облікується",
"однопакетний",
Expand All @@ -409,12 +400,14 @@
"пайпі",
"паралелить",
"Парсимо",
"парсять",
"Пасивн",
"патчить",
"патчів",
"перiод",
"перебілд",
"перебілджує",
"перегенеровую",
"перегенеруй",
"перегенерують",
"перемаршрутовуються",
Expand Down Expand Up @@ -447,8 +440,10 @@
"платформозалежному",
"побрейнштормимо",
"поведі",
"Позамежі",
"портабельна",
"пофайловий",
"почастинно",
"превʼю",
"прогонить",
"програмкова",
Expand Down Expand Up @@ -486,8 +481,8 @@
"резолвнувся",
"резолвнуту",
"резолву",
"Реімплементація",
"реімплементації",
"Реімплементація",
"реімплементовано",
"реімплементує",
"реімплементується",
Expand All @@ -502,6 +497,7 @@
"реплеїться",
"реплей",
"реплею",
"репозиторний",
"ресканить",
"ретраєм",
"ретраїв",
Expand All @@ -514,14 +510,16 @@
"розпарсених",
"розсинхрон",
"розсинхроні",
"розсинхронізований",
"роутінг",
"роутінговими",
"роутінгових",
"роутінгові",
"сабпроцесу",
"сабсет",
"самопокращення",
"санітизоване",
"Санітизує",
"сабпроцесу",
"серіалізатором",
"сесійність",
"синхр",
Expand Down Expand Up @@ -570,6 +568,7 @@
"уточнюючі",
"фактум",
"фейл",
"фейлиться",
"фейляться",
"фенсить",
"фенситься",
Expand All @@ -580,7 +579,9 @@
"фолбек",
"фолбеком",
"форвардить",
"форки",
"фронтенд",
"фронтматер",
"фронтматері",
"фронтматером",
"фронтматеру",
Expand Down
56 changes: 51 additions & 5 deletions crates/mt-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,9 +257,20 @@ fn run_is_failure(path: &Path) -> bool {
}
}

// count(run із result ∈ execution-failure і NNN > останнього fact) — graph.md.
// «Прийнятий fact» (graph.md): актуальний fact_N (max NNN) без pending-audit_N
// або з audit-result_N: success. Відхилений аудитом fact прийнятим НЕ є, тому
// межу не рухає — інакше цикл «провал → провал → сирий fact → аудит відхилив»
// обнуляв би лічильник вічно, і драбина ніколи не дійшла б до engineer/unresolvable.
fn accepted_fact_nnn(dir: &Path) -> u64 {
match accepted_fact_state(dir) {
FactState::Resolved => max_nnn(dir, "fact_", ".md"),
FactState::PendingAudit | FactState::None => 0,
}
}

// count(run із result ∈ execution-failure і NNN > останнього прийнятого fact) — graph.md.
fn failed_streak(dir: &Path) -> u64 {
let since = max_nnn(dir, "fact_", ".md");
let since = accepted_fact_nnn(dir);
fs::read_dir(dir)
.ok()
.into_iter()
Expand Down Expand Up @@ -1283,8 +1294,24 @@ mod tests {
assert_eq!(state_of("task", &files, &[], None), TaskState::Waiting);
}
#[test]
fn streak_counts_only_runs_after_last_fact() {
// run_001 failed → fact_002 прийнято → run_003 failed: streak 1, не 2.
fn streak_counts_only_runs_after_accepted_fact() {
// fact_002 прийнято (аудит success) → run_001 не рахується, streak 1 < 2.
let files = [
("task.md", ""),
("a.md", ""),
("run_001.md", "---\nresult: failed\n---\n"),
("fact_002.md", ""),
("pending-audit_002.md", ""),
("audit-result_002.md", "---\nresult: success\n---\n"),
("run_003.md", "---\nresult: failed\n---\n"),
];
// Прийнятий fact → resolved має пріоритет над станом лічильника.
assert_eq!(state_of("task", &files, &[], Some(2)), TaskState::Resolved);
}
#[test]
fn rejected_fact_does_not_reset_streak() {
// fact_002 відхилено аудитом → межа 0 → рахуються run_001 і run_003:
// streak 2 ≥ agent_retry_max 2 → failed, драбина термінує.
let files = [
("task.md", ""),
("a.md", ""),
Expand All @@ -1294,7 +1321,26 @@ mod tests {
("audit-result_002.md", "---\nresult: failed\n---\n"),
("run_003.md", "---\nresult: failed\n---\n"),
];
assert_eq!(state_of("task", &files, &[], Some(2)), TaskState::Waiting);
assert_eq!(state_of("task", &files, &[], Some(2)), TaskState::Failed);
}
#[test]
fn rejected_fact_livelock_terminates() {
// Патологія, яку закриває межа «прийнятий fact»: кожен цикл дає сирий
// fact, аудит його відхиляє. Раніше лічильник обнулявся вічно.
let files = [
("task.md", ""),
("a.md", ""),
("run_001.md", "---\nresult: failed\n---\n"),
("fact_002.md", ""),
("pending-audit_002.md", ""),
("audit-result_002.md", "---\nresult: failed\n---\n"),
("run_003.md", "---\nresult: failed\n---\n"),
("fact_004.md", ""),
("pending-audit_004.md", ""),
("audit-result_004.md", "---\nresult: failed\n---\n"),
("run_005.md", "---\nresult: failed\n---\n"),
];
assert_eq!(state_of("task", &files, &[], None), TaskState::Failed);
}

// ── unresolvable ──
Expand Down
7 changes: 5 additions & 2 deletions docs/conformance.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
| --- | --- | --- | --- |
| Сканування графа, `deps/`, denylist | РЕАЛІЗОВАНО | `lib.rs` `scan_tasks`/`scan_dir` | — |
| Derived-стани вузла | ЧАСТКОВО | `lib.rs` `detect_state` | `stalled` не виводиться (немає інтеграції з remote claim refs); `blocked-invalid-dep` як warning |
| `failed_streak` за категорією `result` | РЕАЛІЗОВАНО | `lib.rs` `failed_streak`/`is_execution_failure` | — (межа рахунку — останній `fact_NNN`; спека каже «останній *прийнятий* fact» — відкрите питання нижче) |
| `failed_streak`: категорія `result` + межа «прийнятий fact» | РЕАЛІЗОВАНО | `lib.rs` `failed_streak`/`is_execution_failure`/`accepted_fact_nnn` | — |
| Файловий контракт `a.md`/`h.md` | ЧАСТКОВО | `lib.rs` `write_executor_flag` | Пишуться markdown-секціями, спека вимагає YAML-фронтматер; немає `secrets`, `interactive`, `assignee`, `notify`, `parent` — **конфлікт спека↔код, потребує рішення** |
| Артефакти version chain (читання) | РЕАЛІЗОВАНО | `artifacts.rs` | — |
| `schema_version` fail-closed | ВІДСУТНЄ | — | Поле пишеться, але жоден читач не валідує версію |
Expand Down Expand Up @@ -114,8 +114,11 @@
5. **M6 фаза 0 — модельний трек Дельти.** Паралельно від хвилі 2, як велить roadmap: `mandates.yaml` (включно з `kind: model` і `audacity`), `decision-request` із `leverage_facets`, `chosen_option`, стан `awaiting-decision`, квіз-гейт, конверсія вичерпаної драбини в розвилку. Соціальних ризиків нема — механіка обкатується на моделях.
6. **M3 / M5 / M4.** Dashboard і поверхні; retro (MVP не чекає M1–M4 — дані вже є); файловий шар i18n.

## Закриті питання

- **Межа `failed_streak`** (2026-08-09): рішення — спека виграє, код приведено до неї. Межа — останній *прийнятий* fact (`accepted_fact_nnn`); відхилений аудитом fact межу не рухає. Причина: інакше цикл «провал → провал → сирий fact → аудит відхилив» обнуляє лічильник вічно, і драбина ретраїв ніколи не доходить до EngineerAgent чи `unresolvable` — livelock. Тест-сторож: `rejected_fact_livelock_terminates`.

## Відкриті питання

- **Межа `failed_streak`.** Спека каже «NNN > останнього *прийнятого* fact»; реалізація рахує від останнього `fact_NNN` незалежно від вердикту аудиту. Розходження проявляється лише в rework-циклі після провального аудиту. Потрібне рішення: уточнити спеку чи змінити код.
- **Формат `a.md`/`h.md`.** Спека вимагає YAML-фронтматер, код пише markdown-секції. Один із двох має поступитись — це контракт, який читають і люди, і агенти.
- **`mt-napi`.** За рішенням Г специфікації `2026-07-23-mt-cli-rust.md` крейт мав бути видалений, але лишається у workspace members.
Loading