popeye-cli 1.0.0 → 1.1.0

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 (171) hide show
  1. package/README.md +521 -125
  2. package/dist/adapters/claude.d.ts +16 -4
  3. package/dist/adapters/claude.d.ts.map +1 -1
  4. package/dist/adapters/claude.js +679 -33
  5. package/dist/adapters/claude.js.map +1 -1
  6. package/dist/adapters/gemini.d.ts +55 -0
  7. package/dist/adapters/gemini.d.ts.map +1 -0
  8. package/dist/adapters/gemini.js +318 -0
  9. package/dist/adapters/gemini.js.map +1 -0
  10. package/dist/adapters/openai.d.ts.map +1 -1
  11. package/dist/adapters/openai.js +41 -7
  12. package/dist/adapters/openai.js.map +1 -1
  13. package/dist/auth/claude.d.ts +11 -9
  14. package/dist/auth/claude.d.ts.map +1 -1
  15. package/dist/auth/claude.js +107 -71
  16. package/dist/auth/claude.js.map +1 -1
  17. package/dist/auth/gemini.d.ts +58 -0
  18. package/dist/auth/gemini.d.ts.map +1 -0
  19. package/dist/auth/gemini.js +172 -0
  20. package/dist/auth/gemini.js.map +1 -0
  21. package/dist/auth/index.d.ts +11 -7
  22. package/dist/auth/index.d.ts.map +1 -1
  23. package/dist/auth/index.js +23 -5
  24. package/dist/auth/index.js.map +1 -1
  25. package/dist/auth/keychain.d.ts +20 -7
  26. package/dist/auth/keychain.d.ts.map +1 -1
  27. package/dist/auth/keychain.js +85 -29
  28. package/dist/auth/keychain.js.map +1 -1
  29. package/dist/auth/openai.d.ts +2 -2
  30. package/dist/auth/openai.d.ts.map +1 -1
  31. package/dist/auth/openai.js +30 -32
  32. package/dist/auth/openai.js.map +1 -1
  33. package/dist/cli/index.d.ts.map +1 -1
  34. package/dist/cli/index.js +4 -7
  35. package/dist/cli/index.js.map +1 -1
  36. package/dist/cli/interactive.d.ts +2 -2
  37. package/dist/cli/interactive.d.ts.map +1 -1
  38. package/dist/cli/interactive.js +1380 -183
  39. package/dist/cli/interactive.js.map +1 -1
  40. package/dist/config/defaults.d.ts +6 -1
  41. package/dist/config/defaults.d.ts.map +1 -1
  42. package/dist/config/defaults.js +10 -2
  43. package/dist/config/defaults.js.map +1 -1
  44. package/dist/config/index.d.ts +10 -0
  45. package/dist/config/index.d.ts.map +1 -1
  46. package/dist/config/index.js +19 -0
  47. package/dist/config/index.js.map +1 -1
  48. package/dist/config/schema.d.ts +20 -0
  49. package/dist/config/schema.d.ts.map +1 -1
  50. package/dist/config/schema.js +7 -0
  51. package/dist/config/schema.js.map +1 -1
  52. package/dist/generators/python.d.ts.map +1 -1
  53. package/dist/generators/python.js +1 -0
  54. package/dist/generators/python.js.map +1 -1
  55. package/dist/generators/typescript.d.ts.map +1 -1
  56. package/dist/generators/typescript.js +1 -0
  57. package/dist/generators/typescript.js.map +1 -1
  58. package/dist/state/index.d.ts +108 -0
  59. package/dist/state/index.d.ts.map +1 -1
  60. package/dist/state/index.js +551 -4
  61. package/dist/state/index.js.map +1 -1
  62. package/dist/state/registry.d.ts +52 -0
  63. package/dist/state/registry.d.ts.map +1 -0
  64. package/dist/state/registry.js +215 -0
  65. package/dist/state/registry.js.map +1 -0
  66. package/dist/types/cli.d.ts +4 -0
  67. package/dist/types/cli.d.ts.map +1 -1
  68. package/dist/types/cli.js.map +1 -1
  69. package/dist/types/consensus.d.ts +69 -4
  70. package/dist/types/consensus.d.ts.map +1 -1
  71. package/dist/types/consensus.js +24 -3
  72. package/dist/types/consensus.js.map +1 -1
  73. package/dist/types/workflow.d.ts +55 -0
  74. package/dist/types/workflow.d.ts.map +1 -1
  75. package/dist/types/workflow.js +16 -0
  76. package/dist/types/workflow.js.map +1 -1
  77. package/dist/workflow/auto-fix.d.ts +45 -0
  78. package/dist/workflow/auto-fix.d.ts.map +1 -0
  79. package/dist/workflow/auto-fix.js +274 -0
  80. package/dist/workflow/auto-fix.js.map +1 -0
  81. package/dist/workflow/consensus.d.ts +44 -2
  82. package/dist/workflow/consensus.d.ts.map +1 -1
  83. package/dist/workflow/consensus.js +565 -17
  84. package/dist/workflow/consensus.js.map +1 -1
  85. package/dist/workflow/execution-mode.d.ts +10 -4
  86. package/dist/workflow/execution-mode.d.ts.map +1 -1
  87. package/dist/workflow/execution-mode.js +547 -58
  88. package/dist/workflow/execution-mode.js.map +1 -1
  89. package/dist/workflow/index.d.ts +14 -2
  90. package/dist/workflow/index.d.ts.map +1 -1
  91. package/dist/workflow/index.js +69 -6
  92. package/dist/workflow/index.js.map +1 -1
  93. package/dist/workflow/milestone-workflow.d.ts +34 -0
  94. package/dist/workflow/milestone-workflow.d.ts.map +1 -0
  95. package/dist/workflow/milestone-workflow.js +414 -0
  96. package/dist/workflow/milestone-workflow.js.map +1 -0
  97. package/dist/workflow/plan-mode.d.ts +14 -1
  98. package/dist/workflow/plan-mode.d.ts.map +1 -1
  99. package/dist/workflow/plan-mode.js +589 -47
  100. package/dist/workflow/plan-mode.js.map +1 -1
  101. package/dist/workflow/plan-storage.d.ts +142 -0
  102. package/dist/workflow/plan-storage.d.ts.map +1 -0
  103. package/dist/workflow/plan-storage.js +331 -0
  104. package/dist/workflow/plan-storage.js.map +1 -0
  105. package/dist/workflow/project-verification.d.ts +37 -0
  106. package/dist/workflow/project-verification.d.ts.map +1 -0
  107. package/dist/workflow/project-verification.js +381 -0
  108. package/dist/workflow/project-verification.js.map +1 -0
  109. package/dist/workflow/task-workflow.d.ts +37 -0
  110. package/dist/workflow/task-workflow.d.ts.map +1 -0
  111. package/dist/workflow/task-workflow.js +383 -0
  112. package/dist/workflow/task-workflow.js.map +1 -0
  113. package/dist/workflow/test-runner.d.ts +1 -0
  114. package/dist/workflow/test-runner.d.ts.map +1 -1
  115. package/dist/workflow/test-runner.js +9 -5
  116. package/dist/workflow/test-runner.js.map +1 -1
  117. package/dist/workflow/ui-designer.d.ts +82 -0
  118. package/dist/workflow/ui-designer.d.ts.map +1 -0
  119. package/dist/workflow/ui-designer.js +234 -0
  120. package/dist/workflow/ui-designer.js.map +1 -0
  121. package/dist/workflow/ui-setup.d.ts +58 -0
  122. package/dist/workflow/ui-setup.d.ts.map +1 -0
  123. package/dist/workflow/ui-setup.js +685 -0
  124. package/dist/workflow/ui-setup.js.map +1 -0
  125. package/dist/workflow/ui-verification.d.ts +114 -0
  126. package/dist/workflow/ui-verification.d.ts.map +1 -0
  127. package/dist/workflow/ui-verification.js +258 -0
  128. package/dist/workflow/ui-verification.js.map +1 -0
  129. package/dist/workflow/workflow-logger.d.ts +110 -0
  130. package/dist/workflow/workflow-logger.d.ts.map +1 -0
  131. package/dist/workflow/workflow-logger.js +267 -0
  132. package/dist/workflow/workflow-logger.js.map +1 -0
  133. package/package.json +2 -2
  134. package/src/adapters/claude.ts +815 -34
  135. package/src/adapters/gemini.ts +373 -0
  136. package/src/adapters/openai.ts +40 -7
  137. package/src/auth/claude.ts +120 -78
  138. package/src/auth/gemini.ts +207 -0
  139. package/src/auth/index.ts +28 -8
  140. package/src/auth/keychain.ts +95 -28
  141. package/src/auth/openai.ts +29 -36
  142. package/src/cli/index.ts +4 -7
  143. package/src/cli/interactive.ts +1641 -216
  144. package/src/config/defaults.ts +10 -2
  145. package/src/config/index.ts +21 -0
  146. package/src/config/schema.ts +7 -0
  147. package/src/generators/python.ts +1 -0
  148. package/src/generators/typescript.ts +1 -0
  149. package/src/state/index.ts +713 -4
  150. package/src/state/registry.ts +278 -0
  151. package/src/types/cli.ts +4 -0
  152. package/src/types/consensus.ts +65 -6
  153. package/src/types/workflow.ts +35 -0
  154. package/src/workflow/auto-fix.ts +340 -0
  155. package/src/workflow/consensus.ts +750 -16
  156. package/src/workflow/execution-mode.ts +673 -74
  157. package/src/workflow/index.ts +95 -6
  158. package/src/workflow/milestone-workflow.ts +576 -0
  159. package/src/workflow/plan-mode.ts +696 -50
  160. package/src/workflow/plan-storage.ts +482 -0
  161. package/src/workflow/project-verification.ts +471 -0
  162. package/src/workflow/task-workflow.ts +525 -0
  163. package/src/workflow/test-runner.ts +10 -5
  164. package/src/workflow/ui-designer.ts +337 -0
  165. package/src/workflow/ui-setup.ts +797 -0
  166. package/src/workflow/ui-verification.ts +357 -0
  167. package/src/workflow/workflow-logger.ts +353 -0
  168. package/tests/config/config.test.ts +1 -1
  169. package/tests/types/consensus.test.ts +3 -3
  170. package/tests/workflow/plan-mode.test.ts +213 -0
  171. package/tests/workflow/test-runner.test.ts +5 -3
