Drei vom Architekten abgenommene Tasks, gebündelt als Checkpoint: - TSK-0242: Agent-Alias-Mapping (kimi-ah → kimi kanonisiert, Rollen → preferredAgent), reopenTask räumt claimedBy ab, fsWatch reindiziert direkte Datei-Edits, work-Default 300s → 50s, task_list mit Limit. - TSK-0245: zwei Agent-Klassen (dispatch loop|architect). Watchdog mahnt architekt-getriebene Agenten nur noch EINMAL statt im Minutentakt; `task dispatch` startet sie explizit, `task record` trägt extern erledigte Arbeit mit origin=external nach. - TSK-0249: Lifecycle wird serverseitig erzwungen (open→review scheitert mit klarer Meldung), claimedBy/doneBy überleben bis done, Presence pro Agent, Review-Watchdog, GET /architect/pulse (1.4 kB statt 34 kB, since-Cursor, omitted statt stillem Abschneiden), unbekannter Agent → 400 statt 500. Alle Punkte live am laufenden Hub nachgemessen, nicht aus Agenten-Logs übernommen. Tests: 242 → 279 grün, tsc sauber. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
60 lines
2.8 KiB
TypeScript
60 lines
2.8 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
import { mkdtempSync, rmSync } from 'fs';
|
|
import { tmpdir } from 'os';
|
|
import { join } from 'path';
|
|
import { init } from '../src/cli/commands/init.js';
|
|
import { createTask, claimTask, reviewTask, doneTask } from '../src/core/services/taskService.js';
|
|
import { createHandoff } from '../src/core/services/handoffService.js';
|
|
import { createMessage } from '../src/core/services/messageService.js';
|
|
import { computeBudget, TOKENS_PER_COORDINATION_ACTION } from '../src/core/services/budgetService.js';
|
|
|
|
/** Architect coordination work must be visible in the budget report (TSK-0226). */
|
|
describe('budget: architect activity', () => {
|
|
let cwd: string;
|
|
|
|
beforeEach(() => {
|
|
cwd = mkdtempSync(join(tmpdir(), 'ah-budget-'));
|
|
init(cwd, { projectName: 'budget-test', yes: true });
|
|
});
|
|
|
|
afterEach(() => {
|
|
rmSync(cwd, { recursive: true, force: true });
|
|
});
|
|
|
|
it('counts reviews, handoffs and messages as architect activity', () => {
|
|
// Implementer-owned task; the architect only coordinates around it.
|
|
createTask(cwd, { title: 'Feature', role: 'implementer', assignedTo: 'codex' });
|
|
claimTask(cwd, 'TSK-0001', 'codex');
|
|
reviewTask(cwd, 'TSK-0001', 'claude');
|
|
createHandoff(cwd, { fromRole: 'architect', toRole: 'implementer', fromAgent: 'claude', toAgent: 'codex', taskId: 'TSK-0001', summary: 'scope' });
|
|
createMessage(cwd, { from: 'claude', to: 'codex', text: 'please review' });
|
|
|
|
const report = computeBudget(cwd);
|
|
const claude = report.agents.find((a) => a.name === 'claude');
|
|
expect(claude).toBeDefined();
|
|
// reviewer + handoff + message = 3 actions, all estimated (never silent fabrication).
|
|
expect(claude!.actions).toBe(3);
|
|
expect(claude!.tokens).toBe(3 * TOKENS_PER_COORDINATION_ACTION);
|
|
expect(claude!.estimated).toBe(true);
|
|
expect(claude!.estimatedTokens).toBe(claude!.tokens);
|
|
expect(claude!.costEur).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('a done approval by someone other than the owner counts as an action', () => {
|
|
createTask(cwd, { title: 'Feature', role: 'implementer', assignedTo: 'codex' });
|
|
claimTask(cwd, 'TSK-0001', 'codex');
|
|
reviewTask(cwd, 'TSK-0001', 'claude');
|
|
// Architect approves: doneBy = claude, owner = codex.
|
|
doneTask(cwd, 'TSK-0001', { doneBy: 'claude', doneTokens: 8000 });
|
|
|
|
const report = computeBudget(cwd);
|
|
const claude = report.agents.find((a) => a.name === 'claude')!;
|
|
// review + approval; the implementer keeps the real task tokens.
|
|
expect(claude.actions).toBe(2);
|
|
const codex = report.agents.find((a) => a.name === 'codex')!;
|
|
expect(codex.realTokens).toBe(8000);
|
|
// No double count: claude's action estimate does not include the 8000.
|
|
expect(claude.tokens).toBe(2 * TOKENS_PER_COORDINATION_ACTION);
|
|
});
|
|
});
|