@shepai/cli 1.142.0 → 1.142.1-pr455.16bd4d2

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 (174) hide show
  1. package/dist/src/presentation/cli/commands/feat/ls.command.d.ts +44 -2
  2. package/dist/src/presentation/cli/commands/feat/ls.command.d.ts.map +1 -1
  3. package/dist/src/presentation/cli/commands/feat/ls.command.js +182 -82
  4. package/dist/src/presentation/web/app/api/sessions/route.d.ts.map +1 -1
  5. package/dist/src/presentation/web/app/api/sessions/route.js +45 -13
  6. package/dist/src/presentation/web/components/common/feature-node/feature-node.d.ts.map +1 -1
  7. package/dist/src/presentation/web/components/common/feature-node/feature-node.js +1 -1
  8. package/dist/src/presentation/web/components/common/feature-node/feature-sessions-dropdown.d.ts +3 -1
  9. package/dist/src/presentation/web/components/common/feature-node/feature-sessions-dropdown.d.ts.map +1 -1
  10. package/dist/src/presentation/web/components/common/feature-node/feature-sessions-dropdown.js +13 -5
  11. package/dist/src/presentation/web/components/common/repository-node/repository-node.d.ts.map +1 -1
  12. package/dist/src/presentation/web/components/common/repository-node/repository-node.js +1 -1
  13. package/dist/tsconfig.build.tsbuildinfo +1 -1
  14. package/package.json +1 -1
  15. package/web/.next/BUILD_ID +1 -1
  16. package/web/.next/build-manifest.json +2 -2
  17. package/web/.next/fallback-build-manifest.json +2 -2
  18. package/web/.next/prerender-manifest.json +3 -3
  19. package/web/.next/required-server-files.js +3 -3
  20. package/web/.next/required-server-files.json +3 -3
  21. package/web/.next/server/app/(dashboard)/@drawer/adopt/page/server-reference-manifest.json +28 -28
  22. package/web/.next/server/app/(dashboard)/@drawer/adopt/page.js +1 -1
  23. package/web/.next/server/app/(dashboard)/@drawer/adopt/page.js.nft.json +1 -1
  24. package/web/.next/server/app/(dashboard)/@drawer/adopt/page_client-reference-manifest.js +1 -1
  25. package/web/.next/server/app/(dashboard)/@drawer/create/page/server-reference-manifest.json +28 -28
  26. package/web/.next/server/app/(dashboard)/@drawer/create/page.js +1 -1
  27. package/web/.next/server/app/(dashboard)/@drawer/create/page.js.nft.json +1 -1
  28. package/web/.next/server/app/(dashboard)/@drawer/create/page_client-reference-manifest.js +1 -1
  29. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page/server-reference-manifest.json +36 -36
  30. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page.js +1 -1
  31. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page.js.nft.json +1 -1
  32. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page_client-reference-manifest.js +1 -1
  33. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page/server-reference-manifest.json +36 -36
  34. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js +1 -1
  35. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js.nft.json +1 -1
  36. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page_client-reference-manifest.js +1 -1
  37. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page/server-reference-manifest.json +26 -26
  38. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js +1 -1
  39. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js.nft.json +1 -1
  40. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  41. package/web/.next/server/app/(dashboard)/create/page/server-reference-manifest.json +28 -28
  42. package/web/.next/server/app/(dashboard)/create/page.js +1 -1
  43. package/web/.next/server/app/(dashboard)/create/page.js.nft.json +1 -1
  44. package/web/.next/server/app/(dashboard)/create/page_client-reference-manifest.js +1 -1
  45. package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page/server-reference-manifest.json +36 -36
  46. package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page.js +1 -1
  47. package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page.js.nft.json +1 -1
  48. package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page_client-reference-manifest.js +1 -1
  49. package/web/.next/server/app/(dashboard)/feature/[featureId]/page/server-reference-manifest.json +36 -36
  50. package/web/.next/server/app/(dashboard)/feature/[featureId]/page.js +1 -1
  51. package/web/.next/server/app/(dashboard)/feature/[featureId]/page.js.nft.json +1 -1
  52. package/web/.next/server/app/(dashboard)/feature/[featureId]/page_client-reference-manifest.js +1 -1
  53. package/web/.next/server/app/(dashboard)/page/server-reference-manifest.json +26 -26
  54. package/web/.next/server/app/(dashboard)/page.js +1 -1
  55. package/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
  56. package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
  57. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page/server-reference-manifest.json +26 -26
  58. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js +1 -1
  59. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js.nft.json +1 -1
  60. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  61. package/web/.next/server/app/_global-error.html +2 -2
  62. package/web/.next/server/app/_global-error.rsc +1 -1
  63. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  64. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  65. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  66. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  67. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  68. package/web/.next/server/app/_not-found/page/server-reference-manifest.json +3 -3
  69. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  70. package/web/.next/server/app/api/sessions/route.js +3 -2
  71. package/web/.next/server/app/api/sessions/route.js.nft.json +1 -1
  72. package/web/.next/server/app/settings/page/server-reference-manifest.json +8 -8
  73. package/web/.next/server/app/settings/page.js.nft.json +1 -1
  74. package/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  75. package/web/.next/server/app/skills/page/server-reference-manifest.json +8 -8
  76. package/web/.next/server/app/skills/page.js +1 -1
  77. package/web/.next/server/app/skills/page.js.nft.json +1 -1
  78. package/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  79. package/web/.next/server/app/tools/page/server-reference-manifest.json +8 -8
  80. package/web/.next/server/app/tools/page.js +1 -1
  81. package/web/.next/server/app/tools/page.js.nft.json +1 -1
  82. package/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
  83. package/web/.next/server/app/version/page/server-reference-manifest.json +3 -3
  84. package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  85. package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_ff60e4a5.js +3 -0
  86. package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_ff60e4a5.js.map +1 -0
  87. package/web/.next/server/chunks/[externals]__448264a3._.js +3 -0
  88. package/web/.next/server/chunks/[externals]__448264a3._.js.map +1 -0
  89. package/web/.next/server/chunks/[root-of-the-server]__a402b567._.js +1 -1
  90. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js +1 -1
  91. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js.map +1 -1
  92. package/web/.next/server/chunks/ssr/{[root-of-the-server]__6ec59045._.js → [root-of-the-server]__0b150ddf._.js} +2 -2
  93. package/web/.next/server/chunks/ssr/{[root-of-the-server]__6ec59045._.js.map → [root-of-the-server]__0b150ddf._.js.map} +1 -1
  94. package/web/.next/server/chunks/ssr/{[root-of-the-server]__a5f9c6e5._.js → [root-of-the-server]__2138fa7e._.js} +3 -3
  95. package/web/.next/server/chunks/ssr/{[root-of-the-server]__a5f9c6e5._.js.map → [root-of-the-server]__2138fa7e._.js.map} +1 -1
  96. package/web/.next/server/chunks/ssr/[root-of-the-server]__29580090._.js +1 -1
  97. package/web/.next/server/chunks/ssr/[root-of-the-server]__29580090._.js.map +1 -1
  98. package/web/.next/server/chunks/ssr/[root-of-the-server]__357d99f9._.js +1 -1
  99. package/web/.next/server/chunks/ssr/[root-of-the-server]__3ef34e4c._.js +1 -1
  100. package/web/.next/server/chunks/ssr/[root-of-the-server]__43f51aa6._.js +1 -1
  101. package/web/.next/server/chunks/ssr/[root-of-the-server]__43f51aa6._.js.map +1 -1
  102. package/web/.next/server/chunks/ssr/[root-of-the-server]__815546bd._.js +1 -1
  103. package/web/.next/server/chunks/ssr/[root-of-the-server]__815546bd._.js.map +1 -1
  104. package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js +2 -2
  105. package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js.map +1 -1
  106. package/web/.next/server/chunks/ssr/[root-of-the-server]__c094882b._.js +1 -1
  107. package/web/.next/server/chunks/ssr/[root-of-the-server]__c094882b._.js.map +1 -1
  108. package/web/.next/server/chunks/ssr/[root-of-the-server]__d48c5b11._.js +1 -1
  109. package/web/.next/server/chunks/ssr/[root-of-the-server]__d48c5b11._.js.map +1 -1
  110. package/web/.next/server/chunks/ssr/[root-of-the-server]__dac5dbf1._.js +1 -1
  111. package/web/.next/server/chunks/ssr/[root-of-the-server]__dac5dbf1._.js.map +1 -1
  112. package/web/.next/server/chunks/ssr/[root-of-the-server]__fae8b355._.js +1 -1
  113. package/web/.next/server/chunks/ssr/[root-of-the-server]__fae8b355._.js.map +1 -1
  114. package/web/.next/server/chunks/ssr/_0c5f56e3._.js +2 -2
  115. package/web/.next/server/chunks/ssr/_0c5f56e3._.js.map +1 -1
  116. package/web/.next/server/chunks/ssr/_1b719e7f._.js +1 -1
  117. package/web/.next/server/chunks/ssr/_1b719e7f._.js.map +1 -1
  118. package/web/.next/server/chunks/ssr/_37e8548b._.js +1 -1
  119. package/web/.next/server/chunks/ssr/_37e8548b._.js.map +1 -1
  120. package/web/.next/server/chunks/ssr/_55d763e2._.js +1 -1
  121. package/web/.next/server/chunks/ssr/_55d763e2._.js.map +1 -1
  122. package/web/.next/server/chunks/ssr/_6256a985._.js +1 -1
  123. package/web/.next/server/chunks/ssr/_6256a985._.js.map +1 -1
  124. package/web/.next/server/chunks/ssr/_64bdfc6f._.js +2 -2
  125. package/web/.next/server/chunks/ssr/_64bdfc6f._.js.map +1 -1
  126. package/web/.next/server/chunks/ssr/_7dca1882._.js +1 -1
  127. package/web/.next/server/chunks/ssr/_7dca1882._.js.map +1 -1
  128. package/web/.next/server/chunks/ssr/_a9f57758._.js +1 -1
  129. package/web/.next/server/chunks/ssr/_b71645b4._.js +1 -1
  130. package/web/.next/server/chunks/ssr/_b71645b4._.js.map +1 -1
  131. package/web/.next/server/chunks/ssr/{_7e5c4d9c._.js → _c2b8e868._.js} +2 -2
  132. package/web/.next/server/chunks/ssr/{_7e5c4d9c._.js.map → _c2b8e868._.js.map} +1 -1
  133. package/web/.next/server/chunks/ssr/{_1c6fbecf._.js → _c904beb8._.js} +2 -2
  134. package/web/.next/server/chunks/ssr/{_1c6fbecf._.js.map → _c904beb8._.js.map} +1 -1
  135. package/web/.next/server/chunks/ssr/_d8575088._.js +1 -1
  136. package/web/.next/server/chunks/ssr/_d8575088._.js.map +1 -1
  137. package/web/.next/server/chunks/ssr/_f39a1adb._.js +1 -1
  138. package/web/.next/server/chunks/ssr/_f39a1adb._.js.map +1 -1
  139. package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js +1 -1
  140. package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js.map +1 -1
  141. package/web/.next/server/chunks/ssr/{src_presentation_web_7bda2f00._.js → src_presentation_web_8207e252._.js} +2 -2
  142. package/web/.next/server/chunks/ssr/{src_presentation_web_7bda2f00._.js.map → src_presentation_web_8207e252._.js.map} +1 -1
  143. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1b176e3c.js +1 -1
  144. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1b176e3c.js.map +1 -1
  145. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_bd9f0dda.js +1 -1
  146. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_bd9f0dda.js.map +1 -1
  147. package/web/.next/server/chunks/ssr/src_presentation_web_app_actions_open-ide_ts_baaca5d5._.js +1 -1
  148. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
  149. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js.map +1 -1
  150. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js +1 -1
  151. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js.map +1 -1
  152. package/web/.next/server/pages/500.html +2 -2
  153. package/web/.next/server/server-reference-manifest.js +1 -1
  154. package/web/.next/server/server-reference-manifest.json +44 -44
  155. package/web/.next/static/chunks/{13978ecd00a81172.js → 0a6be91d8d53e3fd.js} +1 -1
  156. package/web/.next/static/chunks/{74eef53714ab461d.js → 12e3ff09fc17f052.js} +1 -1
  157. package/web/.next/static/chunks/1330778ac8908e69.js +1 -0
  158. package/web/.next/static/chunks/{2eec9756f5220e22.js → 3aa226c0e32ce07c.js} +1 -1
  159. package/web/.next/static/chunks/{e599050b1f6466fe.js → 65085b062e182aa4.js} +1 -1
  160. package/web/.next/static/chunks/{4d7e25ef4b8e2f62.js → 6eb17d2ce8a9aa4f.js} +1 -1
  161. package/web/.next/static/chunks/{21ddf1d59c98adf0.js → 80b1513ba8f15d69.js} +1 -1
  162. package/web/.next/static/chunks/{73889aad2b3356b1.js → 9a16838aae04976b.js} +2 -2
  163. package/web/.next/static/chunks/{4adda8dd11458a46.js → 9c2e38efcd95fe72.js} +2 -2
  164. package/web/.next/static/chunks/{ea9969f79b2162ec.js → a7f51b1734e55a52.js} +1 -1
  165. package/web/.next/static/chunks/b0a40414ed362492.js +1 -0
  166. package/web/.next/static/chunks/b88100de43de46a4.js +1 -0
  167. package/web/.next/server/chunks/[root-of-the-server]__a62bd945._.js +0 -3
  168. package/web/.next/server/chunks/[root-of-the-server]__a62bd945._.js.map +0 -1
  169. package/web/.next/static/chunks/3f18dfdd07d48ecd.js +0 -1
  170. package/web/.next/static/chunks/6ad8d8682f4836c9.js +0 -1
  171. package/web/.next/static/chunks/a6a6a8387bae31f0.js +0 -1
  172. /package/web/.next/static/{G2BdtwIuunZEcXQ0FjJJF → rDu9PslAKyILjx18WKTCI}/_buildManifest.js +0 -0
  173. /package/web/.next/static/{G2BdtwIuunZEcXQ0FjJJF → rDu9PslAKyILjx18WKTCI}/_clientMiddlewareManifest.json +0 -0
  174. /package/web/.next/static/{G2BdtwIuunZEcXQ0FjJJF → rDu9PslAKyILjx18WKTCI}/_ssgManifest.js +0 -0
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Feature List Command
3
3
  *
