diff --git a/docs/observer/STATUS.md b/docs/observer/STATUS.md index bb1f6468..2097768e 100644 --- a/docs/observer/STATUS.md +++ b/docs/observer/STATUS.md @@ -1,6 +1,6 @@ # Brain Status (auto-generated) -Last updated: 2026-06-02T10:04:24.915Z +Last updated: 2026-06-02T10:14:43.123Z | Контролёр | Состояние | Детали | |---|---|---| @@ -97,8 +97,8 @@ Episodes since last run: 542 / threshold: 10 | PID | Имя | CPU-время | Возраст | |---|---|---|---| -| 10388 | Code | 3.01ч | NaNч | -| 3220 | MsMpEng | 1.13ч | 0.0ч | +| 10388 | Code | 3.05ч | 1327306.2ч | +| 3220 | MsMpEng | 1.14ч | 0.0ч | ⚠️ Проверь, не «осиротевшие» ли это процессы от завершённых Claude-сессий. diff --git a/tools/enforce-tdd-gate.mjs b/tools/enforce-tdd-gate.mjs index ef9f8538..919bf96d 100644 --- a/tools/enforce-tdd-gate.mjs +++ b/tools/enforce-tdd-gate.mjs @@ -108,6 +108,11 @@ function hasFailingTestRun(turn) { // Numeric: "Tests N failed | M passed" with N>0 const m = txt.match(/Tests\s+(\d+)\s+failed/); if (m && Number(m[1]) > 0) return true; + // JSON reporter (composer test / php artisan test → pest): {"result":"failed",...} + // or {"failed":N}/{"errors":N} with N>0. command-not-found / error REDs lack the + // English "Failed" keyword above, so recognise the structured marker too. + if (/"result"\s*:\s*"failed"/.test(txt)) return true; + if (/"(?:failed|errors)"\s*:\s*[1-9]/.test(txt)) return true; } } } diff --git a/tools/enforce-tdd-gate.test.mjs b/tools/enforce-tdd-gate.test.mjs index aae8049f..505dacfd 100644 --- a/tools/enforce-tdd-gate.test.mjs +++ b/tools/enforce-tdd-gate.test.mjs @@ -168,3 +168,25 @@ describe('enforce-tdd-gate / decide', () => { expect(r.block).toBe(false); }); }); + +describe('enforce-tdd-gate / decide — JSON pest reporter RED (composer test)', () => { + // `composer test` (php artisan test) emits machine JSON like {"result":"failed",...}. + // command-not-found / error REDs lack the English "Failed" keyword, so the gate must + // recognise the structured failure marker, else legit RED runs go unseen. + it('recognizes {"result":"failed"} JSON output as a RED run', () => { + const r = decide({ + toolName: 'Write', + filePath: 'wt/app/app/Console/Commands/FooCommand.php', + transcriptEntries: [ + userMsg('add backfill command'), + assistantUses([ + { id: 't1', name: 'Write', input: { file_path: 'wt/app/tests/Feature/Console/FooCommandTest.php' } }, + { id: 't2', name: 'Bash', input: { command: 'composer test -- tests/Feature/Console/FooCommandTest.php # pest' } }, + ]), + toolResults([{ id: 't2', content: '{"tool":"pest","result":"failed","tests":4,"passed":0,"errors":4}' }]), + ], + classification: null, + }); + expect(r.block).toBe(false); + }); +});