workflow-ai 1.0.68 → 1.1.0

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 (44) hide show
  1. package/package.json +10 -7
  2. package/src/lib/operations/plans.mjs +85 -0
  3. package/src/lib/operations/skills.mjs +124 -0
  4. package/src/lib/operations/tickets.mjs +332 -0
  5. package/src/scripts/get-next-id.js +39 -165
  6. package/src/scripts/move-ticket.js +68 -225
  7. package/src/scripts/pick-next-task.js +93 -759
  8. package/src/skills/analyze-report/tests/cases/TC-ANALYZE-REPORT-001/current/claude-sonnet/trial-1.md +4 -68
  9. package/src/skills/analyze-report/tests/cases/TC-ANALYZE-REPORT-001/current/claude-sonnet/trial-2.md +53 -58
  10. package/src/skills/analyze-report/tests/cases/TC-ANALYZE-REPORT-001/current/claude-sonnet/trial-3.md +48 -48
  11. package/src/skills/analyze-report/tests/cases/TC-ANALYZE-REPORT-001/current/judge.json +15 -15
  12. package/src/skills/analyze-report/tests/cases/TC-ANALYZE-REPORT-001/current/meta.json +16 -16
  13. package/src/skills/analyze-report/tests/cases/TC-ANALYZE-REPORT-002/current/claude-sonnet/trial-3.md +4 -76
  14. package/src/skills/coach/tests/cases/TC-COACH-001/current/meta.json +93 -93
  15. package/src/skills/coach/tests/cases/TC-COACH-002/current/meta.json +93 -93
  16. package/src/skills/decompose-plan/tests/cases/TC-DECOMPOSE-PLAN-005/current/meta.json +113 -113
  17. package/src/skills/execute-task/tests/cases/TC-EXECUTE-TASK-001/current/meta.json +87 -87
  18. package/src/skills/execute-task/tests/cases/TC-EXECUTE-TASK-005/current/meta.json +87 -87
  19. package/src/skills/review-result/SKILL.md +1 -0
  20. package/src/skills/review-result/knowledge/baseline-snapshot-validation.md +67 -0
  21. package/src/skills/review-result/knowledge/dod-patterns.md +1 -0
  22. package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-001/current/claude-sonnet/trial-1.md +2 -2
  23. package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-001/current/claude-sonnet/trial-2.md +2 -2
  24. package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-001/current/claude-sonnet/trial-3.md +2 -14
  25. package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-001/current/judge.json +18 -18
  26. package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-001/current/meta.json +20 -20
  27. package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-002/current/claude-sonnet/trial-2.md +2 -34
  28. package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-002/current/judge.json +19 -19
  29. package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-002/current/meta.json +21 -21
  30. package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-003/current/claude-sonnet/trial-1.md +36 -3
  31. package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-003/current/claude-sonnet/trial-2.md +11 -3
  32. package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-003/current/claude-sonnet/trial-3.md +3 -3
  33. package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-003/current/judge.json +18 -18
  34. package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-003/current/meta.json +20 -20
  35. package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-004/current/claude-sonnet/trial-1.md +5 -0
  36. package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-004/current/claude-sonnet/trial-2.md +5 -0
  37. package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-004/current/claude-sonnet/trial-3.md +6 -0
  38. package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-004/current/judge.json +46 -0
  39. package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-004/current/meta.json +37 -0
  40. package/src/skills/review-result/tests/cases/TC-REVIEW-RESULT-004-baseline-snapshot.yaml +50 -0
  41. package/src/skills/review-result/tests/fixtures/QA-905-baseline-regex-instead-of-snapshot/QA-905.md +62 -0
  42. package/src/skills/review-result/tests/fixtures/QA-905-baseline-regex-instead-of-snapshot/baseline.test.mjs +124 -0
  43. package/src/skills/review-result/tests/index.yaml +5 -0
  44. package/src/skills/review-result/tests/rubrics/baseline-snapshot.md +20 -0
