popeye-cli 2.0.0 → 2.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 (117) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/CONTRIBUTING.md +23 -2
  3. package/README.md +47 -18
  4. package/dist/adapters/gemini.js +3 -3
  5. package/dist/adapters/openai.js +2 -2
  6. package/dist/adapters/openai.js.map +1 -1
  7. package/dist/auth/gemini.js +1 -1
  8. package/dist/cli/commands/create.d.ts.map +1 -1
  9. package/dist/cli/commands/create.js +11 -5
  10. package/dist/cli/commands/create.js.map +1 -1
  11. package/dist/cli/commands/resume.d.ts.map +1 -1
  12. package/dist/cli/commands/resume.js +9 -1
  13. package/dist/cli/commands/resume.js.map +1 -1
  14. package/dist/cli/interactive.d.ts.map +1 -1
  15. package/dist/cli/interactive.js +29 -3
  16. package/dist/cli/interactive.js.map +1 -1
  17. package/dist/config/defaults.d.ts.map +1 -1
  18. package/dist/config/defaults.js +7 -2
  19. package/dist/config/defaults.js.map +1 -1
  20. package/dist/config/index.d.ts +1 -7
  21. package/dist/config/index.d.ts.map +1 -1
  22. package/dist/config/popeye-md.d.ts +32 -0
  23. package/dist/config/popeye-md.d.ts.map +1 -0
  24. package/dist/config/popeye-md.js +111 -0
  25. package/dist/config/popeye-md.js.map +1 -0
  26. package/dist/config/schema.d.ts +3 -21
  27. package/dist/config/schema.d.ts.map +1 -1
  28. package/dist/config/schema.js +21 -8
  29. package/dist/config/schema.js.map +1 -1
  30. package/dist/pipeline/bridges/review-bridge.d.ts +70 -0
  31. package/dist/pipeline/bridges/review-bridge.d.ts.map +1 -0
  32. package/dist/pipeline/bridges/review-bridge.js +266 -0
  33. package/dist/pipeline/bridges/review-bridge.js.map +1 -0
  34. package/dist/pipeline/consensus/consensus-runner.js +3 -3
  35. package/dist/pipeline/consensus/consensus-runner.js.map +1 -1
  36. package/dist/pipeline/orchestrator.d.ts +2 -0
  37. package/dist/pipeline/orchestrator.d.ts.map +1 -1
  38. package/dist/pipeline/orchestrator.js +5 -1
  39. package/dist/pipeline/orchestrator.js.map +1 -1
  40. package/dist/pipeline/phases/implementation.d.ts.map +1 -1
  41. package/dist/pipeline/phases/implementation.js +5 -2
  42. package/dist/pipeline/phases/implementation.js.map +1 -1
  43. package/dist/pipeline/phases/intake.d.ts.map +1 -1
  44. package/dist/pipeline/phases/intake.js +13 -4
  45. package/dist/pipeline/phases/intake.js.map +1 -1
  46. package/dist/pipeline/phases/recovery-loop.d.ts.map +1 -1
  47. package/dist/pipeline/phases/recovery-loop.js +2 -0
  48. package/dist/pipeline/phases/recovery-loop.js.map +1 -1
  49. package/dist/pipeline/type-defs/artifacts.d.ts +5 -0
  50. package/dist/pipeline/type-defs/artifacts.d.ts.map +1 -1
  51. package/dist/pipeline/type-defs/artifacts.js +1 -0
  52. package/dist/pipeline/type-defs/artifacts.js.map +1 -1
  53. package/dist/pipeline/type-defs/audit.d.ts +3 -0
  54. package/dist/pipeline/type-defs/audit.d.ts.map +1 -1
  55. package/dist/pipeline/type-defs/checks.d.ts +1 -0
  56. package/dist/pipeline/type-defs/checks.d.ts.map +1 -1
  57. package/dist/pipeline/type-defs/packets.d.ts +15 -0
  58. package/dist/pipeline/type-defs/packets.d.ts.map +1 -1
  59. package/dist/pipeline/type-defs/state.d.ts +6 -0
  60. package/dist/pipeline/type-defs/state.d.ts.map +1 -1
  61. package/dist/pipeline/type-defs/state.js +2 -0
  62. package/dist/pipeline/type-defs/state.js.map +1 -1
  63. package/dist/types/consensus.d.ts +5 -1
  64. package/dist/types/consensus.d.ts.map +1 -1
  65. package/dist/types/consensus.js +15 -4
  66. package/dist/types/consensus.js.map +1 -1
  67. package/dist/types/index.d.ts +1 -1
  68. package/dist/types/index.d.ts.map +1 -1
  69. package/dist/types/index.js +1 -1
  70. package/dist/types/index.js.map +1 -1
  71. package/dist/types/project.d.ts +1 -1
  72. package/dist/types/project.d.ts.map +1 -1
  73. package/dist/types/project.js +39 -10
  74. package/dist/types/project.js.map +1 -1
  75. package/dist/types/workflow.d.ts +1 -7
  76. package/dist/types/workflow.d.ts.map +1 -1
  77. package/dist/types/workflow.js +1 -1
  78. package/dist/types/workflow.js.map +1 -1
  79. package/dist/upgrade/handlers.js +5 -5
  80. package/dist/upgrade/handlers.js.map +1 -1
  81. package/dist/workflow/index.d.ts.map +1 -1
  82. package/dist/workflow/index.js +18 -14
  83. package/dist/workflow/index.js.map +1 -1
  84. package/dist/workflow/website-strategy.js +1 -1
  85. package/dist/workflow/website-strategy.js.map +1 -1
  86. package/package.json +1 -1
  87. package/src/adapters/gemini.ts +3 -3
  88. package/src/adapters/openai.ts +2 -2
  89. package/src/auth/gemini.ts +1 -1
  90. package/src/cli/commands/create.ts +12 -6
  91. package/src/cli/commands/resume.ts +9 -1
  92. package/src/cli/interactive.ts +32 -3
  93. package/src/config/defaults.ts +7 -2
  94. package/src/config/popeye-md.ts +139 -0
  95. package/src/config/schema.ts +21 -8
  96. package/src/pipeline/bridges/review-bridge.ts +371 -0
  97. package/src/pipeline/consensus/consensus-runner.ts +3 -3
  98. package/src/pipeline/orchestrator.ts +8 -0
  99. package/src/pipeline/phases/implementation.ts +6 -2
  100. package/src/pipeline/phases/intake.ts +18 -4
  101. package/src/pipeline/phases/recovery-loop.ts +2 -0
  102. package/src/pipeline/type-defs/artifacts.ts +1 -0
  103. package/src/pipeline/type-defs/state.ts +2 -0
  104. package/src/types/consensus.ts +16 -4
  105. package/src/types/index.ts +1 -0
  106. package/src/types/project.ts +39 -10
  107. package/src/types/workflow.ts +1 -1
  108. package/src/upgrade/handlers.ts +5 -5
  109. package/src/workflow/index.ts +18 -14
  110. package/src/workflow/website-strategy.ts +1 -1
  111. package/tests/cli/model-command.test.ts +19 -9
  112. package/tests/config/config.test.ts +3 -3
  113. package/tests/config/popeye-md.test.ts +168 -0
  114. package/tests/pipeline/bridges/review-bridge.test.ts +243 -0
  115. package/tests/pipeline/session-guidance.test.ts +205 -0
  116. package/tests/types/consensus.test.ts +1 -1
  117. package/tests/workflow/pipeline-bootstrap.test.ts +162 -0
