@@ -28,6 +28,7 @@ import {
2828 appendLearning ,
2929 pruneMemory ,
3030 sessionCommits ,
31+ extractPattern ,
3132 MEMORY_MAX_LINES ,
3233 today ,
3334} from '../../src/hooks/maxsim-capture-learnings.js' ;
@@ -85,25 +86,25 @@ describe('appendLearning', () => {
8586 expect ( fs . existsSync ( path . dirname ( memPath ) ) ) . toBe ( true ) ;
8687 } ) ;
8788
88- it ( 'appends a session header with the correct date' , ( ) => {
89+ it ( 'appends a session header with the correct date in pipe-delimited format ' , ( ) => {
8990 const memPath = path . join ( tmpDir , 'MEMORY.md' ) ;
90- appendLearning ( memPath , undefined , [ ] , undefined , undefined ) ;
91+ appendLearning ( memPath , 'sess1234abcd' , [ ] , 'user_exit' , undefined ) ;
9192 const content = fs . readFileSync ( memPath , 'utf8' ) ;
92- expect ( content ) . toContain ( `## Session ${ today ( ) } ` ) ;
93+ expect ( content ) . toContain ( `### ${ today ( ) } | sess1234 | user_exit | 0 commits ` ) ;
9394 } ) ;
9495
9596 it ( 'includes session ID (first 8 chars) in the header' , ( ) => {
9697 const memPath = path . join ( tmpDir , 'MEMORY.md' ) ;
9798 appendLearning ( memPath , 'abc12345xyz' , [ ] , undefined , undefined ) ;
9899 const content = fs . readFileSync ( memPath , 'utf8' ) ;
99- expect ( content ) . toContain ( '( abc12345) ' ) ;
100+ expect ( content ) . toContain ( '| abc12345 | ' ) ;
100101 } ) ;
101102
102- it ( 'includes stop_reason in square brackets in the header' , ( ) => {
103+ it ( 'includes stop_reason in the pipe-delimited header' , ( ) => {
103104 const memPath = path . join ( tmpDir , 'MEMORY.md' ) ;
104105 appendLearning ( memPath , undefined , [ ] , 'user_exit' , undefined ) ;
105106 const content = fs . readFileSync ( memPath , 'utf8' ) ;
106- expect ( content ) . toContain ( '[ user_exit] ' ) ;
107+ expect ( content ) . toContain ( '| user_exit | ' ) ;
107108 } ) ;
108109
109110 it ( 'writes "no commits recorded this session" when commits array is empty' , ( ) => {
@@ -113,19 +114,18 @@ describe('appendLearning', () => {
113114 expect ( content ) . toContain ( '- no commits recorded this session' ) ;
114115 } ) ;
115116
116- it ( 'writes each commit on its own line with "- commit :" prefix ' , ( ) => {
117+ it ( 'writes all commits on a single "- commits :" line ' , ( ) => {
117118 const memPath = path . join ( tmpDir , 'MEMORY.md' ) ;
118119 appendLearning ( memPath , undefined , [ 'abc1234 fix bug' , 'def5678 add feature' ] , undefined , undefined ) ;
119120 const content = fs . readFileSync ( memPath , 'utf8' ) ;
120- expect ( content ) . toContain ( '- commit: abc1234 fix bug' ) ;
121- expect ( content ) . toContain ( '- commit: def5678 add feature' ) ;
121+ expect ( content ) . toContain ( '- commits: abc1234 fix bug, def5678 add feature' ) ;
122122 } ) ;
123123
124- it ( 'records the commit count on a summary line ' , ( ) => {
124+ it ( 'records the commit count in the header ' , ( ) => {
125125 const memPath = path . join ( tmpDir , 'MEMORY.md' ) ;
126126 appendLearning ( memPath , undefined , [ 'a' , 'b' , 'c' ] , undefined , undefined ) ;
127127 const content = fs . readFileSync ( memPath , 'utf8' ) ;
128- expect ( content ) . toContain ( '- 3 commit(s) made this session ' ) ;
128+ expect ( content ) . toContain ( '| 3 commits ' ) ;
129129 } ) ;
130130
131131 it ( 'includes pattern summary when provided' , ( ) => {
@@ -149,15 +149,15 @@ describe('appendLearning', () => {
149149 appendLearning ( memPath , 'session2' , [ 'commit-b' ] , undefined , undefined ) ;
150150 const content = fs . readFileSync ( memPath , 'utf8' ) ;
151151 expect ( content ) . toContain ( '# Existing content' ) ;
152- expect ( content ) . toContain ( '( session1) ' ) ;
153- expect ( content ) . toContain ( '( session2) ' ) ;
152+ expect ( content ) . toContain ( '| session1 | ' ) ;
153+ expect ( content ) . toContain ( '| session2 | ' ) ;
154154 } ) ;
155155
156- it ( 'omits stop_reason bracket when stop_reason is undefined' , ( ) => {
156+ it ( 'uses "unknown" for stop_reason when undefined' , ( ) => {
157157 const memPath = path . join ( tmpDir , 'MEMORY.md' ) ;
158158 appendLearning ( memPath , undefined , [ ] , undefined , undefined ) ;
159159 const content = fs . readFileSync ( memPath , 'utf8' ) ;
160- expect ( content ) . not . toMatch ( / \[ . * \] / ) ;
160+ expect ( content ) . toContain ( '| unknown |' ) ;
161161 } ) ;
162162} ) ;
163163
@@ -264,3 +264,54 @@ describe('sessionCommits', () => {
264264 expect ( result ) . toEqual ( [ 'fallback-commit' ] ) ;
265265 } ) ;
266266} ) ;
267+
268+ // ---------------------------------------------------------------------------
269+ // extractPattern
270+ // ---------------------------------------------------------------------------
271+
272+ describe ( 'extractPattern' , ( ) => {
273+ it ( 'returns undefined for empty/whitespace input' , ( ) => {
274+ expect ( extractPattern ( '' ) ) . toBeUndefined ( ) ;
275+ expect ( extractPattern ( ' ' ) ) . toBeUndefined ( ) ;
276+ expect ( extractPattern ( '\n\n' ) ) . toBeUndefined ( ) ;
277+ } ) ;
278+
279+ it ( 'finds a line starting with "Pattern:" prefix' , ( ) => {
280+ const msg = 'Some preamble.\nPattern: always run tests before committing.\nMore text.' ;
281+ expect ( extractPattern ( msg ) ) . toBe ( 'Pattern: always run tests before committing.' ) ;
282+ } ) ;
283+
284+ it ( 'finds a line starting with "Learning:" prefix' , ( ) => {
285+ const msg = 'Debugging session complete.\nLearning: the config file must be UTF-8.' ;
286+ expect ( extractPattern ( msg ) ) . toBe ( 'Learning: the config file must be UTF-8.' ) ;
287+ } ) ;
288+
289+ it ( 'finds a bullet-prefixed learning line' , ( ) => {
290+ const msg = 'Summary:\n- Found that the API rate limits at 100 req/s.' ;
291+ expect ( extractPattern ( msg ) ) . toBe ( '- Found that the API rate limits at 100 req/s.' ) ;
292+ } ) ;
293+
294+ it ( 'extracts bullet points when there are 1-5 of them' , ( ) => {
295+ const msg = 'Results:\n- Added auth module\n- Fixed login bug\n- Updated tests' ;
296+ expect ( extractPattern ( msg ) ) . toBe ( '- Added auth module; - Fixed login bug; - Updated tests' ) ;
297+ } ) ;
298+
299+ it ( 'falls back to the last sentence when no prefix or bullets match' , ( ) => {
300+ const msg = 'I refactored several files. The build is now green. All tests pass.' ;
301+ expect ( extractPattern ( msg ) ) . toBe ( 'All tests pass.' ) ;
302+ } ) ;
303+
304+ it ( 'caps result at 200 characters' , ( ) => {
305+ const longLine = `Pattern: ${ 'x' . repeat ( 300 ) } ` ;
306+ const result = extractPattern ( longLine ) ;
307+ expect ( result ) . toBeDefined ( ) ;
308+ expect ( result ?. length ) . toBe ( 200 ) ;
309+ } ) ;
310+
311+ it ( 'uses last 200 chars as final fallback when no sentences found' , ( ) => {
312+ const msg = `no punctuation here just a long stream of words ${ 'word ' . repeat ( 50 ) } ` ;
313+ const result = extractPattern ( msg ) ;
314+ expect ( result ) . toBeDefined ( ) ;
315+ expect ( result ?. length ) . toBeLessThanOrEqual ( 200 ) ;
316+ } ) ;
317+ } ) ;
0 commit comments