knowledge システム追加
This commit is contained in:
parent
b963261c3a
commit
e7d5dbfb33
0
resources/global/en/knowledge/.gitkeep
Normal file
0
resources/global/en/knowledge/.gitkeep
Normal file
0
resources/global/ja/knowledge/.gitkeep
Normal file
0
resources/global/ja/knowledge/.gitkeep
Normal file
447
src/__tests__/knowledge.test.ts
Normal file
447
src/__tests__/knowledge.test.ts
Normal file
@ -0,0 +1,447 @@
|
|||||||
|
/**
|
||||||
|
* Tests for knowledge category feature
|
||||||
|
*
|
||||||
|
* Covers:
|
||||||
|
* - Schema validation for knowledge field at piece and movement level
|
||||||
|
* - Piece parser resolution of knowledge references
|
||||||
|
* - InstructionBuilder knowledge content injection
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||||
|
import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from 'node:fs';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import { tmpdir } from 'node:os';
|
||||||
|
import {
|
||||||
|
PieceConfigRawSchema,
|
||||||
|
PieceMovementRawSchema,
|
||||||
|
ParallelSubMovementRawSchema,
|
||||||
|
} from '../core/models/index.js';
|
||||||
|
import { normalizePieceConfig } from '../infra/config/loaders/pieceParser.js';
|
||||||
|
import { InstructionBuilder } from '../core/piece/instruction/InstructionBuilder.js';
|
||||||
|
import type { InstructionContext } from '../core/piece/instruction/instruction-context.js';
|
||||||
|
import type { PieceMovement } from '../core/models/types.js';
|
||||||
|
|
||||||
|
describe('PieceConfigRawSchema knowledge field', () => {
|
||||||
|
it('should accept knowledge map at piece level', () => {
|
||||||
|
const raw = {
|
||||||
|
name: 'test-piece',
|
||||||
|
knowledge: {
|
||||||
|
frontend: 'frontend.md',
|
||||||
|
backend: 'backend.md',
|
||||||
|
},
|
||||||
|
movements: [
|
||||||
|
{ name: 'step1', persona: 'coder.md', instruction: '{task}' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = PieceConfigRawSchema.safeParse(raw);
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
if (result.success) {
|
||||||
|
expect(result.data.knowledge).toEqual({
|
||||||
|
frontend: 'frontend.md',
|
||||||
|
backend: 'backend.md',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept piece without knowledge field', () => {
|
||||||
|
const raw = {
|
||||||
|
name: 'test-piece',
|
||||||
|
movements: [
|
||||||
|
{ name: 'step1', persona: 'coder.md', instruction: '{task}' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = PieceConfigRawSchema.safeParse(raw);
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
if (result.success) {
|
||||||
|
expect(result.data.knowledge).toBeUndefined();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('PieceMovementRawSchema knowledge field', () => {
|
||||||
|
it('should accept knowledge as a string reference', () => {
|
||||||
|
const raw = {
|
||||||
|
name: 'implement',
|
||||||
|
persona: 'coder.md',
|
||||||
|
knowledge: 'frontend',
|
||||||
|
instruction: '{task}',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = PieceMovementRawSchema.safeParse(raw);
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
if (result.success) {
|
||||||
|
expect(result.data.knowledge).toBe('frontend');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept knowledge as array of string references', () => {
|
||||||
|
const raw = {
|
||||||
|
name: 'implement',
|
||||||
|
persona: 'coder.md',
|
||||||
|
knowledge: ['frontend', 'backend'],
|
||||||
|
instruction: '{task}',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = PieceMovementRawSchema.safeParse(raw);
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
if (result.success) {
|
||||||
|
expect(result.data.knowledge).toEqual(['frontend', 'backend']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept movement without knowledge field', () => {
|
||||||
|
const raw = {
|
||||||
|
name: 'implement',
|
||||||
|
persona: 'coder.md',
|
||||||
|
instruction: '{task}',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = PieceMovementRawSchema.safeParse(raw);
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
if (result.success) {
|
||||||
|
expect(result.data.knowledge).toBeUndefined();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept both stance and knowledge fields', () => {
|
||||||
|
const raw = {
|
||||||
|
name: 'implement',
|
||||||
|
persona: 'coder.md',
|
||||||
|
stance: 'coding',
|
||||||
|
knowledge: 'frontend',
|
||||||
|
instruction: '{task}',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = PieceMovementRawSchema.safeParse(raw);
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
if (result.success) {
|
||||||
|
expect(result.data.stance).toBe('coding');
|
||||||
|
expect(result.data.knowledge).toBe('frontend');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ParallelSubMovementRawSchema knowledge field', () => {
|
||||||
|
it('should accept knowledge on parallel sub-movements', () => {
|
||||||
|
const raw = {
|
||||||
|
name: 'sub-step',
|
||||||
|
persona: 'reviewer.md',
|
||||||
|
knowledge: 'security',
|
||||||
|
instruction_template: 'Review security',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = ParallelSubMovementRawSchema.safeParse(raw);
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
if (result.success) {
|
||||||
|
expect(result.data.knowledge).toBe('security');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept knowledge array on parallel sub-movements', () => {
|
||||||
|
const raw = {
|
||||||
|
name: 'sub-step',
|
||||||
|
persona: 'reviewer.md',
|
||||||
|
knowledge: ['security', 'performance'],
|
||||||
|
instruction_template: 'Review',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = ParallelSubMovementRawSchema.safeParse(raw);
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
if (result.success) {
|
||||||
|
expect(result.data.knowledge).toEqual(['security', 'performance']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('normalizePieceConfig knowledge resolution', () => {
|
||||||
|
let tempDir: string;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
tempDir = mkdtempSync(join(tmpdir(), 'takt-knowledge-test-'));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve knowledge from piece-level map to movement', () => {
|
||||||
|
const frontendKnowledge = '# Frontend Knowledge\n\nUse React for components.';
|
||||||
|
writeFileSync(join(tempDir, 'frontend.md'), frontendKnowledge);
|
||||||
|
|
||||||
|
const raw = {
|
||||||
|
name: 'test-piece',
|
||||||
|
knowledge: {
|
||||||
|
frontend: 'frontend.md',
|
||||||
|
},
|
||||||
|
movements: [
|
||||||
|
{
|
||||||
|
name: 'implement',
|
||||||
|
persona: 'coder.md',
|
||||||
|
knowledge: 'frontend',
|
||||||
|
instruction: '{task}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const piece = normalizePieceConfig(raw, tempDir);
|
||||||
|
|
||||||
|
expect(piece.knowledge).toBeDefined();
|
||||||
|
expect(piece.knowledge!['frontend']).toBe(frontendKnowledge);
|
||||||
|
expect(piece.movements[0].knowledgeContents).toEqual([frontendKnowledge]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve multiple knowledge references', () => {
|
||||||
|
const frontendKnowledge = '# Frontend\nReact patterns.';
|
||||||
|
const backendKnowledge = '# Backend\nAPI design.';
|
||||||
|
writeFileSync(join(tempDir, 'frontend.md'), frontendKnowledge);
|
||||||
|
writeFileSync(join(tempDir, 'backend.md'), backendKnowledge);
|
||||||
|
|
||||||
|
const raw = {
|
||||||
|
name: 'test-piece',
|
||||||
|
knowledge: {
|
||||||
|
frontend: 'frontend.md',
|
||||||
|
backend: 'backend.md',
|
||||||
|
},
|
||||||
|
movements: [
|
||||||
|
{
|
||||||
|
name: 'implement',
|
||||||
|
persona: 'coder.md',
|
||||||
|
knowledge: ['frontend', 'backend'],
|
||||||
|
instruction: '{task}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const piece = normalizePieceConfig(raw, tempDir);
|
||||||
|
|
||||||
|
expect(piece.movements[0].knowledgeContents).toHaveLength(2);
|
||||||
|
expect(piece.movements[0].knowledgeContents).toContain(frontendKnowledge);
|
||||||
|
expect(piece.movements[0].knowledgeContents).toContain(backendKnowledge);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve knowledge on parallel sub-movements', () => {
|
||||||
|
const securityKnowledge = '# Security\nOWASP guidelines.';
|
||||||
|
writeFileSync(join(tempDir, 'security.md'), securityKnowledge);
|
||||||
|
|
||||||
|
const raw = {
|
||||||
|
name: 'test-piece',
|
||||||
|
knowledge: {
|
||||||
|
security: 'security.md',
|
||||||
|
},
|
||||||
|
movements: [
|
||||||
|
{
|
||||||
|
name: 'review',
|
||||||
|
parallel: [
|
||||||
|
{
|
||||||
|
name: 'sec-review',
|
||||||
|
persona: 'reviewer.md',
|
||||||
|
knowledge: 'security',
|
||||||
|
instruction_template: 'Review security',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rules: [{ condition: 'approved', next: 'COMPLETE' }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const piece = normalizePieceConfig(raw, tempDir);
|
||||||
|
|
||||||
|
expect(piece.movements[0].parallel).toHaveLength(1);
|
||||||
|
expect(piece.movements[0].parallel![0].knowledgeContents).toEqual([securityKnowledge]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle inline knowledge content', () => {
|
||||||
|
const raw = {
|
||||||
|
name: 'test-piece',
|
||||||
|
knowledge: {
|
||||||
|
inline: 'This is inline knowledge content.',
|
||||||
|
},
|
||||||
|
movements: [
|
||||||
|
{
|
||||||
|
name: 'implement',
|
||||||
|
persona: 'coder.md',
|
||||||
|
knowledge: 'inline',
|
||||||
|
instruction: '{task}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const piece = normalizePieceConfig(raw, tempDir);
|
||||||
|
|
||||||
|
expect(piece.knowledge!['inline']).toBe('This is inline knowledge content.');
|
||||||
|
expect(piece.movements[0].knowledgeContents).toEqual(['This is inline knowledge content.']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle direct file path reference without piece-level map', () => {
|
||||||
|
const directKnowledge = '# Direct Knowledge\nLoaded directly.';
|
||||||
|
writeFileSync(join(tempDir, 'direct.md'), directKnowledge);
|
||||||
|
|
||||||
|
const raw = {
|
||||||
|
name: 'test-piece',
|
||||||
|
movements: [
|
||||||
|
{
|
||||||
|
name: 'implement',
|
||||||
|
persona: 'coder.md',
|
||||||
|
knowledge: 'direct.md',
|
||||||
|
instruction: '{task}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const piece = normalizePieceConfig(raw, tempDir);
|
||||||
|
|
||||||
|
expect(piece.movements[0].knowledgeContents).toEqual([directKnowledge]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should treat non-file reference as inline content when knowledge reference not found in map', () => {
|
||||||
|
const raw = {
|
||||||
|
name: 'test-piece',
|
||||||
|
movements: [
|
||||||
|
{
|
||||||
|
name: 'implement',
|
||||||
|
persona: 'coder.md',
|
||||||
|
knowledge: 'nonexistent',
|
||||||
|
instruction: '{task}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const piece = normalizePieceConfig(raw, tempDir);
|
||||||
|
|
||||||
|
// Non-.md references that are not in the knowledge map are treated as inline content
|
||||||
|
expect(piece.movements[0].knowledgeContents).toEqual(['nonexistent']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Test helpers for InstructionBuilder ---
|
||||||
|
|
||||||
|
function createMinimalStep(instructionTemplate: string): PieceMovement {
|
||||||
|
return {
|
||||||
|
name: 'test-step',
|
||||||
|
personaDisplayName: 'coder',
|
||||||
|
instructionTemplate,
|
||||||
|
passPreviousResponse: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMinimalContext(overrides: Partial<InstructionContext> = {}): InstructionContext {
|
||||||
|
return {
|
||||||
|
task: 'Test task',
|
||||||
|
iteration: 1,
|
||||||
|
maxIterations: 10,
|
||||||
|
movementIteration: 1,
|
||||||
|
cwd: '/tmp/test',
|
||||||
|
projectCwd: '/tmp/test',
|
||||||
|
userInputs: [],
|
||||||
|
language: 'ja',
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- InstructionBuilder knowledge injection tests ---
|
||||||
|
|
||||||
|
describe('InstructionBuilder knowledge injection', () => {
|
||||||
|
it('should inject knowledge section when knowledgeContents present in step', () => {
|
||||||
|
const step = createMinimalStep('{task}');
|
||||||
|
step.knowledgeContents = ['# Frontend Knowledge\n\nUse React.'];
|
||||||
|
const ctx = createMinimalContext();
|
||||||
|
const builder = new InstructionBuilder(step, ctx);
|
||||||
|
const result = builder.build();
|
||||||
|
|
||||||
|
expect(result).toContain('## Knowledge');
|
||||||
|
expect(result).toContain('Frontend Knowledge');
|
||||||
|
expect(result).toContain('Use React.');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not inject knowledge section when no knowledgeContents', () => {
|
||||||
|
const step = createMinimalStep('{task}');
|
||||||
|
const ctx = createMinimalContext();
|
||||||
|
const builder = new InstructionBuilder(step, ctx);
|
||||||
|
const result = builder.build();
|
||||||
|
|
||||||
|
expect(result).not.toContain('## Knowledge');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should prefer context knowledgeContents over step knowledgeContents', () => {
|
||||||
|
const step = createMinimalStep('{task}');
|
||||||
|
step.knowledgeContents = ['Step knowledge.'];
|
||||||
|
const ctx = createMinimalContext({
|
||||||
|
knowledgeContents: ['Context knowledge.'],
|
||||||
|
});
|
||||||
|
const builder = new InstructionBuilder(step, ctx);
|
||||||
|
const result = builder.build();
|
||||||
|
|
||||||
|
expect(result).toContain('Context knowledge.');
|
||||||
|
expect(result).not.toContain('Step knowledge.');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should join multiple knowledge contents with separator', () => {
|
||||||
|
const step = createMinimalStep('{task}');
|
||||||
|
step.knowledgeContents = ['Knowledge A content.', 'Knowledge B content.'];
|
||||||
|
const ctx = createMinimalContext();
|
||||||
|
const builder = new InstructionBuilder(step, ctx);
|
||||||
|
const result = builder.build();
|
||||||
|
|
||||||
|
expect(result).toContain('Knowledge A content.');
|
||||||
|
expect(result).toContain('Knowledge B content.');
|
||||||
|
expect(result).toContain('---');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should inject knowledge section in English', () => {
|
||||||
|
const step = createMinimalStep('{task}');
|
||||||
|
step.knowledgeContents = ['# API Guidelines\n\nUse REST conventions.'];
|
||||||
|
const ctx = createMinimalContext({ language: 'en' });
|
||||||
|
const builder = new InstructionBuilder(step, ctx);
|
||||||
|
const result = builder.build();
|
||||||
|
|
||||||
|
expect(result).toContain('## Knowledge');
|
||||||
|
expect(result).toContain('API Guidelines');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('knowledge and stance coexistence', () => {
|
||||||
|
let tempDir: string;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
tempDir = mkdtempSync(join(tmpdir(), 'takt-knowledge-stance-test-'));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve both stance and knowledge for same movement', () => {
|
||||||
|
const stanceContent = '# Coding Stance\nWrite clean code.';
|
||||||
|
const knowledgeContent = '# Frontend Knowledge\nUse TypeScript.';
|
||||||
|
writeFileSync(join(tempDir, 'coding.md'), stanceContent);
|
||||||
|
writeFileSync(join(tempDir, 'frontend.md'), knowledgeContent);
|
||||||
|
|
||||||
|
const raw = {
|
||||||
|
name: 'test-piece',
|
||||||
|
stances: {
|
||||||
|
coding: 'coding.md',
|
||||||
|
},
|
||||||
|
knowledge: {
|
||||||
|
frontend: 'frontend.md',
|
||||||
|
},
|
||||||
|
movements: [
|
||||||
|
{
|
||||||
|
name: 'implement',
|
||||||
|
persona: 'coder.md',
|
||||||
|
stance: 'coding',
|
||||||
|
knowledge: 'frontend',
|
||||||
|
instruction: '{task}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const piece = normalizePieceConfig(raw, tempDir);
|
||||||
|
|
||||||
|
expect(piece.stances!['coding']).toBe(stanceContent);
|
||||||
|
expect(piece.knowledge!['frontend']).toBe(knowledgeContent);
|
||||||
|
expect(piece.movements[0].stanceContents).toEqual([stanceContent]);
|
||||||
|
expect(piece.movements[0].knowledgeContents).toEqual([knowledgeContent]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -83,6 +83,8 @@ export interface PieceMovement {
|
|||||||
parallel?: PieceMovement[];
|
parallel?: PieceMovement[];
|
||||||
/** Resolved stance content strings (from piece-level stances map, resolved at parse time) */
|
/** Resolved stance content strings (from piece-level stances map, resolved at parse time) */
|
||||||
stanceContents?: string[];
|
stanceContents?: string[];
|
||||||
|
/** Resolved knowledge content strings (from piece-level knowledge map, resolved at parse time) */
|
||||||
|
knowledgeContents?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Loop detection configuration */
|
/** Loop detection configuration */
|
||||||
@ -131,6 +133,8 @@ export interface PieceConfig {
|
|||||||
personas?: Record<string, string>;
|
personas?: Record<string, string>;
|
||||||
/** Resolved stance definitions — map of name to file content (resolved at parse time) */
|
/** Resolved stance definitions — map of name to file content (resolved at parse time) */
|
||||||
stances?: Record<string, string>;
|
stances?: Record<string, string>;
|
||||||
|
/** Resolved knowledge definitions — map of name to file content (resolved at parse time) */
|
||||||
|
knowledge?: Record<string, string>;
|
||||||
/** Resolved instruction definitions — map of name to file content (resolved at parse time) */
|
/** Resolved instruction definitions — map of name to file content (resolved at parse time) */
|
||||||
instructions?: Record<string, string>;
|
instructions?: Record<string, string>;
|
||||||
/** Resolved report format definitions — map of name to file content (resolved at parse time) */
|
/** Resolved report format definitions — map of name to file content (resolved at parse time) */
|
||||||
|
|||||||
@ -122,6 +122,8 @@ export const ParallelSubMovementRawSchema = z.object({
|
|||||||
persona_name: z.string().optional(),
|
persona_name: z.string().optional(),
|
||||||
/** Stance reference(s) — key name(s) from piece-level stances map */
|
/** Stance reference(s) — key name(s) from piece-level stances map */
|
||||||
stance: z.union([z.string(), z.array(z.string())]).optional(),
|
stance: z.union([z.string(), z.array(z.string())]).optional(),
|
||||||
|
/** Knowledge reference(s) — key name(s) from piece-level knowledge map */
|
||||||
|
knowledge: z.union([z.string(), z.array(z.string())]).optional(),
|
||||||
allowed_tools: z.array(z.string()).optional(),
|
allowed_tools: z.array(z.string()).optional(),
|
||||||
provider: z.enum(['claude', 'codex', 'mock']).optional(),
|
provider: z.enum(['claude', 'codex', 'mock']).optional(),
|
||||||
model: z.string().optional(),
|
model: z.string().optional(),
|
||||||
@ -146,6 +148,8 @@ export const PieceMovementRawSchema = z.object({
|
|||||||
persona_name: z.string().optional(),
|
persona_name: z.string().optional(),
|
||||||
/** Stance reference(s) — key name(s) from piece-level stances map */
|
/** Stance reference(s) — key name(s) from piece-level stances map */
|
||||||
stance: z.union([z.string(), z.array(z.string())]).optional(),
|
stance: z.union([z.string(), z.array(z.string())]).optional(),
|
||||||
|
/** Knowledge reference(s) — key name(s) from piece-level knowledge map */
|
||||||
|
knowledge: z.union([z.string(), z.array(z.string())]).optional(),
|
||||||
allowed_tools: z.array(z.string()).optional(),
|
allowed_tools: z.array(z.string()).optional(),
|
||||||
provider: z.enum(['claude', 'codex', 'mock']).optional(),
|
provider: z.enum(['claude', 'codex', 'mock']).optional(),
|
||||||
model: z.string().optional(),
|
model: z.string().optional(),
|
||||||
@ -200,6 +204,8 @@ export const PieceConfigRawSchema = z.object({
|
|||||||
personas: z.record(z.string(), z.string()).optional(),
|
personas: z.record(z.string(), z.string()).optional(),
|
||||||
/** Piece-level stance definitions — map of name to .md file path or inline content */
|
/** Piece-level stance definitions — map of name to .md file path or inline content */
|
||||||
stances: z.record(z.string(), z.string()).optional(),
|
stances: z.record(z.string(), z.string()).optional(),
|
||||||
|
/** Piece-level knowledge definitions — map of name to .md file path or inline content */
|
||||||
|
knowledge: z.record(z.string(), z.string()).optional(),
|
||||||
/** Piece-level instruction definitions — map of name to .md file path or inline content */
|
/** Piece-level instruction definitions — map of name to .md file path or inline content */
|
||||||
instructions: z.record(z.string(), z.string()).optional(),
|
instructions: z.record(z.string(), z.string()).optional(),
|
||||||
/** Piece-level report format definitions — map of name to .md file path or inline content */
|
/** Piece-level report format definitions — map of name to .md file path or inline content */
|
||||||
|
|||||||
@ -78,6 +78,7 @@ export class MovementExecutor {
|
|||||||
pieceDescription: this.deps.getPieceDescription(),
|
pieceDescription: this.deps.getPieceDescription(),
|
||||||
retryNote: this.deps.getRetryNote(),
|
retryNote: this.deps.getRetryNote(),
|
||||||
stanceContents: step.stanceContents,
|
stanceContents: step.stanceContents,
|
||||||
|
knowledgeContents: step.knowledgeContents,
|
||||||
}).build();
|
}).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -104,6 +104,11 @@ export class InstructionBuilder {
|
|||||||
const stanceContent = hasStance ? stanceContents!.join('\n\n---\n\n') : '';
|
const stanceContent = hasStance ? stanceContents!.join('\n\n---\n\n') : '';
|
||||||
const stanceReminder = ''; // Reminder text is in the template itself
|
const stanceReminder = ''; // Reminder text is in the template itself
|
||||||
|
|
||||||
|
// Knowledge injection (domain-specific knowledge, no reminder needed)
|
||||||
|
const knowledgeContents = this.context.knowledgeContents ?? this.step.knowledgeContents;
|
||||||
|
const hasKnowledge = !!(knowledgeContents && knowledgeContents.length > 0);
|
||||||
|
const knowledgeContent = hasKnowledge ? knowledgeContents!.join('\n\n---\n\n') : '';
|
||||||
|
|
||||||
return loadTemplate('perform_phase1_message', language, {
|
return loadTemplate('perform_phase1_message', language, {
|
||||||
workingDirectory: this.context.cwd,
|
workingDirectory: this.context.cwd,
|
||||||
editRule,
|
editRule,
|
||||||
@ -128,6 +133,8 @@ export class InstructionBuilder {
|
|||||||
hasStance,
|
hasStance,
|
||||||
stanceContent,
|
stanceContent,
|
||||||
stanceReminder,
|
stanceReminder,
|
||||||
|
hasKnowledge,
|
||||||
|
knowledgeContent,
|
||||||
instructions,
|
instructions,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -44,6 +44,8 @@ export interface InstructionContext {
|
|||||||
retryNote?: string;
|
retryNote?: string;
|
||||||
/** Resolved stance content strings for injection into instruction */
|
/** Resolved stance content strings for injection into instruction */
|
||||||
stanceContents?: string[];
|
stanceContents?: string[];
|
||||||
|
/** Resolved knowledge content strings for injection into instruction */
|
||||||
|
knowledgeContents?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -132,7 +132,7 @@ interface ResourceRef {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Known resource type directories that can be referenced from piece YAML */
|
/** Known resource type directories that can be referenced from piece YAML */
|
||||||
const RESOURCE_TYPES = ['personas', 'stances', 'instructions', 'report-formats'];
|
const RESOURCE_TYPES = ['personas', 'stances', 'knowledge', 'instructions', 'report-formats'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract resource relative paths from a builtin piece YAML.
|
* Extract resource relative paths from a builtin piece YAML.
|
||||||
|
|||||||
@ -106,6 +106,8 @@ interface PieceSections {
|
|||||||
personas?: Record<string, string>;
|
personas?: Record<string, string>;
|
||||||
/** Stance name → resolved content */
|
/** Stance name → resolved content */
|
||||||
resolvedStances?: Record<string, string>;
|
resolvedStances?: Record<string, string>;
|
||||||
|
/** Knowledge name → resolved content */
|
||||||
|
resolvedKnowledge?: Record<string, string>;
|
||||||
/** Instruction name → resolved content */
|
/** Instruction name → resolved content */
|
||||||
resolvedInstructions?: Record<string, string>;
|
resolvedInstructions?: Record<string, string>;
|
||||||
/** Report format name → resolved content */
|
/** Report format name → resolved content */
|
||||||
@ -232,6 +234,9 @@ function normalizeStepFromRaw(
|
|||||||
const stanceRef = (step as Record<string, unknown>).stance as string | string[] | undefined;
|
const stanceRef = (step as Record<string, unknown>).stance as string | string[] | undefined;
|
||||||
const stanceContents = resolveRefList(stanceRef, sections.resolvedStances, pieceDir);
|
const stanceContents = resolveRefList(stanceRef, sections.resolvedStances, pieceDir);
|
||||||
|
|
||||||
|
const knowledgeRef = (step as Record<string, unknown>).knowledge as string | string[] | undefined;
|
||||||
|
const knowledgeContents = resolveRefList(knowledgeRef, sections.resolvedKnowledge, pieceDir);
|
||||||
|
|
||||||
const expandedInstruction = step.instruction
|
const expandedInstruction = step.instruction
|
||||||
? resolveRefToContent(step.instruction, sections.resolvedInstructions, pieceDir)
|
? resolveRefToContent(step.instruction, sections.resolvedInstructions, pieceDir)
|
||||||
: undefined;
|
: undefined;
|
||||||
@ -253,6 +258,7 @@ function normalizeStepFromRaw(
|
|||||||
report: normalizeReport(step.report, pieceDir, sections.resolvedReportFormats),
|
report: normalizeReport(step.report, pieceDir, sections.resolvedReportFormats),
|
||||||
passPreviousResponse: step.pass_previous_response ?? true,
|
passPreviousResponse: step.pass_previous_response ?? true,
|
||||||
stanceContents,
|
stanceContents,
|
||||||
|
knowledgeContents,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (step.parallel && step.parallel.length > 0) {
|
if (step.parallel && step.parallel.length > 0) {
|
||||||
@ -299,12 +305,14 @@ export function normalizePieceConfig(raw: unknown, pieceDir: string): PieceConfi
|
|||||||
const parsed = PieceConfigRawSchema.parse(raw);
|
const parsed = PieceConfigRawSchema.parse(raw);
|
||||||
|
|
||||||
const resolvedStances = resolveSectionMap(parsed.stances, pieceDir);
|
const resolvedStances = resolveSectionMap(parsed.stances, pieceDir);
|
||||||
|
const resolvedKnowledge = resolveSectionMap(parsed.knowledge, pieceDir);
|
||||||
const resolvedInstructions = resolveSectionMap(parsed.instructions, pieceDir);
|
const resolvedInstructions = resolveSectionMap(parsed.instructions, pieceDir);
|
||||||
const resolvedReportFormats = resolveSectionMap(parsed.report_formats, pieceDir);
|
const resolvedReportFormats = resolveSectionMap(parsed.report_formats, pieceDir);
|
||||||
|
|
||||||
const sections: PieceSections = {
|
const sections: PieceSections = {
|
||||||
personas: parsed.personas,
|
personas: parsed.personas,
|
||||||
resolvedStances,
|
resolvedStances,
|
||||||
|
resolvedKnowledge,
|
||||||
resolvedInstructions,
|
resolvedInstructions,
|
||||||
resolvedReportFormats,
|
resolvedReportFormats,
|
||||||
};
|
};
|
||||||
@ -321,6 +329,7 @@ export function normalizePieceConfig(raw: unknown, pieceDir: string): PieceConfi
|
|||||||
description: parsed.description,
|
description: parsed.description,
|
||||||
personas: parsed.personas,
|
personas: parsed.personas,
|
||||||
stances: resolvedStances,
|
stances: resolvedStances,
|
||||||
|
knowledge: resolvedKnowledge,
|
||||||
instructions: resolvedInstructions,
|
instructions: resolvedInstructions,
|
||||||
reportFormats: resolvedReportFormats,
|
reportFormats: resolvedReportFormats,
|
||||||
movements,
|
movements,
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
pieceStructure, iteration, movementIteration, movement, hasReport, reportInfo,
|
pieceStructure, iteration, movementIteration, movement, hasReport, reportInfo,
|
||||||
phaseNote, hasTaskSection, userRequest, hasPreviousResponse, previousResponse,
|
phaseNote, hasTaskSection, userRequest, hasPreviousResponse, previousResponse,
|
||||||
hasUserInputs, userInputs, hasRetryNote, retryNote, hasStance, stanceContent,
|
hasUserInputs, userInputs, hasRetryNote, retryNote, hasStance, stanceContent,
|
||||||
stanceReminder, instructions
|
stanceReminder, hasKnowledge, knowledgeContent, instructions
|
||||||
builder: InstructionBuilder
|
builder: InstructionBuilder
|
||||||
-->
|
-->
|
||||||
## Execution Context
|
## Execution Context
|
||||||
@ -25,6 +25,13 @@ The following stances are behavioral standards applied to this movement. You MUS
|
|||||||
|
|
||||||
{{stanceContent}}
|
{{stanceContent}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
{{#if hasKnowledge}}
|
||||||
|
|
||||||
|
## Knowledge
|
||||||
|
The following knowledge is domain-specific information for this movement. Use it as reference.
|
||||||
|
|
||||||
|
{{knowledgeContent}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
## Piece Context
|
## Piece Context
|
||||||
{{#if pieceName}}- Piece: {{pieceName}}
|
{{#if pieceName}}- Piece: {{pieceName}}
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
pieceStructure, iteration, movementIteration, movement, hasReport, reportInfo,
|
pieceStructure, iteration, movementIteration, movement, hasReport, reportInfo,
|
||||||
phaseNote, hasTaskSection, userRequest, hasPreviousResponse, previousResponse,
|
phaseNote, hasTaskSection, userRequest, hasPreviousResponse, previousResponse,
|
||||||
hasUserInputs, userInputs, hasRetryNote, retryNote, hasStance, stanceContent,
|
hasUserInputs, userInputs, hasRetryNote, retryNote, hasStance, stanceContent,
|
||||||
stanceReminder, instructions
|
stanceReminder, hasKnowledge, knowledgeContent, instructions
|
||||||
builder: InstructionBuilder
|
builder: InstructionBuilder
|
||||||
-->
|
-->
|
||||||
## 実行コンテキスト
|
## 実行コンテキスト
|
||||||
@ -24,6 +24,13 @@
|
|||||||
|
|
||||||
{{stanceContent}}
|
{{stanceContent}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
{{#if hasKnowledge}}
|
||||||
|
|
||||||
|
## Knowledge
|
||||||
|
以下のナレッジはこのムーブメントに適用されるドメイン固有の知識です。参考にしてください。
|
||||||
|
|
||||||
|
{{knowledgeContent}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
## Piece Context
|
## Piece Context
|
||||||
{{#if pieceName}}- ピース: {{pieceName}}
|
{{#if pieceName}}- ピース: {{pieceName}}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user