@shepai/cli 1.52.2 → 1.53.1

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 (186) hide show
  1. package/apis/json-schema/CiFixRecord.yaml +24 -0
  2. package/apis/json-schema/Feature.yaml +3 -0
  3. package/apis/json-schema/PullRequest.yaml +10 -0
  4. package/apis/json-schema/SdlcLifecycle.yaml +1 -0
  5. package/apis/json-schema/WorkflowConfig.yaml +15 -0
  6. package/dist/packages/core/src/application/ports/output/repositories/feature-repository.interface.d.ts +8 -0
  7. package/dist/packages/core/src/application/ports/output/repositories/feature-repository.interface.d.ts.map +1 -1
  8. package/dist/packages/core/src/application/ports/output/services/git-pr-service.interface.d.ts +12 -0
  9. package/dist/packages/core/src/application/ports/output/services/git-pr-service.interface.d.ts.map +1 -1
  10. package/dist/packages/core/src/application/use-cases/features/check-and-unblock-features.use-case.d.ts +30 -0
  11. package/dist/packages/core/src/application/use-cases/features/check-and-unblock-features.use-case.d.ts.map +1 -0
  12. package/dist/packages/core/src/application/use-cases/features/check-and-unblock-features.use-case.js +74 -0
  13. package/dist/packages/core/src/application/use-cases/features/create/create-feature.use-case.d.ts.map +1 -1
  14. package/dist/packages/core/src/application/use-cases/features/create/create-feature.use-case.js +60 -16
  15. package/dist/packages/core/src/application/use-cases/features/create/types.d.ts +2 -0
  16. package/dist/packages/core/src/application/use-cases/features/create/types.d.ts.map +1 -1
  17. package/dist/packages/core/src/application/use-cases/features/delete-feature.use-case.d.ts.map +1 -1
  18. package/dist/packages/core/src/application/use-cases/features/delete-feature.use-case.js +11 -4
  19. package/dist/packages/core/src/application/use-cases/features/update/update-feature-lifecycle.use-case.d.ts +28 -0
  20. package/dist/packages/core/src/application/use-cases/features/update/update-feature-lifecycle.use-case.d.ts.map +1 -0
  21. package/dist/packages/core/src/application/use-cases/features/update/update-feature-lifecycle.use-case.js +53 -0
  22. package/dist/packages/core/src/domain/generated/output.d.ts +47 -1
  23. package/dist/packages/core/src/domain/generated/output.d.ts.map +1 -1
  24. package/dist/packages/core/src/domain/generated/output.js +1 -0
  25. package/dist/packages/core/src/domain/lifecycle-gates.d.ts +16 -0
  26. package/dist/packages/core/src/domain/lifecycle-gates.d.ts.map +1 -0
  27. package/dist/packages/core/src/domain/lifecycle-gates.js +19 -0
  28. package/dist/packages/core/src/infrastructure/di/container.d.ts.map +1 -1
  29. package/dist/packages/core/src/infrastructure/di/container.js +12 -0
  30. package/dist/packages/core/src/infrastructure/persistence/sqlite/mappers/feature.mapper.d.ts +3 -0
  31. package/dist/packages/core/src/infrastructure/persistence/sqlite/mappers/feature.mapper.d.ts.map +1 -1
  32. package/dist/packages/core/src/infrastructure/persistence/sqlite/mappers/feature.mapper.js +8 -0
  33. package/dist/packages/core/src/infrastructure/persistence/sqlite/migrations.d.ts.map +1 -1
  34. package/dist/packages/core/src/infrastructure/persistence/sqlite/migrations.js +28 -1
  35. package/dist/packages/core/src/infrastructure/repositories/sqlite-feature.repository.d.ts +1 -0
  36. package/dist/packages/core/src/infrastructure/repositories/sqlite-feature.repository.d.ts.map +1 -1
  37. package/dist/packages/core/src/infrastructure/repositories/sqlite-feature.repository.js +12 -0
  38. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/feature-agent-graph.d.ts +52 -1
  39. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/feature-agent-graph.d.ts.map +1 -1
  40. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/feature-agent-graph.js +1 -1
  41. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/feature-agent-worker.d.ts.map +1 -1
  42. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/feature-agent-worker.js +7 -2
  43. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/lifecycle-context.d.ts +27 -5
  44. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/lifecycle-context.d.ts.map +1 -1
  45. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/lifecycle-context.js +19 -16
  46. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/merge/ci-helpers.d.ts +24 -0
  47. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/merge/ci-helpers.d.ts.map +1 -0
  48. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/merge/ci-helpers.js +47 -0
  49. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/merge/ci-watch-fix-loop.d.ts +43 -0
  50. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/merge/ci-watch-fix-loop.d.ts.map +1 -0
  51. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/merge/ci-watch-fix-loop.js +118 -0
  52. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/merge/merge-output-parser.d.ts.map +1 -0
  53. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/{merge.node.d.ts → merge/merge.node.d.ts} +10 -4
  54. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/merge/merge.node.d.ts.map +1 -0
  55. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/{merge.node.js → merge/merge.node.js} +44 -17
  56. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/prompts/merge-prompts.d.ts +12 -0
  57. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/prompts/merge-prompts.d.ts.map +1 -1
  58. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/prompts/merge-prompts.js +38 -0
  59. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/state.d.ts +4 -1
  60. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/state.d.ts.map +1 -1
  61. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/state.js +13 -0
  62. package/dist/packages/core/src/infrastructure/services/git/git-pr.service.d.ts +2 -0
  63. package/dist/packages/core/src/infrastructure/services/git/git-pr.service.d.ts.map +1 -1
  64. package/dist/packages/core/src/infrastructure/services/git/git-pr.service.js +29 -1
  65. package/dist/src/presentation/cli/commands/feat/ls.command.d.ts.map +1 -1
  66. package/dist/src/presentation/cli/commands/feat/ls.command.js +40 -3
  67. package/dist/src/presentation/cli/commands/feat/new.command.d.ts.map +1 -1
  68. package/dist/src/presentation/cli/commands/feat/new.command.js +18 -0
  69. package/dist/src/presentation/cli/commands/feat/show.command.d.ts.map +1 -1
  70. package/dist/src/presentation/cli/commands/feat/show.command.js +20 -0
  71. package/dist/src/presentation/web/app/actions/create-feature.d.ts +1 -0
  72. package/dist/src/presentation/web/app/actions/create-feature.d.ts.map +1 -1
  73. package/dist/src/presentation/web/app/actions/create-feature.js +2 -1
  74. package/dist/src/presentation/web/app/page.js +34 -33
  75. package/dist/src/presentation/web/components/common/add-repository-node/add-repository-button.d.ts +6 -0
  76. package/dist/src/presentation/web/components/common/add-repository-node/add-repository-button.d.ts.map +1 -0
  77. package/dist/src/presentation/web/components/common/add-repository-node/add-repository-button.js +23 -0
  78. package/dist/src/presentation/web/components/common/add-repository-node/index.d.ts +1 -0
  79. package/dist/src/presentation/web/components/common/add-repository-node/index.d.ts.map +1 -1
  80. package/dist/src/presentation/web/components/common/add-repository-node/index.js +1 -0
  81. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.d.ts +11 -1
  82. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.d.ts.map +1 -1
  83. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.js +52 -5
  84. package/dist/src/presentation/web/components/common/feature-node/derive-feature-state.d.ts.map +1 -1
  85. package/dist/src/presentation/web/components/common/feature-node/derive-feature-state.js +4 -0
  86. package/dist/src/presentation/web/components/common/feature-node/feature-node.js +1 -1
  87. package/dist/src/presentation/web/components/features/control-center/control-center-inner.d.ts.map +1 -1
  88. package/dist/src/presentation/web/components/features/control-center/control-center-inner.js +13 -4
  89. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts +2 -0
  90. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts.map +1 -1
  91. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.js +26 -8
  92. package/dist/src/presentation/web/components/features/features-canvas/dependency-edge.d.ts +7 -0
  93. package/dist/src/presentation/web/components/features/features-canvas/dependency-edge.d.ts.map +1 -0
  94. package/dist/src/presentation/web/components/features/features-canvas/dependency-edge.js +20 -0
  95. package/dist/src/presentation/web/components/features/features-canvas/dependency-edge.stories.d.ts +6 -0
  96. package/dist/src/presentation/web/components/features/features-canvas/dependency-edge.stories.d.ts.map +1 -0
  97. package/dist/src/presentation/web/components/features/features-canvas/dependency-edge.stories.js +58 -0
  98. package/dist/src/presentation/web/components/features/features-canvas/features-canvas.d.ts.map +1 -1
  99. package/dist/src/presentation/web/components/features/features-canvas/features-canvas.js +7 -3
  100. package/dist/tsconfig.build.tsbuildinfo +1 -1
  101. package/package.json +1 -1
  102. package/web/.next/BUILD_ID +1 -1
  103. package/web/.next/build-manifest.json +2 -2
  104. package/web/.next/cache/.previewinfo +1 -1
  105. package/web/.next/cache/.rscinfo +1 -1
  106. package/web/.next/cache/.tsbuildinfo +1 -1
  107. package/web/.next/cache/config.json +3 -3
  108. package/web/.next/fallback-build-manifest.json +2 -2
  109. package/web/.next/prerender-manifest.json +3 -3
  110. package/web/.next/required-server-files.js +1 -1
  111. package/web/.next/required-server-files.json +1 -1
  112. package/web/.next/server/app/_global-error.html +2 -2
  113. package/web/.next/server/app/_global-error.rsc +1 -1
  114. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  115. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  116. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  117. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  118. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  119. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  120. package/web/.next/server/app/page/server-reference-manifest.json +14 -14
  121. package/web/.next/server/app/page.js.nft.json +1 -1
  122. package/web/.next/server/app/page_client-reference-manifest.js +1 -1
  123. package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  124. package/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js +2 -2
  125. package/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js.map +1 -1
  126. package/web/.next/server/chunks/ssr/[root-of-the-server]__249c74f6._.js +1 -1
  127. package/web/.next/server/chunks/ssr/[root-of-the-server]__2d1a8e37._.js +2 -2
  128. package/web/.next/server/chunks/ssr/[root-of-the-server]__2d1a8e37._.js.map +1 -1
  129. package/web/.next/server/chunks/ssr/_1fafa9db._.js +1 -1
  130. package/web/.next/server/chunks/ssr/_1fafa9db._.js.map +1 -1
  131. package/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js +1 -1
  132. package/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js.map +1 -1
  133. package/web/.next/server/pages/500.html +2 -2
  134. package/web/.next/server/server-reference-manifest.js +1 -1
  135. package/web/.next/server/server-reference-manifest.json +15 -15
  136. package/web/.next/standalone/src/presentation/web/.next/BUILD_ID +1 -1
  137. package/web/.next/standalone/src/presentation/web/.next/build-manifest.json +2 -2
  138. package/web/.next/standalone/src/presentation/web/.next/prerender-manifest.json +3 -3
  139. package/web/.next/standalone/src/presentation/web/.next/required-server-files.json +1 -1
  140. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.html +2 -2
  141. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.rsc +1 -1
  142. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  143. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  144. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  145. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  146. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  147. package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  148. package/web/.next/standalone/src/presentation/web/.next/server/app/page/server-reference-manifest.json +14 -14
  149. package/web/.next/standalone/src/presentation/web/.next/server/app/page.js.nft.json +1 -1
  150. package/web/.next/standalone/src/presentation/web/.next/server/app/page_client-reference-manifest.js +1 -1
  151. package/web/.next/standalone/src/presentation/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  152. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js +2 -2
  153. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__249c74f6._.js +1 -1
  154. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__2d1a8e37._.js +2 -2
  155. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_1fafa9db._.js +1 -1
  156. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js +1 -1
  157. package/web/.next/standalone/src/presentation/web/.next/server/pages/500.html +2 -2
  158. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.js +1 -1
  159. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.json +15 -15
  160. package/web/.next/standalone/src/presentation/web/app/actions/create-feature.ts +4 -1
  161. package/web/.next/standalone/src/presentation/web/app/page.tsx +36 -36
  162. package/web/.next/standalone/src/presentation/web/components/common/add-repository-node/add-repository-button.tsx +43 -0
  163. package/web/.next/standalone/src/presentation/web/components/common/add-repository-node/index.ts +1 -0
  164. package/web/.next/standalone/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.tsx +199 -2
  165. package/web/.next/standalone/src/presentation/web/components/common/feature-node/derive-feature-state.ts +5 -0
  166. package/web/.next/standalone/src/presentation/web/components/common/feature-node/feature-node.tsx +1 -1
  167. package/web/.next/standalone/src/presentation/web/components/features/control-center/control-center-inner.tsx +20 -1
  168. package/web/.next/standalone/src/presentation/web/components/features/control-center/use-control-center-state.ts +43 -12
  169. package/web/.next/standalone/src/presentation/web/components/features/features-canvas/dependency-edge.stories.tsx +81 -0
  170. package/web/.next/standalone/src/presentation/web/components/features/features-canvas/dependency-edge.tsx +29 -0
  171. package/web/.next/standalone/src/presentation/web/components/features/features-canvas/features-canvas.tsx +16 -2
  172. package/web/.next/standalone/src/presentation/web/server.js +1 -1
  173. package/web/.next/static/chunks/212e3c682d3b7f50.js +10 -0
  174. package/web/.next/static/chunks/476fe030d242c153.css +2 -0
  175. package/web/.next/static/chunks/{31e5030eed01a941.js → 68fb91b6eca14536.js} +1 -1
  176. package/web/.next/trace +1 -1
  177. package/web/.next/trace-build +1 -1
  178. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/merge-output-parser.d.ts.map +0 -1
  179. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/merge.node.d.ts.map +0 -1
  180. package/web/.next/static/chunks/23cae32e6f42833d.js +0 -10
  181. package/web/.next/static/chunks/59b8bd47023e0249.css +0 -2
  182. /package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/{merge-output-parser.d.ts → merge/merge-output-parser.d.ts} +0 -0
  183. /package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/{merge-output-parser.js → merge/merge-output-parser.js} +0 -0
  184. /package/web/.next/static/{-c2lwSbfsfZHNnfB5CogT → EWkET8BUeCcZJxcYQpxDY}/_buildManifest.js +0 -0
  185. /package/web/.next/static/{-c2lwSbfsfZHNnfB5CogT → EWkET8BUeCcZJxcYQpxDY}/_clientMiddlewareManifest.json +0 -0
  186. /package/web/.next/static/{-c2lwSbfsfZHNnfB5CogT → EWkET8BUeCcZJxcYQpxDY}/_ssgManifest.js +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../../../../../../packages/core/src/infrastructure/services/agents/feature-agent/state.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAElE;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqEjC,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,OAAO,sBAAsB,CAAC,KAAK,CAAC"}
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../../../../../../packages/core/src/infrastructure/services/agents/feature-agent/state.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAE/E;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkFjC,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,OAAO,sBAAsB,CAAC,KAAK,CAAC"}
@@ -75,4 +75,17 @@ export const FeatureAgentAnnotation = Annotation.Root({
75
75
  reducer: (_prev, next) => next,
76
76
  default: () => false,
77
77
  }),