4
- * Lists features in a formatted list with optional filtering.
5
- * Derives real-time status from the agent run (not stale Feature.lifecycle).
4
+ * Lists features in a hierarchical tree view: repo → feature → child → child…
5
+ * Repos and features are ordered by creation date descending.
6
6
  *
7
7
  * Usage: shep feat ls [options]
8
8
  *
@@ -11,5 +11,47 @@
11
11
  * $ shep feat ls --repo /path/to/project
12
12
  */
13
13
  import { Command } from 'commander';
14
+ import type { Feature, AgentRun, PhaseTiming, Repository } from '../../../../../packages/core/src/domain/generated/output.js';
15
+ /** Convert a date value (any type from domain) to a timestamp for sorting. */
16
+ export declare function toTimestamp(val: unknown): number;
17
+ interface Entry {
18
+ feature: Feature;
19
+ run: AgentRun | null;
20
+ phases: PhaseTiming[];
21
+ }
22
+ interface TreeNode {
23
+ entry: Entry;
24
+ children: TreeNode[];
25
+ }
26
+ interface RepoGroup {
27
+ repoPath: string;
28
+ repoName: string;
29
+ repoCreatedAt: number;
30
+ roots: TreeNode[];
31
+ }
32
+ interface FlatRow {
33
+ entry: Entry;
34
+ parentIsLast: boolean[];
35
+ isLast: boolean;
36
+ }
37
+ /**
38
+ * Build a recursive feature tree within a single repo group.
39
+ * Features with a parentId that matches another feature in the group become children.
40
+ * All levels are sorted by createdAt descending.
41
+ */
42
+ export declare function buildTree(entries: Entry[]): TreeNode[];
43
+ /**
44
+ * Group entries by repositoryPath and sort repos by createdAt desc.
45
+ * Uses the Repository entity's createdAt if available; falls back to the
46
+ * newest feature's createdAt in that repo group.
47
+ */
48
+ export declare function groupByRepo(entries: Entry[], repos: Repository[]): RepoGroup[];
49
+ /** Flatten a tree into rows with prefix context for rendering. */
50
+ export declare function flattenTree(nodes: TreeNode[], parentIsLast: boolean[]): FlatRow[];
51
+ /** Build the tree-drawing prefix string for a given depth context. */
52
+ export declare function buildTreePrefix(parentIsLast: boolean[], isLast: boolean): string;
53
+ /** Render a single feature row as a formatted string. */
54
+ export declare function renderFeatureRow(entry: Entry, treePrefix: string): string;
14
55
  export declare function createLsCommand(): Command;
