@shepai/cli 1.52.2 → 1.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) 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 +39 -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.d.ts.map +1 -1
  75. package/dist/src/presentation/web/app/page.js +34 -6
  76. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.d.ts +11 -1
  77. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.d.ts.map +1 -1
  78. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.js +52 -5
  79. package/dist/src/presentation/web/components/common/feature-node/derive-feature-state.d.ts.map +1 -1
  80. package/dist/src/presentation/web/components/common/feature-node/derive-feature-state.js +4 -0
  81. package/dist/src/presentation/web/components/common/feature-node/feature-node.js +1 -1
  82. package/dist/src/presentation/web/components/features/control-center/control-center-inner.d.ts.map +1 -1
  83. package/dist/src/presentation/web/components/features/control-center/control-center-inner.js +13 -4
  84. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts +2 -0
  85. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts.map +1 -1
  86. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.js +26 -8
  87. package/dist/src/presentation/web/components/features/features-canvas/dependency-edge.d.ts +7 -0
  88. package/dist/src/presentation/web/components/features/features-canvas/dependency-edge.d.ts.map +1 -0
  89. package/dist/src/presentation/web/components/features/features-canvas/dependency-edge.js +20 -0
  90. package/dist/src/presentation/web/components/features/features-canvas/dependency-edge.stories.d.ts +6 -0
  91. package/dist/src/presentation/web/components/features/features-canvas/dependency-edge.stories.d.ts.map +1 -0
  92. package/dist/src/presentation/web/components/features/features-canvas/dependency-edge.stories.js +58 -0
  93. package/dist/src/presentation/web/components/features/features-canvas/features-canvas.d.ts.map +1 -1
  94. package/dist/src/presentation/web/components/features/features-canvas/features-canvas.js +5 -1
  95. package/dist/tsconfig.build.tsbuildinfo +1 -1
  96. package/package.json +1 -1
  97. package/web/.next/BUILD_ID +1 -1
  98. package/web/.next/build-manifest.json +2 -2
  99. package/web/.next/cache/.previewinfo +1 -1
  100. package/web/.next/cache/.rscinfo +1 -1
  101. package/web/.next/cache/.tsbuildinfo +1 -1
  102. package/web/.next/cache/config.json +3 -3
  103. package/web/.next/fallback-build-manifest.json +2 -2
  104. package/web/.next/prerender-manifest.json +3 -3
  105. package/web/.next/required-server-files.js +1 -1
  106. package/web/.next/required-server-files.json +1 -1
  107. package/web/.next/server/app/_global-error.html +2 -2
  108. package/web/.next/server/app/_global-error.rsc +1 -1
  109. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  110. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  111. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  112. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  113. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  114. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  115. package/web/.next/server/app/page/server-reference-manifest.json +14 -14
  116. package/web/.next/server/app/page.js.nft.json +1 -1
  117. package/web/.next/server/app/page_client-reference-manifest.js +1 -1
  118. package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  119. package/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js +2 -2
  120. package/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js.map +1 -1
  121. package/web/.next/server/chunks/ssr/[root-of-the-server]__249c74f6._.js +1 -1
  122. package/web/.next/server/chunks/ssr/[root-of-the-server]__2d1a8e37._.js +2 -2
  123. package/web/.next/server/chunks/ssr/[root-of-the-server]__2d1a8e37._.js.map +1 -1
  124. package/web/.next/server/chunks/ssr/_1fafa9db._.js +1 -1
  125. package/web/.next/server/chunks/ssr/_1fafa9db._.js.map +1 -1
  126. package/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js +1 -1
  127. package/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js.map +1 -1
  128. package/web/.next/server/pages/500.html +2 -2
  129. package/web/.next/server/server-reference-manifest.js +1 -1
  130. package/web/.next/server/server-reference-manifest.json +15 -15
  131. package/web/.next/standalone/src/presentation/web/.next/BUILD_ID +1 -1
  132. package/web/.next/standalone/src/presentation/web/.next/build-manifest.json +2 -2
  133. package/web/.next/standalone/src/presentation/web/.next/prerender-manifest.json +3 -3
  134. package/web/.next/standalone/src/presentation/web/.next/required-server-files.json +1 -1
  135. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.html +2 -2
  136. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.rsc +1 -1
  137. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  138. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  139. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  140. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  141. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  142. package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  143. package/web/.next/standalone/src/presentation/web/.next/server/app/page/server-reference-manifest.json +14 -14
  144. package/web/.next/standalone/src/presentation/web/.next/server/app/page.js.nft.json +1 -1
  145. package/web/.next/standalone/src/presentation/web/.next/server/app/page_client-reference-manifest.js +1 -1
  146. package/web/.next/standalone/src/presentation/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  147. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js +2 -2
  148. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__249c74f6._.js +1 -1
  149. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__2d1a8e37._.js +2 -2
  150. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_1fafa9db._.js +1 -1
  151. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js +1 -1
  152. package/web/.next/standalone/src/presentation/web/.next/server/pages/500.html +2 -2
  153. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.js +1 -1
  154. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.json +15 -15
  155. package/web/.next/standalone/src/presentation/web/app/actions/create-feature.ts +4 -1
  156. package/web/.next/standalone/src/presentation/web/app/page.tsx +36 -6
  157. package/web/.next/standalone/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.tsx +199 -2
  158. package/web/.next/standalone/src/presentation/web/components/common/feature-node/derive-feature-state.ts +5 -0
  159. package/web/.next/standalone/src/presentation/web/components/common/feature-node/feature-node.tsx +1 -1
  160. package/web/.next/standalone/src/presentation/web/components/features/control-center/control-center-inner.tsx +20 -1
  161. package/web/.next/standalone/src/presentation/web/components/features/control-center/use-control-center-state.ts +43 -12
  162. package/web/.next/standalone/src/presentation/web/components/features/features-canvas/dependency-edge.stories.tsx +81 -0
  163. package/web/.next/standalone/src/presentation/web/components/features/features-canvas/dependency-edge.tsx +29 -0
  164. package/web/.next/standalone/src/presentation/web/components/features/features-canvas/features-canvas.tsx +9 -0
  165. package/web/.next/standalone/src/presentation/web/server.js +1 -1
  166. package/web/.next/static/chunks/48309f9ad2a84b15.css +2 -0
  167. package/web/.next/static/chunks/5123ae1490fd2606.js +10 -0
  168. package/web/.next/static/chunks/{31e5030eed01a941.js → 68fb91b6eca14536.js} +1 -1
  169. package/web/.next/trace +1 -1
  170. package/web/.next/trace-build +1 -1
  171. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/merge-output-parser.d.ts.map +0 -1
  172. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/merge.node.d.ts.map +0 -1
  173. package/web/.next/static/chunks/23cae32e6f42833d.js +0 -10
  174. package/web/.next/static/chunks/59b8bd47023e0249.css +0 -2
  175. /package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/{merge-output-parser.d.ts → merge/merge-output-parser.d.ts} +0 -0
  176. /package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/{merge-output-parser.js → merge/merge-output-parser.js} +0 -0
  177. /package/web/.next/static/{-c2lwSbfsfZHNnfB5CogT → OXSWdTgy-1XJBN9IJtQPz}/_buildManifest.js +0 -0
  178. /package/web/.next/static/{-c2lwSbfsfZHNnfB5CogT → OXSWdTgy-1XJBN9IJtQPz}/_clientMiddlewareManifest.json +0 -0
  179. /package/web/.next/static/{-c2lwSbfsfZHNnfB5CogT → OXSWdTgy-1XJBN9IJtQPz}/_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;AAmKpC,wBAAgB,eAAe,IAAI,OAAO,CAgGzC"}
