@shepai/cli 1.56.0 → 1.58.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.
- package/apis/json-schema/NotificationEventConfig.yaml +20 -0
- package/apis/json-schema/NotificationEventType.yaml +4 -0
- package/dist/packages/core/src/application/ports/output/services/git-pr-service.interface.d.ts +22 -0
- package/dist/packages/core/src/application/ports/output/services/git-pr-service.interface.d.ts.map +1 -1
- package/dist/packages/core/src/domain/factories/settings-defaults.factory.d.ts.map +1 -1
- package/dist/packages/core/src/domain/factories/settings-defaults.factory.js +4 -0
- package/dist/packages/core/src/domain/generated/output.d.ts +21 -1
- package/dist/packages/core/src/domain/generated/output.d.ts.map +1 -1
- package/dist/packages/core/src/domain/generated/output.js +4 -0
- package/dist/packages/core/src/infrastructure/persistence/sqlite/mappers/settings.mapper.d.ts +4 -0
- package/dist/packages/core/src/infrastructure/persistence/sqlite/mappers/settings.mapper.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/persistence/sqlite/mappers/settings.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 +18 -0
- package/dist/packages/core/src/infrastructure/services/git/git-pr.service.d.ts +3 -1
- 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 +28 -0
- package/dist/packages/core/src/infrastructure/services/notifications/notification.service.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/notifications/notification.service.js +4 -0
- package/dist/packages/core/src/infrastructure/services/pr-sync/pr-sync-watcher.service.d.ts +61 -0
- package/dist/packages/core/src/infrastructure/services/pr-sync/pr-sync-watcher.service.d.ts.map +1 -0
- package/dist/packages/core/src/infrastructure/services/pr-sync/pr-sync-watcher.service.js +286 -0
- package/dist/src/presentation/cli/commands/ui.command.d.ts.map +1 -1
- package/dist/src/presentation/cli/commands/ui.command.js +6 -0
- package/dist/src/presentation/web/components/common/add-repository-node/add-repository-button.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/add-repository-node/add-repository-button.js +4 -2
- package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node.stories.d.ts +2 -0
- package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node.stories.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node.stories.js +6 -0
- 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 +6 -0
- 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 +9 -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 +3 -3
- package/dist/src/presentation/web/components/features/features-canvas/features-canvas.stories.d.ts +0 -1
- package/dist/src/presentation/web/components/features/features-canvas/features-canvas.stories.d.ts.map +1 -1
- package/dist/src/presentation/web/components/features/features-canvas/features-canvas.stories.js +1 -31
- package/dist/src/presentation/web/components/layouts/app-shell/app-shell.d.ts.map +1 -1
- package/dist/src/presentation/web/components/layouts/app-shell/app-shell.js +5 -1
- package/dist/src/presentation/web/dev-server.js +11 -0
- 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/page.js +1 -1
- package/web/.next/server/app/_global-error/page.js.nft.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/server-reference-manifest.json +17 -1
- package/web/.next/server/app/_not-found/page.js +2 -2
- package/web/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/api/dialog/pick-files/route.js +1 -1
- package/web/.next/server/app/api/dialog/pick-files/route.js.nft.json +1 -1
- package/web/.next/server/app/page/server-reference-manifest.json +41 -41
- package/web/.next/server/app/page.js +2 -3
- 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/tools/page/server-reference-manifest.json +17 -1
- package/web/.next/server/app/tools/page.js +2 -2
- package/web/.next/server/app/tools/page.js.nft.json +1 -1
- package/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/version/page/server-reference-manifest.json +17 -1
- package/web/.next/server/app/version/page.js +2 -2
- package/web/.next/server/app/version/page.js.nft.json +1 -1
- package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
- package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js +2 -2
- package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js.map +1 -1
- package/web/.next/server/chunks/{[root-of-the-server]__332c8d91._.js → [root-of-the-server]__a6bf88cc._.js} +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__08ba9bd3._.js +4 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__08ba9bd3._.js.map +1 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__6b17a22d._.js +4 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__6b17a22d._.js.map +1 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__804c006d._.js +4 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__804c006d._.js.map +1 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__86c7bba5._.js +9 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__86c7bba5._.js.map +1 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__9add7c3a._.js +12 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__9add7c3a._.js.map +1 -0
- package/web/.next/server/chunks/ssr/{[root-of-the-server]__dbd220d2._.js → [root-of-the-server]__b79b0516._.js} +2 -2
- package/web/.next/server/chunks/ssr/{[root-of-the-server]__dbd220d2._.js.map → [root-of-the-server]__b79b0516._.js.map} +1 -1
- package/web/.next/server/chunks/ssr/{[root-of-the-server]__757c7912._.js → [root-of-the-server]__f7f90528._.js} +2 -2
- package/web/.next/server/chunks/ssr/{[root-of-the-server]__757c7912._.js.map → [root-of-the-server]__f7f90528._.js.map} +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__fbc89707._.js +1 -1
- package/web/.next/server/chunks/ssr/{_0e2c2b4f._.js → _027596cf._.js} +2 -2
- package/web/.next/server/chunks/ssr/_027596cf._.js.map +1 -0
- package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.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 +63 -42
- 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/page.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error/page.js.nft.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/server-reference-manifest.json +17 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page.js +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page.js.nft.json +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/api/dialog/pick-files/route.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/api/dialog/pick-files/route.js.nft.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/page/server-reference-manifest.json +41 -41
- package/web/.next/standalone/src/presentation/web/.next/server/app/page.js +2 -3
- 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/tools/page/server-reference-manifest.json +17 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/tools/page.js +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/app/tools/page.js.nft.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/version/page/server-reference-manifest.json +17 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/version/page.js +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/app/version/page.js.nft.json +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/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/{[root-of-the-server]__332c8d91._.js → [root-of-the-server]__a6bf88cc._.js} +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__08ba9bd3._.js +4 -0
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__6b17a22d._.js +4 -0
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__804c006d._.js +4 -0
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__86c7bba5._.js +9 -0
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__9add7c3a._.js +12 -0
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/{[root-of-the-server]__dbd220d2._.js → [root-of-the-server]__b79b0516._.js} +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/{[root-of-the-server]__757c7912._.js → [root-of-the-server]__f7f90528._.js} +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__fbc89707._.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/{_0e2c2b4f._.js → _027596cf._.js} +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.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 +63 -42
- package/web/.next/standalone/src/presentation/web/components/common/add-repository-node/add-repository-button.tsx +24 -15
- package/web/.next/standalone/src/presentation/web/components/common/add-repository-node/add-repository-node.stories.tsx +12 -0
- package/web/.next/standalone/src/presentation/web/components/common/feature-node/derive-feature-state.ts +6 -0
- package/web/.next/standalone/src/presentation/web/components/features/control-center/control-center-inner.tsx +10 -0
- package/web/.next/standalone/src/presentation/web/components/features/features-canvas/features-canvas.stories.tsx +0 -41
- package/web/.next/standalone/src/presentation/web/components/features/features-canvas/features-canvas.tsx +2 -7
- package/web/.next/standalone/src/presentation/web/components/layouts/app-shell/app-shell.tsx +6 -0
- package/web/.next/standalone/src/presentation/web/dev-server.ts +15 -0
- package/web/.next/standalone/src/presentation/web/server.js +1 -1
- package/web/.next/static/chunks/3f5de337d3fc604f.js +1 -0
- package/web/.next/static/chunks/60bbbe7ff679eb4c.css +2 -0
- package/web/.next/static/chunks/{ef237b70209d9853.js → 67459433cc866344.js} +1 -1
- package/web/.next/static/chunks/eb0e60434c992fc1.js +10 -0
- package/web/.next/trace +1 -1
- package/web/.next/trace-build +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__2d1a8e37._.js +0 -9
- package/web/.next/server/chunks/ssr/[root-of-the-server]__2d1a8e37._.js.map +0 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__48c68a96._.js +0 -12
- package/web/.next/server/chunks/ssr/[root-of-the-server]__48c68a96._.js.map +0 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__950afd5e._.js +0 -3
- package/web/.next/server/chunks/ssr/[root-of-the-server]__950afd5e._.js.map +0 -1
- package/web/.next/server/chunks/ssr/_0e2c2b4f._.js.map +0 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app__not-found_page_actions_a836bb2a.js +0 -3
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app__not-found_page_actions_a836bb2a.js.map +0 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_f1dd7791.js +0 -3
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_f1dd7791.js.map +0 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_version_page_actions_ea925032.js +0 -3
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_version_page_actions_ea925032.js.map +0 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__2d1a8e37._.js +0 -9
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__48c68a96._.js +0 -12
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__950afd5e._.js +0 -3
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app__not-found_page_actions_a836bb2a.js +0 -3
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_f1dd7791.js +0 -3
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_version_page_actions_ea925032.js +0 -3
- package/web/.next/static/chunks/4651f20be1a3b7d7.css +0 -2
- package/web/.next/static/chunks/62eba5d9a5e28761.js +0 -10
- package/web/.next/static/chunks/ca4bb930b4a3f3a9.js +0 -1
- /package/web/.next/server/chunks/{[root-of-the-server]__332c8d91._.js.map → [root-of-the-server]__a6bf88cc._.js.map} +0 -0
- /package/web/.next/static/{TNUev4cyuW_BghWxcCTOD → e7Q45G0MI6cgci9Gn2J-i}/_buildManifest.js +0 -0
- /package/web/.next/static/{TNUev4cyuW_BghWxcCTOD → e7Q45G0MI6cgci9Gn2J-i}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{TNUev4cyuW_BghWxcCTOD → e7Q45G0MI6cgci9Gn2J-i}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PR Sync Watcher Service
|
|
3
|
+
*
|
|
4
|
+
* Polls GitHub PR status and CI status for features in the Review lifecycle
|
|
5
|
+
* stage, updating feature records and emitting notifications when transitions
|
|
6
|
+
* are detected. Follows the NotificationWatcherService polling pattern.
|
|
7
|
+
*
|
|
8
|
+
* Maintains in-memory tracking of last-known PR and CI status per feature
|
|
9
|
+
* to avoid duplicate updates and notifications. Features are grouped by
|
|
10
|
+
* repositoryPath for batch `gh pr list` queries.
|
|
11
|
+
*/
|
|
12
|
+
import { SdlcLifecycle, PrStatus, CiStatus, AgentRunStatus, NotificationEventType, NotificationSeverity, } from '../../../domain/generated/output.js';
|
|
13
|
+
const DEFAULT_POLL_INTERVAL_MS = 3000;
|
|
14
|
+
const TAG = '[PrSyncWatcher]';
|
|
15
|
+
const CI_STATUS_MAP = {
|
|
16
|
+
success: CiStatus.Success,
|
|
17
|
+
failure: CiStatus.Failure,
|
|
18
|
+
pending: CiStatus.Pending,
|
|
19
|
+
};
|
|
20
|
+
export class PrSyncWatcherService {
|
|
21
|
+
featureRepo;
|
|
22
|
+
agentRunRepo;
|
|
23
|
+
gitPrService;
|
|
24
|
+
notificationService;
|
|
25
|
+
pollIntervalMs;
|
|
26
|
+
trackedFeatures = new Map();
|
|
27
|
+
skippedRepos = new Set();
|
|
28
|
+
intervalId = null;
|
|
29
|
+
constructor(featureRepo, agentRunRepo, gitPrService, notificationService, pollIntervalMs = DEFAULT_POLL_INTERVAL_MS) {
|
|
30
|
+
this.featureRepo = featureRepo;
|
|
31
|
+
this.agentRunRepo = agentRunRepo;
|
|
32
|
+
this.gitPrService = gitPrService;
|
|
33
|
+
this.notificationService = notificationService;
|
|
34
|
+
this.pollIntervalMs = pollIntervalMs;
|
|
35
|
+
}
|
|
36
|
+
isRunning() {
|
|
37
|
+
return this.intervalId !== null;
|
|
38
|
+
}
|
|
39
|
+
start() {
|
|
40
|
+
if (this.intervalId !== null)
|
|
41
|
+
return;
|
|
42
|
+
// eslint-disable-next-line no-console
|
|
43
|
+
console.log(`${TAG} Starting (poll every ${this.pollIntervalMs}ms)`);
|
|
44
|
+
// Run first poll immediately
|
|
45
|
+
void this.poll();
|
|
46
|
+
this.intervalId = setInterval(() => {
|
|
47
|
+
void this.poll();
|
|
48
|
+
}, this.pollIntervalMs);
|
|
49
|
+
}
|
|
50
|
+
stop() {
|
|
51
|
+
if (this.intervalId !== null) {
|
|
52
|
+
clearInterval(this.intervalId);
|
|
53
|
+
this.intervalId = null;
|
|
54
|
+
// eslint-disable-next-line no-console
|
|
55
|
+
console.log(`${TAG} Stopped`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async poll() {
|
|
59
|
+
try {
|
|
60
|
+
const allFeatures = await this.featureRepo.list({ lifecycle: SdlcLifecycle.Review });
|
|
61
|
+
// Include features with a valid repositoryPath (with or without PR data)
|
|
62
|
+
const features = allFeatures.filter((f) => f.repositoryPath);
|
|
63
|
+
if (features.length === 0) {
|
|
64
|
+
this.trackedFeatures.clear();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
// Group features by repositoryPath for batch queries
|
|
68
|
+
const byRepo = new Map();
|
|
69
|
+
for (const feature of features) {
|
|
70
|
+
const group = byRepo.get(feature.repositoryPath) ?? [];
|
|
71
|
+
group.push(feature);
|
|
72
|
+
byRepo.set(feature.repositoryPath, group);
|
|
73
|
+
}
|
|
74
|
+
// Process each repository
|
|
75
|
+
for (const [repoPath, repoFeatures] of byRepo) {
|
|
76
|
+
await this.processRepository(repoPath, repoFeatures);
|
|
77
|
+
}
|
|
78
|
+
// Prune features no longer in Review
|
|
79
|
+
const currentFeatureIds = new Set(features.map((f) => f.id));
|
|
80
|
+
for (const trackedId of this.trackedFeatures.keys()) {
|
|
81
|
+
if (!currentFeatureIds.has(trackedId)) {
|
|
82
|
+
this.trackedFeatures.delete(trackedId);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
88
|
+
// eslint-disable-next-line no-console
|
|
89
|
+
console.warn(`${TAG} Poll failed: ${msg}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async processRepository(repoPath, features) {
|
|
93
|
+
if (this.skippedRepos.has(repoPath))
|
|
94
|
+
return;
|
|
95
|
+
// Skip repos without a git remote — gh pr list will always fail
|
|
96
|
+
try {
|
|
97
|
+
if (!(await this.gitPrService.hasRemote(repoPath))) {
|
|
98
|
+
// eslint-disable-next-line no-console
|
|
99
|
+
console.log(`${TAG} Skipping ${repoPath} (no git remote)`);
|
|
100
|
+
this.skippedRepos.add(repoPath);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
let prStatuses;
|
|
108
|
+
try {
|
|
109
|
+
prStatuses = await this.gitPrService.listPrStatuses(repoPath);
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
113
|
+
// eslint-disable-next-line no-console
|
|
114
|
+
console.warn(`${TAG} listPrStatuses failed for ${repoPath}: ${msg}`);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
// Build lookups by PR number and by head branch name.
|
|
118
|
+
// Multiple PRs can share the same headRefName (e.g. old merged PR + new open PR).
|
|
119
|
+
// Prefer Open PRs so we don't match stale merged/closed ones.
|
|
120
|
+
const statusByNumber = new Map();
|
|
121
|
+
const statusByBranch = new Map();
|
|
122
|
+
for (const pr of prStatuses) {
|
|
123
|
+
statusByNumber.set(pr.number, pr);
|
|
124
|
+
if (pr.headRefName) {
|
|
125
|
+
const existing = statusByBranch.get(pr.headRefName);
|
|
126
|
+
if (!existing || (existing.state !== PrStatus.Open && pr.state === PrStatus.Open)) {
|
|
127
|
+
statusByBranch.set(pr.headRefName, pr);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
for (const feature of features) {
|
|
132
|
+
await this.processFeature(feature, statusByNumber, statusByBranch);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async processFeature(feature, statusByNumber, statusByBranch) {
|
|
136
|
+
// If feature has no PR data, try to discover an Open PR by matching branch name.
|
|
137
|
+
// Only link Open PRs — merged/closed PRs with the same branch are likely stale
|
|
138
|
+
// from a previous iteration. The watcher will catch Open→Merged transitions
|
|
139
|
+
// on subsequent polls once the PR is linked.
|
|
140
|
+
if (!feature.pr) {
|
|
141
|
+
const matchedPr = statusByBranch.get(feature.branch);
|
|
142
|
+
if (matchedPr?.state !== PrStatus.Open)
|
|
143
|
+
return;
|
|
144
|
+
// eslint-disable-next-line no-console
|
|
145
|
+
console.log(`${TAG} Discovered PR #${matchedPr.number} for "${feature.name}" via branch "${feature.branch}"`);
|
|
146
|
+
feature.pr = {
|
|
147
|
+
url: matchedPr.url,
|
|
148
|
+
number: matchedPr.number,
|
|
149
|
+
status: PrStatus.Open,
|
|
150
|
+
};
|
|
151
|
+
this.trackedFeatures.set(feature.id, {
|
|
152
|
+
prStatus: PrStatus.Open,
|
|
153
|
+
ciStatus: undefined,
|
|
154
|
+
featureName: feature.name,
|
|
155
|
+
});
|
|
156
|
+
await this.featureRepo.update(feature);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const pr = feature.pr;
|
|
160
|
+
const prStatusInfo = statusByNumber.get(pr.number);
|
|
161
|
+
let needsUpdate = false;
|
|
162
|
+
// Initialize or retrieve tracked state
|
|
163
|
+
const prevState = this.trackedFeatures.get(feature.id);
|
|
164
|
+
if (!prevState) {
|
|
165
|
+
// First time seeing this feature — initialize tracking
|
|
166
|
+
this.trackedFeatures.set(feature.id, {
|
|
167
|
+
prStatus: pr.status,
|
|
168
|
+
ciStatus: pr.ciStatus,
|
|
169
|
+
featureName: feature.name,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
const tracked = this.trackedFeatures.get(feature.id);
|
|
173
|
+
// Check PR status transition
|
|
174
|
+
if (prStatusInfo && prStatusInfo.state !== tracked.prStatus) {
|
|
175
|
+
const newPrStatus = prStatusInfo.state;
|
|
176
|
+
// eslint-disable-next-line no-console
|
|
177
|
+
console.log(`${TAG} PR #${pr.number} status changed: ${tracked.prStatus} -> ${newPrStatus} for "${feature.name}"`);
|
|
178
|
+
feature.pr = { ...pr, status: newPrStatus };
|
|
179
|
+
if (newPrStatus === PrStatus.Merged) {
|
|
180
|
+
feature.lifecycle = SdlcLifecycle.Maintain;
|
|
181
|
+
await this.completeAgentRun(feature);
|
|
182
|
+
this.emitNotification(NotificationEventType.PrMerged, feature.id, feature.agentRunId ?? '', feature.name, `PR #${pr.number} merged for ${feature.name}`, NotificationSeverity.Success);
|
|
183
|
+
}
|
|
184
|
+
else if (newPrStatus === PrStatus.Closed) {
|
|
185
|
+
this.emitNotification(NotificationEventType.PrClosed, feature.id, feature.agentRunId ?? '', feature.name, `PR #${pr.number} closed for ${feature.name}`, NotificationSeverity.Warning);
|
|
186
|
+
}
|
|
187
|
+
tracked.prStatus = newPrStatus;
|
|
188
|
+
needsUpdate = true;
|
|
189
|
+
}
|
|
190
|
+
// Check CI status transition
|
|
191
|
+
try {
|
|
192
|
+
const ciResult = await this.gitPrService.getCiStatus(feature.repositoryPath, feature.branch);
|
|
193
|
+
const newCiStatus = CI_STATUS_MAP[ciResult.status] ?? CiStatus.Pending;
|
|
194
|
+
if (newCiStatus !== tracked.ciStatus) {
|
|
195
|
+
// eslint-disable-next-line no-console
|
|
196
|
+
console.log(`${TAG} CI status changed: ${tracked.ciStatus ?? 'none'} -> ${newCiStatus} for "${feature.name}"`);
|
|
197
|
+
feature.pr = { ...(feature.pr ?? pr), ciStatus: newCiStatus };
|
|
198
|
+
if (newCiStatus === CiStatus.Success) {
|
|
199
|
+
this.emitNotification(NotificationEventType.PrChecksPassed, feature.id, feature.agentRunId ?? '', feature.name, `CI checks passed for ${feature.name}`, NotificationSeverity.Success);
|
|
200
|
+
}
|
|
201
|
+
else if (newCiStatus === CiStatus.Failure) {
|
|
202
|
+
this.emitNotification(NotificationEventType.PrChecksFailed, feature.id, feature.agentRunId ?? '', feature.name, `CI checks failed for ${feature.name}`, NotificationSeverity.Error);
|
|
203
|
+
}
|
|
204
|
+
tracked.ciStatus = newCiStatus;
|
|
205
|
+
needsUpdate = true;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
210
|
+
// eslint-disable-next-line no-console
|
|
211
|
+
console.warn(`${TAG} getCiStatus failed for "${feature.name}": ${msg}`);
|
|
212
|
+
}
|
|
213
|
+
if (needsUpdate) {
|
|
214
|
+
await this.featureRepo.update(feature);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/** Mark associated agent run as completed so the UI reflects "done" state. */
|
|
218
|
+
async completeAgentRun(feature) {
|
|
219
|
+
if (!feature.agentRunId)
|
|
220
|
+
return;
|
|
221
|
+
try {
|
|
222
|
+
await this.agentRunRepo.updateStatus(feature.agentRunId, AgentRunStatus.completed);
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
226
|
+
// eslint-disable-next-line no-console
|
|
227
|
+
console.warn(`${TAG} Failed to complete agent run ${feature.agentRunId}: ${msg}`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
emitNotification(eventType, featureId, agentRunId, featureName, message, severity) {
|
|
231
|
+
const event = {
|
|
232
|
+
eventType,
|
|
233
|
+
featureId,
|
|
234
|
+
agentRunId,
|
|
235
|
+
featureName,
|
|
236
|
+
message,
|
|
237
|
+
severity,
|
|
238
|
+
timestamp: new Date().toISOString(),
|
|
239
|
+
};
|
|
240
|
+
this.notificationService.notify(event);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// --- Singleton accessors (follows NotificationWatcherService pattern) ---
|
|
244
|
+
let watcherInstance = null;
|
|
245
|
+
/**
|
|
246
|
+
* Initialize the PR sync watcher singleton.
|
|
247
|
+
* Must be called once during web server startup.
|
|
248
|
+
*
|
|
249
|
+
* @throws Error if the watcher is already initialized
|
|
250
|
+
*/
|
|
251
|
+
export function initializePrSyncWatcher(featureRepo, agentRunRepo, gitPrService, notificationService, pollIntervalMs) {
|
|
252
|
+
if (watcherInstance !== null) {
|
|
253
|
+
throw new Error('PR sync watcher already initialized. Cannot re-initialize.');
|
|
254
|
+
}
|
|
255
|
+
watcherInstance = new PrSyncWatcherService(featureRepo, agentRunRepo, gitPrService, notificationService, pollIntervalMs);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Get the PR sync watcher singleton.
|
|
259
|
+
*
|
|
260
|
+
* @returns The PR sync watcher service
|
|
261
|
+
* @throws Error if the watcher hasn't been initialized yet
|
|
262
|
+
*/
|
|
263
|
+
export function getPrSyncWatcher() {
|
|
264
|
+
if (watcherInstance === null) {
|
|
265
|
+
throw new Error('PR sync watcher not initialized. Call initializePrSyncWatcher() during web server startup.');
|
|
266
|
+
}
|
|
267
|
+
return watcherInstance;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Check if the PR sync watcher has been initialized.
|
|
271
|
+
*/
|
|
272
|
+
export function hasPrSyncWatcher() {
|
|
273
|
+
return watcherInstance !== null;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Reset the PR sync watcher singleton (for testing purposes only).
|
|
277
|
+
* Stops the watcher if running before resetting.
|
|
278
|
+
*
|
|
279
|
+
* @internal
|
|
280
|
+
*/
|
|
281
|
+
export function resetPrSyncWatcher() {
|
|
282
|
+
if (watcherInstance !== null) {
|
|
283
|
+
watcherInstance.stop();
|
|
284
|
+
}
|
|
285
|
+
watcherInstance = null;
|
|
286
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui.command.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/cli/commands/ui.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,OAAO,EAAwB,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"ui.command.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/cli/commands/ui.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,OAAO,EAAwB,MAAM,WAAW,CAAC;AA+B1D;;GAEG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAkFzC"}
|
|
@@ -20,6 +20,7 @@ import { container } from '../../../../packages/core/src/infrastructure/di/conta
|
|
|
20
20
|
import { setVersionEnvVars } from '../../../../packages/core/src/infrastructure/services/version.service.js';
|
|
21
21
|
import { resolveWebDir } from '../../../../packages/core/src/infrastructure/services/web-server.service.js';
|
|
22
22
|
import { initializeNotificationWatcher, getNotificationWatcher, } from '../../../../packages/core/src/infrastructure/services/notifications/notification-watcher.service.js';
|
|
23
|
+
import { initializePrSyncWatcher, getPrSyncWatcher, } from '../../../../packages/core/src/infrastructure/services/pr-sync/pr-sync-watcher.service.js';
|
|
23
24
|
import { BrowserOpenerService } from '../../../../packages/core/src/infrastructure/services/browser-opener.service.js';
|
|
24
25
|
import { colors, fmt, messages } from '../ui/index.js';
|
|
25
26
|
function parsePort(value) {
|
|
@@ -63,6 +64,10 @@ Examples:
|
|
|
63
64
|
const notificationService = container.resolve('INotificationService');
|
|
64
65
|
initializeNotificationWatcher(runRepo, phaseTimingRepo, featureRepo, notificationService);
|
|
65
66
|
getNotificationWatcher().start();
|
|
67
|
+
// Start PR sync watcher to detect PR/CI status transitions on GitHub
|
|
68
|
+
const gitPrService = container.resolve('IGitPrService');
|
|
69
|
+
initializePrSyncWatcher(featureRepo, runRepo, gitPrService, notificationService);
|
|
70
|
+
getPrSyncWatcher().start();
|
|
66
71
|
const url = `http://localhost:${port}`;
|
|
67
72
|
messages.success(`Server ready at ${fmt.code(url)}`);
|
|
68
73
|
messages.info('Press Ctrl+C to stop');
|
|
@@ -84,6 +89,7 @@ Examples:
|
|
|
84
89
|
// Force exit after 5s if graceful shutdown stalls
|
|
85
90
|
const forceExit = setTimeout(() => process.exit(0), 5000);
|
|
86
91
|
forceExit.unref();
|
|
92
|
+
getPrSyncWatcher().stop();
|
|
87
93
|
getNotificationWatcher().stop();
|
|
88
94
|
await service.stop();
|
|
89
95
|
process.exit(0);
|
|
@@ -1 +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":"
|
|
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":"AAQA,UAAU,wBAAwB;IAChC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAED,wBAAgB,mBAAmB,CAAC,EAAE,QAAQ,EAAE,EAAE,wBAAwB,2CAuCzE"}
|
package/dist/src/presentation/web/components/common/add-repository-node/add-repository-button.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from 'react';
|
|
4
|
-
import {
|
|
4
|
+
import { FolderPlus, Loader2 } from 'lucide-react';
|
|
5
|
+
import { Button } from '../../ui/button.js';
|
|
6
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../ui/tooltip.js';
|
|
5
7
|
import { pickFolder } from './pick-folder.js';
|
|
6
8
|
export function AddRepositoryButton({ onSelect }) {
|
|
7
9
|
const [loading, setLoading] = useState(false);
|
|
@@ -19,5 +21,5 @@ export function AddRepositoryButton({ onSelect }) {
|
|
|
19
21
|
setLoading(false);
|
|
20
22
|
}
|
|
21
23
|
}
|
|
22
|
-
return (
|
|
24
|
+
return (_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": "Add Repository", "data-testid": "add-repository-button", onClick: handleClick, disabled: loading, children: loading ? (_jsx(Loader2, { className: "h-5 w-5 animate-spin" })) : (_jsx(FolderPlus, { className: "h-5 w-5" })) }) }), _jsx(TooltipContent, { children: "Add Repository" })] }) }));
|
|
23
25
|
}
|
|
@@ -5,4 +5,6 @@ export default meta;
|
|
|
5
5
|
type Story = StoryObj<AddRepositoryNodeData>;
|
|
6
6
|
export declare const Default: Story;
|
|
7
7
|
export declare const WithCallback: Story;
|
|
8
|
+
/** Add Repository button as it appears in the top bar alongside other toggles */
|
|
9
|
+
export declare const TopBarButton: StoryObj;
|
|
8
10
|
//# sourceMappingURL=add-repository-node.stories.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"add-repository-node.stories.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/add-repository-node/add-repository-node.stories.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"add-repository-node.stories.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/add-repository-node/add-repository-node.stories.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAIvD,OAAO,KAAK,EAAE,qBAAqB,EAAyB,MAAM,8BAA8B,CAAC;AAgCjG,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,qBAAqB,CAOrC,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,qBAAqB,CAAC,CAAC;AAE7C,eAAO,MAAM,OAAO,EAAE,KAErB,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,KAK1B,CAAC;AAIF,iFAAiF;AACjF,eAAO,MAAM,YAAY,EAAE,QAM1B,CAAC"}
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { useMemo } from 'react';
|
|
3
3
|
import { ReactFlowProvider, ReactFlow } from '@xyflow/react';
|
|
4
4
|
import { AddRepositoryNode } from './add-repository-node.js';
|
|
5
|
+
import { AddRepositoryButton } from './add-repository-button.js';
|
|
5
6
|
const nodeTypes = { addRepositoryNode: AddRepositoryNode };
|
|
6
7
|
function AddRepositoryNodeCanvas({ data, style = { width: 500, height: 250 }, }) {
|
|
7
8
|
const nodes = useMemo(() => [{ id: 'node-1', type: 'addRepositoryNode', position: { x: 0, y: 0 }, data }], [data]);
|
|
@@ -25,3 +26,8 @@ export const WithCallback = {
|
|
|
25
26
|
},
|
|
26
27
|
render: (args) => _jsx(AddRepositoryNodeCanvas, { data: args }),
|
|
27
28
|
};
|
|
29
|
+
// --- Add Repository Button (top bar icon) stories ---
|
|
30
|
+
/** Add Repository button as it appears in the top bar alongside other toggles */
|
|
31
|
+
export const TopBarButton = {
|
|
32
|
+
render: () => (_jsx("div", { className: "flex gap-1", children: _jsx(AddRepositoryButton, { onSelect: () => undefined }) })),
|
|
33
|
+
};
|
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;AAEH,OAAO,EAIL,qBAAqB,EACtB,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,KAAK,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AAE3F;;;;;;;;;;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;AAED,oGAAoG;AACpG,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,qBAAqB,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;AAEH,OAAO,EAIL,qBAAqB,EACtB,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,KAAK,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AAE3F;;;;;;;;;;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;AAED,oGAAoG;AACpG,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,qBAAqB,GAAG,gBAAgB,CAkBtF;AAYD,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,GAAG,SAAS,GAC5B,qBAAqB,GAAG,SAAS,CAGnC"}
|
|
@@ -84,7 +84,13 @@ export function mapEventTypeToState(eventType) {
|
|
|
84
84
|
case NotificationEventType.AgentCompleted:
|
|
85
85
|
return 'done';
|
|
86
86
|
case NotificationEventType.AgentFailed:
|
|
87
|
+
case NotificationEventType.PrChecksFailed:
|
|
87
88
|
return 'error';
|
|
89
|
+
case NotificationEventType.PrMerged:
|
|
90
|
+
case NotificationEventType.PrChecksPassed:
|
|
91
|
+
return 'done';
|
|
92
|
+
case NotificationEventType.PrClosed:
|
|
93
|
+
return 'action-required';
|
|
88
94
|
}
|
|
89
95
|
}
|
|
90
96
|
/** Maps an SSE event phaseName to a FeatureLifecyclePhase. Mirrors page.tsx nodeToLifecyclePhase. */
|
package/dist/src/presentation/web/components/features/control-center/control-center-inner.d.ts.map
CHANGED
|
@@ -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;AAY5E,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,
|
|
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;AAY5E,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,2CA0azF"}
|
package/dist/src/presentation/web/components/features/control-center/control-center-inner.js
CHANGED
|
@@ -236,6 +236,15 @@ export function ControlCenterInner({ initialNodes, initialEdges }) {
|
|
|
236
236
|
window.addEventListener('shep:open-create-drawer', handler);
|
|
237
237
|
return () => window.removeEventListener('shep:open-create-drawer', handler);
|
|
238
238
|
}, [handleAddFeature]);
|
|
239
|
+
// Listen for global "add repository" events from the top bar button
|
|
240
|
+
useEffect(() => {
|
|
241
|
+
const handler = (e) => {
|
|
242
|
+
const path = e.detail.path;
|
|
243
|
+
handleAddRepository(path);
|
|
244
|
+
};
|
|
245
|
+
window.addEventListener('shep:add-repository', handler);
|
|
246
|
+
return () => window.removeEventListener('shep:add-repository', handler);
|
|
247
|
+
}, [handleAddRepository]);
|
|
239
248
|
// Listen for notification "Review" clicks to open the relevant drawer
|
|
240
249
|
useEffect(() => {
|
|
241
250
|
const handler = (e) => {
|
package/dist/src/presentation/web/components/features/features-canvas/features-canvas.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"features-canvas.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/features/features-canvas/features-canvas.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAKlE,OAAO,KAAK,EAAE,eAAe,EAAmB,MAAM,kCAAkC,CAAC;AAEzF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAE9E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yCAAyC,CAAC;AAGrF,MAAM,MAAM,cAAc,GAAG,eAAe,GAAG,kBAAkB,GAAG,qBAAqB,CAAC;AAE1F,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,cAAc,CAAC,EAAE,KAAK,IAAI,CAAC;IAChE,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,UAAU,EAAE,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IACtE,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IAChD,eAAe,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/C,kBAAkB,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,SAAS,CAAC,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,CAAC;IAC7C,kBAAkB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC9B;AAED,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,KAAK,EACL,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,SAAS,EACT,WAAW,EACX,WAAW,EACX,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAClB,OAAO,EACP,UAAU,GACX,EAAE,mBAAmB,
|
|
1
|
+
{"version":3,"file":"features-canvas.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/features/features-canvas/features-canvas.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAKlE,OAAO,KAAK,EAAE,eAAe,EAAmB,MAAM,kCAAkC,CAAC;AAEzF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAE9E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yCAAyC,CAAC;AAGrF,MAAM,MAAM,cAAc,GAAG,eAAe,GAAG,kBAAkB,GAAG,qBAAqB,CAAC;AAE1F,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,cAAc,CAAC,EAAE,KAAK,IAAI,CAAC;IAChE,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,UAAU,EAAE,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IACtE,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IAChD,eAAe,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/C,kBAAkB,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,SAAS,CAAC,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,CAAC;IAC7C,kBAAkB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC9B;AAED,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,KAAK,EACL,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,SAAS,EACT,WAAW,EACX,WAAW,EACX,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAClB,OAAO,EACP,UAAU,GACX,EAAE,mBAAmB,2CAmHrB"}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useCallback, useMemo } from 'react';
|
|
4
|
-
import { ReactFlow, Background, Controls,
|
|
4
|
+
import { ReactFlow, Background, Controls, ReactFlowProvider } from '@xyflow/react';
|
|
5
5
|
import { Plus } from 'lucide-react';
|
|
6
6
|
import { Button } from '../../ui/button.js';
|
|
7
7
|
import { EmptyState } from '../../common/empty-state/index.js';
|
|
8
8
|
import { FeatureNode } from '../../common/feature-node/index.js';
|
|
9
9
|
import { RepositoryNode } from '../../common/repository-node/index.js';
|
|
10
|
-
import {
|
|
10
|
+
import { AddRepositoryNode } from '../../common/add-repository-node/index.js';
|
|
11
11
|
import { DependencyEdge } from './dependency-edge.js';
|
|
12
12
|
export function FeaturesCanvas({ nodes, edges, onNodesChange, onAddFeature, onNodeAction, onNodeSettings, onConnect, onNodeClick, onPaneClick, onRepositoryAdd, onRepositoryDelete, onRepositorySelect, toolbar, emptyState, }) {
|
|
13
13
|
const nodeTypes = useMemo(() => ({
|
|
@@ -63,5 +63,5 @@ export function FeaturesCanvas({ nodes, edges, onNodesChange, onAddFeature, onNo
|
|
|
63
63
|
}
|
|
64
64
|
return (_jsx("div", { "data-testid": "features-canvas-empty", children: _jsx(EmptyState, { title: "No features yet", description: "Get started by creating your first feature.", action: _jsxs(Button, { onClick: onAddFeature, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "New Feature"] }) }) }));
|
|
65
65
|
}
|
|
66
|
-
return (_jsx("div", { "data-testid": "features-canvas", className: "h-full w-full", children: _jsx(ReactFlowProvider, { children: _jsxs(ReactFlow, { nodes: enrichedNodes, edges: edges, nodeTypes: nodeTypes, edgeTypes: edgeTypes, isValidConnection: isValidConnection, onConnect: onConnect, onNodesChange: onNodesChange, onNodeClick: onNodeClick, onPaneClick: onPaneClick, defaultViewport: { x: 30, y: 30, zoom: 0.85 }, nodesDraggable: false, nodesConnectable: false, elementsSelectable: false, children: [_jsx(Background, {}), _jsx(Controls, {}),
|
|
66
|
+
return (_jsx("div", { "data-testid": "features-canvas", className: "h-full w-full", children: _jsx(ReactFlowProvider, { children: _jsxs(ReactFlow, { nodes: enrichedNodes, edges: edges, nodeTypes: nodeTypes, edgeTypes: edgeTypes, isValidConnection: isValidConnection, onConnect: onConnect, onNodesChange: onNodesChange, onNodeClick: onNodeClick, onPaneClick: onPaneClick, defaultViewport: { x: 30, y: 30, zoom: 0.85 }, nodesDraggable: false, nodesConnectable: false, elementsSelectable: false, children: [_jsx(Background, {}), _jsx(Controls, {}), toolbar] }) }) }));
|
|
67
67
|
}
|
package/dist/src/presentation/web/components/features/features-canvas/features-canvas.stories.d.ts
CHANGED
|
@@ -14,5 +14,4 @@ export declare const SingleRepositoryMultipleFeatures: Story;
|
|
|
14
14
|
export declare const MultipleRepositories: Story;
|
|
15
15
|
export declare const MultipleRepositoriesMixedConnections: Story;
|
|
16
16
|
export declare const InteractiveWithRepository: Story;
|
|
17
|
-
export declare const AddRepositoryButtonClearanceCheck: Story;
|
|
18
17
|
//# sourceMappingURL=features-canvas.stories.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"features-canvas.stories.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/features/features-canvas/features-canvas.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAOnD,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,cAAc,CAcrC,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAwKnC,eAAO,MAAM,KAAK,EAAE,KAMnB,CAAC;AAEF,eAAO,MAAM,UAAU,EAAE,KAKxB,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,KAK3B,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,KAK5B,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,KAWzB,CAAC;AA4DF,eAAO,MAAM,mCAAmC,EAAE,KAOjD,CAAC;AAkCF,eAAO,MAAM,gCAAgC,EAAE,KAS9C,CAAC;AAgEF,eAAO,MAAM,gCAAgC,EAAE,KAW9C,CAAC;AAuDF,eAAO,MAAM,oBAAoB,EAAE,KAUlC,CAAC;AA2GF,eAAO,MAAM,oCAAoC,EAAE,KAOlD,CAAC;AAEF,eAAO,MAAM,yBAAyB,EAAE,KASvC,CAAC
|
|
1
|
+
{"version":3,"file":"features-canvas.stories.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/features/features-canvas/features-canvas.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAOnD,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,cAAc,CAcrC,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAwKnC,eAAO,MAAM,KAAK,EAAE,KAMnB,CAAC;AAEF,eAAO,MAAM,UAAU,EAAE,KAKxB,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,KAK3B,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,KAK5B,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,KAWzB,CAAC;AA4DF,eAAO,MAAM,mCAAmC,EAAE,KAOjD,CAAC;AAkCF,eAAO,MAAM,gCAAgC,EAAE,KAS9C,CAAC;AAgEF,eAAO,MAAM,gCAAgC,EAAE,KAW9C,CAAC;AAuDF,eAAO,MAAM,oBAAoB,EAAE,KAUlC,CAAC;AA2GF,eAAO,MAAM,oCAAoC,EAAE,KAOlD,CAAC;AAEF,eAAO,MAAM,yBAAyB,EAAE,KASvC,CAAC"}
|
package/dist/src/presentation/web/components/features/features-canvas/features-canvas.stories.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { FeaturesCanvas } from './features-canvas.js';
|
|
3
3
|
import { layoutWithDagre } from '../../../lib/layout-with-dagre.js';
|
|
4
4
|
const meta = {
|
|
@@ -562,33 +562,3 @@ export const InteractiveWithRepository = {
|
|
|
562
562
|
onRepositoryAdd: () => undefined,
|
|
563
563
|
},
|
|
564
564
|
};
|
|
565
|
-
// Story to visually verify the Add Repository button does not overlap the ThemeToggle.
|
|
566
|
-
// The simulated ThemeToggle (top-3 right-3, 40×40 px) is rendered as an overlay so
|
|
567
|
-
// reviewers can confirm the mr-16 (64 px) gap keeps the Panel clear of the toggle zone.
|
|
568
|
-
export const AddRepositoryButtonClearanceCheck = {
|
|
569
|
-
decorators: [
|
|
570
|
-
(Story) => (_jsxs("div", { style: { height: '100vh', position: 'relative' }, children: [_jsx(Story, {}), _jsx("div", { style: {
|
|
571
|
-
position: 'absolute',
|
|
572
|
-
top: 12,
|
|
573
|
-
right: 12,
|
|
574
|
-
width: 40,
|
|
575
|
-
height: 40,
|
|
576
|
-
background: 'rgba(255, 0, 0, 0.35)',
|
|
577
|
-
border: '2px dashed red',
|
|
578
|
-
borderRadius: 6,
|
|
579
|
-
display: 'flex',
|
|
580
|
-
alignItems: 'center',
|
|
581
|
-
justifyContent: 'center',
|
|
582
|
-
fontSize: 10,
|
|
583
|
-
pointerEvents: 'none',
|
|
584
|
-
zIndex: 50,
|
|
585
|
-
}, children: "Theme" })] })),
|
|
586
|
-
],
|
|
587
|
-
args: {
|
|
588
|
-
nodes: repoFeatureNodes,
|
|
589
|
-
edges: repoFeatureEdges,
|
|
590
|
-
onNodeAction: () => undefined,
|
|
591
|
-
onNodeSettings: () => undefined,
|
|
592
|
-
onRepositorySelect: () => undefined,
|
|
593
|
-
},
|
|
594
|
-
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-shell.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/layouts/app-shell/app-shell.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"app-shell.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/layouts/app-shell/app-shell.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AASpD,UAAU,aAAa;IACrB,QAAQ,EAAE,SAAS,CAAC;CACrB;AAgCD,wBAAgB,QAAQ,CAAC,EAAE,QAAQ,EAAE,EAAE,aAAa,2CAMnD"}
|
|
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { useCallback } from 'react';
|
|
4
4
|
import { SidebarProvider, SidebarInset } from '../../ui/sidebar.js';
|
|
5
5
|
import { AppSidebar } from '../../layouts/app-sidebar/index.js';
|
|
6
|
+
import { AddRepositoryButton } from '../../common/add-repository-node/index.js';
|
|
6
7
|
import { ThemeToggle } from '../../common/theme-toggle/index.js';
|
|
7
8
|
import { SoundToggle } from '../../common/sound-toggle/index.js';
|
|
8
9
|
import { AgentEventsProvider } from '../../../hooks/agent-events-provider.js';
|
|
@@ -14,7 +15,10 @@ function AppShellInner({ children }) {
|
|
|
14
15
|
const handleNewFeature = useCallback(() => {
|
|
15
16
|
window.dispatchEvent(new CustomEvent('shep:open-create-drawer'));
|
|
16
17
|
}, []);
|
|
17
|
-
|
|
18
|
+
const handleRepositorySelect = useCallback((path) => {
|
|
19
|
+
window.dispatchEvent(new CustomEvent('shep:add-repository', { detail: { path } }));
|
|
20
|
+
}, []);
|
|
21
|
+
return (_jsxs(SidebarProvider, { children: [_jsx(AppSidebar, { features: [], onNewFeature: handleNewFeature }), _jsx(SidebarInset, { children: _jsxs("div", { className: "relative h-full", children: [_jsxs("div", { className: "absolute top-3 right-3 z-50 flex gap-1", children: [_jsx(AddRepositoryButton, { onSelect: handleRepositorySelect }), _jsx(SoundToggle, {}), _jsx(ThemeToggle, {})] }), _jsx("main", { className: "h-full", children: children })] }) })] }));
|
|
18
22
|
}
|
|
19
23
|
export function AppShell({ children }) {
|
|
20
24
|
return (_jsx(AgentEventsProvider, { children: _jsx(AppShellInner, { children: children }) }));
|
|
@@ -18,6 +18,7 @@ import { initializeContainer, container } from '../../../packages/core/src/infra
|
|
|
18
18
|
import { InitializeSettingsUseCase } from '../../../packages/core/src/application/use-cases/settings/initialize-settings.use-case.js';
|
|
19
19
|
import { initializeSettings } from '../../../packages/core/src/infrastructure/services/settings.service.js';
|
|
20
20
|
import { initializeNotificationWatcher, getNotificationWatcher, } from '../../../packages/core/src/infrastructure/services/notifications/notification-watcher.service.js';
|
|
21
|
+
import { initializePrSyncWatcher, getPrSyncWatcher, } from '../../../packages/core/src/infrastructure/services/pr-sync/pr-sync-watcher.service.js';
|
|
21
22
|
const DEFAULT_PORT = 3000;
|
|
22
23
|
async function isPortAvailable(port) {
|
|
23
24
|
return new Promise((resolve) => {
|
|
@@ -61,6 +62,10 @@ async function main() {
|
|
|
61
62
|
const notificationService = container.resolve('INotificationService');
|
|
62
63
|
initializeNotificationWatcher(runRepo, phaseTimingRepo, featureRepo, notificationService);
|
|
63
64
|
getNotificationWatcher().start();
|
|
65
|
+
// Start PR sync watcher to detect PR/CI status transitions on GitHub
|
|
66
|
+
const gitPrService = container.resolve('IGitPrService');
|
|
67
|
+
initializePrSyncWatcher(featureRepo, runRepo, gitPrService, notificationService);
|
|
68
|
+
getPrSyncWatcher().start();
|
|
64
69
|
}
|
|
65
70
|
catch (error) {
|
|
66
71
|
console.warn('[dev-server] DI initialization failed — features will be empty:', error);
|
|
@@ -106,6 +111,12 @@ async function main() {
|
|
|
106
111
|
catch {
|
|
107
112
|
/* not initialized */
|
|
108
113
|
}
|
|
114
|
+
try {
|
|
115
|
+
getPrSyncWatcher().stop();
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
/* not initialized */
|
|
119
|
+
}
|
|
109
120
|
server.closeAllConnections();
|
|
110
121
|
await Promise.all([
|
|
111
122
|
new Promise((resolve) => server.close(() => resolve())),
|