33 */
44
55import path from 'path' ;
6- import type { Memory , MemoryCategory , MemoryType } from './types/index.js' ;
6+ import type { Memory } from './types/index.js' ;
77import { CODEBASE_CONTEXT_DIRNAME , MEMORY_FILENAME } from './constants/codebase-context.js' ;
88import {
99 appendMemoryFile ,
@@ -19,69 +19,100 @@ const MEMORY_CATEGORIES = [
1919 'testing' ,
2020 'dependencies' ,
2121 'conventions'
22- ] as const satisfies readonly MemoryCategory [ ] ;
22+ ] as const ;
23+ type CliMemoryCategory = ( typeof MEMORY_CATEGORIES ) [ number ] ;
2324
24- const MEMORY_TYPES = [
25- 'convention' ,
26- 'decision' ,
27- 'gotcha' ,
28- 'failure'
29- ] as const satisfies readonly MemoryType [ ] ;
25+ const MEMORY_TYPES = [ 'convention' , 'decision' , 'gotcha' , 'failure' ] as const ;
26+ type CliMemoryType = ( typeof MEMORY_TYPES ) [ number ] ;
3027
3128const MEMORY_CATEGORY_SET : ReadonlySet < string > = new Set ( MEMORY_CATEGORIES ) ;
32- function isMemoryCategory ( value : string ) : value is MemoryCategory {
29+ function isCliMemoryCategory ( value : string ) : value is CliMemoryCategory {
3330 return MEMORY_CATEGORY_SET . has ( value ) ;
3431}
3532
3633const MEMORY_TYPE_SET : ReadonlySet < string > = new Set ( MEMORY_TYPES ) ;
37- function isMemoryType ( value : string ) : value is MemoryType {
34+ function isCliMemoryType ( value : string ) : value is CliMemoryType {
3835 return MEMORY_TYPE_SET . has ( value ) ;
3936}
4037
41- function exitWithError ( message : string ) : never {
42- console . error ( message ) ;
43- process . exit ( 1 ) ;
44- }
45-
4638export async function handleMemoryCli ( args : string [ ] ) : Promise < void > {
4739 // Resolve project root: use CODEBASE_ROOT env or cwd (argv[2] is "memory", not a path)
4840 const cliRoot = process . env . CODEBASE_ROOT || process . cwd ( ) ;
4941 const memoryPath = path . join ( cliRoot , CODEBASE_CONTEXT_DIRNAME , MEMORY_FILENAME ) ;
5042 const subcommand = args [ 0 ] ; // list | add | remove
43+ const useJson = args . includes ( '--json' ) ;
44+
45+ const listUsage =
46+ 'Usage: codebase-context memory list [--category <cat>] [--type <type>] [--query <text>] [--json]' ;
47+ const addUsage =
48+ 'Usage: codebase-context memory add --type <type> --category <category> --memory <text> --reason <text> [--json]' ;
49+ const removeUsage = 'Usage: codebase-context memory remove <id> [--json]' ;
50+
51+ const exitWithUsageError = ( message : string , usage ?: string ) : never => {
52+ if ( useJson ) {
53+ console . log (
54+ JSON . stringify (
55+ {
56+ status : 'error' ,
57+ message,
58+ ...( usage ? { usage } : { } )
59+ } ,
60+ null ,
61+ 2
62+ )
63+ ) ;
64+ } else {
65+ console . error ( message ) ;
66+ if ( usage ) console . error ( usage ) ;
67+ }
68+ process . exit ( 1 ) ;
69+ } ;
5170
5271 if ( subcommand === 'list' ) {
5372 const memories = await readMemoriesFile ( memoryPath ) ;
54- const opts : { category ?: MemoryCategory ; type ?: MemoryType ; query ?: string } = { } ;
73+ const opts : { category ?: CliMemoryCategory ; type ?: CliMemoryType ; query ?: string } = { } ;
5574
5675 for ( let i = 1 ; i < args . length ; i ++ ) {
5776 if ( args [ i ] === '--category' ) {
5877 const value = args [ i + 1 ] ;
5978 if ( ! value || value . startsWith ( '--' ) ) {
60- exitWithError (
61- `Error: --category requires a value. Allowed: ${ MEMORY_CATEGORIES . join ( ', ' ) } `
79+ exitWithUsageError (
80+ `Error: --category requires a value. Allowed: ${ MEMORY_CATEGORIES . join ( ', ' ) } ` ,
81+ listUsage
6282 ) ;
6383 }
64- if ( ! isMemoryCategory ( value ) ) {
65- exitWithError (
66- `Error: invalid --category "${ value } ". Allowed: ${ MEMORY_CATEGORIES . join ( ', ' ) } `
84+
85+ if ( isCliMemoryCategory ( value ) ) {
86+ opts . category = value ;
87+ } else {
88+ exitWithUsageError (
89+ `Error: invalid --category "${ value } ". Allowed: ${ MEMORY_CATEGORIES . join ( ', ' ) } ` ,
90+ listUsage
6791 ) ;
6892 }
69- opts . category = value ;
7093 i ++ ;
7194 } else if ( args [ i ] === '--type' ) {
7295 const value = args [ i + 1 ] ;
7396 if ( ! value || value . startsWith ( '--' ) ) {
74- exitWithError ( `Error: --type requires a value. Allowed: ${ MEMORY_TYPES . join ( ', ' ) } ` ) ;
97+ exitWithUsageError (
98+ `Error: --type requires a value. Allowed: ${ MEMORY_TYPES . join ( ', ' ) } ` ,
99+ listUsage
100+ ) ;
75101 }
76- if ( ! isMemoryType ( value ) ) {
77- exitWithError ( `Error: invalid --type "${ value } ". Allowed: ${ MEMORY_TYPES . join ( ', ' ) } ` ) ;
102+
103+ if ( isCliMemoryType ( value ) ) {
104+ opts . type = value ;
105+ } else {
106+ exitWithUsageError (
107+ `Error: invalid --type "${ value } ". Allowed: ${ MEMORY_TYPES . join ( ', ' ) } ` ,
108+ listUsage
109+ ) ;
78110 }
79- opts . type = value ;
80111 i ++ ;
81112 } else if ( args [ i ] === '--query' ) {
82113 const value = args [ i + 1 ] ;
83114 if ( ! value || value . startsWith ( '--' ) ) {
84- exitWithError ( 'Error: --query requires a value.' ) ;
115+ exitWithUsageError ( 'Error: --query requires a value.' , listUsage ) ;
85116 }
86117 opts . query = value ;
87118 i ++ ;
@@ -92,7 +123,6 @@ export async function handleMemoryCli(args: string[]): Promise<void> {
92123
93124 const filtered = filterMemories ( memories , opts ) ;
94125 const enriched = withConfidence ( filtered ) ;
95- const useJson = args . includes ( '--json' ) ;
96126
97127 if ( useJson ) {
98128 console . log ( JSON . stringify ( enriched , null , 2 ) ) ;
@@ -111,101 +141,129 @@ export async function handleMemoryCli(args: string[]): Promise<void> {
111141 }
112142 }
113143 } else if ( subcommand === 'add' ) {
114- let type : MemoryType = 'decision' ;
115- let category : MemoryCategory | undefined ;
144+ let type : CliMemoryType = 'decision' ;
145+ let category : CliMemoryCategory | undefined ;
116146 let memory : string | undefined ;
117147 let reason : string | undefined ;
118148
119149 for ( let i = 1 ; i < args . length ; i ++ ) {
120150 if ( args [ i ] === '--type' ) {
121151 const value = args [ i + 1 ] ;
122152 if ( ! value || value . startsWith ( '--' ) ) {
123- exitWithError ( `Error: --type requires a value. Allowed: ${ MEMORY_TYPES . join ( ', ' ) } ` ) ;
153+ exitWithUsageError (
154+ `Error: --type requires a value. Allowed: ${ MEMORY_TYPES . join ( ', ' ) } ` ,
155+ addUsage
156+ ) ;
124157 }
125- if ( ! isMemoryType ( value ) ) {
126- exitWithError ( `Error: invalid --type "${ value } ". Allowed: ${ MEMORY_TYPES . join ( ', ' ) } ` ) ;
158+
159+ if ( isCliMemoryType ( value ) ) {
160+ type = value ;
161+ } else {
162+ exitWithUsageError (
163+ `Error: invalid --type "${ value } ". Allowed: ${ MEMORY_TYPES . join ( ', ' ) } ` ,
164+ addUsage
165+ ) ;
127166 }
128- type = value ;
129167 i ++ ;
130168 } else if ( args [ i ] === '--category' ) {
131169 const value = args [ i + 1 ] ;
132170 if ( ! value || value . startsWith ( '--' ) ) {
133- exitWithError (
134- `Error: --category requires a value. Allowed: ${ MEMORY_CATEGORIES . join ( ', ' ) } `
171+ exitWithUsageError (
172+ `Error: --category requires a value. Allowed: ${ MEMORY_CATEGORIES . join ( ', ' ) } ` ,
173+ addUsage
135174 ) ;
136175 }
137- if ( ! isMemoryCategory ( value ) ) {
138- exitWithError (
139- `Error: invalid --category "${ value } ". Allowed: ${ MEMORY_CATEGORIES . join ( ', ' ) } `
176+
177+ if ( isCliMemoryCategory ( value ) ) {
178+ category = value ;
179+ } else {
180+ exitWithUsageError (
181+ `Error: invalid --category "${ value } ". Allowed: ${ MEMORY_CATEGORIES . join ( ', ' ) } ` ,
182+ addUsage
140183 ) ;
141184 }
142- category = value ;
143185 i ++ ;
144186 } else if ( args [ i ] === '--memory' ) {
145187 const value = args [ i + 1 ] ;
146188 if ( ! value || value . startsWith ( '--' ) ) {
147- exitWithError ( 'Error: --memory requires a value.' ) ;
189+ exitWithUsageError ( 'Error: --memory requires a value.' , addUsage ) ;
148190 }
149191 memory = value ;
150192 i ++ ;
151193 } else if ( args [ i ] === '--reason' ) {
152194 const value = args [ i + 1 ] ;
153195 if ( ! value || value . startsWith ( '--' ) ) {
154- exitWithError ( 'Error: --reason requires a value.' ) ;
196+ exitWithUsageError ( 'Error: --reason requires a value.' , addUsage ) ;
155197 }
156198 reason = value ;
157199 i ++ ;
200+ } else if ( args [ i ] === '--json' ) {
201+ // handled above
158202 }
159203 }
160204
161205 if ( ! category || ! memory || ! reason ) {
162- console . error (
163- 'Usage: codebase-context memory add --type <type> --category <category> --memory <text> --reason <text>'
164- ) ;
165- console . error ( 'Required: --category, --memory, --reason' ) ;
166- process . exit ( 1 ) ;
206+ exitWithUsageError ( 'Error: required flags missing: --category, --memory, --reason' , addUsage ) ;
207+ return ;
167208 }
168209
210+ const requiredCategory = category ;
211+ const requiredMemory = memory ;
212+ const requiredReason = reason ;
213+
169214 const crypto = await import ( 'crypto' ) ;
170- const hashContent = `${ type } :${ category } :${ memory } :${ reason } ` ;
215+ const hashContent = `${ type } :${ requiredCategory } :${ requiredMemory } :${ requiredReason } ` ;
171216 const hash = crypto . createHash ( 'sha256' ) . update ( hashContent ) . digest ( 'hex' ) ;
172217 const id = hash . substring ( 0 , 12 ) ;
173218
174219 const newMemory : Memory = {
175220 id,
176221 type,
177- category,
178- memory,
179- reason,
222+ category : requiredCategory ,
223+ memory : requiredMemory ,
224+ reason : requiredReason ,
180225 date : new Date ( ) . toISOString ( )
181226 } ;
182227 const result = await appendMemoryFile ( memoryPath , newMemory ) ;
183228
229+ if ( useJson ) {
230+ console . log ( JSON . stringify ( result , null , 2 ) ) ;
231+ return ;
232+ }
233+
184234 if ( result . status === 'duplicate' ) {
185235 console . log ( `Already exists: [${ id } ] ${ memory } ` ) ;
186- } else {
187- console . log ( `Added: [${ id } ] ${ memory } ` ) ;
236+ return ;
188237 }
238+
239+ console . log ( `Added: [${ id } ] ${ memory } ` ) ;
189240 } else if ( subcommand === 'remove' ) {
190- const id = args [ 1 ] ;
191- if ( ! id ) {
192- console . error ( 'Usage: codebase-context memory remove <id>' ) ;
193- process . exit ( 1 ) ;
241+ const id = args . slice ( 1 ) . find ( ( value ) => value !== '--json' && ! value . startsWith ( '--' ) ) ;
242+ if ( id === undefined ) {
243+ exitWithUsageError ( 'Error: missing memory id.' , removeUsage ) ;
244+ return ;
194245 }
195246
196247 const result = await removeMemory ( memoryPath , id ) ;
197248 if ( result . status === 'not_found' ) {
198- console . error ( `Memory not found: ${ id } ` ) ;
249+ if ( useJson ) {
250+ console . log ( JSON . stringify ( { status : 'not_found' , id } , null , 2 ) ) ;
251+ } else {
252+ console . error ( `Memory not found: ${ id } ` ) ;
253+ }
199254 process . exit ( 1 ) ;
200- } else {
201- console . log ( `Removed: ${ id } ` ) ;
202255 }
256+
257+ if ( useJson ) {
258+ console . log ( JSON . stringify ( { status : 'removed' , id } , null , 2 ) ) ;
259+ return ;
260+ }
261+
262+ console . log ( `Removed: ${ id } ` ) ;
203263 } else {
204- console . error ( 'Usage: codebase-context memory <list|add|remove>' ) ;
205- console . error ( '' ) ;
206- console . error ( ' list [--category <cat>] [--type <type>] [--query <text>] [--json]' ) ;
207- console . error ( ' add --type <type> --category <category> --memory <text> --reason <text>' ) ;
208- console . error ( ' remove <id>' ) ;
209- process . exit ( 1 ) ;
264+ exitWithUsageError (
265+ 'Error: unknown subcommand. Expected: list | add | remove' ,
266+ 'Usage: codebase-context memory <list|add|remove>'
267+ ) ;
210268 }
211269}
0 commit comments