workflow-ai 1.0.64 → 1.0.66
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 +377 -277
- package/configs/agent-health-rules.yaml +75 -0
- package/configs/pipeline.yaml +24 -7
- package/package.json +1 -1
- package/src/init.mjs +20 -3
- package/src/lib/agent-health-registry.mjs +245 -0
- package/src/lib/agent-spawner.mjs +47 -6
- package/src/lib/artifact-snapshot.mjs +233 -0
- package/src/lib/error-classifier.mjs +311 -0
- package/src/lib/test-error-classifier.mjs +60 -0
- package/src/lib/test-extends.mjs +58 -0
- package/src/lib/test-version.mjs +21 -0
- package/src/runner.mjs +215 -58
- package/src/scripts/move-to-review.js +5 -7
- package/src/scripts/reset-agent-health.js +62 -0
- package/src/skills/coach/SKILL.md +1 -0
- package/src/skills/coach/tests/cases/TC-COACH-001/current/meta.json +93 -94
- package/src/skills/coach/tests/cases/TC-COACH-002/current/meta.json +93 -94
- package/src/skills/create-plan/SKILL.md +1 -0
- package/src/skills/create-plan/knowledge/test-hygiene.md +47 -0
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-001/current/claude-sonnet/trial-1.md +23 -31
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-001/current/claude-sonnet/trial-2.md +20 -35
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-001/current/claude-sonnet/trial-3.md +36 -19
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-001/current/judge.json +1 -1
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-001/current/kilo-deepseek/trial-2.md +11 -5
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-001/current/kilo-deepseek/trial-3.md +12 -16
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-001/current/kilo-glm/trial-1.md +15 -9
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-001/current/kilo-glm/trial-3.md +15 -14
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-001/current/kilo-minimax/trial-1.md +22 -18
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-001/current/kilo-minimax/trial-2.md +24 -16
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-001/current/kilo-minimax/trial-3.md +13 -20
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-001/current/meta.json +2 -2
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-002/current/claude-sonnet/trial-1.md +14 -19
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-002/current/claude-sonnet/trial-2.md +24 -14
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-002/current/claude-sonnet/trial-3.md +20 -19
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-002/current/judge.json +16 -17
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-002/current/kilo-deepseek/trial-1.md +0 -7
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-002/current/kilo-deepseek/trial-2.md +9 -10
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-002/current/kilo-deepseek/trial-3.md +5 -5
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-002/current/kilo-glm/trial-1.md +20 -4
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-002/current/kilo-glm/trial-2.md +36 -9
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-002/current/kilo-glm/trial-3.md +9 -6
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-002/current/kilo-minimax/trial-1.md +4 -12
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-002/current/kilo-minimax/trial-2.md +6 -8
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-002/current/kilo-minimax/trial-3.md +8 -4
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-002/current/meta.json +10 -11
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-003/current/claude-sonnet/trial-1.md +30 -0
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-003/current/claude-sonnet/trial-2.md +30 -0
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-003/current/claude-sonnet/trial-3.md +30 -0
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-003/current/judge.json +165 -0
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-003/current/kilo-deepseek/trial-1.md +5 -0
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-003/current/kilo-deepseek/trial-2.md +26 -0
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-003/current/kilo-deepseek/trial-3.md +5 -0
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-003/current/kilo-glm/trial-1.md +39 -0
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-003/current/kilo-glm/trial-2.md +37 -0
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-003/current/kilo-glm/trial-3.md +45 -0
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-003/current/kilo-minimax/trial-1.md +26 -0
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-003/current/kilo-minimax/trial-2.md +27 -0
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-003/current/kilo-minimax/trial-3.md +7 -0
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-003/current/meta.json +117 -0
- package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-003-parent-plan-mandatory.yaml +41 -0
- package/src/skills/decompose-gaps/tests/index.yaml +5 -0
- package/src/skills/decompose-gaps/tests/rubrics/parent-plan-mandatory.md +22 -0
- package/src/skills/decompose-gaps/workflows/decompose.md +5 -2
- package/src/skills/decompose-plan/knowledge/atomicity-checklist.md +31 -5
- package/src/skills/decompose-plan/knowledge/capabilities.md +29 -5
- package/src/skills/decompose-plan/knowledge/human-task-rules.md +15 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-004/current/claude-sonnet/trial-1.md +55 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-004/current/claude-sonnet/trial-2.md +49 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-004/current/claude-sonnet/trial-3.md +49 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-004/current/judge.json +163 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-004/current/kilo-deepseek/trial-1.md +104 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-004/current/kilo-deepseek/trial-2.md +45 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-004/current/kilo-deepseek/trial-3.md +58 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-004/current/kilo-glm/trial-1.md +193 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-004/current/kilo-glm/trial-2.md +202 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-004/current/kilo-glm/trial-3.md +155 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-004/current/kilo-minimax/trial-1.md +52 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-004/current/kilo-minimax/trial-2.md +17 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-004/current/kilo-minimax/trial-3.md +0 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-004/current/meta.json +115 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-004-executor-atomicity.yaml +64 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-005/current/claude-sonnet/trial-1.md +59 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-005/current/claude-sonnet/trial-2.md +204 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-005/current/claude-sonnet/trial-3.md +213 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-005/current/judge.json +163 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-005/current/kilo-deepseek/trial-1.md +0 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-005/current/kilo-deepseek/trial-2.md +57 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-005/current/kilo-deepseek/trial-3.md +54 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-005/current/kilo-glm/trial-1.md +147 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-005/current/kilo-glm/trial-2.md +165 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-005/current/kilo-glm/trial-3.md +133 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-005/current/kilo-minimax/trial-1.md +81 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-005/current/kilo-minimax/trial-2.md +108 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-005/current/kilo-minimax/trial-3.md +3 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-005/current/meta.json +114 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-005-capabilities-registry.yaml +78 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-006/current/claude-sonnet/trial-1.md +225 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-006/current/claude-sonnet/trial-2.md +66 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-006/current/claude-sonnet/trial-3.md +36 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-006/current/judge.json +163 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-006/current/kilo-deepseek/trial-1.md +42 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-006/current/kilo-deepseek/trial-2.md +67 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-006/current/kilo-deepseek/trial-3.md +40 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-006/current/kilo-glm/trial-1.md +122 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-006/current/kilo-glm/trial-2.md +131 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-006/current/kilo-glm/trial-3.md +138 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-006/current/kilo-minimax/trial-1.md +41 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-006/current/kilo-minimax/trial-2.md +88 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-006/current/kilo-minimax/trial-3.md +0 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-006/current/meta.json +115 -0
- package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-006-dod-threshold.yaml +72 -0
- package/src/skills/decompose-plan/tests/index.yaml +15 -0
- package/src/skills/decompose-plan/tests/rubrics/capabilities-registry.md +21 -0
- package/src/skills/decompose-plan/tests/rubrics/dod-threshold.md +21 -0
- package/src/skills/decompose-plan/tests/rubrics/executor-atomicity.md +21 -0
- package/src/skills/decompose-plan/workflows/decompose.md +38 -5
- package/src/skills/execute-task/tests/cases/TC-EXECUTE-TASK-001/current/meta.json +87 -88
- package/src/skills/execute-task/tests/cases/TC-EXECUTE-TASK-005/current/meta.json +87 -88
- package/src/skills/manual-testing/SKILL.md +6 -4
- package/src/skills/manual-testing/tests/cases/TC-MANUAL-TESTING-001/current/claude-sonnet/trial-1.md +29 -16
- package/src/skills/manual-testing/tests/cases/TC-MANUAL-TESTING-001/current/claude-sonnet/trial-2.md +21 -54
- package/src/skills/manual-testing/tests/cases/TC-MANUAL-TESTING-001/current/claude-sonnet/trial-3.md +18 -23
- package/src/skills/manual-testing/tests/cases/TC-MANUAL-TESTING-001/current/judge.json +17 -17
- package/src/skills/manual-testing/tests/cases/TC-MANUAL-TESTING-001/current/meta.json +19 -19
- package/src/skills/manual-testing/tests/cases/TC-MANUAL-TESTING-002/current/claude-sonnet/trial-1.md +27 -30
- package/src/skills/manual-testing/tests/cases/TC-MANUAL-TESTING-002/current/claude-sonnet/trial-2.md +16 -23
- package/src/skills/manual-testing/tests/cases/TC-MANUAL-TESTING-002/current/claude-sonnet/trial-3.md +35 -28
- package/src/skills/manual-testing/tests/cases/TC-MANUAL-TESTING-002/current/judge.json +13 -13
- package/src/skills/manual-testing/tests/cases/TC-MANUAL-TESTING-002/current/meta.json +15 -15
- package/src/skills/manual-testing/tests/cases/TC-MANUAL-TESTING-003/current/claude-sonnet/trial-1.md +76 -0
- package/src/skills/manual-testing/tests/cases/TC-MANUAL-TESTING-003/current/claude-sonnet/trial-2.md +71 -0
- package/src/skills/manual-testing/tests/cases/TC-MANUAL-TESTING-003/current/claude-sonnet/trial-3.md +85 -0
- package/src/skills/manual-testing/tests/cases/TC-MANUAL-TESTING-003/current/judge.json +46 -0
- package/src/skills/manual-testing/tests/cases/TC-MANUAL-TESTING-003/current/meta.json +36 -0
- package/src/skills/manual-testing/tests/cases/TC-MANUAL-TESTING-003-qa-non-ui-assertion.yaml +65 -0
- package/src/skills/manual-testing/tests/index.yaml +5 -0
- package/src/skills/manual-testing/tests/rubrics/qa-non-ui-assertion.md +31 -0
- package/src/skills/review-result/SKILL.md +1 -0
- package/src/skills/review-result/knowledge/test-hygiene.md +44 -0
- package/src/skills/review-result/scripts/verify-artifacts.js +157 -14
- package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-003/current/claude-sonnet/trial-1.md +7 -0
- package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-003/current/claude-sonnet/trial-2.md +7 -0
- package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-003/current/claude-sonnet/trial-3.md +7 -0
- package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-003/current/judge.json +163 -0
- package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-003/current/kilo-deepseek/trial-1.md +5 -0
- package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-003/current/kilo-deepseek/trial-2.md +5 -0
- package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-003/current/kilo-deepseek/trial-3.md +11 -0
- package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-003/current/kilo-glm/trial-1.md +16 -0
- package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-003/current/kilo-glm/trial-2.md +18 -0
- package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-003/current/kilo-glm/trial-3.md +17 -0
- package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-003/current/kilo-minimax/trial-1.md +17 -0
- package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-003/current/kilo-minimax/trial-2.md +31 -0
- package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-003/current/kilo-minimax/trial-3.md +5 -0
- package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-003/current/meta.json +115 -0
- package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-003-test-isolation.yaml +50 -0
- package/src/skills/review-result/tests/fixtures/QA-904-test-isolation-violation/QA-904.md +51 -0
- package/src/skills/review-result/tests/fixtures/QA-904-test-isolation-violation/example-test.mjs +36 -0
- package/src/skills/review-result/tests/index.yaml +5 -0
- package/src/skills/review-result/tests/rubrics/test-isolation.md +20 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Проверка изоляции тестов на ревью
|
|
2
|
+
|
|
3
|
+
Когда DoD тикета требует создать/изменить автотест, ревьюер **отдельно** проверяет, что тест не пишет артефакты в рабочий репозиторий или в общие системные пути. Это не дублирует обычный «тесты проходят» — проверка изоляции ортогональна результату прогона (зелёный тест всё равно может быть токсичным).
|
|
4
|
+
|
|
5
|
+
## Зачем ревьюеру проверять это отдельно
|
|
6
|
+
|
|
7
|
+
Тест, который проходит в чистом прогоне, но пишет в рабочий репозиторий, создаёт отложенные инциденты:
|
|
8
|
+
- в CI / на второй машине / при параллельном запуске тесты ложно падают или ложно проходят;
|
|
9
|
+
- при прерывании (Ctrl+C, таймаут) оставленный state подхватывается следующим прогоном как валидный;
|
|
10
|
+
- затираются файлы, нужные параллельным агентам/пайплайну;
|
|
11
|
+
- ложно срабатывают механические проверки (verify-artifacts, git-snapshot).
|
|
12
|
+
|
|
13
|
+
Поэтому «зелёный прогон» — необходимый, но не достаточный сигнал. Нужна **отдельная** проверка изоляции.
|
|
14
|
+
|
|
15
|
+
## Что читать
|
|
16
|
+
|
|
17
|
+
1. Файл(ы) теста, затронутые тикетом.
|
|
18
|
+
2. Соседние test-файлы в той же директории — какой паттерн изоляции уже принят в проекте.
|
|
19
|
+
|
|
20
|
+
## Красные флаги при чтении теста
|
|
21
|
+
|
|
22
|
+
- Путь к тест-артефакту строится от корня репозитория или CWD (любая конструкция «директория исходника + подъём к корню», «текущий рабочий каталог», «заранее заданный projectRoot») **без** префикса стандартного системного tmp-корня языка.
|
|
23
|
+
- `cleanup`-функция удаляет конкретный файл, а не весь tmp-корень. При прерывании до её вызова файл останется в рабочем репозитории.
|
|
24
|
+
- Cleanup — в теле test-функции, а не в teardown/after-hook. Падение assertion до cleanup-вызова = утечка state.
|
|
25
|
+
- Тест модифицирует реальный файл в config-, data-, state-, cache- или иных рабочих каталогах проекта внутри корня репозитория.
|
|
26
|
+
- Глобальные константы теста указывают на рабочий репозиторий и используются более чем в одном тест-кейсе (shared mutable state).
|
|
27
|
+
|
|
28
|
+
## Зелёные сигналы
|
|
29
|
+
|
|
30
|
+
- Корень артефактов создаётся через стандартную tmp-функцию стандартной библиотеки языка (исполнитель выбирает конкретную по языку проекта).
|
|
31
|
+
- Cleanup удаляет **весь корень** рекурсивно и идемпотентен (можно вызвать повторно без ошибки).
|
|
32
|
+
- Cleanup в teardown/after-hook, гарантированно выполняется при любом исходе теста.
|
|
33
|
+
- Каждый тест получает свой свежий корень — нет разделяемого изменяемого состояния между тестами.
|
|
34
|
+
|
|
35
|
+
## Что делать при обнаружении нарушения
|
|
36
|
+
|
|
37
|
+
- Если нарушена изоляция — `failed` с конкретным issue: какая строка файла указывает на реальный каталог проекта, почему cleanup не защищает от прерывания.
|
|
38
|
+
- Не смягчай finding на основании того, что тест «проходит» — проверка изоляции ортогональна прогону.
|
|
39
|
+
- В issue — процитировать фрагмент теста дословно (путь до корня проекта, функция очистки), а не пересказать.
|
|
40
|
+
|
|
41
|
+
## Когда проверка не применяется
|
|
42
|
+
|
|
43
|
+
- Тикет явно просит тест-генератор, который коммитит файлы в репозиторий как артефакт. В этом случае в DoD должно быть явное обоснование.
|
|
44
|
+
- Тест read-only: не создаёт и не модифицирует файлы (только reads).
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
import fs from 'fs';
|
|
29
29
|
import path from 'path';
|
|
30
|
+
import { pathToFileURL } from 'url';
|
|
30
31
|
import { findProjectRoot } from 'workflow-ai/lib/find-root.mjs';
|
|
31
32
|
import { parseFrontmatter } from 'workflow-ai/lib/utils.mjs';
|
|
32
33
|
|
|
@@ -71,13 +72,25 @@ function stripLineSuffix(filePath) {
|
|
|
71
72
|
return match ? match[1] : filePath;
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
function checkFilesExist(filePaths) {
|
|
75
|
+
function checkFilesExist(filePaths, workStartTime) {
|
|
76
|
+
const ticketWorkStart = workStartTime ? new Date(workStartTime) : null;
|
|
75
77
|
return filePaths.map(filePath => {
|
|
76
78
|
const fullPath = path.isAbsolute(filePath) ? filePath : path.join(PROJECT_DIR, filePath);
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
const exists = fs.existsSync(fullPath);
|
|
80
|
+
|
|
81
|
+
if (!exists) {
|
|
82
|
+
return { path: filePath, exists: false, unchanged: false };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!ticketWorkStart) {
|
|
86
|
+
return { path: filePath, exists: true, unchanged: false };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const stats = fs.statSync(fullPath);
|
|
90
|
+
const fileMtime = new Date(stats.mtime);
|
|
91
|
+
const unchanged = fileMtime < ticketWorkStart;
|
|
92
|
+
|
|
93
|
+
return { path: filePath, exists: true, unchanged };
|
|
81
94
|
});
|
|
82
95
|
}
|
|
83
96
|
|
|
@@ -145,29 +158,130 @@ function checkResultSection(body) {
|
|
|
145
158
|
};
|
|
146
159
|
}
|
|
147
160
|
|
|
161
|
+
/**
|
|
162
|
+
* E2E assertion против ghost-execution: парсит секцию `### Implementation assertions`
|
|
163
|
+
* в теле тикета и возвращает список проверок вида
|
|
164
|
+
* { module, export, method?, type? }
|
|
165
|
+
*
|
|
166
|
+
* Формат каждой строки-bullet'а (значения в backticks, ключи через запятую):
|
|
167
|
+
* - module: `path/to/file.mjs`, export: `ClassName`, method: `methodName`
|
|
168
|
+
* - module: `path/to/file.mjs`, export: `namedExport`
|
|
169
|
+
* - module: `path/to/file.mjs`, export: `helper`, type: `function`
|
|
170
|
+
*
|
|
171
|
+
* Секция необязательна. Если её нет — возвращаем пустой массив
|
|
172
|
+
* (тикеты без исполняемых артефактов — например, DOCS/HUMAN — проходят без гейта).
|
|
173
|
+
*/
|
|
174
|
+
function parseImplementationAssertions(body) {
|
|
175
|
+
const headerRegex = /^###\s*(?:Implementation assertions|E2E assertions)\s*$/m;
|
|
176
|
+
const match = headerRegex.exec(body);
|
|
177
|
+
if (!match) return [];
|
|
178
|
+
|
|
179
|
+
const startIdx = match.index + match[0].length;
|
|
180
|
+
const nextH3 = body.indexOf('\n### ', startIdx);
|
|
181
|
+
const nextH2 = body.indexOf('\n## ', startIdx);
|
|
182
|
+
const candidates = [nextH3, nextH2].filter(i => i !== -1);
|
|
183
|
+
const sectionEnd = candidates.length > 0 ? Math.min(...candidates) : body.length;
|
|
184
|
+
const sectionContent = body.substring(startIdx, sectionEnd);
|
|
185
|
+
|
|
186
|
+
const assertions = [];
|
|
187
|
+
const bulletRegex = /^[-*]\s+(.+)$/gm;
|
|
188
|
+
let bulletMatch;
|
|
189
|
+
while ((bulletMatch = bulletRegex.exec(sectionContent)) !== null) {
|
|
190
|
+
const line = bulletMatch[1];
|
|
191
|
+
const kv = {};
|
|
192
|
+
const pairRegex = /(\w+)\s*:\s*`([^`]+)`/g;
|
|
193
|
+
let pair;
|
|
194
|
+
while ((pair = pairRegex.exec(line)) !== null) {
|
|
195
|
+
kv[pair[1]] = pair[2];
|
|
196
|
+
}
|
|
197
|
+
if (kv.module && kv.export) {
|
|
198
|
+
assertions.push({
|
|
199
|
+
module: kv.module,
|
|
200
|
+
export: kv.export,
|
|
201
|
+
method: kv.method || null,
|
|
202
|
+
type: kv.type || 'function',
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return assertions;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Выполняет список implementation assertions против живого кода.
|
|
211
|
+
* Для каждой: dynamic import модуля → достаём export → проверяем method/type.
|
|
212
|
+
* Возвращает массив { ...assertion, ok, reason? }.
|
|
213
|
+
*/
|
|
214
|
+
async function runImplementationAssertions(assertions) {
|
|
215
|
+
const results = [];
|
|
216
|
+
for (const a of assertions) {
|
|
217
|
+
const absPath = path.isAbsolute(a.module) ? a.module : path.join(PROJECT_DIR, a.module);
|
|
218
|
+
if (!fs.existsSync(absPath)) {
|
|
219
|
+
results.push({ ...a, ok: false, reason: `module_not_found: ${a.module}` });
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
let mod;
|
|
223
|
+
try {
|
|
224
|
+
mod = await import(pathToFileURL(absPath).href);
|
|
225
|
+
} catch (err) {
|
|
226
|
+
results.push({ ...a, ok: false, reason: `import_failed: ${err.message.replace(/\n/g, ' ')}` });
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
const exported = mod[a.export];
|
|
230
|
+
if (exported === undefined) {
|
|
231
|
+
results.push({ ...a, ok: false, reason: `export_not_found: ${a.export}` });
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (a.method) {
|
|
235
|
+
// Класс → метод на prototype; объект → метод как свойство
|
|
236
|
+
const target = typeof exported === 'function' ? exported.prototype : exported;
|
|
237
|
+
const fn = target ? target[a.method] : undefined;
|
|
238
|
+
if (typeof fn !== 'function') {
|
|
239
|
+
results.push({ ...a, ok: false, reason: `method_not_function: ${a.export}.${a.method}` });
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
} else {
|
|
243
|
+
if (typeof exported !== a.type) {
|
|
244
|
+
results.push({ ...a, ok: false, reason: `type_mismatch: expected ${a.type}, got ${typeof exported}` });
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
results.push({ ...a, ok: true });
|
|
249
|
+
}
|
|
250
|
+
return results;
|
|
251
|
+
}
|
|
252
|
+
|
|
148
253
|
function verifyTicket(ticketPath) {
|
|
149
254
|
if (!fs.existsSync(ticketPath)) {
|
|
150
255
|
throw new Error(`Ticket file not found: ${ticketPath}`);
|
|
151
256
|
}
|
|
152
|
-
|
|
257
|
+
|
|
153
258
|
const content = fs.readFileSync(ticketPath, 'utf8');
|
|
154
259
|
const { frontmatter, body } = parseFrontmatter(content);
|
|
155
|
-
|
|
260
|
+
|
|
156
261
|
const filePaths = parseChangedFiles(body);
|
|
157
|
-
|
|
158
|
-
|
|
262
|
+
// Точка отсчёта для mtime — created_at: это стабильная метка, которая не
|
|
263
|
+
// перезаписывается при move-ticket / retry-циклах. updated_at мутирует на
|
|
264
|
+
// каждом перемещении (ready → in-progress → review → ready → …), поэтому
|
|
265
|
+
// в retry файлы, реально изменённые в ранней попытке, становятся формально
|
|
266
|
+
// «unchanged» относительно нового updated_at и тикет ложно блокируется.
|
|
267
|
+
const filesExist = checkFilesExist(filePaths, frontmatter.created_at || frontmatter.updated_at);
|
|
268
|
+
|
|
159
269
|
const dodStats = parseDoDCompletion(body);
|
|
160
|
-
|
|
270
|
+
|
|
161
271
|
const resultStats = checkResultSection(body);
|
|
162
|
-
|
|
272
|
+
|
|
273
|
+
const assertions = parseImplementationAssertions(body);
|
|
274
|
+
|
|
163
275
|
return {
|
|
164
276
|
ticket_id: frontmatter.id,
|
|
277
|
+
created_at: frontmatter.created_at,
|
|
165
278
|
files_exist: filesExist,
|
|
166
279
|
dod_completion_pct: dodStats.percentage,
|
|
167
280
|
dod_checked: dodStats.checked,
|
|
168
281
|
dod_completed: dodStats.completed,
|
|
169
282
|
result_exists: resultStats.exists,
|
|
170
|
-
result_filled: resultStats.summaryFilled
|
|
283
|
+
result_filled: resultStats.summaryFilled,
|
|
284
|
+
assertions,
|
|
171
285
|
};
|
|
172
286
|
}
|
|
173
287
|
|
|
@@ -202,10 +316,15 @@ function formatVerdict(result) {
|
|
|
202
316
|
.filter((f) => !f.exists)
|
|
203
317
|
.map((f) => f.path);
|
|
204
318
|
|
|
319
|
+
const unchangedFiles = result.files_exist
|
|
320
|
+
.filter((f) => f.exists && f.unchanged)
|
|
321
|
+
.map((f) => f.path);
|
|
322
|
+
|
|
205
323
|
// Критерии failed:
|
|
206
324
|
// - result_filled == false (секция Result пуста)
|
|
207
325
|
// - dod_completion_pct == 0 (ни один пункт DoD не отмечен)
|
|
208
326
|
// - есть отсутствующие файлы из "Изменённые файлы"
|
|
327
|
+
// - есть неизменённые файлы (file_unchanged)
|
|
209
328
|
const failReasons = [];
|
|
210
329
|
const humanIssues = [];
|
|
211
330
|
if (!result.result_filled) {
|
|
@@ -222,10 +341,26 @@ function formatVerdict(result) {
|
|
|
222
341
|
failReasons.push(`missing_files=${missingFiles.join(',')}`);
|
|
223
342
|
humanIssues.push(`не найдены заявленные файлы: ${missingFiles.join(', ')}`);
|
|
224
343
|
}
|
|
344
|
+
if (unchangedFiles.length > 0) {
|
|
345
|
+
failReasons.push(`file_unchanged=${unchangedFiles.join(',')}`);
|
|
346
|
+
humanIssues.push(
|
|
347
|
+
`файлы не были изменены после начала выполнения тикета: ${unchangedFiles.join(', ')}`
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const assertionFailures = (result.assertionResults || []).filter(r => !r.ok);
|
|
352
|
+
if (assertionFailures.length > 0) {
|
|
353
|
+
const descriptions = assertionFailures.map(r => {
|
|
354
|
+
const target = r.method ? `${r.export}.${r.method}` : r.export;
|
|
355
|
+
return `${target} в ${r.module} (${r.reason})`;
|
|
356
|
+
});
|
|
357
|
+
failReasons.push(`assertion_failed=${descriptions.length}`);
|
|
358
|
+
humanIssues.push(`не прошли E2E-assertions (ghost execution): ${descriptions.join('; ')}`);
|
|
359
|
+
}
|
|
225
360
|
|
|
226
361
|
const status = failReasons.length === 0 ? 'passed' : 'failed';
|
|
227
362
|
|
|
228
|
-
return { status, missingFiles, failReasons, humanIssues };
|
|
363
|
+
return { status, missingFiles, unchangedFiles, failReasons, humanIssues };
|
|
229
364
|
}
|
|
230
365
|
|
|
231
366
|
function buildReviewRow(humanIssues) {
|
|
@@ -288,7 +423,7 @@ function appendReviewNote(ticketPath, humanIssues) {
|
|
|
288
423
|
return true;
|
|
289
424
|
}
|
|
290
425
|
|
|
291
|
-
function main() {
|
|
426
|
+
async function main() {
|
|
292
427
|
const args = process.argv.slice(2);
|
|
293
428
|
|
|
294
429
|
if (args.length === 0) {
|
|
@@ -320,6 +455,8 @@ function main() {
|
|
|
320
455
|
|
|
321
456
|
try {
|
|
322
457
|
const result = verifyTicket(ticketPath);
|
|
458
|
+
result.assertionResults = await runImplementationAssertions(result.assertions || []);
|
|
459
|
+
|
|
323
460
|
const verdict = formatVerdict(result);
|
|
324
461
|
|
|
325
462
|
let reviewNoteWritten = false;
|
|
@@ -327,6 +464,9 @@ function main() {
|
|
|
327
464
|
reviewNoteWritten = appendReviewNote(ticketPath, verdict.humanIssues);
|
|
328
465
|
}
|
|
329
466
|
|
|
467
|
+
const assertionsTotal = result.assertionResults.length;
|
|
468
|
+
const assertionsFailed = result.assertionResults.filter(r => !r.ok).length;
|
|
469
|
+
|
|
330
470
|
console.log('---RESULT---');
|
|
331
471
|
console.log(`status: ${verdict.status}`);
|
|
332
472
|
console.log(`ticket_id: ${result.ticket_id || ''}`);
|
|
@@ -335,6 +475,9 @@ function main() {
|
|
|
335
475
|
console.log(`dod_completed: ${result.dod_completed}`);
|
|
336
476
|
console.log(`result_filled: ${result.result_filled}`);
|
|
337
477
|
console.log(`missing_files: ${verdict.missingFiles.join(',')}`);
|
|
478
|
+
console.log(`unchanged_files: ${verdict.unchangedFiles.join(',')}`);
|
|
479
|
+
console.log(`assertions_total: ${assertionsTotal}`);
|
|
480
|
+
console.log(`assertions_failed: ${assertionsFailed}`);
|
|
338
481
|
if (verdict.failReasons.length > 0) {
|
|
339
482
|
console.log(`fail_reasons: ${verdict.failReasons.join('; ')}`);
|
|
340
483
|
console.log(`issues: ${verdict.humanIssues.join('; ')}`);
|
package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-003/current/claude-sonnet/trial-1.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---RESULT---
|
|
2
|
+
status: failed
|
|
3
|
+
issues:
|
|
4
|
+
- "Пункт DoD #3 не выполнен: тест пишет в рабочий репозиторий — `const testProjectRoot = join(__dirname, '..', '..')` (example-test.mjs:17) строит корень артефактов от __dirname внутри репозитория, а не от системного tmp-корня"
|
|
5
|
+
- "Пункт DoD #3 не выполнен: cleanup удаляет конкретный файл `rmSync(testConfigPath)` (example-test.mjs:32), а не весь tmp-корень — при прерывании до cleanup файл `.workflow/config/sample.yaml` остаётся в рабочем репозитории"
|
|
6
|
+
- "Пункт DoD #3 не выполнен: cleanup вызывается в теле test-функции (example-test.mjs:35), а не в teardown/after-hook — падение assertion до cleanup = утечка state"
|
|
7
|
+
---RESULT---
|
package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-003/current/claude-sonnet/trial-2.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---RESULT---
|
|
2
|
+
status: failed
|
|
3
|
+
issues:
|
|
4
|
+
- "DoD пункт 3 не выполнен: тест пишет в рабочий репозиторий, а не в системный tmp-корень. Строка `const testProjectRoot = join(__dirname, '..', '..')` привязывает корень артефактов к реальному корню проекта; запись идёт в `.workflow/config/sample.yaml` внутри рабочего репозитория. Ожидалось: использование `os.tmpdir()` / `fs.mkdtempSync` как корня."
|
|
5
|
+
- "DoD пункт 3 не выполнен: cleanup (`rmSync(testConfigPath)`) удаляет конкретный файл, а не весь tmp-корень рекурсивно. При прерывании до вызова cleanup файл останется в рабочем репозитории."
|
|
6
|
+
- "DoD пункт 3 не выполнен: `cleanupTestConfig()` вызывается в теле тест-функции, а не в teardown/after-hook. Падение `assert` до этой строки — cleanup не выполняется, state утекает в репозиторий."
|
|
7
|
+
---RESULT---
|
package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-003/current/claude-sonnet/trial-3.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---RESULT---
|
|
2
|
+
status: failed
|
|
3
|
+
issues:
|
|
4
|
+
- "Пункт DoD 3 не выполнен: `const testProjectRoot = join(__dirname, '..', '..')` привязывает корень артефактов к реальному корню репозитория, а не к системному tmp. Тест пишет в `.workflow/config/sample.yaml` рабочего репозитория (example-test.mjs:18-21)."
|
|
5
|
+
- "Пункт DoD 3 не выполнен: cleanup (`rmSync(testConfigPath)`) удаляет конкретный файл, а не весь tmp-корень — при прерывании до вызова файл остаётся в рабочем репозитории (example-test.mjs:24-26)."
|
|
6
|
+
- "Пункт DoD 3 не выполнен: cleanup вызывается в теле test-функции, а не в teardown/after-hook — падение assertion до cleanup = утечка state в рабочем репозитории (example-test.mjs:29-34)."
|
|
7
|
+
---RESULT---
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
{
|
|
2
|
+
"per_model": {
|
|
3
|
+
"claude-sonnet": {
|
|
4
|
+
"pass_count": 3,
|
|
5
|
+
"total": 3,
|
|
6
|
+
"trials": [
|
|
7
|
+
{
|
|
8
|
+
"trial": 1,
|
|
9
|
+
"score": 5,
|
|
10
|
+
"passed": true
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"trial": 2,
|
|
14
|
+
"score": 5,
|
|
15
|
+
"passed": true
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"trial": 3,
|
|
19
|
+
"score": 4,
|
|
20
|
+
"passed": true
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
"kilo-glm": {
|
|
25
|
+
"pass_count": 3,
|
|
26
|
+
"total": 3,
|
|
27
|
+
"trials": [
|
|
28
|
+
{
|
|
29
|
+
"trial": 1,
|
|
30
|
+
"score": 4,
|
|
31
|
+
"passed": true
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"trial": 2,
|
|
35
|
+
"score": 4,
|
|
36
|
+
"passed": true
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"trial": 3,
|
|
40
|
+
"score": 4,
|
|
41
|
+
"passed": true
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
"kilo-minimax": {
|
|
46
|
+
"pass_count": 3,
|
|
47
|
+
"total": 3,
|
|
48
|
+
"trials": [
|
|
49
|
+
{
|
|
50
|
+
"trial": 1,
|
|
51
|
+
"score": 4,
|
|
52
|
+
"passed": true
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"trial": 2,
|
|
56
|
+
"score": 4,
|
|
57
|
+
"passed": true
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"trial": 3,
|
|
61
|
+
"score": 4,
|
|
62
|
+
"passed": true
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
},
|
|
66
|
+
"kilo-deepseek": {
|
|
67
|
+
"pass_count": 2,
|
|
68
|
+
"total": 3,
|
|
69
|
+
"trials": [
|
|
70
|
+
{
|
|
71
|
+
"trial": 1,
|
|
72
|
+
"score": 3,
|
|
73
|
+
"passed": false
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"trial": 2,
|
|
77
|
+
"score": 4,
|
|
78
|
+
"passed": true
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"trial": 3,
|
|
82
|
+
"score": 4,
|
|
83
|
+
"passed": true
|
|
84
|
+
}
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"rubric_scores": [
|
|
89
|
+
{
|
|
90
|
+
"agentId": "claude-sonnet",
|
|
91
|
+
"trial": 1,
|
|
92
|
+
"score": 5,
|
|
93
|
+
"errored": false
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"agentId": "claude-sonnet",
|
|
97
|
+
"trial": 2,
|
|
98
|
+
"score": 5,
|
|
99
|
+
"errored": false
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"agentId": "claude-sonnet",
|
|
103
|
+
"trial": 3,
|
|
104
|
+
"score": 4,
|
|
105
|
+
"errored": false
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"agentId": "kilo-deepseek",
|
|
109
|
+
"trial": 1,
|
|
110
|
+
"score": 3,
|
|
111
|
+
"errored": false
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"agentId": "kilo-deepseek",
|
|
115
|
+
"trial": 2,
|
|
116
|
+
"score": 4,
|
|
117
|
+
"errored": false
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"agentId": "kilo-deepseek",
|
|
121
|
+
"trial": 3,
|
|
122
|
+
"score": 4,
|
|
123
|
+
"errored": false
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"agentId": "kilo-glm",
|
|
127
|
+
"trial": 1,
|
|
128
|
+
"score": 4,
|
|
129
|
+
"errored": false
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"agentId": "kilo-glm",
|
|
133
|
+
"trial": 2,
|
|
134
|
+
"score": 4,
|
|
135
|
+
"errored": false
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"agentId": "kilo-glm",
|
|
139
|
+
"trial": 3,
|
|
140
|
+
"score": 4,
|
|
141
|
+
"errored": false
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"agentId": "kilo-minimax",
|
|
145
|
+
"trial": 1,
|
|
146
|
+
"score": 4,
|
|
147
|
+
"errored": false
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"agentId": "kilo-minimax",
|
|
151
|
+
"trial": 2,
|
|
152
|
+
"score": 4,
|
|
153
|
+
"errored": false
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"agentId": "kilo-minimax",
|
|
157
|
+
"trial": 3,
|
|
158
|
+
"score": 4,
|
|
159
|
+
"errored": false
|
|
160
|
+
}
|
|
161
|
+
],
|
|
162
|
+
"timestamp": "2026-04-21T14:36:41.299Z"
|
|
163
|
+
}
|
package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-003/current/kilo-deepseek/trial-2.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
---RESULT---
|
|
2
|
+
status: failed
|
|
3
|
+
issues:
|
|
4
|
+
- "Пункт DoD 3 не выполнен: ожидалось создание tmp-корня и изоляция в teardown/after-hook, получено: корень артефактов привязан к реальному корню репозитория (строка `const testProjectRoot = join(__dirname, '..', '..');`), cleanup удаляет конкретный файл, cleanup вызывается в теле test-функции"
|
|
5
|
+
---RESULT---
|
package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-003/current/kilo-deepseek/trial-3.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Проверка тикета QA-904 выявила следующие проблемы:
|
|
2
|
+
|
|
3
|
+
1. **Несоответствие путей** — в DoD указан файл `tests/fixtures/QA‑904‑test‑isolation‑violation/example‑test.mjs`, но фактический путь `.workflow/src/skills/review‑result/tests/fixtures/QA‑904‑test‑isolation‑violation/example‑test.mjs`.
|
|
4
|
+
2. **Нарушение изоляции теста** — тест пишет артефакты в рабочий репозиторий (строка `const testProjectRoot = join(__dirname, '..', '..');`), cleanup удаляет только конкретный файл, а не весь tmp‑корень, и находится в теле теста, а не в teardown/after‑hook.
|
|
5
|
+
|
|
6
|
+
---RESULT---
|
|
7
|
+
status: failed
|
|
8
|
+
issues:
|
|
9
|
+
- "Пункт DoD #1 не выполнен: ожидался файл tests/fixtures/QA-904-test-isolation-violation/example-test.mjs, фактический путь .workflow/src/skills/review-result/tests/fixtures/QA-904-test-isolation-violation/example-test.mjs"
|
|
10
|
+
- "Пункт DoD #3 не выполнен: тест нарушает изоляцию — корень артефактов привязан к рабочему репозиторию (строка 10), cleanup удаляет конкретный файл, а не tmp-корень, cleanup в теле теста, а не в teardown/after-hook"
|
|
11
|
+
---RESULT---
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Вердикт: **failed**.
|
|
2
|
+
|
|
3
|
+
Проверка DoD-критериев:
|
|
4
|
+
|
|
5
|
+
1. **Файл создан** — подтверждено.
|
|
6
|
+
2. **Тест проходит** — принято по условию.
|
|
7
|
+
3. **Изоляция** — **нарушена** по трём причинам:
|
|
8
|
+
- `testProjectRoot` указывает на корень репозитория (`join(__dirname, '..', '..')`), а не на системный tmp-каталог.
|
|
9
|
+
- `writeFileSync(testConfigPath, ...)` пишет в `.workflow/config/sample.yaml` внутри рабочего репозитория.
|
|
10
|
+
- `cleanupTestConfig()` вызывается в теле test-функции, а не в `after()`/`teardown`; при падении assertion файл остаётся в репозитории.
|
|
11
|
+
|
|
12
|
+
---RESULT---
|
|
13
|
+
status: failed
|
|
14
|
+
issues:
|
|
15
|
+
- "Пункт DoD 3 не выполнен: тест пишет в рабочий репозиторий (testProjectRoot = join(__dirname, '..', '..') → корень репозитория), а не в tmp-корень; cleanup вызывается в теле test-функции, а не в teardown/after-hook; при падении assertion артефакт остаётся в репозитории"
|
|
16
|
+
---RESULT---
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Тикет прочитан. Три критерия DoD. Критерий 3 (изоляция) нарушен — анализ ниже.
|
|
2
|
+
|
|
3
|
+
**DoD-проверка:**
|
|
4
|
+
|
|
5
|
+
1. **Файл создан** — подтверждено, файл существует.
|
|
6
|
+
2. **Тест проходит** — формально `node --test` success, принимается.
|
|
7
|
+
3. **Изоляция** — НАРУШЕНА:
|
|
8
|
+
- `testProjectRoot = join(__dirname, '..', '..')` — указывает на корень репозитория, а не на системный `tmp`.
|
|
9
|
+
- `createTestConfig` пишет в `.workflow/config/sample.yaml` внутри рабочего репозитория.
|
|
10
|
+
- `cleanupTestConfig` удаляет конкретный файл, не tmp-корень целиком.
|
|
11
|
+
- Cleanup вызывается в теле test-функции, а не в `teardown`/`after`-хуке — при падении assertion до строки cleanup файл остаётся в репозитории.
|
|
12
|
+
|
|
13
|
+
Вердикт: **failed**.
|
|
14
|
+
---RESULT---
|
|
15
|
+
status: failed
|
|
16
|
+
issues:
|
|
17
|
+
- "Пункт DoD 3 не выполнен: ожидалось что тест создаёт и уничтожает tmp-корень, не пишет в рабочий репозиторий (изоляция в teardown/after-hook); получено: testProjectRoot привязан к реальному корню репозитория (join(__dirname, '..', '..')), createTestConfig пишет в .workflow/config/sample.yaml рабочего репозитория, cleanupTestConfig удаляет конкретный файл а не tmp-корень, cleanup вызывается в теле test-функции а не в teardown — файл: src/skills/review-result/tests/fixtures/QA-904-test-isolation-violation/example-test.mjs:12-22"
|
|
18
|
+
---RESULT---
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Скрипт недоступен — перехожу к ручной проверке (fallback по workflow).
|
|
2
|
+
Проверка завершена. Файл существует, но нарушает изоляцию. Формирую вердикт.
|
|
3
|
+
Проверка тикета QA-904 завершена. DoD критерий #3 (изоляция теста) не выполнен — три нарушения:
|
|
4
|
+
|
|
5
|
+
1. **Корень артефактов — рабочий репозиторий** (`example-test.mjs:16`): `join(__dirname, '..', '..')` указывает на корень проекта, а не на системный tmp.
|
|
6
|
+
2. **Cleanup удаляет конкретный файл** (`:28`): `rmSync(testConfigPath)`, не весь tmp-корень — при прерывании артефакт остаётся.
|
|
7
|
+
3. **Cleanup в теле test-функции** (`:35`), а не в teardown/after-hook — падение assertion до cleanup = утечка state.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
---RESULT---
|
|
11
|
+
status: failed
|
|
12
|
+
issues:
|
|
13
|
+
- "DoD #3 не выполнен: тест пишет в рабочий репозиторий — example-test.mjs:16-17, testProjectRoot и testConfigPath указывают на .workflow/config/sample.yaml внутри корня репозитория, а не на tmp-корень через стандартную функцию ОС"
|
|
14
|
+
- "DoD #3 не выполнен: cleanup удаляет конкретный файл (example-test.mjs:28 — rmSync(testConfigPath)), а не весь tmp-корень рекурсивно — при прерывании до вызова cleanup файл остаётся в рабочем репозитории"
|
|
15
|
+
- "DoD #3 не выполнен: cleanup вызывается в теле test-функции (example-test.mjs:35), а не в teardown/after-hook — падение assertion до этой строки оставляет артефакт в репозитории"
|
|
16
|
+
---RESULT---
|
|
17
|
+
```
|
package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-003/current/kilo-minimax/trial-1.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Проверяю DoD пункт 3 (изоляция). Из предоставленного содержимого теста:
|
|
2
|
+
|
|
3
|
+
**Проблемы изоляции:**
|
|
4
|
+
1. **Корень артефактов** — `testProjectRoot = join(__dirname, '..', '..')` привязан к реальному репозиторию, а не к tmp-корню
|
|
5
|
+
2. **Запись в рабочий репозиторий** — пишет в `.workflow/config/sample.yaml`
|
|
6
|
+
3. **Cleanup в теле теста** — `cleanupTestConfig()` вызывается в теле test-функции, а не в after-hooks/teardown
|
|
7
|
+
4. **Удаляется конкретный файл** — `rmSync(testConfigPath)`, а не рекурсивное удаление tmp-корня
|
|
8
|
+
|
|
9
|
+
DoD требует: "создаёт и уничтожает tmp-корень, не пишет в рабочий репозиторий (изоляция в teardown/after-hook)".
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
---RESULT---
|
|
14
|
+
status: failed
|
|
15
|
+
issues:
|
|
16
|
+
- "Пункт DoD #3 не выполнен: тест пишет в рабочий репозиторий (.workflow/config/sample.yaml), а не в tmp-корень; cleanup выполняется в теле теста, а не в teardown/after-hook"
|
|
17
|
+
---RESULT---
|
package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-003/current/kilo-minimax/trial-2.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Проверяю DoD тикета QA-904:
|
|
2
|
+
|
|
3
|
+
**Пункт 1** — Файл создан: ✅ Существует
|
|
4
|
+
|
|
5
|
+
**Пункт 2** — Тест проходит: ✅ Формально `node --test` success
|
|
6
|
+
|
|
7
|
+
**Пункт 3** — Изоляция (tmp-корень, не рабочий репозиторий): ❌ **НАРУШЕН**
|
|
8
|
+
|
|
9
|
+
Анализ теста из тикета:
|
|
10
|
+
|
|
11
|
+
1. `testProjectRoot = join(__dirname, '..', '..')` — вычисляется от расположения теста, ведёт к реальному корню репозитория
|
|
12
|
+
2. `writeFileSync(testConfigPath, ...)` — пишет в `.workflow/config/sample.yaml` рабочего репозитория
|
|
13
|
+
3. `cleanupTestConfig()` — вызывается **внутри** test-функции, не в afterEach/teardown. При падении assertion до этой строки файл останется в рабочем репозитории
|
|
14
|
+
|
|
15
|
+
Это прямое нарушение изоляции: тест пишет в `.workflow/config/`, а не в tmp-корень (напр. `os.tmpdir()`).
|
|
16
|
+
|
|
17
|
+
Секции "## Ревью" в тикете нет — это первое ревью.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Вердикт
|
|
22
|
+
|
|
23
|
+
**DoD пункт 3 не выполнен:** тест записывает артефакты в рабочий репозиторий проекта, а не в tmp-корень. Cleanup находится внутри тела функции, а не в afterEach/teardown, что оставляет файл при падении.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
---RESULT---
|
|
28
|
+
status: failed
|
|
29
|
+
issues:
|
|
30
|
+
- "DoD пункт 3 нарушен: тест пишет в рабочий репозиторий (`.workflow/config/sample.yaml`), а не в tmp-корень. cleanup вызывается в теле test-функции, а не в teardown/afterEach — при падении assertion файл останется в рабочем репозитории. Ожидалось: `os.tmpdir()` + afterEach/teardown."
|
|
31
|
+
---RESULT---
|
package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-003/current/kilo-minimax/trial-3.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
---RESULT---
|
|
2
|
+
status: failed
|
|
3
|
+
issues:
|
|
4
|
+
- "Пункт DoD #3 не выполнен: ожидался tmp-корень с изоляцией в teardown/after-hook, но тест пишет в `.workflow/config/` рабочего репозитория (`testProjectRoot = join(__dirname, '..', '..')`), cleanup вызывается в теле теста (строка 25), а не в afterEach/teardown"
|
|
5
|
+
---RESULT---
|