@@ -0,0 +1,371 @@
1
+ /**
2
+ * Review Bridge — connects /review (rich audit-mode scanner) to the pipeline
3
+ * artifact + CR system when a project is pipeline-managed.
4
+ *
5
+ * When pipeline state exists, /review produces pipeline-native audit_report
6
+ * artifacts and Change Requests instead of injecting recovery milestones
7
+ * into state.json. This keeps the pipeline as the single source of truth.
8
+ */
9
+
10
+ import { randomUUID } from 'node:crypto';
11
+ import type { ProjectState } from '../../types/workflow.js';
12
+ import type {
13
+ PipelineState,
14
+ PipelinePhase,
15
+ ArtifactEntry,
16
+ ArtifactRef,
17
+ } from '../types.js';
18
+ import type { AuditFinding as WorkflowAuditFinding, AuditCategory as WorkflowCategory, AuditSeverity as WorkflowSeverity } from '../../types/audit.js';
19
+ import type { AuditFinding as PipelineAuditFinding, AuditSeverity as PipelineSeverity } from '../type-defs/audit.js';
20
+ import type { ChangeRequest } from '../types.js';
21
+ import { createArtifactManager } from '../artifact-manager.js';
22
+ import { buildChangeRequest, formatChangeRequest, routeChangeRequest } from '../change-request.js';
23
+ import { generateRepoSnapshot, createSnapshotArtifact } from '../repo-snapshot.js';
24
+ import { scanProject } from '../../workflow/audit-scanner.js';
25
+ import { analyzeProject, calculateAuditScores } from '../../workflow/audit-analyzer.js';
26
+ import { buildSummaryReport, buildAuditReport } from '../../workflow/audit-reporter.js';
27
+ import { loadProject, updateState } from '../../state/index.js';
28
+
29
+ // ─── Types ───────────────────────────────────────────────
30
+
31
+ export interface ReviewBridgeOptions {
32
+ projectDir: string;
33
+ depth?: number;
34
+ strict?: boolean;
35
+ onProgress?: (stage: string, message: string) => void;
36
+ }
37
+
38
+ export interface ReviewBridgeResult {
39
+ success: boolean;
40
+ findingsCount: number;
41
+ changeRequestCount: number;
42
+ overallScore: number;
43
+ recommendation: string;
44
+ artifactsCreated: number;
45
+ error?: string;
46
+ }
47
+
48
+ // ─── Pipeline Detection ──────────────────────────────────
49
+
50
+ /**
51
+ * Check if a project is pipeline-managed.
52
+ * A project is pipeline-managed if its state has a pipeline object
53
+ * with a pipelinePhase field.
54
+ *
55
+ * @param state - The project state to check
56
+ * @returns True if pipeline-managed
57
+ */
58
+ export function isPipelineManaged(state: ProjectState): boolean {
59
+ const pipeline = (state as unknown as { pipeline?: PipelineState }).pipeline;
60
+ return !!pipeline?.pipelinePhase;
61
+ }
62
+
63
+ /**
64
+ * Extract pipeline state from project state.
65
+ *
66
+ * @param state - The project state
67
+ * @returns Pipeline state or undefined
68
+ */
69
+ export function extractPipelineState(state: ProjectState): PipelineState | undefined {
70
+ return (state as unknown as { pipeline?: PipelineState }).pipeline;
71
+ }
72
+
73
+ // ─── Severity Mapping ────────────────────────────────────
74
+
75
+ /** Map workflow audit severity to pipeline severity */
76
+ const SEVERITY_MAP: Record<WorkflowSeverity, PipelineSeverity> = {
77
+ critical: 'P0',
78
+ major: 'P1',
79
+ minor: 'P2',
80
+ info: 'P3',
81
+ };
82
+
83
+ export function mapSeverity(severity: WorkflowSeverity): PipelineSeverity {
84
+ return SEVERITY_MAP[severity];
85
+ }
86
+
87
+ // ─── Category Mapping ────────────────────────────────────
88
+
89
+ /** Map workflow audit categories to pipeline audit categories */
90
+ type PipelineCategory = 'integration' | 'config' | 'tests' | 'schema' | 'security' | 'deployment';
91
+
92
+ const CATEGORY_MAP: Record<WorkflowCategory, PipelineCategory> = {
93
+ 'feature-completeness': 'integration',
94
+ 'integration-wiring': 'integration',
95
+ 'test-coverage': 'tests',
96
+ 'config-deployment': 'config',
97
+ 'dependency-sanity': 'deployment',
98
+ 'consistency': 'schema',
99
+ 'security': 'security',
100
+ 'documentation': 'deployment',
101
+ };
102
+
103
+ export function mapCategory(category: WorkflowCategory): PipelineCategory {
104
+ return CATEGORY_MAP[category];
105
+ }
106
+
107
+ // ─── CR Routing ──────────────────────────────────────────
108
+
109
+ /** Determine CR change_type from pipeline audit category */
110
+ const CATEGORY_TO_CHANGE_TYPE: Record<PipelineCategory, ChangeRequest['change_type']> = {
111
+ integration: 'architecture',
112
+ schema: 'architecture',
113
+ security: 'requirement',
114
+ tests: 'config',
115
+ config: 'config',
116
+ deployment: 'config',
117
+ };
118
+
119
+ export function categoryToChangeType(category: PipelineCategory): ChangeRequest['change_type'] {
120
+ return CATEGORY_TO_CHANGE_TYPE[category];
121
+ }
122
+
123
+ // ─── Finding Conversion ──────────────────────────────────
124
+
125
+ /**
126
+ * Convert a workflow AuditFinding to a pipeline AuditFinding.
127
+ *
128
+ * @param finding - Workflow finding
129
+ * @param snapshotRef - Pipeline artifact ref for the repo snapshot
130
+ * @returns Pipeline-native audit finding
131
+ */
132
+ export function convertFinding(
133
+ finding: WorkflowAuditFinding,
134
+ snapshotRef: ArtifactRef,
135
+ ): PipelineAuditFinding {
136
+ const severity = mapSeverity(finding.severity);
137
+ return {
138
+ id: finding.id,
139
+ severity,
140
+ category: mapCategory(finding.category),
141
+ description: `${finding.title}: ${finding.description}`,
142
+ evidence: [snapshotRef],
143
+ file_path: finding.evidence[0]?.file,
144
+ line_number: finding.evidence[0]?.line,
145
+ suggested_owner: 'AUDITOR',
146
+ blocking: severity === 'P0' || severity === 'P1',
147
+ };
148
+ }
149
+
150
+ // ─── Bridge Orchestrator ─────────────────────────────────
151
+
152
+ /**
153
+ * Run /review through the pipeline bridge.
154
+ * Uses the rich audit-mode scanner but writes results as pipeline artifacts
155
+ * and creates Change Requests for blocking findings.
156
+ *
157
+ * Does NOT inject recovery milestones — the pipeline RECOVERY_LOOP handles fixes.
158
+ *
159
+ * @param options - Bridge options
160
+ * @returns Bridge result with counts and score
161
+ */
162
+ export async function runReviewBridge(options: ReviewBridgeOptions): Promise<ReviewBridgeResult> {
163
+ const { projectDir, onProgress } = options;
164
+ const depth = options.depth ?? 2;
165
+ const strict = options.strict ?? false;
166
+
167
+ try {
168
+ // 1. Load state and extract pipeline
169
+ const state = await loadProject(projectDir);
170
+ const pipeline = extractPipelineState(state);
171
+ if (!pipeline) {
172
+ return { success: false, findingsCount: 0, changeRequestCount: 0, overallScore: 0, recommendation: 'error', artifactsCreated: 0, error: 'No pipeline state found' };
173
+ }
174
+
175
+ const artifactManager = createArtifactManager(projectDir);
176
+ artifactManager.ensureDocsStructure();
177
+ const artifacts: ArtifactEntry[] = [];
178
+
179
+ // 2. Generate fresh repo snapshot (pipeline anchor)
180
+ onProgress?.('bridge', 'Generating repo snapshot...');
181
+ const snapshot = await generateRepoSnapshot(projectDir);
182
+ const snapshotEntry = createSnapshotArtifact(snapshot, artifactManager, 'AUDIT');
183
+ artifacts.push(snapshotEntry);
184
+ pipeline.latestRepoSnapshot = artifactManager.toArtifactRef(snapshotEntry);
185
+ const snapshotRef = artifactManager.toArtifactRef(snapshotEntry);
186
+
187
+ // 3. Run rich audit-mode scanner (Stage 1: Scan)
188
+ onProgress?.('bridge', 'Running project scan...');
189
+ const scan = await scanProject(
190
+ projectDir,
191
+ state.language,
192
+ (msg) => onProgress?.('bridge-scan', msg),
193
+ );
194
+ const summary = buildSummaryReport(scan, state);
195
+
196
+ onProgress?.(
197
+ 'bridge',
198
+ `Scan complete: ${scan.totalSourceFiles} source files, ${scan.totalLinesOfCode} LOC`,
199
+ );
200
+
201
+ // 4. Run AI analysis (Stage 2: Analyze)
202
+ onProgress?.('bridge', 'Running AI analysis...');
203
+ const { findings: workflowFindings, searchMetadata } = await analyzeProject(scan, state, {
204
+ depth,
205
+ strict,
206
+ projectDir,
207
+ });
208
+ const scores = calculateAuditScores(workflowFindings, scan);
209
+ const auditReport = buildAuditReport(summary, workflowFindings, scores, searchMetadata, { strict }, randomUUID());
210
+
211
+ onProgress?.(
212
+ 'bridge',
213
+ `Analysis complete: score ${scores.overallScore}%, ${workflowFindings.length} findings`,
214
+ );
215
+
216
+ // 5. Convert findings to pipeline format
217
+ const pipelineFindings = workflowFindings.map((f) => convertFinding(f, snapshotRef));
218
+
219
+ // 6. Build pipeline audit report and store as artifact
220
+ const pipelineAuditReport = {
221
+ audit_id: `audit-${randomUUID().split('-')[0]}`,
222
+ timestamp: new Date().toISOString(),
223
+ repo_snapshot: snapshotRef,
224
+ overall_status: (auditReport.recommendation === 'pass' ? 'PASS' : 'FAIL') as 'PASS' | 'FAIL',
225
+ findings: pipelineFindings,
226
+ system_risk_score: 100 - scores.overallScore,
227
+ recovery_required: auditReport.recommendation === 'major-rework',
228
+ };
229
+
230
+ const auditJsonEntry = artifactManager.createAndStoreJson(
231
+ 'audit_report',
232
+ pipelineAuditReport,
233
+ 'AUDIT',
234
+ );
235
+ artifacts.push(auditJsonEntry);
236
+
237
+ // Store raw text report too
238
+ const textReport = formatAuditSummary(pipelineFindings, scores.overallScore, auditReport.recommendation);
239
+ const auditTextEntry = artifactManager.createAndStoreText(
240
+ 'audit_report',
241
+ textReport,
242
+ 'AUDIT',
243
+ );
244
+ artifacts.push(auditTextEntry);
245
+
246
+ // 7. Create Change Requests for blocking findings
247
+ const changeRequests: ChangeRequest[] = [];
248
+ const blockingFindings = pipelineFindings.filter((f) => f.blocking);
249
+
250
+ if (blockingFindings.length > 0) {
251
+ // Group by category for targeted CRs
252
+ const byCategory = new Map<string, typeof pipelineFindings>();
253
+ for (const f of blockingFindings) {
254
+ const group = byCategory.get(f.category) ?? [];
255
+ group.push(f);
256
+ byCategory.set(f.category, group);
257
+ }
258
+
259
+ for (const [category, findings] of byCategory) {
260
+ const changeType = categoryToChangeType(category as PipelineCategory);
261
+ const cr = buildChangeRequest({
262
+ originPhase: 'AUDIT',
263
+ requestedBy: 'AUDITOR',
264
+ changeType,
265
+ description: `${findings.length} blocking ${category} finding(s): ${findings.map((f) => f.description.slice(0, 80)).join('; ')}`,
266
+ justification: 'Blocking audit findings from /review require pipeline resolution',
267
+ affectedArtifacts: [snapshotRef],
268
+ affectedPhases: getAffectedPhases(category as PipelineCategory),
269
+ riskLevel: findings.some((f) => f.severity === 'P0') ? 'high' : 'medium',
270
+ });
271
+ changeRequests.push(cr);
272
+
273
+ // Store CR as artifact
274
+ const crEntry = artifactManager.createAndStoreText(
275
+ 'change_request',
276
+ formatChangeRequest(cr),
277
+ 'AUDIT',
278
+ );
279
+ artifacts.push(crEntry);
280
+
281
+ // Register in pipeline state for orchestrator routing
282
+ if (!pipeline.pendingChangeRequests) {
283
+ pipeline.pendingChangeRequests = [];
284
+ }
285
+ pipeline.pendingChangeRequests.push({
286
+ cr_id: cr.cr_id,
287
+ change_type: cr.change_type,
288
+ target_phase: routeChangeRequest(cr),
289
+ status: 'proposed',
290
+ });
291
+ }
292
+ }
293
+
294
+ // 8. Persist pipeline state
295
+ pipeline.artifacts.push(...artifacts);
296
+
297
+ // Update INDEX.md
298
+ artifactManager.updateIndex(pipeline.artifacts);
299
+
300
+ // Save updated state (pipeline object is a reference on state)
301
+ await updateState(projectDir, {
302
+ auditReportPath: auditJsonEntry.path,
303
+ auditLastRunAt: new Date().toISOString(),
304
+ auditRunId: pipelineAuditReport.audit_id,
305
+ } as Partial<ProjectState>);
306
+
307
+ onProgress?.(
308
+ 'bridge',
309
+ `Bridge complete: ${artifacts.length} artifacts, ${changeRequests.length} CRs created`,
310
+ );
311
+
312
+ return {
313
+ success: true,
314
+ findingsCount: pipelineFindings.length,
315
+ changeRequestCount: changeRequests.length,
316
+ overallScore: scores.overallScore,
317
+ recommendation: auditReport.recommendation,
318
+ artifactsCreated: artifacts.length,
319
+ };
320
+ } catch (err) {
321
+ const error = err instanceof Error ? err.message : 'Unknown error';
322
+ return { success: false, findingsCount: 0, changeRequestCount: 0, overallScore: 0, recommendation: 'error', artifactsCreated: 0, error };
323
+ }
324
+ }
325
+
326
+ // ─── Helpers ─────────────────────────────────────────────
327
+
328
+ /** Get affected phases for a finding category */
329
+ function getAffectedPhases(category: PipelineCategory): PipelinePhase[] {
330
+ switch (category) {
331
+ case 'integration':
332
+ case 'schema':
333
+ return ['CONSENSUS_ARCHITECTURE', 'IMPLEMENTATION'];
334
+ case 'security':
335
+ return ['CONSENSUS_MASTER_PLAN', 'IMPLEMENTATION'];
336
+ case 'tests':
337
+ return ['QA_VALIDATION'];
338
+ case 'config':
339
+ case 'deployment':
340
+ return ['IMPLEMENTATION', 'PRODUCTION_GATE'];
341
+ }
342
+ }
343
+
344
+ /** Format a text summary of pipeline audit findings */
345
+ function formatAuditSummary(
346
+ findings: PipelineAuditFinding[],
347
+ score: number,
348
+ recommendation: string,
349
+ ): string {
350
+ const lines = [
351
+ '# Pipeline Audit Report (via /review bridge)',
352
+ '',
353
+ `**Score:** ${score}%`,
354
+ `**Recommendation:** ${recommendation}`,
355
+ `**Findings:** ${findings.length}`,
356
+ `**Blocking:** ${findings.filter((f) => f.blocking).length}`,
357
+ '',
358
+ '## Findings',
359
+ '',
360
+ ];
361
+
362
+ for (const f of findings) {
363
+ lines.push(`### [${f.severity}] ${f.description.slice(0, 120)}`);
364
+ lines.push(`- Category: ${f.category}`);
365
+ lines.push(`- Blocking: ${f.blocking ? 'Yes' : 'No'}`);
366
+ if (f.file_path) lines.push(`- File: ${f.file_path}${f.line_number ? `:${f.line_number}` : ''}`);
367
+ lines.push('');
368
+ }
369
+
370
+ return lines.join('\n');
371
+ }
@@ -43,8 +43,8 @@ export interface ReviewerProviderConfig {
43
43
  }
