@@ -12,6 +12,7 @@ import { diffModels } from './diff.ts';
1212import { buildReport } from './report.ts' ;
1313import { serve } from './server.ts' ;
1414import { exportHtml } from './export.ts' ;
15+ import { scanAtRef , splitRefRange } from './gitref.ts' ;
1516import { parseGraph , TOOL_VERSION } from './model.ts' ;
1617import type { Graph , GNode } from './model.ts' ;
1718
@@ -29,10 +30,15 @@ const HELP = `Strata — 多言語・マイクロサービス対応の依存関
2930 strata report [dir|model.json] [-o report.md] アーキテクチャレポート(mermaid + Markdown)を出力
3031 strata metrics [dir|model.json] [--json] サービス結合度(Ca/Ce/不安定度)を算出
3132 strata diff <old> <new> [--json] 2 モデルを比較(依存増減・新規/解消の循環)
33+ [dir] --ref <base>..<head> 2 つの git ref を直接比較(head 省略で作業ツリー)
3234 strata trace [dir|model.json] <関数名/ID> [--up] [--depth N] コールツリーを表示
3335 strata init [dir] [--force] strata.config.json の雛形を作成
3436 strata --version バージョンを表示(不具合報告時に添えてください)
3537
38+ 共通:
39+ --ref <git ref> 作業ツリーではなく、その ref の内容を解析する
40+ (一時 worktree に取り出すので、いま編集中のファイルには触れない)
41+
3642対応: Go / TypeScript / JavaScript / Python / Elixir / Protocol Buffers(gRPC)
3743設定: ワークスペースルートの strata.config.json(docs/SPEC.md 参照)
3844` ;
@@ -52,7 +58,7 @@ function parseArgs(argv: string[]): Args {
5258 }
5359 for ( ; i < argv . length ; i ++ ) {
5460 const a = argv [ i ] ;
55- if ( a === '-o' || a === '--out' || a === '--port' || a === '--depth' || a === '--baseline' ) {
61+ if ( a === '-o' || a === '--out' || a === '--port' || a === '--depth' || a === '--baseline' || a === '--ref' ) {
5662 args . options . set ( a . replace ( / ^ - + / , '' ) , argv [ ++ i ] ?? '' ) ;
5763 } else if ( a === '-v' ) {
5864 args . options . set ( 'version' , true ) ; // よく使われる短縮形だけ受ける
@@ -65,12 +71,19 @@ function parseArgs(argv: string[]): Args {
6571 return args ;
6672}
6773
68- function loadModel ( input : string | undefined ) : Graph {
74+ function loadModel ( input : string | undefined , ref ?: string ) : Graph {
6975 const target = input ?? '.' ;
7076 if ( target . endsWith ( '.json' ) && fs . existsSync ( target ) && fs . statSync ( target ) . isFile ( ) ) {
77+ if ( ref ) throw new Error ( '--ref は model.json ではなくディレクトリに対して指定してください' ) ;
7178 return parseGraph ( fs . readFileSync ( target , 'utf8' ) , target ) ;
7279 }
73- return scan ( target ) ;
80+ return ref ? scanAtRef ( target , ref ) : scan ( target ) ;
81+ }
82+
83+ /** コマンド共通の `--ref <git ref>`。指定がなければ undefined。 */
84+ function refOf ( args : Args ) : string | undefined {
85+ const ref = args . options . get ( 'ref' ) ;
86+ return typeof ref === 'string' && ref !== '' ? ref : undefined ;
7487}
7588
7689function labelOf ( model : Graph , id : string ) : string {
@@ -234,7 +247,9 @@ function main(): void {
234247
235248 switch ( command ) {
236249 case 'scan ': {
237- const model = scan ( args . positional [ 0 ] ?? '.' ) ;
250+ const ref = refOf ( args ) ;
251+ const target = args . positional [ 0 ] ?? '.' ;
252+ const model = ref ? scanAtRef ( target , ref ) : scan ( target ) ;
238253 const out = args . options . get ( 'o' ) ?? args . options . get ( 'out' ) ;
239254 const json = JSON . stringify ( model , null , 2 ) ;
240255 if ( typeof out === 'string' && out !== '' ) {
@@ -285,18 +300,18 @@ function main(): void {
285300 case 'serve ': {
286301 const input = args . positional [ 0 ] ?? '.' ;
287302 const port = Number ( args . options . get ( 'port' ) ?? 7333 ) ;
288- serve ( input , port , { watch : args . options . has ( 'watch' ) } ) ;
303+ serve ( input , port , { watch : args . options . has ( 'watch' ) , ... ( refOf ( args ) ? { ref : refOf ( args ) ! } : { } ) } ) ;
289304 break ;
290305 }
291306 case 'export ': {
292- const model = loadModel ( args . positional [ 0 ] ) ;
307+ const model = loadModel ( args . positional [ 0 ] , refOf ( args ) ) ;
293308 const out = ( args . options . get ( 'o' ) as string ) || ( args . options . get ( 'out' ) as string ) || 'report.html' ;
294309 fs . writeFileSync ( out , exportHtml ( model ) ) ;
295310 console . log ( `書き出しました: ${ path . resolve ( out ) } ` ) ;
296311 break ;
297312 }
298313 case 'check ': {
299- const model = loadModel ( args . positional [ 0 ] ) ;
314+ const model = loadModel ( args . positional [ 0 ] , refOf ( args ) ) ;
300315 const violations = checkRules ( model , model . rules ) ;
301316 // SARIF 出力(GitHub Code Scanning 連携)。テキスト出力の代わりに構造化して出す
302317 if ( args . options . has ( 'sarif' ) ) {
@@ -339,14 +354,26 @@ function main(): void {
339354 break ;
340355 }
341356 case 'diff ': {
342- if ( args . positional . length < 2 ) {
357+ // --ref base..head を渡すと、その 2 つの ref を一時 worktree に取り出して比較する
358+ // (head を省くと作業ツリーと比べる)。model.json を 2 つ渡す従来の形も残す。
359+ const spec = refOf ( args ) ;
360+ const range = spec ? splitRefRange ( spec ) : null ;
361+ let a : Graph ;
362+ let b : Graph ;
363+ if ( spec ) {
364+ const dir = args . positional [ 0 ] ?? '.' ;
365+ a = scanAtRef ( dir , range ? range . base : spec ) ;
366+ b = range ? scanAtRef ( dir , range . head ) : loadModel ( dir ) ;
367+ } else if ( args . positional . length >= 2 ) {
368+ a = loadModel ( args . positional [ 0 ] ) ;
369+ b = loadModel ( args . positional [ 1 ] ) ;
370+ } else {
343371 console . error ( '使い方: strata diff <old: dir|model.json> <new: dir|model.json> [--json]' ) ;
344- console . error ( ' 各 git ref で strata scan -o した model.json を渡すと、変化を比較できます' ) ;
372+ console . error ( ' strata diff [dir] --ref <base>..<head> [--json] 2 つの git ref を比較' ) ;
373+ console . error ( ' strata diff [dir] --ref <base> [--json] ref と作業ツリーを比較' ) ;
345374 process . exitCode = 1 ;
346375 return ;
347376 }
348- const a = loadModel ( args . positional [ 0 ] ) ;
349- const b = loadModel ( args . positional [ 1 ] ) ;
350377 const d = diffModels ( a , b ) ;
351378 if ( args . options . has ( 'json' ) ) {
352379 console . log ( JSON . stringify ( d , null , 2 ) ) ;
@@ -375,7 +402,7 @@ function main(): void {
375402 break ;
376403 }
377404 case 'metrics' : {
378- const model = loadModel ( args . positional [ 0 ] ) ;
405+ const model = loadModel ( args . positional [ 0 ] , refOf ( args ) ) ;
379406 const metrics = computeServiceMetrics ( model ) ;
380407 if ( args . options . has ( 'json' ) ) {
381408 console . log ( JSON . stringify ( metrics , null , 2 ) ) ;
@@ -401,7 +428,7 @@ function main(): void {
401428 break ;
402429 }
403430 case 'report ': {
404- const model = loadModel ( args . positional [ 0 ] ) ;
431+ const model = loadModel ( args . positional [ 0 ] , refOf ( args ) ) ;
405432 const md = buildReport ( model ) ;
406433 const out = args . options . get ( 'o' ) ?? args . options . get ( 'out' ) ;
407434 if ( typeof out === 'string' && out !== '' ) {
@@ -420,7 +447,7 @@ function main(): void {
420447 }
421448 const query = args . positional [ args . positional . length - 1 ] ;
422449 const input = args . positional . length >= 2 ? args . positional [ 0 ] : '.' ;
423- const model = loadModel ( input ) ;
450+ const model = loadModel ( input , refOf ( args ) ) ;
424451 let target = model . nodes . find ( ( n ) => n . id === query ) ;
425452 if ( ! target ) {
426453 const matches = model . nodes . filter (
0 commit comments