Commit Graph

4 Commits

Author SHA1 Message Date
Дмитрий 81cbd8c1c2 feat(brain-retro #7): C1+C2+C3+C4 router-discipline fixes
retro #7 (docs/observer/notes/2026-05-27-brain-retro-7.md) surfaced 4
candidates against 23 turns since retro #6. All four implemented TDD.

C1 — translit slang vocabulary in router-classifier-regex-fallback.mjs.
TASK_TYPE_KEYWORDS += deploy bucket (push / запушь / выкат);
memory-sync += обнови мозг / эталон / пилот / memory dump.

C2 — short_ambiguous_block in router-tool-gate.mjs + router-prehook.mjs.
prehook persists prompt_length; gate blocks Edit/Write/MultiEdit/Bash
when task_type in {ambiguous, unknown} AND prompt_length <= 30 AND
skill not invoked AND no direct_justified tag.

C3 — self-assessment timeout 30s to 50s in observer-self-assessment-api.mjs.
Windows TLS handshake + Sonnet latency exceeded 30s. Stop-hook has 60s
budget; 50s leaves headroom. DEFAULT_TIMEOUT_MS exported for tests.

C4 — Reviewer findings block in status-md-generator.mjs. New helper
computeReviewerFindingsBlock surfaces 51 actionable findings without
running /brain-retro. Detects batch-reviewed via
outcome_reviewed_source=direct_api_batch. MD012 guard test added.

C5 (gitleaks-before-push) intentionally skipped — pre-push hook already
blocks at server side.

Tests: 956/956 root tools, 0 regressions. LEFTHOOK=0 used per quirk #111.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 06:46:55 +03:00
Дмитрий 7b4da1477e fix(classifier,gate): G parser-quirks + H unknown-not-blocking + A1/A2/B3/C1
Brain-retro #6 follow-up #2 (consolidated). Eight independent fixes:

A1 — task_cost wiring (cost tracking)
  - router-prehook.mjs: capture classifier LLM usage via onUsage callback,
    persist to state.task_cost.classifier_input_tokens / output_tokens.
  - observer-transcript-parser.mjs: merge router-state.task_cost on top of
    extractTokenUsage(turn). State-file values win for classifier/
    self_assessment/reviewer fields.
  - New buildCostFromClassifierUsage() exported from router-prehook.
  - Verified live: state file now shows real input_tokens=190 /
    output_tokens=598 / cache_read=10075 (was 0 before).

A2 — self-assessment coverage
  - observer-self-assessment-api.mjs: DEFAULT_TIMEOUT_MS 10s -> 30s.
  - .claude/settings.json: Stop-hook timeout 15s -> 60s.
  - Same Windows TLS handshake issue. Was 85% no_self_assessment in retro #6.

B3 — brain-retro SKILL.md reconciliation
  - Step 5b: batch=default for N>=20, subagent for N<20.

C1 — dead-code cleanup
  - Removed recommendNode import + getClassificationMap + getDormancy from
    observer-transcript-parser.mjs.

G — parseClassifierResponse Pass 3 (fixLLMJsonQuirks)
  - Root cause: real Sonnet output sometimes contains raw newlines inside
    string values (multi-line reason_for_choice) and trailing commas, which
    strict JSON.parse rejects. Result was llm_error_type=parse_null on
    every other call, falling back to regex with task_type=unknown.
  - Fix: after Pass 1 (clean) and Pass 2 (brace-extract) fail, try Pass 3
    that escapes raw newline/tab inside string values and strips trailing
    commas before final JSON.parse attempt. Pure char-walk, no JSON5 dep.

H — 'unknown' added to NON_BLOCKING_TASK_TYPES in router-tool-gate.mjs
  - Until G fully proves itself, blocking Bash/Edit on unknown is too strict.
    With G in place, parse_null should be rare; H gives a safety net.

Tests added: +9 across 5 test files. Regression: 913 vitest tests in tools/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 19:25:16 +03:00
Дмитрий 6cb8be6919 test(observer): align readRuntimeFlag tests with mode/value fix (050b349a) 2026-05-25 18:29:56 +03:00
Дмитрий b437597286 feat(observer): wire real LLM self-assessment API call — phase 3 deferred #5
- NEW tools/observer-self-assessment-api.mjs
  buildSelfAssessmentPrompt({ prompt, recommendedNode, actualNode, chainExecuted })
  pure, handles nulls/undefined, returns { system, user } strings
  callSelfAssessmentApi(opts) async, fail-quiet — returns string|null
  AbortController + timeout race (works even when fetchImpl ignores signal)
  guards: !apiKey -> return null immediately (no fetch call)
  guards: !response.ok, fetch throw, JSON parse error -> return null
  passes x-api-key + authorization headers per ProxyAPI two-header pattern
  readRuntimeFlag(name, { homedir, fsImpl }) reads ~/.claude/runtime/<name>.json
  returns value field string or 'off' on missing/malformed

- NEW tools/observer-self-assessment-api.test.mjs: 14 tests, 0 failed
  1. buildSelfAssessmentPrompt all 4 fields interpolated
  2. buildSelfAssessmentPrompt null/undefined inputs (2 tests)
  3. callSelfAssessmentApi returns null when apiKey falsy (2 tests)
  4. returns content[0].text on 200 ok (fake fetchImpl)
  5. returns null on non-2xx (response.ok=false)
  6. returns null on fetch throw
  7. returns null on timeout (never-resolving fake fetchImpl, timeoutMs=30ms)
  8. sends correct headers+body shape (spy fetchImpl)
  9. readRuntimeFlag reads {"value":"on"}, returns 'off' on missing/malformed (4 tests)

- EDIT tools/observer-stop-hook.mjs
  import { callSelfAssessmentApi, readRuntimeFlag } added
  stdin 'end' handler made async
  step 3.5 inserted between buildEpisodeFromContext and appendEpisode:
  reads self-assessment-mode runtime flag; if 'on' and ROUTER_LLM_KEY set,
  calls callSelfAssessmentApi and attaches ep.self_assessment via buildSelfAssessment()
  fail-quiet: on any error apiResult=null -> self_assessment_pending: true

Regression: 628/628 tests passed (35 test files), 0 failed
gitleaks: 0 leaks on all 3 files

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 14:28:26 +03:00