@@ -435,4 +435,175 @@ export default function register(ctx: PluginContext): void {
435435 process . exit ( 1 ) ;
436436 }
437437 } ) ;
438+
439+ githubCommand
440+ . command ( 'delegate <id>' )
441+ . description ( 'Delegate a work item to GitHub Copilot coding agent' )
442+ . option ( '--force' , 'Bypass do-not-delegate tag guard rail' , false )
443+ . option ( '--prefix <prefix>' , 'Override the default prefix' )
444+ . action ( async ( id : string , options : { force ?: boolean ; prefix ?: string } ) => {
445+ utils . requireInitialized ( ) ;
446+ const db = utils . getDatabase ( options . prefix ) ;
447+ const isJsonMode = utils . isJsonMode ( ) ;
448+
449+ // Resolve work item
450+ const normalizedId = utils . normalizeCliId ( id , options . prefix ) || id ;
451+ const item = db . get ( normalizedId ) ;
452+ if ( ! item ) {
453+ output . error ( `Work item not found: ${ normalizedId } ` , {
454+ success : false ,
455+ error : `Work item not found: ${ normalizedId } ` ,
456+ } ) ;
457+ process . exit ( 1 ) ;
458+ }
459+
460+ // Guard rail: do-not-delegate tag
461+ if ( Array . isArray ( item . tags ) && item . tags . includes ( 'do-not-delegate' ) ) {
462+ if ( ! options . force ) {
463+ const message = `Work item ${ normalizedId } has a "do-not-delegate" tag. Use --force to override.` ;
464+ output . error ( message , {
465+ success : false ,
466+ error : 'do-not-delegate' ,
467+ workItemId : normalizedId ,
468+ } ) ;
469+ process . exit ( 1 ) ;
470+ }
471+ if ( ! isJsonMode ) {
472+ console . log ( `Warning: Work item ${ normalizedId } has a "do-not-delegate" tag. Proceeding due to --force.` ) ;
473+ }
474+ }
475+
476+ // Guard rail: children warning
477+ const children = db . getChildren ( normalizedId ) ;
478+ if ( children . length > 0 ) {
479+ const nonClosedChildren = children . filter (
480+ c => c . status !== 'completed' && c . status !== 'deleted'
481+ ) ;
482+ if ( nonClosedChildren . length > 0 ) {
483+ // In non-interactive mode (JSON or non-TTY), proceed with single item only
484+ const isInteractive = ! isJsonMode && process . stdout . isTTY === true && process . stdin . isTTY === true ;
485+ if ( isInteractive ) {
486+ const readline = await import ( 'node:readline' ) ;
487+ const rl = readline . createInterface ( { input : process . stdin , output : process . stdout } ) ;
488+ const answer = await new Promise < string > ( resolve => {
489+ rl . question (
490+ `Work item ${ normalizedId } has ${ nonClosedChildren . length } open child item(s). ` +
491+ `Only the specified item will be delegated. Continue? (y/N): ` ,
492+ resolve
493+ ) ;
494+ } ) ;
495+ rl . close ( ) ;
496+ if ( answer . toLowerCase ( ) !== 'y' && answer . toLowerCase ( ) !== 'yes' ) {
497+ if ( ! isJsonMode ) {
498+ console . log ( 'Delegation cancelled.' ) ;
499+ }
500+ process . exit ( 0 ) ;
501+ }
502+ } else {
503+ // Non-interactive: proceed with single item, log warning
504+ if ( ! isJsonMode ) {
505+ console . log (
506+ `Warning: Work item ${ normalizedId } has ${ nonClosedChildren . length } open child item(s). ` +
507+ `Delegating only the specified item.`
508+ ) ;
509+ }
510+ }
511+ }
512+ }
513+
514+ // Guard rails passed — delegate flow placeholder
515+ // The actual push + assign + local state update is wired in WL-0MM8LXODU1DA2PON
516+ try {
517+ const githubConfig = resolveGithubConfig ( { repo : ( options as any ) . repo , labelPrefix : ( options as any ) . labelPrefix } ) ;
518+
519+ // Push the work item to GitHub (smart sync)
520+ const items = db . getAll ( ) ;
521+ const comments = db . getAllComments ( ) ;
522+ const { updatedItems } = await upsertIssuesFromWorkItems (
523+ [ item ] ,
524+ comments . filter ( c => c . workItemId === item . id ) ,
525+ githubConfig ,
526+ ( ) => { } // no progress rendering for single-item push
527+ ) ;
528+ if ( updatedItems . length > 0 ) {
529+ db . import ( updatedItems ) ;
530+ }
531+
532+ // Resolve the GitHub issue number (may have been set by the push)
533+ const refreshedItem = db . get ( normalizedId ) ;
534+ const issueNumber = refreshedItem ?. githubIssueNumber ?? item . githubIssueNumber ;
535+ if ( ! issueNumber ) {
536+ const message = `Failed to resolve GitHub issue number for ${ normalizedId } after push.` ;
537+ output . error ( message , {
538+ success : false ,
539+ error : message ,
540+ workItemId : normalizedId ,
541+ } ) ;
542+ process . exit ( 1 ) ;
543+ }
544+
545+ // Assign the issue to copilot
546+ const { assignGithubIssueAsync } = await import ( '../github.js' ) ;
547+ const assignResult = await assignGithubIssueAsync ( githubConfig , issueNumber , 'copilot' ) ;
548+
549+ if ( ! assignResult . ok ) {
550+ // Assignment failed: do NOT update local state, add comment, re-push
551+ const failureMessage = `Failed to assign copilot to GitHub issue #${ issueNumber } : ${ assignResult . error } ` ;
552+ db . createComment ( {
553+ workItemId : normalizedId ,
554+ author : 'wl-delegate' ,
555+ comment : failureMessage ,
556+ } ) ;
557+ // Re-push to restore consistency after comment
558+ const refreshedComments = db . getAllComments ( ) ;
559+ await upsertIssuesFromWorkItems (
560+ [ db . get ( normalizedId ) ! ] ,
561+ refreshedComments . filter ( c => c . workItemId === normalizedId ) ,
562+ githubConfig ,
563+ ( ) => { }
564+ ) ;
565+ output . error ( failureMessage , {
566+ success : false ,
567+ error : assignResult . error ,
568+ workItemId : normalizedId ,
569+ issueNumber,
570+ issueUrl : `https://github.com/${ githubConfig . repo } /issues/${ issueNumber } ` ,
571+ pushed : true ,
572+ assigned : false ,
573+ } ) ;
574+ process . exit ( 1 ) ;
575+ }
576+
577+ // Assignment succeeded: update local state
578+ db . update ( normalizedId , {
579+ status : 'in-progress' as any ,
580+ assignee : '@github-copilot' ,
581+ } ) ;
582+
583+ const issueUrl = `https://github.com/${ githubConfig . repo } /issues/${ issueNumber } ` ;
584+
585+ if ( isJsonMode ) {
586+ output . json ( {
587+ success : true ,
588+ workItemId : normalizedId ,
589+ issueNumber,
590+ issueUrl,
591+ pushed : true ,
592+ assigned : true ,
593+ } ) ;
594+ } else {
595+ console . log ( `Pushing to GitHub... done.` ) ;
596+ console . log ( `Assigning to copilot... done.` ) ;
597+ console . log ( `Done. Issue: ${ issueUrl } ` ) ;
598+ }
599+ } catch ( error ) {
600+ const message = `Delegation failed: ${ ( error as Error ) . message } ` ;
601+ output . error ( message , {
602+ success : false ,
603+ error : ( error as Error ) . message ,
604+ workItemId : normalizedId ,
605+ } ) ;
606+ process . exit ( 1 ) ;
607+ }
608+ } ) ;
438609}
0 commit comments