Release: v2.36.9 - Integrate Blog Production Manual & Writing Patterns

This commit is contained in:
g1nation
2026-05-02 21:12:46 +09:00
parent 457d9cf345
commit d5aad75a10
5 changed files with 138 additions and 15 deletions
+65 -14
View File
@@ -416,9 +416,12 @@ export class AgentExecutor {
// 5. Execute Actions
const rationale = this.parseRationale(aiResponseText);
const assistantContent = enforceProjectClaimPolicyInAnswer(
this.sanitizeAssistantContent(aiResponseText),
secondBrainTrace
const assistantContent = this.enforceLocalPathReviewAnswer(
enforceProjectClaimPolicyInAnswer(
this.sanitizeAssistantContent(aiResponseText),
secondBrainTrace
),
localPathContext
);
const traceMarkdown = secondBrainTrace
? renderSecondBrainTraceMarkdown(secondBrainTrace, !!options.secondBrainTraceDebug)
@@ -623,7 +626,8 @@ export class AgentExecutor {
const sections: string[] = [
'[LOCAL PROJECT PATH PREFLIGHT]',
'The user provided a local project path for review or analysis. Use this inspected context before asking for uploads.',
'If access failed, explain the concrete failure. If access succeeded, proceed with code review from the scanned files.'
'If access failed, explain the concrete failure. If access succeeded, proceed with code review from the scanned files.',
'If access succeeded and priority file previews are present, do not say that code was not provided.'
];
for (const candidate of candidates.slice(0, 2)) {
@@ -665,14 +669,14 @@ export class AgentExecutor {
].join('\n');
}
const tree = this.listProjectTree(absPath, absPath, 0, 2, 80);
const priorityFiles = this.findPriorityProjectFiles(absPath).slice(0, 8);
const tree = this.listProjectTree(absPath, absPath, 0, 4, 140);
const priorityFiles = this.findPriorityProjectFiles(absPath).slice(0, 12);
const previews = priorityFiles.map((file) => {
try {
const content = fs.readFileSync(file, 'utf8');
return [
`### ${path.relative(absPath, file)}`,
summarizeText(content, 1800)
summarizeText(content, 2200)
].join('\n');
} catch (error: any) {
return `### ${path.relative(absPath, file)}\nRead failed: ${error.message}`;
@@ -697,6 +701,35 @@ export class AgentExecutor {
}
}
private enforceLocalPathReviewAnswer(content: string, localPathContext: string): string {
if (!localPathContext.includes('Access: succeeded')) {
return content;
}
const asksForUpload = /(코드(?:를|가)?\s*업로드|파일(?:을|를)?\s*업로드|소스\s*코드(?:를)?\s*업로드|코드를 제공|파일을 제공|folder path is not enough|upload (?:the )?(?:source )?code|please provide (?:the )?files)/i.test(content);
const deniesCodeAccess = /(실제 코드 내용이 없|코드 내용이 없|코드가 없|코드를 볼 수 없|소스 코드를 볼 수 없|기술적인 진단.*수 없습니다)/i.test(content);
if (!asksForUpload && !deniesCodeAccess) {
return content;
}
const header = [
'## 경로 확인 결과',
'',
'제공된 로컬 프로젝트 경로에는 접근할 수 있고, 코드 파일도 확인되었습니다. 따라서 파일 업로드를 요청하는 대신, 확인된 파일 구조와 코드 프리뷰를 기준으로 리뷰를 진행해야 합니다.',
'',
'이전 응답의 "코드를 업로드해 주세요" 취지의 문장은 잘못된 안내입니다.'
].join('\n');
return [
header,
'',
content
.replace(/.*(?:코드(?:를|가)?\s*업로드|파일(?:을|를)?\s*업로드|소스\s*코드(?:를)?\s*업로드|코드를 제공|파일을 제공).*$/gmi, '')
.replace(/.*(?:실제 코드 내용이 없|코드 내용이 없|코드가 없|코드를 볼 수 없|소스 코드를 볼 수 없).*$/gmi, '')
.trim()
].filter(Boolean).join('\n\n');
}
private listProjectTree(root: string, current: string, depth: number, maxDepth: number, limit: number): string {
if (limit <= 0 || depth > maxDepth) {
return '';
@@ -741,8 +774,8 @@ export class AgentExecutor {
'webpack.config.js'
]);
const results: string[] = [];
const visit = (dir: string, depth: number) => {
if (depth > 3 || results.length >= 14) return;
const visit = (dir: string, depth: number, inSourceArea: boolean) => {
if (depth > 6 || results.length >= 24) return;
let entries: fs.Dirent[] = [];
try {
entries = fs.readdirSync(dir, { withFileTypes: true })
@@ -754,16 +787,19 @@ export class AgentExecutor {
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
if (/^(src|app|pages|components|docs|lib|server|backend|frontend|config)$/i.test(entry.name)) {
visit(fullPath, depth + 1);
const nextInSourceArea = inSourceArea || /^(src|app|pages|components|docs|lib|server|backend|frontend|config|features|core|hooks|systems|store|model|utils|ui|api)$/i.test(entry.name);
if (nextInSourceArea) {
visit(fullPath, depth + 1, nextInSourceArea);
}
continue;
}
const relative = path.relative(root, fullPath);
const isSourceCode = /\.(ts|tsx|js|jsx)$/i.test(entry.name);
if (
exactNames.has(entry.name)
|| /(^|[\\/])(src|app|pages|components|docs|lib|server|backend|frontend)[\\/].+\.(ts|tsx|js|jsx|md|json)$/i.test(relative)
|| (inSourceArea && isSourceCode)
|| /(^|[\\/])(src|app|pages|components|docs|lib|server|backend|frontend|features|core)[\\/].+\.(ts|tsx|js|jsx|md|json)$/i.test(relative)
|| /\.(config|rc)\.(js|ts|json)$/i.test(entry.name)
) {
results.push(fullPath);
@@ -771,8 +807,23 @@ export class AgentExecutor {
}
};
visit(root, 0);
return Array.from(new Set(results));
visit(root, 0, false);
return Array.from(new Set(results)).sort((a, b) => {
const rank = (file: string) => {
const relative = path.relative(root, file);
if (path.basename(file) === 'package.json') return 0;
if (/readme\.md$/i.test(file)) return 1;
if (/^src[\\/]App\.tsx$/i.test(relative)) return 2;
if (/^src[\\/]main\.tsx$/i.test(relative)) return 3;
if (/^src[\\/]features[\\/]game[\\/]hooks[\\/]useGameEngine\.ts$/i.test(relative)) return 4;
if (/^src[\\/]features[\\/]game[\\/]systems[\\/]/i.test(relative)) return 5;
if (/^src[\\/]features[\\/]game[\\/]ui[\\/]/i.test(relative)) return 6;
if (/^src[\\/]/i.test(relative)) return 7;
if (/^docs[\\/]|\.md$/i.test(relative)) return 8;
return 9;
};
return rank(a) - rank(b) || a.localeCompare(b);
});
}
private buildMemoryContext(currentPrompt: string): string {