@@ -3,6 +3,20 @@ import { defaultProjectsRoot } from "./frontend-lib/usecases/menu-helpers.js"
33import type { CreateFlowContext } from "./menu-create-flow-types.js"
44import type { CreateInputs } from "./menu-types.js"
55
6+ /**
7+ * Removes leading path separators from a path segment.
8+ *
9+ * @param value - Path segment to normalize.
10+ * @returns The segment without leading slash characters.
11+ *
12+ * @pure true
13+ * @effect n/a
14+ * @invariant Result length is less than or equal to input length.
15+ * @precondition `value` is a finite string.
16+ * @postcondition The result does not start with `/` unless it is empty.
17+ * @complexity O(n) time and O(n) space where n = |value|.
18+ * @throws Never
19+ */
620const trimLeftSlash = ( value : string ) : string => {
721 let start = 0
822 while ( start < value . length && value [ start ] === "/" ) {
@@ -11,6 +25,20 @@ const trimLeftSlash = (value: string): string => {
1125 return value . slice ( start )
1226}
1327
28+ /**
29+ * Removes trailing path separators from a path segment.
30+ *
31+ * @param value - Path segment to normalize.
32+ * @returns The segment without trailing slash characters.
33+ *
34+ * @pure true
35+ * @effect n/a
36+ * @invariant Result length is less than or equal to input length.
37+ * @precondition `value` is a finite string.
38+ * @postcondition The result does not end with `/` unless it is empty.
39+ * @complexity O(n) time and O(n) space where n = |value|.
40+ * @throws Never
41+ */
1442const trimRightSlash = ( value : string ) : string => {
1543 let end = value . length
1644 while ( end > 0 && value [ end - 1 ] === "/" ) {
@@ -19,24 +47,54 @@ const trimRightSlash = (value: string): string => {
1947 return value . slice ( 0 , end )
2048}
2149
50+ /**
51+ * Joins normalized POSIX-style path parts while preserving a root `/`.
52+ *
53+ * @param parts - Ordered path parts, starting with the base directory.
54+ * @returns A slash-separated path with empty non-root segments removed.
55+ *
56+ * @pure true
57+ * @effect n/a
58+ * @invariant A leading `/` base remains absolute in the result.
59+ * @precondition Each part is a finite string.
60+ * @postcondition Non-root parts do not introduce duplicate separators.
61+ * @complexity O(p + n) time and O(p + n) space where p = |parts| and n = total input length.
62+ * @throws Never
63+ */
2264const joinPath = ( ...parts : ReadonlyArray < string > ) : string => {
2365 const cleaned = parts
2466 . filter ( ( part ) => part . length > 0 )
2567 . map ( ( part , index ) => {
2668 if ( index === 0 ) {
27- return trimRightSlash ( part )
69+ const trimmed = trimRightSlash ( part )
70+ return trimmed . length === 0 && part . startsWith ( "/" ) ? "/" : trimmed
2871 }
2972 return trimRightSlash ( trimLeftSlash ( part ) )
3073 } )
74+ . filter ( ( part , index ) => index === 0 || part . length > 0 )
75+
76+ if ( cleaned . length === 0 ) {
77+ return ""
78+ }
79+ if ( cleaned [ 0 ] === "/" ) {
80+ return cleaned . length === 1 ? "/" : `/${ cleaned . slice ( 1 ) . join ( "/" ) } `
81+ }
3182 return cleaned . join ( "/" )
3283}
3384
3485/**
3586 * Normalizes legacy cwd input into the create-flow context record.
3687 *
88+ * @param context - Legacy cwd string or already-normalized context record.
89+ * @returns A context record with at least `cwd` defined.
90+ *
3791 * @pure true
92+ * @effect n/a
3893 * @invariant string input maps to { cwd: input }
39- * @complexity O(1)
94+ * @precondition `context` is a finite cwd string or CreateFlowContext.
95+ * @postcondition Object context input is preserved by reference.
96+ * @complexity O(1) time and O(1) space.
97+ * @throws Never
4098 */
4199// CHANGE: normalize create-flow context boundaries into one record shape
42100// WHY: pure helpers can share cwd and optional projectsRoot resolution
@@ -55,6 +113,20 @@ export const normalizeCreateFlowContext = (
55113 ? { cwd : context }
56114 : context
57115
116+ /**
117+ * Resolves the configured projects root or derives it from cwd.
118+ *
119+ * @param context - Create-flow context with cwd and optional projectsRoot.
120+ * @returns Explicit non-blank projectsRoot, otherwise the cwd-derived default.
121+ *
122+ * @pure true
123+ * @effect n/a
124+ * @invariant Non-blank `projectsRoot` takes precedence over cwd defaults.
125+ * @precondition `context.cwd` is a finite string.
126+ * @postcondition Returned root is a finite string.
127+ * @complexity O(n) time and O(n) space where n = |context.projectsRoot ?? context.cwd|.
128+ * @throws Never
129+ */
58130const resolveProjectsRoot = ( context : CreateFlowContext ) : string =>
59131 context . projectsRoot ?. trim ( ) . length
60132 ? context . projectsRoot
@@ -63,9 +135,17 @@ const resolveProjectsRoot = (context: CreateFlowContext): string =>
63135/**
64136 * Resolves the default output directory for a repo input.
65137 *
138+ * @param context - Create-flow context used to select the projects root.
139+ * @param repoUrl - Repository input accepted by `resolveRepoInput`.
140+ * @returns Default output directory under the resolved projects root.
141+ *
66142 * @pure true
143+ * @effect n/a
67144 * @invariant output is rooted under the resolved projects root
68- * @complexity O(n) where n = |repoUrl|
145+ * @precondition `repoUrl` is a finite string.
146+ * @postcondition The result contains the repository path parts in order.
147+ * @complexity O(n) time and O(n) space where n = |repoUrl|.
148+ * @throws Never
69149 */
70150// CHANGE: derive create-flow output directory from repo identity and context root
71151// WHY: repo URL, branch suffix, and browser-provided projectsRoot must resolve consistently
@@ -87,9 +167,17 @@ export const resolveDefaultOutDir = (context: CreateFlowContext, repoUrl: string
87167/**
88168 * Resolves partial create-flow values into total create command inputs.
89169 *
170+ * @param contextOrCwd - Legacy cwd string or create-flow context.
171+ * @param values - Partial create inputs collected by the flow.
172+ * @returns Total create inputs with deterministic defaults.
173+ *
90174 * @pure true
175+ * @effect n/a
91176 * @invariant every CreateInputs field is defined in the result
92- * @complexity O(n) where n = |repoUrl|
177+ * @precondition `values` is a finite partial record.
178+ * @postcondition Explicit false boolean fields remain false in the result.
179+ * @complexity O(n) time and O(n) space where n = |repoUrl|.
180+ * @throws Never
93181 */
94182// CHANGE: totalize create-flow partial values with deterministic defaults
95183// WHY: completion must hand the shell a complete create command input record
0 commit comments