feat(#137): create Forced Continuation Prompt System
Implement prompt generation system that produces continuation prompts based on verification failures to force AI agents to complete work. Service: - generatePrompt: Complete prompt from failure context - generateTestFailurePrompt: Test-specific guidance - generateBuildErrorPrompt: Build error resolution - generateCoveragePrompt: Coverage improvement strategy - generateIncompleteWorkPrompt: Completion requirements Templates: - base.template: System/user prompt structure - test-failure.template: Test fix guidance - build-error.template: Compilation error guidance - coverage.template: Coverage improvement strategy - incomplete-work.template: Completion requirements Constraint escalation: - Attempt 1: Normal guidance - Attempt 2: Focus only on failures - Attempt 3: Minimal changes only - Final: Last attempt warning Priority levels: critical/high/normal based on failure severity Tests: 24 passing with 95.31% coverage Fixes #137 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,12 @@
|
|||||||
|
import { Module } from "@nestjs/common";
|
||||||
|
import { ContinuationPromptsService } from "./continuation-prompts.service";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Continuation Prompts Module
|
||||||
|
* Generates forced continuation prompts for incomplete AI agent work
|
||||||
|
*/
|
||||||
|
@Module({
|
||||||
|
providers: [ContinuationPromptsService],
|
||||||
|
exports: [ContinuationPromptsService],
|
||||||
|
})
|
||||||
|
export class ContinuationPromptsModule {}
|
||||||
@@ -0,0 +1,387 @@
|
|||||||
|
import { describe, it, expect, beforeEach } from "vitest";
|
||||||
|
import { ContinuationPromptsService } from "./continuation-prompts.service";
|
||||||
|
import { ContinuationPromptContext, FailureDetail, ContinuationPrompt } from "./interfaces";
|
||||||
|
|
||||||
|
describe("ContinuationPromptsService", () => {
|
||||||
|
let service: ContinuationPromptsService;
|
||||||
|
let baseContext: ContinuationPromptContext;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
service = new ContinuationPromptsService();
|
||||||
|
baseContext = {
|
||||||
|
taskId: "task-1",
|
||||||
|
originalTask: "Implement user authentication",
|
||||||
|
attemptNumber: 1,
|
||||||
|
maxAttempts: 3,
|
||||||
|
failures: [],
|
||||||
|
filesChanged: ["src/auth/auth.service.ts"],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("generatePrompt", () => {
|
||||||
|
it("should generate a prompt with system and user sections", () => {
|
||||||
|
const context: ContinuationPromptContext = {
|
||||||
|
...baseContext,
|
||||||
|
failures: [
|
||||||
|
{
|
||||||
|
type: "test-failure",
|
||||||
|
message: "Test failed: should authenticate user",
|
||||||
|
details: "Expected 200, got 401",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const prompt = service.generatePrompt(context);
|
||||||
|
|
||||||
|
expect(prompt).toBeDefined();
|
||||||
|
expect(prompt.systemPrompt).toContain("CRITICAL RULES");
|
||||||
|
expect(prompt.userPrompt).toContain("Implement user authentication");
|
||||||
|
expect(prompt.userPrompt).toContain("Test failed");
|
||||||
|
expect(prompt.constraints).toBeInstanceOf(Array);
|
||||||
|
expect(prompt.priority).toBe("high");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should include attempt number in prompt", () => {
|
||||||
|
const context: ContinuationPromptContext = {
|
||||||
|
...baseContext,
|
||||||
|
attemptNumber: 2,
|
||||||
|
failures: [
|
||||||
|
{
|
||||||
|
type: "build-error",
|
||||||
|
message: "Type error in auth.service.ts",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const prompt = service.generatePrompt(context);
|
||||||
|
|
||||||
|
expect(prompt.userPrompt).toContain("attempt 2 of 3");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should escalate priority on final attempt", () => {
|
||||||
|
const context: ContinuationPromptContext = {
|
||||||
|
...baseContext,
|
||||||
|
attemptNumber: 3,
|
||||||
|
maxAttempts: 3,
|
||||||
|
failures: [
|
||||||
|
{
|
||||||
|
type: "test-failure",
|
||||||
|
message: "Tests still failing",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const prompt = service.generatePrompt(context);
|
||||||
|
|
||||||
|
expect(prompt.priority).toBe("critical");
|
||||||
|
expect(prompt.constraints).toContain(
|
||||||
|
"This is your LAST attempt. Failure means manual intervention required."
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle multiple failure types", () => {
|
||||||
|
const context: ContinuationPromptContext = {
|
||||||
|
...baseContext,
|
||||||
|
failures: [
|
||||||
|
{
|
||||||
|
type: "test-failure",
|
||||||
|
message: "Auth test failed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "build-error",
|
||||||
|
message: "Type error",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "coverage",
|
||||||
|
message: "Coverage below 85%",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const prompt = service.generatePrompt(context);
|
||||||
|
|
||||||
|
expect(prompt.userPrompt).toContain("Auth test failed");
|
||||||
|
expect(prompt.userPrompt).toContain("Type error");
|
||||||
|
expect(prompt.userPrompt).toContain("Coverage below 85%");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("generateTestFailurePrompt", () => {
|
||||||
|
it("should format test failures with details", () => {
|
||||||
|
const failures: FailureDetail[] = [
|
||||||
|
{
|
||||||
|
type: "test-failure",
|
||||||
|
message: "should authenticate user",
|
||||||
|
details: "Expected 200, got 401",
|
||||||
|
location: "auth.service.spec.ts:42",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "test-failure",
|
||||||
|
message: "should reject invalid credentials",
|
||||||
|
details: "AssertionError: expected false to be true",
|
||||||
|
location: "auth.service.spec.ts:58",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const prompt = service.generateTestFailurePrompt(failures);
|
||||||
|
|
||||||
|
expect(prompt).toContain("should authenticate user");
|
||||||
|
expect(prompt).toContain("Expected 200, got 401");
|
||||||
|
expect(prompt).toContain("auth.service.spec.ts:42");
|
||||||
|
expect(prompt).toContain("should reject invalid credentials");
|
||||||
|
expect(prompt).toContain("Fix the implementation");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should include guidance for fixing tests", () => {
|
||||||
|
const failures: FailureDetail[] = [
|
||||||
|
{
|
||||||
|
type: "test-failure",
|
||||||
|
message: "Test failed",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const prompt = service.generateTestFailurePrompt(failures);
|
||||||
|
|
||||||
|
expect(prompt).toContain("Read the test");
|
||||||
|
expect(prompt).toContain("Fix the implementation");
|
||||||
|
expect(prompt).toContain("Run the test");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("generateBuildErrorPrompt", () => {
|
||||||
|
it("should format build errors with location", () => {
|
||||||
|
const failures: FailureDetail[] = [
|
||||||
|
{
|
||||||
|
type: "build-error",
|
||||||
|
message: "Type 'string' is not assignable to type 'number'",
|
||||||
|
location: "auth.service.ts:25",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "build-error",
|
||||||
|
message: "Cannot find name 'User'",
|
||||||
|
location: "auth.service.ts:42",
|
||||||
|
suggestion: "Import User from '@/entities'",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const prompt = service.generateBuildErrorPrompt(failures);
|
||||||
|
|
||||||
|
expect(prompt).toContain("Type 'string' is not assignable");
|
||||||
|
expect(prompt).toContain("auth.service.ts:25");
|
||||||
|
expect(prompt).toContain("Cannot find name 'User'");
|
||||||
|
expect(prompt).toContain("Import User from");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should include build-specific guidance", () => {
|
||||||
|
const failures: FailureDetail[] = [
|
||||||
|
{
|
||||||
|
type: "build-error",
|
||||||
|
message: "Syntax error",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const prompt = service.generateBuildErrorPrompt(failures);
|
||||||
|
|
||||||
|
expect(prompt).toContain("TypeScript");
|
||||||
|
expect(prompt).toContain("Do not proceed until build passes");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("generateCoveragePrompt", () => {
|
||||||
|
it("should show coverage gap", () => {
|
||||||
|
const prompt = service.generateCoveragePrompt(72, 85);
|
||||||
|
|
||||||
|
expect(prompt).toContain("72%");
|
||||||
|
expect(prompt).toContain("85%");
|
||||||
|
expect(prompt).toContain("13%"); // gap
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should provide guidance for improving coverage", () => {
|
||||||
|
const prompt = service.generateCoveragePrompt(80, 85);
|
||||||
|
|
||||||
|
expect(prompt).toContain("uncovered code paths");
|
||||||
|
expect(prompt).toContain("edge cases");
|
||||||
|
expect(prompt).toContain("error handling");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("generateIncompleteWorkPrompt", () => {
|
||||||
|
it("should list incomplete work items", () => {
|
||||||
|
const issues = [
|
||||||
|
"TODO: Implement password hashing",
|
||||||
|
"FIXME: Add error handling",
|
||||||
|
"Missing validation for email format",
|
||||||
|
];
|
||||||
|
|
||||||
|
const prompt = service.generateIncompleteWorkPrompt(issues);
|
||||||
|
|
||||||
|
expect(prompt).toContain("TODO: Implement password hashing");
|
||||||
|
expect(prompt).toContain("FIXME: Add error handling");
|
||||||
|
expect(prompt).toContain("Missing validation");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should emphasize completion requirement", () => {
|
||||||
|
const issues = ["Missing feature X"];
|
||||||
|
|
||||||
|
const prompt = service.generateIncompleteWorkPrompt(issues);
|
||||||
|
|
||||||
|
expect(prompt).toContain("MUST complete ALL aspects");
|
||||||
|
expect(prompt).toContain("Do not leave TODO");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getConstraints", () => {
|
||||||
|
it("should return basic constraints for first attempt", () => {
|
||||||
|
const constraints = service.getConstraints(1, 3);
|
||||||
|
|
||||||
|
expect(constraints).toBeInstanceOf(Array);
|
||||||
|
expect(constraints.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should escalate constraints on second attempt", () => {
|
||||||
|
const constraints = service.getConstraints(2, 3);
|
||||||
|
|
||||||
|
expect(constraints).toContain("Focus only on failures, no new features");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should add strict constraints on third attempt", () => {
|
||||||
|
const constraints = service.getConstraints(3, 3);
|
||||||
|
|
||||||
|
expect(constraints).toContain("Minimal changes only, fix exact issues");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should add final warning on last attempt", () => {
|
||||||
|
const constraints = service.getConstraints(3, 3);
|
||||||
|
|
||||||
|
expect(constraints).toContain(
|
||||||
|
"This is your LAST attempt. Failure means manual intervention required."
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle different max attempts", () => {
|
||||||
|
const constraints = service.getConstraints(5, 5);
|
||||||
|
|
||||||
|
expect(constraints).toContain(
|
||||||
|
"This is your LAST attempt. Failure means manual intervention required."
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("formatFailuresForPrompt", () => {
|
||||||
|
it("should format failures with all details", () => {
|
||||||
|
const failures: FailureDetail[] = [
|
||||||
|
{
|
||||||
|
type: "test-failure",
|
||||||
|
message: "Test failed",
|
||||||
|
details: "Expected true, got false",
|
||||||
|
location: "file.spec.ts:10",
|
||||||
|
suggestion: "Check the implementation",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const formatted = service.formatFailuresForPrompt(failures);
|
||||||
|
|
||||||
|
expect(formatted).toContain("test-failure");
|
||||||
|
expect(formatted).toContain("Test failed");
|
||||||
|
expect(formatted).toContain("Expected true, got false");
|
||||||
|
expect(formatted).toContain("file.spec.ts:10");
|
||||||
|
expect(formatted).toContain("Check the implementation");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle failures without optional fields", () => {
|
||||||
|
const failures: FailureDetail[] = [
|
||||||
|
{
|
||||||
|
type: "lint-error",
|
||||||
|
message: "Unused variable",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const formatted = service.formatFailuresForPrompt(failures);
|
||||||
|
|
||||||
|
expect(formatted).toContain("lint-error");
|
||||||
|
expect(formatted).toContain("Unused variable");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should format multiple failures", () => {
|
||||||
|
const failures: FailureDetail[] = [
|
||||||
|
{
|
||||||
|
type: "test-failure",
|
||||||
|
message: "Test 1 failed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "build-error",
|
||||||
|
message: "Build error",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "coverage",
|
||||||
|
message: "Low coverage",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const formatted = service.formatFailuresForPrompt(failures);
|
||||||
|
|
||||||
|
expect(formatted).toContain("Test 1 failed");
|
||||||
|
expect(formatted).toContain("Build error");
|
||||||
|
expect(formatted).toContain("Low coverage");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle empty failures array", () => {
|
||||||
|
const failures: FailureDetail[] = [];
|
||||||
|
|
||||||
|
const formatted = service.formatFailuresForPrompt(failures);
|
||||||
|
|
||||||
|
expect(formatted).toBe("");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("priority assignment", () => {
|
||||||
|
it("should set normal priority for first attempt with minor issues", () => {
|
||||||
|
const context: ContinuationPromptContext = {
|
||||||
|
...baseContext,
|
||||||
|
attemptNumber: 1,
|
||||||
|
failures: [
|
||||||
|
{
|
||||||
|
type: "lint-error",
|
||||||
|
message: "Minor lint issue",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const prompt = service.generatePrompt(context);
|
||||||
|
|
||||||
|
expect(prompt.priority).toBe("normal");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should set high priority for build errors", () => {
|
||||||
|
const context: ContinuationPromptContext = {
|
||||||
|
...baseContext,
|
||||||
|
failures: [
|
||||||
|
{
|
||||||
|
type: "build-error",
|
||||||
|
message: "Build failed",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const prompt = service.generatePrompt(context);
|
||||||
|
|
||||||
|
expect(prompt.priority).toBe("high");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should set high priority for test failures", () => {
|
||||||
|
const context: ContinuationPromptContext = {
|
||||||
|
...baseContext,
|
||||||
|
failures: [
|
||||||
|
{
|
||||||
|
type: "test-failure",
|
||||||
|
message: "Test failed",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const prompt = service.generatePrompt(context);
|
||||||
|
|
||||||
|
expect(prompt.priority).toBe("high");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
import { Injectable } from "@nestjs/common";
|
||||||
|
import { ContinuationPromptContext, FailureDetail, ContinuationPrompt } from "./interfaces";
|
||||||
|
import {
|
||||||
|
BASE_CONTINUATION_SYSTEM,
|
||||||
|
BASE_USER_PROMPT,
|
||||||
|
TEST_FAILURE_TEMPLATE,
|
||||||
|
BUILD_ERROR_TEMPLATE,
|
||||||
|
COVERAGE_TEMPLATE,
|
||||||
|
INCOMPLETE_WORK_TEMPLATE,
|
||||||
|
} from "./templates";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service for generating continuation prompts when AI agent work is incomplete
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class ContinuationPromptsService {
|
||||||
|
/**
|
||||||
|
* Generate a complete continuation prompt from context
|
||||||
|
*/
|
||||||
|
generatePrompt(context: ContinuationPromptContext): ContinuationPrompt {
|
||||||
|
const systemPrompt = BASE_CONTINUATION_SYSTEM;
|
||||||
|
const constraints = this.getConstraints(context.attemptNumber, context.maxAttempts);
|
||||||
|
|
||||||
|
// Format failures based on their types
|
||||||
|
const formattedFailures = this.formatFailuresByType(context.failures);
|
||||||
|
|
||||||
|
// Build user prompt
|
||||||
|
const userPrompt = BASE_USER_PROMPT.replace("{{taskDescription}}", context.originalTask)
|
||||||
|
.replace("{{attemptNumber}}", String(context.attemptNumber))
|
||||||
|
.replace("{{maxAttempts}}", String(context.maxAttempts))
|
||||||
|
.replace("{{failures}}", formattedFailures)
|
||||||
|
.replace("{{constraints}}", this.formatConstraints(constraints));
|
||||||
|
|
||||||
|
// Determine priority
|
||||||
|
const priority = this.determinePriority(context);
|
||||||
|
|
||||||
|
return {
|
||||||
|
systemPrompt,
|
||||||
|
userPrompt,
|
||||||
|
constraints,
|
||||||
|
priority,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate test failure specific prompt
|
||||||
|
*/
|
||||||
|
generateTestFailurePrompt(failures: FailureDetail[]): string {
|
||||||
|
const formattedFailures = this.formatFailuresForPrompt(failures);
|
||||||
|
return TEST_FAILURE_TEMPLATE.replace("{{failures}}", formattedFailures);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate build error specific prompt
|
||||||
|
*/
|
||||||
|
generateBuildErrorPrompt(failures: FailureDetail[]): string {
|
||||||
|
const formattedErrors = this.formatFailuresForPrompt(failures);
|
||||||
|
return BUILD_ERROR_TEMPLATE.replace("{{errors}}", formattedErrors);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate coverage improvement prompt
|
||||||
|
*/
|
||||||
|
generateCoveragePrompt(current: number, required: number): string {
|
||||||
|
const gap = required - current;
|
||||||
|
return COVERAGE_TEMPLATE.replace("{{currentCoverage}}", String(current))
|
||||||
|
.replace("{{requiredCoverage}}", String(required))
|
||||||
|
.replace("{{gap}}", String(gap))
|
||||||
|
.replace("{{uncoveredFiles}}", "(See coverage report for details)");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate incomplete work prompt
|
||||||
|
*/
|
||||||
|
generateIncompleteWorkPrompt(issues: string[]): string {
|
||||||
|
const formattedIssues = issues.map((issue) => `- ${issue}`).join("\n");
|
||||||
|
return INCOMPLETE_WORK_TEMPLATE.replace("{{issues}}", formattedIssues);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get constraints based on attempt number
|
||||||
|
*/
|
||||||
|
getConstraints(attemptNumber: number, maxAttempts: number): string[] {
|
||||||
|
const constraints: string[] = [
|
||||||
|
"Address ALL failures listed above",
|
||||||
|
"Run all quality checks before claiming completion",
|
||||||
|
];
|
||||||
|
|
||||||
|
if (attemptNumber >= 2) {
|
||||||
|
constraints.push("Focus only on failures, no new features");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attemptNumber >= 3) {
|
||||||
|
constraints.push("Minimal changes only, fix exact issues");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attemptNumber >= maxAttempts) {
|
||||||
|
constraints.push("This is your LAST attempt. Failure means manual intervention required.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return constraints;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format failures for inclusion in prompt
|
||||||
|
*/
|
||||||
|
formatFailuresForPrompt(failures: FailureDetail[]): string {
|
||||||
|
if (failures.length === 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return failures
|
||||||
|
.map((failure, index) => {
|
||||||
|
const parts: string[] = [`${String(index + 1)}. [${failure.type}] ${failure.message}`];
|
||||||
|
|
||||||
|
if (failure.location) {
|
||||||
|
parts.push(` Location: ${failure.location}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failure.details) {
|
||||||
|
parts.push(` Details: ${failure.details}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failure.suggestion) {
|
||||||
|
parts.push(` Suggestion: ${failure.suggestion}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts.join("\n");
|
||||||
|
})
|
||||||
|
.join("\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format failures by type using appropriate templates
|
||||||
|
*/
|
||||||
|
private formatFailuresByType(failures: FailureDetail[]): string {
|
||||||
|
const sections: string[] = [];
|
||||||
|
|
||||||
|
// Group failures by type
|
||||||
|
const testFailures = failures.filter((f) => f.type === "test-failure");
|
||||||
|
const buildErrors = failures.filter((f) => f.type === "build-error");
|
||||||
|
const coverageIssues = failures.filter((f) => f.type === "coverage");
|
||||||
|
const incompleteWork = failures.filter((f) => f.type === "incomplete-work");
|
||||||
|
const lintErrors = failures.filter((f) => f.type === "lint-error");
|
||||||
|
|
||||||
|
if (testFailures.length > 0) {
|
||||||
|
sections.push(this.generateTestFailurePrompt(testFailures));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buildErrors.length > 0) {
|
||||||
|
sections.push(this.generateBuildErrorPrompt(buildErrors));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (coverageIssues.length > 0) {
|
||||||
|
// Extract coverage numbers from message if available
|
||||||
|
const coverageFailure = coverageIssues[0];
|
||||||
|
if (coverageFailure) {
|
||||||
|
const match = /(\d+)%.*?(\d+)%/.exec(coverageFailure.message);
|
||||||
|
if (match?.[1] && match[2]) {
|
||||||
|
sections.push(this.generateCoveragePrompt(parseInt(match[1]), parseInt(match[2])));
|
||||||
|
} else {
|
||||||
|
sections.push(this.formatFailuresForPrompt(coverageIssues));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (incompleteWork.length > 0) {
|
||||||
|
const issues = incompleteWork.map((f) => f.message);
|
||||||
|
sections.push(this.generateIncompleteWorkPrompt(issues));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lintErrors.length > 0) {
|
||||||
|
sections.push("Lint Errors:\n" + this.formatFailuresForPrompt(lintErrors));
|
||||||
|
}
|
||||||
|
|
||||||
|
return sections.join("\n\n---\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format constraints as a bulleted list
|
||||||
|
*/
|
||||||
|
private formatConstraints(constraints: string[]): string {
|
||||||
|
return "CONSTRAINTS:\n" + constraints.map((c) => `- ${c}`).join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine priority based on context
|
||||||
|
*/
|
||||||
|
private determinePriority(context: ContinuationPromptContext): "critical" | "high" | "normal" {
|
||||||
|
// Final attempt is always critical
|
||||||
|
if (context.attemptNumber >= context.maxAttempts) {
|
||||||
|
return "critical";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build errors and test failures are high priority
|
||||||
|
const hasCriticalFailures = context.failures.some(
|
||||||
|
(f) => f.type === "build-error" || f.type === "test-failure"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (hasCriticalFailures) {
|
||||||
|
return "high";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Everything else is normal
|
||||||
|
return "normal";
|
||||||
|
}
|
||||||
|
}
|
||||||
3
apps/api/src/continuation-prompts/index.ts
Normal file
3
apps/api/src/continuation-prompts/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * from "./continuation-prompts.module";
|
||||||
|
export * from "./continuation-prompts.service";
|
||||||
|
export * from "./interfaces";
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
export interface ContinuationPromptContext {
|
||||||
|
taskId: string;
|
||||||
|
originalTask: string;
|
||||||
|
attemptNumber: number;
|
||||||
|
maxAttempts: number;
|
||||||
|
failures: FailureDetail[];
|
||||||
|
previousOutput?: string;
|
||||||
|
filesChanged: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FailureDetail {
|
||||||
|
type: "test-failure" | "build-error" | "lint-error" | "coverage" | "incomplete-work";
|
||||||
|
message: string;
|
||||||
|
details?: string;
|
||||||
|
location?: string; // file:line
|
||||||
|
suggestion?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ContinuationPrompt {
|
||||||
|
systemPrompt: string;
|
||||||
|
userPrompt: string;
|
||||||
|
constraints: string[];
|
||||||
|
priority: "critical" | "high" | "normal";
|
||||||
|
}
|
||||||
1
apps/api/src/continuation-prompts/interfaces/index.ts
Normal file
1
apps/api/src/continuation-prompts/interfaces/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from "./continuation-prompt.interface";
|
||||||
18
apps/api/src/continuation-prompts/templates/base.template.ts
Normal file
18
apps/api/src/continuation-prompts/templates/base.template.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
export const BASE_CONTINUATION_SYSTEM = `You are continuing work on a task that was not completed successfully.
|
||||||
|
Your previous attempt did not pass quality gates. You MUST fix the issues below.
|
||||||
|
|
||||||
|
CRITICAL RULES:
|
||||||
|
1. You MUST address EVERY failure listed
|
||||||
|
2. Do NOT defer work to future tasks
|
||||||
|
3. Do NOT claim done until all gates pass
|
||||||
|
4. Run tests before claiming completion
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const BASE_USER_PROMPT = `Task: {{taskDescription}}
|
||||||
|
|
||||||
|
Previous attempt {{attemptNumber}} of {{maxAttempts}} did not pass quality gates.
|
||||||
|
|
||||||
|
{{failures}}
|
||||||
|
|
||||||
|
{{constraints}}
|
||||||
|
`;
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export const BUILD_ERROR_TEMPLATE = `Build errors detected:
|
||||||
|
{{errors}}
|
||||||
|
|
||||||
|
Fix these TypeScript/compilation errors. Do not proceed until build passes.
|
||||||
|
|
||||||
|
Steps:
|
||||||
|
1. Read the error messages carefully
|
||||||
|
2. Fix type mismatches, missing imports, or syntax errors
|
||||||
|
3. Run build to verify it passes
|
||||||
|
`;
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
export const COVERAGE_TEMPLATE = `Test coverage is below required threshold.
|
||||||
|
|
||||||
|
Current coverage: {{currentCoverage}}%
|
||||||
|
Required coverage: {{requiredCoverage}}%
|
||||||
|
Gap: {{gap}}%
|
||||||
|
|
||||||
|
Files with insufficient coverage:
|
||||||
|
{{uncoveredFiles}}
|
||||||
|
|
||||||
|
Steps to improve coverage:
|
||||||
|
1. Identify uncovered code paths
|
||||||
|
2. Write tests for uncovered scenarios
|
||||||
|
3. Focus on edge cases and error handling
|
||||||
|
4. Run coverage report to verify improvement
|
||||||
|
`;
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
export const INCOMPLETE_WORK_TEMPLATE = `The task implementation is incomplete.
|
||||||
|
|
||||||
|
Issues detected:
|
||||||
|
{{issues}}
|
||||||
|
|
||||||
|
You MUST complete ALL aspects of the task. Do not leave TODO comments or deferred work.
|
||||||
|
|
||||||
|
Steps:
|
||||||
|
1. Review each incomplete item
|
||||||
|
2. Implement the missing functionality
|
||||||
|
3. Write tests for the new code
|
||||||
|
4. Verify all requirements are met
|
||||||
|
`;
|
||||||
5
apps/api/src/continuation-prompts/templates/index.ts
Normal file
5
apps/api/src/continuation-prompts/templates/index.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export * from "./base.template";
|
||||||
|
export * from "./test-failure.template";
|
||||||
|
export * from "./build-error.template";
|
||||||
|
export * from "./coverage.template";
|
||||||
|
export * from "./incomplete-work.template";
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
export const TEST_FAILURE_TEMPLATE = `The following tests are failing:
|
||||||
|
{{failures}}
|
||||||
|
|
||||||
|
For each failing test:
|
||||||
|
1. Read the test to understand what is expected
|
||||||
|
2. Fix the implementation to pass the test
|
||||||
|
3. Run the test to verify it passes
|
||||||
|
4. Do NOT skip or modify tests - fix the implementation
|
||||||
|
`;
|
||||||
Reference in New Issue
Block a user