11import path from "node:path" ;
22import { PlanItem , ParseOptions } from "./types.js" ;
33import {
4- computeDepth ,
54 hasExtension ,
65 isBareKnownFile ,
76 looksLikeDirToken ,
@@ -25,13 +24,13 @@ export function parseToPlan(input: string, opts: ParseOptions): ParseResult {
2524 . map ( ( l ) => l . replace ( / \s + $ / , "" ) )
2625 . filter ( ( l ) => l . trim ( ) . length > 0 ) ;
2726
28- // inferRoot: primeira linha é algo como "my- project/" (apenas a barra de fechamento)
27+ // infer base root from a first-line token like " project/"
2928 let baseRoot = rootDir ;
3029 if ( opts . inferRoot && lines . length > 0 ) {
31- const firstContent = sanitizeContent ( lines [ 0 ] ) ;
32- if ( firstContent . endsWith ( "/" ) ) {
33- const token = firstContent . replace ( / \/ + $ / , "" ) ;
34- if ( ! token . includes ( "/" ) ) {
30+ const first = sanitizeContent ( lines [ 0 ] ) ;
31+ if ( first . endsWith ( "/" ) ) {
32+ const token = first . replace ( / \/ + $ / , "" ) ;
33+ if ( token && ! token . includes ( "/" ) ) {
3534 baseRoot = safeJoin ( rootDir , token ) ;
3635 lines = lines . slice ( 1 ) ;
3736 }
@@ -40,10 +39,12 @@ export function parseToPlan(input: string, opts: ParseOptions): ParseResult {
4039
4140 const plan : PlanItem [ ] = [ ] ;
4241 const seen = new Set < string > ( ) ;
42+
43+ // directory stack by depth
4344 const stack : string [ ] = [ ] ;
4445 stack [ 0 ] = baseRoot ;
4546
46- // garanta que o diretório baseRoot apareça no plano
47+ // ensure baseRoot is part of the plan
4748 addDir ( baseRoot ) ;
4849
4950 for ( const raw of lines ) {
@@ -52,14 +53,14 @@ export function parseToPlan(input: string, opts: ParseOptions): ParseResult {
5253 const content0 = sanitizeContent ( unclean ) ;
5354 if ( ! content0 ) continue ;
5455
55- // caminho absoluto relativo ao baseRoot: "/foo/bar"
56+ // "/foo/bar" relative to baseRoot
5657 if ( content0 . startsWith ( "/" ) ) {
5758 const rel = content0 . replace ( / ^ \/ + / , "" ) ;
5859 addAny ( rel , 0 ) ;
5960 continue ;
6061 }
6162
62- // possui separadores
63+ // token with slashes
6364 if ( content0 . includes ( "/" ) ) {
6465 const isDir = content0 . endsWith ( "/" ) ;
6566 const rel = isDir ? content0 . slice ( 0 , - 1 ) : content0 ;
@@ -77,7 +78,7 @@ export function parseToPlan(input: string, opts: ParseOptions): ParseResult {
7778 continue ;
7879 }
7980
80- // token "nu" sem barra
81+ // bare token
8182 if ( looksLikeDirToken ( content0 ) ) {
8283 const parent = stack [ depth ] ?? baseRoot ;
8384 const abs = safeJoin ( parent , content0 . replace ( / \/ $ / , "" ) ) ;
@@ -96,14 +97,35 @@ export function parseToPlan(input: string, opts: ParseOptions): ParseResult {
9697 function sanitizeContent ( s : string ) : string {
9798 let out = s . trim ( ) ;
9899 out = stripBullets ( out ) ;
99- out = out . replace ( / ^ (?: [ │ ├ └ ] ? [ ─ - ] ) \s * / , "" ) ;
100+ // drop a leading branch marker like "├─ " or "└─ " or "- "
101+ out = out . replace ( / ^ (?: [ │ ├ └ ] \s * ) ? [ ─ - ] \s * / u, "" ) ;
100102 out = stripTrailingComments ( out ) ;
101103 out = out . trim ( ) ;
102104 out = out . replace ( / \/ \s + $ / , "/" ) ;
103105 out = out . replace ( / \s + \( (?: o p c i o n a l | o p t i o n a l ) [ ^ ) ] * \) \s * $ / i, "" ) ;
104106 return out ;
105107 }
106108
109+ function computeDepth ( prefix : string ) : number {
110+ // remove the trailing branch marker (├─ or └─) so it doesn't count as indent
111+ let p = prefix . replace ( / [ ├ └ ] ─ \s * $ / u, "" ) ;
112+ // if box pipes exist, depth equals number of pipes
113+ const pipes = ( p . match ( / │ / g) || [ ] ) . length ;
114+ if ( pipes > 0 ) return pipes ;
115+ // otherwise, count groups of two spaces
116+ p = p . replace ( / [ ├ └ ─ ] / g, " " ) . replace ( / \t / g, " " ) ;
117+ let groups = 0 , acc = 0 ;
118+ for ( const ch of p ) {
119+ if ( ch === " " ) {
120+ acc ++ ;
121+ if ( acc === 2 ) { groups ++ ; acc = 0 ; }
122+ } else {
123+ acc = 0 ;
124+ }
125+ }
126+ return groups ;
127+ }
128+
107129 function addAny ( rel : string , depth : number ) {
108130 const isDir =
109131 rel . endsWith ( "/" ) ||
@@ -121,6 +143,21 @@ export function parseToPlan(input: string, opts: ParseOptions): ParseResult {
121143 }
122144
123145 function addDir ( abs : string ) {
146+ // include all ancestor directories under baseRoot
147+ const rel = path . relative ( baseRoot , abs ) ;
148+ if ( rel && rel !== "." ) {
149+ const parts = rel . split ( path . sep ) . filter ( Boolean ) ;
150+ let cur = baseRoot ;
151+ for ( const seg of parts ) {
152+ cur = safeJoin ( cur , seg ) ;
153+ pushDir ( cur ) ;
154+ }
155+ } else {
156+ pushDir ( baseRoot ) ;
157+ }
158+ }
159+
160+ function pushDir ( abs : string ) {
124161 const key = `d:${ abs } ` ;
125162 if ( ! seen . has ( key ) ) {
126163 seen . add ( key ) ;
0 commit comments