* feat: track project-level .takt/pieces in version control * feat: track project-level takt facets for customizable resources * chore: include project .takt/config.yaml in git-tracked subset * takt: github-issue-284-faceted-prompting
159 lines
5.0 KiB
TypeScript
159 lines
5.0 KiB
TypeScript
/**
|
|
* Unit tests for faceted-prompting compose module.
|
|
*/
|
|
|
|
import { describe, it, expect } from 'vitest';
|
|
import { compose } from '../../faceted-prompting/index.js';
|
|
import type { FacetSet, ComposeOptions } from '../../faceted-prompting/index.js';
|
|
|
|
const defaultOptions: ComposeOptions = { contextMaxChars: 2000 };
|
|
|
|
describe('compose', () => {
|
|
it('should place persona in systemPrompt', () => {
|
|
const facets: FacetSet = {
|
|
persona: { body: 'You are a helpful assistant.' },
|
|
};
|
|
|
|
const result = compose(facets, defaultOptions);
|
|
|
|
expect(result.systemPrompt).toBe('You are a helpful assistant.');
|
|
expect(result.userMessage).toBe('');
|
|
});
|
|
|
|
it('should place instruction in userMessage', () => {
|
|
const facets: FacetSet = {
|
|
instruction: { body: 'Implement feature X.' },
|
|
};
|
|
|
|
const result = compose(facets, defaultOptions);
|
|
|
|
expect(result.systemPrompt).toBe('');
|
|
expect(result.userMessage).toBe('Implement feature X.');
|
|
});
|
|
|
|
it('should place policy in userMessage with conflict notice', () => {
|
|
const facets: FacetSet = {
|
|
policies: [{ body: 'Follow clean code principles.' }],
|
|
};
|
|
|
|
const result = compose(facets, defaultOptions);
|
|
|
|
expect(result.systemPrompt).toBe('');
|
|
expect(result.userMessage).toContain('Follow clean code principles.');
|
|
expect(result.userMessage).toContain('If prompt content conflicts with source files');
|
|
});
|
|
|
|
it('should place knowledge in userMessage with conflict notice', () => {
|
|
const facets: FacetSet = {
|
|
knowledge: [{ body: 'Architecture documentation.' }],
|
|
};
|
|
|
|
const result = compose(facets, defaultOptions);
|
|
|
|
expect(result.systemPrompt).toBe('');
|
|
expect(result.userMessage).toContain('Architecture documentation.');
|
|
expect(result.userMessage).toContain('If prompt content conflicts with source files');
|
|
});
|
|
|
|
it('should compose all facets in correct order: policy, knowledge, instruction', () => {
|
|
const facets: FacetSet = {
|
|
persona: { body: 'You are a coder.' },
|
|
policies: [{ body: 'POLICY' }],
|
|
knowledge: [{ body: 'KNOWLEDGE' }],
|
|
instruction: { body: 'INSTRUCTION' },
|
|
};
|
|
|
|
const result = compose(facets, defaultOptions);
|
|
|
|
expect(result.systemPrompt).toBe('You are a coder.');
|
|
|
|
const policyIdx = result.userMessage.indexOf('POLICY');
|
|
const knowledgeIdx = result.userMessage.indexOf('KNOWLEDGE');
|
|
const instructionIdx = result.userMessage.indexOf('INSTRUCTION');
|
|
|
|
expect(policyIdx).toBeLessThan(knowledgeIdx);
|
|
expect(knowledgeIdx).toBeLessThan(instructionIdx);
|
|
});
|
|
|
|
it('should join multiple policies with separator', () => {
|
|
const facets: FacetSet = {
|
|
policies: [
|
|
{ body: 'Policy A' },
|
|
{ body: 'Policy B' },
|
|
],
|
|
};
|
|
|
|
const result = compose(facets, defaultOptions);
|
|
|
|
expect(result.userMessage).toContain('Policy A');
|
|
expect(result.userMessage).toContain('---');
|
|
expect(result.userMessage).toContain('Policy B');
|
|
});
|
|
|
|
it('should join multiple knowledge items with separator', () => {
|
|
const facets: FacetSet = {
|
|
knowledge: [
|
|
{ body: 'Knowledge A' },
|
|
{ body: 'Knowledge B' },
|
|
],
|
|
};
|
|
|
|
const result = compose(facets, defaultOptions);
|
|
|
|
expect(result.userMessage).toContain('Knowledge A');
|
|
expect(result.userMessage).toContain('---');
|
|
expect(result.userMessage).toContain('Knowledge B');
|
|
});
|
|
|
|
it('should truncate policy content exceeding contextMaxChars', () => {
|
|
const longPolicy = 'x'.repeat(3000);
|
|
const facets: FacetSet = {
|
|
policies: [{ body: longPolicy, sourcePath: '/path/policy.md' }],
|
|
};
|
|
|
|
const result = compose(facets, { contextMaxChars: 2000 });
|
|
|
|
expect(result.userMessage).toContain('...TRUNCATED...');
|
|
expect(result.userMessage).toContain('Policy is authoritative');
|
|
});
|
|
|
|
it('should truncate knowledge content exceeding contextMaxChars', () => {
|
|
const longKnowledge = 'y'.repeat(3000);
|
|
const facets: FacetSet = {
|
|
knowledge: [{ body: longKnowledge, sourcePath: '/path/knowledge.md' }],
|
|
};
|
|
|
|
const result = compose(facets, { contextMaxChars: 2000 });
|
|
|
|
expect(result.userMessage).toContain('...TRUNCATED...');
|
|
expect(result.userMessage).toContain('Knowledge is truncated');
|
|
});
|
|
|
|
it('should handle empty facet set', () => {
|
|
const result = compose({}, defaultOptions);
|
|
|
|
expect(result.systemPrompt).toBe('');
|
|
expect(result.userMessage).toBe('');
|
|
});
|
|
|
|
it('should include source path for single policy', () => {
|
|
const facets: FacetSet = {
|
|
policies: [{ body: 'Policy text', sourcePath: '/policies/coding.md' }],
|
|
};
|
|
|
|
const result = compose(facets, defaultOptions);
|
|
|
|
expect(result.userMessage).toContain('Policy Source: /policies/coding.md');
|
|
});
|
|
|
|
it('should include source path for single knowledge', () => {
|
|
const facets: FacetSet = {
|
|
knowledge: [{ body: 'Knowledge text', sourcePath: '/knowledge/arch.md' }],
|
|
};
|
|
|
|
const result = compose(facets, defaultOptions);
|
|
|
|
expect(result.userMessage).toContain('Knowledge Source: /knowledge/arch.md');
|
|
});
|
|
});
|