popeye-cli 1.0.1 → 1.2.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 (216) hide show
  1. package/.env.example +24 -1
  2. package/CONTRIBUTING.md +275 -0
  3. package/OPEN_SOURCE_MANIFESTO.md +172 -0
  4. package/README.md +832 -123
  5. package/dist/adapters/claude.d.ts +19 -4
  6. package/dist/adapters/claude.d.ts.map +1 -1
  7. package/dist/adapters/claude.js +908 -42
  8. package/dist/adapters/claude.js.map +1 -1
  9. package/dist/adapters/gemini.d.ts +55 -0
  10. package/dist/adapters/gemini.d.ts.map +1 -0
  11. package/dist/adapters/gemini.js +318 -0
  12. package/dist/adapters/gemini.js.map +1 -0
  13. package/dist/adapters/grok.d.ts +73 -0
  14. package/dist/adapters/grok.d.ts.map +1 -0
  15. package/dist/adapters/grok.js +430 -0
  16. package/dist/adapters/grok.js.map +1 -0
  17. package/dist/adapters/openai.d.ts +1 -1
  18. package/dist/adapters/openai.d.ts.map +1 -1
  19. package/dist/adapters/openai.js +47 -8
  20. package/dist/adapters/openai.js.map +1 -1
  21. package/dist/auth/claude.d.ts +11 -9
  22. package/dist/auth/claude.d.ts.map +1 -1
  23. package/dist/auth/claude.js +107 -71
  24. package/dist/auth/claude.js.map +1 -1
  25. package/dist/auth/gemini.d.ts +58 -0
  26. package/dist/auth/gemini.d.ts.map +1 -0
  27. package/dist/auth/gemini.js +172 -0
  28. package/dist/auth/gemini.js.map +1 -0
  29. package/dist/auth/grok.d.ts +73 -0
  30. package/dist/auth/grok.d.ts.map +1 -0
  31. package/dist/auth/grok.js +211 -0
  32. package/dist/auth/grok.js.map +1 -0
  33. package/dist/auth/index.d.ts +14 -7
  34. package/dist/auth/index.d.ts.map +1 -1
  35. package/dist/auth/index.js +41 -6
  36. package/dist/auth/index.js.map +1 -1
  37. package/dist/auth/keychain.d.ts +20 -7
  38. package/dist/auth/keychain.d.ts.map +1 -1
  39. package/dist/auth/keychain.js +85 -29
  40. package/dist/auth/keychain.js.map +1 -1
  41. package/dist/auth/openai.d.ts +2 -2
  42. package/dist/auth/openai.d.ts.map +1 -1
  43. package/dist/auth/openai.js +30 -32
  44. package/dist/auth/openai.js.map +1 -1
  45. package/dist/cli/commands/auth.d.ts +1 -1
  46. package/dist/cli/commands/auth.d.ts.map +1 -1
  47. package/dist/cli/commands/auth.js +79 -8
  48. package/dist/cli/commands/auth.js.map +1 -1
  49. package/dist/cli/commands/create.d.ts.map +1 -1
  50. package/dist/cli/commands/create.js +15 -4
  51. package/dist/cli/commands/create.js.map +1 -1
  52. package/dist/cli/interactive.d.ts.map +1 -1
  53. package/dist/cli/interactive.js +1494 -114
  54. package/dist/cli/interactive.js.map +1 -1
  55. package/dist/config/defaults.d.ts +9 -1
  56. package/dist/config/defaults.d.ts.map +1 -1
  57. package/dist/config/defaults.js +19 -2
  58. package/dist/config/defaults.js.map +1 -1
  59. package/dist/config/index.d.ts +19 -0
  60. package/dist/config/index.d.ts.map +1 -1
  61. package/dist/config/index.js +33 -1
  62. package/dist/config/index.js.map +1 -1
  63. package/dist/config/schema.d.ts +47 -0
  64. package/dist/config/schema.d.ts.map +1 -1
  65. package/dist/config/schema.js +29 -1
  66. package/dist/config/schema.js.map +1 -1
  67. package/dist/generators/fullstack.d.ts +32 -0
  68. package/dist/generators/fullstack.d.ts.map +1 -0
  69. package/dist/generators/fullstack.js +497 -0
  70. package/dist/generators/fullstack.js.map +1 -0
  71. package/dist/generators/index.d.ts +4 -3
  72. package/dist/generators/index.d.ts.map +1 -1
  73. package/dist/generators/index.js +15 -1
  74. package/dist/generators/index.js.map +1 -1
  75. package/dist/generators/python.d.ts +17 -1
  76. package/dist/generators/python.d.ts.map +1 -1
  77. package/dist/generators/python.js +34 -20
  78. package/dist/generators/python.js.map +1 -1
  79. package/dist/generators/templates/fullstack.d.ts +113 -0
  80. package/dist/generators/templates/fullstack.d.ts.map +1 -0
  81. package/dist/generators/templates/fullstack.js +1004 -0
  82. package/dist/generators/templates/fullstack.js.map +1 -0
  83. package/dist/generators/typescript.d.ts +19 -1
  84. package/dist/generators/typescript.d.ts.map +1 -1
  85. package/dist/generators/typescript.js +37 -20
  86. package/dist/generators/typescript.js.map +1 -1
  87. package/dist/state/index.d.ts +108 -0
  88. package/dist/state/index.d.ts.map +1 -1
  89. package/dist/state/index.js +551 -4
  90. package/dist/state/index.js.map +1 -1
  91. package/dist/state/registry.d.ts +52 -0
  92. package/dist/state/registry.d.ts.map +1 -0
  93. package/dist/state/registry.js +215 -0
  94. package/dist/state/registry.js.map +1 -0
  95. package/dist/types/cli.d.ts +8 -0
  96. package/dist/types/cli.d.ts.map +1 -1
  97. package/dist/types/cli.js.map +1 -1
  98. package/dist/types/consensus.d.ts +186 -4
  99. package/dist/types/consensus.d.ts.map +1 -1
  100. package/dist/types/consensus.js +35 -3
  101. package/dist/types/consensus.js.map +1 -1
  102. package/dist/types/project.d.ts +76 -0
  103. package/dist/types/project.d.ts.map +1 -1
  104. package/dist/types/project.js +1 -1
  105. package/dist/types/project.js.map +1 -1
  106. package/dist/types/workflow.d.ts +217 -16
  107. package/dist/types/workflow.d.ts.map +1 -1
  108. package/dist/types/workflow.js +40 -1
  109. package/dist/types/workflow.js.map +1 -1
  110. package/dist/workflow/auto-fix.d.ts +45 -0
  111. package/dist/workflow/auto-fix.d.ts.map +1 -0
  112. package/dist/workflow/auto-fix.js +274 -0
  113. package/dist/workflow/auto-fix.js.map +1 -0
  114. package/dist/workflow/consensus.d.ts +70 -2
  115. package/dist/workflow/consensus.d.ts.map +1 -1
  116. package/dist/workflow/consensus.js +872 -17
  117. package/dist/workflow/consensus.js.map +1 -1
  118. package/dist/workflow/execution-mode.d.ts +10 -4
  119. package/dist/workflow/execution-mode.d.ts.map +1 -1
  120. package/dist/workflow/execution-mode.js +547 -58
  121. package/dist/workflow/execution-mode.js.map +1 -1
  122. package/dist/workflow/index.d.ts +14 -2
  123. package/dist/workflow/index.d.ts.map +1 -1
  124. package/dist/workflow/index.js +69 -6
  125. package/dist/workflow/index.js.map +1 -1
  126. package/dist/workflow/milestone-workflow.d.ts +34 -0
  127. package/dist/workflow/milestone-workflow.d.ts.map +1 -0
  128. package/dist/workflow/milestone-workflow.js +414 -0
  129. package/dist/workflow/milestone-workflow.js.map +1 -0
  130. package/dist/workflow/plan-mode.d.ts +80 -3
  131. package/dist/workflow/plan-mode.d.ts.map +1 -1
  132. package/dist/workflow/plan-mode.js +767 -49
  133. package/dist/workflow/plan-mode.js.map +1 -1
  134. package/dist/workflow/plan-storage.d.ts +386 -0
  135. package/dist/workflow/plan-storage.d.ts.map +1 -0
  136. package/dist/workflow/plan-storage.js +878 -0
  137. package/dist/workflow/plan-storage.js.map +1 -0
  138. package/dist/workflow/project-verification.d.ts +37 -0
  139. package/dist/workflow/project-verification.d.ts.map +1 -0
  140. package/dist/workflow/project-verification.js +381 -0
  141. package/dist/workflow/project-verification.js.map +1 -0
  142. package/dist/workflow/task-workflow.d.ts +37 -0
  143. package/dist/workflow/task-workflow.d.ts.map +1 -0
  144. package/dist/workflow/task-workflow.js +386 -0
  145. package/dist/workflow/task-workflow.js.map +1 -0
  146. package/dist/workflow/test-runner.d.ts +9 -0
  147. package/dist/workflow/test-runner.d.ts.map +1 -1
  148. package/dist/workflow/test-runner.js +101 -5
  149. package/dist/workflow/test-runner.js.map +1 -1
  150. package/dist/workflow/ui-designer.d.ts +82 -0
  151. package/dist/workflow/ui-designer.d.ts.map +1 -0
  152. package/dist/workflow/ui-designer.js +234 -0
  153. package/dist/workflow/ui-designer.js.map +1 -0
  154. package/dist/workflow/ui-setup.d.ts +58 -0
  155. package/dist/workflow/ui-setup.d.ts.map +1 -0
  156. package/dist/workflow/ui-setup.js +685 -0
  157. package/dist/workflow/ui-setup.js.map +1 -0
  158. package/dist/workflow/ui-verification.d.ts +114 -0
  159. package/dist/workflow/ui-verification.d.ts.map +1 -0
  160. package/dist/workflow/ui-verification.js +258 -0
  161. package/dist/workflow/ui-verification.js.map +1 -0
  162. package/dist/workflow/workflow-logger.d.ts +110 -0
  163. package/dist/workflow/workflow-logger.d.ts.map +1 -0
  164. package/dist/workflow/workflow-logger.js +267 -0
  165. package/dist/workflow/workflow-logger.js.map +1 -0
  166. package/dist/workflow/workspace-manager.d.ts +342 -0
  167. package/dist/workflow/workspace-manager.d.ts.map +1 -0
  168. package/dist/workflow/workspace-manager.js +733 -0
  169. package/dist/workflow/workspace-manager.js.map +1 -0
  170. package/package.json +2 -2
  171. package/src/adapters/claude.ts +1067 -47
  172. package/src/adapters/gemini.ts +373 -0
  173. package/src/adapters/grok.ts +492 -0
  174. package/src/adapters/openai.ts +48 -9
  175. package/src/auth/claude.ts +120 -78
  176. package/src/auth/gemini.ts +207 -0
  177. package/src/auth/grok.ts +255 -0
  178. package/src/auth/index.ts +47 -9
  179. package/src/auth/keychain.ts +95 -28
  180. package/src/auth/openai.ts +29 -36
  181. package/src/cli/commands/auth.ts +89 -10
  182. package/src/cli/commands/create.ts +13 -4
  183. package/src/cli/interactive.ts +1774 -142
  184. package/src/config/defaults.ts +19 -2
  185. package/src/config/index.ts +36 -1
  186. package/src/config/schema.ts +30 -1
  187. package/src/generators/fullstack.ts +551 -0
  188. package/src/generators/index.ts +25 -1
  189. package/src/generators/python.ts +65 -20
  190. package/src/generators/templates/fullstack.ts +1047 -0
  191. package/src/generators/typescript.ts +69 -20
  192. package/src/state/index.ts +713 -4
  193. package/src/state/registry.ts +278 -0
  194. package/src/types/cli.ts +8 -0
  195. package/src/types/consensus.ts +197 -6
  196. package/src/types/project.ts +82 -1
  197. package/src/types/workflow.ts +90 -1
  198. package/src/workflow/auto-fix.ts +340 -0
  199. package/src/workflow/consensus.ts +1180 -16
  200. package/src/workflow/execution-mode.ts +673 -74
  201. package/src/workflow/index.ts +95 -6
  202. package/src/workflow/milestone-workflow.ts +576 -0
  203. package/src/workflow/plan-mode.ts +924 -50
  204. package/src/workflow/plan-storage.ts +1282 -0
  205. package/src/workflow/project-verification.ts +471 -0
  206. package/src/workflow/task-workflow.ts +528 -0
  207. package/src/workflow/test-runner.ts +120 -5
  208. package/src/workflow/ui-designer.ts +337 -0
  209. package/src/workflow/ui-setup.ts +797 -0
  210. package/src/workflow/ui-verification.ts +357 -0
  211. package/src/workflow/workflow-logger.ts +353 -0
  212. package/src/workflow/workspace-manager.ts +912 -0
  213. package/tests/config/config.test.ts +1 -1
  214. package/tests/types/consensus.test.ts +3 -3
  215. package/tests/workflow/plan-mode.test.ts +213 -0
  216. 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,14 @@ export interface AuthStatus {
67
67
  keyLastFour?: string;
68
68
  modelAccess?: string[];
69
69
  };