78
+ // --- CI watch/fix loop state ---
79
+ ciFixAttempts: Annotation({
80
+ reducer: (_prev, next) => next,
81
+ default: () => 0,
82
+ }),
83
+ ciFixHistory: Annotation({
84
+ reducer: (prev, next) => [...prev, ...next],
85
+ default: () => [],
86
+ }),
87
+ ciFixStatus: Annotation({
88
+ reducer: (_prev, next) => next,
89
+ default: () => 'idle',
90
+ }),
78
91
  });
@@ -23,6 +23,8 @@ export declare class GitPrService implements IGitPrService {
23
23
  deleteBranch(cwd: string, branch: string, deleteRemote?: boolean): Promise<void>;
24
24
  getPrDiffSummary(cwd: string, baseBranch: string): Promise<DiffSummary>;
25
25
  verifyMerge(cwd: string, featureBranch: string, baseBranch: string): Promise<boolean>;
26
+ getFailureLogs(runId: string, _branch: string, logMaxChars?: number): Promise<string>;
27
+ private truncateLog;
26
28
  private parseGitError;
27
29
  private parseGhError;
28
30
  private parsePrNumberFromUrl;
@@ -1 +1 @@
1
- {"version":3,"file":"git-pr.service.d.ts","sourceRoot":"","sources":["../../../../../../../packages/core/src/infrastructure/services/git/git-pr.service.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wEAAwE,CAAC;AAC5G,OAAO,KAAK,EACV,cAAc,EACd,WAAW,EACX,aAAa,EACb,cAAc,EACf,MAAM,wEAAwE,CAAC;AAOhF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D,qBACa,YAAa,YAAW,aAAa;IACZ,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,YAAY;IAErE,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKxC,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAuC9C,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKpD,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAWxD,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAYvE,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAuClE,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,GAAE,aAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAUzF,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUnF,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAkBjE,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAuBjF,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAWhF,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAevE,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAW3F,OAAO,CAAC,aAAa;IAcrB,OAAO,CAAC,YAAY;IAepB,OAAO,CAAC,oBAAoB;IAK5B,OAAO,CAAC,aAAa;CAiBtB"}
1
+ {"version":3,"file":"git-pr.service.d.ts","sourceRoot":"","sources":["../../../../../../../packages/core/src/infrastructure/services/git/git-pr.service.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wEAAwE,CAAC;AAC5G,OAAO,KAAK,EACV,cAAc,EACd,WAAW,EACX,aAAa,EACb,cAAc,EACf,MAAM,wEAAwE,CAAC;AAOhF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D,qBACa,YAAa,YAAW,aAAa;IACZ,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,YAAY;IAErE,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKxC,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAuC9C,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKpD,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAWxD,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAYvE,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAuClE,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,GAAE,aAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAUzF,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUnF,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAkBjE,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAwCjF,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAWhF,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAevE,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAWrF,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC;IAW3F,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,aAAa;IAcrB,OAAO,CAAC,YAAY;IAepB,OAAO,CAAC,oBAAoB;IAK5B,OAAO,CAAC,aAAa;CAiBtB"}
@@ -155,7 +155,15 @@ let GitPrService = class GitPrService {
155
155
  }
156
156
  async watchCi(cwd, branch, timeoutMs) {
157
157
  try {
158
- const args = ['run', 'watch', '--branch', branch];
158
+ // gh run watch requires a run ID — it does not support --branch.
159
+ // First, resolve the latest run ID for the branch via gh run list.
160
+ const { stdout: listOut } = await this.execFile('gh', ['run', 'list', '--branch', branch, '--json', 'databaseId', '--limit', '1'], { cwd });
161
+ const runs = JSON.parse(listOut);
162
+ if (runs.length === 0 || !runs[0].databaseId) {
163
+ return { status: 'pending' };
164
+ }
165
+ const runId = String(runs[0].databaseId);
166
+ const args = ['run', 'watch', runId, '--exit-status'];
159
167
  const { stdout } = await this.execFile('gh', args, {
160
168
  cwd,
161
169
  ...(timeoutMs ? { timeout: timeoutMs } : {}),
@@ -172,6 +180,10 @@ let GitPrService = class GitPrService {
172
180
  if (message.includes('timed out') || message.includes('timeout')) {
173
181
  throw new GitPrError(message, GitPrErrorCode.CI_TIMEOUT, cause);
174
182
  }
183
+ // gh run watch --exit-status exits non-zero when the run fails
184
+ if (message.includes('exit code') || message.includes('exit status')) {
185
+ return { status: 'failure', logExcerpt: message };
186
+ }
175
187
  throw new GitPrError(message, GitPrErrorCode.GIT_ERROR, cause);
176
188
  }
177
189
  }
@@ -202,6 +214,22 @@ let GitPrService = class GitPrService {
202
214
  return false;
203
215
  }
204
216
  }
217
+ async getFailureLogs(runId, _branch, logMaxChars = 50_000) {
218
+ try {
219
+ const { stdout } = await this.execFile('gh', ['run', 'view', runId, '--log-failed'], {
220
+ cwd: undefined,
221
+ });
222
+ return this.truncateLog(stdout, logMaxChars, runId);
223
+ }
224
+ catch (error) {
225
+ throw this.parseGhError(error);
226
+ }
227
+ }
228
+ truncateLog(output, maxChars, runId) {
229
+ if (output.length <= maxChars)
230
+ return output;
231
+ return `${output.slice(0, maxChars)}\n[Log truncated at ${maxChars} chars — full log available via gh run view ${runId}]`;
232
+ }
205
233
  parseGitError(error) {
206
234
  const message = error instanceof Error ? error.message : String(error);
207
235
  const cause = error instanceof Error ? error : undefined;
@@ -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;AA8JpC,wBAAgB,eAAe,IAAI,OAAO,CA8DzC"}
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;AAoKpC,wBAAgB,eAAe,IAAI,OAAO,CAgGzC"}
@@ -22,6 +22,7 @@ const NODE_TO_PHASE = {
22
22
  research: 'Researching',
23
23
  plan: 'Planning',
24
24
  implement: 'Implementing',
25
+ merge: 'Merging',
25
26
  };
26
27
  /** Map graph node names to review action labels. */
27
28
  const NODE_TO_REVIEW = {
@@ -46,6 +47,10 @@ const STATUS_PRIORITY = {
46
47
  * Returns the current graph node as a phase label with an activity indicator.
47
48
  */
48
49
  function formatStatus(feature, run) {
50
+ // Blocked features show "Waiting" — the parent relationship is conveyed by tree indentation
51
+ if (feature.lifecycle === 'Blocked') {
52
+ return `${colors.warning(symbols.dotEmpty)} ${colors.warning('Blocked')}`;
53
+ }
49
54
  if (!run) {
50
55
  return `${colors.muted(symbols.dotEmpty)} ${colors.muted(feature.lifecycle)}`;
51
56
  }
@@ -172,11 +177,43 @@ export function createLsCommand() {
172
177
  const paired = features
173
178
  .map((feature, i) => ({ feature, run: runs[i], phases: timings[i] }))
174
179
  .sort((a, b) => getStatusPriority(a.run) - getStatusPriority(b.run));
175
- const rows = paired.map(({ feature, run, phases }) => {
180
+ const childrenByParent = new Map();
181
+ const roots = [];
182
+ for (const entry of paired) {
183
+ const pid = entry.feature.parentId;
184
+ if (pid && entry.feature.lifecycle === 'Blocked') {
185
+ const list = childrenByParent.get(pid) ?? [];
186
+ list.push(entry);
187
+ childrenByParent.set(pid, list);
188
+ }
189
+ else {
190
+ roots.push(entry);
191
+ }
192
+ }
193
+ // Flatten: parent then its blocked children
194
+ const ordered = [];
195
+ for (const entry of roots) {
196
+ ordered.push({ entry, indent: false });
197
+ const kids = childrenByParent.get(entry.feature.id);
198
+ if (kids) {
199
+ for (const kid of kids) {
200
+ ordered.push({ entry: kid, indent: true });
201
+ }
202
+ childrenByParent.delete(entry.feature.id);
203
+ }
204
+ }
205
+ // Append orphaned blocked children (parent not in current list)
206
+ for (const kids of childrenByParent.values()) {
207
+ for (const kid of kids) {
208
+ ordered.push({ entry: kid, indent: false });
209
+ }
210
+ }
211
+ const rows = ordered.map(({ entry: { feature, run, phases }, indent }) => {
176
212
  const repo = path.basename(feature.repositoryPath);
213
+ const prefix = indent ? `${colors.muted('└')} ` : '';
177
214
  return [
178
- feature.id.slice(0, 8),
179
- truncate(feature.name, 30),
215
+ prefix + feature.id.slice(0, 8),
216
+ prefix + truncate(feature.name, indent ? 28 : 30),
180
217
  formatStatus(feature, run),
181
218
  colors.muted(repo),
182
219
  formatGates(feature),
@@ -1 +1 @@
1
- {"version":3,"file":"new.command.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/cli/commands/feat/new.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA8CpC;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAwF1C"}
1
+ {"version":3,"file":"new.command.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/cli/commands/feat/new.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAiDpC;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CA4G1C"}
@@ -14,6 +14,7 @@ import { createHash } from 'node:crypto';
14
14
  import { join } from 'node:path';
15
15
  import { container } from '../../../../../packages/core/src/infrastructure/di/container.js';
16
16
  import { CreateFeatureUseCase } from '../../../../../packages/core/src/application/use-cases/features/create/create-feature.use-case.js';
17
+ import { SdlcLifecycle } from '../../../../../packages/core/src/domain/generated/output.js';
17
18
  import { colors, messages, spinner } from '../../ui/index.js';
18
19
  import { getShepHomeDir } from '../../../../../packages/core/src/infrastructure/services/filesystem/shep-directory.service.js';
19
20
  import { getSettings, hasSettings } from '../../../../../packages/core/src/infrastructure/services/settings.service.js';
@@ -46,6 +47,7 @@ export function createNewCommand() {
46
47
  .option('--allow-plan', 'Auto-approve through planning, pause at implementation')
47
48
  .option('--allow-merge', 'Auto-approve merge phase')
48
49
  .option('--allow-all', 'Run fully autonomous (no approval pauses)')
50
+ .option('--parent <fid>', 'Parent feature ID (full or partial prefix)')
49
51
  .action(async (description, options) => {
50
52
  try {
51
53
  const useCase = container.resolve(CreateFeatureUseCase);
@@ -62,12 +64,25 @@ export function createNewCommand() {
62
64
  allowMerge: options.allowMerge ?? defaults.allowMerge,
63
65
  };
64
66
  const push = options.push ?? defaults.push;
67
+ // Resolve parent feature ID if --parent flag is provided
68
+ let parentId;
69
+ if (options.parent) {
70
+ const featureRepo = container.resolve('IFeatureRepository');
71
+ const parentFeature = await featureRepo.findByIdPrefix(options.parent);
72
+ if (!parentFeature) {
73
+ messages.error(`Parent feature not found: ${options.parent}`);
74
+ process.exitCode = 1;
75
+ return;
76
+ }
77
+ parentId = parentFeature.id;
78
+ }
65
79
  const result = await spinner('Thinking', () => useCase.execute({
66
80
  userInput: description,
67
81
  repositoryPath: repoPath,
68
82
  approvalGates,
69
83
  push,
70
84
  openPr,
85
+ ...(parentId !== undefined && { parentId }),
71
86
  }));
72
87
  const { feature, warning } = result;
73
88
  const repoHash = createHash('sha256').update(repoPath).digest('hex').slice(0, 16);
@@ -78,6 +93,9 @@ export function createNewCommand() {
78
93
  messages.warning(warning);
79
94
  }
80
95
  messages.success('Feature created');
96
+ if (feature.lifecycle === SdlcLifecycle.Blocked) {
97
+ messages.info(`Feature created in Blocked state — waiting for parent to reach Implementation`);
98
+ }
81
99
  console.log(` ${colors.muted('ID:')} ${colors.accent(feature.id)}`);
82
100
  console.log(` ${colors.muted('Name:')} ${feature.name}`);
83
101
  console.log(` ${colors.muted('Branch:')} ${colors.accent(feature.branch)}`);
@@ -1 +1 @@
1
- {"version":3,"file":"show.command.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/cli/commands/feat/show.command.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,OAAO,KAAK,EAEV,QAAQ,EACR,WAAW,EACX,sBAAsB,EACvB,MAAM,8BAA8B,CAAC;AAmOtC;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,WAAW,EAAE,EACtB,UAAU,EAAE,QAAQ,GAAG,IAAI,EAC3B,UAAU,GAAE,sBAAsB,EAAO,GACxC,MAAM,EAAE,CAkJV;AAED,wBAAgB,iBAAiB,IAAI,OAAO,CAyJ3C"}
1
+ {"version":3,"file":"show.command.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/cli/commands/feat/show.command.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,OAAO,KAAK,EAEV,QAAQ,EACR,WAAW,EACX,sBAAsB,EACvB,MAAM,8BAA8B,CAAC;AAmOtC;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,WAAW,EAAE,EACtB,UAAU,EAAE,QAAQ,GAAG,IAAI,EAC3B,UAAU,GAAE,sBAAsB,EAAO,GACxC,MAAM,EAAE,CAkJV;AAED,wBAAgB,iBAAiB,IAAI,OAAO,CAgL3C"}
@@ -368,6 +368,20 @@ export function createShowCommand() {
368
368
  const lines = renderPhaseTimings(timings, run, rejections);
369
369
  textBlocks.push({ title: 'Phase Timing', content: lines.join('\n') });
370
370
  }
371
+ if (feature.pr?.ciFixHistory && feature.pr.ciFixHistory.length > 0) {
372
+ const fixLines = feature.pr.ciFixHistory.map((r) => {
373
+ const outcomeColor = r.outcome === 'fixed'
374
+ ? colors.success
375
+ : r.outcome === 'timeout'
376
+ ? colors.warning
377
+ : colors.error;
378
+ return ` #${r.attempt} ${outcomeColor(r.outcome)} ${colors.muted(r.startedAt)} ${r.failureSummary.slice(0, 60)}`;
379
+ });
380
+ textBlocks.push({
381
+ title: 'CI Fix History',
382
+ content: fixLines.join('\n'),
383
+ });
384
+ }
371
385
  if (run?.status === 'waiting_approval') {
372
386
  const phase = run.result?.startsWith('node:')
373
387
  ? (NODE_TO_PHASE[run.result.slice(5)] ?? run.result.slice(5))
@@ -456,6 +470,12 @@ export function createShowCommand() {
456
470
  : colors.warning(feature.pr.ciStatus)
457
471
  : null,
458
472
  },
473
+ {
474
+ label: 'CI Fixes',
475
+ value: feature.pr.ciFixAttempts && feature.pr.ciFixAttempts > 0
476
+ ? `${feature.pr.ciFixAttempts} attempt(s)`
477
+ : null,
478
+ },
459
479
  ],
460
480
  },
461
481
  ]
@@ -15,6 +15,7 @@ interface CreateFeatureInput {
15
15
  };
16
16
  push?: boolean;
17
17
  openPr?: boolean;
18
+ parentId?: string;
18
19
  }
19
20
  export declare function createFeature(input: CreateFeatureInput): Promise<{
20
21
  feature?: Feature;
@@ -1 +1 @@
1
- {"version":3,"file":"create-feature.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/web/app/actions/create-feature.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sCAAsC,CAAC;AAEpE,UAAU,UAAU;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAQD,UAAU,kBAAkB;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B,aAAa,CAAC,EAAE;QACd,QAAQ,EAAE,OAAO,CAAC;QAClB,SAAS,EAAE,OAAO,CAAC;QACnB,UAAU,CAAC,EAAE,OAAO,CAAC;KACtB,CAAC;IACF,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAqBD,wBAAsB,aAAa,CACjC,KAAK,EAAE,kBAAkB,GACxB,OAAO,CAAC;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAgChD"}
1
+ {"version":3,"file":"create-feature.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/web/app/actions/create-feature.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sCAAsC,CAAC;AAEpE,UAAU,UAAU;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAQD,UAAU,kBAAkB;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B,aAAa,CAAC,EAAE;QACd,QAAQ,EAAE,OAAO,CAAC;QAClB,SAAS,EAAE,OAAO,CAAC;QACnB,UAAU,CAAC,EAAE,OAAO,CAAC;KACtB,CAAC;IACF,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAqBD,wBAAsB,aAAa,CACjC,KAAK,EAAE,kBAAkB,GACxB,OAAO,CAAC;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAkChD"}
@@ -12,7 +12,7 @@ function composeUserInput(name, description, attachments) {
12
12
  return userInput;
13
13
  }
14
14
  export async function createFeature(input) {
15
- const { name, description, repositoryPath, attachments, approvalGates, push, openPr } = input;
15
+ const { name, description, repositoryPath, attachments, approvalGates, push, openPr, parentId } = input;
16
16
  if (!name?.trim()) {
17
17
  return { error: 'name is required' };
18
18
  }
@@ -33,6 +33,7 @@ export async function createFeature(input) {
33
33
  approvalGates: gates,
34
34
  push: push ?? false,
35
35
  openPr: openPr ?? false,
36
+ ...(parentId ? { parentId } : {}),
36
37
  });
37
38
  return { feature: result.feature };
38
39
  }
@@ -60,6 +60,14 @@ export default async function HomePage() {
60
60
  : ((agentNode ? nodeToLifecyclePhase[agentNode] : undefined) ??
61
61
  lifecycleMap[feature.lifecycle] ??
62
62
  'requirements');
63
+ // Resolve blockedBy display name from parent feature
64
+ let blockedBy;
65
+ if (feature.parentId && feature.lifecycle === 'Blocked') {
66
+ const parentEntry = featuresWithRuns.find((e) => e.feature.id === feature.parentId);
67
+ if (parentEntry) {
68
+ blockedBy = parentEntry.feature.name;
69
+ }
70
+ }
63
71
  const nodeData = {
64
72
  name: feature.name,
65
73
  description: feature.description ?? feature.slug,
@@ -72,6 +80,7 @@ export default async function HomePage() {
72
80
  progress: deriveProgress(feature),
73
81
  ...(run?.agentType && { agentType: run.agentType }),
74
82
  ...(run?.error && { errorMessage: run.error }),
83
+ ...(blockedBy && { blockedBy }),
75
84
  };
76
85
  const featureNodeId = `feat-${feature.id}`;
77
86
  nodes.push({
@@ -80,46 +89,38 @@ export default async function HomePage() {
80
89
  position: { x: 0, y: 0 },
81
90
  data: nodeData,
82
91
  });
83
- edges.push({
84
- id: `edge-${repoNodeId}-${featureNodeId}`,
85
- source: repoNodeId,
86
- target: featureNodeId,
87
- style: { strokeDasharray: '5 5' },
88
- });
92
+ // Child features connect via parent→child dependency edge, not directly to repo
93
+ if (!feature.parentId) {
94
+ edges.push({
95
+ id: `edge-${repoNodeId}-${featureNodeId}`,
96
+ source: repoNodeId,
97
+ target: featureNodeId,
98
+ style: { strokeDasharray: '5 5' },
99
+ });
100
+ }
89
101
  });
90
102
  }
103
+ // Add parent→child dependency edges
104
+ for (const { feature } of featuresWithRuns) {
105
+ if (feature.parentId) {
106
+ const parentNodeId = `feat-${feature.parentId}`;
107
+ const childNodeId = `feat-${feature.id}`;
108
+ // Only add edge if both nodes exist on the canvas
109
+ if (nodes.some((n) => n.id === parentNodeId) && nodes.some((n) => n.id === childNodeId)) {
110
+ edges.push({
111
+ id: `dep-${parentNodeId}-${childNodeId}`,
112
+ source: parentNodeId,
113
+ target: childNodeId,
114
+ type: 'dependencyEdge',
115
+ });
116
+ }
117
+ }
118
+ }
91
119
  // Use dagre LR layout for compact, automatic positioning
92
120
  const laid = layoutWithDagre(nodes, edges, {
93
121
  direction: 'LR',
94
122
  ranksep: 200,
95
123
  nodesep: 15,
96
124
  });
97
- // Position "+ Add Repository" below the last repo node (no-features case) or
98
- // vertically centered with a bottom feature node (features case).
99
- const repoNodes = laid.nodes.filter((n) => n.type === 'repositoryNode');
100
- const featureNodes = laid.nodes.filter((n) => n.type === 'featureNode');
101
- const repoX = repoNodes[0]?.position.x ?? 0;
102
- let addRepoPosition;
103
- if (featureNodes.length > 0) {
104
- // Mirror how dagre centers repo nodes with their connected features
105
- const sortedFeatures = [...featureNodes].sort((a, b) => a.position.y - b.position.y);
106
- const centerIdx = Math.floor(sortedFeatures.length / 2);
107
- const targetFeature = sortedFeatures[centerIdx + 1] ?? sortedFeatures[sortedFeatures.length - 1];
108
- // Center the add-repo node (h=50) with the target feature node (h=140)
109
- addRepoPosition = { x: repoX, y: targetFeature.position.y + 70 - 25 };
110
- }
111
- else {
112
- // No features — place below the bottom-most repo node with a gap
113
- const lastRepoY = repoNodes.length > 0 ? Math.max(...repoNodes.map((n) => n.position.y)) : 0;
114
- const repoHeight = 50;
115
- const gap = 200;
116
- addRepoPosition = { x: repoX, y: lastRepoY + repoHeight + gap };
117
- }
118
- laid.nodes.push({
119
- id: 'add-repository',
120
- type: 'addRepositoryNode',
121
- position: addRepoPosition,
122
- data: {},
123
- });
124
125
  return (_jsx("div", { className: "h-screen w-full", children: _jsx(ControlCenter, { initialNodes: laid.nodes, initialEdges: laid.edges }) }));
125
126
  }
@@ -0,0 +1,6 @@
1
+ interface AddRepositoryButtonProps {
2
+ onSelect?: (path: string) => void;
3
+ }
4
+ export declare function AddRepositoryButton({ onSelect }: AddRepositoryButtonProps): import("react/jsx-runtime").JSX.Element;
5
+ export {};
6
+ //# sourceMappingURL=add-repository-button.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add-repository-button.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/add-repository-node/add-repository-button.tsx"],"names":[],"mappings":"AAMA,UAAU,wBAAwB;IAChC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAED,wBAAgB,mBAAmB,CAAC,EAAE,QAAQ,EAAE,EAAE,wBAAwB,2CAgCzE"}
@@ -0,0 +1,23 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState } from 'react';
4
+ import { Plus, Loader2 } from 'lucide-react';
5
+ import { pickFolder } from './pick-folder.js';
6
+ export function AddRepositoryButton({ onSelect }) {
7
+ const [loading, setLoading] = useState(false);
8
+ async function handleClick() {
9
+ if (loading)
10
+ return;
11
+ setLoading(true);
12
+ try {
13
+ const path = await pickFolder();
14
+ if (path) {
15
+ onSelect?.(path);
16
+ }
17
+ }
18
+ finally {
19
+ setLoading(false);
20
+ }
21
+ }
22
+ return (_jsxs("button", { type: "button", "data-testid": "add-repository-button", onClick: handleClick, disabled: loading, className: "border-muted-foreground/30 flex cursor-pointer items-center gap-2 rounded-full border-2 border-dashed px-3 py-1.5 text-xs transition-colors hover:border-blue-500 hover:text-blue-500 disabled:cursor-wait disabled:opacity-60", children: [loading ? (_jsx(Loader2, { className: "h-3.5 w-3.5 shrink-0 animate-spin" })) : (_jsx(Plus, { className: "h-3.5 w-3.5 shrink-0" })), _jsx("span", { className: "font-medium", children: loading ? 'Opening...' : 'Add Repository' })] }));
23
+ }
@@ -1,3 +1,4 @@
1
1
  export { AddRepositoryNode } from './add-repository-node.js';
2
+ export { AddRepositoryButton } from './add-repository-button.js';
2
3
  export type { AddRepositoryNodeData, AddRepositoryNodeType } from './add-repository-node-config.js';
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/add-repository-node/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,YAAY,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/add-repository-node/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,YAAY,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC"}
@@ -1 +1,2 @@
1
1
  export { AddRepositoryNode } from './add-repository-node.js';
2
+ export { AddRepositoryButton } from './add-repository-button.js';
@@ -1,6 +1,11 @@
1
1
  import type { FileAttachment } from '../../../../../../packages/core/src/infrastructure/services/file-dialog.service.js';
2
2
  import type { WorkflowDefaults } from '../../../app/actions/get-workflow-defaults.js';
3
3
  export type { FileAttachment } from '../../../../../../packages/core/src/infrastructure/services/file-dialog.service.js';
4
+ /** Minimal feature descriptor for the parent selector. */
5
+ export interface ParentFeatureOption {
6
+ id: string;
7
+ name: string;
8
+ }
4
9
  export interface FeatureCreatePayload {
5
10
  name: string;
6
11
  description?: string;
@@ -13,6 +18,7 @@ export interface FeatureCreatePayload {
13
18
  };
14
19
  push: boolean;
15
20
  openPr: boolean;
21
+ parentId?: string;
16
22
  }
17
23
  export interface FeatureCreateDrawerProps {
18
24
  open: boolean;
@@ -21,6 +27,10 @@ export interface FeatureCreateDrawerProps {
21
27
  repositoryPath: string;
22
28
  isSubmitting?: boolean;
23
29
  workflowDefaults?: WorkflowDefaults;
30
+ /** List of existing features available for selection as a parent. */
31
+ features?: ParentFeatureOption[];
32
+ /** Pre-select a parent feature when the drawer opens (e.g. from (+) button on a feature node). */
33
+ initialParentId?: string;
24
34
  }
25
- export declare function FeatureCreateDrawer({ open, onClose, onSubmit, repositoryPath, isSubmitting, workflowDefaults, }: FeatureCreateDrawerProps): import("react/jsx-runtime").JSX.Element;
35
+ export declare function FeatureCreateDrawer({ open, onClose, onSubmit, repositoryPath, isSubmitting, workflowDefaults, features, initialParentId, }: FeatureCreateDrawerProps): import("react/jsx-runtime").JSX.Element;
26
36
  //# sourceMappingURL=feature-create-drawer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"feature-create-drawer.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.tsx"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0DAA0D,CAAC;AAC/F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAC;AAG5E,YAAY,EAAE,cAAc,EAAE,MAAM,0DAA0D,CAAC;AAE/F,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,cAAc,EAAE,CAAC;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE;QACb,QAAQ,EAAE,OAAO,CAAC;QAClB,SAAS,EAAE,OAAO,CAAC;QACnB,UAAU,EAAE,OAAO,CAAC;KACrB,CAAC;IACF,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;CACjB;AAcD,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,CAAC,IAAI,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC/C,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC;AAED,wBAAgB,mBAAmB,CAAC,EAClC,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,cAAc,EACd,YAAoB,EACpB,gBAAgB,GACjB,EAAE,wBAAwB,2CA2O1B"}
1
+ {"version":3,"file":"feature-create-drawer.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.tsx"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0DAA0D,CAAC;AAC/F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAC;AAG5E,YAAY,EAAE,cAAc,EAAE,MAAM,0DAA0D,CAAC;AAE/F,0DAA0D;AAC1D,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,cAAc,EAAE,CAAC;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE;QACb,QAAQ,EAAE,OAAO,CAAC;QAClB,SAAS,EAAE,OAAO,CAAC;QACnB,UAAU,EAAE,OAAO,CAAC;KACrB,CAAC;IACF,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAcD,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,CAAC,IAAI,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC/C,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,qEAAqE;IACrE,QAAQ,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACjC,kGAAkG;IAClG,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAgB,mBAAmB,CAAC,EAClC,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,cAAc,EACd,YAAoB,EACpB,gBAAgB,EAChB,QAAQ,EACR,eAAe,GAChB,EAAE,wBAAwB,2CAmR1B"}
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useState, useCallback, useEffect } from 'react';
4
- import { XIcon, PlusIcon, FileIcon, FileTextIcon, ImageIcon, CodeIcon, Trash2Icon, } from 'lucide-react';
3
+ import { useState, useCallback, useEffect, useRef } from 'react';
4
+ import { XIcon, PlusIcon, FileIcon, FileTextIcon, ImageIcon, CodeIcon, Trash2Icon, ChevronsUpDown, CheckIcon, } from 'lucide-react';
5
5
  import { cn } from '../../../lib/utils.js';
6
6
  import { Drawer, DrawerContent, DrawerHeader, DrawerTitle, DrawerDescription, DrawerFooter, } from '../../ui/drawer.js';
7
7
  import { Button } from '../../ui/button.js';
@@ -12,6 +12,7 @@ import { CheckboxGroup } from '../../ui/checkbox-group.js';
12
12
  import { CheckboxGroupItem } from '../../ui/checkbox-group-item.js';
13
13
  import { Separator } from '../../ui/separator.js';
14
14
  import { Badge } from '../../ui/badge.js';
15
+ import { Popover, PopoverContent, PopoverTrigger } from '../../ui/popover.js';
15
16
  import { pickFiles } from './pick-files.js';
16
17
  const AUTO_APPROVE_OPTIONS = [
17
18
  { id: 'allowPrd', label: 'PRD', description: 'Auto-approve requirements move to planning.' },
@@ -23,7 +24,7 @@ const EMPTY_GATES = {
23
24
  allowPlan: false,
24
25
  allowMerge: false,
25
26
  };
26
- export function FeatureCreateDrawer({ open, onClose, onSubmit, repositoryPath, isSubmitting = false, workflowDefaults, }) {
27
+ export function FeatureCreateDrawer({ open, onClose, onSubmit, repositoryPath, isSubmitting = false, workflowDefaults, features, initialParentId, }) {
27
28
  const defaultGates = workflowDefaults?.approvalGates ?? EMPTY_GATES;
28
29
  const defaultPush = workflowDefaults?.push ?? false;
29
30
  const defaultOpenPr = workflowDefaults?.openPr ?? false;
@@ -33,6 +34,7 @@ export function FeatureCreateDrawer({ open, onClose, onSubmit, repositoryPath, i
33
34
  const [approvalGates, setApprovalGates] = useState({ ...defaultGates });
34
35
  const [push, setPush] = useState(defaultPush);
35
36
  const [openPr, setOpenPr] = useState(defaultOpenPr);
37
+ const [parentId, setParentId] = useState(undefined);
36
38
  // Sync state when workflowDefaults load asynchronously
37
39
  useEffect(() => {
38
40
  if (workflowDefaults) {
@@ -41,6 +43,12 @@ export function FeatureCreateDrawer({ open, onClose, onSubmit, repositoryPath, i
41
43
  setOpenPr(workflowDefaults.openPr);
42
44
  }
43
45
  }, [workflowDefaults]);
46
+ // Pre-select parent when initialParentId changes (e.g. (+) button on feature node)
47
+ useEffect(() => {
48
+ if (open && initialParentId) {
49
+ setParentId(initialParentId);
50
+ }
51
+ }, [open, initialParentId]);
44
52
  const resetForm = useCallback(() => {
45
53
  setName('');
46
54
  setDescription('');
@@ -48,6 +56,7 @@ export function FeatureCreateDrawer({ open, onClose, onSubmit, repositoryPath, i
48
56
  setApprovalGates({ ...defaultGates });
49
57
  setPush(defaultPush);
50
58
  setOpenPr(defaultOpenPr);
59
+ setParentId(undefined);
51
60
  }, [defaultGates, defaultPush, defaultOpenPr]);
52
61
  const handleOpenChange = useCallback((nextOpen) => {
53
62
  if (!nextOpen) {
@@ -72,8 +81,19 @@ export function FeatureCreateDrawer({ open, onClose, onSubmit, repositoryPath, i
72
81
  },
73
82
  push: push || openPr,
74
83
  openPr,
84
+ ...(parentId ? { parentId } : {}),
75
85
  });
76
- }, [name, description, attachments, approvalGates, repositoryPath, onSubmit, push, openPr]);
86
+ }, [
87
+ name,
88
+ description,
89
+ attachments,
90
+ approvalGates,
91
+ repositoryPath,
92
+ onSubmit,
93
+ push,
94
+ openPr,
95
+ parentId,
96
+ ]);
77
97
  const handleAddFiles = useCallback(async () => {
78
98
  try {
79
99
  const files = await pickFiles();
@@ -88,7 +108,34 @@ export function FeatureCreateDrawer({ open, onClose, onSubmit, repositoryPath, i
88
108
  const handleRemoveFile = useCallback((path) => {
89
109
  setAttachments((prev) => prev.filter((f) => f.path !== path));
90
110
  }, []);
91
- return (_jsx(Drawer, { direction: "right", modal: false, handleOnly: true, open: open, onOpenChange: handleOpenChange, children: _jsxs(DrawerContent, { direction: "right", className: "w-96", showCloseButton: false, children: [_jsx("button", { type: "button", "aria-label": "Close", onClick: onClose, className: "ring-offset-background focus:ring-ring absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden", children: _jsx(XIcon, { className: "size-4" }) }), _jsxs(DrawerHeader, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "h-2.5 w-2.5 shrink-0 rounded-full bg-blue-500" }), _jsx(DrawerTitle, { children: "NEW FEATURE" })] }), _jsx(DrawerDescription, { asChild: true, children: _jsx("div", { children: _jsx(Badge, { variant: "secondary", children: "Creating..." }) }) })] }), _jsx(Separator, {}), _jsx("div", { className: "flex-1 overflow-y-auto p-4", children: _jsxs("form", { id: "create-feature-form", onSubmit: handleSubmit, className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "feature-name", className: "text-muted-foreground text-xs font-semibold tracking-wider", children: "FEATURE NAME" }), _jsx(Input, { id: "feature-name", placeholder: "e.g. GitHub OAuth Login", value: name, onChange: (e) => setName(e.target.value), required: true, disabled: isSubmitting })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "feature-description", className: "text-muted-foreground text-xs font-semibold tracking-wider", children: "DESCRIPTION" }), _jsx(Textarea, { id: "feature-description", placeholder: "Describe what this feature does...", value: description, onChange: (e) => setDescription(e.target.value), rows: 4, disabled: isSubmitting })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { className: "text-muted-foreground text-xs font-semibold tracking-wider", children: "APPROVE" }), _jsx(CheckboxGroup, { label: "Autonomous Mode", description: "YOLO!", parentAriaLabel: "Auto approve all", options: AUTO_APPROVE_OPTIONS, value: approvalGates, onValueChange: setApprovalGates, disabled: isSubmitting })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { className: "text-muted-foreground text-xs font-semibold tracking-wider", children: "GIT" }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(CheckboxGroupItem, { id: "push", label: "Push", description: "Push branch to remote after implementation.", checked: push || openPr, onCheckedChange: setPush, disabled: openPr || isSubmitting }), _jsx(CheckboxGroupItem, { id: "open-pr", label: "Create PR", description: "Open a pull request after pushing.", checked: openPr, onCheckedChange: setOpenPr, disabled: isSubmitting })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs(Label, { className: "text-muted-foreground text-xs font-semibold tracking-wider", children: ["ATTACHMENTS", attachments.length > 0 && (_jsxs("span", { className: "text-muted-foreground/60 ml-1.5", children: ["(", attachments.length, ")"] }))] }), _jsxs(Button, { type: "button", variant: "outline", size: "xs", onClick: handleAddFiles, disabled: isSubmitting, children: [_jsx(PlusIcon, { className: "size-3" }), "Add Files"] })] }), attachments.length > 0 && (_jsx("div", { className: "flex flex-col gap-2", children: attachments.map((file) => (_jsx(AttachmentCard, { file: file, onRemove: () => handleRemoveFile(file.path), disabled: isSubmitting }, file.path))) }))] })] }) }), _jsx(Separator, {}), _jsxs(DrawerFooter, { className: "flex-row justify-end gap-2", children: [_jsx(Button, { variant: "outline", onClick: onClose, disabled: isSubmitting, children: "Cancel" }), _jsx(Button, { type: "submit", form: "create-feature-form", disabled: !name.trim() || isSubmitting, children: isSubmitting ? 'Creating...' : '+ Create Feature' })] })] }) }));
111
+ const hasFeatures = features && features.length > 0;
112
+ return (_jsx(Drawer, { direction: "right", modal: false, handleOnly: true, open: open, onOpenChange: handleOpenChange, children: _jsxs(DrawerContent, { direction: "right", className: "w-96", showCloseButton: false, children: [_jsx("button", { type: "button", "aria-label": "Close", onClick: onClose, className: "ring-offset-background focus:ring-ring absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden", children: _jsx(XIcon, { className: "size-4" }) }), _jsxs(DrawerHeader, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "h-2.5 w-2.5 shrink-0 rounded-full bg-blue-500" }), _jsx(DrawerTitle, { children: "NEW FEATURE" })] }), _jsx(DrawerDescription, { asChild: true, children: _jsx("div", { children: _jsx(Badge, { variant: "secondary", children: "Creating..." }) }) })] }), _jsx(Separator, {}), _jsx("div", { className: "flex-1 overflow-y-auto p-4", children: _jsxs("form", { id: "create-feature-form", onSubmit: handleSubmit, className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "feature-name", className: "text-muted-foreground text-xs font-semibold tracking-wider", children: "FEATURE NAME" }), _jsx(Input, { id: "feature-name", placeholder: "e.g. GitHub OAuth Login", value: name, onChange: (e) => setName(e.target.value), required: true, disabled: isSubmitting })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "feature-description", className: "text-muted-foreground text-xs font-semibold tracking-wider", children: "DESCRIPTION" }), _jsx(Textarea, { id: "feature-description", placeholder: "Describe what this feature does...", value: description, onChange: (e) => setDescription(e.target.value), rows: 4, disabled: isSubmitting })] }), hasFeatures ? (_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "parent-feature", className: "text-muted-foreground text-xs font-semibold tracking-wider", children: "PARENT FEATURE" }), _jsx(ParentFeatureCombobox, { features: features, value: parentId, onChange: setParentId, disabled: isSubmitting })] })) : null, _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { className: "text-muted-foreground text-xs font-semibold tracking-wider", children: "APPROVE" }), _jsx(CheckboxGroup, { label: "Autonomous Mode", description: "YOLO!", parentAriaLabel: "Auto approve all", options: AUTO_APPROVE_OPTIONS, value: approvalGates, onValueChange: setApprovalGates, disabled: isSubmitting })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { className: "text-muted-foreground text-xs font-semibold tracking-wider", children: "GIT" }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(CheckboxGroupItem, { id: "push", label: "Push", description: "Push branch to remote after implementation.", checked: push || openPr, onCheckedChange: setPush, disabled: openPr || isSubmitting }), _jsx(CheckboxGroupItem, { id: "open-pr", label: "Create PR", description: "Open a pull request after pushing.", checked: openPr, onCheckedChange: setOpenPr, disabled: isSubmitting })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs(Label, { className: "text-muted-foreground text-xs font-semibold tracking-wider", children: ["ATTACHMENTS", attachments.length > 0 && (_jsxs("span", { className: "text-muted-foreground/60 ml-1.5", children: ["(", attachments.length, ")"] }))] }), _jsxs(Button, { type: "button", variant: "outline", size: "xs", onClick: handleAddFiles, disabled: isSubmitting, children: [_jsx(PlusIcon, { className: "size-3" }), "Add Files"] })] }), attachments.length > 0 && (_jsx("div", { className: "flex flex-col gap-2", children: attachments.map((file) => (_jsx(AttachmentCard, { file: file, onRemove: () => handleRemoveFile(file.path), disabled: isSubmitting }, file.path))) }))] })] }) }), _jsx(Separator, {}), _jsxs(DrawerFooter, { className: "flex-row justify-end gap-2", children: [_jsx(Button, { variant: "outline", onClick: onClose, disabled: isSubmitting, children: "Cancel" }), _jsx(Button, { type: "submit", form: "create-feature-form", disabled: !name.trim() || isSubmitting, children: isSubmitting ? 'Creating...' : '+ Create Feature' })] })] }) }));
113
+ }
114
+ function ParentFeatureCombobox({ features, value, onChange, disabled, }) {
115
+ const [open, setOpen] = useState(false);
116
+ const [query, setQuery] = useState('');
117
+ const inputRef = useRef(null);
118
+ const selectedFeature = features.find((f) => f.id === value);
119
+ const filtered = query.trim()
120
+ ? features.filter((f) => f.name.toLowerCase().includes(query.toLowerCase()) ||
121
+ f.id.toLowerCase().includes(query.toLowerCase()))
122
+ : features;
123
+ const handleSelect = useCallback((id) => {
124
+ onChange(id);
125
+ setOpen(false);
126
+ setQuery('');
127
+ }, [onChange]);
128
+ useEffect(() => {
129
+ if (open) {
130
+ setTimeout(() => inputRef.current?.focus(), 0);
131
+ }
132
+ else {
133
+ setQuery('');
134
+ }
135
+ }, [open]);
136
+ return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { id: "parent-feature", type: "button", role: "combobox", "aria-expanded": open, "aria-label": "Parent Feature", disabled: disabled, "data-testid": "parent-feature-combobox", className: cn('border-input bg-background ring-offset-background focus:ring-ring flex h-9 w-full items-center justify-between rounded-md border px-3 py-2 text-sm focus:ring-2 focus:ring-offset-2 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50', !selectedFeature && 'text-muted-foreground'), children: [_jsx("span", { className: "truncate", children: selectedFeature
137
+ ? `${selectedFeature.name} (${selectedFeature.id.slice(0, 8)})`
138
+ : 'Select parent feature...' }), _jsx(ChevronsUpDown, { className: "ml-2 h-4 w-4 shrink-0 opacity-50" })] }) }), _jsx(PopoverContent, { className: "w-80 p-0", align: "start", "data-testid": "parent-feature-combobox-content", children: _jsxs("div", { className: "flex flex-col", children: [_jsx("div", { className: "border-b p-2", children: _jsx(Input, { ref: inputRef, placeholder: "Search features...", value: query, onChange: (e) => setQuery(e.target.value), className: "h-8 border-0 p-0 text-sm shadow-none focus-visible:ring-0", "data-testid": "parent-feature-search" }) }), _jsxs("div", { className: "max-h-48 overflow-y-auto py-1", role: "listbox", "aria-label": "Features", children: [_jsxs("button", { type: "button", role: "option", "aria-selected": value === undefined, onClick: () => handleSelect(undefined), className: cn('hover:bg-accent hover:text-accent-foreground flex w-full items-center gap-2 px-3 py-2 text-sm', value === undefined && 'bg-accent/50'), "data-testid": "parent-feature-option-none", children: [_jsx(CheckIcon, { className: cn('h-4 w-4 shrink-0', value !== undefined && 'invisible') }), _jsx("span", { className: "text-muted-foreground italic", children: "No parent" })] }), filtered.length === 0 && query ? (_jsx("p", { className: "text-muted-foreground px-3 py-2 text-sm", children: "No features found." })) : (filtered.map((f) => (_jsxs("button", { type: "button", role: "option", "aria-selected": value === f.id, onClick: () => handleSelect(f.id), className: cn('hover:bg-accent hover:text-accent-foreground flex w-full items-center gap-2 px-3 py-2 text-sm', value === f.id && 'bg-accent/50'), "data-testid": `parent-feature-option-${f.id}`, children: [_jsx(CheckIcon, { className: cn('h-4 w-4 shrink-0', value !== f.id && 'invisible') }), _jsxs("span", { className: "truncate", children: [f.name, ' ', _jsxs("span", { className: "text-muted-foreground font-mono text-xs", children: ["(", f.id.slice(0, 8), ")"] })] })] }, f.id))))] })] }) })] }));
92
139
  }
93
140
  /* ---------------------------------------------------------------------------
94
141
  * Private sub-components & utilities
@@ -1 +1 @@
1
- {"version":3,"file":"derive-feature-state.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-node/derive-feature-state.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAEpE;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,QAAQ,GAAG,IAAI,GAAG,gBAAgB,CAuC9F;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAYvD"}
1
+ {"version":3,"file":"derive-feature-state.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-node/derive-feature-state.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAEpE;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,QAAQ,GAAG,IAAI,GAAG,gBAAgB,CA4C9F;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAYvD"}