1616import { execFileSync } from 'node:child_process' ;
1717import { createReadStream , readdirSync , statSync } from 'node:fs' ;
1818import { createInterface } from 'node:readline' ;
19- import { join } from 'node:path' ;
19+ import { isAbsolute , join , resolve } from 'node:path' ;
20+ import { fileURLToPath } from 'node:url' ;
2021import { createHash } from 'node:crypto' ;
2122
2223/** Explore calls in one transcript line: [{ query, projectPath?, maxFiles? }]. */
@@ -51,8 +52,9 @@ const ESCAPES = { n: '\n', t: '\t', r: '\r', b: '\b', f: '\f', v: '\v', 0: '\0'
5152/**
5253 * The top-level string and number fields of the JavaScript object literal that
5354 * opens at `src[open]` ('{'). Code-mode calls are JavaScript, not JSON: keys may
54- * be bare, strings may use any quote and may contain braces. Values that are
55- * not plain literals (expressions, template strings with `${`) are skipped.
55+ * be bare, strings may use any quote and may contain braces. A field whose
56+ * value is not a plain literal (a variable, an expression, a template string
57+ * with `${`) is null, so a caller can tell it was present but unreadable.
5658 */
5759export function literalFields ( src , open ) {
5860 const fields = { } ;
@@ -68,9 +70,10 @@ export function literalFields(src, open) {
6870 }
6971 i = j ;
7072 if ( depth === 1 && expectKey ) { key = s ; expectKey = false ; }
71- else if ( depth === 1 && expectValue && key !== undefined && literal ) fields [ key ] = s ;
73+ else if ( depth === 1 && expectValue && key !== undefined ) fields [ key ] = literal ? s : null ;
7274 expectValue = false ;
7375 } else if ( ch === '{' || ch === '[' || ch === '(' ) {
76+ if ( depth === 1 && expectValue && key !== undefined ) fields [ key ] = null ;
7477 depth ++ ;
7578 if ( depth === 1 ) expectKey = true ;
7679 expectValue = false ;
@@ -84,13 +87,27 @@ export function literalFields(src, open) {
8487 const word = / ^ [ A - Z a - z _ $ 0 - 9 . ] + / . exec ( src . slice ( i ) ) [ 0 ] ;
8588 i += word . length - 1 ;
8689 if ( expectKey ) { key = word ; expectKey = false ; }
87- else if ( expectValue && key !== undefined && / ^ \d + $ / . test ( word ) ) fields [ key ] = Number ( word ) ;
90+ else if ( expectValue && key !== undefined ) fields [ key ] = / ^ \d + $ / . test ( word ) ? Number ( word ) : null ;
8891 expectValue = false ;
8992 }
9093 }
9194 return fields ;
9295}
9396
97+ /**
98+ * The directory a call asked about: its projectPath, resolved against the
99+ * session's working directory when relative, or that directory when the call
100+ * named none. Null when it cannot be known (a projectPath that is not a plain
101+ * string, or a relative one with no session directory): such a call is
102+ * dropped, never credited to the directory the session started in.
103+ */
104+ export function callDirectory ( call , cwd ) {
105+ if ( call . projectPath === undefined ) return cwd ?? null ;
106+ if ( typeof call . projectPath !== 'string' || call . projectPath === '' ) return null ;
107+ if ( isAbsolute ( call . projectPath ) ) return call . projectPath ;
108+ return cwd ? resolve ( cwd , call . projectPath ) : null ;
109+ }
110+
94111/** `owner/repo` from a remote on github.com itself (HTTPS, SSH URL or scp form), or null. */
95112export function repoSlug ( url ) {
96113 const text = url . trim ( ) ;
@@ -115,7 +132,10 @@ async function main(argv) {
115132 const inputs = [ ] ;
116133 let since = '' ;
117134 for ( let i = 0 ; i < argv . length ; i ++ ) {
118- if ( argv [ i ] === '--public' ) pub . add ( argv [ ++ i ] . toLowerCase ( ) ) ;
135+ if ( argv [ i ] === '--public' ) {
136+ const name = argv [ ++ i ] ;
137+ pub . add ( repoSlug ( name ) ?? name . toLowerCase ( ) . replace ( / \. g i t $ / , '' ) ) ;
138+ }
119139 else if ( argv [ i ] === '--since' ) since = argv [ ++ i ] ;
120140 else inputs . push ( argv [ i ] ) ;
121141 }
@@ -148,7 +168,7 @@ async function main(argv) {
148168 cwd = entry . cwd ?? entry . payload ?. cwd ?? cwd ;
149169 if ( since && ( entry . timestamp ?? '' ) < since ) continue ;
150170 for ( const call of callsInLine ( entry ) ) {
151- const dir = call . projectPath ?? cwd ;
171+ const dir = callDirectory ( call , cwd ) ;
152172 const slug = dir ? slugFor ( dir ) : null ;
153173 if ( ! slug || ! pub . has ( slug ) ) { dropped ++ ; continue ; }
154174 const prompt = call . query . trim ( ) . replace ( / \s + / g, ' ' ) ;
@@ -166,8 +186,10 @@ async function main(argv) {
166186 }
167187 }
168188 console . log ( JSON . stringify ( { repos } , null , 2 ) ) ;
169- console . error ( `kept ${ kept } , duplicates ${ duplicate } , dropped ${ dropped } (not on a --public repository)` ) ;
189+ console . error ( `kept ${ kept } , duplicates ${ duplicate } , dropped ${ dropped } (project unknown or not on a --public repository)` ) ;
170190 return 0 ;
171191}
172192
173- if ( import . meta. url === `file://${ process . argv [ 1 ] } ` ) main ( process . argv . slice ( 2 ) ) . then ( ( code ) => process . exit ( code ) ) ;
193+ if ( process . argv [ 1 ] && resolve ( process . argv [ 1 ] ) === fileURLToPath ( import . meta. url ) ) {
194+ main ( process . argv . slice ( 2 ) ) . then ( ( code ) => process . exit ( code ) ) ;
195+ }
0 commit comments