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.
Files changed (89) hide show
  1. package/index.ts +51 -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 +23 -0
  16. package/packages/config-capture/src/index.ts +357 -0
  17. package/packages/context-compiler/package.json +1 -1
  18. package/packages/context-discovery/package.json +1 -1
  19. package/packages/context-manager/package.json +1 -1
  20. package/packages/cookie-sanitizer/package.json +1 -1
  21. package/packages/cost-optimizer/package.json +1 -1
  22. package/packages/dependency-analyzer/package.json +1 -1
  23. package/packages/django-plugin/package.json +1 -1
  24. package/packages/doc-generator/package.json +1 -1
  25. package/packages/evaluation-engine/package.json +1 -1
  26. package/packages/evaluation-runner/package.json +1 -1
  27. package/packages/event-bus/package.json +1 -1
  28. package/packages/event-store/package.json +1 -1
  29. package/packages/experience-replay/package.json +1 -1
  30. package/packages/feedback-collector/package.json +1 -1
  31. package/packages/framework-detector/package.json +1 -1
  32. package/packages/framework-plugin-sdk/package.json +1 -1
  33. package/packages/frappe-plugin/package.json +1 -1
  34. package/packages/generic-web-plugin/package.json +1 -1
  35. package/packages/health-monitor/package.json +1 -1
  36. package/packages/intent-analyzer/package.json +1 -1
  37. package/packages/laravel-plugin/package.json +1 -1
  38. package/packages/learning-engine/package.json +1 -1
  39. package/packages/mcp-adapter/package.json +1 -1
  40. package/packages/memory-engine/package.json +1 -1
  41. package/packages/model-registry/package.json +1 -1
  42. package/packages/nextjs-plugin/package.json +1 -1
  43. package/packages/notification/package.json +1 -1
  44. package/packages/observability/package.json +1 -1
  45. package/packages/performance-optimizer/package.json +1 -1
  46. package/packages/privilege-broker/package.json +1 -1
  47. package/packages/project-analyzer/package.json +1 -1
  48. package/packages/projection-engine/package.json +1 -1
  49. package/packages/prompt-compiler/package.json +1 -1
  50. package/packages/prompt-versioning/package.json +1 -1
  51. package/packages/provider-adapter-sdk/package.json +1 -1
  52. package/packages/provider-router/package.json +1 -1
  53. package/packages/provider-selector/package.json +1 -1
  54. package/packages/providers/package.json +1 -1
  55. package/packages/quota-manager/package.json +1 -1
  56. package/packages/rate-limiter/package.json +1 -1
  57. package/packages/react-vite-plugin/package.json +1 -1
  58. package/packages/release-manager/package.json +1 -1
  59. package/packages/requirement-compiler/package.json +1 -1
  60. package/packages/runtime/package.json +1 -1
  61. package/packages/scheduler/package.json +1 -1
  62. package/packages/scheduler-adapter/package.json +1 -1
  63. package/packages/session/package.json +1 -1
  64. package/packages/session-api/package.json +1 -1
  65. package/packages/session-export/package.json +1 -1
  66. package/packages/shared-context/package.json +1 -1
  67. package/packages/skill-registry/package.json +1 -1
  68. package/packages/sprint-planner/package.json +1 -1
  69. package/packages/subscription-engine/package.json +1 -1
  70. package/packages/task-compiler/package.json +1 -1
  71. package/packages/test-data-generator/package.json +1 -1
  72. package/packages/test-generator/package.json +1 -1
  73. package/packages/todo-bd-sync/package.json +1 -1
  74. package/packages/todo-bd-sync/src/task-analyzer.ts +266 -0
  75. package/packages/token-estimation/package.json +1 -1
  76. package/packages/token-optimizer/package.json +1 -1
  77. package/packages/tui/package.json +1 -1
  78. package/packages/types/package.json +1 -1
  79. package/packages/workflow-events/package.json +1 -1
  80. package/packages/workspace-scanner/package.json +1 -1
  81. package/packages/worktree/package.json +1 -1
  82. package/packages/write-review/package.json +22 -0
  83. package/packages/write-review/src/blackboard.ts +245 -0
  84. package/packages/write-review/src/gate.ts +167 -0
  85. package/packages/write-review/src/index.ts +18 -0
  86. package/packages/write-review/src/injection.ts +157 -0
  87. package/packages/write-review/src/review.ts +201 -0
  88. package/packages/write-review/src/trigger.ts +130 -0
  89. package/packages/write-review/src/types.ts +88 -0
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Smart Auto-Todo Task Analyzer
3
+ *
4
+ * Decides when to automatically create bd issues based on:
5
+ * 1. Task complexity signals
6
+ * 2. Session state (pending task count)
7
+ *
8
+ * Philosophy:
9
+ * - Simple tasks (grep, quick edit) -> NO todo (avoid noise)
10
+ * - Complex tasks OR multiple pending tasks -> AUTO-CREATE todo
11
+ */
12
+
13
+ import { getOpenBdIssues } from "./sync.js";
14
+
15
+ // ─── Complexity Signals ─────────────────────────────────────────────────────────
16
+
17
+ interface TaskSignals {
18
+ /** Estimated number of steps required */
19
+ stepCount: number;
20
+ /** Number of files likely involved */
21
+ fileCount: number;
22
+ /** Requires research (web search, docs lookup) */
23
+ needsResearch: boolean;
24
+ /** Creates new files */
25
+ createsFiles: boolean;
26
+ /** User explicitly says task is complex */
27
+ explicitlyComplex: boolean;
28
+ /** Likely to have subtasks discovered during work */
29
+ likelySubtasks: boolean;
30
+ }
31
+
32
+ const COMPLEX_KEYWORDS = [
33
+ "complicated", "complex", "tricky", "difficult", "refactor",
34
+ "architecture", "design", "implement from scratch", "rewrite",
35
+ "investigate", "debug", "fix the bug", "performance",
36
+ ];
37
+
38
+ const RESEARCH_KEYWORDS = [
39
+ "search", "look up", "find", "research", "check", "verify",
40
+ "how to", "best practice", "compare", "evaluate", "review",
41
+ ];
42
+
43
+ const SUBTASK_KEYWORDS = [
44
+ "also", "plus", "and also", "while you're at it", "on top of that",
45
+ "additionally", "another thing", "don't forget", "should also",
46
+ ];
47
+
48
+ /**
49
+ * Analyze user request text for complexity signals
50
+ */
51
+ export function analyzeTaskSignals(userMessage: string): TaskSignals {
52
+ const lower = userMessage.toLowerCase();
53
+
54
+ // Count implied steps from action verbs
55
+ const actionVerbs = [
56
+ "create", "build", "implement", "design", "setup", "configure",
57
+ "migrate", "convert", "refactor", "optimize", "fix", "update",
58
+ "add", "remove", "delete", "modify", "change", "replace",
59
+ ];
60
+ const stepCount = Math.max(1, actionVerbs.filter(v => lower.includes(v)).length);
61
+
62
+ // Estimate file count from file paths and extensions
63
+ const filePatterns = /[\w-]+\.(ts|js|py|json|md|yaml|yml|tsx|jsx|html|css|sql)/g;
64
+ const fileMatches = userMessage.match(filePatterns) || [];
65
+ const explicitFiles = fileMatches.length;
66
+
67
+ // Check for directory patterns
68
+ const dirPatterns = /(?:src|lib|packages|apps|components|hooks|utils|api)\/[\w/-]+/g;
69
+ const dirMatches = userMessage.match(dirPatterns) || [];
70
+
71
+ // Count "and", "also", "+" for multi-task indicators
72
+ const multiTaskIndicator = (lower.match(/and also|also|\+/g) || []).length;
73
+ const estimatedFiles = Math.max(1, explicitFiles + dirMatches.length + multiTaskIndicator);
74
+
75
+ return {
76
+ stepCount,
77
+ fileCount: estimatedFiles,
78
+ needsResearch: RESEARCH_KEYWORDS.some(k => lower.includes(k)),
79
+ createsFiles: lower.includes("create") || lower.includes("add") || lower.includes("new"),
80
+ explicitlyComplex: COMPLEX_KEYWORDS.some(k => lower.includes(k)),
81
+ likelySubtasks: SUBTASK_KEYWORDS.some(k => lower.includes(k)) || multiTaskIndicator >= 2,
82
+ };
83
+ }
84
+
85
+ // ─── Decision Logic ───────────────────────────────────────────────────────────
86
+
87
+ export interface AutoTodoDecision {
88
+ shouldCreate: boolean;
89
+ reason: string;
90
+ confidence: "high" | "medium" | "low";
91
+ skipReason?: string;
92
+ }
93
+
94
+ /**
95
+ * Decide whether to auto-create a todo for the given task
96
+ */
97
+ export function decideAutoTodo(
98
+ userMessage: string,
99
+ options: {
100
+ forceMode?: boolean;
101
+ skipMode?: boolean;
102
+ } = {},
103
+ ): AutoTodoDecision {
104
+ const signals = analyzeTaskSignals(userMessage);
105
+ const pendingTasks = getOpenBdIssues().filter(i => i.status !== "closed");
106
+ const pendingCount = pendingTasks.length;
107
+
108
+ // ── Skip Conditions ────────────────────────────────────────────────────
109
+ // Never create todos for these
110
+
111
+ const simplePatterns = [
112
+ /^simply\s/i,
113
+ /^just\s/i,
114
+ /^quick\s/i,
115
+ /^can you\s+(grep|find|cat|ls|echo)/i,
116
+ /^(grep|find|cat|ls)\s+[\w.-]+/i,
117
+ /^what('s| is) the\s+\w+\s+\?$/i,
118
+ /^how do i\s+\w+\s*\?$/i,
119
+ ];
120
+
121
+ if (simplePatterns.some(p => p.test(userMessage.trim()))) {
122
+ return {
123
+ shouldCreate: false,
124
+ reason: "Simple request (lookup/one-liner)",
125
+ confidence: "high",
126
+ skipReason: "Task is too simple to warrant tracking",
127
+ };
128
+ }
129
+
130
+ // Check for single file, single action with no complexity
131
+ if (
132
+ signals.stepCount === 1 &&
133
+ signals.fileCount <= 1 &&
134
+ !signals.needsResearch &&
135
+ !signals.createsFiles &&
136
+ !signals.explicitlyComplex &&
137
+ pendingCount === 0
138
+ ) {
139
+ return {
140
+ shouldCreate: false,
141
+ reason: "Single action, single file, no research needed",
142
+ confidence: "high",
143
+ skipReason: "Task is straightforward",
144
+ };
145
+ }
146
+
147
+ // ── Force Modes ──────────────────────────────────────────────────────
148
+ if (options.forceMode) {
149
+ return {
150
+ shouldCreate: true,
151
+ reason: "Force mode enabled",
152
+ confidence: "high",
153
+ };
154
+ }
155
+
156
+ if (options.skipMode) {
157
+ return {
158
+ shouldCreate: false,
159
+ reason: "Skip mode enabled",
160
+ confidence: "high",
161
+ skipReason: "User requested skip",
162
+ };
163
+ }
164
+
165
+ // ── Auto-Create Conditions ──────────────────────────────────────────
166
+ const reasons: string[] = [];
167
+
168
+ // HIGH CONFIDENCE: Task explicitly complex
169
+ if (signals.explicitlyComplex) {
170
+ reasons.push("explicitly complex");
171
+ }
172
+
173
+ // HIGH CONFIDENCE: Multiple steps
174
+ if (signals.stepCount >= 3) {
175
+ reasons.push(`${signals.stepCount} steps implied`);
176
+ }
177
+
178
+ // HIGH CONFIDENCE: Multiple files
179
+ if (signals.fileCount >= 3) {
180
+ reasons.push(`${signals.fileCount} files involved`);
181
+ }
182
+
183
+ // MEDIUM CONFIDENCE: Research needed
184
+ if (signals.needsResearch && signals.stepCount >= 2) {
185
+ reasons.push("research + action");
186
+ }
187
+
188
+ // MEDIUM CONFIDENCE: Creates files + action
189
+ if (signals.createsFiles && signals.fileCount >= 2) {
190
+ reasons.push("creates multiple files");
191
+ }
192
+
193
+ // MEDIUM CONFIDENCE: Likely subtasks
194
+ if (signals.likelySubtasks) {
195
+ reasons.push("likely has subtasks");
196
+ }
197
+
198
+ // HIGH CONFIDENCE: Already 2+ pending tasks (session overload)
199
+ if (pendingCount >= 2) {
200
+ reasons.push(`${pendingCount} pending tasks in session`);
201
+ }
202
+
203
+ // HIGH CONFIDENCE: 3+ pending tasks
204
+ if (pendingCount >= 3) {
205
+ reasons.push("session overwhelmed (3+ pending)");
206
+ }
207
+
208
+ if (reasons.length === 0) {
209
+ return {
210
+ shouldCreate: false,
211
+ reason: "No strong complexity signals",
212
+ confidence: "low",
213
+ skipReason: "Task doesn't meet auto-create threshold",
214
+ };
215
+ }
216
+
217
+ // Need at least 2 signals OR high-pending count
218
+ const highConfidenceReasons = ["explicitly complex", "pending tasks in session", "session overwhelmed"];
219
+ const hasHighConfidence = reasons.some(r => highConfidenceReasons.some(h => r.includes(h)));
220
+ const shouldCreate = reasons.length >= 2 || hasHighConfidence || pendingCount >= 2;
221
+
222
+ return {
223
+ shouldCreate,
224
+ reason: reasons.join(", "),
225
+ confidence: hasHighConfidence ? "high" : reasons.length >= 2 ? "medium" : "low",
226
+ };
227
+ }
228
+
229
+ // ─── Quick Score ─────────────────────────────────────────────────────────────
230
+
231
+ /**
232
+ * Quick numeric score (0-100) for task complexity
233
+ * Higher = more likely to need a todo
234
+ */
235
+ export function getTaskComplexityScore(userMessage: string): number {
236
+ const signals = analyzeTaskSignals(userMessage);
237
+ const pendingTasks = getOpenBdIssues().filter(i => i.status !== "closed");
238
+
239
+ let score = 0;
240
+
241
+ // Step count (0-25)
242
+ score += Math.min(25, signals.stepCount * 8);
243
+
244
+ // File count (0-20)
245
+ score += Math.min(20, signals.fileCount * 5);
246
+
247
+ // Research needed (0-15)
248
+ if (signals.needsResearch) score += 15;
249
+
250
+ // Creates files (0-10)
251
+ if (signals.createsFiles) score += 10;
252
+
253
+ // Explicitly complex (0-20)
254
+ if (signals.explicitlyComplex) score += 20;
255
+
256
+ // Likely subtasks (0-10)
257
+ if (signals.likelySubtasks) score += 10;
258
+
259
+ // Pending task overload (0-25)
260
+ const pendingCount = pendingTasks.length;
261
+ if (pendingCount >= 3) score += 25;
262
+ else if (pendingCount >= 2) score += 15;
263
+ else if (pendingCount >= 1) score += 5;
264
+
265
+ return Math.min(100, score);
266
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/token-estimation",
3
- "version": "0.10.5",
3
+ "version": "0.10.7",
4
4
  "description": "pi-harness-runtime — token-estimation",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/token-optimizer",
3
- "version": "0.10.5",
3
+ "version": "0.10.7",
4
4
  "description": "Token Budget Optimizer — maximize prompt value within token limits (RFC-0011)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/tui",
3
- "version": "0.10.5",
3
+ "version": "0.10.7",
4
4
  "description": "pi-harness-runtime — tui",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/types",
3
- "version": "0.10.5",
3
+ "version": "0.10.7",
4
4
  "description": "Shared TypeScript types for pi-harness-runtime packages",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/workflow-events",
3
- "version": "0.10.5",
3
+ "version": "0.10.7",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/workspace-scanner",
3
- "version": "0.10.5",
3
+ "version": "0.10.7",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/worktree",
3
- "version": "0.10.5",
3
+ "version": "0.10.7",
4
4
  "description": "pi-harness-runtime — worktree",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@moocoding/write-review",
3
+ "version": "0.10.7",
4
+ "description": "Two-agent write-review loop with blackboard coordination",
5
+ "type": "module",
6
+ "main": "src/index.ts",
7
+ "types": "src/index.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./src/index.ts",
11
+ "types": "./src/index.ts"
12
+ }
13
+ },
14
+ "keywords": [
15
+ "pi-extension",
16
+ "write-review",
17
+ "blackboard",
18
+ "multi-agent"
19
+ ],
20
+ "author": "MooCoding",
21
+ "license": "MIT"
22
+ }
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Write-Review Blackboard
3
+ *
4
+ * File-based coordination between writer and reviewer agents.
5
+ * Location: {project}/.write-review/status.json
6
+ */
7
+
8
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
9
+ import { join } from "path";
10
+ import type { WriteReviewStatus, ReviewPhase, Verdict } from "./types.js";
11
+
12
+ const DEFAULT_DIR = ".write-review";
13
+ const STATUS_FILE = "status.json";
14
+
15
+ export class WriteReviewBlackboard {
16
+ private readonly projectPath: string;
17
+ private readonly dir: string;
18
+ private status: WriteReviewStatus | null = null;
19
+
20
+ constructor(projectPath: string, dir: string = DEFAULT_DIR) {
21
+ this.projectPath = projectPath;
22
+ this.dir = join(projectPath, dir);
23
+ }
24
+
25
+ /**
26
+ * Initialize a new write-review session
27
+ */
28
+ init(): void {
29
+ mkdirSync(this.dir, { recursive: true });
30
+ this.status = {
31
+ projectPath: this.projectPath,
32
+ phase: "idle",
33
+ writerDone: false,
34
+ iteration: 0,
35
+ createdAt: new Date().toISOString(),
36
+ updatedAt: new Date().toISOString(),
37
+ };
38
+ this.save();
39
+ }
40
+
41
+ /**
42
+ * Load status from disk
43
+ */
44
+ load(): WriteReviewStatus | null {
45
+ const path = join(this.dir, STATUS_FILE);
46
+ if (!existsSync(path)) {
47
+ return null;
48
+ }
49
+ try {
50
+ this.status = JSON.parse(readFileSync(path, "utf-8"));
51
+ return this.status;
52
+ } catch {
53
+ return null;
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Save status to disk
59
+ */
60
+ save(): void {
61
+ if (!this.status) return;
62
+ this.status.updatedAt = new Date().toISOString();
63
+ mkdirSync(this.dir, { recursive: true });
64
+ const path = join(this.dir, STATUS_FILE);
65
+ writeFileSync(path, JSON.stringify(this.status, null, 2));
66
+ }
67
+
68
+ /**
69
+ * Get current status
70
+ */
71
+ getStatus(): WriteReviewStatus | null {
72
+ return this.status;
73
+ }
74
+
75
+ /**
76
+ * Start a new write session
77
+ */
78
+ startWriting(): void {
79
+ if (!this.status) this.init();
80
+ this.status!.phase = "writing";
81
+ this.status!.writerDone = false;
82
+ this.status!.iteration++;
83
+ this.save();
84
+ }
85
+
86
+ /**
87
+ * Mark writer as done
88
+ */
89
+ writerDone(message?: string): void {
90
+ if (!this.status) return;
91
+ this.status.writerDone = true;
92
+ this.status.writerMessage = message;
93
+ this.status.phase = "pending_review";
94
+ this.save();
95
+ }
96
+
97
+ /**
98
+ * Start reviewing
99
+ */
100
+ startReview(): void {
101
+ if (!this.status) return;
102
+ this.status.phase = "reviewing";
103
+ this.status.reviewerStarted = new Date().toISOString();
104
+ this.save();
105
+ }
106
+
107
+ /**
108
+ * Record verdict
109
+ */
110
+ setVerdict(verdict: Verdict, message?: string): void {
111
+ if (!this.status) return;
112
+ this.status.verdict = verdict;
113
+ this.status.verdictMessage = message;
114
+
115
+ switch (verdict) {
116
+ case "approved":
117
+ this.status.phase = "approved";
118
+ this.status.approvedAt = new Date().toISOString();
119
+ break;
120
+ case "blocked":
121
+ this.status.phase = "blocked";
122
+ this.status.blockedAt = new Date().toISOString();
123
+ break;
124
+ case "changes_requested":
125
+ this.status.phase = "changes_requested";
126
+ break;
127
+ }
128
+ this.save();
129
+ }
130
+
131
+ /**
132
+ * Record code files written
133
+ */
134
+ setCodeFiles(files: string[]): void {
135
+ if (!this.status) return;
136
+ this.status.codeFiles = files;
137
+ this.save();
138
+ }
139
+
140
+ /**
141
+ * Record requested changes
142
+ */
143
+ setChangesRequested(changes: string[]): void {
144
+ if (!this.status) return;
145
+ this.status.changesRequested = changes;
146
+ this.save();
147
+ }
148
+
149
+ /**
150
+ * Get current phase
151
+ */
152
+ getPhase(): ReviewPhase {
153
+ return this.status?.phase ?? "idle";
154
+ }
155
+
156
+ /**
157
+ * Get iteration number
158
+ */
159
+ getIteration(): number {
160
+ return this.status?.iteration ?? 0;
161
+ }
162
+
163
+ /**
164
+ * Check if build is allowed
165
+ */
166
+ canBuild(): boolean {
167
+ return this.status?.phase === "approved";
168
+ }
169
+
170
+ /**
171
+ * Get blackboard directory path
172
+ */
173
+ getPath(): string {
174
+ return this.dir;
175
+ }
176
+
177
+ /**
178
+ * Reset to idle
179
+ */
180
+ reset(): void {
181
+ if (!this.status) return;
182
+ this.status.phase = "idle";
183
+ this.status.writerDone = false;
184
+ this.status.verdict = undefined;
185
+ this.status.verdictMessage = undefined;
186
+ this.status.reviewerStarted = undefined;
187
+ this.status.approvedAt = undefined;
188
+ this.status.blockedAt = undefined;
189
+ this.status.codeFiles = undefined;
190
+ this.status.changesRequested = undefined;
191
+ this.save();
192
+ }
193
+
194
+ /**
195
+ * Check if a review session exists for this project
196
+ */
197
+ exists(): boolean {
198
+ const path = join(this.dir, STATUS_FILE);
199
+ return existsSync(path);
200
+ }
201
+
202
+ /**
203
+ * Export status as markdown for display
204
+ */
205
+ toMarkdown(): string {
206
+ if (!this.status) return "No active write-review session.";
207
+
208
+ const { phase, iteration, verdict, verdictMessage, codeFiles } = this.status;
209
+ const lines = [
210
+ `## Write-Review Status`,
211
+ ``,
212
+ `| Field | Value |`,
213
+ `|-------|-------|`,
214
+ `| Phase | ${phase} |`,
215
+ `| Iteration | ${iteration} |`,
216
+ `| Verdict | ${verdict ?? "pending"} |`,
217
+ ];
218
+
219
+ if (verdictMessage) {
220
+ lines.push(`| Message | ${verdictMessage} |`);
221
+ }
222
+
223
+ if (codeFiles && codeFiles.length > 0) {
224
+ lines.push(``, `### Code Files`);
225
+ for (const file of codeFiles) {
226
+ lines.push(`- ${file}`);
227
+ }
228
+ }
229
+
230
+ return lines.join("\n");
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Create or get blackboard for project
236
+ */
237
+ export function createBlackboard(projectPath: string, dir?: string): WriteReviewBlackboard {
238
+ const bb = new WriteReviewBlackboard(projectPath, dir);
239
+ if (!bb.exists()) {
240
+ bb.init();
241
+ } else {
242
+ bb.load();
243
+ }
244
+ return bb;
245
+ }