@@ -5,6 +5,9 @@ import { Logger } from '../utils/logger';
55import { TemplateEngine , TemplateContext } from '../services/TemplateEngine' ;
66import { FileUtils } from '../utils/fileUtils' ;
77import { generateSanitizedId } from '../utils/bundleNameUtils' ;
8+ import { ScaffoldDefaults } from '../types/hub' ;
9+ import { HubManager } from '../services/HubManager' ;
10+ import { resolveRunnerPattern } from '../utils/scaffoldUtils' ;
811import { NpmCliWrapper } from '../utils/NpmCliWrapper' ;
912
1013
@@ -232,28 +235,39 @@ export class ScaffoldCommand {
232235 /**
233236 * Run the scaffold command with full UI flow
234237 */
235- static async runWithUI ( ) : Promise < void > {
238+ static async runWithUI ( hubManager ?: HubManager ) : Promise < void > {
236239 const logger = Logger . getInstance ( ) ;
237240
238241 try {
239242 // Step 1: Select scaffold type
240243 const scaffoldType = await ScaffoldCommand . promptForScaffoldType ( ) ;
241244 if ( ! scaffoldType ) { return ; }
242245
243- // Step 2: Handle skill creation in existing project
246+ // Step 2: Load scaffold defaults from active hub
247+ let scaffoldDefaults : ScaffoldDefaults | undefined ;
248+ if ( hubManager ) {
249+ try {
250+ const activeHub = await hubManager . getActiveHub ( ) ;
251+ scaffoldDefaults = activeHub ?. config ?. scaffoldDefaults ;
252+ } catch {
253+ // No active hub or hub load failed — proceed without defaults
254+ }
255+ }
256+
257+ // Step 3: Handle skill creation in existing project
244258 if ( scaffoldType . value === ScaffoldType . Skill && await ScaffoldCommand . handleSkillInExistingProject ( ) ) {
245259 return ;
246260 }
247261
248- // Step 3 : Select target directory
262+ // Step 4 : Select target directory
249263 const targetPath = await ScaffoldCommand . promptForTargetDirectory ( scaffoldType . label ) ;
250264 if ( ! targetPath ) { return ; }
251265
252- // Step 4 : Collect project details
253- const options = await ScaffoldCommand . promptForProjectDetails ( scaffoldType . value ) ;
266+ // Step 5 : Collect project details
267+ const options = await ScaffoldCommand . promptForProjectDetails ( scaffoldType . value , scaffoldDefaults ) ;
254268 if ( ! options ) { return ; }
255269
256- // Step 5 : Execute scaffold with progress
270+ // Step 6 : Execute scaffold with progress
257271 await vscode . window . withProgress (
258272 { location : vscode . ProgressLocation . Notification , title : `Scaffolding ${ scaffoldType . label } ...` } ,
259273 async ( ) => {
@@ -262,7 +276,7 @@ export class ScaffoldCommand {
262276 }
263277 ) ;
264278
265- // Step 6 : Post-scaffold actions
279+ // Step 7 : Post-scaffold actions
266280 await ScaffoldCommand . handlePostScaffoldActions ( scaffoldType . label , targetPath ) ;
267281 } catch ( error ) {
268282 logger . error ( 'Scaffold failed' , error as Error ) ;
@@ -338,7 +352,7 @@ export class ScaffoldCommand {
338352 /**
339353 * Collect project details from user input
340354 */
341- private static async promptForProjectDetails ( type : ScaffoldType ) : Promise < ScaffoldOptions | undefined > {
355+ private static async promptForProjectDetails ( type : ScaffoldType , scaffoldDefaults ?: ScaffoldDefaults ) : Promise < ScaffoldOptions | undefined > {
342356 // Get project name
343357 const projectName = await vscode . window . showInputBox ( {
344358 prompt : 'Enter project name (optional)' ,
@@ -347,13 +361,11 @@ export class ScaffoldCommand {
347361 ignoreFocusOut : true
348362 } ) ;
349363
350- // Get GitHub runner choice
351- const githubRunner = await ScaffoldCommand . promptForGitHubRunner ( ) ;
352364 let details : { description ?: string ; author ?: string ; tags ?: string [ ] } = { } ;
353- let orgDetails : { organizationName ?: string ; internalContact ?: string ; legalContact ?: string ; organizationPolicyLink ?: string } = { } ;
354-
365+ let orgDetails : { author ?: string ; githubOrg ?: string ; organizationName ?: string ; internalContact ?: string ; legalContact ?: string ; organizationPolicyLink ?: string } = { } ;
366+
355367 // Collect additional details if needed
356-
368+
357369 if ( type === ScaffoldType . Apm ) {
358370 const apmDetails = await ScaffoldCommand . promptForApmDetails ( ) ;
359371 if ( apmDetails ) {
@@ -370,9 +382,12 @@ export class ScaffoldCommand {
370382
371383 // For GitHub type, collect organization details for InnerSource LICENSE
372384 if ( type === ScaffoldType . GitHub ) {
373- orgDetails = await ScaffoldCommand . promptForOrganizationDetails ( ) ;
385+ orgDetails = await ScaffoldCommand . promptForOrganizationDetails ( scaffoldDefaults ) ;
374386 }
375387
388+ // Get GitHub runner choice (always asked, prefilled from hub defaults)
389+ const githubRunner = await ScaffoldCommand . promptForGitHubRunner ( scaffoldDefaults , orgDetails . githubOrg ) ;
390+
376391 return {
377392 projectName,
378393 githubRunner,
@@ -384,7 +399,29 @@ export class ScaffoldCommand {
384399 /**
385400 * Prompt for GitHub Actions runner configuration
386401 */
387- private static async promptForGitHubRunner ( ) : Promise < string > {
402+ private static async promptForGitHubRunner ( scaffoldDefaults ?: ScaffoldDefaults , githubOrg ?: string ) : Promise < string > {
403+ // If hub provides a runner default, resolve the pattern and pre-fill
404+ if ( scaffoldDefaults ?. githubRunner ) {
405+ const resolvedRunner = githubOrg
406+ ? resolveRunnerPattern ( scaffoldDefaults . githubRunner , githubOrg )
407+ : scaffoldDefaults . githubRunner ;
408+ const customRunner = await vscode . window . showInputBox ( {
409+ prompt : 'Enter GitHub Actions runner label' ,
410+ placeHolder : 'my-runner or [self-hosted, linux, x64]' ,
411+ value : resolvedRunner ,
412+ validateInput : ( value ) => {
413+ if ( ! value || value . trim ( ) . length === 0 ) {
414+ return 'Runner label cannot be empty' ;
415+ }
416+ return undefined ;
417+ } ,
418+ ignoreFocusOut : true
419+ } ) ;
420+ // undefined means the user pressed Escape — use resolved default
421+ // empty string is prevented by validateInput
422+ return customRunner ?? resolvedRunner ;
423+ }
424+
388425 const runnerChoice = await vscode . window . showQuickPick (
389426 [
390427 {
@@ -484,56 +521,63 @@ export class ScaffoldCommand {
484521 }
485522
486523 /**
487- * Prompt for organization details for InnerSource LICENSE and docs
524+ * Prompt for organization details for InnerSource LICENSE and docs.
525+ * When hub scaffoldDefaults are present, individual org-level fields are
526+ * skipped (hub value used directly). Fields without a hub default are still prompted.
488527 */
489- private static async promptForOrganizationDetails ( ) : Promise < {
528+ private static async promptForOrganizationDetails (
529+ scaffoldDefaults ?: ScaffoldDefaults
530+ ) : Promise < {
490531 author ?: string ;
491532 githubOrg ?: string ;
492- organizationName ?: string ;
493- internalContact ?: string ;
494- legalContact ?: string ;
495- organizationPolicyLink ?: string
533+ organizationName ?: string ;
534+ internalContact ?: string ;
535+ legalContact ?: string ;
536+ organizationPolicyLink ?: string
496537 } > {
497538 const author = await vscode . window . showInputBox ( {
498539 prompt : 'Enter author name (for package.json)' ,
499540 placeHolder : 'Your Name or Team Name' ,
500541 ignoreFocusOut : true
501542 } ) ;
502543
544+ // githubOrg: always asked, prefilled from hub defaults if present
503545 const githubOrg = await vscode . window . showInputBox ( {
504546 prompt : 'Enter GitHub organization/username (for repository URLs)' ,
505547 placeHolder : 'your-org' ,
548+ value : scaffoldDefaults ?. githubOrg || '' ,
506549 ignoreFocusOut : true
507550 } ) ;
508551
509- const organizationName = await vscode . window . showInputBox ( {
552+ // Each org field: use hub default if present, otherwise prompt
553+ const organizationName = scaffoldDefaults ?. organizationName ?? await vscode . window . showInputBox ( {
510554 prompt : 'Enter organization name (for LICENSE)' ,
511555 placeHolder : 'Your Organization Name' ,
512556 value : 'Your Organization' ,
513557 ignoreFocusOut : true
514558 } ) ;
515559
516- const internalContact = await vscode . window . showInputBox ( {
560+ const internalContact = scaffoldDefaults ?. internalContact ?? await vscode . window . showInputBox ( {
517561 prompt : 'Enter internal contact email (for security/support inquiries)' ,
518562 placeHolder : 'security@yourorg.com' ,
519563 value : 'security@yourorg.com' ,
520564 ignoreFocusOut : true
521565 } ) ;
522566
523- const legalContact = await vscode . window . showInputBox ( {
567+ const legalContact = scaffoldDefaults ?. legalContact ?? await vscode . window . showInputBox ( {
524568 prompt : 'Enter legal contact email (for licensing questions)' ,
525569 placeHolder : 'legal@yourorg.com' ,
526570 value : 'legal@yourorg.com' ,
527571 ignoreFocusOut : true
528572 } ) ;
529573
530- const organizationPolicyLink = await vscode . window . showInputBox ( {
574+ const organizationPolicyLink = scaffoldDefaults ?. organizationPolicyLink ?? await vscode . window . showInputBox ( {
531575 prompt : 'Enter organization policy URL (optional)' ,
532576 placeHolder : 'https://yourorg.com/policies' ,
533577 ignoreFocusOut : true
534578 } ) ;
535579
536- return {
580+ return {
537581 author,
538582 githubOrg,
539583 organizationName,
0 commit comments