@camaradesuk/git-worktree-tools 1.7.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/README.md +69 -8
  2. package/dist/cli/cleanpr.js +11 -5
  3. package/dist/cli/cleanpr.js.map +1 -1
  4. package/dist/cli/cleanpr.test.js +10 -1
  5. package/dist/cli/cleanpr.test.js.map +1 -1
  6. package/dist/cli/newpr.js +100 -28
  7. package/dist/cli/newpr.js.map +1 -1
  8. package/dist/cli/newpr.test.js +24 -6
  9. package/dist/cli/newpr.test.js.map +1 -1
  10. package/dist/cli/prs.d.ts +22 -0
  11. package/dist/cli/prs.d.ts.map +1 -0
  12. package/dist/cli/prs.js +275 -0
  13. package/dist/cli/prs.js.map +1 -0
  14. package/dist/cli/prs.test.d.ts +8 -0
  15. package/dist/cli/prs.test.d.ts.map +1 -0
  16. package/dist/cli/prs.test.js +408 -0
  17. package/dist/cli/prs.test.js.map +1 -0
  18. package/dist/cli/wt/interactive-menu.d.ts +2 -0
  19. package/dist/cli/wt/interactive-menu.d.ts.map +1 -1
  20. package/dist/cli/wt/interactive-menu.js +17 -0
  21. package/dist/cli/wt/interactive-menu.js.map +1 -1
  22. package/dist/cli/wt/interactive-menu.test.js +25 -0
  23. package/dist/cli/wt/interactive-menu.test.js.map +1 -1
  24. package/dist/cli/wt/prs.d.ts +21 -0
  25. package/dist/cli/wt/prs.d.ts.map +1 -0
  26. package/dist/cli/wt/prs.js +251 -0
  27. package/dist/cli/wt/prs.js.map +1 -0
  28. package/dist/cli/wt/prs.test.d.ts +5 -0
  29. package/dist/cli/wt/prs.test.d.ts.map +1 -0
  30. package/dist/cli/wt/prs.test.js +410 -0
  31. package/dist/cli/wt/prs.test.js.map +1 -0
  32. package/dist/cli/wt.d.ts +1 -0
  33. package/dist/cli/wt.d.ts.map +1 -1
  34. package/dist/cli/wt.js +5 -0
  35. package/dist/cli/wt.js.map +1 -1
  36. package/dist/cli/wtconfig.test.js +3 -0
  37. package/dist/cli/wtconfig.test.js.map +1 -1
  38. package/dist/cli/wtlink.d.ts +2 -1
  39. package/dist/cli/wtlink.d.ts.map +1 -1
  40. package/dist/cli/wtlink.js +41 -2
  41. package/dist/cli/wtlink.js.map +1 -1
  42. package/dist/e2e/cli.e2e.test.js +6 -2
  43. package/dist/e2e/cli.e2e.test.js.map +1 -1
  44. package/dist/e2e/helpers/cli-runner.d.ts.map +1 -1
  45. package/dist/e2e/helpers/cli-runner.js +1 -0
  46. package/dist/e2e/helpers/cli-runner.js.map +1 -1
  47. package/dist/e2e/helpers/gh-mock.d.ts.map +1 -1
  48. package/dist/e2e/helpers/gh-mock.js +55 -1
  49. package/dist/e2e/helpers/gh-mock.js.map +1 -1
  50. package/dist/e2e/helpers/pty-wrapper.d.ts +15 -0
  51. package/dist/e2e/helpers/pty-wrapper.d.ts.map +1 -1
  52. package/dist/e2e/helpers/pty-wrapper.js +65 -0
  53. package/dist/e2e/helpers/pty-wrapper.js.map +1 -1
  54. package/dist/e2e/newpr-full-flow.e2e.test.js +1 -0
  55. package/dist/e2e/newpr-full-flow.e2e.test.js.map +1 -1
  56. package/dist/e2e/prs/prs.e2e.test.d.ts +7 -0
  57. package/dist/e2e/prs/prs.e2e.test.d.ts.map +1 -0
  58. package/dist/e2e/prs/prs.e2e.test.js +606 -0
  59. package/dist/e2e/prs/prs.e2e.test.js.map +1 -0
  60. package/dist/e2e/wt/interactive-menu.e2e.test.d.ts +8 -0
  61. package/dist/e2e/wt/interactive-menu.e2e.test.d.ts.map +1 -0
  62. package/dist/e2e/wt/interactive-menu.e2e.test.js +583 -0
  63. package/dist/e2e/wt/interactive-menu.e2e.test.js.map +1 -0
  64. package/dist/e2e/wt/wt.e2e.test.js +217 -4
  65. package/dist/e2e/wt/wt.e2e.test.js.map +1 -1
  66. package/dist/integration/prs.integration.test.d.ts +8 -0
  67. package/dist/integration/prs.integration.test.d.ts.map +1 -0
  68. package/dist/integration/prs.integration.test.js +478 -0
  69. package/dist/integration/prs.integration.test.js.map +1 -0
  70. package/dist/lib/config-validation.d.ts.map +1 -1
  71. package/dist/lib/config-validation.js +56 -0
  72. package/dist/lib/config-validation.js.map +1 -1
  73. package/dist/lib/config.d.ts +34 -0
  74. package/dist/lib/config.d.ts.map +1 -1
  75. package/dist/lib/config.js +10 -0
  76. package/dist/lib/config.js.map +1 -1
  77. package/dist/lib/git.d.ts +32 -0
  78. package/dist/lib/git.d.ts.map +1 -1
  79. package/dist/lib/git.js +95 -1
  80. package/dist/lib/git.js.map +1 -1
  81. package/dist/lib/git.test.js +118 -1
  82. package/dist/lib/git.test.js.map +1 -1
  83. package/dist/lib/github.d.ts +41 -0
  84. package/dist/lib/github.d.ts.map +1 -1
  85. package/dist/lib/github.js +109 -0
  86. package/dist/lib/github.js.map +1 -1
  87. package/dist/lib/global-check.d.ts +2 -2
  88. package/dist/lib/global-check.js +3 -3
  89. package/dist/lib/global-check.js.map +1 -1
  90. package/dist/lib/global-check.test.js +3 -6
  91. package/dist/lib/global-check.test.js.map +1 -1
  92. package/dist/lib/json-output.d.ts +1 -0
  93. package/dist/lib/json-output.d.ts.map +1 -1
  94. package/dist/lib/json-output.js +2 -0
  95. package/dist/lib/json-output.js.map +1 -1
  96. package/dist/lib/lswt/action-executors.d.ts.map +1 -1
  97. package/dist/lib/lswt/action-executors.js +2 -1
  98. package/dist/lib/lswt/action-executors.js.map +1 -1
  99. package/dist/lib/lswt/action-executors.test.js +1 -0
  100. package/dist/lib/lswt/action-executors.test.js.map +1 -1
  101. package/dist/lib/lswt/actions.d.ts +1 -0
  102. package/dist/lib/lswt/actions.d.ts.map +1 -1
  103. package/dist/lib/lswt/actions.js +38 -10
  104. package/dist/lib/lswt/actions.js.map +1 -1
  105. package/dist/lib/lswt/actions.test.js +34 -22
  106. package/dist/lib/lswt/actions.test.js.map +1 -1
  107. package/dist/lib/lswt/interactive.js +8 -8
  108. package/dist/lib/lswt/interactive.js.map +1 -1
  109. package/dist/lib/prompts.d.ts.map +1 -1
  110. package/dist/lib/prompts.js +16 -12
  111. package/dist/lib/prompts.js.map +1 -1
  112. package/dist/lib/prs/actions.d.ts +70 -0
  113. package/dist/lib/prs/actions.d.ts.map +1 -0
  114. package/dist/lib/prs/actions.js +444 -0
  115. package/dist/lib/prs/actions.js.map +1 -0
  116. package/dist/lib/prs/actions.test.d.ts +5 -0
  117. package/dist/lib/prs/actions.test.d.ts.map +1 -0
  118. package/dist/lib/prs/actions.test.js +313 -0
  119. package/dist/lib/prs/actions.test.js.map +1 -0
  120. package/dist/lib/prs/data.d.ts +48 -0
  121. package/dist/lib/prs/data.d.ts.map +1 -0
  122. package/dist/lib/prs/data.js +171 -0
  123. package/dist/lib/prs/data.js.map +1 -0
  124. package/dist/lib/prs/data.test.d.ts +5 -0
  125. package/dist/lib/prs/data.test.d.ts.map +1 -0
  126. package/dist/lib/prs/data.test.js +417 -0
  127. package/dist/lib/prs/data.test.js.map +1 -0
  128. package/dist/lib/prs/details.d.ts +57 -0
  129. package/dist/lib/prs/details.d.ts.map +1 -0
  130. package/dist/lib/prs/details.js +246 -0
  131. package/dist/lib/prs/details.js.map +1 -0
  132. package/dist/lib/prs/details.test.d.ts +5 -0
  133. package/dist/lib/prs/details.test.d.ts.map +1 -0
  134. package/dist/lib/prs/details.test.js +325 -0
  135. package/dist/lib/prs/details.test.js.map +1 -0
  136. package/dist/lib/prs/filters.d.ts +56 -0
  137. package/dist/lib/prs/filters.d.ts.map +1 -0
  138. package/dist/lib/prs/filters.js +171 -0
  139. package/dist/lib/prs/filters.js.map +1 -0
  140. package/dist/lib/prs/filters.test.d.ts +5 -0
  141. package/dist/lib/prs/filters.test.d.ts.map +1 -0
  142. package/dist/lib/prs/filters.test.js +312 -0
  143. package/dist/lib/prs/filters.test.js.map +1 -0
  144. package/dist/lib/prs/formatters.d.ts +83 -0
  145. package/dist/lib/prs/formatters.d.ts.map +1 -0
  146. package/dist/lib/prs/formatters.js +389 -0
  147. package/dist/lib/prs/formatters.js.map +1 -0
  148. package/dist/lib/prs/formatters.test.d.ts +2 -0
  149. package/dist/lib/prs/formatters.test.d.ts.map +1 -0
  150. package/dist/lib/prs/formatters.test.js +387 -0
  151. package/dist/lib/prs/formatters.test.js.map +1 -0
  152. package/dist/lib/prs/interactive.d.ts +50 -0
  153. package/dist/lib/prs/interactive.d.ts.map +1 -0
  154. package/dist/lib/prs/interactive.js +453 -0
  155. package/dist/lib/prs/interactive.js.map +1 -0
  156. package/dist/lib/prs/interactive.test.d.ts +5 -0
  157. package/dist/lib/prs/interactive.test.d.ts.map +1 -0
  158. package/dist/lib/prs/interactive.test.js +364 -0
  159. package/dist/lib/prs/interactive.test.js.map +1 -0
  160. package/dist/lib/prs/types.d.ts +152 -0
  161. package/dist/lib/prs/types.d.ts.map +1 -0
  162. package/dist/lib/prs/types.js +17 -0
  163. package/dist/lib/prs/types.js.map +1 -0
  164. package/dist/lib/wtlink/config-manifest.d.ts +101 -0
  165. package/dist/lib/wtlink/config-manifest.d.ts.map +1 -0
  166. package/dist/lib/wtlink/config-manifest.js +219 -0
  167. package/dist/lib/wtlink/config-manifest.js.map +1 -0
  168. package/dist/lib/wtlink/config-manifest.test.d.ts +2 -0
  169. package/dist/lib/wtlink/config-manifest.test.d.ts.map +1 -0
  170. package/dist/lib/wtlink/config-manifest.test.js +460 -0
  171. package/dist/lib/wtlink/config-manifest.test.js.map +1 -0
  172. package/dist/lib/wtlink/link-configs.d.ts.map +1 -1
  173. package/dist/lib/wtlink/link-configs.js +36 -11
  174. package/dist/lib/wtlink/link-configs.js.map +1 -1
  175. package/dist/lib/wtlink/main-menu.d.ts.map +1 -1
  176. package/dist/lib/wtlink/main-menu.js +58 -50
  177. package/dist/lib/wtlink/main-menu.js.map +1 -1
  178. package/dist/lib/wtlink/main-menu.test.js +42 -40
  179. package/dist/lib/wtlink/main-menu.test.js.map +1 -1
  180. package/dist/lib/wtlink/manage-manifest.d.ts +9 -0
  181. package/dist/lib/wtlink/manage-manifest.d.ts.map +1 -1
  182. package/dist/lib/wtlink/manage-manifest.js +346 -25
  183. package/dist/lib/wtlink/manage-manifest.js.map +1 -1
  184. package/dist/lib/wtlink/manage-manifest.test.js +52 -1
  185. package/dist/lib/wtlink/manage-manifest.test.js.map +1 -1
  186. package/dist/lib/wtlink/validate-manifest.d.ts.map +1 -1
  187. package/dist/lib/wtlink/validate-manifest.js +27 -6
  188. package/dist/lib/wtlink/validate-manifest.js.map +1 -1
  189. package/package.json +1 -1
  190. package/schemas/worktreerc.schema.json +26 -1
