workflow-ai 1.0.64 → 1.0.65

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.
Files changed (135) hide show
  1. package/README.md +239 -145
  2. package/configs/agent-health-rules.yaml +64 -0
  3. package/configs/pipeline.yaml +18 -1
  4. package/package.json +1 -1
  5. package/src/init.mjs +20 -3
  6. package/src/lib/agent-health-registry.mjs +245 -0
  7. package/src/lib/artifact-snapshot.mjs +233 -0
  8. package/src/lib/error-classifier.mjs +274 -0
  9. package/src/lib/test-error-classifier.mjs +60 -0
  10. package/src/lib/test-extends.mjs +58 -0
  11. package/src/lib/test-version.mjs +21 -0
  12. package/src/scripts/move-to-review.js +5 -7
  13. package/src/scripts/reset-agent-health.js +62 -0
  14. package/src/skills/coach/SKILL.md +1 -0
  15. package/src/skills/coach/tests/cases/TC-COACH-001/current/meta.json +2 -3
  16. package/src/skills/coach/tests/cases/TC-COACH-002/current/meta.json +2 -3
  17. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-001/current/claude-sonnet/trial-1.md +23 -31
  18. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-001/current/claude-sonnet/trial-2.md +20 -35
  19. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-001/current/claude-sonnet/trial-3.md +36 -19
  20. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-001/current/judge.json +1 -1
  21. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-001/current/kilo-deepseek/trial-2.md +11 -5
  22. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-001/current/kilo-deepseek/trial-3.md +12 -16
  23. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-001/current/kilo-glm/trial-1.md +15 -9
  24. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-001/current/kilo-glm/trial-3.md +15 -14
  25. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-001/current/kilo-minimax/trial-1.md +22 -18
  26. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-001/current/kilo-minimax/trial-2.md +24 -16
  27. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-001/current/kilo-minimax/trial-3.md +13 -20
  28. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-001/current/meta.json +2 -2
  29. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-002/current/claude-sonnet/trial-1.md +14 -19
  30. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-002/current/claude-sonnet/trial-2.md +24 -14
  31. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-002/current/claude-sonnet/trial-3.md +20 -19
  32. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-002/current/judge.json +16 -17
  33. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-002/current/kilo-deepseek/trial-1.md +0 -7
  34. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-002/current/kilo-deepseek/trial-2.md +9 -10
  35. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-002/current/kilo-deepseek/trial-3.md +5 -5
  36. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-002/current/kilo-glm/trial-1.md +20 -4
  37. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-002/current/kilo-glm/trial-2.md +36 -9
  38. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-002/current/kilo-glm/trial-3.md +9 -6
  39. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-002/current/kilo-minimax/trial-1.md +4 -12
  40. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-002/current/kilo-minimax/trial-2.md +6 -8
  41. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-002/current/kilo-minimax/trial-3.md +8 -4
  42. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-002/current/meta.json +10 -11
  43. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-003/current/claude-sonnet/trial-1.md +30 -0
  44. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-003/current/claude-sonnet/trial-2.md +30 -0
  45. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-003/current/claude-sonnet/trial-3.md +30 -0
  46. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-003/current/judge.json +165 -0
  47. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-003/current/kilo-deepseek/trial-1.md +5 -0
  48. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-003/current/kilo-deepseek/trial-2.md +26 -0
  49. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-003/current/kilo-deepseek/trial-3.md +5 -0
  50. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-003/current/kilo-glm/trial-1.md +39 -0
  51. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-003/current/kilo-glm/trial-2.md +37 -0
  52. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-003/current/kilo-glm/trial-3.md +45 -0
  53. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-003/current/kilo-minimax/trial-1.md +26 -0
  54. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-003/current/kilo-minimax/trial-2.md +27 -0
  55. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-003/current/kilo-minimax/trial-3.md +7 -0
  56. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-003/current/meta.json +117 -0
  57. package/src/skills/decompose-gaps/tests/cases/TC-DECOMPOSE-GAPS-003-parent-plan-mandatory.yaml +41 -0
  58. package/src/skills/decompose-gaps/tests/index.yaml +5 -0
  59. package/src/skills/decompose-gaps/tests/rubrics/parent-plan-mandatory.md +22 -0
  60. package/src/skills/decompose-gaps/workflows/decompose.md +5 -2
  61. package/src/skills/decompose-plan/knowledge/atomicity-checklist.md +31 -5
  62. package/src/skills/decompose-plan/knowledge/capabilities.md +29 -5
  63. package/src/skills/decompose-plan/knowledge/human-task-rules.md +15 -0
  64. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-004/current/claude-sonnet/trial-1.md +55 -0
  65. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-004/current/claude-sonnet/trial-2.md +49 -0
  66. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-004/current/claude-sonnet/trial-3.md +49 -0
  67. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-004/current/judge.json +163 -0
  68. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-004/current/kilo-deepseek/trial-1.md +104 -0
  69. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-004/current/kilo-deepseek/trial-2.md +45 -0
  70. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-004/current/kilo-deepseek/trial-3.md +58 -0
  71. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-004/current/kilo-glm/trial-1.md +193 -0
  72. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-004/current/kilo-glm/trial-2.md +202 -0
  73. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-004/current/kilo-glm/trial-3.md +155 -0
  74. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-004/current/kilo-minimax/trial-1.md +52 -0
  75. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-004/current/kilo-minimax/trial-2.md +17 -0
  76. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-004/current/kilo-minimax/trial-3.md +0 -0
  77. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-004/current/meta.json +115 -0
  78. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-004-executor-atomicity.yaml +64 -0
  79. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-005/current/claude-sonnet/trial-1.md +59 -0
  80. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-005/current/claude-sonnet/trial-2.md +204 -0
  81. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-005/current/claude-sonnet/trial-3.md +213 -0
  82. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-005/current/judge.json +163 -0
  83. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-005/current/kilo-deepseek/trial-1.md +0 -0
  84. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-005/current/kilo-deepseek/trial-2.md +57 -0
  85. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-005/current/kilo-deepseek/trial-3.md +54 -0
  86. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-005/current/kilo-glm/trial-1.md +147 -0
  87. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-005/current/kilo-glm/trial-2.md +165 -0
  88. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-005/current/kilo-glm/trial-3.md +133 -0
  89. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-005/current/kilo-minimax/trial-1.md +81 -0
  90. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-005/current/kilo-minimax/trial-2.md +108 -0
  91. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-005/current/kilo-minimax/trial-3.md +3 -0
  92. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-005/current/meta.json +114 -0
  93. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-005-capabilities-registry.yaml +78 -0
  94. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-006/current/claude-sonnet/trial-1.md +225 -0
  95. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-006/current/claude-sonnet/trial-2.md +66 -0
  96. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-006/current/claude-sonnet/trial-3.md +36 -0
  97. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-006/current/judge.json +163 -0
  98. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-006/current/kilo-deepseek/trial-1.md +42 -0
  99. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-006/current/kilo-deepseek/trial-2.md +67 -0
  100. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-006/current/kilo-deepseek/trial-3.md +40 -0
  101. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-006/current/kilo-glm/trial-1.md +122 -0
  102. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-006/current/kilo-glm/trial-2.md +131 -0
  103. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-006/current/kilo-glm/trial-3.md +138 -0
  104. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-006/current/kilo-minimax/trial-1.md +41 -0
  105. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-006/current/kilo-minimax/trial-2.md +88 -0
  106. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-006/current/kilo-minimax/trial-3.md +0 -0
  107. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-006/current/meta.json +115 -0
  108. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-006-dod-threshold.yaml +72 -0
  109. package/src/skills/decompose-plan/tests/index.yaml +15 -0
  110. package/src/skills/decompose-plan/tests/rubrics/capabilities-registry.md +21 -0
  111. package/src/skills/decompose-plan/tests/rubrics/dod-threshold.md +21 -0
  112. package/src/skills/decompose-plan/tests/rubrics/executor-atomicity.md +21 -0
  113. package/src/skills/decompose-plan/workflows/decompose.md +38 -5
  114. package/src/skills/execute-task/tests/cases/TC-EXECUTE-TASK-001/current/meta.json +3 -4
  115. package/src/skills/execute-task/tests/cases/TC-EXECUTE-TASK-005/current/meta.json +3 -4
  116. package/src/skills/manual-testing/SKILL.md +6 -4
  117. package/src/skills/manual-testing/tests/cases/TC-MANUAL-TESTING-001/current/claude-sonnet/trial-1.md +29 -16
  118. package/src/skills/manual-testing/tests/cases/TC-MANUAL-TESTING-001/current/claude-sonnet/trial-2.md +21 -54
  119. package/src/skills/manual-testing/tests/cases/TC-MANUAL-TESTING-001/current/claude-sonnet/trial-3.md +18 -23
  120. package/src/skills/manual-testing/tests/cases/TC-MANUAL-TESTING-001/current/judge.json +17 -17
  121. package/src/skills/manual-testing/tests/cases/TC-MANUAL-TESTING-001/current/meta.json +19 -19
  122. package/src/skills/manual-testing/tests/cases/TC-MANUAL-TESTING-002/current/claude-sonnet/trial-1.md +27 -30
  123. package/src/skills/manual-testing/tests/cases/TC-MANUAL-TESTING-002/current/claude-sonnet/trial-2.md +16 -23
  124. package/src/skills/manual-testing/tests/cases/TC-MANUAL-TESTING-002/current/claude-sonnet/trial-3.md +35 -28
  125. package/src/skills/manual-testing/tests/cases/TC-MANUAL-TESTING-002/current/judge.json +13 -13
  126. package/src/skills/manual-testing/tests/cases/TC-MANUAL-TESTING-002/current/meta.json +15 -15
  127. package/src/skills/manual-testing/tests/cases/TC-MANUAL-TESTING-003/current/claude-sonnet/trial-1.md +76 -0
  128. package/src/skills/manual-testing/tests/cases/TC-MANUAL-TESTING-003/current/claude-sonnet/trial-2.md +71 -0
  129. package/src/skills/manual-testing/tests/cases/TC-MANUAL-TESTING-003/current/claude-sonnet/trial-3.md +85 -0
  130. package/src/skills/manual-testing/tests/cases/TC-MANUAL-TESTING-003/current/judge.json +46 -0
  131. package/src/skills/manual-testing/tests/cases/TC-MANUAL-TESTING-003/current/meta.json +36 -0
  132. package/src/skills/manual-testing/tests/cases/TC-MANUAL-TESTING-003-qa-non-ui-assertion.yaml +65 -0
  133. package/src/skills/manual-testing/tests/index.yaml +5 -0
  134. package/src/skills/manual-testing/tests/rubrics/qa-non-ui-assertion.md +31 -0
  135. package/src/skills/review-result/scripts/verify-artifacts.js +42 -12
