88 */
99
1010import { Command } from 'commander' ;
11- import { existsSync , readFileSync , writeFileSync , mkdirSync } from 'fs' ;
12- import { join } from 'path' ;
11+ import {
12+ existsSync ,
13+ readFileSync ,
14+ writeFileSync ,
15+ mkdirSync ,
16+ readdirSync ,
17+ } from 'fs' ;
18+ import { join , basename } from 'path' ;
19+ import { homedir } from 'os' ;
20+ import { createHash } from 'crypto' ;
1321
1422interface Decision {
1523 id : string ;
@@ -29,6 +37,94 @@ function getDecisionStorePath(projectRoot: string): string {
2937 return join ( projectRoot , '.stackmemory' , 'session-decisions.json' ) ;
3038}
3139
40+ function getProjectId ( projectRoot : string ) : string {
41+ const hash = createHash ( 'sha256' ) . update ( projectRoot ) . digest ( 'hex' ) ;
42+ return hash . slice ( 0 , 12 ) ;
43+ }
44+
45+ function getHistoryDir ( ) : string {
46+ return join ( homedir ( ) , '.stackmemory' , 'decision-history' ) ;
47+ }
48+
49+ function archiveDecisions ( projectRoot : string , decisions : Decision [ ] ) : void {
50+ if ( decisions . length === 0 ) return ;
51+
52+ const historyDir = getHistoryDir ( ) ;
53+ const projectId = getProjectId ( projectRoot ) ;
54+ const projectDir = join ( historyDir , projectId ) ;
55+
56+ if ( ! existsSync ( projectDir ) ) {
57+ mkdirSync ( projectDir , { recursive : true } ) ;
58+ }
59+
60+ // Save with timestamp
61+ const timestamp = new Date ( ) . toISOString ( ) . replace ( / [: .] / g, '-' ) ;
62+ const archivePath = join ( projectDir , `${ timestamp } .json` ) ;
63+
64+ const archive = {
65+ projectRoot,
66+ projectName : basename ( projectRoot ) ,
67+ archivedAt : new Date ( ) . toISOString ( ) ,
68+ decisions,
69+ } ;
70+
71+ writeFileSync ( archivePath , JSON . stringify ( archive , null , 2 ) ) ;
72+ }
73+
74+ interface HistoricalDecision extends Decision {
75+ projectName : string ;
76+ archivedAt : string ;
77+ }
78+
79+ function loadDecisionHistory ( projectRoot ?: string ) : HistoricalDecision [ ] {
80+ const historyDir = getHistoryDir ( ) ;
81+ if ( ! existsSync ( historyDir ) ) return [ ] ;
82+
83+ const allDecisions : HistoricalDecision [ ] = [ ] ;
84+
85+ try {
86+ const projectDirs = projectRoot
87+ ? [ getProjectId ( projectRoot ) ]
88+ : readdirSync ( historyDir ) ;
89+
90+ for ( const projectId of projectDirs ) {
91+ const projectDir = join ( historyDir , projectId ) ;
92+ if ( ! existsSync ( projectDir ) ) continue ;
93+
94+ try {
95+ const files = readdirSync ( projectDir ) . filter ( ( f ) =>
96+ f . endsWith ( '.json' )
97+ ) ;
98+ for ( const file of files ) {
99+ try {
100+ const content = JSON . parse (
101+ readFileSync ( join ( projectDir , file ) , 'utf-8' )
102+ ) ;
103+ for ( const d of content . decisions || [ ] ) {
104+ allDecisions . push ( {
105+ ...d ,
106+ projectName : content . projectName || 'unknown' ,
107+ archivedAt : content . archivedAt ,
108+ } ) ;
109+ }
110+ } catch {
111+ // Skip invalid files
112+ }
113+ }
114+ } catch {
115+ // Skip unreadable directories
116+ }
117+ }
118+ } catch {
119+ // History dir unreadable
120+ }
121+
122+ // Sort by timestamp descending
123+ return allDecisions . sort (
124+ ( a , b ) => new Date ( b . timestamp ) . getTime ( ) - new Date ( a . timestamp ) . getTime ( )
125+ ) ;
126+ }
127+
32128function loadDecisions ( projectRoot : string ) : DecisionStore {
33129 const storePath = getDecisionStorePath ( projectRoot ) ;
34130 if ( existsSync ( storePath ) ) {
@@ -104,10 +200,42 @@ export function createDecisionCommand(): Command {
104200 . command ( 'list' )
105201 . description ( 'List all decisions from this session' )
106202 . option ( '--json' , 'Output as JSON' )
203+ . option ( '--history' , 'Include historical decisions' )
204+ . option ( '--all' , 'Show all projects (with --history)' )
107205 . action ( ( options ) => {
108206 const projectRoot = process . cwd ( ) ;
109207 const store = loadDecisions ( projectRoot ) ;
110208
209+ if ( options . history ) {
210+ const history = loadDecisionHistory (
211+ options . all ? undefined : projectRoot
212+ ) ;
213+
214+ if ( options . json ) {
215+ console . log ( JSON . stringify ( history , null , 2 ) ) ;
216+ return ;
217+ }
218+
219+ if ( history . length === 0 ) {
220+ console . log ( 'No decision history found.' ) ;
221+ return ;
222+ }
223+
224+ console . log ( `Decision History (${ history . length } ):\n` ) ;
225+ for ( const d of history . slice ( 0 , 50 ) ) {
226+ const category = d . category ? `[${ d . category } ] ` : '' ;
227+ const project = options . all ? `(${ d . projectName } ) ` : '' ;
228+ console . log ( `${ project } ${ category } ${ d . what } ` ) ;
229+ if ( d . why ) {
230+ console . log ( ` Rationale: ${ d . why } ` ) ;
231+ }
232+ const date = new Date ( d . timestamp ) . toLocaleDateString ( ) ;
233+ console . log ( ` Date: ${ date } ` ) ;
234+ console . log ( '' ) ;
235+ }
236+ return ;
237+ }
238+
111239 if ( options . json ) {
112240 console . log ( JSON . stringify ( store . decisions , null , 2 ) ) ;
113241 return ;
@@ -137,8 +265,9 @@ export function createDecisionCommand(): Command {
137265 // Clear decisions (for new session)
138266 cmd
139267 . command ( 'clear' )
140- . description ( 'Clear all decisions (start fresh session )' )
268+ . description ( 'Clear all decisions (archives to history first )' )
141269 . option ( '--force' , 'Skip confirmation' )
270+ . option ( '--no-archive' , 'Do not archive decisions' )
142271 . action ( ( options ) => {
143272 const projectRoot = process . cwd ( ) ;
144273 const store = loadDecisions ( projectRoot ) ;
@@ -150,10 +279,17 @@ export function createDecisionCommand(): Command {
150279
151280 if ( ! options . force ) {
152281 console . log ( `This will clear ${ store . decisions . length } decisions.` ) ;
282+ console . log ( 'Decisions will be archived to history.' ) ;
153283 console . log ( 'Use --force to confirm.' ) ;
154284 return ;
155285 }
156286
287+ // Archive before clearing (unless --no-archive)
288+ if ( options . archive !== false ) {
289+ archiveDecisions ( projectRoot , store . decisions ) ;
290+ console . log ( `Archived ${ store . decisions . length } decisions to history.` ) ;
291+ }
292+
157293 const newStore : DecisionStore = {
158294 decisions : [ ] ,
159295 sessionStart : new Date ( ) . toISOString ( ) ,
0 commit comments