44
44
 
45
45
  const DEFAULT_PROVIDERS: ReviewerProviderConfig[] = [
46
- { provider: 'openai', model: 'gpt-4o', temperature: 0.3 },
47
- { provider: 'gemini', model: 'gemini-2.0-flash', temperature: 0.3 },
46
+ { provider: 'openai', model: 'gpt-4.1', temperature: 0.3 },
47
+ { provider: 'gemini', model: 'gemini-2.5-flash', temperature: 0.3 },
48
48
  ];
49
49
 
50
50
  // ─── Consensus Runner ────────────────────────────────────
@@ -138,7 +138,7 @@ export class ConsensusRunner {
138
138
  const vote: ReviewerVote = {
139
139
  reviewer_id: 'iterative-reviewer',
140
140
  provider: 'openai',
141
- model: this.config.consensusConfig?.openaiModel ?? 'gpt-4o',
141
+ model: this.config.consensusConfig?.openaiModel ?? 'gpt-4.1',
142
142
  temperature: this.config.consensusConfig?.temperature ?? 0.3,
143
143
  prompt_hash: createHash('sha256').update(prompt).digest('hex'),
144
144
  vote: result.approved ? 'APPROVE' : 'REJECT',
@@ -53,6 +53,8 @@ export interface PipelineOptions {
53
53
  projectDir: string;
54
54
  state: ProjectState;
55
55
  consensusConfig?: Partial<ConsensusConfig>;
56
+ /** User steering, upgrade context, or resume instructions */
57
+ additionalContext?: string;
56
58
  onPhaseStart?: (phase: PipelinePhase) => void;
57
59
  onPhaseComplete?: (phase: PipelinePhase, result: PhaseResult) => void;
58
60
  onProgress?: (message: string) => void;
@@ -88,6 +90,7 @@ export async function runPipeline(options: PipelineOptions): Promise<PipelineRes
88
90
  projectDir,
89
91
  state,
90
92
  consensusConfig,
93
+ additionalContext,
91
94
  onPhaseStart,
92
95
  onPhaseComplete,
93
96
  onProgress,
@@ -97,6 +100,11 @@ export async function runPipeline(options: PipelineOptions): Promise<PipelineRes
97
100
  const pipeline: PipelineState = (state as unknown as { pipeline?: PipelineState }).pipeline
98
101
  ?? createDefaultPipelineState();
99
102
 
103
+ // Persist user guidance in pipeline state so it survives resume
104
+ if (additionalContext && !pipeline.sessionGuidance) {
105
+ pipeline.sessionGuidance = additionalContext;
106
+ }
107
+
100
108
  // Create context dependencies
101
109
  const gateEngine = createGateEngine();
102
110
  const artifactManager = createArtifactManager(projectDir);
@@ -26,11 +26,15 @@ export async function runImplementation(context: PhaseContext): Promise<PhaseRes
26
26
  .join('\n\n');
27
27
  }
28
28
 
29
- // Run existing execution mode with optional role context
29
+ // Merge session guidance with role prompt so execution sees user intent
30
+ const guidance = pipeline.sessionGuidance;
31
+ const systemPrompt = [combinedRolePrompt, guidance].filter(Boolean).join('\n\n') || undefined;
32
+
33
+ // Run existing execution mode with optional role context + guidance
30
34
  const { runExecutionMode } = await import('../../workflow/execution-mode.js');
31
35
  await runExecutionMode({
32
36
  projectDir,
33
- ...(combinedRolePrompt ? { systemPrompt: combinedRolePrompt } : {}),
37
+ ...(systemPrompt ? { systemPrompt } : {}),
34
38
  });
35
39
 
36
40
  // Generate post-implementation repo snapshot
@@ -27,17 +27,31 @@ export async function runIntake(context: PhaseContext): Promise<PhaseResult> {
27
27
  }
28
28
  pipeline.constitutionHash = computeConstitutionHash(projectDir);
29
29
 
30
- // 3. Expand idea using existing workflow
30
+ // 3. Store additional_context artifact if session guidance provided
31
+ const guidance = pipeline.sessionGuidance ?? '';
32
+ if (guidance) {
33
+ const ctxEntry = artifactManager.createAndStoreText(
34
+ 'additional_context',
35
+ guidance,
36
+ 'INTAKE',
37
+ );
38
+ artifacts.push(ctxEntry);
39
+ }
40
+
41
+ // 4. Expand idea using existing workflow
31
42
  const { expandIdea, createPlan } = await import('../../workflow/plan-mode.js');
32
43
  const expandedIdea = await expandIdea(
33
44
  context.state.specification ?? context.state.idea ?? '',
34
45
  context.state.language,
35
46
  );
36
47
 
37
- // 4. Create master plan using existing workflow
38
- const plan = await createPlan(expandedIdea, '', context.state.language);
48
+ // 5. Create master plan prepend guidance so planner sees constraints first
49
+ const planInput = guidance
50
+ ? `${guidance}\n\n---\n\n${expandedIdea}`
51
+ : expandedIdea;
52
+ const plan = await createPlan(planInput, '', context.state.language);
39
53
 
40
- // 5. Store master plan as artifact
54
+ // 6. Store master plan as artifact
41
55
  const planEntry = artifactManager.createAndStoreText(
42
56
  'master_plan',
43
57
  plan,
@@ -33,9 +33,11 @@ export async function runRecoveryLoop(context: PhaseContext): Promise<PhaseResul
33
33
 
34
34
  // 3. Generate RCA via Claude with Debugger skill
35
35
  const { executePrompt } = await import('../../adapters/claude.js');
36
+ const guidance = pipeline.sessionGuidance;
36
37
  const rcaPrompt = [
37
38
  debuggerSkill.systemPrompt,
38
39
  '',
40
+ ...(guidance ? ['## User Guidance', guidance, ''] : []),
39
41
  '## Failure Evidence',
40
42
  failureEvidence,
41
43
  '',
@@ -32,6 +32,7 @@ export const ArtifactTypeSchema = z.enum([
32
32
  'resolved_commands',
33
33
  'constitution',
34
34
  'change_request',
35
+ 'additional_context',
35
36
  ]);
36
37
  export type ArtifactType = z.infer<typeof ArtifactTypeSchema>;
37
38
 
@@ -97,6 +97,8 @@ export const PipelineStateSchema = z.object({
97
97
  resolvedCommands: ResolvedCommandsSchema.optional(),
98
98
  /** Tracks which phase failed, for recovery routing */
99
99
  failedPhase: PipelinePhaseSchema.optional(),
100
+ /** Session guidance: user steering, upgrade context, or resume instructions */
101
+ sessionGuidance: z.string().optional(),
100
102
  /** Pending change requests that force re-routing to consensus phases (v1.1) */
101
103
  pendingChangeRequests: z.array(z.object({
102
104
  cr_id: z.string(),
@@ -101,8 +101,8 @@ export interface ConsensusConfig {
101
101
  export const DEFAULT_CONSENSUS_CONFIG: Omit<ConsensusConfig, 'openaiKey' | 'geminiKey' | 'grokKey'> = {
102
102
  threshold: 95,
103
103
  maxIterations: 10,
104
- openaiModel: 'gpt-4o',
105
- geminiModel: 'gemini-2.0-flash',
104
+ openaiModel: 'gpt-4.1',
105
+ geminiModel: 'gemini-2.5-flash',
106
106
  grokModel: DEFAULT_GROK_MODEL,
107
107
  reviewer: 'openai',
108
108
  arbitrator: 'gemini',
@@ -122,7 +122,19 @@ export const AIProviderSchema = z.enum(['openai', 'gemini', 'grok']);
122
122
  /**
123
123
  * Known Gemini models (used for suggestions and display, not strict validation)
124
124
  */
125
- export const KNOWN_GEMINI_MODELS = ['gemini-2.0-flash', 'gemini-1.5-pro', 'gemini-1.5-flash'] as const;
125
+ export const KNOWN_GEMINI_MODELS = [
126
+ 'gemini-2.5-pro', 'gemini-2.5-flash', 'gemini-2.5-flash-lite',
127
+ 'gemini-2.0-flash', 'gemini-2.0-pro',
128
+ 'gemini-1.5-pro', 'gemini-1.5-flash',
129
+ ] as const;
130
+
131
+ /**
132
+ * Known Grok models (used for suggestions and display, not strict validation)
133
+ */
134
+ export const KNOWN_GROK_MODELS = [
135
+ 'grok-4-0709', 'grok-3', 'grok-3-mini',
136
+ 'grok-3-fast', 'grok-3-mini-fast', 'grok-2',
137
+ ] as const;
126
138
 
127
139
  /**
128
140
  * Zod schema for Gemini model - accepts any non-empty string to support new models
@@ -144,7 +156,7 @@ export const ConsensusConfigSchema = z.object({
144
156
  geminiKey: z.string().optional(),
145
157
  grokKey: z.string().optional(),
146
158
  openaiModel: OpenAIModelSchema,
147
- geminiModel: GeminiModelSchema.default('gemini-2.0-flash'),
159
+ geminiModel: GeminiModelSchema.default('gemini-2.5-flash'),
148
160
  grokModel: GrokModelSchema.default(DEFAULT_GROK_MODEL),
149
161
  reviewer: AIProviderSchema.default('openai'),
150
162
  arbitrator: AIProviderSchema.default('gemini'),
@@ -58,6 +58,7 @@ export {
58
58
  DEFAULT_GROK_MODEL,
59
59
  AIProviderSchema,
60
60
  KNOWN_GEMINI_MODELS,
61
+ KNOWN_GROK_MODELS,
61
62
  GeminiModelSchema,
62
63
  GrokModelSchema,
63
64
  type AIProvider,
@@ -242,7 +242,12 @@ export interface WebsiteSpec {
242
242
  /**
243
243
  * Known OpenAI models (used for suggestions and display, not strict validation)
244
244
  */
245
- export const KNOWN_OPENAI_MODELS = ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'o1-preview', 'o1-mini'] as const;
245
+ export const KNOWN_OPENAI_MODELS = [
246
+ 'gpt-4.1', 'gpt-4.1-mini', 'gpt-4.1-nano',
247
+ 'o3', 'o3-mini', 'o4-mini',
248
+ 'gpt-4o', 'gpt-4o-mini',
249
+ 'gpt-4-turbo', 'o1-preview', 'o1-mini',
250
+ ] as const;
246
251
 
247
252
  /**
248
253
  * OpenAI model schema - accepts any non-empty string to support new models
@@ -289,24 +294,48 @@ export interface GenerationOptions {
289
294
  * Available OpenAI models with descriptions
290
295
  */
291
296
  export const OPENAI_MODELS: Record<OpenAIModel, { description: string; recommended: string }> = {
292
- 'gpt-4o': {
293
- description: 'Most capable, best reasoning',
297
+ 'gpt-4.1': {
298
+ description: 'Smartest non-reasoning model, 1M context',
294
299
  recommended: 'Complex projects',
295
300
  },
301
+ 'gpt-4.1-mini': {
302
+ description: 'Fast, strong instruction following',
303
+ recommended: 'Medium complexity',
304
+ },
305
+ 'gpt-4.1-nano': {
306
+ description: 'Fastest, most cost-efficient',
307
+ recommended: 'Simple projects',
308
+ },
309
+ 'o3': {
310
+ description: 'Strongest reasoning model',
311
+ recommended: 'Architectural decisions',
312
+ },
313
+ 'o3-mini': {
314
+ description: 'Efficient reasoning',
315
+ recommended: 'Code review',
316
+ },
317
+ 'o4-mini': {
318
+ description: 'Fast reasoning, best on STEM',
319
+ recommended: 'Technical analysis',
320
+ },
321
+ 'gpt-4o': {
322
+ description: 'Multimodal, strong all-rounder',
323
+ recommended: 'General purpose',
324
+ },
296
325
  'gpt-4o-mini': {
297
326
  description: 'Fast, cost-effective',
298
- recommended: 'Simple projects',
327
+ recommended: 'Simple tasks',
299
328
  },
300
329
  'gpt-4-turbo': {
301
- description: 'High capability, faster',
302
- recommended: 'Medium complexity',
330
+ description: 'High capability, legacy',
331
+ recommended: 'Backward compatibility',
303
332
  },
304
333
  'o1-preview': {
305
- description: 'Advanced reasoning',
306
- recommended: 'Architectural decisions',
334
+ description: 'Advanced reasoning (legacy)',
335
+ recommended: 'Legacy reasoning tasks',
307
336
  },
308
337
  'o1-mini': {
309
- description: 'Efficient reasoning',
310
- recommended: 'Code review',
338
+ description: 'Efficient reasoning (legacy)',
339
+ recommended: 'Legacy reasoning tasks',
311
340
  },
312
341
  };
@@ -254,7 +254,7 @@ export const ProjectStateSchema = z.object({
254
254
  name: z.string(),
255
255
  idea: z.string(),
256
256
  language: OutputLanguageSchema,
257
- openaiModel: z.enum(['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'o1-preview', 'o1-mini']),
257
+ openaiModel: z.string().min(1),
258
258
  phase: WorkflowPhaseSchema,
259
259
  status: ProjectStatusSchema,
260
260
  specification: z.string().optional(),