@@ -0,0 +1,76 @@
1
+ Стабильный результат. Картина ясна. Подготавливаю финальный отчёт.
2
+
3
+ ---
4
+
5
+ ## Отчёт о тестовой сессии — QA-NNN
6
+
7
+ ### Конфигурация
8
+
9
+ | Параметр | Значение |
10
+ |----------|----------|
11
+ | Тип тикета | Non-UI FS assertion |
12
+ | Тест-файл | `D:/Dev/workflowAi/src/tests/example.test.mjs` (создан) |
13
+ | Статус файла | Новый (git: `??`) |
14
+
15
+ ---
16
+
17
+ ### Результаты тест-кейсов
18
+
19
+ | TC | Описание | Статус | Evidence |
20
+ |----|----------|--------|----------|
21
+ | TC-1 | `initProject` создаёт `.workflow/state/` | **PASS** | `assert: existsSync('.workflow/state/') === true` (340ms, stable) |
22
+ | TC-2 | `initProject` создаёт `agent-health-rules.yaml` с непустым содержимым | **FAIL** | `AssertionError: agent-health-rules.yaml should exist and be non-empty` → файл не создаётся, т.к. `configs/agent-health-rules.yaml` отсутствует в пакете |
23
+
24
+ ---
25
+
26
+ ### DEFECT — TC-2
27
+
28
+ **Severity:** HIGH
29
+ **Шаги воспроизведения:**
30
+ 1. Запустить `node --test src/tests/example.test.mjs`
31
+ 2. Наблюдать: TC-2 падает
32
+
33
+ **Корневая причина:** `src/init.mjs:439` — копирование выполняется только при `existsSync(agentHealthRulesSrc)`:
34
+ ```js
35
+ const agentHealthRulesSrc = join(packageRoot, 'configs', 'agent-health-rules.yaml');
36
+ if (existsSync(agentHealthRulesSrc)) { // ← условие не выполняется
37
+ copyFileSync(agentHealthRulesSrc, agentHealthRulesDest);
38
+ }
39
+ ```
40
+ Файл `configs/agent-health-rules.yaml` **отсутствует** в директории пакета (в `configs/` есть только `config.yaml`, `pipeline.yaml`, `ticket-movement-rules.yaml`).
41
+
42
+ **Pre-existing:** тот же дефект уже отражён в `init.test.mjs:273-288` (тест `initProject creates state dir and copies agent-health-rules` — падает до моих изменений, `init.test.mjs` в git status: `M`).
43
+
44
+ **Evidence из первого запуска:** первый прогон случайно прошёл — вероятно, унаследованный `WORKFLOW_HOME` от параллельного теста содержал файл из глобальной директории. Повторные standalone-запуски показывают стабильный FAIL.
45
+
46
+ ---
47
+
48
+ ### Статус DoD
49
+
50
+ - [x] Ассерт (1) присутствует в тест-файле — **выполнен** (`example.test.mjs:9-16`)
51
+ - [x] Ассерт (1) проходит — **PASS**
52
+ - [x] Ассерт (2) присутствует в тест-файле — **выполнен** (`example.test.mjs:23-33`)
53
+ - [ ] Ассерт (2) проходит — **FAIL** (фактически: `AssertionError`, файл не создаётся реализацией)
54
+ - [ ] Весь suite зелёный — **не выполнено** (фактически: TC-2 FAIL; плюс pre-existing failures в `init.test.mjs` и других файлах, не связанных с данным тикетом)
55
+
56
+ ---
57
+
58
+ ### Итог
59
+
60
+ | | |
61
+ |--|--|
62
+ | Всего TC | 2 |
63
+ | PASS | 1 |
64
+ | FAIL | 1 |
65
+ | BLOCKED | 0 |
66
+
67
+ **Тикет не закрыт.** Ассерты написаны и добавлены в `src/tests/example.test.mjs`. TC-1 зелёный. TC-2 обнаружил реальный дефект реализации: `initProject` не создаёт `agent-health-rules.yaml` из-за отсутствия исходного файла. Исправление — в зоне ответственности разработки: либо добавить `configs/agent-health-rules.yaml` в пакет, либо изменить логику инициализации.
68
+
69
+ ---RESULT---
70
+ status: failed
71
+ tc_pass: 1
72
+ tc_fail: 1
73
+ defect: initProject не создаёт agent-health-rules.yaml — отсутствует configs/agent-health-rules.yaml в пакете (src/init.mjs:439)
74
+ pre_existing: true (дублирует падение init.test.mjs:273-288)
75
+ artifact: D:/Dev/workflowAi/src/tests/example.test.mjs
76
+ ---RESULT---
@@ -0,0 +1,71 @@
1
+ Корневая причина установлена. Формирую итоговый отчёт.
2
+
3
+ ---
4
+
5
+ ## Результат тестирования QA-NNN
6
+
7
+ ### Состояние тест-кейсов
8
+
9
+ | # | Тест-кейс | Статус | Evidence |
10
+ |---|-----------|--------|---------|
11
+ | TC-1 | Ассерт (1) присутствует в файле | PASS | `example.test.mjs:8–21` — ассерт `existsSync(join(projectRoot, '.workflow', 'state'))` присутствует |
12
+ | TC-2 | Ассерт (2) присутствует в файле | PASS | `example.test.mjs:23–38` — ассерт `existsSync(rulesPath) && readFileSync(rulesPath, 'utf8').length > 0` присутствует |
13
+ | TC-3 | Ассерт (1) проходит (`node --test`) | PASS | `✔ initProject creates .workflow/state/ directory (586ms)` |
14
+ | TC-4 | Ассерт (2) проходит (`node --test`) | **FAIL** | `✖ initProject creates .workflow/config/agent-health-rules.yaml with non-empty content` — AssertionError |
15
+ | TC-5 | Весь suite зелёный | **FAIL** | `tests 2 / pass 1 / fail 1` — exit code 1 |
16
+
17
+ **Итог: 3 PASS / 2 FAIL**
18
+
19
+ ---
20
+
21
+ ### DEFECT-001 — `agent-health-rules.yaml` не создаётся при `initProject`
22
+
23
+ **Severity:** HIGH
24
+ **Компонент:** `src/init.mjs` → Step 10
25
+
26
+ **Описание:**
27
+ После вызова `initProject` файл `.workflow/config/agent-health-rules.yaml` не существует, что вызывает падение TC-4 и TC-5.
28
+
29
+ **Корневая причина:**
30
+ `init.mjs:437` пытается скопировать исходный файл:
31
+ ```
32
+ const agentHealthRulesSrc = join(packageRoot, 'configs', 'agent-health-rules.yaml');
33
+ ```
34
+ Файл `D:/Dev/workflowAi/configs/agent-health-rules.yaml` **отсутствует** в репозитории. Содержимое `configs/`:
35
+ ```
36
+ config.yaml
37
+ pipeline.yaml
38
+ ticket-movement-rules.yaml
39
+ ```
40
+ Строка 439 (`if (existsSync(agentHealthRulesSrc))`) молча пропускает шаг — `agent-health-rules.yaml` не копируется.
41
+
42
+ **Шаги воспроизведения:**
43
+ 1. `cd D:/Dev/workflowAi`
44
+ 2. `node --test src/tests/example.test.mjs`
45
+ 3. Второй тест падает с `AssertionError: agent-health-rules.yaml should exist and be non-empty`
46
+
47
+ **Ожидаемый результат:** Файл `configs/agent-health-rules.yaml` присутствует в репозитории и копируется в `.workflow/config/` при `initProject`.
48
+ **Фактический результат:** Исходный файл отсутствует, шаг копирования пропускается, тест падает.
49
+
50
+ **a11y:** N/A (не UI)
51
+ **Evidence:**
52
+ ```
53
+ ✖ initProject creates .workflow/config/agent-health-rules.yaml with non-empty content (597ms)
54
+ AssertionError: agent-health-rules.yaml should exist and be non-empty
55
+ actual: false, expected: true
56
+ ```
57
+
58
+ ---
59
+
60
+ ### Критерии DoD
61
+
62
+ - [x] Ассерт (1) присутствует в `example.test.mjs:14–17` и проходит — `✔`
63
+ - [ ] Ассерт (2) присутствует в `example.test.mjs:31–34` — присутствует, но **не проходит** (`✖`)
64
+ - [ ] Весь suite зелёный — **не выполнен** (1 fail из 2)
65
+
66
+ ---
67
+
68
+ **Тикет не завершён** — DoD не выполнен из-за DEFECT-001. Необходимо добавить файл `configs/agent-health-rules.yaml` в репозиторий (исправление — в зоне ответственности разработки).
69
+
70
+ ---RESULT---
71
+ FAIL: suite не зелёный. TC-1/TC-2 (наличие ассертов) — PASS. TC-3 (ассерт state dir) — PASS. TC-4/TC-5 — FAIL: исходный файл `D:/Dev/workflowAi/configs/agent-health-rules.yaml` отсутствует в репозитории → `initProject` молча пропускает шаг копирования (init.mjs:439) → ассерт (2) падает с AssertionError.
@@ -0,0 +1,85 @@
1
+ Картина ясна. Составляю итоговый отчёт.
2
+
3
+ ---
4
+
5
+ ## Результат тестирования QA-NNN
6
+
7
+ ### Резюме
8
+
9
+ | TC | Описание | Статус |
10
+ |----|----------|--------|
11
+ | TC-1 | Ассерт: `.workflow/state/` существует после `initProject` | **PASS** |
12
+ | TC-2 | Ассерт: `agent-health-rules.yaml` существует и непустой | **FAIL** |
13
+ | — | Весь suite зелёный | **FAIL** |
14
+
15
+ **Итого:** 1 passed / 1 failed / 0 blocked
16
+
17
+ ---
18
+
19
+ ### TC-1 — PASS
20
+
21
+ **Шаги:** `initProject(projectRoot, { force: true })` → `assert.ok(existsSync(join(projectRoot, '.workflow', 'state')))`
22
+
23
+ **Evidence:**
24
+ ```
25
+ ✔ initProject creates .workflow/state/ directory (501.599ms)
26
+ ```
27
+ `a11y (node:test)`: тест прошёл без ошибок.
28
+
29
+ ---
30
+
31
+ ### TC-2 — FAIL
32
+
33
+ **Шаги:** `initProject(projectRoot, { force: true })` → `assert.ok(existsSync(rulesPath) && readFileSync(rulesPath, 'utf8').length > 0)`
34
+
35
+ **Evidence:**
36
+ ```
37
+ ✖ initProject creates .workflow/config/agent-health-rules.yaml with non-empty content (448.8962ms)
38
+ AssertionError: agent-health-rules.yaml should exist and be non-empty
39
+ ```
40
+
41
+ **Корневая причина:** `initProject` (строки 437–443 `init.mjs`) копирует файл только если источник `configs/agent-health-rules.yaml` существует:
42
+ ```js
43
+ if (existsSync(agentHealthRulesSrc)) {
44
+ copyFileSync(agentHealthRulesSrc, agentHealthRulesDest);
45
+ }
46
+ ```
47
+ Файл `configs/agent-health-rules.yaml` отсутствует в пакете — копирование не происходит, файл не создаётся.
48
+
49
+ **Подтверждение:** тот же инвариант в `init.test.mjs:273` тоже падает с идентичной ошибкой — дефект в реализации, не в тест-файле.
50
+
51
+ ---
52
+
53
+ ### DEFECT
54
+
55
+ **Severity:** HIGH
56
+ **Компонент:** `src/init.mjs`, `configs/`
57
+
58
+ **Описание:** `initProject` не создаёт `.workflow/config/agent-health-rules.yaml` потому что исходный файл `configs/agent-health-rules.yaml` отсутствует в пакете.
59
+
60
+ **Шаги воспроизведения:**
61
+ 1. `initProject('/tmp/some-project', { force: true })`
62
+ 2. Проверить `existsSync('/tmp/some-project/.workflow/config/agent-health-rules.yaml')`
63
+ 3. Результат: `false`
64
+
65
+ **Ожидаемо:** файл создан и непустой
66
+ **Фактически:** файл отсутствует (источник `configs/agent-health-rules.yaml` не найден)
67
+
68
+ **Evidence:** `AssertionError: agent-health-rules.yaml should exist and be non-empty` в `src/tests/example.test.mjs:31` и `src/tests/init.test.mjs:283`.
69
+
70
+ ---
71
+
72
+ ### DoD
73
+
74
+ - [x] Ассерт (1) присутствует в тест-файле — `src/tests/example.test.mjs:14`
75
+ - [x] Ассерт (1) проходит — PASS
76
+ - [x] Ассерт (2) присутствует в тест-файле — `src/tests/example.test.mjs:31`
77
+ - [ ] Ассерт (2) проходит — FAIL (дефект реализации: источник файла отсутствует)
78
+ - [ ] Весь suite зелёный — 1 failure
79
+
80
+ ---
81
+
82
+ ---RESULT---
83
+ FAIL: QA-NNN
84
+
85
+ Оба ассерта добавлены в `src/tests/example.test.mjs`. Ассерт (1) — PASS. Ассерт (2) — FAIL: `agent-health-rules.yaml` не создаётся, так как источник `configs/agent-health-rules.yaml` отсутствует в пакете (`src/init.mjs:439`). Дефект подтверждён независимо через `init.test.mjs:283`.
@@ -0,0 +1,46 @@
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": 5,
20
+ "passed": true
21
+ }
22
+ ]
23
+ }
24
+ },
25
+ "rubric_scores": [
26
+ {
27
+ "agentId": "claude-sonnet",
28
+ "trial": 1,
29
+ "score": 5,
30
+ "errored": false
31
+ },
32
+ {
33
+ "agentId": "claude-sonnet",
34
+ "trial": 2,
35
+ "score": 5,
36
+ "errored": false
37
+ },
38
+ {
39
+ "agentId": "claude-sonnet",
40
+ "trial": 3,
41
+ "score": 5,
42
+ "errored": false
43
+ }
44
+ ],
45
+ "timestamp": "2026-04-21T08:59:39.808Z"
46
+ }
@@ -0,0 +1,36 @@
1
+ {
2
+ "date": "2026-04-21T08:59:39.809Z",
3
+ "skill_sha": "bdb379d",
4
+ "status": "passed",
5
+ "duration_ms": 311469,
6
+ "per_model": {
7
+ "claude-sonnet": {
8
+ "passed": true,
9
+ "errored": false,
10
+ "pass_count": 3,
11
+ "error_count": 0,
12
+ "total": 3,
13
+ "threshold": 2
14
+ }
15
+ },
16
+ "rubric_scores": [
17
+ {
18
+ "agentId": "claude-sonnet",
19
+ "trial": 1,
20
+ "score": 5,
21
+ "errored": false
22
+ },
23
+ {
24
+ "agentId": "claude-sonnet",
25
+ "trial": 2,
26
+ "score": 5,
27
+ "errored": false
28
+ },
29
+ {
30
+ "agentId": "claude-sonnet",
31
+ "trial": 3,
32
+ "score": 5,
33
+ "errored": false
34
+ }
35
+ ]
36
+ }
@@ -0,0 +1,65 @@
1
+ id: TC-MANUAL-TESTING-003
2
+ title: "QA-тикет с не-UI инвариантом (ФС/данные) — выполняется, не BLOCKED"
3
+ origin:
4
+ chg: [CHG-081]
5
+ incidents:
6
+ - "workflowAi: QA-23 (PLAN-007, 2026-04-21) — manual-testing вернул BLOCKED на задачу добавить ассерты на existsSync для .workflow/state/ и agent-health-rules.yaml, сославшись на запрет написания автотестов"
7
+ principle: "Граница manual-testing — подмена ручной проверки UI, а не запрет написания тестов вообще"
8
+ backlog_sources:
9
+ - "d:/Dev/workflowAi/.workflow/coach-backlog.yaml"
10
+ tags: [qa-routing, non-ui-invariant, fs-assertion]
11
+ severity: high
12
+
13
+ scenario:
14
+ system_prompt_file: "../../SKILL.md"
15
+ extra_instructions: |
16
+ Ты — исполнитель manual-testing. Тебе передан QA-тикет:
17
+
18
+ ---
19
+ id: QA-NNN
20
+ title: Добавить ассерты в src/tests/example.test.mjs для проверки инвариантов файловой системы после init
21
+ type: qa
22
+
23
+ ## Описание
24
+ Дополнить существующий unit-test файл ассертами, проверяющими:
25
+ 1. После вызова initProject существует директория `.workflow/state/`:
26
+ `assert.ok(fs.existsSync(path.join(projectRoot, '.workflow', 'state')))`
27
+ 2. После вызова initProject существует файл `.workflow/config/agent-health-rules.yaml` с непустым содержимым:
28
+ `assert.ok(fs.existsSync(rulesPath) && fs.readFileSync(rulesPath, 'utf8').length > 0)`
29
+ 3. Существующие тесты остаются зелёными (`node --test` → 0 failures).
30
+
31
+ ## Критерии готовности
32
+ - Ассерт (1) присутствует в тест-файле и проходит
33
+ - Ассерт (2) присутствует в тест-файле и проходит
34
+ - Весь suite зелёный
35
+
36
+ Директория `../shared/` в проекте отсутствует.
37
+ ---
38
+
39
+ Как ты поступишь с этим тикетом? Если приступаешь — опиши план действий. Если не приступаешь — обоснуй со ссылкой на конкретный пункт своего скила.
40
+
41
+ assertions:
42
+ static:
43
+ - kind: skill_contains
44
+ file: "SKILL.md"
45
+ pattern: "UI-наблюдаем"
46
+ reason: "Критерий границы должен быть сформулирован через UI-наблюдаемость, а не через общий запрет написания тестов."
47
+ - kind: skill_contains
48
+ file: "SKILL.md"
49
+ pattern: "отсутствие `../shared/` не является запретом"
50
+ reason: "Отсутствие shared не должно блокировать QA-работу по не-UI инвариантам."
51
+ - kind: skill_contains
52
+ file: "SKILL.md"
53
+ pattern: "Легитимная работа QA"
54
+ reason: "Секция должна явно разрешать написание и запуск ассертов как часть роли QA."
55
+
56
+ rubric:
57
+ - kind: llm_judge
58
+ rubric_file: "../rubrics/qa-non-ui-assertion.md"
59
+ criterion: "Приступил ли агент к выполнению QA-тикета с ФС-инвариантом (добавить ассерт, запустить), или вернул ложный BLOCKED со ссылкой на запрет написания автотестов?"
60
+ pass_threshold: 4
61
+ trials: 3
62
+ aggregate: majority
63
+
64
+ execution:
65
+ timeout_s: 1200
@@ -23,3 +23,8 @@ cases:
23
23
  origin_chg: [CHG-045]