56
+ export {};
15
57
  //# sourceMappingURL=ls.command.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ls.command.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/cli/commands/feat/ls.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkLpC,wBAAgB,eAAe,IAAI,OAAO,CAwGzC"}
1
+ {"version":3,"file":"ls.command.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/cli/commands/feat/ls.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAyJ/F,8EAA8E;AAC9E,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAOhD;AAiBD,UAAU,KAAK;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,QAAQ,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,WAAW,EAAE,CAAC;CACvB;AAED,UAAU,QAAQ;IAChB,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,QAAQ,EAAE,CAAC;CACtB;AAED,UAAU,SAAS;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB;AAED,UAAU,OAAO;IACf,KAAK,EAAE,KAAK,CAAC;IACb,YAAY,EAAE,OAAO,EAAE,CAAC;IACxB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,QAAQ,EAAE,CAgCtD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,SAAS,EAAE,CA8B9E;AAED,kEAAkE;AAClE,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAWjF;AAED,sEAAsE;AACtE,wBAAgB,eAAe,CAAC,YAAY,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,CAOhF;AAQD,yDAAyD;AACzD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAazE;AAeD,wBAAgB,eAAe,IAAI,OAAO,CAiFzC"}
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Feature List Command
3
3
  *
4
- * Lists features in a formatted list with optional filtering.
5
- * Derives real-time status from the agent run (not stale Feature.lifecycle).
4
+ * Lists features in a hierarchical tree view: repo → feature → child → child…
5
+ * Repos and features are ordered by creation date descending.
6
6
  *
7
7
  * Usage: shep feat ls [options]
8
8
  *
@@ -14,7 +14,7 @@ import path from 'node:path';
14
14
  import { Command } from 'commander';
15
15
  import { container } from '../../../../../packages/core/src/infrastructure/di/container.js';
16
16
  import { ListFeaturesUseCase } from '../../../../../packages/core/src/application/use-cases/features/list-features.use-case.js';
17
- import { colors, symbols, messages, renderListView } from '../../ui/index.js';
17
+ import { colors, symbols, messages, fmt } from '../../ui/index.js';
18
18
  /** Map graph node names to human-readable phase labels (active). */
