@shepai/cli 1.130.0 → 1.130.1-pr419.86475da
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/WorkflowConfig.yaml +5 -0
- package/dist/packages/core/src/domain/generated/output.d.ts +4 -0
- package/dist/packages/core/src/domain/generated/output.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/agents/common/agent-validator.service.js +1 -1
- package/dist/packages/core/src/infrastructure/services/agents/common/executors/cursor-executor.service.d.ts +5 -5
- package/dist/packages/core/src/infrastructure/services/agents/common/executors/cursor-executor.service.js +8 -8
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/fast-feature-agent-graph.d.ts +5 -0
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/fast-feature-agent-graph.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/feature-agent-graph.d.ts +17 -0
- 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/nodes/evidence-output-parser.d.ts +52 -1
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/evidence-output-parser.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/evidence-output-parser.js +121 -1
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/evidence.node.d.ts +6 -2
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/evidence.node.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/evidence.node.js +139 -75
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/implement.node.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/implement.node.js +17 -63
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/prompts/evidence-prompts.d.ts +12 -0
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/prompts/evidence-prompts.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/prompts/evidence-prompts.js +50 -0
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/state.d.ts +1 -0
- 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 +4 -0
- package/dist/packages/core/src/infrastructure/services/deployment/deployment.service.d.ts +21 -1
- package/dist/packages/core/src/infrastructure/services/deployment/deployment.service.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/deployment/deployment.service.js +152 -12
- package/dist/packages/core/src/infrastructure/services/deployment/detect-dev-script.d.ts +1 -0
- package/dist/packages/core/src/infrastructure/services/deployment/detect-dev-script.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/deployment/detect-dev-script.js +3 -2
- package/dist/src/presentation/web/app/actions/check-agent-auth.js +1 -1
- package/dist/src/presentation/web/dev-server.js +7 -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/fallback-build-manifest.json +2 -2
- package/web/.next/prerender-manifest.json +3 -3
- package/web/.next/required-server-files.js +3 -3
- package/web/.next/required-server-files.json +3 -3
- package/web/.next/server/app/(dashboard)/@drawer/adopt/page/server-reference-manifest.json +24 -24
- package/web/.next/server/app/(dashboard)/@drawer/adopt/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/adopt/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/create/page/server-reference-manifest.json +24 -24
- package/web/.next/server/app/(dashboard)/@drawer/create/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/create/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page/server-reference-manifest.json +30 -30
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page/server-reference-manifest.json +30 -30
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page/server-reference-manifest.json +22 -22
- package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/create/page/server-reference-manifest.json +24 -24
- package/web/.next/server/app/(dashboard)/create/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/create/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page/server-reference-manifest.json +30 -30
- package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/feature/[featureId]/page/server-reference-manifest.json +30 -30
- package/web/.next/server/app/(dashboard)/feature/[featureId]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/feature/[featureId]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/page/server-reference-manifest.json +22 -22
- package/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page/server-reference-manifest.json +22 -22
- package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page_client-reference-manifest.js +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 +3 -3
- package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/settings/page/server-reference-manifest.json +8 -8
- package/web/.next/server/app/settings/page.js.nft.json +1 -1
- package/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/skills/page/server-reference-manifest.json +8 -8
- package/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/tools/page/server-reference-manifest.json +8 -8
- package/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/version/page/server-reference-manifest.json +3 -3
- package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
- package/web/.next/server/chunks/[root-of-the-server]__a402b567._.js +1 -1
- package/web/.next/server/chunks/[root-of-the-server]__cd67a84c._.js.map +1 -1
- package/web/.next/server/chunks/[root-of-the-server]__d1dc8fcd._.js.map +1 -1
- package/web/.next/server/chunks/ssr/744ca_web__next-internal_server_app_(dashboard)_@drawer_adopt_page_actions_4e741df2.js +1 -1
- package/web/.next/server/chunks/ssr/744ca_web__next-internal_server_app_(dashboard)_@drawer_adopt_page_actions_4e741df2.js.map +1 -1
- package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js +1 -1
- package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js.map +1 -1
- package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_026fdffa.js +1 -1
- package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_026fdffa.js.map +1 -1
- package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_8b91e197.js +1 -1
- package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_8b91e197.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__357d99f9._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__43f51aa6._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__43f51aa6._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__5142820f._.js +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__5142820f._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__57018345._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__6bb00d90._.js +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__6bb00d90._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__755cfe02._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__755cfe02._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__7894f27f._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__7894f27f._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__815546bd._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__815546bd._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__84f72684._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__99ab2519._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__99ab2519._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__a628fb91._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__a628fb91._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__bfba58e6._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__bfba58e6._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__c3ab8422._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__c3ab8422._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_02ec1aea._.js +1 -1
- package/web/.next/server/chunks/ssr/_02ec1aea._.js.map +1 -1
- package/web/.next/server/chunks/ssr/{_c6edd1ef._.js → _59831c3c._.js} +2 -2
- package/web/.next/server/chunks/ssr/{_c6edd1ef._.js.map → _59831c3c._.js.map} +1 -1
- package/web/.next/server/chunks/ssr/_7dca1882._.js +1 -1
- package/web/.next/server/chunks/ssr/_7dca1882._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_a9f57758._.js +1 -1
- package/web/.next/server/chunks/ssr/{_2081d51d._.js → _cf09a53c._.js} +2 -2
- package/web/.next/server/chunks/ssr/{_2081d51d._.js.map → _cf09a53c._.js.map} +1 -1
- package/web/.next/server/chunks/ssr/_d4b20e29._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_d8575088._.js +1 -1
- package/web/.next/server/chunks/ssr/_d8575088._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_f39a1adb._.js +1 -1
- package/web/.next/server/chunks/ssr/_f39a1adb._.js.map +1 -1
- package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js +1 -1
- package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_8e7fe0d1._.js +2 -2
- package/web/.next/server/chunks/ssr/src_presentation_web_8e7fe0d1._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_ca051134.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_ca051134.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1b176e3c.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1b176e3c.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_bd9f0dda.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_bd9f0dda.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_b9023017._.js +2 -2
- package/web/.next/server/chunks/ssr/src_presentation_web_b9023017._.js.map +1 -1
- 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_components_features_control-center_7ac3562e._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js.map +1 -1
- package/web/.next/server/chunks/ssr/{src_presentation_web_de41598a._.js → src_presentation_web_e164bc5b._.js} +2 -2
- package/web/.next/server/chunks/ssr/{src_presentation_web_de41598a._.js.map → src_presentation_web_e164bc5b._.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 +38 -38
- package/web/.next/static/chunks/{3b5d54efc5f21506.js → 031c5ad2b3cc0fcf.js} +1 -1
- package/web/.next/static/chunks/{f8ce00057329618b.js → 1be2438b5f127e76.js} +1 -1
- package/web/.next/static/chunks/{22d08ce7fbc67732.js → 255bf7de35259808.js} +1 -1
- package/web/.next/static/chunks/{090b4b644f4526bf.js → 512315907ba20e64.js} +1 -1
- package/web/.next/static/chunks/{339e72cfa282c94b.js → 67dce937a03f61e0.js} +1 -1
- package/web/.next/static/chunks/{724dfca1907b26d7.js → 8e5d3752c2c82dbb.js} +1 -1
- package/web/.next/static/chunks/8ebc4fcc77449017.js +1 -0
- package/web/.next/static/chunks/{bdf477379453ead2.js → a1d525f20fc5274a.js} +2 -2
- package/web/.next/static/chunks/{8d936038e7d013d2.js → ab2c3c9ca5dd40a8.js} +1 -1
- package/web/.next/static/chunks/{9387d6dde6140968.js → cf791b69763664cf.js} +2 -2
- package/web/.next/static/chunks/{747b3443a866e39f.js → ec9886a3509ecef2.js} +1 -1
- package/web/.next/static/chunks/{90a32a641dd33f9a.js → ffb939826e7c743f.js} +1 -1
- package/web/.next/static/chunks/bcdcdce9139bfd25.js +0 -1
- /package/web/.next/static/{FkranKu-K5LY21_CdI_Jp → 0np7GuitLHRGDWR1YFoLi}/_buildManifest.js +0 -0
- /package/web/.next/static/{FkranKu-K5LY21_CdI_Jp → 0np7GuitLHRGDWR1YFoLi}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{FkranKu-K5LY21_CdI_Jp → 0np7GuitLHRGDWR1YFoLi}/_ssgManifest.js +0 -0
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
* Returns empty array gracefully on any parsing failure.
|
|
7
7
|
*
|
|
8
8
|
* Also provides validation to ensure UI-related evidence includes app-level
|
|
9
|
-
* proof (not just Storybook screenshots)
|
|
9
|
+
* proof (not just Storybook screenshots), evidence completeness by task type,
|
|
10
|
+
* and file existence verification.
|
|
10
11
|
*/
|
|
12
|
+
import { stat } from 'node:fs/promises';
|
|
11
13
|
import { EvidenceType } from '../../../../../domain/generated/output.js';
|
|
12
14
|
// Matches a fenced JSON code block: ```json ... ```
|
|
13
15
|
const JSON_BLOCK_RE = /```json\s*\n([\s\S]*?)\n\s*```/;
|
|
@@ -117,3 +119,121 @@ export function validateUiEvidenceHasAppProof(evidence) {
|
|
|
117
119
|
warnings,
|
|
118
120
|
};
|
|
119
121
|
}
|
|
122
|
+
// Keyword patterns for task type inference (compiled once at module level)
|
|
123
|
+
const UI_KEYWORDS = /\b(component|ui|page|style|layout|markup|visual)\b/i;
|
|
124
|
+
const TEST_KEYWORDS = /\b(tests?|specs?|unit tests?|integration tests?)\b/i;
|
|
125
|
+
const CLI_KEYWORDS = /\b(cli|command|terminal|shell)\b/i;
|
|
126
|
+
/**
|
|
127
|
+
* Infer task types from a task's description, acceptance criteria, and tdd field.
|
|
128
|
+
* Returns an array of inferred types. Tasks can have multiple types.
|
|
129
|
+
* Tasks without clear signals return an empty array (no completeness requirement).
|
|
130
|
+
*/
|
|
131
|
+
export function inferTaskTypes(task) {
|
|
132
|
+
const types = new Set();
|
|
133
|
+
const textSources = [task.description, ...task.acceptanceCriteria];
|
|
134
|
+
const combined = textSources.join(' ');
|
|
135
|
+
if (UI_KEYWORDS.test(combined)) {
|
|
136
|
+
types.add('ui');
|
|
137
|
+
}
|
|
138
|
+
if (TEST_KEYWORDS.test(combined) || task.tdd !== null) {
|
|
139
|
+
types.add('test');
|
|
140
|
+
}
|
|
141
|
+
if (CLI_KEYWORDS.test(combined)) {
|
|
142
|
+
types.add('cli');
|
|
143
|
+
}
|
|
144
|
+
return [...types];
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Validate evidence completeness against task requirements.
|
|
148
|
+
* Checks three dimensions:
|
|
149
|
+
* 1. UI tasks have app-level screenshots (not just Storybook)
|
|
150
|
+
* 2. Test tasks have TestOutput evidence
|
|
151
|
+
* 3. CLI tasks have TerminalRecording evidence
|
|
152
|
+
*
|
|
153
|
+
* Pure function — no side effects, no I/O.
|
|
154
|
+
*/
|
|
155
|
+
export function validateEvidenceCompleteness(evidence, tasks) {
|
|
156
|
+
const errors = [];
|
|
157
|
+
for (const task of tasks) {
|
|
158
|
+
const types = inferTaskTypes(task);
|
|
159
|
+
if (types.includes('ui')) {
|
|
160
|
+
// Check for app-level screenshots (not just Storybook)
|
|
161
|
+
const screenshots = evidence.filter(isScreenshot);
|
|
162
|
+
const appScreenshots = screenshots.filter(isAppEvidence);
|
|
163
|
+
if (appScreenshots.length === 0) {
|
|
164
|
+
errors.push({
|
|
165
|
+
type: 'ui',
|
|
166
|
+
taskId: task.id,
|
|
167
|
+
taskTitle: task.title,
|
|
168
|
+
message: `Missing app-level screenshot for ${task.id} (UI task '${task.title}'). Storybook-only screenshots are insufficient.`,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (types.includes('test')) {
|
|
173
|
+
const hasTestOutput = evidence.some((e) => e.type === EvidenceType.TestOutput);
|
|
174
|
+
if (!hasTestOutput) {
|
|
175
|
+
errors.push({
|
|
176
|
+
type: 'completeness',
|
|
177
|
+
taskId: task.id,
|
|
178
|
+
taskTitle: task.title,
|
|
179
|
+
message: `No TestOutput evidence for ${task.id} (test task '${task.title}'). Test results are required.`,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (types.includes('cli')) {
|
|
184
|
+
const hasTerminalRecording = evidence.some((e) => e.type === EvidenceType.TerminalRecording);
|
|
185
|
+
if (!hasTerminalRecording) {
|
|
186
|
+
errors.push({
|
|
187
|
+
type: 'completeness',
|
|
188
|
+
taskId: task.id,
|
|
189
|
+
taskTitle: task.title,
|
|
190
|
+
message: `No TerminalRecording evidence for ${task.id} (CLI task '${task.title}'). Terminal output is required.`,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return { valid: errors.length === 0, errors };
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Verify that each Evidence record's file exists on disk with non-zero size.
|
|
199
|
+
* Returns an array of error messages for missing or empty files.
|
|
200
|
+
* Never throws — all errors are caught and returned as strings (NFR-10).
|
|
201
|
+
*/
|
|
202
|
+
export async function validateFileExistence(evidence) {
|
|
203
|
+
if (evidence.length === 0)
|
|
204
|
+
return [];
|
|
205
|
+
const results = await Promise.all(evidence.map(async (e) => {
|
|
206
|
+
try {
|
|
207
|
+
const stats = await stat(e.relativePath);
|
|
208
|
+
if (stats.size === 0) {
|
|
209
|
+
return `Evidence file has zero size: ${e.relativePath} (${e.description})`;
|
|
210
|
+
}
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
const code = err.code;
|
|
215
|
+
if (code === 'ENOENT') {
|
|
216
|
+
return `Evidence file not found: ${e.relativePath} (${e.description})`;
|
|
217
|
+
}
|
|
218
|
+
if (code === 'EACCES') {
|
|
219
|
+
return `Evidence file not accessible (permission denied): ${e.relativePath} (${e.description})`;
|
|
220
|
+
}
|
|
221
|
+
return `Evidence file error: ${e.relativePath} (${e.description}) — ${err instanceof Error ? err.message : String(err)}`;
|
|
222
|
+
}
|
|
223
|
+
}));
|
|
224
|
+
return results.filter((r) => r !== null);
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Full validation pipeline: runs completeness checks and file existence checks,
|
|
228
|
+
* returns a unified ValidationResult with all detected issues.
|
|
229
|
+
*/
|
|
230
|
+
export async function validateEvidence(evidence, tasks) {
|
|
231
|
+
const completenessResult = validateEvidenceCompleteness(evidence, tasks);
|
|
232
|
+
const fileErrors = await validateFileExistence(evidence);
|
|
233
|
+
const fileValidationErrors = fileErrors.map((msg) => ({
|
|
234
|
+
type: 'fileExistence',
|
|
235
|
+
message: msg,
|
|
236
|
+
}));
|
|
237
|
+
const allErrors = [...completenessResult.errors, ...fileValidationErrors];
|
|
238
|
+
return { valid: allErrors.length === 0, errors: allErrors };
|
|
239
|
+
}
|
package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/evidence.node.d.ts
CHANGED
|
@@ -13,8 +13,12 @@
|
|
|
13
13
|
* other nodes (createNodeLogger, buildExecutorOptions, retryExecute,
|
|
14
14
|
* getCompletedPhases, markPhaseComplete).
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
16
|
+
* Includes a validation loop that inspects Evidence[] for quality and
|
|
17
|
+
* completeness after each collection attempt. If validation fails, the
|
|
18
|
+
* agent is retried with structured feedback about what's missing. After
|
|
19
|
+
* exhausting retries, the system proceeds with partial evidence (FR-8
|
|
20
|
+
* graceful degradation). Each attempt is recorded as a separate phase
|
|
21
|
+
* activity (evidence:attempt-N) for timing visibility.
|
|
18
22
|
*/
|
|
19
23
|
import type { IAgentExecutor } from '../../../../../application/ports/output/agents/agent-executor.interface.js';
|
|
20
24
|
import type { FeatureAgentState } from '../state.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"evidence.node.d.ts","sourceRoot":"","sources":["../../../../../../../../../packages/core/src/infrastructure/services/agents/feature-agent/nodes/evidence.node.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"evidence.node.d.ts","sourceRoot":"","sources":["../../../../../../../../../packages/core/src/infrastructure/services/agents/feature-agent/nodes/evidence.node.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+DAA+D,CAAC;AAEpG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAoDrD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,cAAc,IAG3C,OAAO,iBAAiB,KAAG,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAmJ7E"}
|
package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/evidence.node.js
CHANGED
|
@@ -13,17 +13,51 @@
|
|
|
13
13
|
* other nodes (createNodeLogger, buildExecutorOptions, retryExecute,
|
|
14
14
|
* getCompletedPhases, markPhaseComplete).
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
16
|
+
* Includes a validation loop that inspects Evidence[] for quality and
|
|
17
|
+
* completeness after each collection attempt. If validation fails, the
|
|
18
|
+
* agent is retried with structured feedback about what's missing. After
|
|
19
|
+
* exhausting retries, the system proceeds with partial evidence (FR-8
|
|
20
|
+
* graceful degradation). Each attempt is recorded as a separate phase
|
|
21
|
+
* activity (evidence:attempt-N) for timing visibility.
|
|
18
22
|
*/
|
|
23
|
+
import yaml from 'js-yaml';
|
|
19
24
|
import { isGraphBubbleUp } from '@langchain/langgraph';
|
|
20
|
-
import { createNodeLogger, getCompletedPhases, markPhaseComplete, retryExecute, buildExecutorOptions, } from './node-helpers.js';
|
|
25
|
+
import { createNodeLogger, getCompletedPhases, markPhaseComplete, readSpecFile, retryExecute, buildExecutorOptions, } from './node-helpers.js';
|
|
21
26
|
import { reportNodeStart } from '../heartbeat.js';
|
|
22
27
|
import { recordPhaseStart, recordPhaseEnd } from '../phase-timing-context.js';
|
|
23
28
|
import { updateNodeLifecycle } from '../lifecycle-context.js';
|
|
24
|
-
import { buildEvidencePrompt } from './prompts/evidence-prompts.js';
|
|
25
|
-
import { parseEvidenceRecords, validateUiEvidenceHasAppProof } from './evidence-output-parser.js';
|
|
29
|
+
import { buildEvidencePrompt, buildEvidenceRetryPrompt } from './prompts/evidence-prompts.js';
|
|
30
|
+
import { parseEvidenceRecords, validateUiEvidenceHasAppProof, validateEvidence, } from './evidence-output-parser.js';
|
|
26
31
|
import { hasSettings, getSettings } from '../../../settings.service.js';
|
|
32
|
+
const DEFAULT_MAX_RETRIES = 3;
|
|
33
|
+
/**
|
|
34
|
+
* Parse tasks.yaml into TaskForValidation[] for evidence completeness checking.
|
|
35
|
+
* Returns empty array if tasks.yaml is missing or unparseable.
|
|
36
|
+
*/
|
|
37
|
+
function parseTasks(specDir) {
|
|
38
|
+
const content = readSpecFile(specDir, 'tasks.yaml');
|
|
39
|
+
if (!content)
|
|
40
|
+
return [];
|
|
41
|
+
try {
|
|
42
|
+
const data = yaml.load(content);
|
|
43
|
+
if (!data?.tasks || !Array.isArray(data.tasks))
|
|
44
|
+
return [];
|
|
45
|
+
return data.tasks
|
|
46
|
+
.filter((t) => t !== null && typeof t === 'object')
|
|
47
|
+
.map((t) => ({
|
|
48
|
+
id: String(t.id ?? ''),
|
|
49
|
+
title: String(t.title ?? ''),
|
|
50
|
+
description: String(t.description ?? ''),
|
|
51
|
+
acceptanceCriteria: Array.isArray(t.acceptanceCriteria)
|
|
52
|
+
? t.acceptanceCriteria.map(String)
|
|
53
|
+
: [],
|
|
54
|
+
tdd: t.tdd != null ? t.tdd : null,
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
27
61
|
/**
|
|
28
62
|
* Factory that creates the evidence collection node function.
|
|
29
63
|
*
|
|
@@ -47,79 +81,109 @@ export function createEvidenceNode(executor) {
|
|
|
47
81
|
_needsReexecution: false,
|
|
48
82
|
};
|
|
49
83
|
}
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
84
|
+
// --- Configuration ---
|
|
85
|
+
const settings = hasSettings() ? getSettings() : undefined;
|
|
86
|
+
const commitEvidence = settings?.workflow.commitEvidence ?? false;
|
|
87
|
+
const maxRetries = settings?.workflow.evidenceRetries ?? DEFAULT_MAX_RETRIES;
|
|
88
|
+
const options = buildExecutorOptions(state);
|
|
89
|
+
const tasks = parseTasks(state.specDir);
|
|
90
|
+
// --- Validation retry loop ---
|
|
91
|
+
let allEvidence = [];
|
|
92
|
+
const allMessages = [];
|
|
93
|
+
let attempt = 0;
|
|
94
|
+
let lastErrors = [];
|
|
95
|
+
while (attempt < maxRetries) {
|
|
96
|
+
attempt++;
|
|
97
|
+
const attemptStart = Date.now();
|
|
98
|
+
// Build prompt: base for first attempt, retry with feedback for subsequent
|
|
99
|
+
const prompt = attempt === 1
|
|
100
|
+
? buildEvidencePrompt(state, { commitEvidence })
|
|
101
|
+
: buildEvidenceRetryPrompt(state, lastErrors, { commitEvidence });
|
|
102
|
+
const timingId = await recordPhaseStart(`evidence:attempt-${attempt}`, {
|
|
103
|
+
prompt,
|
|
104
|
+
modelId: state.model,
|
|
105
|
+
agentType: executor.agentType,
|
|
106
|
+
});
|
|
68
107
|
try {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
evidence
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
log.error(`
|
|
83
|
-
|
|
108
|
+
log.info(`Attempt ${attempt}/${maxRetries}: executing agent at cwd=${options.cwd}`);
|
|
109
|
+
const result = await retryExecute(executor, prompt, options, { logger: log });
|
|
110
|
+
const durationMs = Date.now() - attemptStart;
|
|
111
|
+
const elapsed = (durationMs / 1000).toFixed(1);
|
|
112
|
+
log.info(`Attempt ${attempt}: agent complete (${result.result.length} chars, ${elapsed}s)`);
|
|
113
|
+
// --- Parse evidence records (graceful degradation) ---
|
|
114
|
+
let evidence;
|
|
115
|
+
try {
|
|
116
|
+
evidence = parseEvidenceRecords(result.result);
|
|
117
|
+
log.info(`Attempt ${attempt}: parsed ${evidence.length} evidence record(s)`);
|
|
118
|
+
}
|
|
119
|
+
catch (parseErr) {
|
|
120
|
+
const msg = parseErr instanceof Error ? parseErr.message : String(parseErr);
|
|
121
|
+
log.error(`Attempt ${attempt}: evidence parsing failed: ${msg} — continuing with empty evidence`);
|
|
122
|
+
evidence = [];
|
|
123
|
+
}
|
|
124
|
+
// Accumulate evidence from all attempts (FR-11)
|
|
125
|
+
allEvidence = [...allEvidence, ...evidence];
|
|
126
|
+
// --- Validate UI evidence (informational warnings) ---
|
|
127
|
+
const uiResult = validateUiEvidenceHasAppProof(evidence);
|
|
128
|
+
if (uiResult.warnings.length > 0) {
|
|
129
|
+
for (const warning of uiResult.warnings) {
|
|
130
|
+
log.error(`Evidence validation: ${warning}`);
|
|
131
|
+
allMessages.push(`[evidence] Warning: ${warning}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// --- Validate evidence completeness + file existence ---
|
|
135
|
+
const validationResult = await validateEvidence(allEvidence, tasks);
|
|
136
|
+
await recordPhaseEnd(timingId, Date.now() - attemptStart, {
|
|
137
|
+
inputTokens: result.usage?.inputTokens,
|
|
138
|
+
outputTokens: result.usage?.outputTokens,
|
|
139
|
+
cacheCreationInputTokens: result.usage?.cacheCreationInputTokens,
|
|
140
|
+
cacheReadInputTokens: result.usage?.cacheReadInputTokens,
|
|
141
|
+
costUsd: result.usage?.costUsd,
|
|
142
|
+
numTurns: result.usage?.numTurns,
|
|
143
|
+
durationApiMs: result.usage?.durationApiMs,
|
|
144
|
+
exitCode: validationResult.valid ? 'success' : 'error',
|
|
145
|
+
});
|
|
146
|
+
if (validationResult.valid) {
|
|
147
|
+
log.info(`Attempt ${attempt}: validation passed`);
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
// Validation failed
|
|
151
|
+
lastErrors = validationResult.errors;
|
|
152
|
+
const errorSummary = validationResult.errors.map((e) => e.message).join('; ');
|
|
153
|
+
if (attempt < maxRetries) {
|
|
154
|
+
log.error(`Attempt ${attempt}: validation failed (${validationResult.errors.length} errors), retrying — ${errorSummary}`);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
// Exhausted retries — graceful degradation (FR-8)
|
|
158
|
+
log.error(`Attempt ${attempt}: validation failed after ${maxRetries} attempts — proceeding with partial evidence`);
|
|
159
|
+
allMessages.push(`[evidence] Warning: Validation failed after ${maxRetries} retries: ${errorSummary}`);
|
|
84
160
|
}
|
|
85
161
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
messages: [
|
|
101
|
-
`[evidence] Complete — ${evidence.length} evidence record(s) captured (${elapsed}s)`,
|
|
102
|
-
...(evidence.length === 0
|
|
103
|
-
? ['[evidence] Warning: no evidence records parsed from agent output']
|
|
104
|
-
: []),
|
|
105
|
-
...validationMessages,
|
|
106
|
-
],
|
|
107
|
-
_needsReexecution: false,
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
catch (err) {
|
|
111
|
-
// Re-throw LangGraph control-flow exceptions (interrupt, Command, etc.)
|
|
112
|
-
if (isGraphBubbleUp(err))
|
|
113
|
-
throw err;
|
|
114
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
115
|
-
const durationMs = Date.now() - startTime;
|
|
116
|
-
const elapsed = (durationMs / 1000).toFixed(1);
|
|
117
|
-
log.error(`Evidence collection failed: ${message} (${elapsed}s)`);
|
|
118
|
-
await recordPhaseEnd(timingId, durationMs, {
|
|
119
|
-
exitCode: 'error',
|
|
120
|
-
errorMessage: message.slice(0, 1000),
|
|
121
|
-
});
|
|
122
|
-
throw new Error(`[evidence] ${message}`);
|
|
162
|
+
catch (err) {
|
|
163
|
+
const durationMs = Date.now() - attemptStart;
|
|
164
|
+
await recordPhaseEnd(timingId, durationMs, {
|
|
165
|
+
exitCode: 'error',
|
|
166
|
+
errorMessage: (err instanceof Error ? err.message : String(err)).slice(0, 1000),
|
|
167
|
+
});
|
|
168
|
+
// Re-throw LangGraph control-flow exceptions
|
|
169
|
+
if (isGraphBubbleUp(err))
|
|
170
|
+
throw err;
|
|
171
|
+
// Non-retryable executor error — propagate immediately
|
|
172
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
173
|
+
log.error(`Attempt ${attempt}: evidence collection failed: ${message}`);
|
|
174
|
+
throw new Error(`[evidence] ${message}`);
|
|
175
|
+
}
|
|
123
176
|
}
|
|
177
|
+
markPhaseComplete(state.specDir, 'evidence', log);
|
|
178
|
+
allMessages.unshift(`[evidence] Complete — ${allEvidence.length} evidence record(s) captured in ${attempt} attempt(s)`, ...(allEvidence.length === 0
|
|
179
|
+
? ['[evidence] Warning: no evidence records parsed from agent output']
|
|
180
|
+
: []));
|
|
181
|
+
return {
|
|
182
|
+
currentNode: 'evidence',
|
|
183
|
+
evidence: allEvidence,
|
|
184
|
+
evidenceRetries: attempt,
|
|
185
|
+
messages: allMessages,
|
|
186
|
+
_needsReexecution: false,
|
|
187
|
+
};
|
|
124
188
|
};
|
|
125
189
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"implement.node.d.ts","sourceRoot":"","sources":["../../../../../../../../../packages/core/src/infrastructure/services/agents/feature-agent/nodes/implement.node.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+DAA+D,CAAC;
|
|
1
|
+
{"version":3,"file":"implement.node.d.ts","sourceRoot":"","sources":["../../../../../../../../../packages/core/src/infrastructure/services/agents/feature-agent/nodes/implement.node.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+DAA+D,CAAC;AACpG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAwErD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,cAAc,IAG5C,OAAO,iBAAiB,KAAG,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAiQ7E"}
|
package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/implement.node.js
CHANGED
|
@@ -7,16 +7,15 @@
|
|
|
7
7
|
* feature.yaml between phases.
|
|
8
8
|
*/
|
|
9
9
|
import yaml from 'js-yaml';
|
|
10
|
-
import { writeFileSync
|
|
11
|
-
import { join
|
|
10
|
+
import { writeFileSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
12
|
import { interrupt, isGraphBubbleUp } from '@langchain/langgraph';
|
|
13
13
|
import { createNodeLogger, readSpecFile, buildExecutorOptions, shouldInterrupt, safeYamlLoad, retryExecute, getCompletedPhases, markPhaseComplete, } from './node-helpers.js';
|
|
14
14
|
import { reportNodeStart } from '../heartbeat.js';
|
|
15
15
|
import { recordPhaseStart, recordPhaseEnd, recordApprovalWaitStart, updatePhasePrompt, } from '../phase-timing-context.js';
|
|
16
16
|
import { updateNodeLifecycle } from '../lifecycle-context.js';
|
|
17
17
|
import { buildImplementPhasePrompt, } from './prompts/implement.prompt.js';
|
|
18
|
-
import {
|
|
19
|
-
import { parseEvidenceRecords } from './evidence-output-parser.js';
|
|
18
|
+
import { createEvidenceNode } from './evidence.node.js';
|
|
20
19
|
import { hasSettings, getSettings } from '../../../settings.service.js';
|
|
21
20
|
/**
|
|
22
21
|
* Update feature.yaml with current implementation progress.
|
|
@@ -193,11 +192,16 @@ export function createImplementNode(executor) {
|
|
|
193
192
|
messages.push(`[implement] Complete: ${totalTasks} tasks across ${totalPhases} phases (${elapsed}s)`);
|
|
194
193
|
// --- Evidence sub-agent: capture proof of completion (settings-gated) ---
|
|
195
194
|
const evidenceEnabled = hasSettings() && getSettings().workflow.enableEvidence;
|
|
196
|
-
|
|
197
|
-
if (
|
|
195
|
+
let evidenceResult = {};
|
|
196
|
+
if (evidenceEnabled) {
|
|
197
|
+
const evidenceNode = createEvidenceNode(executor);
|
|
198
|
+
evidenceResult = await evidenceNode(state);
|
|
199
|
+
messages.push(`[implement] Evidence: ${(evidenceResult.evidence ?? []).length} record(s) captured`);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
198
202
|
log.info('Evidence collection disabled via settings — skipping');
|
|
203
|
+
messages.push('[implement] Evidence: 0 record(s) captured');
|
|
199
204
|
}
|
|
200
|
-
messages.push(`[implement] Evidence: ${evidence.length} record(s) captured`);
|
|
201
205
|
// Record top-level implement phase completion
|
|
202
206
|
await recordPhaseEnd(implementTimingId, Date.now() - startTime, {
|
|
203
207
|
inputTokens: totalInputTokens || undefined,
|
|
@@ -215,7 +219,12 @@ export function createImplementNode(executor) {
|
|
|
215
219
|
message: `Implementation complete: ${totalTasks} tasks across ${totalPhases} phases. Approve to finish.`,
|
|
216
220
|
});
|
|
217
221
|
}
|
|
218
|
-
return {
|
|
222
|
+
return {
|
|
223
|
+
currentNode: 'implement',
|
|
224
|
+
evidence: evidenceResult.evidence ?? [],
|
|
225
|
+
evidenceRetries: evidenceResult.evidenceRetries ?? 0,
|
|
226
|
+
messages: [...messages, ...(evidenceResult.messages ?? [])],
|
|
227
|
+
};
|
|
219
228
|
}
|
|
220
229
|
catch (err) {
|
|
221
230
|
if (isGraphBubbleUp(err))
|
|
@@ -240,58 +249,3 @@ export function createImplementNode(executor) {
|
|
|
240
249
|
}
|
|
241
250
|
};
|
|
242
251
|
}
|
|
243
|
-
/**
|
|
244
|
-
* Sub-agent call to collect evidence after implementation completes.
|
|
245
|
-
* Graceful degradation: returns empty array on any failure so evidence
|
|
246
|
-
* collection never blocks the workflow.
|
|
247
|
-
*/
|
|
248
|
-
async function collectEvidence(executor, state, log) {
|
|
249
|
-
try {
|
|
250
|
-
log.info('Collecting evidence (sub-agent)');
|
|
251
|
-
const commitEvidence = hasSettings() && getSettings().workflow.commitEvidence;
|
|
252
|
-
const prompt = buildEvidencePrompt(state, { commitEvidence });
|
|
253
|
-
const options = buildExecutorOptions(state);
|
|
254
|
-
const result = await retryExecute(executor, prompt, options, { logger: log });
|
|
255
|
-
try {
|
|
256
|
-
const evidence = parseEvidenceRecords(result.result);
|
|
257
|
-
log.info(`Parsed ${evidence.length} evidence record(s)`);
|
|
258
|
-
saveEvidenceManifest(state, evidence, log);
|
|
259
|
-
return evidence;
|
|
260
|
-
}
|
|
261
|
-
catch (parseErr) {
|
|
262
|
-
const msg = parseErr instanceof Error ? parseErr.message : String(parseErr);
|
|
263
|
-
log.error(`Warning: evidence parsing failed: ${msg} — continuing with empty evidence`);
|
|
264
|
-
return [];
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
catch (err) {
|
|
268
|
-
// Re-throw LangGraph control-flow exceptions
|
|
269
|
-
if (isGraphBubbleUp(err))
|
|
270
|
-
throw err;
|
|
271
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
272
|
-
log.error(`Evidence collection failed: ${msg} — continuing without evidence`);
|
|
273
|
-
return [];
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
/**
|
|
277
|
-
* Save evidence manifest to the shep home evidence folder so the
|
|
278
|
-
* merge review UI can read it without accessing graph state.
|
|
279
|
-
*/
|
|
280
|
-
function saveEvidenceManifest(state, evidence, log) {
|
|
281
|
-
if (evidence.length === 0)
|
|
282
|
-
return;
|
|
283
|
-
try {
|
|
284
|
-
const cwd = state.worktreePath || state.repositoryPath;
|
|
285
|
-
// Worktree path: ~/.shep/repos/<hash>/wt/<slug>
|
|
286
|
-
// Evidence dir: ~/.shep/repos/<hash>/evidence/<featureId>/
|
|
287
|
-
const repoHashDir = dirname(dirname(cwd));
|
|
288
|
-
const evidenceDir = join(repoHashDir, 'evidence', state.featureId);
|
|
289
|
-
mkdirSync(evidenceDir, { recursive: true });
|
|
290
|
-
writeFileSync(join(evidenceDir, 'manifest.json'), JSON.stringify(evidence, null, 2), 'utf-8');
|
|
291
|
-
log.info(`Saved evidence manifest to ${evidenceDir}/manifest.json`);
|
|
292
|
-
}
|
|
293
|
-
catch (err) {
|
|
294
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
295
|
-
log.error(`Failed to save evidence manifest: ${msg}`);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
@@ -11,9 +11,21 @@
|
|
|
11
11
|
* graph state.
|
|
12
12
|
*/
|
|
13
13
|
import type { FeatureAgentState } from '../../state.js';
|
|
14
|
+
import type { ValidationError } from '../evidence-output-parser.js';
|
|
14
15
|
export interface EvidencePromptOptions {
|
|
15
16
|
/** Whether to commit evidence to the worktree / feature branch */
|
|
16
17
|
commitEvidence: boolean;
|
|
17
18
|
}
|
|
18
19
|
export declare function buildEvidencePrompt(state: FeatureAgentState, options?: EvidencePromptOptions): string;
|
|
20
|
+
/**
|
|
21
|
+
* Build a retry prompt for evidence collection that augments the base evidence
|
|
22
|
+
* prompt with structured validation feedback listing specific failures.
|
|
23
|
+
*
|
|
24
|
+
* Follows the repair.node.ts pattern of passing validation errors into the
|
|
25
|
+
* retry prompt so the agent can focus on fixing specific gaps rather than
|
|
26
|
+
* recollecting all evidence blindly.
|
|
27
|
+
*
|
|
28
|
+
* When errors is empty, returns the base prompt without a feedback section.
|
|
29
|
+
*/
|
|
30
|
+
export declare function buildEvidenceRetryPrompt(state: FeatureAgentState, errors: ValidationError[], options?: EvidencePromptOptions): string;
|
|
19
31
|
//# sourceMappingURL=evidence-prompts.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"evidence-prompts.d.ts","sourceRoot":"","sources":["../../../../../../../../../../packages/core/src/infrastructure/services/agents/feature-agent/nodes/prompts/evidence-prompts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"evidence-prompts.d.ts","sourceRoot":"","sources":["../../../../../../../../../../packages/core/src/infrastructure/services/agents/feature-agent/nodes/prompts/evidence-prompts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAEpE,MAAM,WAAW,qBAAqB;IACpC,kEAAkE;IAClE,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,iBAAiB,EACxB,OAAO,GAAE,qBAAiD,GACzD,MAAM,CA0MR;AAgCD;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,iBAAiB,EACxB,MAAM,EAAE,eAAe,EAAE,EACzB,OAAO,GAAE,qBAAiD,GACzD,MAAM,CAqBR"}
|
|
@@ -208,3 +208,53 @@ If no evidence can be captured (e.g., no UI to screenshot, no tests to run), out
|
|
|
208
208
|
- Do NOT capture evidence for documentation-only changes or spec files
|
|
209
209
|
${commitSection}`;
|
|
210
210
|
}
|
|
211
|
+
function formatValidationErrors(errors) {
|
|
212
|
+
if (errors.length === 0)
|
|
213
|
+
return '';
|
|
214
|
+
const uiErrors = errors.filter((e) => e.type === 'ui');
|
|
215
|
+
const completenessErrors = errors.filter((e) => e.type === 'completeness');
|
|
216
|
+
const fileErrors = errors.filter((e) => e.type === 'fileExistence');
|
|
217
|
+
const sections = [];
|
|
218
|
+
if (uiErrors.length > 0) {
|
|
219
|
+
sections.push(`### Missing App-Level Screenshots\n\n${uiErrors.map((e) => `- ${e.message}`).join('\n')}`);
|
|
220
|
+
}
|
|
221
|
+
if (completenessErrors.length > 0) {
|
|
222
|
+
sections.push(`### Missing Evidence by Task Type\n\n${completenessErrors
|
|
223
|
+
.map((e) => `- ${e.message}`)
|
|
224
|
+
.join('\n')}`);
|
|
225
|
+
}
|
|
226
|
+
if (fileErrors.length > 0) {
|
|
227
|
+
sections.push(`### File Issues\n\n${fileErrors.map((e) => `- ${e.message}`).join('\n')}`);
|
|
228
|
+
}
|
|
229
|
+
return sections.join('\n\n');
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Build a retry prompt for evidence collection that augments the base evidence
|
|
233
|
+
* prompt with structured validation feedback listing specific failures.
|
|
234
|
+
*
|
|
235
|
+
* Follows the repair.node.ts pattern of passing validation errors into the
|
|
236
|
+
* retry prompt so the agent can focus on fixing specific gaps rather than
|
|
237
|
+
* recollecting all evidence blindly.
|
|
238
|
+
*
|
|
239
|
+
* When errors is empty, returns the base prompt without a feedback section.
|
|
240
|
+
*/
|
|
241
|
+
export function buildEvidenceRetryPrompt(state, errors, options = { commitEvidence: false }) {
|
|
242
|
+
const basePrompt = buildEvidencePrompt(state, options);
|
|
243
|
+
if (errors.length === 0)
|
|
244
|
+
return basePrompt;
|
|
245
|
+
const feedbackSection = `
|
|
246
|
+
|
|
247
|
+
## VALIDATION FEEDBACK
|
|
248
|
+
|
|
249
|
+
The previous evidence collection attempt was insufficient. Address the following missing evidence. Focus ONLY on fixing the listed gaps — do NOT recollect evidence that was already captured successfully.
|
|
250
|
+
|
|
251
|
+
${formatValidationErrors(errors)}
|
|
252
|
+
|
|
253
|
+
### Instructions
|
|
254
|
+
|
|
255
|
+
- Review the issues above and capture the missing evidence
|
|
256
|
+
- Focus on the specific gaps listed — do not re-capture evidence that already exists
|
|
257
|
+
- Ensure new evidence files are saved to the correct paths
|
|
258
|
+
- Output ALL evidence records (both previously captured and newly captured) in the JSON output block`;
|
|
259
|
+
return basePrompt + feedbackSection;
|
|
260
|
+
}
|
|
@@ -49,6 +49,7 @@ export declare const FeatureAgentAnnotation: import("@langchain/langgraph").Anno
|
|
|
49
49
|
push: import("@langchain/langgraph").BinaryOperatorAggregate<boolean, boolean>;
|
|
50
50
|
openPr: import("@langchain/langgraph").BinaryOperatorAggregate<boolean, boolean>;
|
|
51
51
|
evidence: import("@langchain/langgraph").BinaryOperatorAggregate<Evidence[], Evidence[]>;
|
|
52
|
+
evidenceRetries: import("@langchain/langgraph").BinaryOperatorAggregate<number, number>;
|
|
52
53
|
resumeReason: import("@langchain/langgraph").BinaryOperatorAggregate<string | undefined, string | undefined>;
|
|
53
54
|
ciFixAttempts: import("@langchain/langgraph").BinaryOperatorAggregate<number, number>;
|
|
54
55
|
ciFixHistory: import("@langchain/langgraph").BinaryOperatorAggregate<CiFixRecord[], CiFixRecord[]>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../../../../../../packages/core/src/infrastructure/services/agents/feature-agent/state.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAEzF;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../../../../../../packages/core/src/infrastructure/services/agents/feature-agent/state.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAEzF;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoGjC,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,OAAO,sBAAsB,CAAC,KAAK,CAAC"}
|
|
@@ -84,6 +84,10 @@ export const FeatureAgentAnnotation = Annotation.Root({
|
|
|
84
84
|
reducer: (prev, next) => [...prev, ...next],
|
|
85
85
|
default: () => [],
|
|
86
86
|
}),
|
|
87
|
+
evidenceRetries: Annotation({
|
|
88
|
+
reducer: (_prev, next) => next,
|
|
89
|
+
default: () => 0,
|
|
90
|
+
}),
|
|
87
91
|
// --- Resume context (set when resuming from error/crash/stop) ---
|
|
88
92
|
resumeReason: Annotation({
|
|
89
93
|
reducer: (_prev, next) => next ?? _prev,
|