This commit is contained in:
cghislai 2025-06-08 13:27:23 +02:00
parent cf23a8ba97
commit d1cebaca1a
11 changed files with 244 additions and 195 deletions

View File

@ -206,7 +206,12 @@ export class ProcessorService {
}
}
// Commit and push changes if any workitems were updated
if (USE_LOCAL_REPO) {
console.log('Skipping commit and push changes to main repository: Using local repository');
return;
}
// Commit and push changes if any qworkitems were updated
if (updatedAnyWorkitem) {
console.log('Committing changes to workitem files...');
await this.sharedRepositoryService.commitChanges(

View File

@ -199,13 +199,14 @@ export class ProjectWorkitemsService {
guidelinePaths
.map(g => g.trim())
.forEach(fileName => {
console.debug("Collected guideline file: " + fileName);
const filePath = path.join(projectRepoPath, fileName);
if (fs.existsSync(filePath)) {
relevantFiles[fileName] = fs.readFileSync(filePath, 'utf-8');
}
});
console.log(`ProjectWorkitemsService: Collected ${Object.keys(relevantFiles).length} relevant files for workitem ${workitem.name}`);
console.log(`ProjectWorkitemsService: Collected ${Object.keys(relevantFiles).length} guideline files for workitem ${workitem.name}`);
} catch (error) {
console.error(`Error collecting relevant files for workitem ${workitem.name}:`, error);
}
@ -270,10 +271,16 @@ export class ProjectWorkitemsService {
DRY_RUN_SKIP_GEMINI
);
const workItemPrompt = `\n`
+ `---\n`
+ `Here is the work item prompt: ${workitemName}\n`
+ `${workitemContent}\n`
+ `---\n`;
// Process the model stream
const result = await geminiFileSystemService.processModelStream(
guidelines,
workitemContent,
workItemPrompt,
projectRepoPath
);

View File

@ -20,7 +20,7 @@ export interface FunctionArgs {
dirPath?: string;
searchString?: string;
filePattern?: string;
decision?: 'create' | 'update' | 'delete' | 'skip';
outcome?: 'create' | 'update' | 'delete' | 'skip';
reason?: string;
}
@ -144,7 +144,7 @@ export class GeminiFileSystemService {
properties: {
searchString: {
type: FunctionDeclarationSchemaType.STRING,
description: "String to search for in project files"
description: "String to search for in project files (case sensitive)"
},
filePattern: {
type: FunctionDeclarationSchemaType.STRING,
@ -169,22 +169,22 @@ export class GeminiFileSystemService {
}
},
{
name: "makeDecision",
description: "State your decision about implementing the workitem",
name: "reportFinalOutcome",
description: "Submit the final outcome for compliance with guidelines. Can only be called once.",
parameters: {
type: FunctionDeclarationSchemaType.OBJECT,
properties: {
decision: {
outcome: {
type: FunctionDeclarationSchemaType.STRING,
description: "Your decision: 'create', 'update', 'delete', or 'skip'",
description: "The final outcome: 'create', 'update', 'delete', or 'skip'",
enum: ["create", "update", "delete", "skip"]
},
reason: {
type: FunctionDeclarationSchemaType.STRING,
description: "Reason for your decision"
description: "Reason for this outcome. For instance, 'create' when files have been created, 'skip' when no files has been created, or 'update' when files have been updated."
}
},
required: ["decision", "reason"]
required: ["outcome", "reason"]
}
}
]
@ -322,7 +322,7 @@ export class GeminiFileSystemService {
}
} else if (entry.isFile()) {
// Check if the file matches the pattern
if (!filePattern || this.matchesPattern(entry.name, filePattern)) {
if (!filePattern || this.matchesPattern(relativePath, filePattern)) {
searchInFile(fullPath, relativePath);
}
}
@ -335,6 +335,7 @@ export class GeminiFileSystemService {
// Start the search from the root path
searchInDirectory(rootPath, rootPath);
console.debug(`Search returned ${results.length} results`)
return results;
}
@ -387,8 +388,12 @@ export class GeminiFileSystemService {
// Create the prompt
const prompt = `
Here is your guideline:
${guidelines}
Additional content:
${additionalContent}
You have access to the following function calls to help you understand the project structure and create implementations:
@ -398,11 +403,13 @@ You have access to the following function calls to help you understand the proje
- listFiles(dirPath): List files in a directory in the project repository
- grepFiles(searchString, filePattern): Search for a string in project files, optionally filtered by a file pattern
- deleteFile(filePath): Delete a file from the project repository
- makeDecision(decision, reason): State your decision about implementing the workitem. Decision must be one of: 'create', 'update', 'delete', 'skip'
IMPORTANT!!: First use the function calls above to actually implement the workitem. Make all necessary function calls to fully implement the workitem.
IMPORTANT: First use the function calls above to comply with the guidelines. Create, update, or delete all required files.
After you have implemented the workitem using function calls, use the makeDecision function to state your final decision with a reason.
Then, once finished with all the guidelines above, use this function once to report the overall outcome:
- reportFinalOutcome(outcome, reason): Outcome must be one of: 'create', 'update', 'delete', 'skip'
You won't be able to update other files once you've made a decision.
`;
// Instantiate the model with our file operation tools
@ -410,9 +417,9 @@ After you have implemented the workitem using function calls, use the makeDecisi
model: this.model,
tools: this.fileOperationTools,
generation_config: {
temperature: 0.1, // Very low temperature for more deterministic responses
top_p: 0.95, // Higher top_p to allow more diverse completions when needed
top_k: 40, // Consider only the top 40 tokens
temperature: 0.3, // Very low temperature for more deterministic responses
top_p: 0.8, // Higher top_p to allow more diverse completions when needed
top_k: 60, // Consider only the top 40 tokens
},
});
@ -498,14 +505,14 @@ After you have implemented the workitem using function calls, use the makeDecisi
// Track the file deleted
filesDeleted.push(functionArgs.filePath!);
break;
case 'makeDecision':
console.debug(`- received makeDecision function call: ${functionArgs.decision} - ${functionArgs.reason}`);
case 'reportFinalOutcome':
console.debug(`- received reportFinalOutcome function call: ${functionArgs.outcome} - ${functionArgs.reason}`);
// Store the decision
decision = {
decision: functionArgs.decision!,
decision: functionArgs.outcome!,
reason: functionArgs.reason!
};
functionResponse = `Decision recorded: ${functionArgs.decision} - ${functionArgs.reason}`;
functionResponse = `Outcome recorded: ${functionArgs.outcome} - ${functionArgs.reason}`;
break;
default:
throw new Error(`Unknown function: ${functionName}`);
@ -532,6 +539,10 @@ After you have implemented the workitem using function calls, use the makeDecisi
modelResponses.push(nextResult.textContent);
}
if (nextResult.functionCall) {
if (decision != null) {
console.warn(`Received another function call for ${nextResult.functionCall.name}, but a decision hsa been recorded. Ignoring stream`);
break;
}
pendingFunctionCalls.push(nextResult.functionCall);
}
@ -560,14 +571,19 @@ After you have implemented the workitem using function calls, use the makeDecisi
modelResponses.push(nextResult.textContent);
}
if (nextResult.functionCall) {
if (decision != null) {
console.warn(`Received another function call for ${nextResult.functionCall.name}, but a decision hsa been recorded. Ignoring stream`);
break;
}
pendingFunctionCalls.push(nextResult.functionCall);
}
}
}
}
// If no explicit decision was made using the makeDecision function, try to parse it from the text
// If no explicit decision was made using the reportFinalOutcome function, try to parse it from the text
if (!decision) {
console.warn(`No decision function call made during the stream session`);
try {
// Try to parse a JSON decision from the text
const jsonMatch = finalResponse.match(/\{[\s\S]*"decision"[\s\S]*\}/);
@ -579,11 +595,11 @@ After you have implemented the workitem using function calls, use the makeDecisi
}
}
console.debug(`- Completed gemini stream processing. Final response: ${decision}`);
console.debug(`- Completed gemini stream processing. Final response: ${decision?.decision} - ${decision?.reason}`);
return {
text: finalResponse,
decision: decision,
decision: decision ?? {decision: "skip", reason: "No decision received/parsed"},
modelResponses: modelResponses,
filesWritten: filesWritten,
filesDeleted: filesDeleted

View File

@ -93,15 +93,12 @@ ${gitPatch}
You are tasked with creating a pull request description for changes to test specifications.
The following is a summary of the changes made:
${description}
${gitPatch && gitPatch !== "No changes detected." ? gitPatchSection : ''}
Create a clear, professional pull request description that:
1. Explains that this PR was automatically generated
2. Summarizes the changes (added, updated, deleted, and failed workitems)
3. If code changes are provided, analyze them and include a summary of the key changes
4. Uses markdown formatting for better readability
5. Keeps the description concise but informative
Keeps the description concise but informative
The pull request description should be ready to use without further editing.
`;

View File

@ -32,7 +32,7 @@ export class ProjectTestSpecsService {
// Read project guidelines
const projectGuidelines = await this.projectService.readProjectGuidelines(project.path);
const result = await this.processTestSpec(project, projectRepoPath, projectGuidelines);
const result = await this.generateTestSpecs(project, projectRepoPath, projectGuidelines);
// Generate git patch if any files were written
let gitPatch: string | undefined = undefined;
@ -64,11 +64,10 @@ export class ProjectTestSpecsService {
* Process a test spec using Gemini
* @param project Project containing the test spec
* @param projectRepoPath Path to the project repository
* @param testSpec Test spec to process
* @param projectGuidelines Project guidelines
* @returns Result of the processing
*/
private async processTestSpec(
private async generateTestSpecs(
project: Project,
projectRepoPath: string,
projectGuidelines: string
@ -78,7 +77,7 @@ export class ProjectTestSpecsService {
const relevantFiles = await this.collectRelevantFiles(project, projectRepoPath);
// Let Gemini generate the implementation
const result = await this.generateImplementations(
const result = await this.generateAllTestSpecs(
projectRepoPath,
projectGuidelines,
relevantFiles
@ -133,6 +132,7 @@ export class ProjectTestSpecsService {
guidelinePaths
.map(g => g.trim())
.forEach(fileName => {
console.debug("Collected guideline file: " + fileName);
const filePath = path.join(projectRepoPath, fileName);
if (fs.existsSync(filePath)) {
relevantFiles[fileName] = fs.readFileSync(filePath, 'utf-8');
@ -151,11 +151,10 @@ export class ProjectTestSpecsService {
* Generate implementation using Gemini API
* @param projectRepoPath Path to the project repository
* @param guidelines Project guidelines
* @param testSpec Test spec to implement
* @param relevantFiles Additional relevant files to include in the prompt
* @returns Object containing the generated text, parsed decision, and files written/deleted
*/
private async generateImplementations(
private async generateAllTestSpecs(
projectRepoPath: string,
guidelines: string,
relevantFiles: Record<string, string> = {}

View File

@ -5,5 +5,5 @@ Nitro backend server in quarkus
- [x] Repo host: https://gitea.fteamdev.valuya.be/
- [x] Repo url: https://gitea.fteamdev.valuya.be/cghislai/nitro-back.git
- [x] Target branch: main
- [ ] AI guidelines:
- [x] AI guidelines: nitro-it/src/test/resources/workitems/AI_DEFINITION.md
- [x] Jira component: nitro

View File

@ -0,0 +1,23 @@
## Document archiving
Nitro admins want to be able to archive documents in every status. Once the document reaches the
status ARCHIVED, it cannot be COMPLETED afterwards.
When a document is archived using the dedicated endpoint, its status should be set ARCHIVED directly.
When a document in the status TO_EXPORT is archived, and that an export was in progress at that time,
the export should complete, but the document status must not change and the document must not be
set problematic once the export completes.
Only users that are superAdmins may archive documents.
- [ ] Jira: NITRO-0003
- [ ] Implementation:
- [ ] Pull Request:
- [x] Active
### Log
2025-06-08T09:58:06.287Z - Workitem has been implemented.
- Created nitro-it/src/test/resources/workitems/2025-06-08-document-archvigin.feature

View File

@ -11,5 +11,10 @@ The nitro-back backend should have a /test endpoint implemented returning the js
### Log
2025-06-08T09:58:26.902Z - Workitem has been updated.
- Created nitro-it/src/test/resources/workitems/test_workitem.feature
PR: https://gitea.fteamdev.valuya.be/cghislai/nitro-back/pulls/1
2025-06-08T07:36:00.901Z - Workitem has been implemented.
- Created nitro-it/src/test/resources/workitems/test_workitem.feature

View File

@ -1,7 +1,4 @@
## Test spec implementation
- Iterate over cucumber feature definitions in the `nitro-it/src/test/resources/workitems/` folder.
- For each of them, a corresponding test implementation should be created if it does not exist.
- Test implementations should be created in the `nitro-it/src/test/java/be/fiscalteam/nitro/bdd` folder, following the
same structure as the feature definition files. One test file per feature definition.
This is your guideline for the implementation of the feature file:
- Iterate over cucumber ".feature" definition files in the `nitro-it/src/test/resources/workitems/` directory.
- For each of them create all required files to implement the feature.

View File

@ -5,5 +5,5 @@ Nitro backend server in quarkus
- [x] Repo host: https://gitea.fteamdev.valuya.be/
- [x] Repo url: https://gitea.fteamdev.valuya.be/cghislai/nitro-back.git
- [x] Target branch: main
- [ ] AI guidelines:
- [x] AI guidelines: nitro-it/src/test/resources/workitems/AI_IMPLEMENTATION.md
- [x] Jira component: nitro