mirror of
https://github.com/jparkerweb/plan2code.git
synced 2026-09-17 16:22:23 -07:00
c2579a7b6a
Bot now acts as an authentic QA agent instead of rubber-stamping every question and step. - intelligent-responder answers AskUserQuestion via LLM using current observations (tools used, files created, errors) instead of keyword matching; auto-responder removed. - evaluator runs after each step with step-specific criteria (prompts/evaluation-criteria.ts), produces 0-100 score plus strengths/weaknesses/critical issues. Avg <60 blocks finalization. - observation-collector captures tool_use, file writes, errors, and question reasoning; session-runner falls back to scraping tool_use blocks when canUseTool doesn't fire and dedupes both sources. - Bot writes BOT-EVALUATION.md and BOT-NOTES.md for metrics analysis. - Idea generator: 12 categories instead of CLI/web-app coin flip, stronger seed adherence, less developer-tool bias. - Init step now writes a minimal AGENTS.md stub instead of running the full init skill, avoiding hallucinated architecture before plan. - Implement step uses Read/Write/Edit/Glob/Grep directly instead of a Skill sub-session that produced no visible tool observations. - bin: add --help, accept --idea="value" form, strip surrounding quotes; evaluator maxTurns 3 -> 30; warn when parser misses SCORE.
114 lines
3.0 KiB
TypeScript
114 lines
3.0 KiB
TypeScript
import type { ExecutionObservation, StepName } from './types.js';
|
|
|
|
/**
|
|
* Collects detailed observations during step execution.
|
|
* Tracks tool usage, messages, errors, file changes, and questions asked.
|
|
*/
|
|
export class ObservationCollector {
|
|
private observations: ExecutionObservation;
|
|
private recentToolKeys: Set<string> = new Set();
|
|
|
|
constructor(step: StepName) {
|
|
this.observations = {
|
|
step,
|
|
startTime: Date.now(),
|
|
endTime: 0,
|
|
tools: [],
|
|
assistantMessages: [],
|
|
errors: [],
|
|
filesCreated: [],
|
|
filesModified: [],
|
|
questionsAsked: [],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Records a tool invocation with its input and output.
|
|
* Deduplicates if the same tool+input is recorded from both canUseTool and the message stream.
|
|
*/
|
|
recordToolUse(
|
|
toolName: string,
|
|
input: Record<string, unknown>,
|
|
output: unknown,
|
|
allowed: boolean
|
|
): void {
|
|
// Deduplicate based on tool name + serialized input (within a short time window)
|
|
const key = `${toolName}:${JSON.stringify(input)}`;
|
|
if (this.recentToolKeys.has(key)) {
|
|
return;
|
|
}
|
|
this.recentToolKeys.add(key);
|
|
// Clean up old keys periodically to avoid unbounded growth
|
|
if (this.recentToolKeys.size > 500) {
|
|
const entries = [...this.recentToolKeys];
|
|
this.recentToolKeys = new Set(entries.slice(entries.length - 250));
|
|
}
|
|
|
|
this.observations.tools.push({
|
|
toolName,
|
|
input,
|
|
output,
|
|
timestamp: Date.now(),
|
|
allowed,
|
|
autoAnswered: toolName === 'AskUserQuestion',
|
|
});
|
|
|
|
// Extract file paths from common tools
|
|
if (toolName === 'Write' && input.file_path) {
|
|
this.observations.filesCreated.push(input.file_path as string);
|
|
}
|
|
if (toolName === 'Edit' && input.file_path) {
|
|
this.observations.filesModified.push(input.file_path as string);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Records messages from the session (assistant text, errors).
|
|
*/
|
|
recordMessage(message: any): void {
|
|
if (message.type === 'assistant') {
|
|
for (const block of message.message.content) {
|
|
if (block.type === 'text') {
|
|
this.observations.assistantMessages.push(block.text);
|
|
}
|
|
}
|
|
}
|
|
if (message.type === 'error') {
|
|
this.observations.errors.push(message.error?.message ?? 'Unknown error');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Records a question that was asked and the LLM-generated answer.
|
|
*/
|
|
recordQuestion(
|
|
question: string,
|
|
options: Array<{ label: string; description: string }>,
|
|
selectedAnswer: string,
|
|
llmReasoning: string
|
|
): void {
|
|
this.observations.questionsAsked.push({
|
|
question,
|
|
options,
|
|
llmReasoning,
|
|
selectedAnswer,
|
|
timestamp: Date.now(),
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Gets a snapshot of current observations (for real-time decision making).
|
|
*/
|
|
getSnapshot(): ExecutionObservation {
|
|
return { ...this.observations, endTime: Date.now() };
|
|
}
|
|
|
|
/**
|
|
* Finalizes observations and returns the complete record.
|
|
*/
|
|
finalize(): ExecutionObservation {
|
|
this.observations.endTime = Date.now();
|
|
return { ...this.observations };
|
|
}
|
|
}
|