@shepai/cli 1.52.1 → 1.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +39 -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.d.ts.map +1 -1
- package/dist/src/presentation/web/app/page.js +34 -6
- 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 +5 -1
- package/dist/src/presentation/web/next.config.d.ts.map +1 -1
- package/dist/src/presentation/web/next.config.js +11 -2
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +2 -2
- 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 +5 -2
- package/web/.next/required-server-files.json +5 -2
- 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 +5 -2
- 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 -6
- 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 +9 -0
- package/web/.next/standalone/src/presentation/web/next.config.ts +11 -2
- package/web/.next/standalone/src/presentation/web/server.js +1 -1
- package/web/.next/static/chunks/48309f9ad2a84b15.css +2 -0
- package/web/.next/static/chunks/5123ae1490fd2606.js +10 -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/web/next.config.mjs +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/59b8bd47023e0249.css +0 -2
- package/web/.next/static/chunks/de88642301974a9c.js +0 -10
- /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/{bBUbYZdszzvMJTi9A76CZ → OXSWdTgy-1XJBN9IJtQPz}/_buildManifest.js +0 -0
- /package/web/.next/static/{bBUbYZdszzvMJTi9A76CZ → OXSWdTgy-1XJBN9IJtQPz}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{bBUbYZdszzvMJTi9A76CZ → OXSWdTgy-1XJBN9IJtQPz}/_ssgManifest.js +0 -0
package/dist/packages/core/src/infrastructure/services/agents/feature-agent/lifecycle-context.d.ts
CHANGED
|
@@ -5,22 +5,44 @@
|
|
|
5
5
|
* SDLC lifecycle as they execute, following the same pattern as
|
|
6
6
|
* phase-timing-context.ts.
|
|
7
7
|
*
|
|
8
|
-
* The worker calls setLifecycleContext() once after DI init
|
|
9
|
-
* Nodes call updateNodeLifecycle()
|
|
10
|
-
* Errors are swallowed so lifecycle updates
|
|
8
|
+
* The worker calls setLifecycleContext() once after DI init, passing an
|
|
9
|
+
* UpdateFeatureLifecycleUseCase instance. Nodes call updateNodeLifecycle()
|
|
10
|
+
* at the start of execution. Errors are swallowed so lifecycle updates
|
|
11
|
+
* never block graph execution.
|
|
12
|
+
*
|
|
13
|
+
* Routing lifecycle updates through UpdateFeatureLifecycleUseCase ensures
|
|
14
|
+
* that CheckAndUnblockFeaturesUseCase fires on every transition,
|
|
15
|
+
* automatically unblocking blocked children that are now eligible to start.
|
|
16
|
+
*/
|
|
17
|
+
import { SdlcLifecycle } from '../../../../domain/generated/output.js';
|
|
18
|
+
/**
|
|
19
|
+
* Minimal interface satisfied by UpdateFeatureLifecycleUseCase.
|
|
20
|
+
* Using a structural interface avoids a concrete import between infrastructure
|
|
21
|
+
* and application layer classes.
|
|
11
22
|
*/
|
|
12
|
-
|
|
23
|
+
interface LifecycleUpdater {
|
|
24
|
+
execute(input: {
|
|
25
|
+
featureId: string;
|
|
26
|
+
lifecycle: SdlcLifecycle;
|
|
27
|
+
}): Promise<void>;
|
|
28
|
+
}
|
|
13
29
|
/**
|
|
14
30
|
* Set the lifecycle context. Called once by the worker after DI init.
|
|
31
|
+
*
|
|
32
|
+
* @param featureId - The feature being processed.
|
|
33
|
+
* @param updater - An UpdateFeatureLifecycleUseCase instance (or compatible updater).
|
|
15
34
|
*/
|
|
16
|
-
export declare function setLifecycleContext(featureId: string,
|
|
35
|
+
export declare function setLifecycleContext(featureId: string, updater: LifecycleUpdater): void;
|
|
17
36
|
/**
|
|
18
37
|
* Clear the lifecycle context. Useful for testing.
|
|
19
38
|
*/
|
|
20
39
|
export declare function clearLifecycleContext(): void;
|
|
21
40
|
/**
|
|
22
41
|
* Update the feature's lifecycle to match the current graph node.
|
|
42
|
+
* Routes through UpdateFeatureLifecycleUseCase so that
|
|
43
|
+
* CheckAndUnblockFeaturesUseCase fires on every transition.
|
|
23
44
|
* No-op if context is not set or the node name has no lifecycle mapping.
|
|
24
45
|
*/
|
|
25
46
|
export declare function updateNodeLifecycle(nodeName: string): Promise<void>;
|
|
47
|
+
export {};
|
|
26
48
|
//# sourceMappingURL=lifecycle-context.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecycle-context.d.ts","sourceRoot":"","sources":["../../../../../../../../packages/core/src/infrastructure/services/agents/feature-agent/lifecycle-context.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"lifecycle-context.d.ts","sourceRoot":"","sources":["../../../../../../../../packages/core/src/infrastructure/services/agents/feature-agent/lifecycle-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAE7D;;;;GAIG;AACH,UAAU,gBAAgB;IACxB,OAAO,CAAC,KAAK,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,aAAa,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChF;AAeD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAGtF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAG5C;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAWzE"}
|
package/dist/packages/core/src/infrastructure/services/agents/feature-agent/lifecycle-context.js
CHANGED
|
@@ -5,13 +5,18 @@
|
|
|
5
5
|
* SDLC lifecycle as they execute, following the same pattern as
|
|
6
6
|
* phase-timing-context.ts.
|
|
7
7
|
*
|
|
8
|
-
* The worker calls setLifecycleContext() once after DI init
|
|
9
|
-
* Nodes call updateNodeLifecycle()
|
|
10
|
-
* Errors are swallowed so lifecycle updates
|
|
8
|
+
* The worker calls setLifecycleContext() once after DI init, passing an
|
|
9
|
+
* UpdateFeatureLifecycleUseCase instance. Nodes call updateNodeLifecycle()
|
|
10
|
+
* at the start of execution. Errors are swallowed so lifecycle updates
|
|
11
|
+
* never block graph execution.
|
|
12
|
+
*
|
|
13
|
+
* Routing lifecycle updates through UpdateFeatureLifecycleUseCase ensures
|
|
14
|
+
* that CheckAndUnblockFeaturesUseCase fires on every transition,
|
|
15
|
+
* automatically unblocking blocked children that are now eligible to start.
|
|
11
16
|
*/
|
|
12
17
|
import { SdlcLifecycle } from '../../../../domain/generated/output.js';
|
|
13
18
|
let contextFeatureId;
|
|
14
|
-
let
|
|
19
|
+
let contextUpdater;
|
|
15
20
|
/** Map graph node names to their corresponding SDLC lifecycle stage. */
|
|
16
21
|
const NODE_TO_LIFECYCLE = {
|
|
17
22
|
analyze: SdlcLifecycle.Analyze,
|
|
@@ -23,37 +28,35 @@ const NODE_TO_LIFECYCLE = {
|
|
|
23
28
|
};
|
|
24
29
|
/**
|
|
25
30
|
* Set the lifecycle context. Called once by the worker after DI init.
|
|
31
|
+
*
|
|
32
|
+
* @param featureId - The feature being processed.
|
|
33
|
+
* @param updater - An UpdateFeatureLifecycleUseCase instance (or compatible updater).
|
|
26
34
|
*/
|
|
27
|
-
export function setLifecycleContext(featureId,
|
|
35
|
+
export function setLifecycleContext(featureId, updater) {
|
|
28
36
|
contextFeatureId = featureId;
|
|
29
|
-
|
|
37
|
+
contextUpdater = updater;
|
|
30
38
|
}
|
|
31
39
|
/**
|
|
32
40
|
* Clear the lifecycle context. Useful for testing.
|
|
33
41
|
*/
|
|
34
42
|
export function clearLifecycleContext() {
|
|
35
43
|
contextFeatureId = undefined;
|
|
36
|
-
|
|
44
|
+
contextUpdater = undefined;
|
|
37
45
|
}
|
|
38
46
|
/**
|
|
39
47
|
* Update the feature's lifecycle to match the current graph node.
|
|
48
|
+
* Routes through UpdateFeatureLifecycleUseCase so that
|
|
49
|
+
* CheckAndUnblockFeaturesUseCase fires on every transition.
|
|
40
50
|
* No-op if context is not set or the node name has no lifecycle mapping.
|
|
41
51
|
*/
|
|
42
52
|
export async function updateNodeLifecycle(nodeName) {
|
|
43
|
-
if (!contextFeatureId || !
|
|
53
|
+
if (!contextFeatureId || !contextUpdater)
|
|
44
54
|
return;
|
|
45
55
|
const lifecycle = NODE_TO_LIFECYCLE[nodeName];
|
|
46
56
|
if (!lifecycle)
|
|
47
57
|
return;
|
|
48
58
|
try {
|
|
49
|
-
|
|
50
|
-
if (!feature)
|
|
51
|
-
return;
|
|
52
|
-
await contextRepository.update({
|
|
53
|
-
...feature,
|
|
54
|
-
lifecycle,
|
|
55
|
-
updatedAt: new Date(),
|
|
56
|
-
});
|
|
59
|
+
await contextUpdater.execute({ featureId: contextFeatureId, lifecycle });
|
|
57
60
|
}
|
|
58
61
|
catch {
|
|
59
62
|
// Swallow — lifecycle update failure is non-fatal
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CI Helper Utilities
|
|
3
|
+
*
|
|
4
|
+
* Shared helpers for the CI watch/fix loop and merge node:
|
|
5
|
+
* - extractRunId: parse GitHub Actions run ID from URL
|
|
6
|
+
* - handleCiTerminalFailure: update feature repo on CI failure
|
|
7
|
+
* - buildCiExhaustedError: structured error for exhausted/timed-out loops
|
|
8
|
+
*/
|
|
9
|
+
import type { IFeatureRepository } from '../../../../../../application/ports/output/repositories/feature-repository.interface.js';
|
|
10
|
+
import { type CiFixRecord } from '../../../../../../domain/generated/output.js';
|
|
11
|
+
/**
|
|
12
|
+
* Extract the numeric GitHub Actions run ID from a run URL.
|
|
13
|
+
* Example: https://github.com/org/repo/actions/runs/12345 → "12345"
|
|
14
|
+
*/
|
|
15
|
+
export declare function extractRunId(runUrl: string): string | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Update the feature repository to mark CI as failed before throwing.
|
|
18
|
+
*/
|
|
19
|
+
export declare function handleCiTerminalFailure(feature: Awaited<ReturnType<Pick<IFeatureRepository, 'findById'>['findById']>>, prUrl: string | null, prNumber: number | null, featureRepository: Pick<IFeatureRepository, 'findById' | 'update'>, messages: string[]): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Build a structured error message describing the CI fix loop outcome.
|
|
22
|
+
*/
|
|
23
|
+
export declare function buildCiExhaustedError(attempts: number, history: CiFixRecord[], reason: 'exhausted' | 'timeout'): Error;
|
|
24
|
+
//# sourceMappingURL=ci-helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ci-helpers.d.ts","sourceRoot":"","sources":["../../../../../../../../../../packages/core/src/infrastructure/services/agents/feature-agent/nodes/merge/ci-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yEAAyE,CAAC;AAClH,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAEpF;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAG/D;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,kBAAkB,EAAE,UAAU,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAC9E,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,iBAAiB,EAAE,IAAI,CAAC,kBAAkB,EAAE,UAAU,GAAG,QAAQ,CAAC,EAClE,QAAQ,EAAE,MAAM,EAAE,GACjB,OAAO,CAAC,IAAI,CAAC,CAef;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,WAAW,EAAE,EACtB,MAAM,EAAE,WAAW,GAAG,SAAS,GAC9B,KAAK,CAUP"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CI Helper Utilities
|
|
3
|
+
*
|
|
4
|
+
* Shared helpers for the CI watch/fix loop and merge node:
|
|
5
|
+
* - extractRunId: parse GitHub Actions run ID from URL
|
|
6
|
+
* - handleCiTerminalFailure: update feature repo on CI failure
|
|
7
|
+
* - buildCiExhaustedError: structured error for exhausted/timed-out loops
|
|
8
|
+
*/
|
|
9
|
+
import { PrStatus, CiStatus } from '../../../../../../domain/generated/output.js';
|
|
10
|
+
/**
|
|
11
|
+
* Extract the numeric GitHub Actions run ID from a run URL.
|
|
12
|
+
* Example: https://github.com/org/repo/actions/runs/12345 → "12345"
|
|
13
|
+
*/
|
|
14
|
+
export function extractRunId(runUrl) {
|
|
15
|
+
const match = runUrl.match(/\/runs\/(\d+)/);
|
|
16
|
+
return match ? match[1] : undefined;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Update the feature repository to mark CI as failed before throwing.
|
|
20
|
+
*/
|
|
21
|
+
export async function handleCiTerminalFailure(feature, prUrl, prNumber, featureRepository, messages) {
|
|
22
|
+
if (feature && prUrl && prNumber) {
|
|
23
|
+
await featureRepository.update({
|
|
24
|
+
...feature,
|
|
25
|
+
lifecycle: feature.lifecycle,
|
|
26
|
+
pr: {
|
|
27
|
+
url: prUrl,
|
|
28
|
+
number: prNumber,
|
|
29
|
+
status: PrStatus.Open,
|
|
30
|
+
ciStatus: CiStatus.Failure,
|
|
31
|
+
},
|
|
32
|
+
updatedAt: new Date(),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
messages.push(`[merge] CI watch/fix loop failed — feature halted`);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Build a structured error message describing the CI fix loop outcome.
|
|
39
|
+
*/
|
|
40
|
+
export function buildCiExhaustedError(attempts, history, reason) {
|
|
41
|
+
const reasonStr = reason === 'timeout' ? 'CI watch timed out' : `all ${attempts} fix attempt(s) exhausted`;
|
|
42
|
+
const historyStr = history
|
|
43
|
+
.map((r) => ` - Attempt ${r.attempt}: ${r.outcome} (started ${r.startedAt})`)
|
|
44
|
+
.join('\n');
|
|
45
|
+
const detail = historyStr ? `\nAttempt history:\n${historyStr}` : '';
|
|
46
|
+
return new Error(`CI watch/fix loop failed — ${reasonStr}.${detail}\nReview CI logs and fix manually.`);
|
|
47
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CI Watch/Fix Loop
|
|
3
|
+
*
|
|
4
|
+
* After a push, watches CI status and attempts automatic fixes when CI fails.
|
|
5
|
+
* Respects configurable max attempts, timeout, and log size from settings.
|
|
6
|
+
*/
|
|
7
|
+
import type { IAgentExecutor } from '../../../../../../application/ports/output/agents/agent-executor.interface.js';
|
|
8
|
+
import type { IFeatureRepository } from '../../../../../../application/ports/output/repositories/feature-repository.interface.js';
|
|
9
|
+
import type { IGitPrService } from '../../../../../../application/ports/output/services/git-pr-service.interface.js';
|
|
10
|
+
import { CiStatus, type CiFixRecord } from '../../../../../../domain/generated/output.js';
|
|
11
|
+
import type { NodeLogger } from '../node-helpers.js';
|
|
12
|
+
import type { AgentExecutionOptions } from '../../../../../../application/ports/output/agents/agent-executor.interface.js';
|
|
13
|
+
export interface CiWatchFixDeps {
|
|
14
|
+
executor: IAgentExecutor;
|
|
15
|
+
gitPrService: IGitPrService;
|
|
16
|
+
featureRepository: Pick<IFeatureRepository, 'findById' | 'update'>;
|
|
17
|
+
}
|
|
18
|
+
export interface CiWatchFixParams {
|
|
19
|
+
cwd: string;
|
|
20
|
+
branch: string;
|
|
21
|
+
options: AgentExecutionOptions;
|
|
22
|
+
feature: Awaited<ReturnType<Pick<IFeatureRepository, 'findById'>['findById']>>;
|
|
23
|
+
prUrl: string | null;
|
|
24
|
+
prNumber: number | null;
|
|
25
|
+
existingAttempts: number;
|
|
26
|
+
messages: string[];
|
|
27
|
+
log: NodeLogger;
|
|
28
|
+
}
|
|
29
|
+
export type CiFixStatusValue = 'idle' | 'watching' | 'fixing' | 'success' | 'exhausted' | 'timeout';
|
|
30
|
+
export interface CiWatchFixResult {
|
|
31
|
+
ciStatus: CiStatus;
|
|
32
|
+
ciFixAttempts: number;
|
|
33
|
+
ciFixHistory: CiFixRecord[];
|
|
34
|
+
ciFixStatus: CiFixStatusValue;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Run the CI watch/fix loop. Watches for the initial CI result, then
|
|
38
|
+
* iteratively attempts fixes up to the configured maximum.
|
|
39
|
+
*
|
|
40
|
+
* Throws on timeout or exhausted attempts (after updating feature state).
|
|
41
|
+
*/
|
|
42
|
+
export declare function runCiWatchFixLoop(deps: CiWatchFixDeps, params: CiWatchFixParams): Promise<CiWatchFixResult>;
|
|
43
|
+
//# sourceMappingURL=ci-watch-fix-loop.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ci-watch-fix-loop.d.ts","sourceRoot":"","sources":["../../../../../../../../../../packages/core/src/infrastructure/services/agents/feature-agent/nodes/merge/ci-watch-fix-loop.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+DAA+D,CAAC;AACpG,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yEAAyE,CAAC;AAClH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iEAAiE,CAAC;AAKrG,OAAO,EAAE,QAAQ,EAAE,KAAK,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,+DAA+D,CAAC;AAK3G,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,cAAc,CAAC;IACzB,YAAY,EAAE,aAAa,CAAC;IAC5B,iBAAiB,EAAE,IAAI,CAAC,kBAAkB,EAAE,UAAU,GAAG,QAAQ,CAAC,CAAC;CACpE;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,qBAAqB,CAAC;IAC/B,OAAO,EAAE,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,kBAAkB,EAAE,UAAU,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAC/E,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,GAAG,EAAE,UAAU,CAAC;CACjB;AAED,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,UAAU,GAAG,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,CAAC;AAEpG,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,QAAQ,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,WAAW,EAAE,gBAAgB,CAAC;CAC/B;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,cAAc,EACpB,MAAM,EAAE,gBAAgB,GACvB,OAAO,CAAC,gBAAgB,CAAC,CAiH3B"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CI Watch/Fix Loop
|
|
3
|
+
*
|
|
4
|
+
* After a push, watches CI status and attempts automatic fixes when CI fails.
|
|
5
|
+
* Respects configurable max attempts, timeout, and log size from settings.
|
|
6
|
+
*/
|
|
7
|
+
import { GitPrError, GitPrErrorCode, } from '../../../../../../application/ports/output/services/git-pr-service.interface.js';
|
|
8
|
+
import { CiStatus } from '../../../../../../domain/generated/output.js';
|
|
9
|
+
import { retryExecute } from '../node-helpers.js';
|
|
10
|
+
import { buildCiWatchFixPrompt } from '../prompts/merge-prompts.js';
|
|
11
|
+
import { extractRunId, handleCiTerminalFailure, buildCiExhaustedError } from './ci-helpers.js';
|
|
12
|
+
import { getSettings } from '../../../../../services/settings.service.js';
|
|
13
|
+
/**
|
|
14
|
+
* Run the CI watch/fix loop. Watches for the initial CI result, then
|
|
15
|
+
* iteratively attempts fixes up to the configured maximum.
|
|
16
|
+
*
|
|
17
|
+
* Throws on timeout or exhausted attempts (after updating feature state).
|
|
18
|
+
*/
|
|
19
|
+
export async function runCiWatchFixLoop(deps, params) {
|
|
20
|
+
const { executor, gitPrService } = deps;
|
|
21
|
+
const { cwd, branch, options, feature, prUrl, prNumber, messages, log } = params;
|
|
22
|
+
const settings = getSettings();
|
|
23
|
+
const maxAttempts = settings.workflow?.ciMaxFixAttempts ?? 3;
|
|
24
|
+
const timeoutMs = settings.workflow?.ciWatchTimeoutMs ?? 600_000;
|
|
25
|
+
const logMaxChars = settings.workflow?.ciLogMaxChars ?? 50_000;
|
|
26
|
+
let ciFixAttempts = params.existingAttempts;
|
|
27
|
+
const ciFixHistory = [];
|
|
28
|
+
let ciFixStatus;
|
|
29
|
+
log.info(`Starting CI watch (maxAttempts=${maxAttempts}, timeout=${timeoutMs}ms)`);
|
|
30
|
+
// Check if any CI run exists for this branch
|
|
31
|
+
const initialCiStatus = await gitPrService.getCiStatus(cwd, branch);
|
|
32
|
+
if (!initialCiStatus.runUrl) {
|
|
33
|
+
log.info('No CI run detected after push — skipping CI watch');
|
|
34
|
+
return { ciStatus: CiStatus.Success, ciFixAttempts, ciFixHistory, ciFixStatus: 'idle' };
|
|
35
|
+
}
|
|
36
|
+
let runUrl = initialCiStatus.runUrl;
|
|
37
|
+
// Initial CI watch
|
|
38
|
+
let watchResult;
|
|
39
|
+
try {
|
|
40
|
+
watchResult = await gitPrService.watchCi(cwd, branch, timeoutMs);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
if (err instanceof GitPrError && err.code === GitPrErrorCode.CI_TIMEOUT) {
|
|
44
|
+
log.info('Initial CI watch timed out');
|
|
45
|
+
ciFixHistory.push({
|
|
46
|
+
attempt: ciFixAttempts + 1,
|
|
47
|
+
startedAt: new Date().toISOString(),
|
|
48
|
+
failureSummary: 'CI watch timed out',
|
|
49
|
+
outcome: 'timeout',
|
|
50
|
+
});
|
|
51
|
+
await handleCiTerminalFailure(feature, prUrl, prNumber, deps.featureRepository, messages);
|
|
52
|
+
throw buildCiExhaustedError(ciFixAttempts + 1, ciFixHistory, 'timeout');
|
|
53
|
+
}
|
|
54
|
+
throw err;
|
|
55
|
+
}
|
|
56
|
+
if (watchResult.status === 'success') {
|
|
57
|
+
log.info('CI passed on first watch');
|
|
58
|
+
return { ciStatus: CiStatus.Success, ciFixAttempts, ciFixHistory, ciFixStatus: 'success' };
|
|
59
|
+
}
|
|
60
|
+
// CI failed — enter fix loop
|
|
61
|
+
while (true) {
|
|
62
|
+
// Fail-fast: check attempt count BEFORE invoking executor (NFR-3)
|
|
63
|
+
if (ciFixAttempts >= maxAttempts) {
|
|
64
|
+
log.info(`CI fix loop exhausted after ${ciFixAttempts} attempt(s)`);
|
|
65
|
+
ciFixStatus = 'exhausted';
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
// Fetch failure logs
|
|
69
|
+
const runId = extractRunId(runUrl) ?? '';
|
|
70
|
+
const failureLogs = await gitPrService.getFailureLogs(runId, branch, logMaxChars);
|
|
71
|
+
const startedAt = new Date().toISOString();
|
|
72
|
+
log.info(`CI fix attempt ${ciFixAttempts + 1}/${maxAttempts} for run ${runId}`);
|
|
73
|
+
// Invoke fix executor
|
|
74
|
+
const fixPrompt = buildCiWatchFixPrompt(failureLogs, ciFixAttempts + 1, maxAttempts, branch);
|
|
75
|
+
await retryExecute(executor, fixPrompt, options, { logger: log });
|
|
76
|
+
ciFixAttempts++;
|
|
77
|
+
// Get updated run URL (new run triggered by push in fix)
|
|
78
|
+
const updatedCiStatus = await gitPrService.getCiStatus(cwd, branch);
|
|
79
|
+
if (updatedCiStatus.runUrl)
|
|
80
|
+
runUrl = updatedCiStatus.runUrl;
|
|
81
|
+
// Watch CI after fix
|
|
82
|
+
let fixWatchResult;
|
|
83
|
+
try {
|
|
84
|
+
fixWatchResult = await gitPrService.watchCi(cwd, branch, timeoutMs);
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
if (err instanceof GitPrError && err.code === GitPrErrorCode.CI_TIMEOUT) {
|
|
88
|
+
log.info(`CI watch timed out during fix attempt ${ciFixAttempts}`);
|
|
89
|
+
ciFixHistory.push({
|
|
90
|
+
attempt: ciFixAttempts,
|
|
91
|
+
startedAt,
|
|
92
|
+
failureSummary: failureLogs.slice(0, 500),
|
|
93
|
+
outcome: 'timeout',
|
|
94
|
+
});
|
|
95
|
+
ciFixStatus = 'timeout';
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
throw err;
|
|
99
|
+
}
|
|
100
|
+
const outcome = fixWatchResult.status === 'success' ? 'fixed' : 'failed';
|
|
101
|
+
ciFixHistory.push({
|
|
102
|
+
attempt: ciFixAttempts,
|
|
103
|
+
startedAt,
|
|
104
|
+
failureSummary: failureLogs.slice(0, 500),
|
|
105
|
+
outcome,
|
|
106
|
+
});
|
|
107
|
+
if (fixWatchResult.status === 'success') {
|
|
108
|
+
log.info(`CI passed after fix attempt ${ciFixAttempts}`);
|
|
109
|
+
ciFixStatus = 'success';
|
|
110
|
+
return { ciStatus: CiStatus.Success, ciFixAttempts, ciFixHistory, ciFixStatus };
|
|
111
|
+
}
|
|
112
|
+
log.info(`CI still failing after fix attempt ${ciFixAttempts}`);
|
|
113
|
+
messages.push(`[merge] CI fix attempt ${ciFixAttempts}/${maxAttempts} — still failing`);
|
|
114
|
+
}
|
|
115
|
+
// Handle terminal failure states
|
|
116
|
+
await handleCiTerminalFailure(feature, prUrl, prNumber, deps.featureRepository, messages);
|
|
117
|
+
throw buildCiExhaustedError(ciFixAttempts, ciFixHistory, ciFixStatus);
|
|
118
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merge-output-parser.d.ts","sourceRoot":"","sources":["../../../../../../../../../../packages/core/src/infrastructure/services/agents/feature-agent/nodes/merge/merge-output-parser.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI7D;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAI/D"}
|
|
@@ -6,11 +6,16 @@
|
|
|
6
6
|
* 2. Merge/squash (via agent executor, after approval gate)
|
|
7
7
|
*
|
|
8
8
|
* Uses interrupt() for the merge approval gate when allowMerge=false.
|
|
9
|
+
*
|
|
10
|
+
* Sub-modules:
|
|
11
|
+
* - ci-watch-fix-loop.ts: CI watch + automatic fix loop
|
|
12
|
+
* - ci-helpers.ts: shared CI utility functions
|
|
13
|
+
* - merge-output-parser.ts: structured data extraction from agent output
|
|
9
14
|
*/
|
|
10
|
-
import type { IAgentExecutor } from '
|
|
11
|
-
import type { FeatureAgentState } from '
|
|
12
|
-
import type { IFeatureRepository } from '
|
|
13
|
-
import type { DiffSummary } from '
|
|
15
|
+
import type { IAgentExecutor } from '../../../../../../application/ports/output/agents/agent-executor.interface.js';
|
|
16
|
+
import type { FeatureAgentState } from '../../state.js';
|
|
17
|
+
import type { IFeatureRepository } from '../../../../../../application/ports/output/repositories/feature-repository.interface.js';
|
|
18
|
+
import type { DiffSummary, IGitPrService } from '../../../../../../application/ports/output/services/git-pr-service.interface.js';
|
|
14
19
|
export interface MergeNodeDeps {
|
|
15
20
|
executor: IAgentExecutor;
|
|
16
21
|
getDiffSummary: (cwd: string, baseBranch: string) => Promise<DiffSummary>;
|
|
@@ -22,6 +27,7 @@ export interface MergeNodeDeps {
|
|
|
22
27
|
* Returns true if baseBranch contains all commits from featureBranch.
|
|
23
28
|
*/
|
|
24
29
|
verifyMerge: (cwd: string, featureBranch: string, baseBranch: string) => Promise<boolean>;
|
|
30
|
+
gitPrService: IGitPrService;
|
|
25
31
|
}
|
|
26
32
|
/**
|
|
27
33
|
* Factory that creates the merge node function.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merge.node.d.ts","sourceRoot":"","sources":["../../../../../../../../../../packages/core/src/infrastructure/services/agents/feature-agent/nodes/merge/merge.node.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+DAA+D,CAAC;AACpG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yEAAyE,CAAC;AAClH,OAAO,KAAK,EACV,WAAW,EACX,aAAa,EACd,MAAM,iEAAiE,CAAC;AAsBzE,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,cAAc,CAAC;IACzB,cAAc,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;IAC1E,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7C,gBAAgB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACnD,iBAAiB,EAAE,IAAI,CAAC,kBAAkB,EAAE,UAAU,GAAG,QAAQ,CAAC,CAAC;IACnE;;;OAGG;IACH,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1F,YAAY,EAAE,aAAa,CAAC;CAC7B;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,aAAa,IAGnC,OAAO,iBAAiB,KAAG,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,CA6M7E"}
|
|
@@ -6,15 +6,21 @@
|
|
|
6
6
|
* 2. Merge/squash (via agent executor, after approval gate)
|
|
7
7
|
*
|
|
8
8
|
* Uses interrupt() for the merge approval gate when allowMerge=false.
|
|
9
|
+
*
|
|
10
|
+
* Sub-modules:
|
|
11
|
+
* - ci-watch-fix-loop.ts: CI watch + automatic fix loop
|
|
12
|
+
* - ci-helpers.ts: shared CI utility functions
|
|
13
|
+
* - merge-output-parser.ts: structured data extraction from agent output
|
|
9
14
|
*/
|
|
10
15
|
import { interrupt, isGraphBubbleUp } from '@langchain/langgraph';
|
|
11
|
-
import { SdlcLifecycle, PrStatus } from '
|
|
12
|
-
import { createNodeLogger, shouldInterrupt, retryExecute, buildExecutorOptions, getCompletedPhases, clearCompletedPhase, markPhaseComplete, } from '
|
|
13
|
-
import { reportNodeStart } from '
|
|
14
|
-
import { recordPhaseStart, recordPhaseEnd, recordApprovalWaitStart, } from '
|
|
15
|
-
import { updateNodeLifecycle } from '
|
|
16
|
-
import { buildCommitPushPrPrompt, buildMergeSquashPrompt } from '
|
|
16
|
+
import { SdlcLifecycle, PrStatus } from '../../../../../../domain/generated/output.js';
|
|
17
|
+
import { createNodeLogger, shouldInterrupt, retryExecute, buildExecutorOptions, getCompletedPhases, clearCompletedPhase, markPhaseComplete, } from '../node-helpers.js';
|
|
18
|
+
import { reportNodeStart } from '../../heartbeat.js';
|
|
19
|
+
import { recordPhaseStart, recordPhaseEnd, recordApprovalWaitStart, } from '../../phase-timing-context.js';
|
|
20
|
+
import { updateNodeLifecycle } from '../../lifecycle-context.js';
|
|
21
|
+
import { buildCommitPushPrPrompt, buildMergeSquashPrompt } from '../prompts/merge-prompts.js';
|
|
17
22
|
import { parseCommitHash, parsePrUrl } from './merge-output-parser.js';
|
|
23
|
+
import { runCiWatchFixLoop } from './ci-watch-fix-loop.js';
|
|
18
24
|
/**
|
|
19
25
|
* Factory that creates the merge node function.
|
|
20
26
|
*
|
|
@@ -28,9 +34,6 @@ export function createMergeNode(deps) {
|
|
|
28
34
|
reportNodeStart('merge');
|
|
29
35
|
await updateNodeLifecycle('merge');
|
|
30
36
|
// --- Rejection detection on resume (same pattern as executeNode) ---
|
|
31
|
-
// On resume after interrupt, check if merge was already completed.
|
|
32
|
-
// Rejection: clear phase, schedule re-execution via conditional edge.
|
|
33
|
-
// Approval: fall through to post-interrupt work (lifecycle update, optional merge/squash).
|
|
34
37
|
const completedPhases = getCompletedPhases(state.specDir);
|
|
35
38
|
const isResumeAfterInterrupt = completedPhases.includes('merge') && shouldInterrupt('merge', state.approvalGates);
|
|
36
39
|
if (isResumeAfterInterrupt && state._approvalAction === 'rejected') {
|
|
@@ -47,12 +50,10 @@ export function createMergeNode(deps) {
|
|
|
47
50
|
}
|
|
48
51
|
const messages = [];
|
|
49
52
|
const startTime = Date.now();
|
|
50
|
-
// Record merge phase timing
|
|
51
53
|
const mergeTimingId = await recordPhaseStart('merge');
|
|
52
54
|
try {
|
|
53
55
|
const { executor } = deps;
|
|
54
56
|
const cwd = state.worktreePath;
|
|
55
|
-
// Resolve branch name from feature
|
|
56
57
|
const feature = await deps.featureRepository.findById(state.featureId);
|
|
57
58
|
const branch = feature?.branch ?? `feat/${state.featureId}`;
|
|
58
59
|
const baseBranch = await deps.getDefaultBranch(cwd);
|
|
@@ -60,7 +61,10 @@ export function createMergeNode(deps) {
|
|
|
60
61
|
let commitHash = state.commitHash;
|
|
61
62
|
let prUrl = state.prUrl;
|
|
62
63
|
let prNumber = state.prNumber;
|
|
63
|
-
|
|
64
|
+
let ciStatus = state.ciStatus;
|
|
65
|
+
let ciFixAttempts = state.ciFixAttempts ?? 0;
|
|
66
|
+
let ciFixHistory = state.ciFixHistory ?? [];
|
|
67
|
+
let ciFixStatus = state.ciFixStatus ?? 'idle';
|
|
64
68
|
// --- Check for git remote (needed by both agent calls) ---
|
|
65
69
|
const remoteAvailable = await deps.hasRemote(cwd);
|
|
66
70
|
// --- Agent Call 1: Commit + Push + PR (skip on approval resume) ---
|
|
@@ -68,14 +72,12 @@ export function createMergeNode(deps) {
|
|
|
68
72
|
if (!remoteAvailable) {
|
|
69
73
|
log.info('No git remote configured — skipping push and PR, will merge locally');
|
|
70
74
|
}
|
|
71
|
-
// Override push/openPr when no remote is available
|
|
72
75
|
const effectiveState = remoteAvailable ? state : { ...state, push: false, openPr: false };
|
|
73
76
|
log.info('Agent call 1: commit + push + PR');
|
|
74
77
|
const commitPushPrPrompt = buildCommitPushPrPrompt(effectiveState, branch, baseBranch);
|
|
75
78
|
const commitResult = await retryExecute(executor, commitPushPrPrompt, options, {
|
|
76
79
|
logger: log,
|
|
77
80
|
});
|
|
78
|
-
// Parse structured data from agent output
|
|
79
81
|
commitHash = parseCommitHash(commitResult.result) ?? state.commitHash;
|
|
80
82
|
messages.push(`[merge] Agent completed commit/push/PR operations`);
|
|
81
83
|
if (effectiveState.openPr) {
|
|
@@ -86,6 +88,28 @@ export function createMergeNode(deps) {
|
|
|
86
88
|
messages.push(`[merge] PR created: ${prUrl}`);
|
|
87
89
|
}
|
|
88
90
|
}
|
|
91
|
+
// --- CI watch/fix loop (when push or openPr is enabled) ---
|
|
92
|
+
if (effectiveState.push || effectiveState.openPr) {
|
|
93
|
+
const ciResult = await runCiWatchFixLoop({
|
|
94
|
+
executor,
|
|
95
|
+
gitPrService: deps.gitPrService,
|
|
96
|
+
featureRepository: deps.featureRepository,
|
|
97
|
+
}, {
|
|
98
|
+
cwd,
|
|
99
|
+
branch,
|
|
100
|
+
options,
|
|
101
|
+
feature,
|
|
102
|
+
prUrl,
|
|
103
|
+
prNumber,
|
|
104
|
+
existingAttempts: ciFixAttempts,
|
|
105
|
+
messages,
|
|
106
|
+
log,
|
|
107
|
+
});
|
|
108
|
+
ciStatus = ciResult.ciStatus;
|
|
109
|
+
ciFixAttempts = ciResult.ciFixAttempts;
|
|
110
|
+
ciFixHistory = ciResult.ciFixHistory;
|
|
111
|
+
ciFixStatus = ciResult.ciFixStatus;
|
|
112
|
+
}
|
|
89
113
|
// --- Merge approval gate ---
|
|
90
114
|
if (shouldInterrupt('merge', state.approvalGates)) {
|
|
91
115
|
log.info('Interrupting for merge approval');
|
|
@@ -99,6 +123,7 @@ export function createMergeNode(deps) {
|
|
|
99
123
|
diffSummary,
|
|
100
124
|
prUrl,
|
|
101
125
|
prNumber,
|
|
126
|
+
ciStatus,
|
|
102
127
|
});
|
|
103
128
|
}
|
|
104
129
|
}
|
|
@@ -141,6 +166,8 @@ export function createMergeNode(deps) {
|
|
|
141
166
|
status: merged ? PrStatus.Merged : PrStatus.Open,
|
|
142
167
|
...(commitHash ? { commitHash } : {}),
|
|
143
168
|
...(ciStatus ? { ciStatus: ciStatus } : {}),
|
|
169
|
+
...(ciFixAttempts > 0 ? { ciFixAttempts } : {}),
|
|
170
|
+
...(ciFixHistory.length > 0 ? { ciFixHistory } : {}),
|
|
144
171
|
},
|
|
145
172
|
}
|
|
146
173
|
: {}),
|
|
@@ -159,21 +186,21 @@ export function createMergeNode(deps) {
|
|
|
159
186
|
prUrl,
|
|
160
187
|
prNumber,
|
|
161
188
|
ciStatus,
|
|
189
|
+
ciFixAttempts,
|
|
190
|
+
ciFixHistory,
|
|
191
|
+
ciFixStatus,
|
|
162
192
|
_approvalAction: null,
|
|
163
193
|
_rejectionFeedback: null,
|
|
164
194
|
_needsReexecution: false,
|
|
165
195
|
};
|
|
166
196
|
}
|
|
167
197
|
catch (err) {
|
|
168
|
-
// Re-throw LangGraph control-flow exceptions (interrupt, etc.)
|
|
169
198
|
if (isGraphBubbleUp(err))
|
|
170
199
|
throw err;
|
|
171
200
|
const message = err instanceof Error ? err.message : String(err);
|
|
172
201
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
173
202
|
log.error(`Merge failed: ${message} (${elapsed}s)`);
|
|
174
|
-
// Record phase end even on failure so timing shows duration, not "running"
|
|
175
203
|
await recordPhaseEnd(mergeTimingId, Date.now() - startTime);
|
|
176
|
-
// Re-throw so LangGraph does NOT checkpoint this node as completed.
|
|
177
204
|
throw err;
|
|
178
205
|
}
|
|
179
206
|
};
|
|
@@ -13,6 +13,18 @@ import type { FeatureAgentState } from '../../state.js';
|
|
|
13
13
|
* on state.push and state.openPr flags.
|
|
14
14
|
*/
|
|
15
15
|
export declare function buildCommitPushPrPrompt(state: FeatureAgentState, branch: string, baseBranch: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Build a prompt for the CI watch/fix agent call.
|
|
18
|
+
*
|
|
19
|
+
* Instructs the executor to diagnose CI failure logs, apply a targeted fix,
|
|
20
|
+
* commit with the prescribed conventional message format, and push to the branch.
|
|
21
|
+
*
|
|
22
|
+
* @param failureLogs - Truncated CI failure log output
|
|
23
|
+
* @param attemptNumber - 1-based current attempt number
|
|
24
|
+
* @param maxAttempts - Maximum allowed attempts
|
|
25
|
+
* @param branch - Feature branch name to push to after fixing
|
|
26
|
+
*/
|
|
27
|
+
export declare function buildCiWatchFixPrompt(failureLogs: string, attemptNumber: number, maxAttempts: number, branch: string): string;
|
|
16
28
|
/**
|
|
17
29
|
* Build a prompt for the merge/squash agent call.
|
|
18
30
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"merge-prompts.d.ts","sourceRoot":"","sources":["../../../../../../../../../../packages/core/src/infrastructure/services/agents/feature-agent/nodes/prompts/merge-prompts.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAqCxD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,iBAAiB,EACxB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,GACjB,MAAM,CAyDR;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,iBAAiB,EACxB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,SAAS,UAAQ,GAChB,MAAM,CAkER"}
|
|
1
|
+
{"version":3,"file":"merge-prompts.d.ts","sourceRoot":"","sources":["../../../../../../../../../../packages/core/src/infrastructure/services/agents/feature-agent/nodes/prompts/merge-prompts.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAqCxD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,iBAAiB,EACxB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,GACjB,MAAM,CAyDR;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,GACb,MAAM,CA0BR;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,iBAAiB,EACxB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,SAAS,UAAQ,GAChB,MAAM,CAkER"}
|
|
@@ -97,6 +97,44 @@ ${steps.join('\n')}
|
|
|
97
97
|
- Do NOT amend existing commits
|
|
98
98
|
- If there are no changes to commit, skip the commit step and report that no changes were found`;
|
|
99
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Build a prompt for the CI watch/fix agent call.
|
|
102
|
+
*
|
|
103
|
+
* Instructs the executor to diagnose CI failure logs, apply a targeted fix,
|
|
104
|
+
* commit with the prescribed conventional message format, and push to the branch.
|
|
105
|
+
*
|
|
106
|
+
* @param failureLogs - Truncated CI failure log output
|
|
107
|
+
* @param attemptNumber - 1-based current attempt number
|
|
108
|
+
* @param maxAttempts - Maximum allowed attempts
|
|
109
|
+
* @param branch - Feature branch name to push to after fixing
|
|
110
|
+
*/
|
|
111
|
+
export function buildCiWatchFixPrompt(failureLogs, attemptNumber, maxAttempts, branch) {
|
|
112
|
+
return `You are fixing a CI failure on branch \`${branch}\` (attempt ${attemptNumber}/${maxAttempts}).
|
|
113
|
+
|
|
114
|
+
## CI Failure Logs
|
|
115
|
+
|
|
116
|
+
The following logs were retrieved from the failed GitHub Actions run:
|
|
117
|
+
|
|
118
|
+
\`\`\`
|
|
119
|
+
${failureLogs}
|
|
120
|
+
\`\`\`
|
|
121
|
+
|
|
122
|
+
## Instructions
|
|
123
|
+
|
|
124
|
+
1. Analyze the CI failure logs above to diagnose the root cause
|
|
125
|
+
2. Apply a targeted fix to resolve the failure — change only what is necessary
|
|
126
|
+
3. Stage all changes: \`git add -A\`
|
|
127
|
+
4. Commit with this exact conventional commit message format:
|
|
128
|
+
\`fix(ci): attempt ${attemptNumber}/${maxAttempts} — <short description of what you fixed>\`
|
|
129
|
+
5. Push the fix to the branch: \`git push origin ${branch}\`
|
|
130
|
+
|
|
131
|
+
## Constraints
|
|
132
|
+
|
|
133
|
+
- Fix ONLY the issue(s) causing the CI failure — do not refactor unrelated code
|
|
134
|
+
- The commit message MUST start with \`fix(ci): attempt ${attemptNumber}/${maxAttempts} — \`
|
|
135
|
+
- Do NOT create a new branch — push directly to \`${branch}\`
|
|
136
|
+
- If the failure is unclear, make your best diagnosis and explain your reasoning in the commit message`;
|
|
137
|
+
}
|
|
100
138
|
/**
|
|
101
139
|
* Build a prompt for the merge/squash agent call.
|
|
102
140
|
*
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ApprovalGates } from '../../../../domain/generated/output.js';
|
|
1
|
+
import type { ApprovalGates, CiFixRecord } from '../../../../domain/generated/output.js';
|
|
2
2
|
/**
|
|
3
3
|
* State annotation for the feature-agent graph.
|
|
4
4
|
*
|
|
@@ -47,6 +47,9 @@ export declare const FeatureAgentAnnotation: import("@langchain/langgraph").Anno
|
|
|
47
47
|
ciStatus: import("@langchain/langgraph").BinaryOperatorAggregate<string | null, string | null>;
|
|
48
48
|
push: import("@langchain/langgraph").BinaryOperatorAggregate<boolean, boolean>;
|
|
49
49
|
openPr: import("@langchain/langgraph").BinaryOperatorAggregate<boolean, boolean>;
|
|
50
|
+
ciFixAttempts: import("@langchain/langgraph").BinaryOperatorAggregate<number, number>;
|
|
51
|
+
ciFixHistory: import("@langchain/langgraph").BinaryOperatorAggregate<CiFixRecord[], CiFixRecord[]>;
|
|
52
|
+
ciFixStatus: import("@langchain/langgraph").BinaryOperatorAggregate<"success" | "timeout" | "idle" | "watching" | "fixing" | "exhausted", "success" | "timeout" | "idle" | "watching" | "fixing" | "exhausted">;
|
|
50
53
|
}>;
|
|
51
54
|
export type FeatureAgentState = typeof FeatureAgentAnnotation.State;
|
|
52
55
|
//# sourceMappingURL=state.d.ts.map
|