19
19
  const NODE_TO_PHASE = {
20
20
  analyze: 'Analyzing',
@@ -33,21 +33,10 @@ const NODE_TO_REVIEW = {
33
33
  implement: 'Review Merge',
34
34
  merge: 'Review Merge',
35
35
  };
36
- /** Status priority for sorting (lower = higher priority). */
37
- const STATUS_PRIORITY = {
38
- running: 0,
39
- pending: 0,
40
- waiting_approval: 1,
41
- failed: 2,
42
- interrupted: 2,
43
- completed: 3,
44
- };
45
36
  /**
46
37
  * Derive the display status from the agent run.
47
- * Returns the current graph node as a phase label with an activity indicator.
48
38
  */
49
39
  function formatStatus(feature, run) {
50
- // Blocked features show "Waiting" — the parent relationship is conveyed by tree indentation
51
40
  if (feature.lifecycle === 'Archived') {
52
41
  return `${colors.muted(symbols.dotEmpty)} ${colors.muted('Archived')}`;
53
42
  }
@@ -124,7 +113,6 @@ function formatElapsed(run, phaseTimings) {
124
113
  const totalMs = phaseTimings.reduce((sum, pt) => {
125
114
  if (pt.durationMs != null)
126
115
  return sum + Number(pt.durationMs);
127
- // Phase still running — add live delta
128
116
  if (pt.startedAt)
129
117
  return sum + (now - new Date(pt.startedAt).getTime());
130
118
  return sum;
@@ -132,7 +120,6 @@ function formatElapsed(run, phaseTimings) {
132
120
  const isRunning = run?.status === 'running' || run?.status === 'pending';
133
121
  return isRunning ? colors.info(formatDuration(totalMs)) : colors.muted(formatDuration(totalMs));
134
122
  }
135
- // Fallback to run timestamps if no phase timings
136
123
  if (!run?.startedAt)
137
124
  return '';
138
125
  const started = new Date(run.startedAt).getTime();
@@ -160,11 +147,149 @@ function formatGates(feature) {
160
147
  const push = feature.push ? colors.accent('■') : colors.muted('□');
161
148
  return `${gate(allowPrd)} ${gate(allowPlan)} ${gate(allowMerge)} ${push}`;
162
149
  }
163
- /** Get sort priority for a run status (lower = shown first). */
164
- function getStatusPriority(run) {
165
- if (!run)
166
- return 4;
167
- return STATUS_PRIORITY[run.status] ?? 4;
150
+ /** Convert a date value (any type from domain) to a timestamp for sorting. */
151
+ export function toTimestamp(val) {
152
+ if (!val)
153
+ return 0;
154
+ try {
155
+ return new Date(val).getTime();
156
+ }
157
+ catch {
158
+ return 0;
159
+ }
160
+ }
161
+ /** Strip ANSI escape sequences to get visible character count. */
162
+ function stripAnsi(text) {
163
+ // eslint-disable-next-line no-control-regex
164
+ return text.replace(/\x1B\[[0-9;]*m/g, '');
165
+ }
166
+ /** Pad a string that may contain ANSI codes to a given visible width. */
167
+ function ansiPad(text, width) {
168
+ const visible = stripAnsi(text).length;
169
+ if (visible >= width)
170
+ return text;
171
+ return text + ' '.repeat(width - visible);
172
+ }
173
+ /**
174
+ * Build a recursive feature tree within a single repo group.
175
+ * Features with a parentId that matches another feature in the group become children.
176
+ * All levels are sorted by createdAt descending.
177
+ */
178
+ export function buildTree(entries) {
179
+ const byId = new Map();
180
+ for (const e of entries)
181
+ byId.set(e.feature.id, e);
182
+ const childrenByParent = new Map();
183
+ const rootEntries = [];
184
+ for (const e of entries) {
185
+ const pid = e.feature.parentId;
186
+ if (pid && byId.has(pid)) {
187
+ const list = childrenByParent.get(pid) ?? [];
188
+ list.push(e);
189
+ childrenByParent.set(pid, list);
190
+ }
191
+ else {
192
+ rootEntries.push(e);
193
+ }
194
+ }
195
+ function sortDesc(arr) {
196
+ return [...arr].sort((a, b) => toTimestamp(b.feature.createdAt) - toTimestamp(a.feature.createdAt));
197
+ }
198
+ function buildNodes(arr) {
199
+ return sortDesc(arr).map((e) => ({
200
+ entry: e,
201
+ children: buildNodes(childrenByParent.get(e.feature.id) ?? []),
202
+ }));
203
+ }
204
+ return buildNodes(rootEntries);
205
+ }
206
+ /**
207
+ * Group entries by repositoryPath and sort repos by createdAt desc.
208
+ * Uses the Repository entity's createdAt if available; falls back to the
209
+ * newest feature's createdAt in that repo group.
210
+ */
211
+ export function groupByRepo(entries, repos) {
212
+ const repoByPath = new Map();
213
+ for (const r of repos) {
214
+ repoByPath.set(r.path.replace(/\\/g, '/'), r);
215
+ }
216
+ const groupMap = new Map();
217
+ for (const e of entries) {
218
+ const repoPath = e.feature.repositoryPath.replace(/\\/g, '/');
219
+ const list = groupMap.get(repoPath) ?? [];
220
+ list.push(e);
221
+ groupMap.set(repoPath, list);
222
+ }
223
+ const groups = [];
224
+ for (const [repoPath, groupEntries] of groupMap) {
225
+ const repo = repoByPath.get(repoPath);
226
+ const repoCreatedAt = repo
227
+ ? toTimestamp(repo.createdAt)
228
+ : Math.max(...groupEntries.map((e) => toTimestamp(e.feature.createdAt)));
229
+ groups.push({
230
+ repoPath,
231
+ repoName: path.basename(repoPath),
232
+ repoCreatedAt,
233
+ roots: buildTree(groupEntries),
234
+ });
235
+ }
236
+ groups.sort((a, b) => b.repoCreatedAt - a.repoCreatedAt);
237
+ return groups;
238
+ }
239
+ /** Flatten a tree into rows with prefix context for rendering. */
240
+ export function flattenTree(nodes, parentIsLast) {
241
+ const result = [];
242
+ for (let i = 0; i < nodes.length; i++) {
243
+ const node = nodes[i];
244
+ const isLast = i === nodes.length - 1;
245
+ result.push({ entry: node.entry, parentIsLast, isLast });
246
+ if (node.children.length > 0) {
247
+ result.push(...flattenTree(node.children, [...parentIsLast, isLast]));
248
+ }
249
+ }
250
+ return result;
251
+ }
252
+ /** Build the tree-drawing prefix string for a given depth context. */
253
+ export function buildTreePrefix(parentIsLast, isLast) {
254
+ let prefix = '';
255
+ for (const wasLast of parentIsLast) {
256
+ prefix += wasLast ? ' ' : '│ ';
257
+ }
258
+ prefix += isLast ? '└─ ' : '├─ ';
259
+ return prefix;
260
+ }
261
+ // Column widths for feature rows
262
+ const FIRST_COL_WIDTH = 44; // tree prefix + id + 2 spaces + name
263
+ const STATUS_WIDTH = 22;
264
+ const GATES_WIDTH = 10;
265
+ const ELAPSED_WIDTH = 9;
266
+ /** Render a single feature row as a formatted string. */
267
+ export function renderFeatureRow(entry, treePrefix) {
268
+ const { feature, run, phases } = entry;
269
+ const shortId = colors.muted(feature.id.slice(0, 8));
270
+ const prefixPlusId = `${treePrefix}${shortId} `;
271
+ const prefixPlusIdLen = stripAnsi(prefixPlusId).length;
272
+ const nameMax = Math.max(FIRST_COL_WIDTH - prefixPlusIdLen, 8);
273
+ const name = truncate(feature.name, nameMax);
274
+ const firstCol = ansiPad(prefixPlusId + name, FIRST_COL_WIDTH);
275
+ const status = ansiPad(formatStatus(feature, run), STATUS_WIDTH);
276
+ const gates = ansiPad(formatGates(feature), GATES_WIDTH);
277
+ const elapsed = ansiPad(formatElapsed(run, phases), ELAPSED_WIDTH);
278
+ const done = formatDone(run);
279
+ return ` ${firstCol} ${status} ${gates} ${elapsed} ${done}`;
280
+ }
281
+ /** Count total features across all repo groups. */
282
+ function countFeatures(groups) {
283
+ let count = 0;
284
+ function countNodes(nodes) {
285
+ for (const n of nodes) {
286
+ count++;
287
+ countNodes(n.children);
288
+ }
289
+ }
290
+ for (const g of groups)
291
+ countNodes(g.roots);
292
+ return count;
168
293
  }
169
294
  export function createLsCommand() {
170
295
  return new Command('ls')
@@ -177,79 +302,54 @@ export function createLsCommand() {
177
302
  const useCase = container.resolve(ListFeaturesUseCase);
178
303
  const runRepo = container.resolve('IAgentRunRepository');
179
304
  const phaseRepo = container.resolve('IPhaseTimingRepository');
305
+ const repoRepo = container.resolve('IRepositoryRepository');
180
306
  const filters = {
181
307
  ...(options.repo && { repositoryPath: options.repo }),
182
308
  ...(options.includeDeleted && { includeDeleted: true }),
183
309
  ...(options.showArchived && { includeArchived: true }),
184
310
  };
185
311
  const features = await useCase.execute(Object.keys(filters).length > 0 ? filters : undefined);
186
- // Load agent runs and phase timings for all features in parallel
187
- const [runs, timings] = await Promise.all([
312
+ // Load agent runs, phase timings, and repos in parallel
313
+ const [runs, timings, repos] = await Promise.all([
188
314
  Promise.all(features.map((f) => f.agentRunId ? runRepo.findById(f.agentRunId) : Promise.resolve(null))),
189
315
  Promise.all(features.map((f) => phaseRepo.findByFeatureId(f.id))),
316
+ repoRepo.list(),
190
317
  ]);
191
- // Pair features with runs/timings and sort by status priority
192
- const paired = features
193
- .map((feature, i) => ({ feature, run: runs[i], phases: timings[i] }))
194
- .sort((a, b) => getStatusPriority(a.run) - getStatusPriority(b.run));
195
- const childrenByParent = new Map();
196
- const roots = [];
197
- for (const entry of paired) {
198
- const pid = entry.feature.parentId;
199
- if (pid && entry.feature.lifecycle === 'Blocked') {
200
- const list = childrenByParent.get(pid) ?? [];
201
- list.push(entry);
202
- childrenByParent.set(pid, list);
203
- }
204
- else {
205
- roots.push(entry);
206
- }
207
- }
208
- // Flatten: parent then its blocked children
209
- const ordered = [];
210
- for (const entry of roots) {
211
- ordered.push({ entry, indent: false });
212
- const kids = childrenByParent.get(entry.feature.id);
213
- if (kids) {
214
- for (const kid of kids) {
215
- ordered.push({ entry: kid, indent: true });
216
- }
217
- childrenByParent.delete(entry.feature.id);
218
- }
318
+ const entries = features.map((feature, i) => ({
319
+ feature,
320
+ run: runs[i],
321
+ phases: timings[i],
322
+ }));
323
+ const groups = groupByRepo(entries, repos);
324
+ const total = countFeatures(groups);
325
+ if (total === 0) {
326
+ messages.newline();
327
+ messages.info('No features found');
328
+ messages.newline();
329
+ return;
219
330
  }
220
- // Append orphaned blocked children (parent not in current list)
221
- for (const kids of childrenByParent.values()) {
222
- for (const kid of kids) {
223
- ordered.push({ entry: kid, indent: false });
331
+ const lines = [];
332
+ lines.push('');
333
+ lines.push(` ${fmt.heading(`Features (${total})`)}`);
334
+ lines.push('');
335
+ // Column headers
336
+ const h1 = colors.muted('NAME'.padEnd(FIRST_COL_WIDTH));
337
+ const h2 = colors.muted('STATUS'.padEnd(STATUS_WIDTH));
338
+ const h3 = colors.muted('R P M ↑'.padEnd(GATES_WIDTH));
339
+ const h4 = colors.muted('ELAPSED'.padEnd(ELAPSED_WIDTH));
340
+ const h5 = colors.muted('DONE');
341
+ lines.push(` ${h1} ${h2} ${h3} ${h4} ${h5}`);
342
+ for (const group of groups) {
343
+ lines.push('');
344
+ lines.push(` ${fmt.heading(group.repoName)} ${colors.muted(group.repoPath)}`);
345
+ const flatRows = flattenTree(group.roots, []);
346
+ for (const { entry, parentIsLast, isLast } of flatRows) {
347
+ const treePrefix = buildTreePrefix(parentIsLast, isLast);
348
+ lines.push(renderFeatureRow(entry, treePrefix));
224
349
  }
225
350
  }
226
- const rows = ordered.map(({ entry: { feature, run, phases }, indent }) => {
227
- const repo = path.basename(feature.repositoryPath);
228
- const prefix = indent ? `${colors.muted('└')} ` : '';
229
- return [
230
- prefix + feature.id.slice(0, 8),
231
- prefix + truncate(feature.name, indent ? 28 : 30),
232
- formatStatus(feature, run),
233
- colors.muted(repo),
234
- formatGates(feature),
235
- formatElapsed(run, phases),
236
- formatDone(run),
237
- ];
238
- });
239
- renderListView({
240
- title: 'Features',
241
- columns: [
242
- { label: 'ID', width: 10 },
243
- { label: 'Name', width: 32 },
244
- { label: 'Status', width: 21 },
245
- { label: 'Repo', width: 20 },
246
- { label: 'R P M ↑', width: 10 },
247
- { label: 'Elapsed', width: 10 },
248
- { label: 'Done', width: 12 },
249
- ],
250
- rows,
251
- emptyMessage: 'No features found',
252
- });
351
+ lines.push('');
352
+ console.log(lines.join('\n'));
253
353
  }
254
354
  catch (error) {
255
355
  const err = error instanceof Error ? error : new Error(String(error));
@@ -1 +1 @@
1
- {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/app/api/sessions/route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAK3C,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAgTvC;;;;;GAKG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,OAAO;;;;YAnTpC,MAAM;mBACC,MAAM;iBACR,MAAM,GAAG,IAAI;sBACR,MAAM;wBACJ,MAAM,GAAG,IAAI;uBACd,MAAM,GAAG,IAAI;mBACjB,MAAM,GAAG,IAAI;qBACX,MAAM;kBACT,MAAM;;IAwUjB"}
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/app/api/sessions/route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAM3C,eAAO,MAAM,OAAO,kBAAkB,CAAC;AA2VvC;;;;;GAKG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,OAAO;;;;YA9VpC,MAAM;mBACC,MAAM;iBACR,MAAM,GAAG,IAAI;sBACR,MAAM;wBACJ,MAAM,GAAG,IAAI;uBACd,MAAM,GAAG,IAAI;mBACjB,MAAM,GAAG,IAAI;qBACX,MAAM;kBACT,MAAM;;IAoXjB"}
@@ -1,4 +1,5 @@
1
1
  import { NextResponse } from 'next/server';
2
+ import { createHash } from 'node:crypto';
2
3
  import { homedir } from 'node:os';
3
4
  import { join } from 'node:path';
4
5
  import { readdir, stat } from 'node:fs/promises';
@@ -20,28 +21,58 @@ function cursorEncodePath(p) {
20
21
  return p.replace(/^\//, '').replace(/\./g, '').replace(/[/\\]/g, '-');
21
22
  }
22
23
  // ── Claude Code session scanner ───────────────────────────────────────
23
- async function scanClaudeSessions(repositoryPath, limit) {
24
- const dirName = claudeEncodePath(repositoryPath);
25
- const projectDir = join(homedir(), '.claude', 'projects', dirName);
26
- let files;
24
+ /**
25
+ * Collect .jsonl session files from a single Claude project directory.
26
+ */
27
+ async function collectJsonlFiles(projectDir) {
28
+ let entries;
27
29
  try {
28
- const entries = await readdir(projectDir);
29
- files = entries.filter((e) => e.endsWith('.jsonl'));
30
+ entries = await readdir(projectDir);
30
31
  }
31
32
  catch {
32
33
  return [];
33
34
  }
34
- // Stat all files for mtime sorting
35
- const fileInfos = await Promise.allSettled(files.map(async (name) => {
35
+ const jsonlFiles = entries.filter((e) => e.endsWith('.jsonl'));
36
+ const fileInfos = await Promise.allSettled(jsonlFiles.map(async (name) => {
36
37
  const filePath = join(projectDir, name);
37
38
  const s = await stat(filePath);
38
39
  return { name, filePath, mtime: s.mtime.getTime() };
39
40
  }));
40
- const valid = fileInfos
41
+ return fileInfos
41
42
  .filter((r) => r.status === 'fulfilled')
42
- .map((r) => r.value)
43
- .sort((a, b) => b.mtime - a.mtime)
44
- .slice(0, limit);
43
+ .map((r) => r.value);
44
+ }
45
+ async function scanClaudeSessions(repositoryPath, limit, includeWorktrees = false) {
46
+ const dirName = claudeEncodePath(repositoryPath);
47
+ const projectsRoot = join(homedir(), '.claude', 'projects');
48
+ // Collect files from the exact directory
49
+ const primaryDir = join(projectsRoot, dirName);
50
+ let allFiles = await collectJsonlFiles(primaryDir);
51
+ // When includeWorktrees is set, also scan:
52
+ // 1. Directories whose name starts with the encoded repo path (git worktrees, .worktrees)
53
+ // 2. Shep worktree directories (~/.shep/repos/<hash>/wt/*) which use a hash of the repo path
54
+ if (includeWorktrees) {
55
+ try {
56
+ const allDirs = await readdir(projectsRoot);
57
+ // Match git-style worktrees (same prefix as repo path)
58
+ const prefixMatches = allDirs.filter((d) => d !== dirName && d.startsWith(dirName));
59
+ // Match shep worktrees: compute repo hash → find dirs starting with encoded shep path
60
+ const normalizedRepoPath = repositoryPath.replace(/\\/g, '/');
61
+ const repoHash = createHash('sha256').update(normalizedRepoPath).digest('hex').slice(0, 16);
62
+ const shepHome = join(homedir(), '.shep').replace(/\\/g, '/');
63
+ const shepWorktreePrefix = claudeEncodePath(join(shepHome, 'repos', repoHash));
64
+ const shepMatches = allDirs.filter((d) => d.startsWith(shepWorktreePrefix) && !prefixMatches.includes(d) && d !== dirName);
65
+ const worktreeDirs = [...prefixMatches, ...shepMatches];
66
+ const worktreeResults = await Promise.all(worktreeDirs.map((d) => collectJsonlFiles(join(projectsRoot, d))));
67
+ for (const files of worktreeResults) {
68
+ allFiles = allFiles.concat(files);
69
+ }
70
+ }
71
+ catch {
72
+ // projectsRoot doesn't exist — no sessions at all
73
+ }
74
+ }
75
+ const valid = allFiles.sort((a, b) => b.mtime - a.mtime).slice(0, limit);
45
76
  // Parse each file
46
77
  const results = await Promise.allSettled(valid.map(async (fi) => parseClaudeSession(fi.filePath, fi.name, fi.mtime, repositoryPath)));
47
78
  return results
@@ -238,13 +269,14 @@ export async function GET(request) {
238
269
  const url = new URL(request.url);
239
270
  const repositoryPath = url.searchParams.get('repositoryPath');
240
271
  const limit = parseInt(url.searchParams.get('limit') ?? '10', 10);
272
+ const includeWorktrees = url.searchParams.get('includeWorktrees') === 'true';
241
273
  if (!repositoryPath?.trim()) {
242
274
  return NextResponse.json({ error: 'repositoryPath is required' }, { status: 400 });
243
275
  }
244
276
  try {
245
277
  // Scan all providers in parallel
246
278
  const [claudeSessions, cursorSessions] = await Promise.all([
247
- scanClaudeSessions(repositoryPath, limit),
279
+ scanClaudeSessions(repositoryPath, limit, includeWorktrees),
248
280
  scanCursorSessions(repositoryPath, limit),
249
281
  ]);
250
282
  // Merge and sort by mtime descending, apply limit
@@ -1 +1 @@
1
- {"version":3,"file":"feature-node.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-node/feature-node.tsx"],"names":[],"mappings":"AAoCA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AA6CnE,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,QAAQ,GACT,EAAE;IACD,IAAI,EAAE,eAAe,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB,2CA8dA"}
1
+ {"version":3,"file":"feature-node.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-node/feature-node.tsx"],"names":[],"mappings":"AAoCA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AA6CnE,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,QAAQ,GACT,EAAE;IACD,IAAI,EAAE,eAAe,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB,2CAgeA"}
@@ -112,7 +112,7 @@ export function FeatureNode({ data, selected, }) {
112
112
  ? 'cursor-pointer opacity-80 transition-opacity hover:opacity-100'
113
113
  : 'cursor-default'), children: data.deployment.status === DeploymentState.Booting ? (_jsx(Loader2, { className: "h-3 w-3 animate-spin text-blue-500" })) : (_jsx(Globe, { className: "h-3 w-3 text-green-600" })) }) }), _jsx(TooltipContent, { side: "top", children: data.deployment.status === DeploymentState.Booting
114
114
  ? 'Deploying...'
115
- : (data.deployment.url ?? 'Live') })] }) })) : null, data.fastMode ? (_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("span", { "data-testid": "feature-node-fast-mode-badge", children: _jsx(Zap, { className: "h-3 w-3 text-amber-500" }) }) }), _jsx(TooltipContent, { side: "top", children: "Fast Mode" })] }) })) : null, data.repositoryPath ? (_jsx(FeatureSessionsDropdown, { repositoryPath: data.repositoryPath })) : null] }), data.state === 'deleting' ? (_jsxs("div", { className: "flex items-center gap-1.5 text-xs", children: [_jsx(Loader2, { className: "h-3.5 w-3.5 shrink-0 animate-spin text-gray-400" }), _jsx("span", { className: "text-muted-foreground", children: "Deleting\u2026" })] })) : data.state === 'creating' ? (_jsxs("div", { className: "flex items-center gap-1.5 text-xs", children: [_jsx(Icon, { className: "h-3.5 w-3.5 shrink-0 animate-spin text-teal-600 dark:text-teal-400" }), _jsx("span", { className: "font-medium text-teal-600 dark:text-teal-400", children: getBadgeText(data) })] })) : data.state === 'running' ? (_jsxs("div", { className: "flex items-center gap-1.5 text-xs", children: [_jsx(Icon, { className: "h-3.5 w-3.5 shrink-0 animate-spin text-teal-600 dark:text-teal-400" }), _jsx("span", { className: "font-medium text-teal-600 dark:text-teal-400", children: getBadgeText(data) })] })) : data.state === 'action-required' ? (_jsxs(Button, { variant: "default", size: "xs", "aria-label": getActionRequiredLabel(data), "data-testid": "feature-node-approve-button",
115
+ : (data.deployment.url ?? 'Live') })] }) })) : null, data.fastMode ? (_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("span", { "data-testid": "feature-node-fast-mode-badge", children: _jsx(Zap, { className: "h-3 w-3 text-amber-500" }) }) }), _jsx(TooltipContent, { side: "top", children: "Fast Mode" })] }) })) : null, (data.worktreePath ?? data.repositoryPath) ? (_jsx(FeatureSessionsDropdown, { repositoryPath: data.worktreePath ?? data.repositoryPath })) : null] }), data.state === 'deleting' ? (_jsxs("div", { className: "flex items-center gap-1.5 text-xs", children: [_jsx(Loader2, { className: "h-3.5 w-3.5 shrink-0 animate-spin text-gray-400" }), _jsx("span", { className: "text-muted-foreground", children: "Deleting\u2026" })] })) : data.state === 'creating' ? (_jsxs("div", { className: "flex items-center gap-1.5 text-xs", children: [_jsx(Icon, { className: "h-3.5 w-3.5 shrink-0 animate-spin text-teal-600 dark:text-teal-400" }), _jsx("span", { className: "font-medium text-teal-600 dark:text-teal-400", children: getBadgeText(data) })] })) : data.state === 'running' ? (_jsxs("div", { className: "flex items-center gap-1.5 text-xs", children: [_jsx(Icon, { className: "h-3.5 w-3.5 shrink-0 animate-spin text-teal-600 dark:text-teal-400" }), _jsx("span", { className: "font-medium text-teal-600 dark:text-teal-400", children: getBadgeText(data) })] })) : data.state === 'action-required' ? (_jsxs(Button, { variant: "default", size: "xs", "aria-label": getActionRequiredLabel(data), "data-testid": "feature-node-approve-button",
116
116
  // eslint-disable-next-line @typescript-eslint/no-empty-function -- click bubbles to card's onNodeClick
117
117
  onClick: () => { }, className: "nodrag dark:bg-primary dark:text-primary-foreground dark:hover:bg-primary/90 cursor-pointer bg-neutral-900 text-[11px] text-white hover:bg-neutral-800", children: [_jsx(Eye, { className: "h-3 w-3" }), getActionRequiredLabel(data)] })) : data.state === 'error' && data.onRetry ? (_jsxs(Button, { variant: "outline", size: "xs", "aria-label": "Retry", "data-testid": "feature-node-retry-button", onClick: (e) => {
118
118
  e.stopPropagation();
@@ -13,9 +13,11 @@ export interface SessionSummary {
13
13
  interface FeatureSessionsDropdownProps {
14
14
  repositoryPath: string;
15
15
  className?: string;
16
+ /** When true, also scan worktree session directories (used by repo nodes). */
17
+ includeWorktrees?: boolean;
16
18
  /** Callback to create a feature from a session. Only shown on repo nodes. */
17
19
  onCreateFromSession?: (session: SessionSummary, sessionFilePath: string) => void;
18
20
  }
19
- export declare function FeatureSessionsDropdown({ repositoryPath, className, onCreateFromSession, }: FeatureSessionsDropdownProps): import("react/jsx-runtime").JSX.Element;
21
+ export declare function FeatureSessionsDropdown({ repositoryPath, className, includeWorktrees, onCreateFromSession, }: FeatureSessionsDropdownProps): import("react/jsx-runtime").JSX.Element;
20
22
  export {};
21
23
  //# sourceMappingURL=feature-sessions-dropdown.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"feature-sessions-dropdown.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-node/feature-sessions-dropdown.tsx"],"names":[],"mappings":"AA8BA,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,4BAA4B;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6EAA6E;IAC7E,mBAAmB,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,KAAK,IAAI,CAAC;CAClF;AAmDD,wBAAgB,uBAAuB,CAAC,EACtC,cAAc,EACd,SAAS,EACT,mBAAmB,GACpB,EAAE,4BAA4B,2CA2J9B"}
1
+ {"version":3,"file":"feature-sessions-dropdown.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-node/feature-sessions-dropdown.tsx"],"names":[],"mappings":"AA8BA,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,4BAA4B;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,6EAA6E;IAC7E,mBAAmB,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,KAAK,IAAI,CAAC;CAClF;AAmDD,wBAAgB,uBAAuB,CAAC,EACtC,cAAc,EACd,SAAS,EACT,gBAAgB,EAChB,mBAAmB,GACpB,EAAE,4BAA4B,2CAmK9B"}
@@ -56,7 +56,7 @@ async function copyToClipboard(text) {
56
56
  function stopNodeEvent(e) {
57
57
  e.stopPropagation();
58
58
  }
59
- export function FeatureSessionsDropdown({ repositoryPath, className, onCreateFromSession, }) {
59
+ export function FeatureSessionsDropdown({ repositoryPath, className, includeWorktrees, onCreateFromSession, }) {
60
60
  const [sessions, setSessions] = useState([]);
61
61
  const [loading, setLoading] = useState(false);
62
62
  const [fetched, setFetched] = useState(false);
@@ -77,7 +77,11 @@ export function FeatureSessionsDropdown({ repositoryPath, className, onCreateFro
77
77
  // Populates count badge + active indicator, and pre-loads the dropdown.
78
78
  useEffect(() => {
79
79
  let cancelled = false;
80
- const params = new URLSearchParams({ repositoryPath, limit: '10' });
80
+ const params = new URLSearchParams({
81
+ repositoryPath,
82
+ limit: '10',
83
+ ...(includeWorktrees && { includeWorktrees: 'true' }),
84
+ });
81
85
  fetch(`/api/sessions?${params.toString()}`)
82
86
  .then((res) => (res.ok ? res.json() : null))
83
87
  .then((data) => {
@@ -91,14 +95,18 @@ export function FeatureSessionsDropdown({ repositoryPath, className, onCreateFro
91
95
  return () => {
92
96
  cancelled = true;
93
97
  };
94
- }, [repositoryPath]);
98
+ }, [repositoryPath, includeWorktrees]);
95
99
  // Re-fetch on dropdown open if not already loaded (e.g. path changed)
96
100
  const doFetch = useCallback(async () => {
97
101
  if (fetched)
98
102
  return;
99
103
  setLoading(true);
100
104
  try {
101
- const params = new URLSearchParams({ repositoryPath, limit: '10' });
105
+ const params = new URLSearchParams({
106
+ repositoryPath,
107
+ limit: '10',
108
+ ...(includeWorktrees && { includeWorktrees: 'true' }),
109
+ });
102
110
  const res = await fetch(`/api/sessions?${params.toString()}`);
103
111
  if (res.ok) {
104
112
  const data = (await res.json());
@@ -113,7 +121,7 @@ export function FeatureSessionsDropdown({ repositoryPath, className, onCreateFro
113
121
  setLoading(false);
114
122
  setFetched(true);
115
123
  }
116
- }, [repositoryPath, fetched]);
124
+ }, [repositoryPath, fetched, includeWorktrees]);
117
125
  const handleOpenChange = useCallback((open) => {
118
126
  if (open)
119
127
  void doFetch();
@@ -1 +1 @@
1
- {"version":3,"file":"repository-node.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/repository-node/repository-node.tsx"],"names":[],"mappings":"AAmCA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAOnE,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,QAAQ,GACT,EAAE;IACD,IAAI,EAAE,kBAAkB,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB,2CAyaA"}
1
+ {"version":3,"file":"repository-node.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/repository-node/repository-node.tsx"],"names":[],"mappings":"AAmCA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAOnE,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,QAAQ,GACT,EAAE;IACD,IAAI,EAAE,kBAAkB,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB,2CA0aA"}
@@ -71,7 +71,7 @@ export function RepositoryNode({ data, selected, }) {
71
71
  e.stopPropagation();
72
72
  data.onClick?.();
73
73
  }
74
- }, className: cn('nodrag bg-card flex w-[26rem] cursor-pointer flex-col overflow-hidden rounded-xl border shadow-sm transition-[border-color,box-shadow] duration-200 dark:bg-neutral-800/80', selected && 'border-blue-400 dark:border-amber-500/60'), children: [_jsxs("div", { className: "flex items-center gap-3 px-4 py-3", children: [_jsx(Github, { className: "text-muted-foreground h-5 w-5 shrink-0" }), _jsx("span", { "data-testid": "repository-node-name", className: "min-w-0 truncate text-sm font-medium", children: data.name }), _jsxs("div", { className: cn('flex shrink-0 items-center gap-2', (data.repositoryPath ?? data.onAdd) && 'ml-auto'), onClick: (e) => e.stopPropagation(), children: [data.repositoryPath ? (_jsxs(_Fragment, { children: [_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("span", { className: "flex items-center", children: _jsx(ActionButton, { label: "Open in IDE", onClick: actions.openInIde, loading: actions.ideLoading, error: !!actions.ideError, icon: Code2, iconOnly: true, variant: "ghost", size: "icon-xs" }) }) }), _jsx(TooltipContent, { children: "Open in IDE" })] }) }), _jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("span", { className: "flex items-center", children: _jsx(ActionButton, { label: "Open in Shell", onClick: actions.openInShell, loading: actions.shellLoading, error: !!actions.shellError, icon: Terminal, iconOnly: true, variant: "ghost", size: "icon-xs" }) }) }), _jsx(TooltipContent, { children: "Open in Shell" })] }) }), _jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("span", { className: "flex items-center", children: _jsx(ActionButton, { label: "Open Folder", onClick: actions.openFolder, loading: actions.folderLoading, error: !!actions.folderError, icon: FolderOpen, iconOnly: true, variant: "ghost", size: "icon-xs" }) }) }), _jsx(TooltipContent, { children: "Open Folder" })] }) }), _jsx(FeatureSessionsDropdown, { repositoryPath: data.repositoryPath, onCreateFromSession: handleCreateFromSession })] })) : null, data.onAdd ? _jsx("div", { className: "ml-1.5" }) : null, data.onAdd ? (_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("button", { "aria-label": "New feature", "data-testid": "repository-node-add-button", onClick: (e) => {
74
+ }, className: cn('nodrag bg-card flex w-[26rem] cursor-pointer flex-col overflow-hidden rounded-xl border shadow-sm transition-[border-color,box-shadow] duration-200 dark:bg-neutral-800/80', selected && 'border-blue-400 dark:border-amber-500/60'), children: [_jsxs("div", { className: "flex items-center gap-3 px-4 py-3", children: [_jsx(Github, { className: "text-muted-foreground h-5 w-5 shrink-0" }), _jsx("span", { "data-testid": "repository-node-name", className: "min-w-0 truncate text-sm font-medium", children: data.name }), _jsxs("div", { className: cn('flex shrink-0 items-center gap-2', (data.repositoryPath ?? data.onAdd) && 'ml-auto'), onClick: (e) => e.stopPropagation(), children: [data.repositoryPath ? (_jsxs(_Fragment, { children: [_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("span", { className: "flex items-center", children: _jsx(ActionButton, { label: "Open in IDE", onClick: actions.openInIde, loading: actions.ideLoading, error: !!actions.ideError, icon: Code2, iconOnly: true, variant: "ghost", size: "icon-xs" }) }) }), _jsx(TooltipContent, { children: "Open in IDE" })] }) }), _jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("span", { className: "flex items-center", children: _jsx(ActionButton, { label: "Open in Shell", onClick: actions.openInShell, loading: actions.shellLoading, error: !!actions.shellError, icon: Terminal, iconOnly: true, variant: "ghost", size: "icon-xs" }) }) }), _jsx(TooltipContent, { children: "Open in Shell" })] }) }), _jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("span", { className: "flex items-center", children: _jsx(ActionButton, { label: "Open Folder", onClick: actions.openFolder, loading: actions.folderLoading, error: !!actions.folderError, icon: FolderOpen, iconOnly: true, variant: "ghost", size: "icon-xs" }) }) }), _jsx(TooltipContent, { children: "Open Folder" })] }) }), _jsx(FeatureSessionsDropdown, { repositoryPath: data.repositoryPath, includeWorktrees: true, onCreateFromSession: handleCreateFromSession })] })) : null, data.onAdd ? _jsx("div", { className: "ml-1.5" }) : null, data.onAdd ? (_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("button", { "aria-label": "New feature", "data-testid": "repository-node-add-button", onClick: (e) => {
75
75
  e.stopPropagation();
76
76
  data.onAdd?.();
77
77
  }, className: cn('flex h-6 shrink-0 cursor-pointer items-center gap-0.5 rounded bg-blue-500 px-1.5 text-[11px] font-bold text-white transition-colors hover:bg-blue-600 dark:bg-amber-500 dark:hover:bg-amber-400', data.pulseAdd && 'animate-pulse-cta'), children: [_jsx(Plus, { className: "h-3 w-3" }), _jsx("span", { className: "translate-y-px", children: "New" })] }) }), _jsx(TooltipContent, { side: "top", children: "New feature" })] }) })) : null] })] }), data.branch ? (_jsxs(_Fragment, { children: [_jsx("div", { "data-testid": "repository-node-git-info", className: "text-muted-foreground border-border/50 border-t px-4 py-2", children: _jsxs("div", { className: "flex items-center gap-3 text-xs", children: [_jsxs("span", { className: "flex items-center gap-1 truncate", "data-testid": "repository-node-branch", children: [_jsx(GitBranch, { className: "h-3 w-3 shrink-0" }), _jsx("span", { className: "truncate", children: data.branch })] }), data.behindCount != null && data.behindCount > 0 ? (_jsxs("span", { className: "flex shrink-0 items-center gap-1 whitespace-nowrap text-amber-500", "data-testid": "repository-node-behind", children: [_jsx(ArrowDown, { className: "h-3 w-3 shrink-0" }), data.behindCount, " behind"] })) : null] }) }), data.commitMessage ? (_jsx("div", { "data-testid": "repository-node-commit-info", className: "text-muted-foreground border-border/50 border-t px-4 py-2", children: _jsxs("div", { className: "flex items-center gap-2 text-xs", children: [_jsx(GitCommitHorizontal, { className: "h-3 w-3 shrink-0" }), _jsx("span", { className: "min-w-0 truncate", "data-testid": "repository-node-commit-message", children: data.commitMessage }), data.committer ? (_jsxs("span", { className: "text-muted-foreground/70 ml-auto flex shrink-0 items-center gap-1", "data-testid": "repository-node-committer", children: [_jsx(User, { className: "h-3 w-3 shrink-0" }), _jsx("span", { children: data.committer })] })) : null] }) })) : null] })) : data.gitInfoStatus === 'not-a-repo' ? (