@@ -18,6 +18,7 @@ const languageMapping: { [key: string]: string } = {
1818 'js' : 'javascript'
1919} ;
2020
21+ // ================== CVB 核心类 ==================
2122export class Cvb {
2223 private content : string ;
2324 private metadata : Record < string , string > ;
@@ -34,6 +35,10 @@ export class Cvb {
3435 return this . metadata ;
3536 }
3637
38+ setMetaData ( key : string , metadata : string ) {
39+ this . metadata [ key ] = metadata ;
40+ }
41+
3742 getFiles ( ) : Record < string , string > {
3843 return this . files ;
3944 }
@@ -116,20 +121,357 @@ export class Cvb {
116121 files,
117122 } ;
118123 }
124+
125+ static getFormatDescription ( ) : string {
126+ return `
127+ CVB 格式介绍:
128+ - 文件以 "## BEGIN_CVB" 开头,以 "## END_CVB" 结尾。
129+ - 元数据部分以 "## META" 开头,以 "## END_META" 结尾,包含用户需求和时间戳。
130+ - 每个文件以 "## FILE:文件路径" 开头,紧接着是 Markdown 格式的代码块,包含文件内容。
131+ - 多个文件按顺序拼接在一起。
132+ ` ;
133+ }
119134}
120135
121- /**
122- * 返回 CVB 格式介绍的静态字符串
123- * @returns CVB 格式介绍
124- */
125- export function getCvbFormatDescription ( ) : string {
126- return `
127- CVB 格式介绍:
128- - 文件以 "## BEGIN_CVB" 开头,以 "## END_CVB" 结尾。
129- - 元数据部分以 "## META" 开头,以 "## END_META" 结尾,包含用户需求和时间戳。
130- - 每个文件以 "## FILE:文件路径" 开头,紧接着是 Markdown 格式的代码块,包含文件内容。
131- - 多个文件按顺序拼接在一起。
132- ` ;
136+ // ================== TCVB 差量格式 ==================
137+ abstract class CvbOperation {
138+ constructor (
139+ public readonly filePath : string ,
140+ public readonly type : 'replace' | 'insert' | 'delete'
141+ ) { }
142+ }
143+
144+ class ReplaceOperation extends CvbOperation {
145+ constructor (
146+ filePath : string ,
147+ public readonly beforeAnchor : string ,
148+ public readonly afterAnchor : string ,
149+ public readonly oldContent : string ,
150+ public readonly newContent : string
151+ ) {
152+ super ( filePath , 'replace' ) ;
153+ }
154+ }
155+
156+ class InsertOperation extends CvbOperation {
157+ constructor (
158+ filePath : string ,
159+ public readonly beforeAnchor : string ,
160+ public readonly afterAnchor : string ,
161+ public readonly content : string
162+ ) {
163+ super ( filePath , 'insert' ) ;
164+ }
165+ }
166+
167+ class DeleteOperation extends CvbOperation {
168+ constructor (
169+ filePath : string ,
170+ public readonly beforeAnchor : string ,
171+ public readonly afterAnchor : string ,
172+ public readonly oldContent : string
173+ ) {
174+ super ( filePath , 'delete' ) ;
175+ }
176+ }
177+
178+ export class TCVB {
179+ private operations : CvbOperation [ ] = [ ] ;
180+
181+ constructor ( tcvbContent : string ) {
182+ this . parse ( tcvbContent ) ;
183+ }
184+
185+ private parse ( content : string ) {
186+ const fileBlockRegex = / ^ # # F I L E : ( .* ?) \n ( [ \s \S ] * ?) (? = ^ # # F I L E : | ^ # # E N D _ T C V B ) / gm;
187+ let fileMatch : RegExpExecArray | null ;
188+
189+ while ( ( fileMatch = fileBlockRegex . exec ( content ) ) !== null ) {
190+ const filePath = filePathNormalize ( fileMatch [ 1 ] ) ;
191+ const operationsBlock = fileMatch [ 2 ] ;
192+
193+ const operationRegex = / ^ # # O P E R A T I O N : ( \w + ) (?: \s + F I L E : ( .* ?) ) ? \n ( [ \s \S ] * ?) (? = ^ # # O P E R A T I O N : | ^ # # F I L E : | ^ # # E N D _ T C V B ) / gm;
194+ let opMatch : RegExpExecArray | null ;
195+
196+ while ( ( opMatch = operationRegex . exec ( operationsBlock ) ) !== null ) {
197+ const type = opMatch [ 1 ] . toLowerCase ( ) ;
198+ const explicitFilePath = opMatch [ 2 ] ? filePathNormalize ( opMatch [ 2 ] ) : null ;
199+ const operationContent = opMatch [ 3 ] . trim ( ) ;
200+
201+ const finalFilePath = explicitFilePath || filePath ;
202+ this . parseOperation ( finalFilePath , type , operationContent ) ;
203+ }
204+ }
205+ }
206+
207+ private parseOperation ( filePath : string , type : string , content : string ) {
208+ try {
209+ switch ( type ) {
210+ case 'replace' :
211+ this . parseReplace ( filePath , content ) ;
212+ break ;
213+ case 'insert' :
214+ this . parseInsert ( filePath , content ) ;
215+ break ;
216+ case 'delete' :
217+ this . parseDelete ( filePath , content ) ;
218+ break ;
219+ default :
220+ throw new Error ( `Unknown operation type: ${ type } ` ) ;
221+ }
222+ } catch ( e ) {
223+ console . error ( `Failed to parse ${ type } operation for ${ filePath } : ${ e } ` ) ;
224+ }
225+ }
226+
227+ private parseReplace ( filePath : string , content : string ) {
228+ const sections = this . parseSections ( content , [ 'BEFORE_ANCHOR' , 'AFTER_ANCHOR' , 'OLD_CONTENT' , 'NEW_CONTENT' ] ) ;
229+ this . operations . push ( new ReplaceOperation (
230+ filePath ,
231+ sections . BEFORE_ANCHOR ,
232+ sections . AFTER_ANCHOR ,
233+ sections . OLD_CONTENT ,
234+ sections . NEW_CONTENT
235+ ) ) ;
236+ }
237+
238+ private parseInsert ( filePath : string , content : string ) {
239+ const sections = this . parseSections ( content , [ 'BEFORE_ANCHOR' , 'AFTER_ANCHOR' , 'INSERT_CONTENT' ] ) ;
240+ this . operations . push ( new InsertOperation (
241+ filePath ,
242+ sections . BEFORE_ANCHOR ,
243+ sections . AFTER_ANCHOR ,
244+ sections . INSERT_CONTENT
245+ ) ) ;
246+ }
247+
248+ private parseDelete ( filePath : string , content : string ) {
249+ const sections = this . parseSections ( content , [ 'BEFORE_ANCHOR' , 'AFTER_ANCHOR' , 'DELETE_CONTENT' ] ) ;
250+ this . operations . push ( new DeleteOperation (
251+ filePath ,
252+ sections . BEFORE_ANCHOR ,
253+ sections . AFTER_ANCHOR ,
254+ sections . DELETE_CONTENT
255+ ) ) ;
256+ }
257+
258+ private parseSections ( content : string , expectedSections : string [ ] ) : Record < string , string > {
259+ const result : Record < string , string > = { } ;
260+ let currentSection : string | null = null ;
261+ let buffer : string [ ] = [ ] ;
262+
263+ for ( const line of content . split ( '\n' ) ) {
264+ const sectionMatch = line . match ( / ^ # # ( [ A - Z _ ] + ) / ) ;
265+ if ( sectionMatch ) {
266+ if ( currentSection ) {
267+ result [ currentSection ] = buffer . join ( '\n' ) . trim ( ) ;
268+ buffer = [ ] ;
269+ }
270+ currentSection = sectionMatch [ 1 ] ;
271+ if ( ! expectedSections . includes ( currentSection ) ) {
272+ throw new Error ( `Unexpected section: ${ currentSection } ` ) ;
273+ }
274+ } else if ( currentSection ) {
275+ buffer . push ( line ) ;
276+ }
277+ }
278+
279+ if ( currentSection ) {
280+ result [ currentSection ] = buffer . join ( '\n' ) . trim ( ) ;
281+ }
282+
283+ // Validate required sections
284+ for ( const section of expectedSections ) {
285+ if ( ! ( section in result ) ) {
286+ throw new Error ( `Missing required section: ${ section } ` ) ;
287+ }
288+ }
289+
290+ return result ;
291+ }
292+
293+ getOperations ( ) : CvbOperation [ ] {
294+ return [ ...this . operations ] ;
295+ }
296+
297+ static getFormatDescription ( ) : string {
298+ return `
299+ TCVB 格式规范(版本2.0):
300+
301+ ## BEGIN_TCVB
302+ [文件块1]
303+ [文件块2]
304+ ...
305+ ## END_TCVB
306+
307+ 文件块格式:
308+ ## FILE:<文件路径>
309+ [操作1]
310+ [操作2]
311+ ...
312+
313+ 操作类型:
314+ 1. 替换操作(REPLACE):
315+ ## OPERATION:REPLACE
316+ ## BEFORE_ANCHOR
317+ [前锚点内容]
318+ ## AFTER_ANCHOR
319+ [后锚点内容]
320+ ## OLD_CONTENT
321+ [被替换内容]
322+ ## NEW_CONTENT
323+ [新内容]
324+
325+ 2. 插入操作(INSERT):
326+ ## OPERATION:INSERT
327+ ## BEFORE_ANCHOR
328+ [插入位置前锚点]
329+ ## AFTER_ANCHOR
330+ [插入位置后锚点]
331+ ## INSERT_CONTENT
332+ [插入内容]
333+
334+ 3. 删除操作(DELETE):
335+ ## OPERATION:DELETE
336+ ## BEFORE_ANCHOR
337+ [被删内容前锚点]
338+ ## AFTER_ANCHOR
339+ [被删内容后锚点]
340+ ## DELETE_CONTENT
341+ [被删除内容]
342+
343+ 高级特性:
344+ 1. 文件路径复用:同一文件下的多个操作共享FILE声明
345+ 2. 混合操作:允许在文件块内任意顺序组合操作类型
346+ 3. 精准锚点:使用至少3行唯一文本作为锚点
347+ 4. 跨文件操作:可通过## OPERATION:TYPE FILE:path 临时指定其他文件
348+
349+ 示例:
350+ ## BEGIN_TCVB
351+ ## FILE:src/app.js
352+ ## OPERATION:REPLACE
353+ ## BEFORE_ANCHOR
354+ function legacy() {
355+ console.log('old');
356+ ## AFTER_ANCHOR
357+ }
358+
359+ ## OLD_CONTENT
360+ return 100;
361+ ## NEW_CONTENT
362+ return 200;
363+
364+ ## OPERATION:INSERT
365+ ## BEFORE_ANCHOR
366+ // == 配置开始 ==
367+ ## AFTER_ANCHOR
368+ // == 配置结束 ==
369+ ## INSERT_CONTENT
370+ timeout: 3000,
371+
372+ ## FILE:README.md
373+ ## OPERATION:DELETE
374+ ## BEFORE_ANCHOR
375+ <!-- DEPRECATED SECTION -->
376+ ## AFTER_ANCHOR
377+ <!-- END DEPRECATED -->
378+ ## DELETE_CONTENT
379+ ...旧内容...
380+ ## END_TCVB
381+ ` ;
382+ }
383+ }
384+
385+ // ================== 合并函数 ==================
386+ export function mergeCvb ( baseCvb : Cvb , tcvb : TCVB ) : Cvb {
387+ const mergedFiles = new Map < string , string > ( Object . entries ( baseCvb . getFiles ( ) ) ) ;
388+
389+ // 按文件分组操作
390+ const operationsByFile = new Map < string , CvbOperation [ ] > ( ) ;
391+ for ( const op of tcvb . getOperations ( ) ) {
392+ if ( ! operationsByFile . has ( op . filePath ) ) {
393+ operationsByFile . set ( op . filePath , [ ] ) ;
394+ }
395+ operationsByFile . get ( op . filePath ) ! . push ( op ) ;
396+ }
397+
398+ // 处理每个文件的修改
399+ for ( const [ filePath , operations ] of operationsByFile ) {
400+ let content = mergedFiles . get ( filePath ) || '' ;
401+
402+ // 按操作顺序执行修改
403+ for ( const op of operations ) {
404+ if ( op instanceof ReplaceOperation ) {
405+ content = applyReplace ( content , op ) ;
406+ } else if ( op instanceof InsertOperation ) {
407+ content = applyInsert ( content , op ) ;
408+ } else if ( op instanceof DeleteOperation ) {
409+ content = applyDelete ( content , op ) ;
410+ }
411+ }
412+
413+ mergedFiles . set ( filePath , content ) ;
414+ }
415+
416+ // 重新生成CVB内容
417+ return rebuildCvb ( baseCvb , mergedFiles ) ;
418+ }
419+
420+ function applyReplace ( content : string , op : ReplaceOperation ) : string {
421+ const pattern = buildPattern ( op . beforeAnchor , op . oldContent , op . afterAnchor ) ;
422+ const replacement = `${ op . beforeAnchor } ${ op . newContent } ${ op . afterAnchor } ` ;
423+ return content . replace ( pattern , replacement ) ;
424+ }
425+
426+ function applyInsert ( content : string , op : InsertOperation ) : string {
427+ const pattern = buildPattern ( op . beforeAnchor , '' , op . afterAnchor ) ;
428+ const replacement = `${ op . beforeAnchor } ${ op . content } ${ op . afterAnchor } ` ;
429+ return content . replace ( pattern , replacement ) ;
430+ }
431+
432+ function applyDelete ( content : string , op : DeleteOperation ) : string {
433+ const pattern = buildPattern ( op . beforeAnchor , op . oldContent , op . afterAnchor ) ;
434+ return content . replace ( pattern , `${ op . beforeAnchor } ${ op . afterAnchor } ` ) ;
435+ }
436+
437+ function buildPattern ( before : string , content : string , after : string ) : RegExp {
438+ return new RegExp (
439+ `${ escapeRegExp ( before ) } ${ escapeRegExp ( content ) } ${ escapeRegExp ( after ) } ` ,
440+ 'gs' // 使用dotall模式匹配换行
441+ ) ;
442+ }
443+
444+ function rebuildCvb ( baseCvb : Cvb , files : Map < string , string > ) : Cvb {
445+ let newContent = `## BEGIN_CVB\n## META\n` ;
446+
447+ // 保留元数据
448+ const metadata = baseCvb . getMetadata ( ) ;
449+ for ( const [ key , value ] of Object . entries ( metadata ) ) {
450+ newContent += `${ key } : ${ value } \n` ;
451+ }
452+ newContent += `## END_META\n\n` ;
453+
454+ // 重建文件内容
455+ for ( const [ filePath , content ] of files ) {
456+ const ext = path . extname ( filePath ) . slice ( 1 ) . toLowerCase ( ) ;
457+ const lang = languageMapping [ ext ] || 'text' ;
458+ newContent += `## FILE:${ filePath } \n\`\`\`${ lang } \n${ content } \n\`\`\`\n\n` ;
459+ }
460+
461+ newContent += `## END_CVB` ;
462+ const cvb = new Cvb ( newContent ) ;
463+
464+ cvb . setMetaData ( "时间戳" , generateTimestamp ( ) ) ;
465+ return cvb ;
466+ }
467+
468+ // ================== 工具函数 ==================
469+ function escapeRegExp ( str : string ) : string {
470+ return str . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' ) ;
471+ }
472+
473+ function filePathNormalize ( rawPath : string ) : string {
474+ return path . normalize ( rawPath . replace ( / ^ [ \\ / ] + / , '' ) ) ;
133475}
134476
135477/**
0 commit comments