@@ -46,6 +46,10 @@ const STATUS_PRIORITY = {
46
46
  * Returns the current graph node as a phase label with an activity indicator.
47
47
  */
48
48
  function formatStatus(feature, run) {
49
+ // Blocked features show "Waiting" — the parent relationship is conveyed by tree indentation
50
+ if (feature.lifecycle === 'Blocked') {
51
+ return `${colors.warning(symbols.dotEmpty)} ${colors.warning('Blocked')}`;
52
+ }
49
53
  if (!run) {
50
54
  return `${colors.muted(symbols.dotEmpty)} ${colors.muted(feature.lifecycle)}`;
51
55
  }
@@ -172,11 +176,43 @@ export function createLsCommand() {
172
176
  const paired = features
173
177
  .map((feature, i) => ({ feature, run: runs[i], phases: timings[i] }))
174
178
  .sort((a, b) => getStatusPriority(a.run) - getStatusPriority(b.run));
175
- const rows = paired.map(({ feature, run, phases }) => {
179
+ const childrenByParent = new Map();
180
+ const roots = [];
181
+ for (const entry of paired) {
182
+ const pid = entry.feature.parentId;
183
+ if (pid && entry.feature.lifecycle === 'Blocked') {
184
+ const list = childrenByParent.get(pid) ?? [];
185
+ list.push(entry);
186
+ childrenByParent.set(pid, list);
187
+ }
188
+ else {
189
+ roots.push(entry);
190
+ }
191
+ }
192
+ // Flatten: parent then its blocked children
193
+ const ordered = [];
194
+ for (const entry of roots) {
195
+ ordered.push({ entry, indent: false });
196
+ const kids = childrenByParent.get(entry.feature.id);
197
+ if (kids) {
198
+ for (const kid of kids) {
199
+ ordered.push({ entry: kid, indent: true });
200
+ }
201
+ childrenByParent.delete(entry.feature.id);
202
+ }
203
+ }
204
+ // Append orphaned blocked children (parent not in current list)
205
+ for (const kids of childrenByParent.values()) {
206
+ for (const kid of kids) {
207
+ ordered.push({ entry: kid, indent: false });
208
+ }
209
+ }
210
+ const rows = ordered.map(({ entry: { feature, run, phases }, indent }) => {
176
211
  const repo = path.basename(feature.repositoryPath);
212
+ const prefix = indent ? `${colors.muted('└')} ` : '';
177
213
  return [
178
- feature.id.slice(0, 8),
179
- truncate(feature.name, 30),
214
+ prefix + feature.id.slice(0, 8),
215
+ prefix + truncate(feature.name, indent ? 28 : 30),
180
216
  formatStatus(feature, run),
181
217
  colors.muted(repo),
182
218
  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
  }
@@ -1 +1 @@
1
- {"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/app/page.tsx"],"names":[],"mappings":"AAcA,uFAAuF;AACvF,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAsBvC,wBAA8B,QAAQ,qDAwHrC"}
1
+ {"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/app/page.tsx"],"names":[],"mappings":"AAcA,uFAAuF;AACvF,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAsBvC,wBAA8B,QAAQ,qDAsJrC"}
@@ -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,14 +89,33 @@ 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',
@@ -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"}
@@ -17,6 +17,10 @@ import { SdlcLifecycle, AgentRunStatus, TaskState } from '../../../../../../pack
17
17
  * 6. No agent run → fall back to plan tasks / lifecycle
18
18
  */
19
19
  export function deriveNodeState(feature, agentRun) {
20
+ // Blocked lifecycle takes priority — child waiting on parent regardless of agent run
21
+ if (feature.lifecycle === SdlcLifecycle.Blocked) {
22
+ return 'blocked';
23
+ }
20
24
  if (agentRun) {
21
25
  switch (agentRun.status) {
22
26
  case AgentRunStatus.waitingApproval:
@@ -61,7 +61,7 @@ function getBadgeText(data) {
61
61
  export function FeatureNode({ data, selected, }) {
62
62
  const config = featureNodeStateConfig[data.state];
63
63
  const Icon = config.icon;
64
- return (_jsxs("div", { className: "group relative", children: [data.showHandles ? (_jsx(Handle, { type: "target", position: Position.Left, isConnectable: false, className: "opacity-0!" })) : null, _jsxs("div", { "data-testid": "feature-node-card", "aria-busy": data.state === 'creating' ? 'true' : undefined, className: cn('bg-card flex min-h-35 w-72 flex-col rounded-lg border p-3 shadow-sm', selected && 'ring-primary ring-2'), children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { "data-testid": "feature-node-lifecycle-label", className: cn('text-[10px] font-semibold tracking-wider'), children: lifecycleDisplayLabels[data.lifecycle] }), data.onSettings ? (_jsx("button", { type: "button", "aria-label": "Settings", "data-testid": "feature-node-settings-button", onClick: (e) => {
64
+ return (_jsxs("div", { className: "group relative", children: [data.showHandles ? (_jsx(Handle, { type: "target", position: Position.Left, isConnectable: false, className: "opacity-0!" })) : null, _jsxs("div", { "data-testid": "feature-node-card", "aria-busy": data.state === 'creating' ? 'true' : undefined, className: cn('bg-card flex min-h-35 w-72 flex-col rounded-lg border p-3 shadow-sm', selected && 'ring-primary ring-2'), children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { "data-testid": "feature-node-lifecycle-label", className: cn('text-[10px] font-semibold tracking-wider'), children: data.state === 'blocked' ? 'BLOCKED' : lifecycleDisplayLabels[data.lifecycle] }), data.onSettings ? (_jsx("button", { type: "button", "aria-label": "Settings", "data-testid": "feature-node-settings-button", onClick: (e) => {
65
65
  e.stopPropagation();
66
66
  data.onSettings?.();
67
67
  }, className: "nodrag text-muted-foreground hover:text-foreground -mt-1 -mr-1 p-1", children: _jsx(Settings, { className: "h-3.5 w-3.5" }) })) : null] }), _jsx("h3", { className: "mt-1 truncate text-sm font-bold", children: data.name }), data.description ? (_jsx("p", { "data-testid": "feature-node-description", className: "text-muted-foreground mt-0.5 truncate text-xs", children: data.description })) : null, _jsx("div", { className: "mt-auto pt-2", children: data.state === 'creating' ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "mt-1.5 flex items-center gap-1.5 text-xs", children: [_jsx(Icon, { className: "h-3.5 w-3.5 shrink-0 animate-spin" }), _jsx("span", { className: "text-muted-foreground", children: getBadgeText(data) })] }), _jsx("div", { "data-testid": "feature-node-progress-bar", className: "bg-muted mt-1.5 h-1 w-full overflow-hidden rounded-full", children: _jsx("div", { className: "motion-safe:animate-indeterminate-progress bg-foreground/30 h-full w-1/3 rounded-full" }) })] })) : data.state === 'running' ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "mt-1.5 flex items-center gap-1.5 text-xs", children: [_jsx(AgentIcon, { agentType: data.agentType, className: "h-3.5 w-3.5 shrink-0" }), _jsx("span", { className: "text-muted-foreground", children: getBadgeText(data) })] }), _jsx("div", { "data-testid": "feature-node-progress-bar", className: "bg-muted mt-1.5 h-1 w-full overflow-hidden rounded-full", children: _jsx("div", { className: "animate-indeterminate-progress bg-foreground/30 h-full w-1/3 rounded-full" }) })] })) : config.showProgressBar ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "text-muted-foreground flex items-center justify-end text-[10px]", children: _jsxs("span", { children: [data.progress, "%"] }) }), _jsx("div", { "data-testid": "feature-node-progress-bar", className: "bg-muted mt-1.5 h-1 w-full overflow-hidden rounded-full", children: _jsx("div", { className: cn('h-full rounded-full transition-all', config.progressClass), style: { width: `${data.progress}%` } }) })] })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: "text-muted-foreground text-[10px]", children: _jsx("span", { children: data.featureId }) }), _jsxs("div", { "data-testid": "feature-node-badge", className: (() => {
@@ -1 +1 @@
1
- {"version":3,"file":"control-center-inner.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/features/control-center/control-center-inner.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAgB1C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAU5E,UAAU,uBAAuB;IAC/B,YAAY,EAAE,cAAc,EAAE,CAAC;IAC/B,YAAY,EAAE,IAAI,EAAE,CAAC;CACtB;AAED,wBAAgB,kBAAkB,CAAC,EAAE,YAAY,EAAE,YAAY,EAAE,EAAE,uBAAuB,2CAiYzF"}
1
+ {"version":3,"file":"control-center-inner.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/features/control-center/control-center-inner.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAgB1C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAW5E,UAAU,uBAAuB;IAC/B,YAAY,EAAE,cAAc,EAAE,CAAC;IAC/B,YAAY,EAAE,IAAI,EAAE,CAAC;CACtB;AAED,wBAAgB,kBAAkB,CAAC,EAAE,YAAY,EAAE,YAAY,EAAE,EAAE,uBAAuB,2CAmZzF"}