pi-harness-runtime 0.10.6 → 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 +27 -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 +1 -1
- 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/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,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
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Write-Review Loop Types
|
|
3
|
+
*
|
|
4
|
+
* State machine for two-agent write-review coordination.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Review phases
|
|
9
|
+
*/
|
|
10
|
+
export type ReviewPhase =
|
|
11
|
+
| "idle"
|
|
12
|
+
| "writing"
|
|
13
|
+
| "pending_review"
|
|
14
|
+
| "reviewing"
|
|
15
|
+
| "approved"
|
|
16
|
+
| "blocked"
|
|
17
|
+
| "changes_requested";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Review verdict
|
|
21
|
+
*/
|
|
22
|
+
export type Verdict = "approved" | "changes_requested" | "blocked";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Blackboard status record
|
|
26
|
+
*/
|
|
27
|
+
export interface WriteReviewStatus {
|
|
28
|
+
projectPath: string;
|
|
29
|
+
phase: ReviewPhase;
|
|
30
|
+
writerDone: boolean;
|
|
31
|
+
writerMessage?: string;
|
|
32
|
+
iteration: number;
|
|
33
|
+
verdict?: Verdict;
|
|
34
|
+
verdictMessage?: string;
|
|
35
|
+
reviewerStarted?: string; // ISO timestamp
|
|
36
|
+
codeFiles?: string[]; // Files written
|
|
37
|
+
changesRequested?: string[]; // Changes needed
|
|
38
|
+
approvedAt?: string; // ISO timestamp
|
|
39
|
+
blockedAt?: string; // ISO timestamp
|
|
40
|
+
updatedAt: string;
|
|
41
|
+
createdAt: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Review trigger context
|
|
46
|
+
*/
|
|
47
|
+
export interface ReviewTriggerContext {
|
|
48
|
+
projectPath: string;
|
|
49
|
+
promptFile: string;
|
|
50
|
+
promptContent: string;
|
|
51
|
+
triggerType: "wiki_read" | "manual" | "code_written";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Extension config
|
|
56
|
+
*/
|
|
57
|
+
export interface WriteReviewConfig {
|
|
58
|
+
debug?: boolean;
|
|
59
|
+
blackboardDir?: string; // Default: "{project}/.write-review"
|
|
60
|
+
wikiDir?: string; // Default: "{project}/wiki"
|
|
61
|
+
enabled?: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Agent roles
|
|
66
|
+
*/
|
|
67
|
+
export const AGENT_ROLES = {
|
|
68
|
+
WRITER: "writer", // Minimax - reads wiki, writes code
|
|
69
|
+
REVIEWER: "reviewer", // Uses pi-subagents review-loop
|
|
70
|
+
HELPER: "helper", // worker - notes tasks to todo
|
|
71
|
+
} as const;
|
|
72
|
+
|
|
73
|
+
export type AgentRole = typeof AGENT_ROLES[keyof typeof AGENT_ROLES];
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* State transition helpers
|
|
77
|
+
*/
|
|
78
|
+
export function isTerminalPhase(phase: ReviewPhase): boolean {
|
|
79
|
+
return phase === "approved" || phase === "blocked";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function needsReview(phase: ReviewPhase): boolean {
|
|
83
|
+
return phase === "pending_review" || phase === "reviewing";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function canWrite(phase: ReviewPhase): boolean {
|
|
87
|
+
return phase === "idle" || phase === "writing" || phase === "changes_requested";
|
|
88
|
+
}
|