@shepai/cli 1.135.2 → 1.135.3-pr443.1bf56b0

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 (181) hide show
  1. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/evidence.node.d.ts.map +1 -1
  2. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/evidence.node.js +2 -1
  3. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/fast-implement.node.d.ts.map +1 -1
  4. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/fast-implement.node.js +1 -23
  5. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/node-helpers.d.ts +6 -1
  6. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/node-helpers.d.ts.map +1 -1
  7. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/node-helpers.js +22 -2
  8. package/dist/src/presentation/web/components/common/merge-review/diff-view.d.ts +3 -0
  9. package/dist/src/presentation/web/components/common/merge-review/diff-view.d.ts.map +1 -1
  10. package/dist/src/presentation/web/components/common/merge-review/diff-view.js +100 -17
  11. package/dist/src/presentation/web/components/common/merge-review/diff-view.stories.d.ts +3 -1
  12. package/dist/src/presentation/web/components/common/merge-review/diff-view.stories.d.ts.map +1 -1
  13. package/dist/src/presentation/web/components/common/merge-review/diff-view.stories.js +31 -1
  14. package/dist/src/presentation/web/components/ui/file-tree.d.ts +39 -0
  15. package/dist/src/presentation/web/components/ui/file-tree.d.ts.map +1 -0
  16. package/dist/src/presentation/web/components/ui/file-tree.js +194 -0
  17. package/dist/src/presentation/web/components/ui/file-tree.stories.d.ts +14 -0
  18. package/dist/src/presentation/web/components/ui/file-tree.stories.d.ts.map +1 -0
  19. package/dist/src/presentation/web/components/ui/file-tree.stories.js +92 -0
  20. package/dist/tsconfig.build.tsbuildinfo +1 -1
  21. package/package.json +1 -1
  22. package/web/.next/BUILD_ID +1 -1
  23. package/web/.next/build-manifest.json +2 -2
  24. package/web/.next/fallback-build-manifest.json +2 -2
  25. package/web/.next/prerender-manifest.json +3 -3
  26. package/web/.next/required-server-files.js +3 -3
  27. package/web/.next/required-server-files.json +3 -3
  28. package/web/.next/server/app/(dashboard)/@drawer/adopt/page/server-reference-manifest.json +24 -24
  29. package/web/.next/server/app/(dashboard)/@drawer/adopt/page.js.nft.json +1 -1
  30. package/web/.next/server/app/(dashboard)/@drawer/adopt/page_client-reference-manifest.js +1 -1
  31. package/web/.next/server/app/(dashboard)/@drawer/create/page/server-reference-manifest.json +24 -24
  32. package/web/.next/server/app/(dashboard)/@drawer/create/page.js.nft.json +1 -1
  33. package/web/.next/server/app/(dashboard)/@drawer/create/page_client-reference-manifest.js +1 -1
  34. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page/server-reference-manifest.json +30 -30
  35. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page.js.nft.json +1 -1
  36. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page_client-reference-manifest.js +1 -1
  37. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page/server-reference-manifest.json +30 -30
  38. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js.nft.json +1 -1
  39. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page_client-reference-manifest.js +1 -1
  40. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page/server-reference-manifest.json +22 -22
  41. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js.nft.json +1 -1
  42. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  43. package/web/.next/server/app/(dashboard)/create/page/server-reference-manifest.json +24 -24
  44. package/web/.next/server/app/(dashboard)/create/page.js.nft.json +1 -1
  45. package/web/.next/server/app/(dashboard)/create/page_client-reference-manifest.js +1 -1
  46. package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page/server-reference-manifest.json +30 -30
  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 +30 -30
  50. package/web/.next/server/app/(dashboard)/feature/[featureId]/page.js.nft.json +1 -1
  51. package/web/.next/server/app/(dashboard)/feature/[featureId]/page_client-reference-manifest.js +1 -1
  52. package/web/.next/server/app/(dashboard)/page/server-reference-manifest.json +22 -22
  53. package/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
  54. package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
  55. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page/server-reference-manifest.json +22 -22
  56. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js.nft.json +1 -1
  57. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  58. package/web/.next/server/app/_global-error.html +2 -2
  59. package/web/.next/server/app/_global-error.rsc +1 -1
  60. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  61. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  62. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  63. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  64. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  65. package/web/.next/server/app/_not-found/page/server-reference-manifest.json +3 -3
  66. package/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  67. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  68. package/web/.next/server/app/api/attachments/preview/route.js.nft.json +1 -1
  69. package/web/.next/server/app/api/evidence/route.js.nft.json +1 -1
  70. package/web/.next/server/app/api/graph-data/route.js.nft.json +1 -1
  71. package/web/.next/server/app/settings/page/server-reference-manifest.json +8 -8
  72. package/web/.next/server/app/settings/page.js.nft.json +1 -1
  73. package/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  74. package/web/.next/server/app/skills/page/server-reference-manifest.json +8 -8
  75. package/web/.next/server/app/skills/page.js.nft.json +1 -1
  76. package/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  77. package/web/.next/server/app/tools/page/server-reference-manifest.json +8 -8
  78. package/web/.next/server/app/tools/page.js.nft.json +1 -1
  79. package/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
  80. package/web/.next/server/app/version/page/server-reference-manifest.json +3 -3
  81. package/web/.next/server/app/version/page.js.nft.json +1 -1
  82. package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  83. package/web/.next/server/chunks/[root-of-the-server]__a402b567._.js +1 -1
  84. package/web/.next/server/chunks/ssr/744ca_web__next-internal_server_app_(dashboard)_@drawer_adopt_page_actions_4e741df2.js +1 -1
  85. package/web/.next/server/chunks/ssr/744ca_web__next-internal_server_app_(dashboard)_@drawer_adopt_page_actions_4e741df2.js.map +1 -1
  86. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js +1 -1
  87. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js.map +1 -1
  88. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_026fdffa.js +1 -1
  89. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_026fdffa.js.map +1 -1
  90. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_8b91e197.js +1 -1
  91. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_8b91e197.js.map +1 -1
  92. package/web/.next/server/chunks/ssr/{[root-of-the-server]__7894f27f._.js → [root-of-the-server]__1a3121ff._.js} +2 -2
  93. package/web/.next/server/chunks/ssr/{[root-of-the-server]__7894f27f._.js.map → [root-of-the-server]__1a3121ff._.js.map} +1 -1
  94. package/web/.next/server/chunks/ssr/[root-of-the-server]__31bc28da._.js +1 -1
  95. package/web/.next/server/chunks/ssr/[root-of-the-server]__31bc28da._.js.map +1 -1
  96. package/web/.next/server/chunks/ssr/[root-of-the-server]__32ecbed9._.js +1 -1
  97. package/web/.next/server/chunks/ssr/[root-of-the-server]__32ecbed9._.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]__90ce4b6d._.js +1 -1
  105. package/web/.next/server/chunks/ssr/[root-of-the-server]__90ce4b6d._.js.map +1 -1
  106. package/web/.next/server/chunks/ssr/[root-of-the-server]__90f9ae71._.js +1 -1
  107. package/web/.next/server/chunks/ssr/[root-of-the-server]__90f9ae71._.js.map +1 -1
  108. package/web/.next/server/chunks/ssr/[root-of-the-server]__96670392._.js +2 -2
  109. package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js +2 -2
  110. package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js.map +1 -1
  111. package/web/.next/server/chunks/ssr/[root-of-the-server]__b3dbadca._.js +1 -1
  112. package/web/.next/server/chunks/ssr/[root-of-the-server]__b3dbadca._.js.map +1 -1
  113. package/web/.next/server/chunks/ssr/_02ec1aea._.js +1 -1
  114. package/web/.next/server/chunks/ssr/_02ec1aea._.js.map +1 -1
  115. package/web/.next/server/chunks/ssr/_67104d9e._.js +1 -1
  116. package/web/.next/server/chunks/ssr/_67104d9e._.js.map +1 -1
  117. package/web/.next/server/chunks/ssr/_7dca1882._.js +1 -1
  118. package/web/.next/server/chunks/ssr/_7dca1882._.js.map +1 -1
  119. package/web/.next/server/chunks/ssr/{_69699ffe._.js → _8c62a935._.js} +2 -2
  120. package/web/.next/server/chunks/ssr/{_69699ffe._.js.map → _8c62a935._.js.map} +1 -1
  121. package/web/.next/server/chunks/ssr/_a9f57758._.js +1 -1
  122. package/web/.next/server/chunks/ssr/_a9f57758._.js.map +1 -1
  123. package/web/.next/server/chunks/ssr/_b6960ca0._.js +2 -2
  124. package/web/.next/server/chunks/ssr/_d0a5c8be._.js +3 -0
  125. package/web/.next/server/chunks/ssr/{_1f958cbc._.js.map → _d0a5c8be._.js.map} +1 -1
  126. package/web/.next/server/chunks/ssr/_d8575088._.js +1 -1
  127. package/web/.next/server/chunks/ssr/_d8575088._.js.map +1 -1
  128. package/web/.next/server/chunks/ssr/_f39a1adb._.js +1 -1
  129. package/web/.next/server/chunks/ssr/_f39a1adb._.js.map +1 -1
  130. package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js +1 -1
  131. package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js.map +1 -1
  132. package/web/.next/server/chunks/ssr/src_presentation_web_0111eafd._.js +3 -0
  133. package/web/.next/server/chunks/ssr/{src_presentation_web_731afe96._.js.map → src_presentation_web_0111eafd._.js.map} +1 -1
  134. package/web/.next/server/chunks/ssr/src_presentation_web_8e7fe0d1._.js +2 -2
  135. package/web/.next/server/chunks/ssr/src_presentation_web_8e7fe0d1._.js.map +1 -1
  136. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_ca051134.js +1 -1
  137. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_ca051134.js.map +1 -1
  138. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1b176e3c.js +1 -1
  139. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1b176e3c.js.map +1 -1
  140. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_bd9f0dda.js +1 -1
  141. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_bd9f0dda.js.map +1 -1
  142. package/web/.next/server/chunks/ssr/src_presentation_web_b9023017._.js +2 -2
  143. package/web/.next/server/chunks/ssr/src_presentation_web_b9023017._.js.map +1 -1
  144. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
  145. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js.map +1 -1
  146. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js +1 -1
  147. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js.map +1 -1
  148. package/web/.next/server/chunks/ssr/src_presentation_web_components_ui_scroll-area_tsx_53ae5be7._.js +3 -0
  149. package/web/.next/server/chunks/ssr/src_presentation_web_components_ui_scroll-area_tsx_53ae5be7._.js.map +1 -0
  150. package/web/.next/server/pages/500.html +2 -2
  151. package/web/.next/server/server-reference-manifest.js +1 -1
  152. package/web/.next/server/server-reference-manifest.json +38 -38
  153. package/web/.next/static/chunks/{3d1b728f59ca0ae6.js → 260c376293ef313f.js} +1 -1
  154. package/web/.next/static/chunks/2f5948883382134c.js +1 -0
  155. package/web/.next/static/chunks/{39302cb19904d221.js → 3cdbfd1816f1dae6.js} +1 -1
  156. package/web/.next/static/chunks/5d8698f0d17c425f.js +2 -0
  157. package/web/.next/static/chunks/{321178bb6199a3e1.js → 60cfec53f7bd64a7.js} +1 -1
  158. package/web/.next/static/chunks/66e554b3fb85d07b.js +1 -0
  159. package/web/.next/static/chunks/72c889d2d1d2c950.js +1 -0
  160. package/web/.next/static/chunks/7486226f7ac8be26.js +1 -0
  161. package/web/.next/static/chunks/{d54a9af6cc2a0c58.js → 8bfc9acd71f1884f.js} +1 -1
  162. package/web/.next/static/chunks/9063a0bcaedece23.js +1 -0
  163. package/web/.next/static/chunks/{52bbd5a4f8ff74bd.js → 970d4d88a2e46223.js} +1 -1
  164. package/web/.next/static/chunks/a6b8b76506ed0486.js +1 -0
  165. package/web/.next/static/chunks/afbe5f1ead5cd56b.js +1 -0
  166. package/web/.next/static/chunks/{d4fbedd39504661f.js → c733b438f9c3dca2.js} +1 -1
  167. package/web/.next/static/chunks/{567a5fd9bc141bde.js → ccbca8058fb94fd9.js} +1 -1
  168. package/web/.next/static/chunks/cea224868e7b317e.css +1 -0
  169. package/web/.next/server/chunks/ssr/_1f958cbc._.js +0 -3
  170. package/web/.next/server/chunks/ssr/src_presentation_web_731afe96._.js +0 -3
  171. package/web/.next/static/chunks/314b8314f77d6921.js +0 -1
  172. package/web/.next/static/chunks/3e2d7d07926b05bf.css +0 -1
  173. package/web/.next/static/chunks/52d2796a174182a7.js +0 -1
  174. package/web/.next/static/chunks/6709ec190dbf8098.js +0 -1
  175. package/web/.next/static/chunks/688971d5bad5dc7c.js +0 -1
  176. package/web/.next/static/chunks/88a7aed01a628019.js +0 -1
  177. package/web/.next/static/chunks/9e13ad97d9c1c52a.js +0 -2
  178. package/web/.next/static/chunks/ddb9106ee55ddb62.js +0 -1
  179. /package/web/.next/static/{yv0Tq9-BtqVnENafaiFF6 → dLwf5BeiVENzBHuUxtSGx}/_buildManifest.js +0 -0
  180. /package/web/.next/static/{yv0Tq9-BtqVnENafaiFF6 → dLwf5BeiVENzBHuUxtSGx}/_clientMiddlewareManifest.json +0 -0
  181. /package/web/.next/static/{yv0Tq9-BtqVnENafaiFF6 → dLwf5BeiVENzBHuUxtSGx}/_ssgManifest.js +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"evidence.node.d.ts","sourceRoot":"","sources":["../../../../../../../../../packages/core/src/infrastructure/services/agents/feature-agent/nodes/evidence.node.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+DAA+D,CAAC;AAEpG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAoDrD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,cAAc,IAG3C,OAAO,iBAAiB,KAAG,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAmJ7E"}