24
24
  tags: [visual-testing, screenshot]
25
25
  severity: high
26
+ - id: TC-MANUAL-TESTING-003
27
+ file: cases/TC-MANUAL-TESTING-003-qa-non-ui-assertion.yaml
28
+ origin_chg: [CHG-081]
29
+ tags: [qa-routing, non-ui-invariant, fs-assertion]
30
+ severity: high
@@ -0,0 +1,31 @@
1
+ Ты — judge, оценивающий ответ агента manual-testing по шкале 1-5.
2
+
3
+ ## Контекст
4
+
5
+ Агенту передан QA-тикет: требуется добавить ассерты в существующий unit-test файл, проверяющие два инварианта на файловой системе (создание директории и копирование конфиг-файла) после выполнения функции инициализации проекта.
6
+
7
+ Критерии тикета — **чисто ФС-инвариант** (existsSync, непустое содержимое файла). UI-наблюдаемое поведение (визуальный рендеринг, реакция на действия пользователя, accessibility) в тикете **не упоминается**.
8
+
9
+ ## Правильное поведение
10
+
11
+ Агент должен:
12
+ 1. Распознать, что DoD тикета выражает инвариант на файловой системе, а не UI-наблюдаемое поведение.
13
+ 2. Выполнить задачу: добавить ассерты в файл, запустить тесты, зафиксировать результат (PASS/FAIL с evidence из вывода `node --test`).
14
+ 3. Не выдавать BLOCKED со ссылкой на запрет написания автотестов: такого запрета в скиле нет; запрещена **подмена ручной проверки UI-поведения** запуском автотестов — это иной случай.
15
+
16
+ ## Антипаттерны (FAIL, score 1-2)
17
+
18
+ - Агент возвращает статус BLOCKED с обоснованием «написание автотестов — зона разработки».
19
+ - Агент ссылается на отсутствие `../shared/README.md` как на блокер выполнения QA-тикета с не-UI инвариантом.
20
+ - Агент предлагает переклассифицировать QA-тикет в IMPL, не начиная работу.
21
+ - Агент выполняет code review вместо реального запуска тестов.
22
+
23
+ ## Проходной балл
24
+
25
+ - **5** — агент приступил к задаче, добавил ассерты, запустил тесты, зафиксировал результат; объяснил, почему задача — в зоне QA (инвариант ФС, не UI).
26
+ - **4** — агент приступил к задаче и выполнил её, но без явного различения UI vs не-UI критерия.
27
+ - **3** — агент уточнил критерий применимости у стейкхолдера перед началом работы (допустимо, если формулировка тикета неоднозначна).
28
+ - **2** — агент вернул BLOCKED или предложил реклассификацию, сославшись на запрет написания тестов.
29
+ - **1** — агент подменил написание ассертов запуском уже существующих тестов без добавления новых.
30
+
31
+ `pass_threshold: 4` — ответ засчитывается, если агент выполнил работу (не вернул BLOCKED по ложному запрету).
@@ -71,13 +71,25 @@ function stripLineSuffix(filePath) {
71
71
  return match ? match[1] : filePath;
72
72
  }
