gemini-cli / packages /core /src /agents /skill-extraction-agent.test.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
7a1ad33 verified
Raw History Blame Contribute Delete
7.26 kB
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, expect, it } from 'vitest';
import { SkillExtractionAgent } from './skill-extraction-agent.js';
import {
EDIT_TOOL_NAME,
GLOB_TOOL_NAME,
GREP_TOOL_NAME,
LS_TOOL_NAME,
READ_FILE_TOOL_NAME,
SHELL_TOOL_NAME,
WRITE_FILE_TOOL_NAME,
} from '../tools/tool-names.js';
import { PREVIEW_GEMINI_FLASH_MODEL } from '../config/models.js';
describe('SkillExtractionAgent', () => {
const skillsDir = '/tmp/skills';
const sessionIndex =
'[NEW] Debug login flow (12 user msgs) — /tmp/chats/session-1.json';
const existingSkillsSummary =
'## Workspace Skills (.gemini/skills — do NOT duplicate)\n- **existing-skill**: Existing description';
const agent = SkillExtractionAgent(
skillsDir,
sessionIndex,
existingSkillsSummary,
);
it('should expose expected metadata, model, and tools', () => {
expect(agent.kind).toBe('local');
expect(agent.name).toBe('confucius');
expect(agent.displayName).toBe('Skill Extractor');
expect(agent.modelConfig.model).toBe(PREVIEW_GEMINI_FLASH_MODEL);
expect(agent.memoryInboxAccess).toBe(true);
expect(agent.autoMemoryExtractionWriteAccess).toBe(true);
expect(agent.includeExtensionContext).toBe(false);
expect(agent.toolConfig?.tools).toEqual(
expect.arrayContaining([
READ_FILE_TOOL_NAME,
WRITE_FILE_TOOL_NAME,
EDIT_TOOL_NAME,
LS_TOOL_NAME,
GLOB_TOOL_NAME,
GREP_TOOL_NAME,
]),
);
expect(agent.toolConfig?.tools).not.toContain(SHELL_TOOL_NAME);
});
it('should default to no skill unless recurrence and durability are proven', () => {
const prompt = agent.promptConfig.systemPrompt;
expect(prompt).toContain('Default to NO SKILL.');
expect(prompt).toContain(
'strong evidence this will recur for future agents in this repo/workflow',
);
expect(prompt).toContain('broader than a single incident');
expect(prompt).toContain('A skill MUST meet ALL of these criteria:');
expect(prompt).toContain(
'Future agents in this repo/workflow are likely to need it',
);
});
it('should explicitly reject one-off incidents and single-session preferences', () => {
const prompt = agent.promptConfig.systemPrompt;
expect(prompt).toContain('Single-session preferences');
expect(prompt).toContain('One-off incidents');
expect(prompt).toContain('Output-style preferences');
expect(prompt).toContain('cannot survive renaming the specific');
});
it('should require all memory updates to go through .inbox/<kind>/*.patch for review', () => {
const prompt = SkillExtractionAgent(
skillsDir,
sessionIndex,
existingSkillsSummary,
'/tmp/memory',
).promptConfig.systemPrompt;
expect(prompt).toContain(
'ALL memory updates are expressed as unified diff `.patch` files',
);
expect(prompt).toContain('EXACTLY ONE canonical patch file per kind');
expect(prompt).toContain('extraction.patch');
expect(prompt).not.toContain('MEMORY.patch');
expect(prompt).not.toContain('verify-workflow.patch');
expect(prompt).toContain('IMPORTANT — incremental updates');
expect(prompt).toContain(
'REWRITE that file by combining its existing hunks with your new',
);
expect(prompt).toContain('private ->');
expect(prompt).toContain('global ->');
expect(prompt).toContain(
'the target MUST be exactly the single global personal memory',
);
expect(prompt).toContain('~/.gemini/GEMINI.md');
expect(prompt).not.toContain('memory.md');
expect(prompt).not.toContain('and siblings');
expect(prompt).toContain(
'Project/workspace shared instructions (GEMINI.md and similar files',
);
expect(prompt).toContain('MEMORY PATCH FORMAT (STRICT)');
expect(prompt).toContain('--- /dev/null');
expect(prompt).toContain('NEVER directly edit MEMORY.md');
expect(prompt).toContain(
'Every patch you write is held for /memory inbox review.',
);
expect(prompt).toContain('the user must approve each patch');
// The MEMORY.md-as-index discipline: sibling creations should pair with
// a MEMORY.md update hunk; the inbox apply step auto-bundles a generic
// pointer if the agent forgets, but the agent should write its own.
expect(prompt).toContain('PRIVATE MEMORY: MEMORY.md IS THE INDEX');
expect(prompt).toContain(
'when you create a new sibling .md file, your patch SHOULD',
);
expect(prompt).toContain('a SECOND HUNK that updates MEMORY.md');
expect(prompt).toContain('inbox apply step');
expect(prompt).toContain('auto-bundle a generic pointer');
// Pointer paths must be ABSOLUTE — the runtime agent reads them directly.
expect(prompt).toContain('IMPORTANT — pointer paths must be ABSOLUTE');
expect(prompt).toContain('Always write the full path');
// The example pointer in the prompt also uses the absolute path.
expect(prompt).toContain(`+- See /tmp/memory/<topic>.md for`);
});
it('surfaces existing inbox patches in the initial query when present', () => {
const pendingInbox = [
'## private (1)',
'',
'### extraction.patch',
'```',
'--- /dev/null',
'+++ /tmp/memory/MEMORY.md',
'@@ -0,0 +1,1 @@',
'+- previously-extracted fact',
'```',
].join('\n');
const agentWithInbox = SkillExtractionAgent(
skillsDir,
sessionIndex,
existingSkillsSummary,
'/tmp/memory',
pendingInbox,
);
const query = agentWithInbox.promptConfig.query ?? '';
expect(query).toContain('# Pending Memory Inbox');
expect(query).toContain('extraction.patch');
expect(query).toContain('previously-extracted fact');
expect(query).toContain(
'REWRITE that patch (overwrite the same path) with',
);
});
it('omits the pending inbox section when nothing is pending', () => {
const agentEmpty = SkillExtractionAgent(
skillsDir,
sessionIndex,
existingSkillsSummary,
'/tmp/memory',
'',
);
const query = agentEmpty.promptConfig.query ?? '';
expect(query).not.toContain('# Pending Memory Inbox');
});
it('should warn that session summaries are user-intent summaries, not workflow evidence', () => {
const query = agent.promptConfig.query ?? '';
expect(query).toContain(existingSkillsSummary);
expect(query).toContain(sessionIndex);
expect(query).toContain('optional workflow hint');
expect(query).toContain(
'workflow hints alone is never enough evidence for a reusable skill.',
);
expect(query).toContain(
'Session summaries describe user intent; optional workflow hints describe likely procedural traces.',
);
expect(query).toContain('Use workflow hints for routing');
expect(query).toContain(
'Only write a skill if the evidence shows a durable, recurring workflow',
);
expect(query).toContain(
'Only write memory if it would clearly help a future session.',
);
expect(query).toContain(
'If recurrence, durability, or future reuse is unclear, create no artifact and explain why.',
);
});
});