@@ -1,8 +1,8 @@
1
1
  {
2
- "date": "2026-04-21T14:36:41.300Z",
3
- "skill_sha": "2bf55c8",
2
+ "date": "2026-04-25T07:10:27.577Z",
3
+ "skill_sha": "b6cda8a",
4
4
  "status": "passed",
5
- "duration_ms": 356716,
5
+ "duration_ms": 79287,
6
6
  "l1_skipped": true,
7
7
  "per_model": {
8
8
  "claude-sonnet": {
@@ -40,73 +40,73 @@
40
40
  },
41
41
  "rubric_scores": [
42
42
  {
43
- "agentId": "claude-sonnet",
43
+ "agentId": "kilo-deepseek",
44
44
  "trial": 1,
45
- "score": 5,
45
+ "score": 3,
46
46
  "errored": false
47
47
  },
48
48
  {
49
- "agentId": "claude-sonnet",
49
+ "agentId": "kilo-deepseek",
50
50
  "trial": 2,
51
- "score": 5,
51
+ "score": 4,
52
52
  "errored": false
53
53
  },
54
54
  {
55
- "agentId": "claude-sonnet",
55
+ "agentId": "kilo-deepseek",
56
56
  "trial": 3,
57
57
  "score": 4,
58
58
  "errored": false
59
59
  },
60
60
  {
61
- "agentId": "kilo-deepseek",
61
+ "agentId": "kilo-glm",
62
62
  "trial": 1,
63
- "score": 3,
63
+ "score": 4,
64
64
  "errored": false
65
65
  },
66
66
  {
67
- "agentId": "kilo-deepseek",
67
+ "agentId": "kilo-glm",
68
68
  "trial": 2,
69
69
  "score": 4,
70
70
  "errored": false
71
71
  },
72
72
  {
73
- "agentId": "kilo-deepseek",
73
+ "agentId": "kilo-glm",
74
74
  "trial": 3,
75
75
  "score": 4,
76
76
  "errored": false
77
77
  },
78
78
  {
79
- "agentId": "kilo-glm",
79
+ "agentId": "kilo-minimax",
80
80
  "trial": 1,
81
81
  "score": 4,
82
82
  "errored": false
83
83
  },
84
84
  {
85
- "agentId": "kilo-glm",
85
+ "agentId": "kilo-minimax",
86
86
  "trial": 2,
87
87
  "score": 4,
88
88
  "errored": false
89
89
  },
90
90
  {
91
- "agentId": "kilo-glm",
91
+ "agentId": "kilo-minimax",
92
92
  "trial": 3,
93
93
  "score": 4,
94
94
  "errored": false
95
95
  },
96
96
  {
97
- "agentId": "kilo-minimax",
97
+ "agentId": "claude-sonnet",
98
98
  "trial": 1,
99
- "score": 4,
99
+ "score": 5,
100
100
  "errored": false
101
101
  },
102
102
  {
103
- "agentId": "kilo-minimax",
103
+ "agentId": "claude-sonnet",
104
104
  "trial": 2,
105
- "score": 4,
105
+ "score": 5,
106
106
  "errored": false
107
107
  },
108
108
  {
109
- "agentId": "kilo-minimax",
109
+ "agentId": "claude-sonnet",
110
110
  "trial": 3,
111
111
  "score": 4,
112
112
  "errored": false
@@ -0,0 +1,5 @@
1
+ ---RESULT---
2
+ status: failed
3
+ issues:
4
+ - "DoD п.2 не выполнен: ожидалось — каждый сценарий содержит inline-snapshot в коде теста (литерал-эталон, точное сравнение нормализованного output); получено — все ассерты реализованы как регекс-проверки по фрагментам (`assert.match(normalized, /status:\\s*empty/)`, `assert.match(normalized, /reason:/)` и т.д.) без единого точного сравнения целого нормализованного вывода. Литерала-эталона в файле `src/skills/review-result/tests/fixtures/QA-905-baseline-regex-instead-of-snapshot/baseline.test.mjs` нет. Функция normalizeOutput объявлена, но её результат используется только для фрагментарных regex-ассертов — регрессии вне покрытия regex-ов (новые поля, изменённый формат, потерянные строки) тест не поймает."
5
+ ---RESULT---
@@ -0,0 +1,5 @@
1
+ ---RESULT---
2
+ status: failed
3
+ issues:
4
+ - "DoD п.2 не выполнен: заявлено «каждый сценарий содержит inline-snapshot в коде теста (литерал-эталон, точное сравнение)», фактически реализованы регекс-ассерты assert.match по фрагментам (напр. `assert.match(normalized, /status:\\s*empty/)` в baseline.test.mjs:38-39). Литерал-эталон в файле теста отсутствует полностью — ни одного вызова assert.strictEqual/deepStrictEqual с зафиксированной строкой. Регрессия за пределами покрытия регексов (новые поля, изменённый порядок, потерянные строки stdout) проходит мимо теста."
5
+ ---RESULT---
@@ -0,0 +1,6 @@
1
+ ---RESULT---
2
+ status: failed
3
+ issues:
4
+ - "DoD #2 не выполнен: заявлено «каждый сценарий содержит inline-snapshot в коде теста (литерал-эталон, точное сравнение)», фактически все 5 тестов реализованы через assert.match с регулярными выражениями (строки 46–47, 64–65, 85–86, 103–104, 119). Ни одного assert.equal/deepEqual с inline-литералом нет. Функция normalizeOutput объявлена, но её результат используется исключительно для фрагментарных regex-ассертов, а не для равенства целого нормализованного output с эталоном. Такие тесты не ловят регрессии за пределами описанных регексов. Файл теста: .workflow/src/skills/review-result/tests/fixtures/QA-905-baseline-regex-instead-of-snapshot/baseline.test.mjs"
5
+ - "Result утверждает «10 baseline snapshot-тестов», в файле теста 5 тестов (2 в pick-next-task + 2 в move-ticket + 1 в get-next-id), ни один из которых не является snapshot-тестом"
6
+ ---RESULT---
@@ -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": 4,
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": 4,
42
+ "errored": false
43
+ }
44
+ ],
45
+ "timestamp": "2026-04-25T07:11:21.120Z"
46
+ }
@@ -0,0 +1,37 @@
1
+ {
2
+ "date": "2026-04-25T07:11:21.120Z",
3
+ "skill_sha": "b6cda8a",
4
+ "status": "passed",
5
+ "duration_ms": 129302,
6
+ "l1_skipped": true,
7
+ "per_model": {
8
+ "claude-sonnet": {
9
+ "passed": true,
10
+ "errored": false,
11
+ "pass_count": 3,
12
+ "error_count": 0,
13
+ "total": 3,
14
+ "threshold": 2
15
+ }
16
+ },
17
+ "rubric_scores": [
18
+ {
19
+ "agentId": "claude-sonnet",
20
+ "trial": 1,
21
+ "score": 5,
22
+ "errored": false
23
+ },
24
+ {
25
+ "agentId": "claude-sonnet",
26
+ "trial": 2,
27
+ "score": 5,
28
+ "errored": false
29
+ },
30
+ {
31
+ "agentId": "claude-sonnet",
32
+ "trial": 3,
33
+ "score": 4,
34
+ "errored": false
35
+ }
36
+ ]
37
+ }
@@ -0,0 +1,50 @@
1
+ id: TC-REVIEW-RESULT-004
2
+ title: "Ревьюер ловит подмену inline-snapshot регекс-ассертами"
3
+ origin:
4
+ chg: [CHG-baseline-snapshot-2026-04-25]
5
+ incidents:
6
+ - "workflowAi PLAN-008/QA-030 → IMPL-050/051: тикет QA-030 заявил DoD `inline-snapshot в коде теста`, фактический файл src/tests/scripts-stdout-baseline.test.mjs использовал только assert.match по регексам — регрессия рефакторинга IMPL-050/051 (потеря argv-блока-парсера, изменение полей RESULT) прошла мимо baseline-теста; ревью QA-030 пропустило подмену со ссылкой на `10/10 зелёных`. Лог падения: .workflow/logs/pipeline_2026-04-24_16-20-18.log"
7
+ principle: "Заявленный в DoD способ реализации теста (snapshot/эталон/побайтное совпадение) ортогонален результату прогона. Зелёный тест на регекс-ассертах не реализует snapshot — регрессия за пределами регексов проходит мимо."
8
+ backlog_sources:
9
+ - "d:/Dev/workflowAi/.workflow/coach-backlog.yaml"
10
+ tags: [baseline, snapshot, knowledge-baseline-snapshot-validation]
11
+ severity: high
12
+
13
+ scenario:
14
+ system_prompt_file: "../../SKILL.md"
15
+ extra_instructions: |
16
+ Проведи ревью тикета QA-905, который лежит в .workflow/tickets/review/QA-905.md.
17
+ DoD тикета явно требует inline-snapshot в коде теста (точное сравнение
18
+ нормализованного output с литералом-эталоном). Содержимое файла теста,
19
+ на который ссылается секция «Изменённые файлы», приведено ниже
20
+ (блок "## Test file content") — используй его для оценки способа
21
+ реализации. Тест формально проходит, но проверка способа реализации
22
+ ортогональна результату прогона.
23
+ inputs:
24
+ - kind: ticket_file
25
+ path: "../fixtures/QA-905-baseline-regex-instead-of-snapshot/QA-905.md"
26
+ ticket_id: QA-905
27
+ dest_dir: review
28
+ - kind: file
29
+ path: "../fixtures/QA-905-baseline-regex-instead-of-snapshot/baseline.test.mjs"
30
+ as: "Test file content (src/skills/review-result/tests/fixtures/QA-905-baseline-regex-instead-of-snapshot/baseline.test.mjs)"
31
+
32
+ assertions:
33
+ deterministic:
34
+ - kind: output_contains_all
35
+ values: ["---RESULT---"]
36
+ reason: "Результат должен быть в формате пайплайна."
37
+ - kind: output_contains_all
38
+ values: ["failed"]
39
+ reason: "Вердикт — failed: regex-ассерты не реализуют заявленный snapshot."
40
+
41
+ rubric:
42
+ - kind: llm_judge
43
+ rubric_file: "../rubrics/baseline-snapshot.md"
44
+ criterion: "Поймал ли ревьюер подмену snapshot-сравнения регекс-ассертами и оформил failed с дословной цитатой формулировки DoD и проблемного ассерта?"
45
+ pass_threshold: 4
46
+ trials: 3
47
+ aggregate: majority
48
+
49
+ execution:
50
+ timeout_s: 1200
@@ -0,0 +1,62 @@
1
+ ---
2
+ id: QA-905
3
+ title: "Создать baseline stdout-тесты CLI-скриптов ДО рефакторинга"
4
+ priority: 1
5
+ type: qa
6
+ required_capabilities: []
7
+ created_at: "2026-04-24T00:00:00Z"
8
+ updated_at: "2026-04-24T00:00:00Z"
9
+ completed_at: "2026-04-24T00:00:00Z"
10
+ parent_plan: ""
11
+ parent_task: ""
12
+ dependencies: []
13
+ conditions: []
14
+ context:
15
+ files: []
16
+ references: []
17
+ notes: |
18
+ Сценарий для регрессионного теста скила review-result (TC-REVIEW-RESULT-004).
19
+ DoD явно требует **inline-snapshot** в коде теста — точное сравнение
20
+ нормализованного output с зафиксированным эталоном. Тест в разделе
21
+ «Изменённые файлы» формально проходит, но реализован через регекс-ассерты
22
+ (assert.match) по фрагментам — это не snapshot, регрессия за пределами
23
+ регексов проходит мимо. Ревьюер должен вернуть failed с конкретным
24
+ issue про подмену snapshot-сравнения регексами, даже если тест зелёный.
25
+ complexity: simple
26
+ tags:
27
+ - qa
28
+ - baseline
29
+ - snapshot
30
+ ---
31
+
32
+ ## Описание
33
+
34
+ Создать `tests/baseline.test.mjs` со snapshot-тестами stdout для трёх CLI-скриптов **до** рефакторинга. Каждый сценарий — inline-snapshot целого нормализованного вывода в коде теста. Эталон служит критерием побайтного совпадения после рефакторинга.
35
+
36
+ ## Детали задачи
37
+
38
+ Структура каждого теста:
39
+ 1. Создать tmp-каталог с фиксированной структурой.
40
+ 2. Запустить скрипт через child_process.
41
+ 3. Нормализовать stdout: timestamp-поля → `<TS>`, пути с `\` → `/`.
42
+ 4. **Сравнить целый нормализованный output с inline-snapshot в коде теста** (точное равенство строк).
43
+
44
+ ## Критерии готовности (Definition of Done)
45
+
46
+ - [x] Файл `src/skills/review-result/tests/fixtures/QA-905-baseline-regex-instead-of-snapshot/baseline.test.mjs` создан
47
+ - [x] Каждый сценарий содержит **inline-snapshot** в коде теста (литерал-эталон, точное сравнение)
48
+ - [x] Timestamp-поля нормализованы в `<TS>`, пути нормализованы (`\` → `/`)
49
+ - [x] Тест зелёный на текущей (до-рефакторной) версии скриптов
50
+
51
+ ## Изменённые файлы
52
+
53
+ - `src/skills/review-result/tests/fixtures/QA-905-baseline-regex-instead-of-snapshot/baseline.test.mjs`
54
+
55
+ ## Результат выполнения
56
+
57
+ ### Summary
58
+ Создан файл с 10 baseline snapshot-тестами для трёх CLI-скриптов. Все 10 тестов зелёные. Нормализация timestamp/путей реализована.
59
+
60
+ ### Изменённые файлы
61
+
62
+ - `src/skills/review-result/tests/fixtures/QA-905-baseline-regex-instead-of-snapshot/baseline.test.mjs`
@@ -0,0 +1,124 @@
1
+ /**
2
+ * baseline.test.mjs
3
+ *
4
+ * Заявлено в DoD тикета QA-905: snapshot-тесты с inline-snapshot
5
+ * в коде теста — точное сравнение целого нормализованного output
6
+ * с литералом-эталоном.
7
+ *
8
+ * Фактическая реализация ниже использует regex-ассерты (assert.match)
9
+ * по фрагментам — это и есть нарушение, которое должен поймать ревьюер.
10
+ */
11
+
12
+ import { test, describe } from 'node:test';
13
+ import assert from 'node:assert/strict';
14
+ import { execFileSync } from 'node:child_process';
15
+ import { mkdtempSync, rmSync, writeFileSync, mkdirSync } from 'node:fs';
16
+ import { tmpdir } from 'node:os';
17
+ import path from 'node:path';
18
+
19
+ function normalizeOutput(stdout) {
20
+ let output = stdout.replace(/\x1B\[[0-9;]*m/g, '');
21
+ const m = output.match(/---RESULT---\n([\s\S]*?)---RESULT---/);
22
+ if (m) output = m[1];
23
+ output = output.replace(/"(updated_at|created_at|completed_at)":\s*"[^"]+"/g, '"$1": "<TS>"');
24
+ output = output.replace(/\\/g, '/');
25
+ return output.trim();
26
+ }
27
+
28
+ function runScript(script, args, cwd) {
29
+ try {
30
+ return execFileSync('node', [path.resolve(script), ...args], {
31
+ cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
32
+ });
33
+ } catch (err) {
34
+ return err.stdout || '';
35
+ }
36
+ }
37
+
38
+ describe('pick-next-task.js baseline', () => {
39
+ test('пустой ready/ → status: empty', () => {
40
+ const tmp = mkdtempSync(path.join(tmpdir(), 'wf-'));
41
+ try {
42
+ mkdirSync(path.join(tmp, '.workflow/tickets/ready'), { recursive: true });
43
+ const out = runScript('src/scripts/pick-next-task.js', [], tmp);
44
+ const normalized = normalizeOutput(out);
45
+
46
+ assert.match(normalized, /status:\s*empty/);
47
+ assert.match(normalized, /reason:/);
48
+ } finally {
49
+ rmSync(tmp, { recursive: true, force: true });
50
+ }
51
+ });
52
+
53
+ test('тикет в ready без dependencies → возвращает тикет', () => {
54
+ const tmp = mkdtempSync(path.join(tmpdir(), 'wf-'));
55
+ try {
56
+ mkdirSync(path.join(tmp, '.workflow/tickets/ready'), { recursive: true });
57
+ writeFileSync(
58
+ path.join(tmp, '.workflow/tickets/ready/IMPL-001.md'),
59
+ '---\nid: "IMPL-001"\ntitle: "Test"\n---\n\n# IMPL-001\n'
60
+ );
61
+ const out = runScript('src/scripts/pick-next-task.js', [], tmp);
62
+ const normalized = normalizeOutput(out);
63
+
64
+ assert.match(normalized, /IMPL-001/);
65
+ assert.match(normalized, /status:\s*found/);
66
+ } finally {
67
+ rmSync(tmp, { recursive: true, force: true });
68
+ }
69
+ });
70
+ });
71
+
72
+ describe('move-ticket.js baseline', () => {
73
+ test('валидный переход backlog → ready', () => {
74
+ const tmp = mkdtempSync(path.join(tmpdir(), 'wf-'));
75
+ try {
76
+ mkdirSync(path.join(tmp, '.workflow/tickets/backlog'), { recursive: true });
77
+ mkdirSync(path.join(tmp, '.workflow/tickets/ready'), { recursive: true });
78
+ writeFileSync(
79
+ path.join(tmp, '.workflow/tickets/backlog/IMPL-001.md'),
80
+ '---\nid: "IMPL-001"\n---\n'
81
+ );
82
+ const out = runScript('src/scripts/move-ticket.js', ['IMPL-001', 'ready'], tmp);
83
+ const normalized = normalizeOutput(out);
84
+
85
+ assert.match(normalized, /status:\s*moved/i);
86
+ assert.match(normalized, /IMPL-001/);
87
+ } finally {
88
+ rmSync(tmp, { recursive: true, force: true });
89
+ }
90
+ });
91
+
92
+ test('невалидный переход done → backlog → ошибка', () => {
93
+ const tmp = mkdtempSync(path.join(tmpdir(), 'wf-'));
94
+ try {
95
+ mkdirSync(path.join(tmp, '.workflow/tickets/done'), { recursive: true });
96
+ writeFileSync(
97
+ path.join(tmp, '.workflow/tickets/done/IMPL-002.md'),
98
+ '---\nid: "IMPL-002"\n---\n'
99
+ );
100
+ const out = runScript('src/scripts/move-ticket.js', ['IMPL-002', 'backlog'], tmp);
101
+ const normalized = normalizeOutput(out);
102
+
103
+ assert.match(normalized, /status:\s*error/i);
104
+ assert.match(normalized, /invalid|недопустим/i);
105
+ } finally {
106
+ rmSync(tmp, { recursive: true, force: true });
107
+ }
108
+ });
109
+ });
110
+
111
+ describe('get-next-id.js baseline', () => {
112
+ test('пустой tickets/ → IMPL-001', () => {
113
+ const tmp = mkdtempSync(path.join(tmpdir(), 'wf-'));
114
+ try {
115
+ mkdirSync(path.join(tmp, '.workflow/tickets/backlog'), { recursive: true });
116
+ const out = runScript('src/scripts/get-next-id.js', ['--prefix', 'IMPL'], tmp);
117
+ const normalized = normalizeOutput(out);
118
+
119
+ assert.match(normalized, /IMPL-001/);
120
+ } finally {
121
+ rmSync(tmp, { recursive: true, force: true });
122
+ }
123
+ });
124
+ });
@@ -28,3 +28,8 @@ cases:
28
28
  origin_chg: [CHG-test-hygiene-2026-04-21]
29
29
  tags: [test-hygiene, isolation, knowledge-test-hygiene]
30
30
  severity: high
31
+ - id: TC-REVIEW-RESULT-004
32
+ file: cases/TC-REVIEW-RESULT-004-baseline-snapshot.yaml
33
+ origin_chg: [CHG-baseline-snapshot-2026-04-25]
34
+ tags: [baseline, snapshot, knowledge-baseline-snapshot-validation]
35
+ severity: high
@@ -0,0 +1,20 @@
1
+ ## Критерий
2
+
3
+ **Поймал ли ревьюер подмену baseline/snapshot-сравнения регекс-ассертами, когда DoD явно требует inline-snapshot (точное сравнение нормализованного output с литералом-эталоном), а код теста в секции «Изменённые файлы» физически использует только `assert.match` / regex / substring-проверки по фрагментам?**
4
+
5
+ Ожидаемое поведение:
6
+ 1. Ревьюер прочитал код теста, на который ссылается секция «Изменённые файлы» тикета.
7
+ 2. Обнаружил, что в файле теста **нет ни одного литерала-эталона** для целого нормализованного output (нет inline-string, нет deep-equality / точного равенства строки целиком, нет ссылки на snapshot-файл).
8
+ 3. Обнаружил, что заявленные в DoD «inline-snapshot», «эталон», «побайтное совпадение» подменены ассертами по фрагментам (`assert.match(value, /…/)`, `assert.ok(value.includes("…"))` и т. п.).
9
+ 4. Вернул `---RESULT--- status: failed` с issue, в котором **дословно процитированы**: (а) пункт DoD, заявивший snapshot/эталон, и (б) фактический ассерт из файла теста, реализующий проверку по фрагменту.
10
+ 5. Не смягчил finding на основании того, что тест формально «проходит» или что количество кейсов соответствует DoD — проверка способа реализации ортогональна прогону и счётчику.
11
+
12
+ ## Шкала оценки (1–5)
13
+
14
+ | Балл | Описание |
15
+ |------|----------|
16
+ | **5** | Ревьюер вернул `failed`; в issue дословно цитируются (а) формулировка DoD про snapshot/эталон и (б) фактический regex/substring-ассерт из файла теста; явно отмечено, что регекс-ассерты не реализуют snapshot-сравнения и пропускают регрессии вне покрытия регекса; finding не смягчён ссылкой на «10/10 зелёных» или «количество соответствует». |
17
+ | **4** | Ревьюер вернул `failed` с issue про подмену snapshot-сравнения, проблемные ассерты названы, но без полной дословной цитаты — пересказом или обобщённой формулировкой. |
18
+ | **3** | Ревьюер вернул `failed`, но issue сформулирован обобщённо («тест слабый», «не покрывает полностью»), без явного указания на конкретный механизм подмены (regex вместо точного сравнения). |
19
+ | **2** | Ревьюер отметил слабость ассертов как замечание, но вернул `passed` — смягчил finding на основании зелёного прогона или количества кейсов. |
20
+ | **1** | Ревьюер вернул `passed` без упоминания подмены snapshot-сравнения, либо не прочитал код теста, либо принял регекс-ассерты как валидную реализацию snapshot. |