@@ -258,6 +258,119 @@ describe("codedecay memory CLI contract", () => {
258258 expect . objectContaining ( { title : "CodeDecay: Risky source changes without changed tests" } )
259259 ] )
260260 ) ;
261+ expect ( parsed . proposals ) . toEqual (
262+ expect . arrayContaining ( [
263+ expect . objectContaining ( {
264+ section : "regressions" ,
265+ title : "Auth smoke failed" ,
266+ source : expect . objectContaining ( { type : "ci-failure" } ) ,
267+ confidence : "high" ,
268+ why : expect . stringContaining ( "CI failure" )
269+ } )
270+ ] )
271+ ) ;
272+ } ) ;
273+
274+ it ( "learns reviewable memory proposals from incident markdown without applying by default" , async ( ) => {
275+ const repo = createLowRiskRepo ( ) ;
276+ const inputPath = join ( repo , "auth-incident.md" ) ;
277+ writeFile (
278+ repo ,
279+ "auth-incident.md" ,
280+ [
281+ "# Auth cache outage" ,
282+ "" ,
283+ "Incident: stale auth cache allowed a forbidden session after deploy." ,
284+ "Prevention: auth cache invalidation must be verified after session changes."
285+ ] . join ( "\n" )
286+ ) ;
287+
288+ const preview = await run ( [ "memory-learn" , "--input" , inputPath , "--format" , "json" ] , repo ) ;
289+ const previewJson = JSON . parse ( preview . stdout ) ;
290+
291+ expect ( preview . exitCode ) . toBe ( 0 ) ;
292+ expect ( previewJson . writtenPath ) . toBeUndefined ( ) ;
293+ expect ( previewJson . memory . invariants ) . toEqual (
294+ expect . arrayContaining ( [ expect . objectContaining ( { name : "Auth cache outage" , severity : "high" } ) ] )
295+ ) ;
296+ expect ( previewJson . proposals ) . toEqual (
297+ expect . arrayContaining ( [
298+ expect . objectContaining ( {
299+ section : "invariants" ,
300+ title : "Auth cache outage" ,
301+ source : expect . objectContaining ( {
302+ type : "incident-markdown" ,
303+ path : inputPath
304+ } ) ,
305+ confidence : "high" ,
306+ why : expect . stringContaining ( "durable rule" )
307+ } )
308+ ] )
309+ ) ;
310+ expect ( existsSync ( join ( repo , ".codedecay/memory.json" ) ) ) . toBe ( false ) ;
311+
312+ const applied = await run ( [ "memory-learn" , "--input" , inputPath , "--apply" , "--format" , "json" ] , repo ) ;
313+ const appliedJson = JSON . parse ( applied . stdout ) ;
314+
315+ expect ( applied . exitCode ) . toBe ( 0 ) ;
316+ expect ( appliedJson . writtenPath ) . toContain ( ".codedecay/memory.json" ) ;
317+ expect ( JSON . parse ( readFileSync ( join ( repo , ".codedecay/memory.json" ) , "utf8" ) ) . invariants ) . toEqual (
318+ expect . arrayContaining ( [ expect . objectContaining ( { name : "Auth cache outage" } ) ] )
319+ ) ;
320+ } ) ;
321+
322+ it ( "reports malformed memory-learn inputs without crashing" , async ( ) => {
323+ const repo = createLowRiskRepo ( ) ;
324+ const inputPath = join ( repo , "broken-learn.json" ) ;
325+ writeFile ( repo , "broken-learn.json" , "{" ) ;
326+
327+ const result = await run ( [ "memory-learn" , "--input" , inputPath , "--format" , "json" ] , repo ) ;
328+
329+ expect ( result . exitCode ) . toBe ( 2 ) ;
330+ expect ( result . stdout ) . toBe ( "" ) ;
331+ expect ( result . stderr ) . toContain ( "Invalid memory-learn input" ) ;
332+ } ) ;
333+
334+ it ( "matches learned past-regression memory in redteam reports after explicit apply" , async ( ) => {
335+ const repo = createRepo ( {
336+ "src/auth/session.ts" : "export function session() { return { ok: true }; }\n"
337+ } ) ;
338+ const inputPath = join ( repo , "memory-learn.json" ) ;
339+ writeFile (
340+ repo ,
341+ "memory-learn.json" ,
342+ JSON . stringify (
343+ {
344+ ciFailures : [
345+ {
346+ title : "Auth smoke failed" ,
347+ message : "Token refresh returned 401 after deploy." ,
348+ command : "pnpm test auth" ,
349+ files : [ "src/auth/session.ts" ]
350+ }
351+ ]
352+ } ,
353+ null ,
354+ 2
355+ )
356+ ) ;
357+
358+ const applied = await run ( [ "memory-learn" , "--input" , inputPath , "--apply" , "--format" , "json" ] , repo ) ;
359+ expect ( applied . exitCode ) . toBe ( 0 ) ;
360+
361+ writeFile ( repo , "src/auth/session.ts" , "export function session() { return { ok: false }; }\n" ) ;
362+ const redteam = await run ( [ "redteam" , "--format" , "json" ] , repo ) ;
363+ const report = JSON . parse ( redteam . stdout ) ;
364+
365+ expect ( redteam . exitCode ) . toBe ( 0 ) ;
366+ expect ( report . analysis . findings ) . toEqual (
367+ expect . arrayContaining ( [
368+ expect . objectContaining ( {
369+ ruleId : "memory-past-regression-area" ,
370+ file : "src/auth/session.ts"
371+ } )
372+ ] )
373+ ) ;
261374 } ) ;
262375
263376 it ( "learns memory from product verification reports" , async ( ) => {
0 commit comments