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.
- package/.env.example +24 -1
- package/CONTRIBUTING.md +275 -0
- package/OPEN_SOURCE_MANIFESTO.md +172 -0
- package/README.md +832 -123
- package/dist/adapters/claude.d.ts +19 -4
- package/dist/adapters/claude.d.ts.map +1 -1
- package/dist/adapters/claude.js +908 -42
- package/dist/adapters/claude.js.map +1 -1
- package/dist/adapters/gemini.d.ts +55 -0
- package/dist/adapters/gemini.d.ts.map +1 -0
- package/dist/adapters/gemini.js +318 -0
- package/dist/adapters/gemini.js.map +1 -0
- package/dist/adapters/grok.d.ts +73 -0
- package/dist/adapters/grok.d.ts.map +1 -0
- package/dist/adapters/grok.js +430 -0
- package/dist/adapters/grok.js.map +1 -0
- package/dist/adapters/openai.d.ts +1 -1
- package/dist/adapters/openai.d.ts.map +1 -1
- package/dist/adapters/openai.js +47 -8
- package/dist/adapters/openai.js.map +1 -1
- package/dist/auth/claude.d.ts +11 -9
- package/dist/auth/claude.d.ts.map +1 -1
- package/dist/auth/claude.js +107 -71
- package/dist/auth/claude.js.map +1 -1
- package/dist/auth/gemini.d.ts +58 -0
- package/dist/auth/gemini.d.ts.map +1 -0
- package/dist/auth/gemini.js +172 -0
- package/dist/auth/gemini.js.map +1 -0
- package/dist/auth/grok.d.ts +73 -0
- package/dist/auth/grok.d.ts.map +1 -0
- package/dist/auth/grok.js +211 -0
- package/dist/auth/grok.js.map +1 -0
- package/dist/auth/index.d.ts +14 -7
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +41 -6
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/keychain.d.ts +20 -7
- package/dist/auth/keychain.d.ts.map +1 -1
- package/dist/auth/keychain.js +85 -29
- package/dist/auth/keychain.js.map +1 -1
- package/dist/auth/openai.d.ts +2 -2
- package/dist/auth/openai.d.ts.map +1 -1
- package/dist/auth/openai.js +30 -32
- package/dist/auth/openai.js.map +1 -1
- package/dist/cli/commands/auth.d.ts +1 -1
- package/dist/cli/commands/auth.d.ts.map +1 -1
- package/dist/cli/commands/auth.js +79 -8
- package/dist/cli/commands/auth.js.map +1 -1
- package/dist/cli/commands/create.d.ts.map +1 -1
- package/dist/cli/commands/create.js +15 -4
- package/dist/cli/commands/create.js.map +1 -1
- package/dist/cli/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +1494 -114
- package/dist/cli/interactive.js.map +1 -1
- package/dist/config/defaults.d.ts +9 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +19 -2
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/index.d.ts +19 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +33 -1
- package/dist/config/index.js.map +1 -1
- package/dist/config/schema.d.ts +47 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +29 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/generators/fullstack.d.ts +32 -0
- package/dist/generators/fullstack.d.ts.map +1 -0
- package/dist/generators/fullstack.js +497 -0
- package/dist/generators/fullstack.js.map +1 -0
- package/dist/generators/index.d.ts +4 -3
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +15 -1
- package/dist/generators/index.js.map +1 -1
- package/dist/generators/python.d.ts +17 -1
- package/dist/generators/python.d.ts.map +1 -1
- package/dist/generators/python.js +34 -20
- package/dist/generators/python.js.map +1 -1
- package/dist/generators/templates/fullstack.d.ts +113 -0
- package/dist/generators/templates/fullstack.d.ts.map +1 -0
- package/dist/generators/templates/fullstack.js +1004 -0
- package/dist/generators/templates/fullstack.js.map +1 -0
- package/dist/generators/typescript.d.ts +19 -1
- package/dist/generators/typescript.d.ts.map +1 -1
- package/dist/generators/typescript.js +37 -20
- package/dist/generators/typescript.js.map +1 -1
- package/dist/state/index.d.ts +108 -0
- package/dist/state/index.d.ts.map +1 -1
- package/dist/state/index.js +551 -4
- package/dist/state/index.js.map +1 -1
- package/dist/state/registry.d.ts +52 -0
- package/dist/state/registry.d.ts.map +1 -0
- package/dist/state/registry.js +215 -0
- package/dist/state/registry.js.map +1 -0
- package/dist/types/cli.d.ts +8 -0
- package/dist/types/cli.d.ts.map +1 -1
- package/dist/types/cli.js.map +1 -1
- package/dist/types/consensus.d.ts +186 -4
- package/dist/types/consensus.d.ts.map +1 -1
- package/dist/types/consensus.js +35 -3
- package/dist/types/consensus.js.map +1 -1
- package/dist/types/project.d.ts +76 -0
- package/dist/types/project.d.ts.map +1 -1
- package/dist/types/project.js +1 -1
- package/dist/types/project.js.map +1 -1
- package/dist/types/workflow.d.ts +217 -16
- package/dist/types/workflow.d.ts.map +1 -1
- package/dist/types/workflow.js +40 -1
- package/dist/types/workflow.js.map +1 -1
- package/dist/workflow/auto-fix.d.ts +45 -0
- package/dist/workflow/auto-fix.d.ts.map +1 -0
- package/dist/workflow/auto-fix.js +274 -0
- package/dist/workflow/auto-fix.js.map +1 -0
- package/dist/workflow/consensus.d.ts +70 -2
- package/dist/workflow/consensus.d.ts.map +1 -1
- package/dist/workflow/consensus.js +872 -17
- package/dist/workflow/consensus.js.map +1 -1
- package/dist/workflow/execution-mode.d.ts +10 -4
- package/dist/workflow/execution-mode.d.ts.map +1 -1
- package/dist/workflow/execution-mode.js +547 -58
- package/dist/workflow/execution-mode.js.map +1 -1
- package/dist/workflow/index.d.ts +14 -2
- package/dist/workflow/index.d.ts.map +1 -1
- package/dist/workflow/index.js +69 -6
- package/dist/workflow/index.js.map +1 -1
- package/dist/workflow/milestone-workflow.d.ts +34 -0
- package/dist/workflow/milestone-workflow.d.ts.map +1 -0
- package/dist/workflow/milestone-workflow.js +414 -0
- package/dist/workflow/milestone-workflow.js.map +1 -0
- package/dist/workflow/plan-mode.d.ts +80 -3
- package/dist/workflow/plan-mode.d.ts.map +1 -1
- package/dist/workflow/plan-mode.js +767 -49
- package/dist/workflow/plan-mode.js.map +1 -1
- package/dist/workflow/plan-storage.d.ts +386 -0
- package/dist/workflow/plan-storage.d.ts.map +1 -0
- package/dist/workflow/plan-storage.js +878 -0
- package/dist/workflow/plan-storage.js.map +1 -0
- package/dist/workflow/project-verification.d.ts +37 -0
- package/dist/workflow/project-verification.d.ts.map +1 -0
- package/dist/workflow/project-verification.js +381 -0
- package/dist/workflow/project-verification.js.map +1 -0
- package/dist/workflow/task-workflow.d.ts +37 -0
- package/dist/workflow/task-workflow.d.ts.map +1 -0
- package/dist/workflow/task-workflow.js +386 -0
- package/dist/workflow/task-workflow.js.map +1 -0
- package/dist/workflow/test-runner.d.ts +9 -0
- package/dist/workflow/test-runner.d.ts.map +1 -1
- package/dist/workflow/test-runner.js +101 -5
- package/dist/workflow/test-runner.js.map +1 -1
- package/dist/workflow/ui-designer.d.ts +82 -0
- package/dist/workflow/ui-designer.d.ts.map +1 -0
- package/dist/workflow/ui-designer.js +234 -0
- package/dist/workflow/ui-designer.js.map +1 -0
- package/dist/workflow/ui-setup.d.ts +58 -0
- package/dist/workflow/ui-setup.d.ts.map +1 -0
- package/dist/workflow/ui-setup.js +685 -0
- package/dist/workflow/ui-setup.js.map +1 -0
- package/dist/workflow/ui-verification.d.ts +114 -0
- package/dist/workflow/ui-verification.d.ts.map +1 -0
- package/dist/workflow/ui-verification.js +258 -0
- package/dist/workflow/ui-verification.js.map +1 -0
- package/dist/workflow/workflow-logger.d.ts +110 -0
- package/dist/workflow/workflow-logger.d.ts.map +1 -0
- package/dist/workflow/workflow-logger.js +267 -0
- package/dist/workflow/workflow-logger.js.map +1 -0
- package/dist/workflow/workspace-manager.d.ts +342 -0
- package/dist/workflow/workspace-manager.d.ts.map +1 -0
- package/dist/workflow/workspace-manager.js +733 -0
- package/dist/workflow/workspace-manager.js.map +1 -0
- package/package.json +2 -2
- package/src/adapters/claude.ts +1067 -47
- package/src/adapters/gemini.ts +373 -0
- package/src/adapters/grok.ts +492 -0
- package/src/adapters/openai.ts +48 -9
- package/src/auth/claude.ts +120 -78
- package/src/auth/gemini.ts +207 -0
- package/src/auth/grok.ts +255 -0
- package/src/auth/index.ts +47 -9
- package/src/auth/keychain.ts +95 -28
- package/src/auth/openai.ts +29 -36
- package/src/cli/commands/auth.ts +89 -10
- package/src/cli/commands/create.ts +13 -4
- package/src/cli/interactive.ts +1774 -142
- package/src/config/defaults.ts +19 -2
- package/src/config/index.ts +36 -1
- package/src/config/schema.ts +30 -1
- package/src/generators/fullstack.ts +551 -0
- package/src/generators/index.ts +25 -1
- package/src/generators/python.ts +65 -20
- package/src/generators/templates/fullstack.ts +1047 -0
- package/src/generators/typescript.ts +69 -20
- package/src/state/index.ts +713 -4
- package/src/state/registry.ts +278 -0
- package/src/types/cli.ts +8 -0
- package/src/types/consensus.ts +197 -6
- package/src/types/project.ts +82 -1
- package/src/types/workflow.ts +90 -1
- package/src/workflow/auto-fix.ts +340 -0
- package/src/workflow/consensus.ts +1180 -16
- package/src/workflow/execution-mode.ts +673 -74
- package/src/workflow/index.ts +95 -6
- package/src/workflow/milestone-workflow.ts +576 -0
- package/src/workflow/plan-mode.ts +924 -50
- package/src/workflow/plan-storage.ts +1282 -0
- package/src/workflow/project-verification.ts +471 -0
- package/src/workflow/task-workflow.ts +528 -0
- package/src/workflow/test-runner.ts +120 -5
- package/src/workflow/ui-designer.ts +337 -0
- package/src/workflow/ui-setup.ts +797 -0
- package/src/workflow/ui-verification.ts +357 -0
- package/src/workflow/workflow-logger.ts +353 -0
- package/src/workflow/workspace-manager.ts +912 -0
- package/tests/config/config.test.ts +1 -1
- package/tests/types/consensus.test.ts +3 -3
- package/tests/workflow/plan-mode.test.ts +213 -0
- 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
package/src/types/consensus.ts
CHANGED
|
@@ -7,7 +7,27 @@ import { z } from 'zod';
|
|
|
7
7
|
import type { OpenAIModel } from './project.js';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
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
|
|
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:
|
|
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(
|
|
63
|
-
openaiKey: z.string().
|
|
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
|
+
}
|
package/src/types/project.ts
CHANGED
|
@@ -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
|
*/
|