@@ -7,6 +7,8 @@ import * as colors from '../colors.js';
7
7
  import * as git from '../git.js';
8
8
  import * as readline from 'readline';
9
9
  import { signal, computed } from '@preact/signals-core';
10
+ import { DEFAULT_MANIFEST_FILE } from '../constants.js';
11
+ import { loadManifestData, saveManifestData } from './config-manifest.js';
10
12
  // ============================================================================
11
13
  // PURE FUNCTIONS - State computation and derivation
12
14
  // ============================================================================
@@ -89,6 +91,22 @@ export function isItemVisible(item, activeFilters) {
89
91
  }
90
92
  return false;
91
93
  }
94
+ /**
95
+ * Execute vim command and return action
96
+ * Parses vim-style commands like :w, :q, :wq, :x, :q!
97
+ */
98
+ export function executeVimCommand(cmd) {
99
+ const trimmed = cmd.trim().toLowerCase();
100
+ if (trimmed === 'q')
101
+ return 'quit';
102
+ if (trimmed === 'q!')
103
+ return 'force-quit';
104
+ if (trimmed === 'w')
105
+ return 'save';
106
+ if (trimmed === 'wq' || trimmed === 'x')
107
+ return 'save-quit';
108
+ return null; // Unknown command
109
+ }
92
110
  /**
93
111
  * Build file tree from flat list of file paths
94
112
  */
