# Calibration 5 — cosmetic-detector excludes git-approval AskUser Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:test-driven-development. Steps use checkbox (`- [ ]`) syntax.

**Goal:** Stop `askuser-cosmetic-detector` from counting/blocking git-operation **approval** AskUsers as "cosmetic A/B" — a scope fix that does NOT lower discipline.

**Architecture:** The detector's target is *simple A/B questions used instead of structured ideation* (brainstorming/writing-plans). A git-approval AskUser (one whose option label is a literal git command) is the *sanctioned git-approval channel* (`enforce-askuser-answer-parser` writes `approve_git_operation` from it) — it is never a substitute for ideation. We add `isGitApprovalQuestion()` and short-circuit `decide()` to `allow` with `isSimpleAB:false` for such questions, so they are neither counted toward the session limit nor hard-blocked. Everything else is unchanged.

**Tech Stack:** Node ESM `.mjs`, vitest.

**Why NO discipline hole (adversarial check):**

- The real target (design-clarification A/B like "Вариант A"/"Вариант B") has NON-git labels → still classified simple → still counted → still hard-blocked at >2. Unchanged.
- A git-approval question is identified ONLY by an option label matching a git-command verb. To "disguise" a cosmetic clarification as exempt, the controller would have to put a literal `git …` command as an option label — but then the chosen answer IS a git command, which `enforce-askuser-answer-parser` turns into a real `approve_git_operation` record; it cannot function as a cosmetic ideation-dodge. So there is no usable bypass.
- Exemption is narrow and structural (label is a git command), mirroring calibrations 1 (Skill) / 3 (test-runner) / 4 (user-prompt fallback): scope fix, not a discipline drop.

---

## Task 1: isGitApprovalQuestion + decide() exemption

**Files:**

- Modify: `tools/askuser-cosmetic-detector.mjs`
- Test: `tools/askuser-cosmetic-detector.test.mjs`

- [ ] **Step 1: Write failing tests**

```javascript
import { isGitApprovalQuestion } from './askuser-cosmetic-detector.mjs';

describe('isGitApprovalQuestion (calibration 5)', () => {
  it('true when an option label is a git command', () => {
    expect(isGitApprovalQuestion([{ options: [{ label: 'git push origin main' }, { label: 'Не пушить' }] }])).toBe(true);
    expect(isGitApprovalQuestion([{ options: [{ label: 'git commit -F x -- a b' }, { label: 'Отмена' }] }])).toBe(true);
  });
  it('false for a non-git A/B', () => {
    expect(isGitApprovalQuestion([{ options: [{ label: 'Вариант А' }, { label: 'Вариант Б' }] }])).toBe(false);
  });
});

// decide(): git-approval question is exempt — allow, not simple, not counted, never blocked even past the session limit.
describe('decide — git-approval exemption (calibration 5)', () => {
  it('allows a git-approval question and does NOT count it even when session is already over the limit', () => {
    const r = decide({
      questions: [{ options: [{ label: 'git push origin main' }, { label: 'Не пушить' }] }],
      simpleCountSession: 5, brainstormingInvoked: false,
    });
    expect(r.block).toBe(false);
    expect(r.action).toBe('allow');
    expect(r.isSimpleAB).toBe(false);
    expect(r.newSessionCount).toBe(5); // unchanged — not counted
  });

  it('REGRESSION: a non-git simple A/B past the limit STILL hard-blocks (discipline intact)', () => {
    const r = decide({
      questions: [{ options: [{ label: 'A' }, { label: 'B' }] }],
      simpleCountSession: 5, brainstormingInvoked: false,
    });
    expect(r.block).toBe(true);
    expect(r.action).toBe('hard_block');
  });
});
```

- [ ] **Step 2: Run RED** — `npx vitest run --root app --config vitest.config.tools.mjs askuser-cosmetic-detector` → fail (isGitApprovalQuestion missing; git-approval not exempt).

- [ ] **Step 3: Implement**

Add near `isSimpleAB`:

```javascript
const GIT_CMD_RE = /\bgit\s+(?:commit|push|add|pull|merge|rebase|reset|checkout|switch|branch|stash|cherry-pick|revert|clean|restore|fetch|tag)\b/i;

/** True if this AskUser is a git-operation approval prompt (an option label is a git command). */
export function isGitApprovalQuestion(questions) {
  if (!Array.isArray(questions)) return false;
  return questions.some((q) =>
    q && Array.isArray(q.options) &&
    q.options.some((o) => o && typeof o.label === 'string' && GIT_CMD_RE.test(o.label)));
}
```

In `decide()`, replace `const simple = isSimpleAB(questions);` with:

```javascript
  // Calibration 5: git-operation approval prompts are the sanctioned approval
  // channel, never cosmetic ideation — exempt from the simple-AB count/block.
  if (isGitApprovalQuestion(questions)) {
    return { action: 'allow', block: false, reason: null, isSimpleAB: false, newSessionCount: simpleCountSession, newTurnCount: simpleCountTurn };
  }
  const simple = isSimpleAB(questions);
```

- [ ] **Step 4: Run GREEN** — same command → pass.

- [ ] **Step 5: Full regression** — `npx vitest run --root app --config vitest.config.tools.mjs` → all green.

- [ ] **Step 6: Commit** (with git-approval).