@@ -0,0 +1,278 @@
1
+ /**
2
+ * Project Registry
3
+ * Tracks all Popeye projects globally and provides discovery functionality
4
+ */
5
+
6
+ import { promises as fs } from 'node:fs';
7
+ import path from 'node:path';
8
+ import { homedir } from 'os';
9
+ import { STATE_DIR, STATE_FILE, loadState } from './persistence.js';
10
+
11
+ /**
12
+ * Global registry directory
13
+ */
14
+ const REGISTRY_DIR = path.join(homedir(), '.popeye');
15
+
16
+ /**
17
+ * Registry file name
18
+ */
19
+ const REGISTRY_FILE = 'projects.json';
20
+
21
+ /**
22
+ * Registered project entry
23
+ */
24
+ export interface RegisteredProject {
25
+ path: string;
26
+ name: string;
27
+ idea?: string;
28
+ language: string;
29
+ phase: string;
30
+ status: string;
31
+ createdAt: string;
32
+ updatedAt: string;
33
+ }
34
+
35
+ /**
36
+ * Project registry structure
37
+ */
38
+ interface ProjectRegistry {
39
+ version: string;
40
+ projects: RegisteredProject[];
41
+ }
42
+
43
+ /**
44
+ * Get registry file path
45
+ */
46
+ function getRegistryPath(): string {
47
+ return path.join(REGISTRY_DIR, REGISTRY_FILE);
48
+ }
49
+
50
+ /**
51
+ * Ensure registry directory exists
52
+ */
53
+ async function ensureRegistryDir(): Promise<void> {
54
+ await fs.mkdir(REGISTRY_DIR, { recursive: true });
55
+ }
56
+
57
+ /**
58
+ * Load the project registry
59
+ */
60
+ async function loadRegistry(): Promise<ProjectRegistry> {
61
+ try {
62
+ const content = await fs.readFile(getRegistryPath(), 'utf-8');
63
+ return JSON.parse(content);
64
+ } catch {
65
+ return { version: '1.0', projects: [] };
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Save the project registry
71
+ */
72
+ async function saveRegistry(registry: ProjectRegistry): Promise<void> {
73
+ await ensureRegistryDir();
74
+ const content = JSON.stringify(registry, null, 2);
75
+ await fs.writeFile(getRegistryPath(), content, 'utf-8');
76
+ }
77
+
78
+ /**
79
+ * Register a new project or update existing registration
80
+ */
81
+ export async function registerProject(projectDir: string): Promise<void> {
82
+ const state = await loadState(projectDir);
83
+ if (!state) return;
84
+
85
+ const registry = await loadRegistry();
86
+
87
+ // Check if project already registered
88
+ const existingIndex = registry.projects.findIndex(p => p.path === projectDir);
89
+
90
+ const entry: RegisteredProject = {
91
+ path: projectDir,
92
+ name: state.name,
93
+ idea: state.idea?.slice(0, 200),
94
+ language: state.language,
95
+ phase: state.phase,
96
+ status: state.status,
97
+ createdAt: state.createdAt,
98
+ updatedAt: state.updatedAt,
99
+ };
100
+
101
+ if (existingIndex >= 0) {
102
+ registry.projects[existingIndex] = entry;
103
+ } else {
104
+ registry.projects.push(entry);
105
+ }
106
+
107
+ await saveRegistry(registry);
108
+ }
109
+
110
+ /**
111
+ * Remove a project from the registry
112
+ */
113
+ export async function unregisterProject(projectDir: string): Promise<void> {
114
+ const registry = await loadRegistry();
115
+ registry.projects = registry.projects.filter(p => p.path !== projectDir);
116
+ await saveRegistry(registry);
117
+ }
118
+
119
+ /**
120
+ * Get all registered projects
121
+ */
122
+ export async function getRegisteredProjects(): Promise<RegisteredProject[]> {
123
+ const registry = await loadRegistry();
124
+
125
+ // Verify each project still exists and update status
126
+ const validProjects: RegisteredProject[] = [];
127
+
128
+ for (const project of registry.projects) {
129
+ try {
130
+ const state = await loadState(project.path);
131
+ if (state) {
132
+ validProjects.push({
133
+ ...project,
134
+ phase: state.phase,
135
+ status: state.status,
136
+ updatedAt: state.updatedAt,
137
+ });
138
+ }
139
+ } catch {
140
+ // Project no longer exists, skip it
141
+ }
142
+ }
143
+
144
+ // Update registry with valid projects only
145
+ if (validProjects.length !== registry.projects.length) {
146
+ await saveRegistry({ ...registry, projects: validProjects });
147
+ }
148
+
149
+ // Sort by updatedAt (most recent first)
150
+ return validProjects.sort((a, b) =>
151
+ new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
152
+ );
153
+ }
154
+
155
+ /**
156
+ * Scan a directory (and subdirectories) for Popeye projects
157
+ */
158
+ export async function scanForProjects(
159
+ baseDir: string,
160
+ maxDepth: number = 3
161
+ ): Promise<RegisteredProject[]> {
162
+ const discovered: RegisteredProject[] = [];
163
+
164
+ async function scanDir(dir: string, depth: number): Promise<void> {
165
+ if (depth > maxDepth) return;
166
+
167
+ try {
168
+ // Check if this directory has a Popeye project
169
+ const statePath = path.join(dir, STATE_DIR, STATE_FILE);
170
+ try {
171
+ await fs.access(statePath);
172
+ const state = await loadState(dir);
173
+ if (state) {
174
+ discovered.push({
175
+ path: dir,
176
+ name: state.name,
177
+ idea: state.idea?.slice(0, 200),
178
+ language: state.language,
179
+ phase: state.phase,
180
+ status: state.status,
181
+ createdAt: state.createdAt,
182
+ updatedAt: state.updatedAt,
183
+ });
184
+ // Register discovered project
185
+ await registerProject(dir);
186
+ }
187
+ } catch {
188
+ // No state file in this directory
189
+ }
190
+
191
+ // Scan subdirectories
192
+ const entries = await fs.readdir(dir, { withFileTypes: true });
193
+ for (const entry of entries) {
194
+ if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
195
+ await scanDir(path.join(dir, entry.name), depth + 1);
196
+ }
197
+ }
198
+ } catch {
199
+ // Can't read directory, skip it
200
+ }
201
+ }
202
+
203
+ await scanDir(baseDir, 0);
204
+
205
+ // Sort by updatedAt (most recent first)
206
+ return discovered.sort((a, b) =>
207
+ new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
208
+ );
209
+ }
210
+
211
+ /**
212
+ * Find projects in the current directory and registered projects
213
+ */
214
+ export async function discoverProjects(cwd: string): Promise<{
215
+ registered: RegisteredProject[];
216
+ discovered: RegisteredProject[];
217
+ all: RegisteredProject[];
218
+ }> {
219
+ // Get registered projects
220
+ const registered = await getRegisteredProjects();
221
+
222
+ // Scan current directory for projects
223
+ const scanned = await scanForProjects(cwd, 2);
224
+
225
+ // Merge, avoiding duplicates
226
+ const registeredPaths = new Set(registered.map(p => p.path));
227
+ const discovered = scanned.filter(p => !registeredPaths.has(p.path));
228
+
229
+ // Combine all projects
230
+ const all = [...registered, ...discovered].sort((a, b) =>
231
+ new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
232
+ );
233
+
234
+ return { registered, discovered, all };
235
+ }
236
+
237
+ /**
238
+ * Format a project for display
239
+ */
240
+ export function formatProjectForDisplay(project: RegisteredProject): {
241
+ name: string;
242
+ status: string;
243
+ path: string;
244
+ lastUpdated: string;
245
+ age: string;
246
+ } {
247
+ const now = new Date();
248
+ const updated = new Date(project.updatedAt);
249
+ const diffMs = now.getTime() - updated.getTime();
250
+ const diffMins = Math.floor(diffMs / 60000);
251
+ const diffHours = Math.floor(diffMs / 3600000);
252
+ const diffDays = Math.floor(diffMs / 86400000);
253
+
254
+ let age: string;
255
+ if (diffMins < 1) {
256
+ age = 'just now';
257
+ } else if (diffMins < 60) {
258
+ age = `${diffMins}m ago`;
259
+ } else if (diffHours < 24) {
260
+ age = `${diffHours}h ago`;
261
+ } else if (diffDays < 7) {
262
+ age = `${diffDays}d ago`;
263
+ } else {
264
+ age = updated.toLocaleDateString();
265
+ }
266
+
267
+ const statusIcon = project.status === 'complete' ? '✓' :
268
+ project.status === 'failed' ? '✗' :
269
+ project.status === 'in-progress' ? '→' : '○';
270
+
271
+ return {
272
+ name: project.name,
273
+ status: `${statusIcon} ${project.phase}/${project.status}`,
274
+ path: project.path,
275
+ lastUpdated: updated.toISOString(),
276
+ age,
277
+ };
278
+ }
package/src/types/cli.ts CHANGED
@@ -67,6 +67,10 @@ export interface AuthStatus {
67
67
  keyLastFour?: string;
68
68
  modelAccess?: string[];
69
69
  };
70
+ gemini?: {
71
+ authenticated: boolean;
72
+ keyLastFour?: string;
73
+ };
70
74
  }