@@ -550,6 +568,23 @@ function renderHelp() {
550
568
  console.log(colors.cyan('║') +
551
569
  ' ' +
552
570
  colors.cyan('║'));
571
+ console.log(colors.cyan('║') +
572
+ ' ' +
573
+ colors.bold('Navigation:') +
574
+ ' ' +
575
+ colors.cyan('║'));
576
+ console.log(colors.cyan('║') +
577
+ ' ↑/↓ or j/k = Navigate up/down ' +
578
+ colors.cyan('║'));
579
+ console.log(colors.cyan('║') +
580
+ ' ←/Esc = Go back (or exit at root) ' +
581
+ colors.cyan('║'));
582
+ console.log(colors.cyan('║') +
583
+ ' →/Enter = Drill into folder ' +
584
+ colors.cyan('║'));
585
+ console.log(colors.cyan('║') +
586
+ ' ' +
587
+ colors.cyan('║'));
553
588
  console.log(colors.cyan('║') +
554
589
  ' ' +
555
590
  colors.bold('View Toggles:') +
@@ -579,10 +614,33 @@ function renderHelp() {
579
614
  ' ' +
580
615
  colors.cyan('║'));
581
616
  console.log(colors.cyan('║') +
582
- ' Q = Save changes and quit ' +
617
+ ' Q = Quit (confirms if unsaved changes) ' +
583
618
  colors.cyan('║'));
584
619
  console.log(colors.cyan('║') +
585
- ' X = Cancel without saving (Ctrl+C also works) ' +
620
+ ' Ctrl+C = Force quit without saving ' +
621
+ colors.cyan('║'));
622
+ console.log(colors.cyan('║') +
623
+ ' ' +
624
+ colors.cyan('║'));
625
+ console.log(colors.cyan('║') +
626
+ ' ' +
627
+ colors.bold('Vim Commands:') +
628
+ ' ' +
629
+ colors.cyan('║'));
630
+ console.log(colors.cyan('║') +
631
+ ' : = Enter command mode ' +
632
+ colors.cyan('║'));
633
+ console.log(colors.cyan('║') +
634
+ ' :w = Save changes (stage for exit) ' +
635
+ colors.cyan('║'));
636
+ console.log(colors.cyan('║') +
637
+ ' :q = Quit (confirms if unsaved) ' +
638
+ colors.cyan('║'));
639
+ console.log(colors.cyan('║') +
640
+ ' :wq/:x = Save and quit ' +
641
+ colors.cyan('║'));
642
+ console.log(colors.cyan('║') +
643
+ ' :q! = Force quit without saving ' +
586
644
  colors.cyan('║'));
587
645
  console.log(colors.cyan('║') +
588
646
  ' ' +
@@ -703,11 +761,14 @@ function renderFooter(state) {
703
761
  console.log('\n' + colors.dim('─'.repeat(110)));
704
762
  // Navigation - spread out with better formatting
705
763
  const navParts = [];
706
- navParts.push(colors.dim('↑↓') + ' Select');
764
+ navParts.push(colors.dim('↑↓/jk') + ' Navigate');
707
765
  if (state.viewMode === 'hierarchical') {
708
- navParts.push(colors.dim('') + ' Back');
766
+ navParts.push(colors.dim('←/esc') + ' Back');
709
767
  navParts.push(colors.dim('→') + ' Drill In');
710
768
  }
769
+ else {
770
+ navParts.push(colors.dim('esc') + ' Exit');
771
+ }
711
772
  const navigation = colors.bold('Navigation: ') + navParts.join(colors.dim(' │ '));
712
773
  // Actions - colorful and spaced out
713
774
  const actions = colors.bold('Actions: ') +
@@ -730,16 +791,16 @@ function renderFooter(state) {
730
791
  ' Skipped');
731
792
  filterParts.push((state.viewMode === 'flat' ? colors.bold(colors.cyan('V')) : colors.dim('V')) + ' Flat');
732
793
  const filters = colors.bold('View: ') + filterParts.join(colors.dim(' │ '));
733
- // Controls - help and exit
794
+ // Controls - help, exit, and vim
734
795
  const controls = colors.bold('Controls: ') +
735
796
  colors.bold('?') +
736
797
  ' Help' +
737
798
  colors.dim(' │ ') +
738
- colors.bold(colors.red('Q')) +
739
- ' Save & Quit' +
799
+ colors.bold(':') +
800
+ ' Vim' +
740
801
  colors.dim(' │ ') +
741
- colors.bold(colors.red('X')) +
742
- ' Cancel';
802
+ colors.bold(colors.red('Q')) +
803
+ ' Quit';
743
804
  console.log(' ' + filters + colors.dim(' ') + controls);
744
805
  }
745
806
  function render(state, allFiles, gitRoot, cachedDisplayItems) {
@@ -773,6 +834,9 @@ async function interactiveManage(allFiles, gitRoot, initialDecisions) {
773
834
  const navigationStack$ = signal([]);
774
835
  const cursorIndex$ = signal(0);
775
836
  const scrollOffset$ = signal(0);
837
+ // Vim command mode state
838
+ const commandMode$ = signal(false);
839
+ const commandBuffer$ = signal('');
776
840
  // Computed signal for visible items (cached, only recomputes when dependencies change)
777
841
  // IMPORTANT: Only read signals that getVisibleItems() actually uses!
778
842
  const visibleItems$ = computed(() => {
@@ -862,17 +926,184 @@ async function interactiveManage(allFiles, gitRoot, initialDecisions) {
862
926
  }
863
927
  process.stdin.removeListener('keypress', onKeypress);
864
928
  };
929
+ // Render command line at the bottom of the screen (vim command mode)
930
+ const renderCommandLine = () => {
931
+ // Clear the screen and re-render everything including the command line
932
+ const displayItems = displayItems$.value;
933
+ const state = buildState();
934
+ console.clear();
935
+ renderStatusHeader(state, allFiles, gitRoot);
936
+ const selectedItem = displayItems[cursorIndex$.value];
937
+ renderActionHint(selectedItem, state);
938
+ renderItems(state, displayItems);
939
+ // Custom footer showing command mode
940
+ console.log('\n' + colors.dim('─'.repeat(110)));
941
+ console.log('');
942
+ console.log(colors.bold(' :') + commandBuffer$.value + colors.bold('█'));
943
+ };
944
+ // Handle save operation
945
+ const handleSave = () => {
946
+ // For now, just show a message - the actual save happens on exit
947
+ console.clear();
948
+ console.log(colors.green('✓ Changes staged (will be saved on exit)'));
949
+ setTimeout(() => {
950
+ renderCurrent();
951
+ }, 800);
952
+ return true;
953
+ };
954
+ // Track initial state for detecting unsaved changes
955
+ const initialDecisions = new Map(decisions$.value);
956
+ // Handle quit with confirmation dialog
957
+ const handleQuitWithConfirmation = () => {
958
+ const hasChanges = decisions$.value.size !== initialDecisions.size ||
959
+ Array.from(decisions$.value.entries()).some(([k, v]) => initialDecisions.get(k) !== v);
960
+ if (!hasChanges) {
961
+ console.clear();
962
+ console.log(colors.dim('Exited - no changes made'));
963
+ cleanup();
964
+ resolve(decisions$.value);
965
+ return;
966
+ }
967
+ // Count changes
968
+ let changeCount = 0;
969
+ for (const [key, value] of decisions$.value.entries()) {
970
+ if (initialDecisions.get(key) !== value) {
971
+ changeCount++;
972
+ }
973
+ }
974
+ for (const key of initialDecisions.keys()) {
975
+ if (!decisions$.value.has(key)) {
976
+ changeCount++;
977
+ }
978
+ }
979
+ // Show confirmation dialog
980
+ console.clear();
981
+ console.log('');
982
+ console.log(colors.bold(colors.cyan(' ╔═══════════════════════════════════════════════════════╗')));
983
+ console.log(colors.cyan(' ║') +
984
+ colors.bold(` Save ${changeCount} change${changeCount === 1 ? '' : 's'} before exiting?`) +
985
+ ' '.slice(0, 24 - changeCount.toString().length) +
986
+ colors.cyan('║'));
987
+ console.log(colors.cyan(' ║') +
988
+ ' ' +
989
+ colors.cyan('║'));
990
+ console.log(colors.cyan(' ║') +
991
+ ' ' +
992
+ colors.bold(colors.green('[Y]')) +
993
+ ' Save & exit ' +
994
+ colors.bold(colors.red('[N]')) +
995
+ ' Discard ' +
996
+ colors.bold('[Esc]') +
997
+ ' Cancel ' +
998
+ colors.cyan('║'));
999
+ console.log(colors.bold(colors.cyan(' ╚═══════════════════════════════════════════════════════╝')));
1000
+ console.log('');
1001
+ // Temporarily remove main keypress handler
1002
+ process.stdin.removeListener('keypress', onKeypress);
1003
+ // Handle confirmation dialog keys
1004
+ const onConfirmKeypress = (confirmStr, confirmKey) => {
1005
+ if (confirmStr === 'y' || confirmStr === 'Y') {
1006
+ process.stdin.removeListener('keypress', onConfirmKeypress);
1007
+ console.clear();
1008
+ console.log(colors.green('✓ Saving decisions...'));
1009
+ cleanup();
1010
+ resolve(decisions$.value);
1011
+ }
1012
+ else if (confirmStr === 'n' || confirmStr === 'N') {
1013
+ process.stdin.removeListener('keypress', onConfirmKeypress);
1014
+ console.clear();
1015
+ console.log(colors.yellow('✗ Discarded changes'));
1016
+ cleanup();
1017
+ resolve(initialDecisions);
1018
+ }
1019
+ else if (confirmKey.name === 'escape') {
1020
+ // Cancel - restore main handler
1021
+ process.stdin.removeListener('keypress', onConfirmKeypress);
1022
+ process.stdin.on('keypress', onKeypress);
1023
+ renderCurrent();
1024
+ }
1025
+ else if (confirmKey.ctrl && confirmKey.name === 'c') {
1026
+ process.stdin.removeListener('keypress', onConfirmKeypress);
1027
+ console.clear();
1028
+ console.log(colors.yellow('✗ Cancelled'));
1029
+ cleanup();
1030
+ resolve(initialDecisions);
1031
+ }
1032
+ };
1033
+ process.stdin.on('keypress', onConfirmKeypress);
1034
+ };
865
1035
  const onKeypress = (str, key) => {
866
1036
  const displayItems = displayItems$.value; // Cached!
867
- if (key.name === 'up') {
1037
+ // Handle vim command mode
1038
+ if (commandMode$.value) {
1039
+ if (key.name === 'escape') {
1040
+ // Exit command mode without executing
1041
+ commandMode$.value = false;
1042
+ commandBuffer$.value = '';
1043
+ renderCurrent();
1044
+ }
1045
+ else if (key.name === 'return') {
1046
+ // Execute the command
1047
+ const action = executeVimCommand(commandBuffer$.value);
1048
+ commandMode$.value = false;
1049
+ commandBuffer$.value = '';
1050
+ if (action === 'save') {
1051
+ handleSave();
1052
+ }
1053
+ else if (action === 'quit') {
1054
+ handleQuitWithConfirmation();
1055
+ }
1056
+ else if (action === 'force-quit') {
1057
+ console.clear();
1058
+ console.log(colors.yellow('✗ Force quit - discarded changes'));
1059
+ cleanup();
1060
+ resolve(initialDecisions);
1061
+ }
1062
+ else if (action === 'save-quit') {
1063
+ console.clear();
1064
+ console.log(colors.green('✓ Saving decisions...'));
1065
+ cleanup();
1066
+ resolve(decisions$.value);
1067
+ }
1068
+ else {
1069
+ // Unknown command - show error briefly
1070
+ console.clear();
1071
+ console.log(colors.red(`Unknown command: :${commandBuffer$.value}`));
1072
+ setTimeout(() => {
1073
+ renderCurrent();
1074
+ }, 800);
1075
+ }
1076
+ }
1077
+ else if (key.name === 'backspace') {
1078
+ commandBuffer$.value = commandBuffer$.value.slice(0, -1);
1079
+ renderCommandLine();
1080
+ }
1081
+ else if (str && str.length === 1 && !key.ctrl) {
1082
+ // Add character to command buffer
1083
+ commandBuffer$.value += str;
1084
+ renderCommandLine();
1085
+ }
1086
+ return;
1087
+ }
1088
+ // Enter vim command mode
1089
+ if (str === ':') {
1090
+ commandMode$.value = true;
1091
+ commandBuffer$.value = '';
1092
+ renderCommandLine();
1093
+ return;
1094
+ }
1095
+ // Navigation: up arrow or k
1096
+ if (key.name === 'up' || str === 'k') {
868
1097
  const newIndex = Math.max(0, cursorIndex$.value - 1);
869
1098
  cursorIndex$.value = newIndex;
870
1099
  renderCurrent();
1100
+ // Navigation: down arrow or j
871
1101
  }
872
- else if (key.name === 'down') {
1102
+ else if (key.name === 'down' || str === 'j') {
873
1103
  const newIndex = Math.min(displayItems.length - 1, cursorIndex$.value + 1);
874
1104
  cursorIndex$.value = newIndex;
875
1105
  renderCurrent();
1106
+ // Navigate into (right arrow)
876
1107
  }
877
1108
  else if (key.name === 'right') {
878
1109
  if (viewMode$.value === 'flat')
@@ -893,10 +1124,45 @@ async function interactiveManage(allFiles, gitRoot, initialDecisions) {
893
1124
  scrollOffset$.value = 0;
894
1125
  }
895
1126
  renderCurrent();
1127
+ // Navigate back (left arrow or Esc)
896
1128
  }
897
- else if (key.name === 'left') {
898
- if (viewMode$.value === 'flat' || navigationStack$.value.length === 0)
1129
+ else if (key.name === 'left' || key.name === 'escape') {
1130
+ // In flat view, Esc exits
1131
+ if (viewMode$.value === 'flat') {
1132
+ if (key.name === 'escape') {
1133
+ // Esc in flat view - check for unsaved changes
1134
+ const hasChanges = decisions$.value.size !== initialDecisions.size ||
1135
+ Array.from(decisions$.value.entries()).some(([k, v]) => initialDecisions.get(k) !== v);
1136
+ if (hasChanges) {
1137
+ handleQuitWithConfirmation();
1138
+ }
1139
+ else {
1140
+ console.clear();
1141
+ console.log(colors.dim('Exited - no changes made'));
1142
+ cleanup();
1143
+ resolve(decisions$.value);
1144
+ }
1145
+ }
1146
+ return;
1147
+ }
1148
+ // In hierarchical view, go back or exit at root
1149
+ if (navigationStack$.value.length === 0) {
1150
+ if (key.name === 'escape') {
1151
+ // Esc at root - check for unsaved changes
1152
+ const hasChanges = decisions$.value.size !== initialDecisions.size ||
1153
+ Array.from(decisions$.value.entries()).some(([k, v]) => initialDecisions.get(k) !== v);
1154
+ if (hasChanges) {
1155
+ handleQuitWithConfirmation();
1156
+ }
1157
+ else {
1158
+ console.clear();
1159
+ console.log(colors.dim('Exited - no changes made'));
1160
+ cleanup();
1161
+ resolve(decisions$.value);
1162
+ }
1163
+ }
899
1164
  return;
1165
+ }
900
1166
  const stack = navigationStack$.value;
901
1167
  navigationStack$.value = stack.slice(0, -1);
902
1168
  cursorIndex$.value = 0;
@@ -953,18 +1219,28 @@ async function interactiveManage(allFiles, gitRoot, initialDecisions) {
953
1219
  decisions$.value = newDecisions;
954
1220
  renderCurrent();
955
1221
  }
1222
+ // Quit: q key - show confirmation if there are changes
956
1223
  }
957
1224
  else if (str === 'q' || str === 'Q') {
1225
+ handleQuitWithConfirmation();
1226
+ // Cancel: x key (deprecated) or Ctrl+C
1227
+ }
1228
+ else if (str === 'x' || str === 'X') {
1229
+ // Deprecated: show notice and handle as Esc
958
1230
  console.clear();
959
- console.log(colors.green(' Saving decisions...'));
960
- cleanup();
961
- resolve(decisions$.value);
1231
+ console.log(colors.yellow('Note: x key is deprecated. Use Esc to go back.'));
1232
+ setTimeout(() => {
1233
+ process.stdin.on('keypress', onKeypress);
1234
+ renderCurrent();
1235
+ }, 1500);
1236
+ process.stdin.removeListener('keypress', onKeypress);
962
1237
  }
963
- else if (str === 'x' || str === 'X' || (key.ctrl && key.name === 'c')) {
1238
+ else if (key.ctrl && key.name === 'c') {
1239
+ // Force quit without confirmation
964
1240
  console.clear();
965
- console.log(colors.yellow('✗ Cancelled - no changes saved'));
1241
+ console.log(colors.yellow('✗ Cancelled'));
966
1242
  cleanup();
967
- resolve(new Map());
1243
+ resolve(initialDecisions);
968
1244
  }
969
1245
  else if (str === '?') {
970
1246
  showHelp$.value = !showHelp$.value;
@@ -1069,6 +1345,12 @@ function getIgnoredFiles(gitRoot, manifestFile) {
1069
1345
  }
1070
1346
  }
1071
1347
  function getManifestEntries(gitRoot, manifestFile) {
1348
+ // Use config-manifest adapter for default manifest file
1349
+ if (manifestFile === DEFAULT_MANIFEST_FILE) {
1350
+ const data = loadManifestData(gitRoot);
1351
+ return data.enabled;
1352
+ }
1353
+ // Legacy behavior for custom manifest files
1072
1354
  const manifestPath = path.join(gitRoot, manifestFile);
1073
1355
  if (!fs.existsSync(manifestPath))
1074
1356
  return [];
@@ -1078,8 +1360,20 @@ function getManifestEntries(gitRoot, manifestFile) {
1078
1360
  .filter((x) => x.trim() && !x.startsWith('#'));
1079
1361
  }
1080
1362
  function getManifestDecisions(gitRoot, manifestFile) {
1081
- const manifestPath = path.join(gitRoot, manifestFile);
1082
1363
  const decisions = new Map();
1364
+ // Use config-manifest adapter for default manifest file
1365
+ if (manifestFile === DEFAULT_MANIFEST_FILE) {
1366
+ const data = loadManifestData(gitRoot);
1367
+ for (const file of data.enabled) {
1368
+ decisions.set(file, 'add');
1369
+ }
1370
+ for (const file of data.disabled) {
1371
+ decisions.set(file, 'comment');
1372
+ }
1373
+ return decisions;
1374
+ }
1375
+ // Legacy behavior for custom manifest files
1376
+ const manifestPath = path.join(gitRoot, manifestFile);
1083
1377
  if (!fs.existsSync(manifestPath))
1084
1378
  return decisions;
1085
1379
  const lines = fs.readFileSync(manifestPath, 'utf-8').split('\n');
@@ -1317,12 +1611,39 @@ export async function run(argv) {
1317
1611
  }
1318
1612
  }
1319
1613
  else {
1320
- if (argv.backup && fs.existsSync(manifestPath)) {
1321
- fs.copyFileSync(manifestPath, manifestBackupFile);
1322
- console.log(`Backed up existing manifest to ${manifestBackupFile}`);
1614
+ // Determine if using default manifest file (config-based) or custom file (legacy)
1615
+ const usingConfigManifest = manifestFile === DEFAULT_MANIFEST_FILE;
1616
+ if (usingConfigManifest) {
1617
+ // Convert finalEntries to enabled/disabled arrays for config-based storage
1618
+ const enabled = [];
1619
+ const disabled = [];
1620
+ for (const entry of finalEntries) {
1621
+ if (entry.startsWith('#')) {
1622
+ // Extract file path from commented entry (handles various prefixes)
1623
+ const commentContent = entry.substring(1).trim();
1624
+ let filePath = commentContent;
1625
+ const prefixMatch = commentContent.match(/^(TRACKED|DELETED|STALE):\s*(.+)/);
1626
+ if (prefixMatch) {
1627
+ filePath = prefixMatch[2];
1628
+ }
1629
+ disabled.push(filePath);
1630
+ }
1631
+ else {
1632
+ enabled.push(entry);
1633
+ }
1634
+ }
1635
+ saveManifestData(mainWorktreeRoot, enabled, disabled);
1636
+ console.log(colors.green(`Successfully updated wtlink config in .worktreerc.`));
1637
+ }
1638
+ else {
1639
+ // Legacy behavior for custom manifest files
1640
+ if (argv.backup && fs.existsSync(manifestPath)) {
1641
+ fs.copyFileSync(manifestPath, manifestBackupFile);
1642
+ console.log(`Backed up existing manifest to ${manifestBackupFile}`);
1643
+ }
1644
+ fs.writeFileSync(manifestPath, finalEntries.join('\n') + '\n');
1645
+ console.log(colors.green(`Successfully updated ${manifestFile}.`));
1323
1646
  }
1324
- fs.writeFileSync(manifestPath, finalEntries.join('\n') + '\n');
1325
- console.log(colors.green(`Successfully updated ${manifestFile}.`));
1326
1647
  }
1327
1648
  }
1328
1649
  //# sourceMappingURL=manage-manifest.js.map