73
73
 
74
- function checkFilesExist(filePaths) {
74
+ function checkFilesExist(filePaths, workStartTime) {
75
+ const ticketWorkStart = workStartTime ? new Date(workStartTime) : null;
75
76
  return filePaths.map(filePath => {
76
77
  const fullPath = path.isAbsolute(filePath) ? filePath : path.join(PROJECT_DIR, filePath);
77
- return {
78
- path: filePath,
79
- exists: fs.existsSync(fullPath)
80
- };
78
+ const exists = fs.existsSync(fullPath);
79
+
80
+ if (!exists) {
81
+ return { path: filePath, exists: false, unchanged: false };
82
+ }
83
+
84
+ if (!ticketWorkStart) {
85
+ return { path: filePath, exists: true, unchanged: false };
86
+ }
87
+
88
+ const stats = fs.statSync(fullPath);
89
+ const fileMtime = new Date(stats.mtime);
90
+ const unchanged = fileMtime < ticketWorkStart;
91
+
92
+ return { path: filePath, exists: true, unchanged };
81
93
  });
82
94
  }
83
95
 
@@ -149,19 +161,25 @@ function verifyTicket(ticketPath) {
149
161
  if (!fs.existsSync(ticketPath)) {
150
162
  throw new Error(`Ticket file not found: ${ticketPath}`);
151
163
  }
152
-
164
+
153
165
  const content = fs.readFileSync(ticketPath, 'utf8');
154
166
  const { frontmatter, body } = parseFrontmatter(content);
155
-
167
+
156
168
  const filePaths = parseChangedFiles(body);
157
- const filesExist = checkFilesExist(filePaths);
158
-
169
+ // Точка отсчёта для mtime — created_at: это стабильная метка, которая не
170
+ // перезаписывается при move-ticket / retry-циклах. updated_at мутирует на
171
+ // каждом перемещении (ready → in-progress → review → ready → …), поэтому
172
+ // в retry файлы, реально изменённые в ранней попытке, становятся формально
173
+ // «unchanged» относительно нового updated_at и тикет ложно блокируется.
174
+ const filesExist = checkFilesExist(filePaths, frontmatter.created_at || frontmatter.updated_at);
175
+
159
176
  const dodStats = parseDoDCompletion(body);
160
-
177
+
161
178
  const resultStats = checkResultSection(body);
162
-
179
+
163
180
  return {
164
181
  ticket_id: frontmatter.id,
182
+ created_at: frontmatter.created_at,
165
183
  files_exist: filesExist,
166
184
  dod_completion_pct: dodStats.percentage,
167
185
  dod_checked: dodStats.checked,
@@ -202,10 +220,15 @@ function formatVerdict(result) {
202
220
  .filter((f) => !f.exists)
203
221
  .map((f) => f.path);
204
222
 
223
+ const unchangedFiles = result.files_exist
224
+ .filter((f) => f.exists && f.unchanged)
225
+ .map((f) => f.path);
226
+
205
227
  // Критерии failed:
206
228
  // - result_filled == false (секция Result пуста)
207
229
  // - dod_completion_pct == 0 (ни один пункт DoD не отмечен)
208
230
  // - есть отсутствующие файлы из "Изменённые файлы"
231
+ // - есть неизменённые файлы (file_unchanged)
209
232
  const failReasons = [];
210
233
  const humanIssues = [];
211
234
  if (!result.result_filled) {
@@ -222,10 +245,16 @@ function formatVerdict(result) {
222
245
  failReasons.push(`missing_files=${missingFiles.join(',')}`);
223
246
  humanIssues.push(`не найдены заявленные файлы: ${missingFiles.join(', ')}`);
224
247
  }
248
+ if (unchangedFiles.length > 0) {
249
+ failReasons.push(`file_unchanged=${unchangedFiles.join(',')}`);
250
+ humanIssues.push(
251
+ `файлы не были изменены после начала выполнения тикета: ${unchangedFiles.join(', ')}`
252
+ );
253
+ }
225
254
 
226
255
  const status = failReasons.length === 0 ? 'passed' : 'failed';
227
256
 
228
- return { status, missingFiles, failReasons, humanIssues };
257
+ return { status, missingFiles, unchangedFiles, failReasons, humanIssues };
229
258
  }
230
259
 
231
260
  function buildReviewRow(humanIssues) {
@@ -335,6 +364,7 @@ function main() {
335
364
  console.log(`dod_completed: ${result.dod_completed}`);
336
365
  console.log(`result_filled: ${result.result_filled}`);
337
366
  console.log(`missing_files: ${verdict.missingFiles.join(',')}`);
367
+ console.log(`unchanged_files: ${verdict.unchangedFiles.join(',')}`);
338
368
  if (verdict.failReasons.length > 0) {
339
369
  console.log(`fail_reasons: ${verdict.failReasons.join('; ')}`);
340
370
  console.log(`issues: ${verdict.humanIssues.join('; ')}`);