feat: integrate unified RAG pipeline and bump version to 2.60.0
This commit is contained in:
@@ -0,0 +1,212 @@
|
||||
/**
|
||||
* ============================================================
|
||||
* Project Memory (프로젝트 기억)
|
||||
*
|
||||
* 프로젝트별 요구사항, 코드 구조, 아키텍처 결정, 버그 기록 등을
|
||||
* 프로젝트 로컬에 저장하고 관리합니다.
|
||||
* 저장 위치: {projectRoot}/.astra/project_memory.json
|
||||
* ============================================================
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as crypto from 'crypto';
|
||||
import { ProjectMemoryStore, ArchitectureDecision, BugRecord, MemoryContextResult } from './types';
|
||||
|
||||
export class ProjectMemory {
|
||||
private store: ProjectMemoryStore;
|
||||
private filePath: string;
|
||||
private dirty = false;
|
||||
|
||||
constructor(projectRoot: string) {
|
||||
const astraDir = path.join(projectRoot, '.astra');
|
||||
if (!fs.existsSync(astraDir)) {
|
||||
fs.mkdirSync(astraDir, { recursive: true });
|
||||
}
|
||||
this.filePath = path.join(astraDir, 'project_memory.json');
|
||||
this.store = this.load(projectRoot);
|
||||
}
|
||||
|
||||
// ─── Persistence ───
|
||||
|
||||
private load(projectRoot: string): ProjectMemoryStore {
|
||||
try {
|
||||
if (fs.existsSync(this.filePath)) {
|
||||
const raw = fs.readFileSync(this.filePath, 'utf-8');
|
||||
return JSON.parse(raw) as ProjectMemoryStore;
|
||||
}
|
||||
} catch { /* start fresh */ }
|
||||
|
||||
return {
|
||||
version: 1,
|
||||
projectId: this.hashPath(projectRoot),
|
||||
projectName: path.basename(projectRoot),
|
||||
techStack: [],
|
||||
architectureDecisions: [],
|
||||
bugRecords: [],
|
||||
requirements: [],
|
||||
designDirection: '',
|
||||
codeConventions: [],
|
||||
lastUpdated: Date.now()
|
||||
};
|
||||
}
|
||||
|
||||
public save(): void {
|
||||
if (!this.dirty) return;
|
||||
try {
|
||||
this.store.lastUpdated = Date.now();
|
||||
fs.writeFileSync(this.filePath, JSON.stringify(this.store, null, 2), 'utf-8');
|
||||
this.dirty = false;
|
||||
} catch { /* silently fail */ }
|
||||
}
|
||||
|
||||
private hashPath(p: string): string {
|
||||
return crypto.createHash('sha256').update(p).digest('hex').slice(0, 12);
|
||||
}
|
||||
|
||||
// ─── Getters ───
|
||||
|
||||
public getStore(): ProjectMemoryStore {
|
||||
return { ...this.store };
|
||||
}
|
||||
|
||||
// ─── Tech Stack ───
|
||||
|
||||
public setTechStack(stack: string[]): void {
|
||||
this.store.techStack = [...new Set(stack)];
|
||||
this.dirty = true;
|
||||
this.save();
|
||||
}
|
||||
|
||||
public addTechStack(tech: string): void {
|
||||
if (!this.store.techStack.includes(tech)) {
|
||||
this.store.techStack.push(tech);
|
||||
this.dirty = true;
|
||||
this.save();
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Architecture Decisions ───
|
||||
|
||||
public addArchitectureDecision(
|
||||
title: string,
|
||||
decision: string,
|
||||
rationale: string,
|
||||
alternatives: string[] = []
|
||||
): ArchitectureDecision {
|
||||
const entry: ArchitectureDecision = {
|
||||
id: crypto.randomUUID(),
|
||||
title,
|
||||
decision,
|
||||
rationale,
|
||||
alternatives,
|
||||
date: Date.now()
|
||||
};
|
||||
this.store.architectureDecisions.push(entry);
|
||||
this.dirty = true;
|
||||
this.save();
|
||||
return entry;
|
||||
}
|
||||
|
||||
// ─── Bug Records ───
|
||||
|
||||
public addBugRecord(
|
||||
description: string,
|
||||
rootCause: string,
|
||||
fix: string,
|
||||
relatedFiles: string[] = []
|
||||
): BugRecord {
|
||||
const entry: BugRecord = {
|
||||
id: crypto.randomUUID(),
|
||||
description,
|
||||
rootCause,
|
||||
fix,
|
||||
date: Date.now(),
|
||||
relatedFiles
|
||||
};
|
||||
this.store.bugRecords.push(entry);
|
||||
this.dirty = true;
|
||||
this.save();
|
||||
return entry;
|
||||
}
|
||||
|
||||
// ─── Requirements ───
|
||||
|
||||
public addRequirement(req: string): void {
|
||||
if (!this.store.requirements.includes(req)) {
|
||||
this.store.requirements.push(req);
|
||||
this.dirty = true;
|
||||
this.save();
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Design Direction ───
|
||||
|
||||
public setDesignDirection(direction: string): void {
|
||||
this.store.designDirection = direction;
|
||||
this.dirty = true;
|
||||
this.save();
|
||||
}
|
||||
|
||||
// ─── Code Conventions ───
|
||||
|
||||
public addCodeConvention(convention: string): void {
|
||||
if (!this.store.codeConventions.includes(convention)) {
|
||||
this.store.codeConventions.push(convention);
|
||||
this.dirty = true;
|
||||
this.save();
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Context Building ───
|
||||
|
||||
public buildContext(currentPrompt: string): MemoryContextResult | null {
|
||||
const sections: string[] = [];
|
||||
|
||||
if (this.store.techStack.length > 0) {
|
||||
sections.push(`Tech Stack: ${this.store.techStack.join(', ')}`);
|
||||
}
|
||||
|
||||
if (this.store.designDirection) {
|
||||
sections.push(`Design Direction: ${this.store.designDirection}`);
|
||||
}
|
||||
|
||||
if (this.store.codeConventions.length > 0) {
|
||||
sections.push(`Code Conventions:\n${this.store.codeConventions.map((c) => ` - ${c}`).join('\n')}`);
|
||||
}
|
||||
|
||||
if (this.store.requirements.length > 0) {
|
||||
const reqs = this.store.requirements.slice(-5);
|
||||
sections.push(`Recent Requirements:\n${reqs.map((r) => ` - ${r}`).join('\n')}`);
|
||||
}
|
||||
|
||||
// Show recent architecture decisions (last 3)
|
||||
if (this.store.architectureDecisions.length > 0) {
|
||||
const recent = this.store.architectureDecisions
|
||||
.sort((a, b) => b.date - a.date)
|
||||
.slice(0, 3);
|
||||
sections.push(
|
||||
`Architecture Decisions:\n${recent.map((d) => ` - ${d.title}: ${d.decision}`).join('\n')}`
|
||||
);
|
||||
}
|
||||
|
||||
// Show recent bugs (last 3)
|
||||
if (this.store.bugRecords.length > 0) {
|
||||
const recent = this.store.bugRecords
|
||||
.sort((a, b) => b.date - a.date)
|
||||
.slice(0, 3);
|
||||
sections.push(
|
||||
`Recent Bugs:\n${recent.map((b) => ` - ${b.description} → ${b.fix}`).join('\n')}`
|
||||
);
|
||||
}
|
||||
|
||||
if (sections.length === 0) return null;
|
||||
|
||||
return {
|
||||
layer: 'project',
|
||||
label: `Project Memory (${this.store.projectName})`,
|
||||
content: sections.join('\n'),
|
||||
relevance: 0.8
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user