70
+ gemini?: {
71
+ authenticated: boolean;
72
+ keyLastFour?: string;
73
+ };
74
+ grok?: {
75
+ authenticated: boolean;
76
+ keyLastFour?: string;
77
+ };
70
78
  }
71
79
 
72
80
  /**
@@ -7,7 +7,27 @@ 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' | 'grok';
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
+ * Grok model type (flexible string - API evolves fast)
21
+ */
22
+ export type GrokModel = string;
23
+
24
+ /**
25
+ * Default Grok model
26
+ */
27
+ export const DEFAULT_GROK_MODEL = 'grok-3';
28
+
29
+ /**
30
+ * Result of a consensus review from OpenAI or Gemini
11
31
  */
12
32
  export interface ConsensusResult {
13
33
  score: number;
@@ -19,6 +39,21 @@ export interface ConsensusResult {
19
39
  rawResponse: string;
20
40
  }
21
41
 
42
+ /**
43
+ * Result of an arbitration decision
44
+ */
45
+ export interface ArbitrationResult {
46
+ approved: boolean;
47
+ score: number;
48
+ analysis: string;
49
+ criticalConcerns: string[];
50
+ minorConcerns: string[];
51
+ subjectiveConcerns: string[];
52
+ reasoning: string;
53
+ suggestedChanges: string[];
54
+ rawResponse: string;
55
+ }
56
+
22
57
  /**
23
58
  * Single consensus iteration record
24
59
  */
@@ -35,33 +70,77 @@ export interface ConsensusIteration {
35
70
  export interface ConsensusConfig {
36
71
  threshold: number;
37
72
  maxIterations: number;
38
- openaiKey: string;
73
+ openaiKey?: string;
74
+ geminiKey?: string;
75
+ grokKey?: string;
39
76
  openaiModel: OpenAIModel;
77
+ geminiModel: GeminiModel;
78
+ grokModel: GrokModel;
79
+ reviewer: AIProvider;
80
+ arbitrator: AIProvider;
81
+ enableArbitration: boolean;
82
+ arbitrationThreshold: number; // Score at which to trigger arbitration (e.g., 85)
83
+ stuckIterations: number; // Number of iterations without improvement before arbitration
40
84
  escalationAction: 'pause' | 'continue' | 'abort';
41
85
  temperature: number;
42
86
  maxTokens: number;
87
+ /** Use optimized consensus with batched feedback and file-based plan storage (default: true) */
88
+ useOptimizedConsensus?: boolean;
89
+ /** Additional reviewers beyond primary (for parallel reviews) */
90
+ additionalReviewers?: AIProvider[];
43
91
  }
44
92
 
45
93
  /**
46
94
  * Default consensus configuration
47
95
  */
48
- export const DEFAULT_CONSENSUS_CONFIG: Omit<ConsensusConfig, 'openaiKey'> = {
96
+ export const DEFAULT_CONSENSUS_CONFIG: Omit<ConsensusConfig, 'openaiKey' | 'geminiKey' | 'grokKey'> = {
49
97
  threshold: 95,
50
- maxIterations: 5,
98
+ maxIterations: 10,
51
99
  openaiModel: 'gpt-4o',
100
+ geminiModel: 'gemini-2.0-flash',
101
+ grokModel: DEFAULT_GROK_MODEL,
102
+ reviewer: 'openai',
103
+ arbitrator: 'gemini',
104
+ enableArbitration: true,
105
+ arbitrationThreshold: 85,
106
+ stuckIterations: 3,
52
107
  escalationAction: 'pause',
53
108
  temperature: 0.3,
54
109
  maxTokens: 4096,
55
110
  };
56
111
 
112
+ /**
113
+ * Zod schema for AI provider
114
+ */
115
+ export const AIProviderSchema = z.enum(['openai', 'gemini', 'grok']);
116
+
117
+ /**
118
+ * Zod schema for Gemini model
119
+ */
120
+ export const GeminiModelSchema = z.enum(['gemini-2.0-flash', 'gemini-1.5-pro', 'gemini-1.5-flash']);
121
+
122
+ /**
123
+ * Zod schema for Grok model (flexible string)
124
+ */
125
+ export const GrokModelSchema = z.string().default(DEFAULT_GROK_MODEL);
126
+
57
127
  /**
58
128
  * Zod schema for consensus config validation
59
129
  */
60
130
  export const ConsensusConfigSchema = z.object({
61
131
  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),
132
+ maxIterations: z.number().min(1).max(20).default(10),
133
+ openaiKey: z.string().optional(),
134
+ geminiKey: z.string().optional(),
135
+ grokKey: z.string().optional(),
64
136
  openaiModel: z.enum(['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'o1-preview', 'o1-mini']),
137
+ geminiModel: GeminiModelSchema.default('gemini-2.0-flash'),
138
+ grokModel: GrokModelSchema.default(DEFAULT_GROK_MODEL),
139
+ reviewer: AIProviderSchema.default('openai'),
140
+ arbitrator: AIProviderSchema.default('gemini'),
141
+ enableArbitration: z.boolean().default(true),
142
+ arbitrationThreshold: z.number().min(0).max(100).default(85),
143
+ stuckIterations: z.number().min(1).max(10).default(3),
65
144
  escalationAction: z.enum(['pause', 'continue', 'abort']).default('pause'),
66
145
  temperature: z.number().min(0).max(2).default(0.3),
67
146
  maxTokens: z.number().min(100).max(32000).default(4096),
@@ -114,3 +193,115 @@ export interface EscalationDetails {
114
193
  unresolvable_concerns: string[];
115
194
  suggestedActions: string[];
116
195
  }
196
+
197
+ /**
198
+ * App target for fullstack reviews
199
+ */
200
+ export type ReviewAppTarget = 'frontend' | 'backend' | 'unified';
201
+
202
+ /**
203
+ * Tagged concern/recommendation with app context
204
+ */
205
+ export interface TaggedItem {
206
+ app: ReviewAppTarget;
207
+ content: string;
208
+ }
209
+
210
+ /**
211
+ * Per-app consensus scores for fullstack projects
212
+ */
213
+ export interface AppConsensusScores {
214
+ frontend?: number;
215
+ backend?: number;
216
+ unified: number; // Combined/overall score
217
+ }
218
+
219
+ /**
220
+ * Per-app feedback for fullstack reviews
221
+ */
222
+ export interface AppFeedback {
223
+ app: ReviewAppTarget;
224
+ score: number;
225
+ concerns: string[];
226
+ recommendations: string[];
227
+ analysis: string;
228
+ }
229
+
230
+ /**
231
+ * Fullstack-aware consensus result
232
+ */
233
+ export interface FullstackConsensusResult extends ConsensusResult {
234
+ /** Per-app breakdown of scores */
235
+ appScores: AppConsensusScores;
236
+ /** Concerns tagged by app */
237
+ taggedConcerns: TaggedItem[];
238
+ /** Recommendations tagged by app */
239
+ taggedRecommendations: TaggedItem[];
240
+ /** Per-app feedback breakdown */
241
+ appFeedback: AppFeedback[];
242
+ /** Whether this is a fullstack project review */
243
+ isFullstack: boolean;
244
+ }
245
+
246
+ /**
247
+ * Fullstack consensus iteration with per-app tracking
248
+ */
249
+ export interface FullstackConsensusIteration extends ConsensusIteration {
250
+ /** Per-app scores for this iteration */
251
+ appScores?: AppConsensusScores;
252
+ /** Per-app approval status */
253
+ appApproved?: {
254
+ frontend?: boolean;
255
+ backend?: boolean;
256
+ unified: boolean;
257
+ };
258
+ }
259
+
260
+ /**
261
+ * Correction/revision record for tracking all changes
262
+ */
263
+ export interface CorrectionRecord {
264
+ id: string;
265
+ timestamp: string;
266
+ app: ReviewAppTarget;
267
+ previousScore: number;
268
+ newScore: number;
269
+ concerns: string[];
270
+ changes: string[];
271
+ reviewer: AIProvider;
272
+ }
273
+
274
+ /**
275
+ * Complete consensus tracking for a plan level (master/milestone/task)
276
+ */
277
+ export interface ConsensusTrackingRecord {
278
+ planLevel: 'master' | 'milestone' | 'task';
279
+ planId: string;
280
+ milestoneName?: string;
281
+ taskName?: string;
282
+ isFullstack: boolean;
283
+
284
+ /** Overall consensus status */
285
+ overallScore: number;
286
+ overallApproved: boolean;
287
+ totalIterations: number;
288
+
289
+ /** Per-app consensus status (fullstack only) */
290
+ frontendScore?: number;
291
+ frontendApproved?: boolean;
292
+ frontendIterations?: number;
293
+
294
+ backendScore?: number;
295
+ backendApproved?: boolean;
296
+ backendIterations?: number;
297
+
298
+ /** All corrections/revisions made */
299
+ corrections: CorrectionRecord[];
300
+
301
+ /** Timestamps */
302
+ startedAt: string;
303
+ completedAt?: string;
304
+
305
+ /** Final status */
306
+ status: 'in-progress' | 'approved' | 'escalated' | 'failed';
307
+ }
@@ -8,9 +8,90 @@ import { z } from 'zod';
8
8
  /**
9
9
  * Supported output languages for generated projects
10
10
  */
11
- export const OutputLanguageSchema = z.enum(['python', 'typescript']);
11
+ export const OutputLanguageSchema = z.enum(['python', 'typescript', 'fullstack']);
12
12
  export type OutputLanguage = z.infer<typeof OutputLanguageSchema>;
13
13
 
14
+ /**
15
+ * Commands configuration for a workspace app
16
+ */
17
+ export interface WorkspaceAppCommands {
18
+ test: string;
19
+ lint: string;
20
+ build: string;
21
+ dev: string;
22
+ typecheck?: string;
23
+ }
24
+
25
+ /**
26
+ * Docker configuration for a workspace app
27
+ */
28
+ export interface WorkspaceAppDocker {
29
+ dockerfile: string;
30
+ imageName: string;
31
+ context: string;
32
+ }
33
+
34
+ /**
35
+ * Single app configuration in a workspace
36
+ */
37
+ export interface WorkspaceApp {
38
+ name: string;
39
+ path: string;
40
+ language: 'python' | 'typescript';
41
+ commands: WorkspaceAppCommands;
42
+ docker?: WorkspaceAppDocker;
43
+ /** Dependencies on other apps or shared packages */
44
+ dependsOn?: string[];
45
+ /** Files to include as context for AI code generation */
46
+ contextRoots?: string[];
47
+ /** UI spec path (frontend only) */
48
+ uiSpec?: string;
49
+ }
50
+
51
+ /**
52
+ * Shared configuration in a workspace
53
+ */
54
+ export interface WorkspaceShared {
55
+ /** OpenAPI spec path for contract-first development */
56
+ contracts?: string;
57
+ /** Generator command for FE client from OpenAPI */
58
+ contractsGenerator?: string;
59
+ }
60
+
61
+ /**
62
+ * Repo-level commands for workspace orchestration
63
+ */
64
+ export interface WorkspaceCommands {
65
+ testAll: string;
66
+ lintAll: string;
67
+ buildAll: string;
68
+ devAll?: string;
69
+ }
70
+
71
+ /**
72
+ * Docker configuration at workspace level
73
+ */
74
+ export interface WorkspaceDocker {
75
+ composePath: string;
76
+ /** Root-level compose for convenience symlink */
77
+ rootComposeSymlink?: boolean;
78
+ }
79
+
80
+ /**
81
+ * Workspace configuration for fullstack projects
82
+ */
83
+ export interface WorkspaceConfig {
84
+ version: '1.0';
85
+ apps: {
86
+ frontend?: WorkspaceApp;
87
+ backend?: WorkspaceApp;
88
+ };
89
+ shared?: WorkspaceShared;
90
+ /** Repo-level commands that orchestrate across apps */
91
+ commands: WorkspaceCommands;
92
+ docker: WorkspaceDocker;
93
+ }
94
+
14
95
  /**
15
96
  * Supported OpenAI models for consensus reviews
16
97
  */