71
75
 
72
76
  /**
@@ -7,7 +7,17 @@ import { z } from 'zod';
7
7
  import type { OpenAIModel } from './project.js';
8
8
 
9
9
  /**
10
- * Result of a consensus review from OpenAI
10
+ * Supported AI providers for reviews and arbitration
11
+ */
12
+ export type AIProvider = 'openai' | 'gemini';
13
+
14
+ /**
15
+ * Supported Gemini models
16
+ */
17
+ export type GeminiModel = 'gemini-2.0-flash' | 'gemini-1.5-pro' | 'gemini-1.5-flash';
18
+
19
+ /**
20
+ * Result of a consensus review from OpenAI or Gemini
11
21
  */
12
22
  export interface ConsensusResult {
13
23
  score: number;
@@ -19,6 +29,21 @@ export interface ConsensusResult {
19
29
  rawResponse: string;
20
30
  }
21
31
 
32
+ /**
33
+ * Result of an arbitration decision
34
+ */
35
+ export interface ArbitrationResult {
36
+ approved: boolean;
37
+ score: number;
38
+ analysis: string;
39
+ criticalConcerns: string[];
40
+ minorConcerns: string[];
41
+ subjectiveConcerns: string[];
42
+ reasoning: string;
43
+ suggestedChanges: string[];
44
+ rawResponse: string;
45
+ }
46
+
22
47
  /**
23
48
  * Single consensus iteration record
24
49
  */
@@ -35,33 +60,67 @@ export interface ConsensusIteration {
35
60
  export interface ConsensusConfig {
36
61
  threshold: number;
37
62
  maxIterations: number;
38
- openaiKey: string;
63
+ openaiKey?: string;
64
+ geminiKey?: string;
39
65
  openaiModel: OpenAIModel;
66
+ geminiModel: GeminiModel;
67
+ reviewer: AIProvider;
68
+ arbitrator: AIProvider;
69
+ enableArbitration: boolean;
70
+ arbitrationThreshold: number; // Score at which to trigger arbitration (e.g., 85)
71
+ stuckIterations: number; // Number of iterations without improvement before arbitration
40
72
  escalationAction: 'pause' | 'continue' | 'abort';
41
73
  temperature: number;
42
74
  maxTokens: number;
75
+ /** Use optimized consensus with batched feedback and file-based plan storage (default: true) */
76
+ useOptimizedConsensus?: boolean;
77
+ /** Additional reviewers beyond primary (for parallel reviews) */
78
+ additionalReviewers?: AIProvider[];
43
79
  }
44
80
 
45
81
  /**
46
82
  * Default consensus configuration
47
83
  */
48
- export const DEFAULT_CONSENSUS_CONFIG: Omit<ConsensusConfig, 'openaiKey'> = {
84
+ export const DEFAULT_CONSENSUS_CONFIG: Omit<ConsensusConfig, 'openaiKey' | 'geminiKey'> = {
49
85
  threshold: 95,
50
- maxIterations: 5,
86
+ maxIterations: 10,
51
87
  openaiModel: 'gpt-4o',
88
+ geminiModel: 'gemini-2.0-flash',
89
+ reviewer: 'openai',
90
+ arbitrator: 'gemini',
91
+ enableArbitration: true,
92
+ arbitrationThreshold: 85,
93
+ stuckIterations: 3,
52
94
  escalationAction: 'pause',
53
95
  temperature: 0.3,
54
96
  maxTokens: 4096,
55
97
  };
56
98
 
99
+ /**
100
+ * Zod schema for AI provider
101
+ */
102
+ export const AIProviderSchema = z.enum(['openai', 'gemini']);
103
+
104
+ /**
105
+ * Zod schema for Gemini model
106
+ */
107
+ export const GeminiModelSchema = z.enum(['gemini-2.0-flash', 'gemini-1.5-pro', 'gemini-1.5-flash']);
108
+
57
109
  /**
58
110
  * Zod schema for consensus config validation
59
111
  */
60
112
  export const ConsensusConfigSchema = z.object({
61
113
  threshold: z.number().min(0).max(100).default(95),
62
- maxIterations: z.number().min(1).max(10).default(5),
63
- openaiKey: z.string().min(1),
114
+ maxIterations: z.number().min(1).max(20).default(10),
115
+ openaiKey: z.string().optional(),
116
+ geminiKey: z.string().optional(),
64
117
  openaiModel: z.enum(['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'o1-preview', 'o1-mini']),
118
+ geminiModel: GeminiModelSchema.default('gemini-2.0-flash'),
119
+ reviewer: AIProviderSchema.default('openai'),
120
+ arbitrator: AIProviderSchema.default('gemini'),
121
+ enableArbitration: z.boolean().default(true),
122
+ arbitrationThreshold: z.number().min(0).max(100).default(85),
123
+ stuckIterations: z.number().min(1).max(10).default(3),
65
124
  escalationAction: z.enum(['pause', 'continue', 'abort']).default('pause'),
66
125
  temperature: z.number().min(0).max(2).default(0.3),
67
126
  maxTokens: z.number().min(100).max(32000).default(4096),
@@ -36,6 +36,14 @@ export interface Task {
36
36
  testsPassed?: boolean;
37
37
  testPlan?: string;
38
38
  error?: string;
39
+ // Per-task consensus tracking
40
+ plan?: string; // Detailed task implementation plan
41
+ consensusScore?: number; // Consensus score for task plan (0-100)
42
+ consensusIterations?: number; // Number of iterations to reach consensus
43
+ consensusApproved?: boolean; // Whether task plan was approved
44
+ planDoc?: string; // Path to task plan document
45
+ testResultsDoc?: string; // Path to test results document
46
+ implementationComplete?: boolean; // Whether code implementation finished (for resume)
39
47
  }
40
48
 
41
49
  /**
@@ -49,6 +57,13 @@ export const TaskSchema = z.object({
49
57
  testsPassed: z.boolean().optional(),
50
58
  testPlan: z.string().optional(),
51
59
  error: z.string().optional(),
60
+ plan: z.string().optional(),
61
+ consensusScore: z.number().optional(),
62
+ consensusIterations: z.number().optional(),
63
+ consensusApproved: z.boolean().optional(),
64
+ planDoc: z.string().optional(),
65
+ testResultsDoc: z.string().optional(),
66
+ implementationComplete: z.boolean().optional(),
52
67
  });
53
68
 
54
69
  /**
@@ -60,6 +75,17 @@ export interface Milestone {
60
75
  description: string;
61
76
  status: TaskStatus;
62
77
  tasks: Task[];
78
+ // Per-milestone consensus tracking
79
+ plan?: string; // Detailed milestone plan
80
+ consensusScore?: number; // Consensus score for milestone plan
81
+ consensusIterations?: number; // Number of iterations to reach consensus
82
+ consensusApproved?: boolean; // Whether milestone plan was approved
83
+ planDoc?: string; // Path: docs/milestone_N_plan.md
84
+ // Milestone completion review
85
+ completionReview?: string; // Code review and summary
86
+ completionScore?: number; // Consensus score for completion
87
+ completionApproved?: boolean; // Whether milestone completion was approved
88
+ completionDoc?: string; // Path: docs/milestone_N_complete.md
63
89
  }
64
90
 
65
91
  /**
@@ -71,6 +97,15 @@ export const MilestoneSchema = z.object({
71
97
  description: z.string(),
72
98
  status: TaskStatusSchema,
73
99
  tasks: z.array(TaskSchema),
100
+ plan: z.string().optional(),
101
+ consensusScore: z.number().optional(),
102
+ consensusIterations: z.number().optional(),
103
+ consensusApproved: z.boolean().optional(),
104
+ planDoc: z.string().optional(),
105
+ completionReview: z.string().optional(),
106
+ completionScore: z.number().optional(),
107
+ completionApproved: z.boolean().optional(),
108
+ completionDoc: z.string().optional(),
74
109
  });
75
110
 
76
111
  /**