@@ -31,7 +31,8 @@ program
3131 . option ( '--min-volume <percent>' , 'Minimum coverage threshold (0-5%)' , parseFloat )
3232 . option ( '--speckle-rescue <pixels>' , 'Despeckle threshold (0-10px)' , parseFloat )
3333 . option ( '--shadow-clamp <percent>' , 'Ink body clamp (0-20%)' , parseFloat )
34- . option ( '--single' , 'Single archetype mode (default: compare 3 adaptive + top-scoring)' )
34+ . option ( '--single' , 'Single archetype mode (default if --archetype provided)' )
35+ . option ( '--compare' , 'Compare mode: Chameleon, Distilled, Salamander + top DNA match (default)' )
3536 . option ( '--recipe <path>' , 'Load settings from recipe JSON' )
3637 . option ( '--save-recipe <path>' , 'Save effective settings to recipe JSON' )
3738 . option ( '--list-archetypes' , 'Print available archetypes and exit' )
@@ -82,6 +83,9 @@ async function run(inputFile, options) {
8283
8384 try {
8485 // Validation
86+ if ( options . compare && ( options . single || options . archetype ) ) {
87+ throw new Error ( '--compare and --single/--archetype are mutually exclusive' ) ;
88+ }
8589 if ( options . single && ! options . archetype ) {
8690 throw new Error ( '--single requires --archetype (which archetype to use?)' ) ;
8791 }
@@ -107,7 +111,8 @@ async function run(inputFile, options) {
107111 // Merge recipe outputs with CLI formats
108112 const recipeFormats = recipe . outputs || [ ] ;
109113 mergedOptions . formats = new Set ( [ ...formats , ...recipeFormats ] ) ;
110- mergedOptions . single = options . single ;
114+ mergedOptions . single = options . single || ! ! mergedOptions . archetype ;
115+ mergedOptions . compare = options . compare ;
111116 mergedOptions . output = options . output || recipe . outputDir ;
112117 mergedOptions . quiet = options . quiet ;
113118 mergedOptions . verbose = options . verbose ;
@@ -116,6 +121,7 @@ async function run(inputFile, options) {
116121 log ( `Loaded recipe: ${ options . recipe } ` ) ;
117122 } else {
118123 mergedOptions . formats = formats ;
124+ mergedOptions . single = options . single || ! ! options . archetype ;
119125 }
120126
121127 // Ingest
@@ -127,10 +133,10 @@ async function run(inputFile, options) {
127133 const basename = path . basename ( inputFile , path . extname ( inputFile ) ) ;
128134 const inputDir = path . dirname ( path . resolve ( inputFile ) ) ;
129135
130- if ( mergedOptions . single || mergedOptions . archetype ) {
136+ if ( mergedOptions . single ) {
131137 await runSingle ( lab16bit , width , height , basename , inputDir , inputFormat , mergedOptions , log , verbose , inputFile ) ;
132138 } else {
133- await runCompare ( lab16bit , width , height , basename , inputDir , inputFormat , mergedOptions , log , verbose ) ;
139+ await runCompare ( lab16bit , width , height , basename , inputDir , inputFormat , mergedOptions , log , verbose , inputFile ) ;
134140 }
135141
136142 const elapsed = ( ( Date . now ( ) - startTime ) / 1000 ) . toFixed ( 1 ) ;
@@ -222,7 +228,7 @@ async function runSingle(lab16bit, width, height, basename, inputDir, inputForma
222228 }
223229}
224230
225- async function runCompare ( lab16bit , width , height , basename , inputDir , inputFormat , options , log , verbose ) {
231+ async function runCompare ( lab16bit , width , height , basename , inputDir , inputFormat , options , log , verbose , inputFile ) {
226232 // Shared DNA computation
227233 log ( 'Computing DNA...' ) ;
228234 const dna = computeDna ( lab16bit , width , height ) ;
@@ -234,7 +240,8 @@ async function runCompare(lab16bit, width, height, basename, inputDir, inputForm
234240 // Deduplicate if top match is one of the pseudos
235241 const uniqueArchetypes = [ ...new Set ( archetypes ) ] ;
236242
237- const parentDir = path . join ( options . output ? path . resolve ( options . output ) : inputDir , `${ basename } _reveal` ) ;
243+ const rootOutputDir = options . output ? path . resolve ( options . output ) : inputDir ;
244+ const parentDir = path . join ( rootOutputDir , `${ basename } _reveal` ) ;
238245 if ( ! fs . existsSync ( parentDir ) ) fs . mkdirSync ( parentDir , { recursive : true } ) ;
239246
240247 const summaryRows = [ ] ;
@@ -258,8 +265,57 @@ async function runCompare(lab16bit, width, height, basename, inputDir, inputForm
258265
259266 // Write outputs in subdirectory — preserve 16-bit depth for PSD/TIFF input
260267 const sixteenBit = inputFormat === 'psd' || inputFormat === 'tiff' ;
261- await writeFlat ( result . colorIndices , result . paletteLab , width , height ,
262- path . join ( subDir , `${ basename } .png` ) , { sixteenBit } ) ;
268+ const subFlatPath = path . join ( subDir , `${ basename } .png` ) ;
269+ await writeFlat ( result . colorIndices , result . paletteLab , width , height , subFlatPath , { sixteenBit } ) ;
270+
271+ // If this is the top DNA match, ALSO write to the root output directory
272+ // to satisfy integration tests and provide a convenient "best" result.
273+ if ( archId === topMatch ) {
274+ const rootFlatPath = path . join ( rootOutputDir , `${ basename } _reveal.png` ) ;
275+ await writeFlat ( result . colorIndices , result . paletteLab , width , height , rootFlatPath , { sixteenBit } ) ;
276+ log ( `Wrote top match to root: ${ rootFlatPath } ` ) ;
277+
278+ const rootOutputFiles = [ path . basename ( rootFlatPath ) ] ;
279+
280+ // Write requested formats to root too
281+ if ( options . formats . has ( 'psd' ) ) {
282+ const rootPsdPath = path . join ( rootOutputDir , `${ basename } _reveal.psd` ) ;
283+ await writePsd ( result . paletteLab , result . paletteRgb , result . masks , result . colorIndices , width , height , rootPsdPath ) ;
284+ rootOutputFiles . push ( path . basename ( rootPsdPath ) ) ;
285+ }
286+
287+ if ( options . formats . has ( 'ora' ) ) {
288+ const rootOraPath = path . join ( rootOutputDir , `${ basename } _reveal.ora` ) ;
289+ await writeOra ( result . paletteLab , result . paletteRgb , result . masks , result . colorIndices , width , height , rootOraPath , result . hexColors ) ;
290+ rootOutputFiles . push ( path . basename ( rootOraPath ) ) ;
291+ }
292+
293+ if ( options . formats . has ( 'plates' ) ) {
294+ const rootPlatePaths = await writePlates ( result . masks , result . hexColors , width , height , rootOutputDir , basename ) ;
295+ rootOutputFiles . push ( ...rootPlatePaths . map ( p => path . basename ( p ) ) ) ;
296+ }
297+
298+ if ( options . json !== false ) {
299+ const rootJsonPath = path . join ( rootOutputDir , `${ basename } _reveal.json` ) ;
300+ writeSidecar ( rootJsonPath , result , {
301+ inputFile : path . basename ( inputFile ) ,
302+ outputFiles : rootOutputFiles ,
303+ trap : options . trap || 0 ,
304+ } ) ;
305+ }
306+
307+ // Save recipe if requested
308+ if ( options . saveRecipe ) {
309+ saveRecipe ( options . saveRecipe , {
310+ archetype : result . config . meta ?. archetypeId ,
311+ colors : result . paletteLab . length ,
312+ trap : options . trap ,
313+ minVolume : result . config . minVolume ,
314+ speckleRescue : result . config . speckleRescue ,
315+ shadowClamp : result . config . shadowClamp ,
316+ } ) ;
317+ }
318+ }
263319
264320 if ( options . formats . has ( 'psd' ) ) {
265321 await writePsd ( result . paletteLab , result . paletteRgb , result . masks , result . colorIndices , width , height ,
0 commit comments