暗黙の claude フォールバックを削除し、未設定時は明確なエラーを返すように変更。 permission は未設定時 readonly にフォールバック。テスト・E2E を新挙動に適合。
105 lines
3.2 KiB
TypeScript
105 lines
3.2 KiB
TypeScript
/**
|
|
* Task name summarization using AI or romanization
|
|
*
|
|
* Generates concise English/romaji summaries for use in branch names and clone paths.
|
|
*/
|
|
|
|
import * as wanakana from 'wanakana';
|
|
import { resolveConfigValues } from '../config/index.js';
|
|
import { getProvider, type ProviderType } from '../providers/index.js';
|
|
import { createLogger, slugify } from '../../shared/utils/index.js';
|
|
import { loadTemplate } from '../../shared/prompts/index.js';
|
|
import type { SummarizeOptions } from './types.js';
|
|
|
|
export type { SummarizeOptions };
|
|
|
|
const log = createLogger('summarize');
|
|
const MAX_ROMAJI_CHUNK_SIZE = 1024;
|
|
|
|
function toRomajiSafely(text: string): string {
|
|
const romajiOptions = { customRomajiMapping: {} };
|
|
try {
|
|
if (text.length <= MAX_ROMAJI_CHUNK_SIZE) {
|
|
return wanakana.toRomaji(text, romajiOptions);
|
|
}
|
|
const convertedChunks: string[] = [];
|
|
for (let i = 0; i < text.length; i += MAX_ROMAJI_CHUNK_SIZE) {
|
|
convertedChunks.push(
|
|
wanakana.toRomaji(text.slice(i, i + MAX_ROMAJI_CHUNK_SIZE), romajiOptions),
|
|
);
|
|
}
|
|
return convertedChunks.join('');
|
|
} catch (err) {
|
|
log.error('Failed to convert to romaji', { error: err, textLength: text.length });
|
|
return text;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convert Japanese text to romaji slug.
|
|
*/
|
|
function toRomajiSlug(text: string): string {
|
|
const romaji = toRomajiSafely(text);
|
|
return slugify(romaji);
|
|
}
|
|
|
|
/**
|
|
* Summarizes task names into concise slugs using AI or romanization.
|
|
*/
|
|
export class TaskSummarizer {
|
|
/**
|
|
* Summarize a task name into a concise slug.
|
|
*
|
|
* @param taskName - Original task name (can be in any language)
|
|
* @param options - Summarization options
|
|
* @returns Slug suitable for branch names (English if LLM, romaji if not)
|
|
*/
|
|
async summarize(
|
|
taskName: string,
|
|
options: SummarizeOptions,
|
|
): Promise<string> {
|
|
const globalConfig = resolveConfigValues(options.cwd, ['branchNameStrategy', 'provider', 'model']);
|
|
const useLLM = options.useLLM ?? (globalConfig.branchNameStrategy === 'ai');
|
|
log.info('Summarizing task name', { taskName, useLLM });
|
|
|
|
if (!useLLM) {
|
|
const slug = toRomajiSlug(taskName);
|
|
log.info('Task name romanized', { original: taskName, slug });
|
|
return slug || 'task';
|
|
}
|
|
if (!globalConfig.provider) {
|
|
throw new Error('No provider configured. Set "provider" in ~/.takt/config.yaml');
|
|
}
|
|
const providerType = globalConfig.provider as ProviderType;
|
|
const model = options.model ?? globalConfig.model;
|
|
|
|
const provider = getProvider(providerType);
|
|
const agent = provider.setup({
|
|
name: 'summarizer',
|
|
systemPrompt: loadTemplate('score_slug_system_prompt', 'en'),
|
|
});
|
|
const prompt = loadTemplate('score_slug_user_prompt', 'en', { taskDescription: taskName });
|
|
const response = await agent.call(prompt, {
|
|
cwd: options.cwd,
|
|
model,
|
|
permissionMode: 'readonly',
|
|
});
|
|
|
|
const slug = slugify(response.content);
|
|
log.info('Task name summarized', { original: taskName, slug });
|
|
|
|
return slug || 'task';
|
|
}
|
|
}
|
|
|
|
// ---- Module-level function ----
|
|
|
|
const defaultSummarizer = new TaskSummarizer();
|
|
|
|
export async function summarizeTaskName(
|
|
taskName: string,
|
|
options: SummarizeOptions,
|
|
): Promise<string> {
|
|
return defaultSummarizer.summarize(taskName, options);
|
|
}
|