1
+ {"version":3,"file":"evidence.node.d.ts","sourceRoot":"","sources":["../../../../../../../../../packages/core/src/infrastructure/services/agents/feature-agent/nodes/evidence.node.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+DAA+D,CAAC;AAEpG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAqDrD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,cAAc,IAG3C,OAAO,iBAAiB,KAAG,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAoJ7E"}
@@ -22,7 +22,7 @@
22
22
  */
23
23
  import yaml from 'js-yaml';
24
24
  import { isGraphBubbleUp } from '@langchain/langgraph';
25
- import { createNodeLogger, getCompletedPhases, markPhaseComplete, readSpecFile, retryExecute, buildExecutorOptions, } from './node-helpers.js';
25
+ import { createNodeLogger, getCompletedPhases, markPhaseComplete, readSpecFile, retryExecute, buildExecutorOptions, saveEvidenceManifest, } from './node-helpers.js';
26
26
  import { reportNodeStart } from '../heartbeat.js';
27
27
  import { recordPhaseStart, recordPhaseEnd } from '../phase-timing-context.js';
28
28
  import { updateNodeLifecycle } from '../lifecycle-context.js';
@@ -174,6 +174,7 @@ export function createEvidenceNode(executor) {
174
174
  throw new Error(`[evidence] ${message}`);
175
175
  }
176
176
  }
