pi-harness-runtime 0.10.5 → 0.10.7
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/index.ts +51 -0
- package/package.json +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/config-capture/package.json +23 -0
- package/packages/config-capture/src/index.ts +357 -0
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/observability/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/todo-bd-sync/src/task-analyzer.ts +266 -0
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/packages/write-review/package.json +22 -0
- package/packages/write-review/src/blackboard.ts +245 -0
- package/packages/write-review/src/gate.ts +167 -0
- package/packages/write-review/src/index.ts +18 -0
- package/packages/write-review/src/injection.ts +157 -0
- package/packages/write-review/src/review.ts +201 -0
- package/packages/write-review/src/trigger.ts +130 -0
- package/packages/write-review/src/types.ts +88 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* No-Build Gate
|
|
3
|
+
*
|
|
4
|
+
* Blocks build commands until review is approved.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { WriteReviewBlackboard } from "./blackboard.js";
|
|
8
|
+
import type { WriteReviewStatus } from "./types.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Commands that trigger build gate
|
|
12
|
+
*/
|
|
13
|
+
const BUILD_COMMANDS = [
|
|
14
|
+
"bench build",
|
|
15
|
+
"npm run build",
|
|
16
|
+
"yarn build",
|
|
17
|
+
"pnpm build",
|
|
18
|
+
"bun run build",
|
|
19
|
+
"npm run dev",
|
|
20
|
+
"yarn dev",
|
|
21
|
+
"pnpm dev",
|
|
22
|
+
"./build",
|
|
23
|
+
"make build",
|
|
24
|
+
"gradle build",
|
|
25
|
+
"mvn package",
|
|
26
|
+
"dotnet build",
|
|
27
|
+
"cargo build",
|
|
28
|
+
"go build",
|
|
29
|
+
"python manage.py migrate",
|
|
30
|
+
"bench migrate",
|
|
31
|
+
"bench --site",
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check if a command is a build command
|
|
36
|
+
*/
|
|
37
|
+
export function isBuildCommand(command: string): boolean {
|
|
38
|
+
const lower = command.toLowerCase().trim();
|
|
39
|
+
return BUILD_COMMANDS.some((cmd) => lower.includes(cmd.toLowerCase()));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Gate result
|
|
44
|
+
*/
|
|
45
|
+
export interface GateResult {
|
|
46
|
+
allowed: boolean;
|
|
47
|
+
reason?: string;
|
|
48
|
+
currentPhase?: string;
|
|
49
|
+
message?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Check if build is allowed
|
|
54
|
+
*/
|
|
55
|
+
export function checkBuildGate(
|
|
56
|
+
command: string,
|
|
57
|
+
blackboard: WriteReviewBlackboard
|
|
58
|
+
): GateResult {
|
|
59
|
+
if (!isBuildCommand(command)) {
|
|
60
|
+
return { allowed: true };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const status = blackboard.load();
|
|
64
|
+
if (!status) {
|
|
65
|
+
// No review session - allow build
|
|
66
|
+
return { allowed: true };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (status.phase === "approved") {
|
|
70
|
+
return {
|
|
71
|
+
allowed: true,
|
|
72
|
+
currentPhase: status.phase,
|
|
73
|
+
message: "Build allowed - review approved.",
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
allowed: false,
|
|
79
|
+
reason: "REVIEW_NOT_APPROVED",
|
|
80
|
+
currentPhase: status.phase,
|
|
81
|
+
message: `Build blocked. Current review phase: ${status.phase}. Wait for "APPROVED" verdict.`,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Format gate rejection message
|
|
87
|
+
*/
|
|
88
|
+
export function formatGateRejection(result: GateResult): string {
|
|
89
|
+
if (result.allowed) {
|
|
90
|
+
return "";
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const lines = [
|
|
94
|
+
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
|
|
95
|
+
"⚠️ BUILD BLOCKED",
|
|
96
|
+
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
|
|
97
|
+
"",
|
|
98
|
+
`Current Phase: ${result.currentPhase}`,
|
|
99
|
+
"",
|
|
100
|
+
"The code is under review. Build is only allowed after APPROVED verdict.",
|
|
101
|
+
"",
|
|
102
|
+
"To check status:",
|
|
103
|
+
` cat {project}/.write-review/status.json`,
|
|
104
|
+
"",
|
|
105
|
+
"To wait for approval, do nothing. The reviewer will post the verdict.",
|
|
106
|
+
"",
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
if (result.currentPhase === "changes_requested") {
|
|
110
|
+
lines.push("Reviewer requested changes. Writer agent should update the code.");
|
|
111
|
+
} else if (result.currentPhase === "reviewing") {
|
|
112
|
+
lines.push("Review is in progress. Please wait...");
|
|
113
|
+
} else if (result.currentPhase === "pending_review") {
|
|
114
|
+
lines.push("Review not started yet. Waiting for reviewer...");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return lines.join("\n");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get phase emoji
|
|
122
|
+
*/
|
|
123
|
+
export function getPhaseEmoji(phase: string): string {
|
|
124
|
+
const emojis: Record<string, string> = {
|
|
125
|
+
idle: "💤",
|
|
126
|
+
writing: "✍️",
|
|
127
|
+
pending_review: "⏳",
|
|
128
|
+
reviewing: "🔍",
|
|
129
|
+
approved: "✅",
|
|
130
|
+
blocked: "🚫",
|
|
131
|
+
changes_requested: "🔄",
|
|
132
|
+
};
|
|
133
|
+
return emojis[phase] ?? "❓";
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Format status for display
|
|
138
|
+
*/
|
|
139
|
+
export function formatStatusDisplay(status: WriteReviewStatus): string {
|
|
140
|
+
const emoji = getPhaseEmoji(status.phase);
|
|
141
|
+
const lines = [
|
|
142
|
+
`${emoji} Write-Review Status`,
|
|
143
|
+
"",
|
|
144
|
+
`Phase: ${status.phase}`,
|
|
145
|
+
`Iteration: ${status.iteration}`,
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
if (status.verdict) {
|
|
149
|
+
lines.push(`Verdict: ${status.verdict}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (status.verdictMessage) {
|
|
153
|
+
lines.push("");
|
|
154
|
+
lines.push("Message:");
|
|
155
|
+
lines.push(status.verdictMessage);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (status.codeFiles && status.codeFiles.length > 0) {
|
|
159
|
+
lines.push("");
|
|
160
|
+
lines.push("Code Files:");
|
|
161
|
+
for (const file of status.codeFiles) {
|
|
162
|
+
lines.push(` - ${file}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return lines.join("\n");
|
|
167
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* write-review - Two-agent code writing with review loop
|
|
3
|
+
*
|
|
4
|
+
* Integrates with pi-harness-runtime via:
|
|
5
|
+
* - Smart trigger detection (prompt files in wiki/)
|
|
6
|
+
* - System prompt injection for writer agent
|
|
7
|
+
* - Subagent integration for reviewer agent
|
|
8
|
+
*
|
|
9
|
+
* Directory structure: {project}/.write-review/
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// Re-export all public APIs
|
|
13
|
+
export * from "./types.js";
|
|
14
|
+
export * from "./blackboard.js";
|
|
15
|
+
export * from "./trigger.js";
|
|
16
|
+
export * from "./injection.js";
|
|
17
|
+
export * from "./gate.js";
|
|
18
|
+
export * from "./review.js";
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System Prompt Injection
|
|
3
|
+
*
|
|
4
|
+
* Adds write-review hints to the agent's system prompt.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Write-review hint for system prompt
|
|
9
|
+
*/
|
|
10
|
+
export const WRITE_REVIEW_HINT = `
|
|
11
|
+
|
|
12
|
+
## Write-Review Loop
|
|
13
|
+
|
|
14
|
+
When reading a prompt from {project}/wiki/*:
|
|
15
|
+
|
|
16
|
+
1. You are the **WRITER** agent (Minimax). Read the wiki prompt and write code.
|
|
17
|
+
2. After writing code, write "## WRITER_DONE" to signal completion.
|
|
18
|
+
3. Do NOT build or commit until you receive "APPROVED" verdict.
|
|
19
|
+
4. If you receive "CHANGES_REQUESTED", update the code accordingly.
|
|
20
|
+
5. Acknowledge each verdict in your response.
|
|
21
|
+
|
|
22
|
+
### Blackboard Path
|
|
23
|
+
{p}/.write-review/status.json
|
|
24
|
+
|
|
25
|
+
### Verdict Types
|
|
26
|
+
- **APPROVED**: Code is ready. You may build/commit.
|
|
27
|
+
- **CHANGES_REQUESTED**: Reviewer found issues. Update code.
|
|
28
|
+
- **BLOCKED**: Critical issues. Human intervention needed.
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Helper agent hint (for worker agent)
|
|
33
|
+
*/
|
|
34
|
+
export const HELPER_HINT = `
|
|
35
|
+
|
|
36
|
+
## Helper Agent Task
|
|
37
|
+
|
|
38
|
+
The writer agent is busy or needs assistance. Your task:
|
|
39
|
+
|
|
40
|
+
1. Note any pending tasks to the Todos list using the todo tool.
|
|
41
|
+
2. Review the changes requested and prepare notes for the writer.
|
|
42
|
+
3. Do NOT write code directly - that's the writer's job.
|
|
43
|
+
4. Write a summary of pending tasks to the blackboard.
|
|
44
|
+
`;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Review reminder hint
|
|
48
|
+
*/
|
|
49
|
+
export const REVIEW_REMINDER_HINT = `
|
|
50
|
+
|
|
51
|
+
## Review Reminder
|
|
52
|
+
|
|
53
|
+
Before committing or building, ALWAYS check the write-review status:
|
|
54
|
+
|
|
55
|
+
\`\`\`bash
|
|
56
|
+
cat {project}/.write-review/status.json
|
|
57
|
+
\`\`\`
|
|
58
|
+
|
|
59
|
+
Only build/commit if the status shows "approved" or "phase": "approved".
|
|
60
|
+
`;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Format hint with project path
|
|
64
|
+
*/
|
|
65
|
+
export function formatWriteReviewHint(projectPath: string): string {
|
|
66
|
+
return WRITE_REVIEW_HINT.replace("{project}", projectPath).replace("{p}", projectPath);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Format review reminder with project path
|
|
71
|
+
*/
|
|
72
|
+
export function formatReviewReminder(projectPath: string): string {
|
|
73
|
+
return REVIEW_REMINDER_HINT.replace("{project}", projectPath);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get all hints to inject
|
|
78
|
+
*/
|
|
79
|
+
export function getWriteReviewHints(projectPath: string): {
|
|
80
|
+
writeHint: string;
|
|
81
|
+
reminderHint: string;
|
|
82
|
+
} {
|
|
83
|
+
return {
|
|
84
|
+
writeHint: formatWriteReviewHint(projectPath),
|
|
85
|
+
reminderHint: formatReviewReminder(projectPath),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Register write-review extension with pi
|
|
91
|
+
*/
|
|
92
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
93
|
+
import { createBlackboard } from "./blackboard.js";
|
|
94
|
+
|
|
95
|
+
export function injectWriterInstructions(
|
|
96
|
+
pi: ExtensionAPI,
|
|
97
|
+
config?: { debug?: boolean }
|
|
98
|
+
): void {
|
|
99
|
+
let currentProject: string | null = null;
|
|
100
|
+
let blackboard: ReturnType<typeof createBlackboard> | null = null;
|
|
101
|
+
let hintsInjected = false;
|
|
102
|
+
|
|
103
|
+
// System prompt injection
|
|
104
|
+
pi.on("before_agent_start", async (event) => {
|
|
105
|
+
const cwd = process.cwd?.() ?? "/";
|
|
106
|
+
if (cwd !== currentProject) {
|
|
107
|
+
currentProject = cwd;
|
|
108
|
+
blackboard = createBlackboard(cwd);
|
|
109
|
+
hintsInjected = false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!hintsInjected && blackboard) {
|
|
113
|
+
const status = blackboard.load();
|
|
114
|
+
if (status && status.phase !== "idle") {
|
|
115
|
+
event.systemPrompt += formatWriteReviewHint(cwd);
|
|
116
|
+
if (status.phase !== "approved") {
|
|
117
|
+
event.systemPrompt += formatReviewReminder(cwd);
|
|
118
|
+
}
|
|
119
|
+
hintsInjected = true;
|
|
120
|
+
if (config?.debug) {
|
|
121
|
+
console.log(`[write-review] Hints injected for ${cwd}, phase: ${status.phase}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Build gate
|
|
128
|
+
pi.on("tool_execution_end", async (event) => {
|
|
129
|
+
const toolName = (event as { tool?: { name?: string } }).tool?.name ?? "";
|
|
130
|
+
if (toolName !== "bash") return;
|
|
131
|
+
|
|
132
|
+
const result = event as { result?: { content?: string } };
|
|
133
|
+
const content = result.result?.content ?? "";
|
|
134
|
+
|
|
135
|
+
// Check for build commands
|
|
136
|
+
const buildCommands = [
|
|
137
|
+
"bench build", "npm run build", "yarn build",
|
|
138
|
+
"pnpm build", "bun run build", "make build",
|
|
139
|
+
"gradle build", "dotnet build", "cargo build",
|
|
140
|
+
];
|
|
141
|
+
const isBuild = buildCommands.some(cmd => content.includes(cmd));
|
|
142
|
+
|
|
143
|
+
if (isBuild && blackboard) {
|
|
144
|
+
const status = blackboard.load();
|
|
145
|
+
if (status && status.phase !== "approved") {
|
|
146
|
+
result.result!.content = content + "\n\n" +
|
|
147
|
+
"⚠️ BUILD BLOCKED - Review not approved yet.\n" +
|
|
148
|
+
`Current phase: ${status.phase}\n` +
|
|
149
|
+
"Wait for APPROVED verdict before building.";
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
if (config?.debug) {
|
|
155
|
+
console.log("[write-review] Extension registered");
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review Integration
|
|
3
|
+
*
|
|
4
|
+
* Uses pi-subagents review-loop for multi-round code review.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { WriteReviewStatus } from "./types.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Review round result
|
|
11
|
+
*/
|
|
12
|
+
export interface ReviewResult {
|
|
13
|
+
verdict: "approved" | "changes_requested" | "blocked";
|
|
14
|
+
iteration: number;
|
|
15
|
+
message: string;
|
|
16
|
+
changesNeeded?: string[];
|
|
17
|
+
issues?: ReviewIssue[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Issue found during review
|
|
22
|
+
*/
|
|
23
|
+
export interface ReviewIssue {
|
|
24
|
+
severity: "blocker" | "major" | "minor" | "suggestion";
|
|
25
|
+
type: "correctness" | "security" | "performance" | "style" | "test" | "docs";
|
|
26
|
+
location: string;
|
|
27
|
+
description: string;
|
|
28
|
+
suggestion?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Build review task for pi-subagents
|
|
33
|
+
*/
|
|
34
|
+
export function buildReviewTask(
|
|
35
|
+
status: WriteReviewStatus,
|
|
36
|
+
projectPath: string
|
|
37
|
+
): string {
|
|
38
|
+
const codeFiles = status.codeFiles ?? [];
|
|
39
|
+
const iteration = status.iteration;
|
|
40
|
+
|
|
41
|
+
const task = `## Review Task - Iteration ${iteration}
|
|
42
|
+
|
|
43
|
+
Review the code written in this iteration.
|
|
44
|
+
|
|
45
|
+
### Code Files to Review:
|
|
46
|
+
${codeFiles.map((f) => `- ${f}`).join("\n") || "No files recorded yet."}
|
|
47
|
+
|
|
48
|
+
### Project Path: ${projectPath}
|
|
49
|
+
|
|
50
|
+
### Review Instructions:
|
|
51
|
+
1. Read the code files listed above.
|
|
52
|
+
2. Run the review-loop workflow using pi-subagents.
|
|
53
|
+
3. Focus on: correctness, security, performance, tests, and code style.
|
|
54
|
+
4. Return a verdict: APPROVED, CHANGES_REQUESTED, or BLOCKED.
|
|
55
|
+
|
|
56
|
+
### Verdict Format:
|
|
57
|
+
\`\`\`
|
|
58
|
+
## Verdict: [APPROVED|CHANGES_REQUESTED|BLOCKED]
|
|
59
|
+
[Your detailed review message]
|
|
60
|
+
\`\`\`
|
|
61
|
+
|
|
62
|
+
For CHANGES_REQUESTED, list specific changes needed:
|
|
63
|
+
\`\`\`
|
|
64
|
+
## Changes Needed:
|
|
65
|
+
1. [Issue description] - [File:Line]
|
|
66
|
+
2. [Issue description] - [File:Line]
|
|
67
|
+
\`\`\`
|
|
68
|
+
`;
|
|
69
|
+
|
|
70
|
+
return task;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Parse verdict from review output
|
|
75
|
+
*/
|
|
76
|
+
export function parseVerdict(output: string): {
|
|
77
|
+
verdict: "approved" | "changes_requested" | "blocked";
|
|
78
|
+
message: string;
|
|
79
|
+
changes?: string[];
|
|
80
|
+
} | null {
|
|
81
|
+
const lines = output.split("\n");
|
|
82
|
+
let verdict: "approved" | "changes_requested" | "blocked" | null = null;
|
|
83
|
+
let message = "";
|
|
84
|
+
const changes: string[] = [];
|
|
85
|
+
let inChanges = false;
|
|
86
|
+
|
|
87
|
+
for (const line of lines) {
|
|
88
|
+
const lower = line.toLowerCase();
|
|
89
|
+
|
|
90
|
+
if (lower.includes("## verdict:")) {
|
|
91
|
+
if (lower.includes("approved")) {
|
|
92
|
+
verdict = "approved";
|
|
93
|
+
} else if (lower.includes("changes_requested") || lower.includes("changes requested")) {
|
|
94
|
+
verdict = "changes_requested";
|
|
95
|
+
} else if (lower.includes("blocked")) {
|
|
96
|
+
verdict = "blocked";
|
|
97
|
+
}
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (lower.includes("## changes needed:") || lower.includes("changes:")) {
|
|
102
|
+
inChanges = true;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (inChanges && line.trim().match(/^\d+\./)) {
|
|
107
|
+
changes.push(line.trim());
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (verdict && line.trim()) {
|
|
112
|
+
message += line + "\n";
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!verdict) return null;
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
verdict,
|
|
120
|
+
message: message.trim(),
|
|
121
|
+
changes: changes.length > 0 ? changes : undefined,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Review angles for parallel review
|
|
127
|
+
*/
|
|
128
|
+
export const REVIEW_ANGLES = [
|
|
129
|
+
{
|
|
130
|
+
name: "correctness",
|
|
131
|
+
description: "Verify implementation matches requirements and handles edge cases",
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
name: "tests",
|
|
135
|
+
description: "Check test coverage and validity",
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: "complexity",
|
|
139
|
+
description: "Look for unnecessary complexity and opportunities to simplify",
|
|
140
|
+
},
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Build parallel review prompt
|
|
145
|
+
*/
|
|
146
|
+
export function buildParallelReviewPrompt(
|
|
147
|
+
codeFiles: string[],
|
|
148
|
+
projectPath: string
|
|
149
|
+
): string {
|
|
150
|
+
return `Run parallel reviewers on this code:
|
|
151
|
+
|
|
152
|
+
1. **correctness** - Verify implementation matches requirements and handles edge cases
|
|
153
|
+
2. **tests** - Check test coverage and validity
|
|
154
|
+
3. **complexity** - Look for unnecessary complexity
|
|
155
|
+
|
|
156
|
+
Files: ${codeFiles.join(", ")}
|
|
157
|
+
Project: ${projectPath}
|
|
158
|
+
|
|
159
|
+
Use pi-subagents to spawn 3 parallel reviewers. Wait for all results, then synthesize into a final verdict.`;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Format review summary for blackboard
|
|
164
|
+
*/
|
|
165
|
+
export function formatReviewSummary(result: ReviewResult): string {
|
|
166
|
+
const lines = [
|
|
167
|
+
`## Review Result - Iteration ${result.iteration}`,
|
|
168
|
+
"",
|
|
169
|
+
`**Verdict:** ${result.verdict.toUpperCase()}`,
|
|
170
|
+
"",
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
if (result.message) {
|
|
174
|
+
lines.push("### Message");
|
|
175
|
+
lines.push(result.message);
|
|
176
|
+
lines.push("");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (result.changesNeeded && result.changesNeeded.length > 0) {
|
|
180
|
+
lines.push("### Changes Needed");
|
|
181
|
+
for (const change of result.changesNeeded) {
|
|
182
|
+
lines.push(`- ${change}`);
|
|
183
|
+
}
|
|
184
|
+
lines.push("");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (result.issues && result.issues.length > 0) {
|
|
188
|
+
lines.push("### Issues Found");
|
|
189
|
+
for (const issue of result.issues) {
|
|
190
|
+
lines.push(`- [${issue.severity}] ${issue.type}: ${issue.description}`);
|
|
191
|
+
if (issue.location) {
|
|
192
|
+
lines.push(` Location: ${issue.location}`);
|
|
193
|
+
}
|
|
194
|
+
if (issue.suggestion) {
|
|
195
|
+
lines.push(` Suggestion: ${issue.suggestion}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return lines.join("\n");
|
|
201
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wiki Prompt Trigger
|
|
3
|
+
*
|
|
4
|
+
* Detects when an agent reads a prompt from {project}/wiki/*
|
|
5
|
+
* This triggers the write-review loop.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync } from "fs";
|
|
9
|
+
import { join, relative } from "path";
|
|
10
|
+
import type { ReviewTriggerContext } from "./types.js";
|
|
11
|
+
|
|
12
|
+
const DEFAULT_WIKI_DIR = "wiki";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Detect if a file path is a wiki prompt
|
|
16
|
+
*/
|
|
17
|
+
export function isWikiPrompt(filePath: string, projectPath: string): boolean {
|
|
18
|
+
const wikiDir = join(projectPath, DEFAULT_WIKI_DIR);
|
|
19
|
+
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
20
|
+
const normalizedWiki = wikiDir.replace(/\\/g, "/");
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
normalizedPath.startsWith(normalizedWiki + "/") ||
|
|
24
|
+
normalizedPath.startsWith(normalizedWiki + "\\")
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Extract prompt metadata from wiki path
|
|
30
|
+
*/
|
|
31
|
+
export function parseWikiPrompt(
|
|
32
|
+
filePath: string,
|
|
33
|
+
projectPath: string
|
|
34
|
+
): { category: string; filename: string } | null {
|
|
35
|
+
const wikiDir = join(projectPath, DEFAULT_WIKI_DIR);
|
|
36
|
+
const rel = relative(wikiDir, filePath).replace(/\\/g, "/");
|
|
37
|
+
|
|
38
|
+
if (!rel || rel.startsWith("..")) return null;
|
|
39
|
+
|
|
40
|
+
const parts = rel.split("/");
|
|
41
|
+
if (parts.length >= 2) {
|
|
42
|
+
return {
|
|
43
|
+
category: parts[0],
|
|
44
|
+
filename: parts.slice(1).join("/"),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
category: "root",
|
|
49
|
+
filename: parts[0],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get all wiki prompt files in a project
|
|
55
|
+
*/
|
|
56
|
+
export function getWikiPrompts(
|
|
57
|
+
projectPath: string
|
|
58
|
+
): { path: string; category: string; filename: string }[] {
|
|
59
|
+
const wikiDir = join(projectPath, DEFAULT_WIKI_DIR);
|
|
60
|
+
|
|
61
|
+
if (!existsSync(wikiDir)) {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const prompts: { path: string; category: string; filename: string }[] = [];
|
|
66
|
+
|
|
67
|
+
function scanDir(dir: string): void {
|
|
68
|
+
const { readdirSync, statSync } = require("fs");
|
|
69
|
+
try {
|
|
70
|
+
for (const entry of readdirSync(dir)) {
|
|
71
|
+
const fullPath = join(dir, entry);
|
|
72
|
+
const stat = statSync(fullPath);
|
|
73
|
+
if (stat.isDirectory()) {
|
|
74
|
+
scanDir(fullPath);
|
|
75
|
+
} else if (stat.isFile() && (entry.endsWith(".md") || entry.endsWith(".txt"))) {
|
|
76
|
+
const relPath = relative(wikiDir, fullPath).replace(/\\/g, "/");
|
|
77
|
+
const parts = relPath.split("/");
|
|
78
|
+
prompts.push({
|
|
79
|
+
path: fullPath,
|
|
80
|
+
category: parts[0],
|
|
81
|
+
filename: parts.slice(1).join("/"),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} catch {
|
|
86
|
+
// Ignore permission errors
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
scanDir(wikiDir);
|
|
91
|
+
return prompts;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Create trigger context from a wiki read event
|
|
96
|
+
*/
|
|
97
|
+
export function createTriggerContext(
|
|
98
|
+
filePath: string,
|
|
99
|
+
projectPath: string,
|
|
100
|
+
triggerType: ReviewTriggerContext["triggerType"] = "wiki_read"
|
|
101
|
+
): ReviewTriggerContext | null {
|
|
102
|
+
if (!isWikiPrompt(filePath, projectPath)) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const parsed = parseWikiPrompt(filePath, projectPath);
|
|
107
|
+
if (!parsed) return null;
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
projectPath,
|
|
111
|
+
promptFile: filePath,
|
|
112
|
+
promptContent: "", // Will be filled by caller
|
|
113
|
+
triggerType,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Check if current working directory has wiki prompts
|
|
119
|
+
*/
|
|
120
|
+
export function hasWikiPrompts(projectPath: string): boolean {
|
|
121
|
+
const wikiDir = join(projectPath, DEFAULT_WIKI_DIR);
|
|
122
|
+
return existsSync(wikiDir);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get wiki directory path
|
|
127
|
+
*/
|
|
128
|
+
export function getWikiDir(projectPath: string): string {
|
|
129
|
+
return join(projectPath, DEFAULT_WIKI_DIR);
|
|
130
|
+
}
|