@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.
- package/apis/json-schema/CiFixRecord.yaml +24 -0
- package/apis/json-schema/Feature.yaml +3 -0
- package/apis/json-schema/PullRequest.yaml +10 -0
- package/apis/json-schema/SdlcLifecycle.yaml +1 -0
- package/apis/json-schema/WorkflowConfig.yaml +15 -0
- package/dist/packages/core/src/application/ports/output/repositories/feature-repository.interface.d.ts +8 -0
- package/dist/packages/core/src/application/ports/output/repositories/feature-repository.interface.d.ts.map +1 -1
- package/dist/packages/core/src/application/ports/output/services/git-pr-service.interface.d.ts +12 -0
- package/dist/packages/core/src/application/ports/output/services/git-pr-service.interface.d.ts.map +1 -1
- package/dist/packages/core/src/application/use-cases/features/check-and-unblock-features.use-case.d.ts +30 -0
- package/dist/packages/core/src/application/use-cases/features/check-and-unblock-features.use-case.d.ts.map +1 -0
- package/dist/packages/core/src/application/use-cases/features/check-and-unblock-features.use-case.js +74 -0
- package/dist/packages/core/src/application/use-cases/features/create/create-feature.use-case.d.ts.map +1 -1
- package/dist/packages/core/src/application/use-cases/features/create/create-feature.use-case.js +60 -16
- package/dist/packages/core/src/application/use-cases/features/create/types.d.ts +2 -0
- package/dist/packages/core/src/application/use-cases/features/create/types.d.ts.map +1 -1
- package/dist/packages/core/src/application/use-cases/features/delete-feature.use-case.d.ts.map +1 -1
- package/dist/packages/core/src/application/use-cases/features/delete-feature.use-case.js +11 -4
- package/dist/packages/core/src/application/use-cases/features/update/update-feature-lifecycle.use-case.d.ts +28 -0
- package/dist/packages/core/src/application/use-cases/features/update/update-feature-lifecycle.use-case.d.ts.map +1 -0
- package/dist/packages/core/src/application/use-cases/features/update/update-feature-lifecycle.use-case.js +53 -0
- package/dist/packages/core/src/domain/generated/output.d.ts +47 -1
- package/dist/packages/core/src/domain/generated/output.d.ts.map +1 -1
- package/dist/packages/core/src/domain/generated/output.js +1 -0
- package/dist/packages/core/src/domain/lifecycle-gates.d.ts +16 -0
- package/dist/packages/core/src/domain/lifecycle-gates.d.ts.map +1 -0
- package/dist/packages/core/src/domain/lifecycle-gates.js +19 -0
- package/dist/packages/core/src/infrastructure/di/container.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/di/container.js +12 -0
- package/dist/packages/core/src/infrastructure/persistence/sqlite/mappers/feature.mapper.d.ts +3 -0
- package/dist/packages/core/src/infrastructure/persistence/sqlite/mappers/feature.mapper.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/persistence/sqlite/mappers/feature.mapper.js +8 -0
- package/dist/packages/core/src/infrastructure/persistence/sqlite/migrations.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/persistence/sqlite/migrations.js +28 -1
- package/dist/packages/core/src/infrastructure/repositories/sqlite-feature.repository.d.ts +1 -0
- package/dist/packages/core/src/infrastructure/repositories/sqlite-feature.repository.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/repositories/sqlite-feature.repository.js +12 -0
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/feature-agent-graph.d.ts +52 -1
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/feature-agent-graph.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/feature-agent-graph.js +1 -1
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/feature-agent-worker.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/feature-agent-worker.js +7 -2
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/lifecycle-context.d.ts +27 -5
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/lifecycle-context.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/lifecycle-context.js +19 -16
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/merge/ci-helpers.d.ts +24 -0
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/merge/ci-helpers.d.ts.map +1 -0
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/merge/ci-helpers.js +47 -0
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/merge/ci-watch-fix-loop.d.ts +43 -0
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/merge/ci-watch-fix-loop.d.ts.map +1 -0
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/merge/ci-watch-fix-loop.js +118 -0
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/merge/merge-output-parser.d.ts.map +1 -0
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/{merge.node.d.ts → merge/merge.node.d.ts} +10 -4
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/merge/merge.node.d.ts.map +1 -0
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/{merge.node.js → merge/merge.node.js} +44 -17
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/prompts/merge-prompts.d.ts +12 -0
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/prompts/merge-prompts.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/prompts/merge-prompts.js +38 -0
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/state.d.ts +4 -1
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/state.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/state.js +13 -0
- package/dist/packages/core/src/infrastructure/services/git/git-pr.service.d.ts +2 -0
- package/dist/packages/core/src/infrastructure/services/git/git-pr.service.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/git/git-pr.service.js +29 -1
- package/dist/src/presentation/cli/commands/feat/ls.command.d.ts.map +1 -1
- package/dist/src/presentation/cli/commands/feat/ls.command.js +40 -3
- package/dist/src/presentation/cli/commands/feat/new.command.d.ts.map +1 -1
- package/dist/src/presentation/cli/commands/feat/new.command.js +18 -0
- package/dist/src/presentation/cli/commands/feat/show.command.d.ts.map +1 -1
- package/dist/src/presentation/cli/commands/feat/show.command.js +20 -0
- package/dist/src/presentation/web/app/actions/create-feature.d.ts +1 -0
- package/dist/src/presentation/web/app/actions/create-feature.d.ts.map +1 -1
- package/dist/src/presentation/web/app/actions/create-feature.js +2 -1
- package/dist/src/presentation/web/app/page.js +34 -33
- package/dist/src/presentation/web/components/common/add-repository-node/add-repository-button.d.ts +6 -0
- package/dist/src/presentation/web/components/common/add-repository-node/add-repository-button.d.ts.map +1 -0
- package/dist/src/presentation/web/components/common/add-repository-node/add-repository-button.js +23 -0
- package/dist/src/presentation/web/components/common/add-repository-node/index.d.ts +1 -0
- package/dist/src/presentation/web/components/common/add-repository-node/index.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/add-repository-node/index.js +1 -0
- package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.d.ts +11 -1
- package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.js +52 -5
- package/dist/src/presentation/web/components/common/feature-node/derive-feature-state.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/feature-node/derive-feature-state.js +4 -0
- package/dist/src/presentation/web/components/common/feature-node/feature-node.js +1 -1
- package/dist/src/presentation/web/components/features/control-center/control-center-inner.d.ts.map +1 -1
- package/dist/src/presentation/web/components/features/control-center/control-center-inner.js +13 -4
- package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts +2 -0
- package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts.map +1 -1
- package/dist/src/presentation/web/components/features/control-center/use-control-center-state.js +26 -8
- package/dist/src/presentation/web/components/features/features-canvas/dependency-edge.d.ts +7 -0
- package/dist/src/presentation/web/components/features/features-canvas/dependency-edge.d.ts.map +1 -0
- package/dist/src/presentation/web/components/features/features-canvas/dependency-edge.js +20 -0
- package/dist/src/presentation/web/components/features/features-canvas/dependency-edge.stories.d.ts +6 -0
- package/dist/src/presentation/web/components/features/features-canvas/dependency-edge.stories.d.ts.map +1 -0
- package/dist/src/presentation/web/components/features/features-canvas/dependency-edge.stories.js +58 -0
- package/dist/src/presentation/web/components/features/features-canvas/features-canvas.d.ts.map +1 -1
- package/dist/src/presentation/web/components/features/features-canvas/features-canvas.js +7 -3
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/build-manifest.json +2 -2
- package/web/.next/cache/.previewinfo +1 -1
- package/web/.next/cache/.rscinfo +1 -1
- package/web/.next/cache/.tsbuildinfo +1 -1
- package/web/.next/cache/config.json +3 -3
- package/web/.next/fallback-build-manifest.json +2 -2
- package/web/.next/prerender-manifest.json +3 -3
- package/web/.next/required-server-files.js +1 -1
- package/web/.next/required-server-files.json +1 -1
- package/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/page/server-reference-manifest.json +14 -14
- package/web/.next/server/app/page.js.nft.json +1 -1
- package/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__249c74f6._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__2d1a8e37._.js +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__2d1a8e37._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_1fafa9db._.js +1 -1
- package/web/.next/server/chunks/ssr/_1fafa9db._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js.map +1 -1
- package/web/.next/server/pages/500.html +2 -2
- package/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/server/server-reference-manifest.json +15 -15
- package/web/.next/standalone/src/presentation/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/src/presentation/web/.next/build-manifest.json +2 -2
- package/web/.next/standalone/src/presentation/web/.next/prerender-manifest.json +3 -3
- package/web/.next/standalone/src/presentation/web/.next/required-server-files.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/page/server-reference-manifest.json +14 -14
- package/web/.next/standalone/src/presentation/web/.next/server/app/page.js.nft.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__249c74f6._.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__2d1a8e37._.js +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_1fafa9db._.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/pages/500.html +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.json +15 -15
- package/web/.next/standalone/src/presentation/web/app/actions/create-feature.ts +4 -1
- package/web/.next/standalone/src/presentation/web/app/page.tsx +36 -36
- package/web/.next/standalone/src/presentation/web/components/common/add-repository-node/add-repository-button.tsx +43 -0
- package/web/.next/standalone/src/presentation/web/components/common/add-repository-node/index.ts +1 -0
- package/web/.next/standalone/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.tsx +199 -2
- package/web/.next/standalone/src/presentation/web/components/common/feature-node/derive-feature-state.ts +5 -0
- package/web/.next/standalone/src/presentation/web/components/common/feature-node/feature-node.tsx +1 -1
- package/web/.next/standalone/src/presentation/web/components/features/control-center/control-center-inner.tsx +20 -1
- package/web/.next/standalone/src/presentation/web/components/features/control-center/use-control-center-state.ts +43 -12
- package/web/.next/standalone/src/presentation/web/components/features/features-canvas/dependency-edge.stories.tsx +81 -0
- package/web/.next/standalone/src/presentation/web/components/features/features-canvas/dependency-edge.tsx +29 -0
- package/web/.next/standalone/src/presentation/web/components/features/features-canvas/features-canvas.tsx +16 -2
- package/web/.next/standalone/src/presentation/web/server.js +1 -1
- package/web/.next/static/chunks/212e3c682d3b7f50.js +10 -0
- package/web/.next/static/chunks/476fe030d242c153.css +2 -0
- package/web/.next/static/chunks/{31e5030eed01a941.js → 68fb91b6eca14536.js} +1 -1
- package/web/.next/trace +1 -1
- package/web/.next/trace-build +1 -1
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/merge-output-parser.d.ts.map +0 -1
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/merge.node.d.ts.map +0 -1
- package/web/.next/static/chunks/23cae32e6f42833d.js +0 -10
- package/web/.next/static/chunks/59b8bd47023e0249.css +0 -2
- /package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/{merge-output-parser.d.ts → merge/merge-output-parser.d.ts} +0 -0
- /package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/{merge-output-parser.js → merge/merge-output-parser.js} +0 -0
- /package/web/.next/static/{-c2lwSbfsfZHNnfB5CogT → EWkET8BUeCcZJxcYQpxDY}/_buildManifest.js +0 -0
- /package/web/.next/static/{-c2lwSbfsfZHNnfB5CogT → EWkET8BUeCcZJxcYQpxDY}/_clientMiddlewareManifest.json +0 -0
- /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;
|
|
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;
|
|
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
|
-
|
|
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;
|
|
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
|
|
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;
|
|
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,
|
|
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
|
]
|
|
@@ -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;
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
}
|
package/dist/src/presentation/web/components/common/add-repository-node/add-repository-button.d.ts
ADDED
|
@@ -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"}
|
package/dist/src/presentation/web/components/common/add-repository-node/add-repository-button.js
ADDED
|
@@ -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 +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"}
|
package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.d.ts
CHANGED
|
@@ -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":"
|
|
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"}
|
package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.js
CHANGED
|
@@ -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
|
-
}, [
|
|
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
|
-
|
|
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
|
package/dist/src/presentation/web/components/common/feature-node/derive-feature-state.d.ts.map
CHANGED
|
@@ -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,
|
|
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"}
|