177
+ saveEvidenceManifest(state, allEvidence, log);
177
178
  markPhaseComplete(state.specDir, 'evidence', log);
178
179
  allMessages.unshift(`[evidence] Complete — ${allEvidence.length} evidence record(s) captured in ${attempt} attempt(s)`, ...(allEvidence.length === 0
179
180
  ? ['[evidence] Warning: no evidence records parsed from agent output']
@@ -1 +1 @@
1
- {"version":3,"file":"fast-implement.node.d.ts","sourceRoot":"","sources":["../../../../../../../../../packages/core/src/infrastructure/services/agents/feature-agent/nodes/fast-implement.node.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+DAA+D,CAAC;AAEpG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAgBrD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,cAAc,IAGhD,OAAO,iBAAiB,KAAG,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,CA0F7E"}
1
+ {"version":3,"file":"fast-implement.node.d.ts","sourceRoot":"","sources":["../../../../../../../../../packages/core/src/infrastructure/services/agents/feature-agent/nodes/fast-implement.node.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+DAA+D,CAAC;AAEpG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAiBrD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,cAAc,IAGhD,OAAO,iBAAiB,KAAG,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,CA0F7E"}
@@ -10,10 +10,8 @@
10
10
  * dependency, returns async (state) => Partial<FeatureAgentState>.
11
11
  */
12
12
  import { execSync } from 'node:child_process';
13
- import { mkdirSync, writeFileSync } from 'node:fs';
14
- import { join, dirname } from 'node:path';
15
13
  import { isGraphBubbleUp } from '@langchain/langgraph';
16
- import { createNodeLogger, buildExecutorOptions, retryExecute, getCompletedPhases, markPhaseComplete, } from './node-helpers.js';
14
+ import { createNodeLogger, buildExecutorOptions, retryExecute, getCompletedPhases, markPhaseComplete, saveEvidenceManifest, } from './node-helpers.js';
17
15
  import { reportNodeStart } from '../heartbeat.js';
18
16
  import { recordPhaseStart, recordPhaseEnd } from '../phase-timing-context.js';
19
17
  import { updateNodeLifecycle } from '../lifecycle-context.js';
@@ -178,23 +176,3 @@ function hasNewCommits(cwd) {
178
176
  return false;
179
177
  }
180
178
  }
181
- /**
182
- * Save evidence manifest to the shep home evidence folder so the
183
- * merge review UI can read it without accessing graph state.
184
- */
185
- function saveEvidenceManifest(state, evidence, log) {
186
- if (evidence.length === 0)
187
- return;
188
- try {
189
- const cwd = state.worktreePath || state.repositoryPath;
190
- const repoHashDir = dirname(dirname(cwd));
191
- const evidenceDir = join(repoHashDir, 'evidence', state.featureId);
192
- mkdirSync(evidenceDir, { recursive: true });
193
- writeFileSync(join(evidenceDir, 'manifest.json'), JSON.stringify(evidence, null, 2), 'utf-8');
194
- log.info(`Saved evidence manifest to ${evidenceDir}/manifest.json`);
195
- }
196
- catch (err) {
197
- const msg = err instanceof Error ? err.message : String(err);
198
- log.error(`Failed to save evidence manifest: ${msg}`);
199
- }
200
- }
@@ -5,7 +5,7 @@
5
5
  * and error handling so every node follows the same patterns.
6
6
  */
7
7
  import type { IAgentExecutor, AgentExecutionOptions, AgentExecutionResult } from '../../../../../application/ports/output/agents/agent-executor.interface.js';
8
- import type { ApprovalGates } from '../../../../../domain/generated/output.js';
8
+ import type { ApprovalGates, Evidence } from '../../../../../domain/generated/output.js';
9
9
  import type { FeatureAgentState } from '../state.js';
10
10
  /**
11
11
  * Create a scoped logger that prefixes messages with the node name.
@@ -168,4 +168,9 @@ export declare function buildResumeContext(resumeReason: string | undefined): st
168
168
  * - Consistent state shape for success and failure
169
169
  */
170
170
  export declare function executeNode(nodeName: string, executor: IAgentExecutor, buildPrompt: (state: FeatureAgentState, log: NodeLogger) => string): (state: FeatureAgentState) => Promise<Partial<FeatureAgentState>>;
171
+ /**
172
+ * Save evidence manifest to the shep home evidence folder so the
173
+ * merge review UI can read it without accessing graph state.
174
+ */
175
+ export declare function saveEvidenceManifest(state: FeatureAgentState, evidence: Evidence[], log: ReturnType<typeof createNodeLogger>): void;
171
176
  //# sourceMappingURL=node-helpers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"node-helpers.d.ts","sourceRoot":"","sources":["../../../../../../../../../packages/core/src/infrastructure/services/agents/feature-agent/nodes/node-helpers.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"node-helpers.d.ts","sourceRoot":"","sources":["../../../../../../../../../packages/core/src/infrastructure/services/agents/feature-agent/nodes/node-helpers.ts"],"names":[],"mappings":"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"}
@@ -5,8 +5,8 @@
5
5
  * and error handling so every node follows the same patterns.
6
6
  */
7
7
  import yaml from 'js-yaml';
8
- import { readFileSync, writeFileSync, renameSync, unlinkSync } from 'node:fs';
9
- import { join } from 'node:path';
8
+ import { mkdirSync, readFileSync, writeFileSync, renameSync, unlinkSync } from 'node:fs';
9
+ import { join, dirname } from 'node:path';
10
10
  import { interrupt, isGraphBubbleUp } from '@langchain/langgraph';
11
11
  import { hasSettings, getSettings } from '../../../../services/settings.service.js';
12
12
  import { reportNodeStart } from '../heartbeat.js';
@@ -525,3 +525,23 @@ export function executeNode(nodeName, executor, buildPrompt) {
525
525
  }
526
526
  };
527
527
  }
528
+ /**
529
+ * Save evidence manifest to the shep home evidence folder so the
530
+ * merge review UI can read it without accessing graph state.
531
+ */
532
+ export function saveEvidenceManifest(state, evidence, log) {
533
+ if (evidence.length === 0)
534
+ return;
535
+ try {
536
+ const cwd = state.worktreePath || state.repositoryPath;
537
+ const repoHashDir = dirname(dirname(cwd));
538
+ const evidenceDir = join(repoHashDir, 'evidence', state.featureId);
539
+ mkdirSync(evidenceDir, { recursive: true });
540
+ writeFileSync(join(evidenceDir, 'manifest.json'), JSON.stringify(evidence, null, 2), 'utf-8');
541
+ log.info(`Saved evidence manifest to ${evidenceDir}/manifest.json`);
542
+ }
543
+ catch (err) {
544
+ const msg = err instanceof Error ? err.message : String(err);
545
+ log.error(`Failed to save evidence manifest: ${msg}`);
546
+ }
547
+ }
@@ -1,4 +1,7 @@
1
+ import { type TreeViewElement } from '../../ui/file-tree.js';
1
2
  import type { MergeReviewFileDiff } from './merge-review-config.js';
3
+ /** Build a tree structure from flat file paths. */
4
+ export declare function buildFileTree(fileDiffs: MergeReviewFileDiff[]): TreeViewElement[];
2
5
  export interface DiffViewProps {
3
6
  fileDiffs: MergeReviewFileDiff[];
4
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"diff-view.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/merge-review/diff-view.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,mBAAmB,EAAuB,MAAM,uBAAuB,CAAC;AA4GtF,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,mBAAmB,EAAE,CAAC;CAClC;AAED,wBAAgB,QAAQ,CAAC,EAAE,SAAS,EAAE,EAAE,aAAa,kDAmBpD"}
1
+ {"version":3,"file":"diff-view.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/merge-review/diff-view.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAsB,KAAK,eAAe,EAAE,MAAM,2BAA2B,CAAC;AACrF,OAAO,KAAK,EAAE,mBAAmB,EAAuB,MAAM,uBAAuB,CAAC;AAStF,mDAAmD;AACnD,wBAAgB,aAAa,CAAC,SAAS,EAAE,mBAAmB,EAAE,GAAG,eAAe,EAAE,CAoCjF;AAgJD,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,mBAAmB,EAAE,CAAC;CAClC;AAED,wBAAgB,QAAQ,CAAC,EAAE,SAAS,EAAE,EAAE,aAAa,kDAkEpD"}
@@ -1,30 +1,113 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { useState } from 'react';
4
- import { ChevronRight, FileText, FilePlus, FileMinus, FileEdit } from 'lucide-react';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState, useMemo, useCallback } from 'react';
4
+ import { FileText, FilePlus, FileMinus, FileEdit } from 'lucide-react';
5
5
  import { cn } from '../../../lib/utils.js';
