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.
Files changed (87) hide show
  1. package/index.ts +27 -0
  2. package/package.json +1 -1
  3. package/packages/a2a-adapter/package.json +1 -1
  4. package/packages/auth/package.json +1 -1
  5. package/packages/autonomous-refactor/package.json +1 -1
  6. package/packages/autonomous-runtime/package.json +1 -1
  7. package/packages/cache-strategy/package.json +1 -1
  8. package/packages/capability-registry/package.json +1 -1
  9. package/packages/checkpoint/package.json +1 -1
  10. package/packages/cli-plugin-sdk/package.json +1 -1
  11. package/packages/clipboard/package.json +1 -1
  12. package/packages/code-generation/package.json +1 -1
  13. package/packages/code-review/package.json +1 -1
  14. package/packages/codex-adapter/package.json +1 -1
  15. package/packages/config-capture/package.json +1 -1
  16. package/packages/context-compiler/package.json +1 -1
  17. package/packages/context-discovery/package.json +1 -1
  18. package/packages/context-manager/package.json +1 -1
  19. package/packages/cookie-sanitizer/package.json +1 -1
  20. package/packages/cost-optimizer/package.json +1 -1
  21. package/packages/dependency-analyzer/package.json +1 -1
  22. package/packages/django-plugin/package.json +1 -1
  23. package/packages/doc-generator/package.json +1 -1
  24. package/packages/evaluation-engine/package.json +1 -1
  25. package/packages/evaluation-runner/package.json +1 -1
  26. package/packages/event-bus/package.json +1 -1
  27. package/packages/event-store/package.json +1 -1
  28. package/packages/experience-replay/package.json +1 -1
  29. package/packages/feedback-collector/package.json +1 -1
  30. package/packages/framework-detector/package.json +1 -1
  31. package/packages/framework-plugin-sdk/package.json +1 -1
  32. package/packages/frappe-plugin/package.json +1 -1
  33. package/packages/generic-web-plugin/package.json +1 -1
  34. package/packages/health-monitor/package.json +1 -1
  35. package/packages/intent-analyzer/package.json +1 -1
  36. package/packages/laravel-plugin/package.json +1 -1
  37. package/packages/learning-engine/package.json +1 -1
  38. package/packages/mcp-adapter/package.json +1 -1
  39. package/packages/memory-engine/package.json +1 -1
  40. package/packages/model-registry/package.json +1 -1
  41. package/packages/nextjs-plugin/package.json +1 -1
  42. package/packages/notification/package.json +1 -1
  43. package/packages/observability/package.json +1 -1
  44. package/packages/performance-optimizer/package.json +1 -1
  45. package/packages/privilege-broker/package.json +1 -1
  46. package/packages/project-analyzer/package.json +1 -1
  47. package/packages/projection-engine/package.json +1 -1
  48. package/packages/prompt-compiler/package.json +1 -1
  49. package/packages/prompt-versioning/package.json +1 -1
  50. package/packages/provider-adapter-sdk/package.json +1 -1
  51. package/packages/provider-router/package.json +1 -1
  52. package/packages/provider-selector/package.json +1 -1
  53. package/packages/providers/package.json +1 -1
  54. package/packages/quota-manager/package.json +1 -1
  55. package/packages/rate-limiter/package.json +1 -1
  56. package/packages/react-vite-plugin/package.json +1 -1
  57. package/packages/release-manager/package.json +1 -1
  58. package/packages/requirement-compiler/package.json +1 -1
  59. package/packages/runtime/package.json +1 -1
  60. package/packages/scheduler/package.json +1 -1
  61. package/packages/scheduler-adapter/package.json +1 -1
  62. package/packages/session/package.json +1 -1
  63. package/packages/session-api/package.json +1 -1
  64. package/packages/session-export/package.json +1 -1
  65. package/packages/shared-context/package.json +1 -1
  66. package/packages/skill-registry/package.json +1 -1
  67. package/packages/sprint-planner/package.json +1 -1
  68. package/packages/subscription-engine/package.json +1 -1
  69. package/packages/task-compiler/package.json +1 -1
  70. package/packages/test-data-generator/package.json +1 -1
  71. package/packages/test-generator/package.json +1 -1
  72. package/packages/todo-bd-sync/package.json +1 -1
  73. package/packages/token-estimation/package.json +1 -1
  74. package/packages/token-optimizer/package.json +1 -1
  75. package/packages/tui/package.json +1 -1
  76. package/packages/types/package.json +1 -1
  77. package/packages/workflow-events/package.json +1 -1
  78. package/packages/workspace-scanner/package.json +1 -1
  79. package/packages/worktree/package.json +1 -1
  80. package/packages/write-review/package.json +22 -0
  81. package/packages/write-review/src/blackboard.ts +245 -0
  82. package/packages/write-review/src/gate.ts +167 -0
  83. package/packages/write-review/src/index.ts +18 -0
  84. package/packages/write-review/src/injection.ts +157 -0
  85. package/packages/write-review/src/review.ts +201 -0
  86. package/packages/write-review/src/trigger.ts +130 -0
  87. 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
+ }