6
+ import { Tree, Folder, File } from '../../ui/file-tree.js';
6
7
  const STATUS_CONFIG = {
7
- added: { icon: FilePlus, label: 'A', className: 'text-green-600' },
8
- modified: { icon: FileEdit, label: 'M', className: 'text-amber-600' },
9
- deleted: { icon: FileMinus, label: 'D', className: 'text-red-600' },
10
- renamed: { icon: FileEdit, label: 'R', className: 'text-blue-600' },
8
+ added: { icon: FilePlus, className: 'text-green-600' },
9
+ modified: { icon: FileEdit, className: 'text-amber-600' },
10
+ deleted: { icon: FileMinus, className: 'text-red-600' },
11
+ renamed: { icon: FileEdit, className: 'text-blue-600' },
11
12
  };
12
- function FileStatusIcon({ status }) {
13
- const config = STATUS_CONFIG[status];
14
- const Icon = config.icon;
15
- return _jsx(Icon, { className: cn('h-3.5 w-3.5 shrink-0', config.className) });
13
+ /** Build a tree structure from flat file paths. */
14
+ export function buildFileTree(fileDiffs) {
15
+ const root = [];
16
+ for (const diff of fileDiffs) {
17
+ const parts = diff.path.split('/');
18
+ let currentLevel = root;
19
+ for (let i = 0; i < parts.length; i++) {
20
+ const part = parts[i];
21
+ const isFile = i === parts.length - 1;
22
+ const pathSoFar = parts.slice(0, i + 1).join('/');
23
+ const existing = currentLevel.find((el) => el.name === part && el.type === (isFile ? 'file' : 'folder'));
24
+ if (existing) {
25
+ if (!isFile && existing.children) {
26
+ currentLevel = existing.children;
27
+ }
28
+ }
29
+ else {
30
+ const node = {
31
+ id: pathSoFar,
32
+ name: part,
33
+ type: isFile ? 'file' : 'folder',
34
+ ...(isFile ? {} : { children: [] }),
35
+ };
36
+ currentLevel.push(node);
37
+ if (!isFile && node.children) {
38
+ currentLevel = node.children;
39
+ }
40
+ }
41
+ }
42
+ }
43
+ return collapseSingleChildFolders(root);
44
+ }
45
+ /** Collapse folders that contain only one child folder (e.g. src/components → src/components). */
46
+ function collapseSingleChildFolders(elements) {
47
+ return elements.map((el) => {
48
+ if (el.type === 'folder' && el.children) {
49
+ const collapsed = collapseSingleChildFolders(el.children);
50
+ if (collapsed.length === 1 && collapsed[0].type === 'folder' && collapsed[0].children) {
51
+ return {
52
+ ...collapsed[0],
53
+ id: `${el.id}/${collapsed[0].name}`,
54
+ name: `${el.name}/${collapsed[0].name}`,
55
+ children: collapsed[0].children,
56
+ };
57
+ }
58
+ return { ...el, children: collapsed };
59
+ }
60
+ return el;
61
+ });
62
+ }
63
+ /** Collect all folder IDs for expanding the tree by default. */
64
+ function getAllFolderIds(elements) {
65
+ const ids = [];
66
+ for (const el of elements) {
67
+ if (el.type === 'folder') {
68
+ ids.push(el.id);
69
+ if (el.children) {
70
+ ids.push(...getAllFolderIds(el.children));
71
+ }
72
+ }
73
+ }
74
+ return ids;
16
75
  }
17
76
  function HunkView({ hunk }) {
18
77
  return (_jsxs("div", { className: "border-border border-t first:border-t-0", children: [_jsx("div", { className: "bg-muted/50 text-muted-foreground px-3 py-1 font-mono text-[10px]", children: hunk.header }), _jsx("div", { className: "font-mono text-[11px] leading-[18px]", children: hunk.lines.map((line) => (_jsxs("div", { className: cn('flex', line.type === 'added' && 'bg-green-50 dark:bg-green-950/30', line.type === 'removed' && 'bg-red-50 dark:bg-red-950/30'), children: [_jsx("span", { className: "text-muted-foreground w-10 shrink-0 px-1 text-right text-[10px] select-none", children: line.oldNumber ?? '' }), _jsx("span", { className: "text-muted-foreground w-10 shrink-0 px-1 text-right text-[10px] select-none", children: line.newNumber ?? '' }), _jsx("span", { className: cn('w-4 shrink-0 text-center select-none', line.type === 'added' && 'text-green-700 dark:text-green-400', line.type === 'removed' && 'text-red-700 dark:text-red-400'), children: line.type === 'added' ? '+' : line.type === 'removed' ? '-' : ' ' }), _jsx("span", { className: "min-w-0 flex-1 pr-2 break-all whitespace-pre-wrap", children: line.content })] }, `${line.type}-${line.oldNumber ?? ''}-${line.newNumber ?? ''}`))) })] }));
19
78
  }
20
- function FileDiffItem({ file }) {
21
- const [isOpen, setIsOpen] = useState(false);
22
- const fileName = file.path.split('/').pop() ?? file.path;
23
- const dirPath = file.path.includes('/') ? file.path.slice(0, file.path.lastIndexOf('/')) : '';
24
- return (_jsxs("div", { className: "border-border border-b last:border-b-0", children: [_jsxs("button", { type: "button", onClick: () => setIsOpen(!isOpen), className: "hover:bg-muted/50 flex w-full items-center gap-2 px-3 py-2 text-left", children: [_jsx(ChevronRight, { className: cn('text-muted-foreground h-3 w-3 shrink-0 transition-transform duration-150', isOpen && 'rotate-90') }), _jsx(FileStatusIcon, { status: file.status }), _jsx("span", { className: "text-foreground min-w-0 flex-1 truncate text-xs", children: dirPath ? (_jsxs(_Fragment, { children: [_jsxs("span", { className: "text-muted-foreground", children: [dirPath, "/"] }), fileName] })) : (fileName) }), file.oldPath ? (_jsxs("span", { className: "text-muted-foreground truncate text-[10px]", children: ["\u2190 ", file.oldPath.split('/').pop()] })) : null, _jsxs("span", { className: "shrink-0 text-[10px]", children: [file.additions > 0 ? _jsxs("span", { className: "text-green-600", children: ["+", file.additions] }) : null, file.additions > 0 && file.deletions > 0 ? ' ' : null, file.deletions > 0 ? _jsxs("span", { className: "text-red-600", children: ["-", file.deletions] }) : null] })] }), isOpen && file.hunks.length > 0 ? (_jsx("div", { className: "border-border overflow-x-auto border-t", children: file.hunks.map((hunk) => (_jsx(HunkView, { hunk: hunk }, hunk.header))) })) : null] }));
79
+ function DiffFileIcon({ status }) {
80
+ const config = STATUS_CONFIG[status];
81
+ const Icon = config.icon;
82
+ return _jsx(Icon, { className: cn('size-4 shrink-0', config.className) });
83
+ }
84
+ function DiffStats({ additions, deletions }) {
85
+ return (_jsxs("span", { className: "ml-auto shrink-0 text-[10px]", children: [additions > 0 ? _jsxs("span", { className: "text-green-600", children: ["+", additions] }) : null, additions > 0 && deletions > 0 ? ' ' : null, deletions > 0 ? _jsxs("span", { className: "text-red-600", children: ["-", deletions] }) : null] }));
86
+ }
87
+ function FileTreeNode({ element, diffMap, selectedFile, onSelectFile, }) {
88
+ if (element.type === 'folder' && element.children) {
89
+ return (_jsx(Folder, { value: element.id, element: element.name, isSelectable: false, children: element.children.map((child) => (_jsx(FileTreeNode, { element: child, diffMap: diffMap, selectedFile: selectedFile, onSelectFile: onSelectFile }, child.id))) }, element.id));
90
+ }
91
+ const diff = diffMap.get(element.id);
92
+ const status = diff?.status ?? 'modified';
93
+ return (_jsx(File, { value: element.id, fileIcon: _jsx(DiffFileIcon, { status: status }), isSelect: selectedFile === element.id, handleSelect: onSelectFile, className: "w-full", children: _jsxs("span", { className: "flex min-w-0 flex-1 items-center gap-1", children: [_jsx("span", { className: "truncate", children: element.name }), diff?.oldPath ? (_jsxs("span", { className: "text-muted-foreground truncate text-[10px]", children: ["\u2190 ", diff.oldPath.split('/').pop()] })) : null, diff ? _jsx(DiffStats, { additions: diff.additions, deletions: diff.deletions }) : null] }) }, element.id));
25
94
  }
26
95
  export function DiffView({ fileDiffs }) {
96
+ const [selectedFile, setSelectedFile] = useState(null);
97
+ const treeElements = useMemo(() => buildFileTree(fileDiffs), [fileDiffs]);
98
+ const allFolderIds = useMemo(() => getAllFolderIds(treeElements), [treeElements]);
99
+ const diffMap = useMemo(() => {
100
+ const map = new Map();
101
+ for (const diff of fileDiffs) {
102
+ map.set(diff.path, diff);
103
+ }
104
+ return map;
105
+ }, [fileDiffs]);
106
+ const handleSelectFile = useCallback((path) => {
107
+ setSelectedFile((prev) => (prev === path ? null : path));
108
+ }, []);
109
+ const selectedDiff = selectedFile ? diffMap.get(selectedFile) : null;
27
110
  if (fileDiffs.length === 0)
28
111
  return null;
29
- return (_jsxs("div", { className: "border-border rounded-lg border", children: [_jsx("div", { className: "px-4 py-3", children: _jsxs("div", { className: "mb-2 flex items-center gap-2", children: [_jsx(FileText, { className: "text-muted-foreground h-4 w-4" }), _jsx("span", { className: "text-foreground text-xs font-semibold", children: "Changed Files" }), _jsxs("span", { className: "text-muted-foreground text-[10px]", children: ["(", fileDiffs.length, ")"] })] }) }), _jsx("div", { className: "border-border border-t", children: fileDiffs.map((file) => (_jsx(FileDiffItem, { file: file }, `${file.status}-${file.path}`))) })] }));
112
+ return (_jsxs("div", { className: "border-border rounded-lg border", children: [_jsx("div", { className: "px-4 py-3", children: _jsxs("div", { className: "mb-2 flex items-center gap-2", children: [_jsx(FileText, { className: "text-muted-foreground h-4 w-4" }), _jsx("span", { className: "text-foreground text-xs font-semibold", children: "Changed Files" }), _jsxs("span", { className: "text-muted-foreground text-[10px]", children: ["(", fileDiffs.length, ")"] })] }) }), _jsxs("div", { className: "border-border border-t", children: [_jsx("div", { className: "py-2", children: _jsx(Tree, { elements: treeElements, initialExpandedItems: allFolderIds, indicator: true, sort: "none", className: "text-xs", children: treeElements.map((element) => (_jsx(FileTreeNode, { element: element, diffMap: diffMap, selectedFile: selectedFile, onSelectFile: handleSelectFile }, element.id))) }) }), selectedDiff && selectedDiff.hunks.length > 0 ? (_jsxs("div", { className: "border-border overflow-x-auto border-t", children: [_jsxs("div", { className: "bg-muted/30 flex items-center gap-2 px-3 py-1.5", children: [_jsx(DiffFileIcon, { status: selectedDiff.status }), _jsx("span", { className: "text-foreground font-mono text-xs", children: selectedDiff.path }), _jsx(DiffStats, { additions: selectedDiff.additions, deletions: selectedDiff.deletions })] }), selectedDiff.hunks.map((hunk) => (_jsx(HunkView, { hunk: hunk }, hunk.header)))] })) : null] })] }));
30
113
  }
@@ -3,7 +3,7 @@ import { DiffView } from './diff-view.js';
3
3
  declare const meta: Meta<typeof DiffView>;
4
4
  export default meta;
5
5
  type Story = StoryObj<typeof DiffView>;
6
- /** Default — multiple files with various change types. */
6
+ /** Default — multiple files with various change types shown in a file tree. */
7
7
  export declare const Default: Story;
8
8
  /** Single modified file. */
9
9
  export declare const SingleFile: Story;
@@ -15,4 +15,6 @@ export declare const OnlyDeletions: Story;
15
15
  export declare const RenamedFile: Story;
16
16
  /** Empty diff list — renders nothing. */
17
17
  export declare const EmptyDiffs: Story;
18
+ /** Root-level files without directory nesting. */
19
+ export declare const RootLevelFiles: Story;
18
20
  //# sourceMappingURL=diff-view.stories.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"diff-view.stories.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/merge-review/diff-view.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AA2HvC,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,QAAQ,CAuB/B,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,QAAQ,CAAC,CAAC;AAEvC,0DAA0D;AAC1D,eAAO,MAAM,OAAO,EAAE,KAIrB,CAAC;AAEF,4BAA4B;AAC5B,eAAO,MAAM,UAAU,EAAE,KAIxB,CAAC;AAEF,4BAA4B;AAC5B,eAAO,MAAM,aAAa,EAAE,KAI3B,CAAC;AAEF,0BAA0B;AAC1B,eAAO,MAAM,aAAa,EAAE,KAI3B,CAAC;AAEF,oBAAoB;AACpB,eAAO,MAAM,WAAW,EAAE,KAIzB,CAAC;AAEF,yCAAyC;AACzC,eAAO,MAAM,UAAU,EAAE,KAIxB,CAAC"}
1
+ {"version":3,"file":"diff-view.stories.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/merge-review/diff-view.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AA2HvC,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,QAAQ,CAuB/B,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,QAAQ,CAAC,CAAC;AAEvC,+EAA+E;AAC/E,eAAO,MAAM,OAAO,EAAE,KAIrB,CAAC;AAEF,4BAA4B;AAC5B,eAAO,MAAM,UAAU,EAAE,KAIxB,CAAC;AAEF,4BAA4B;AAC5B,eAAO,MAAM,aAAa,EAAE,KAI3B,CAAC;AAEF,0BAA0B;AAC1B,eAAO,MAAM,aAAa,EAAE,KAI3B,CAAC;AAEF,oBAAoB;AACpB,eAAO,MAAM,WAAW,EAAE,KAIzB,CAAC;AAEF,yCAAyC;AACzC,eAAO,MAAM,UAAU,EAAE,KAIxB,CAAC;AAEF,kDAAkD;AAClD,eAAO,MAAM,cAAc,EAAE,KA4B5B,CAAC"}
@@ -136,7 +136,7 @@ const meta = {
136
136
  ],
137
137
  };
138
138
  export default meta;
139
- /** Default — multiple files with various change types. */
139
+ /** Default — multiple files with various change types shown in a file tree. */
140
140
  export const Default = {
141
141
  args: {
142
142
  fileDiffs: sampleFiles,
@@ -172,3 +172,33 @@ export const EmptyDiffs = {
172
172
  fileDiffs: [],
173
173
  },
174
174
  };
175
+ /** Root-level files without directory nesting. */
176
+ export const RootLevelFiles = {
177
+ args: {
178
+ fileDiffs: [
179
+ {
180
+ path: 'package.json',
181
+ additions: 3,
182
+ deletions: 1,
183
+ status: 'modified',
184
+ hunks: [
185
+ {
186
+ header: '@@ -10,3 +10,5 @@',
187
+ lines: [
188
+ { type: 'context', content: ' "dependencies": {', oldNumber: 10, newNumber: 10 },
189
+ { type: 'added', content: ' "new-dep": "^1.0.0",', newNumber: 11 },
190
+ { type: 'context', content: ' }', oldNumber: 11, newNumber: 12 },
191
+ ],
192
+ },
193
+ ],
194
+ },
195
+ {
196
+ path: 'README.md',
197
+ additions: 10,
198
+ deletions: 0,
199
+ status: 'modified',
200
+ hunks: [],
201
+ },
202
+ ],
203
+ },
204
+ };
@@ -0,0 +1,39 @@
1
+ import React from 'react';
2
+ import { Accordion as AccordionPrimitive } from 'radix-ui';
3
+ interface TreeViewElement {
4
+ id: string;
5
+ name: string;
6
+ type?: 'file' | 'folder';
7
+ isSelectable?: boolean;
8
+ children?: TreeViewElement[];
9
+ }
10
+ type TreeSortMode = 'default' | 'none' | ((a: TreeViewElement, b: TreeViewElement) => number);
11
+ declare const Tree: React.ForwardRefExoticComponent<{
12
+ initialSelectedId?: string;
13
+ indicator?: boolean;
14
+ elements?: TreeViewElement[];
15
+ initialExpandedItems?: string[];
16
+ openIcon?: React.ReactNode;
17
+ closeIcon?: React.ReactNode;
18
+ sort?: TreeSortMode;
19
+ } & Omit<Omit<AccordionPrimitive.AccordionSingleProps & React.RefAttributes<HTMLDivElement>, "ref"> | Omit<AccordionPrimitive.AccordionMultipleProps & React.RefAttributes<HTMLDivElement>, "ref">, "type" | "value" | "defaultValue" | "onValueChange"> & React.RefAttributes<HTMLDivElement>>;
20
+ declare const Folder: React.ForwardRefExoticComponent<{
21
+ expandedItems?: string[];
22
+ element: string;
23
+ isSelectable?: boolean;
24
+ isSelect?: boolean;
25
+ } & Omit<AccordionPrimitive.AccordionItemProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
26
+ declare const File: React.ForwardRefExoticComponent<{
27
+ value: string;
28
+ handleSelect?: (id: string) => void;
29
+ isSelectable?: boolean;
30
+ isSelect?: boolean;
31
+ fileIcon?: React.ReactNode;
32
+ } & React.ButtonHTMLAttributes<HTMLButtonElement> & React.RefAttributes<HTMLButtonElement>>;
33
+ declare const CollapseButton: React.ForwardRefExoticComponent<{
34
+ elements: TreeViewElement[];
35
+ expandAll?: boolean;
36
+ } & React.HTMLAttributes<HTMLButtonElement> & React.RefAttributes<HTMLButtonElement>>;
37
+ export { CollapseButton, File, Folder, Tree, type TreeViewElement };
38
+ export type { TreeSortMode };
39
+ //# sourceMappingURL=file-tree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-tree.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/web/components/ui/file-tree.tsx"],"names":[],"mappings":"AAEA,OAAO,KAON,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,SAAS,IAAI,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAO3D,UAAU,eAAe;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IACzB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,EAAE,eAAe,EAAE,CAAC;CAC9B;AAED,KAAK,YAAY,GAAG,SAAS,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,CAAC,EAAE,eAAe,KAAK,MAAM,CAAC,CAAC;AAsH9F,QAAA,MAAM,IAAI;wBAZY,MAAM;gBACd,OAAO;eACR,eAAe,EAAE;2BACL,MAAM,EAAE;eACpB,KAAK,CAAC,SAAS;gBACd,KAAK,CAAC,SAAS;WACpB,YAAY;+RA4GpB,CAAC;AA+BF,QAAA,MAAM,MAAM;oBANM,MAAM,EAAE;aACf,MAAM;mBACA,OAAO;eACX,OAAO;0KAuDnB,CAAC;AAIF,QAAA,MAAM,IAAI;WAGC,MAAM;mBACE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI;mBACpB,OAAO;eACX,OAAO;eACP,KAAK,CAAC,SAAS;2FA6C7B,CAAC;AAIF,QAAA,MAAM,cAAc;cAGN,eAAe,EAAE;gBACf,OAAO;qFAmDrB,CAAC;AAIH,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,eAAe,EAAE,CAAC;AACpE,YAAY,EAAE,YAAY,EAAE,CAAC"}
@@ -0,0 +1,194 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { createContext, forwardRef, useCallback, useContext, useEffect, useState, } from 'react';
4
+ import { Accordion as AccordionPrimitive } from 'radix-ui';
5
+ import { FileIcon, FolderIcon, FolderOpenIcon } from 'lucide-react';
6
+ import { cn } from '../../lib/utils.js';
7
+ import { Button } from '../ui/button.js';
8
+ import { ScrollArea } from '../ui/scroll-area.js';
9
+ const TreeContext = createContext(null);
10
+ const useTree = () => {
11
+ const context = useContext(TreeContext);
12
+ if (!context) {
13
+ throw new Error('useTree must be used within a TreeProvider');
14
+ }
15
+ return context;
16
+ };
17
+ const isFolderElement = (element) => {
18
+ if (element.type) {
19
+ return element.type === 'folder';
20
+ }
21
+ return Array.isArray(element.children);
22
+ };
23
+ const mergeExpandedItems = (currentItems, nextItems) => [
24
+ ...new Set([...(currentItems ?? []), ...nextItems]),
25
+ ];
26
+ const treeCollator = new Intl.Collator('en', {
27
+ numeric: true,
28
+ sensitivity: 'base',
29
+ });
30
+ const defaultTreeComparator = (a, b) => {
31
+ const aIsFolder = isFolderElement(a);
32
+ const bIsFolder = isFolderElement(b);
33
+ if (aIsFolder !== bIsFolder) {
34
+ return aIsFolder ? -1 : 1;
35
+ }
36
+ return treeCollator.compare(a.name, b.name);
37
+ };
38
+ const getTreeComparator = (sort) => {
39
+ if (sort === 'none') {
40
+ return undefined;
41
+ }
42
+ if (sort === 'default') {
43
+ return defaultTreeComparator;
44
+ }
45
+ return sort;
46
+ };
47
+ const sortTreeElements = (elements, sort) => {
48
+ const comparator = getTreeComparator(sort);
49
+ const nextElements = elements.map((element) => {
50
+ if (!Array.isArray(element.children)) {
51
+ return element;
52
+ }
53
+ return {
54
+ ...element,
55
+ children: sortTreeElements(element.children, sort),
56
+ };
57
+ });
58
+ if (!comparator) {
59
+ return nextElements;
60
+ }
61
+ return [...nextElements].sort(comparator);
62
+ };
63
+ const renderTreeElements = (elements, sort) => sortTreeElements(elements, sort).map((element) => {
64
+ if (isFolderElement(element)) {
65
+ return (_jsx(Folder, { value: element.id, element: element.name, isSelectable: element.isSelectable, children: Array.isArray(element.children) ? renderTreeElements(element.children, sort) : null }, element.id));
66
+ }
67
+ return (_jsx(File, { value: element.id, isSelectable: element.isSelectable, children: _jsx("span", { children: element.name }) }, element.id));
68
+ });
69
+ const Tree = forwardRef(({ className, elements, initialSelectedId, initialExpandedItems, children, indicator = true, openIcon, closeIcon, sort = 'default', dir, ...props }, ref) => {
70
+ const [selectedId, setSelectedId] = useState(initialSelectedId);
71
+ const [expandedItems, setExpandedItems] = useState(initialExpandedItems);
72
+ const selectItem = useCallback((id) => {
73
+ setSelectedId(id);
74
+ }, []);
75
+ const handleExpand = useCallback((id) => {
76
+ setExpandedItems((prev) => {
77
+ if (prev?.includes(id)) {
78
+ return prev.filter((item) => item !== id);
79
+ }
80
+ return [...(prev ?? []), id];
81
+ });
82
+ }, []);
83
+ const expandSpecificTargetedElements = useCallback((elements, selectId) => {
84
+ if (!elements || !selectId)
85
+ return;
86
+ const findParent = (currentElement, currentPath = []) => {
87
+ const isSelectable = currentElement.isSelectable ?? true;
88
+ const newPath = [...currentPath, currentElement.id];
89
+ if (currentElement.id === selectId) {
90
+ if (isSelectable) {
91
+ setExpandedItems((prev) => mergeExpandedItems(prev, newPath));
92
+ }
93
+ else {
94
+ if (newPath.includes(currentElement.id)) {
95
+ newPath.pop();
96
+ setExpandedItems((prev) => mergeExpandedItems(prev, newPath));
97
+ }
98
+ }
99
+ return;
100
+ }
101
+ if (Array.isArray(currentElement.children) && currentElement.children.length > 0) {
102
+ currentElement.children.forEach((child) => {
103
+ findParent(child, newPath);
104
+ });
105
+ }
106
+ };
107
+ elements.forEach((element) => {
108
+ findParent(element);
109
+ });
110
+ }, []);
111
+ useEffect(() => {
112
+ if (initialSelectedId) {
113
+ expandSpecificTargetedElements(elements, initialSelectedId);
114
+ }
115
+ }, [initialSelectedId, elements, expandSpecificTargetedElements]);
116
+ const direction = dir === 'rtl' ? 'rtl' : 'ltr';
117
+ const treeChildren = children ?? (elements ? renderTreeElements(elements, sort) : null);
118
+ return (_jsx(TreeContext.Provider, { value: {
119
+ selectedId,
120
+ expandedItems,
121
+ handleExpand,
122
+ selectItem,
123
+ setExpandedItems,
124
+ indicator,
125
+ openIcon,
126
+ closeIcon,
127
+ direction,
128
+ }, children: _jsx("div", { className: cn('size-full', className), children: _jsx(ScrollArea, { ref: ref, className: "relative h-full px-2", dir: dir, children: _jsx(AccordionPrimitive.Root, { ...props, type: "multiple", value: expandedItems, className: "flex flex-col gap-1", dir: dir, children: treeChildren }) }) }) }));
129
+ });
130
+ Tree.displayName = 'Tree';
131
+ const TreeIndicator = forwardRef(({ className, ...props }, ref) => {
132
+ const { direction } = useTree();
133
+ return (_jsx("div", { dir: direction, ref: ref, className: cn('bg-muted absolute left-1.5 h-full w-px rounded-md py-3 duration-300 ease-in-out hover:bg-slate-300 rtl:right-1.5', className), ...props }));
134
+ });
135
+ TreeIndicator.displayName = 'TreeIndicator';
136
+ const Folder = forwardRef(({ className, element, value, isSelectable = true, isSelect, children, ...props }, ref) => {
137
+ const { direction, handleExpand, expandedItems, indicator, selectedId, selectItem, openIcon, closeIcon, } = useTree();
138
+ const isSelected = isSelect ?? selectedId === value;
139
+ return (_jsxs(AccordionPrimitive.Item, { ref: ref, ...props, value: value, className: "relative h-full overflow-hidden", children: [_jsxs(AccordionPrimitive.Trigger, { className: cn('flex items-center gap-1 rounded-md text-sm', className, {
140
+ 'bg-muted rounded-md': isSelected && isSelectable,
141
+ 'cursor-pointer': isSelectable,
142
+ 'cursor-not-allowed opacity-50': !isSelectable,
143
+ }), disabled: !isSelectable, onClick: () => {
144
+ selectItem(value);
145
+ handleExpand(value);
146
+ }, children: [expandedItems?.includes(value)
147
+ ? (openIcon ?? _jsx(FolderOpenIcon, { className: "size-4" }))
148
+ : (closeIcon ?? _jsx(FolderIcon, { className: "size-4" })), _jsx("span", { children: element })] }), _jsxs(AccordionPrimitive.Content, { className: "data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down relative h-full overflow-hidden text-sm", children: [element && indicator ? _jsx(TreeIndicator, { "aria-hidden": "true" }) : null, _jsx(AccordionPrimitive.Root, { dir: direction, type: "multiple", className: "ml-5 flex flex-col gap-1 py-1 rtl:mr-5", value: expandedItems, children: children })] })] }));
149
+ });
150
+ Folder.displayName = 'Folder';
151
+ const File = forwardRef(({ value, className, handleSelect, onClick, isSelectable = true, isSelect, fileIcon, children, ...props }, ref) => {
152
+ const { direction, selectedId, selectItem } = useTree();
153
+ const isSelected = isSelect ?? selectedId === value;
154
+ return (_jsxs("button", { ref: ref, type: "button", disabled: !isSelectable, className: cn('flex w-fit items-center gap-1 rounded-md pr-1 text-sm duration-200 ease-in-out rtl:pr-0 rtl:pl-1', {
155
+ 'bg-muted': isSelected && isSelectable,
156
+ }, isSelectable ? 'cursor-pointer' : 'cursor-not-allowed opacity-50', direction === 'rtl' ? 'rtl' : 'ltr', className), onClick: (event) => {
157
+ selectItem(value);
158
+ handleSelect?.(value);
159
+ onClick?.(event);
160
+ }, ...props, children: [fileIcon ?? _jsx(FileIcon, { className: "size-4" }), children] }));
161
+ });
162
+ File.displayName = 'File';
163
+ const CollapseButton = forwardRef(({ className, elements, expandAll = false, children, ...props }, ref) => {
164
+ const { expandedItems, setExpandedItems } = useTree();
165
+ const expendAllTree = useCallback((elements) => {
166
+ const expandedElementIds = [];
167
+ const expandTree = (element) => {
168
+ const isSelectable = element.isSelectable ?? true;
169
+ if (isSelectable && element.children && element.children.length > 0) {
170
+ expandedElementIds.push(element.id);
171
+ for (const child of element.children) {
172
+ expandTree(child);
173
+ }
174
+ }
175
+ };
176
+ for (const element of elements) {
177
+ expandTree(element);
178
+ }
179
+ return [...new Set(expandedElementIds)];
180
+ }, []);
181
+ const closeAll = useCallback(() => {
182
+ setExpandedItems?.([]);
183
+ }, [setExpandedItems]);
184
+ useEffect(() => {
185
+ if (expandAll) {
186
+ setExpandedItems?.(expendAllTree(elements));
187
+ }
188
+ }, [expandAll, elements, expendAllTree, setExpandedItems]);
189
+ return (_jsxs(Button, { variant: "ghost", className: cn('absolute right-2 bottom-1 h-8 w-fit p-1', className), onClick: expandedItems && expandedItems.length > 0
190
+ ? closeAll
191
+ : () => setExpandedItems?.(expendAllTree(elements)), ref: ref, ...props, children: [children, _jsx("span", { className: "sr-only", children: "Toggle" })] }));
192
+ });
193
+ CollapseButton.displayName = 'CollapseButton';
194
+ export { CollapseButton, File, Folder, Tree };
@@ -0,0 +1,14 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Tree } from './file-tree.js';
3
+ declare const meta: Meta<typeof Tree>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof Tree>;
6
+ /** Default file tree with folders expanded. */
7
+ export declare const Default: Story;
8
+ /** Collapsed tree — no items initially expanded. */
9
+ export declare const Collapsed: Story;
10
+ /** Single file — no folders. */
11
+ export declare const SingleFile: Story;
12
+ /** Deeply nested structure. */
13
+ export declare const DeepNesting: Story;
14
+ //# sourceMappingURL=file-tree.stories.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-tree.stories.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/web/components/ui/file-tree.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,IAAI,EAAwB,MAAM,aAAa,CAAC;AAkCzD,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,IAAI,CAc3B,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAEnC,+CAA+C;AAC/C,eAAO,MAAM,OAAO,EAAE,KAKrB,CAAC;AAEF,oDAAoD;AACpD,eAAO,MAAM,SAAS,EAAE,KAIvB,CAAC;AAEF,gCAAgC;AAChC,eAAO,MAAM,UAAU,EAAE,KAIxB,CAAC;AAEF,+BAA+B;AAC/B,eAAO,MAAM,WAAW,EAAE,KA0BzB,CAAC"}