popeye-cli 2.0.0 → 2.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 (161) 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 +33 -4
  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/generators/all.d.ts.map +1 -1
  31. package/dist/generators/all.js +23 -1
  32. package/dist/generators/all.js.map +1 -1
  33. package/dist/pipeline/artifact-manager.d.ts.map +1 -1
  34. package/dist/pipeline/artifact-manager.js +3 -0
  35. package/dist/pipeline/artifact-manager.js.map +1 -1
  36. package/dist/pipeline/bridges/review-bridge.d.ts +70 -0
  37. package/dist/pipeline/bridges/review-bridge.d.ts.map +1 -0
  38. package/dist/pipeline/bridges/review-bridge.js +266 -0
  39. package/dist/pipeline/bridges/review-bridge.js.map +1 -0
  40. package/dist/pipeline/consensus/consensus-runner.js +3 -3
  41. package/dist/pipeline/consensus/consensus-runner.js.map +1 -1
  42. package/dist/pipeline/gate-engine.js +1 -1
  43. package/dist/pipeline/gate-engine.js.map +1 -1
  44. package/dist/pipeline/migration.d.ts.map +1 -1
  45. package/dist/pipeline/migration.js +3 -26
  46. package/dist/pipeline/migration.js.map +1 -1
  47. package/dist/pipeline/orchestrator.d.ts +2 -0
  48. package/dist/pipeline/orchestrator.d.ts.map +1 -1
  49. package/dist/pipeline/orchestrator.js +10 -1
  50. package/dist/pipeline/orchestrator.js.map +1 -1
  51. package/dist/pipeline/phases/implementation.d.ts.map +1 -1
  52. package/dist/pipeline/phases/implementation.js +5 -2
  53. package/dist/pipeline/phases/implementation.js.map +1 -1
  54. package/dist/pipeline/phases/intake.d.ts +1 -0
  55. package/dist/pipeline/phases/intake.d.ts.map +1 -1
  56. package/dist/pipeline/phases/intake.js +56 -8
  57. package/dist/pipeline/phases/intake.js.map +1 -1
  58. package/dist/pipeline/phases/recovery-loop.d.ts.map +1 -1
  59. package/dist/pipeline/phases/recovery-loop.js +2 -0
  60. package/dist/pipeline/phases/recovery-loop.js.map +1 -1
  61. package/dist/pipeline/phases/role-planning.d.ts.map +1 -1
  62. package/dist/pipeline/phases/role-planning.js +2 -3
  63. package/dist/pipeline/phases/role-planning.js.map +1 -1
  64. package/dist/pipeline/skills/constitution-generator.d.ts +51 -0
  65. package/dist/pipeline/skills/constitution-generator.d.ts.map +1 -0
  66. package/dist/pipeline/skills/constitution-generator.js +210 -0
  67. package/dist/pipeline/skills/constitution-generator.js.map +1 -0
  68. package/dist/pipeline/skills/generator.d.ts +65 -0
  69. package/dist/pipeline/skills/generator.d.ts.map +1 -0
  70. package/dist/pipeline/skills/generator.js +221 -0
  71. package/dist/pipeline/skills/generator.js.map +1 -0
  72. package/dist/pipeline/skills/role-map.d.ts +38 -0
  73. package/dist/pipeline/skills/role-map.d.ts.map +1 -0
  74. package/dist/pipeline/skills/role-map.js +234 -0
  75. package/dist/pipeline/skills/role-map.js.map +1 -0
  76. package/dist/pipeline/skills/types.d.ts +47 -0
  77. package/dist/pipeline/skills/types.d.ts.map +1 -0
  78. package/dist/pipeline/skills/types.js +5 -0
  79. package/dist/pipeline/skills/types.js.map +1 -0
  80. package/dist/pipeline/type-defs/artifacts.d.ts +10 -0
  81. package/dist/pipeline/type-defs/artifacts.d.ts.map +1 -1
  82. package/dist/pipeline/type-defs/artifacts.js +2 -0
  83. package/dist/pipeline/type-defs/artifacts.js.map +1 -1
  84. package/dist/pipeline/type-defs/audit.d.ts +6 -0
  85. package/dist/pipeline/type-defs/audit.d.ts.map +1 -1
  86. package/dist/pipeline/type-defs/checks.d.ts +2 -0
  87. package/dist/pipeline/type-defs/checks.d.ts.map +1 -1
  88. package/dist/pipeline/type-defs/packets.d.ts +30 -0
  89. package/dist/pipeline/type-defs/packets.d.ts.map +1 -1
  90. package/dist/pipeline/type-defs/state.d.ts +11 -0
  91. package/dist/pipeline/type-defs/state.d.ts.map +1 -1
  92. package/dist/pipeline/type-defs/state.js +2 -0
  93. package/dist/pipeline/type-defs/state.js.map +1 -1
  94. package/dist/types/consensus.d.ts +5 -1
  95. package/dist/types/consensus.d.ts.map +1 -1
  96. package/dist/types/consensus.js +15 -4
  97. package/dist/types/consensus.js.map +1 -1
  98. package/dist/types/index.d.ts +1 -1
  99. package/dist/types/index.d.ts.map +1 -1
  100. package/dist/types/index.js +1 -1
  101. package/dist/types/index.js.map +1 -1
  102. package/dist/types/project.d.ts +1 -1
  103. package/dist/types/project.d.ts.map +1 -1
  104. package/dist/types/project.js +39 -10
  105. package/dist/types/project.js.map +1 -1
  106. package/dist/types/workflow.d.ts +1 -7
  107. package/dist/types/workflow.d.ts.map +1 -1
  108. package/dist/types/workflow.js +1 -1
  109. package/dist/types/workflow.js.map +1 -1
  110. package/dist/upgrade/handlers.js +5 -5
  111. package/dist/upgrade/handlers.js.map +1 -1
  112. package/dist/workflow/index.d.ts.map +1 -1
  113. package/dist/workflow/index.js +18 -14
  114. package/dist/workflow/index.js.map +1 -1
  115. package/dist/workflow/website-strategy.js +1 -1
  116. package/dist/workflow/website-strategy.js.map +1 -1
  117. package/package.json +1 -1
  118. package/src/adapters/gemini.ts +3 -3
  119. package/src/adapters/openai.ts +2 -2
  120. package/src/auth/gemini.ts +1 -1
  121. package/src/cli/commands/create.ts +12 -6
  122. package/src/cli/commands/resume.ts +9 -1
  123. package/src/cli/interactive.ts +36 -4
  124. package/src/config/defaults.ts +7 -2
  125. package/src/config/popeye-md.ts +139 -0
  126. package/src/config/schema.ts +21 -8
  127. package/src/generators/all.ts +23 -1
  128. package/src/pipeline/artifact-manager.ts +3 -0
  129. package/src/pipeline/bridges/review-bridge.ts +371 -0
  130. package/src/pipeline/consensus/consensus-runner.ts +3 -3
  131. package/src/pipeline/gate-engine.ts +1 -1
  132. package/src/pipeline/migration.ts +5 -30
  133. package/src/pipeline/orchestrator.ts +14 -0
  134. package/src/pipeline/phases/implementation.ts +6 -2
  135. package/src/pipeline/phases/intake.ts +73 -10
  136. package/src/pipeline/phases/recovery-loop.ts +2 -0
  137. package/src/pipeline/phases/role-planning.ts +2 -3
  138. package/src/pipeline/skills/constitution-generator.ts +236 -0
  139. package/src/pipeline/skills/generator.ts +287 -0
  140. package/src/pipeline/skills/role-map.ts +248 -0
  141. package/src/pipeline/skills/types.ts +53 -0
  142. package/src/pipeline/type-defs/artifacts.ts +2 -0
  143. package/src/pipeline/type-defs/state.ts +2 -0
  144. package/src/types/consensus.ts +16 -4
  145. package/src/types/index.ts +1 -0
  146. package/src/types/project.ts +39 -10
  147. package/src/types/workflow.ts +1 -1
  148. package/src/upgrade/handlers.ts +5 -5
  149. package/src/workflow/index.ts +18 -14
  150. package/src/workflow/website-strategy.ts +1 -1
  151. package/tests/cli/model-command.test.ts +19 -9
  152. package/tests/config/config.test.ts +3 -3
  153. package/tests/config/popeye-md.test.ts +168 -0
  154. package/tests/pipeline/bridges/review-bridge.test.ts +243 -0
  155. package/tests/pipeline/migration.test.ts +4 -3
  156. package/tests/pipeline/session-guidance.test.ts +205 -0
  157. package/tests/pipeline/skills/constitution-generator.test.ts +201 -0
  158. package/tests/pipeline/skills/generator.test.ts +213 -0
  159. package/tests/pipeline/skills/role-map.test.ts +198 -0
  160. package/tests/types/consensus.test.ts +1 -1
  161. package/tests/workflow/pipeline-bootstrap.test.ts +162 -0
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Shared popeye.md configuration reader.
3
+ * Reads project-local config from the YAML frontmatter in popeye.md.
4
+ * Used by both interactive mode and CLI commands.
5
+ */
6
+
7
+ import { promises as fs } from 'node:fs';
8
+ import path from 'node:path';
9
+ import { OutputLanguageSchema, type OutputLanguage } from '../types/project.js';
10
+ import type { AIProvider, GeminiModel, GrokModel } from '../types/consensus.js';
11
+ import type { OpenAIModel } from '../types/project.js';
12
+
13
+ // ─── Types ───────────────────────────────────────────────
14
+
15
+ /** Project-local configuration stored in popeye.md */
16
+ export interface PopeyeMdConfig {
17
+ language: OutputLanguage;
18
+ reviewer: AIProvider;
19
+ arbitrator: AIProvider;
20
+ enableArbitration: boolean;
21
+ created: string;
22
+ lastRun: string;
23
+ projectName?: string;
24
+ description?: string;
25
+ notes?: string;
26
+ openaiModel?: OpenAIModel;
27
+ geminiModel?: GeminiModel;
28
+ grokModel?: GrokModel;
29
+ }
30
+
31
+ // ─── Reader ──────────────────────────────────────────────
32
+
33
+ /**
34
+ * Read popeye.md from a project directory.
35
+ * Parses YAML frontmatter for project configuration.
36
+ *
37
+ * @param projectDir - Absolute path to the project root
38
+ * @returns Parsed config or null if file doesn't exist or is invalid
39
+ */
40
+ export async function readPopeyeMdConfig(projectDir: string): Promise<PopeyeMdConfig | null> {
41
+ const configPath = path.join(projectDir, 'popeye.md');
42
+
43
+ try {
44
+ const content = await fs.readFile(configPath, 'utf-8');
45
+
46
+ // Parse YAML frontmatter
47
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
48
+ if (!frontmatterMatch) {
49
+ return null;
50
+ }
51
+
52
+ const frontmatter = frontmatterMatch[1];
53
+ const config: Partial<PopeyeMdConfig> = {};
54
+
55
+ // Parse each line of YAML
56
+ for (const line of frontmatter.split('\n')) {
57
+ const match = line.match(/^(\w+):\s*(.+)$/);
58
+ if (match) {
59
+ const [, key, value] = match;
60
+ const cleanValue = value.trim();
61
+
62
+ switch (key) {
63
+ case 'language':
64
+ if (OutputLanguageSchema.safeParse(cleanValue).success) {
65
+ config.language = cleanValue as OutputLanguage;
66
+ }
67
+ break;
68
+ case 'reviewer':
69
+ if (['openai', 'gemini', 'grok'].includes(cleanValue)) {
70
+ config.reviewer = cleanValue as AIProvider;
71
+ }
72
+ break;
73
+ case 'arbitrator':
74
+ if (['openai', 'gemini', 'grok', 'off'].includes(cleanValue)) {
75
+ if (cleanValue === 'off') {
76
+ config.enableArbitration = false;
77
+ } else {
78
+ config.arbitrator = cleanValue as AIProvider;
79
+ config.enableArbitration = true;
80
+ }
81
+ }
82
+ break;
83
+ case 'created':
84
+ config.created = cleanValue;
85
+ break;
86
+ case 'lastRun':
87
+ config.lastRun = cleanValue;
88
+ break;
89
+ case 'projectName':
90
+ config.projectName = cleanValue;
91
+ break;
92
+ case 'openaiModel':
93
+ if (cleanValue.length > 0) {
94
+ config.openaiModel = cleanValue;
95
+ }
96
+ break;
97
+ case 'geminiModel':
98
+ if (cleanValue.length > 0) {
99
+ config.geminiModel = cleanValue;
100
+ }
101
+ break;
102
+ case 'grokModel':
103
+ if (cleanValue.length > 0) {
104
+ config.grokModel = cleanValue;
105
+ }
106
+ break;
107
+ }
108
+ }
109
+ }
110
+
111
+ // Extract notes section if present
112
+ const notesMatch = content.match(/## Notes\n([\s\S]*?)(?=\n## |$)/);
113
+ if (notesMatch) {
114
+ config.notes = notesMatch[1].trim();
115
+ }
116
+
117
+ // Return config only if we have the essential fields
118
+ if (config.language && config.reviewer) {
119
+ return {
120
+ language: config.language,
121
+ reviewer: config.reviewer,
122
+ arbitrator: config.arbitrator || 'gemini',
123
+ enableArbitration: config.enableArbitration ?? true,
124
+ created: config.created || new Date().toISOString(),
125
+ lastRun: config.lastRun || new Date().toISOString(),
126
+ projectName: config.projectName,
127
+ description: config.description,
128
+ notes: config.notes,
129
+ openaiModel: config.openaiModel,
130
+ geminiModel: config.geminiModel,
131
+ grokModel: config.grokModel,
132
+ };
133
+ }
134
+
135
+ return null;
136
+ } catch {
137
+ return null;
138
+ }
139
+ }
@@ -23,14 +23,17 @@ export const ConsensusSettingsSchema = z.object({
23
23
  * OpenAI API settings schema
24
24
  */
25
25
  export const OpenAISettingsSchema = z.object({
26
- model: z
27
- .enum(['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'o1-preview', 'o1-mini'])
28
- .default('gpt-4o'),
26
+ model: z.string().min(1).default('gpt-4.1'),
29
27
  temperature: z.number().min(0).max(2).default(0.3),
30
28
  max_tokens: z.number().min(100).max(32000).default(4096),
31
29
  available_models: z
32
30
  .array(z.string())
33
- .default(['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'o1-preview', 'o1-mini']),
31
+ .default([
32
+ 'gpt-4.1', 'gpt-4.1-mini', 'gpt-4.1-nano',
33
+ 'o3', 'o3-mini', 'o4-mini',
34
+ 'gpt-4o', 'gpt-4o-mini',
35
+ 'gpt-4-turbo', 'o1-preview', 'o1-mini',
36
+ ]),
34
37
  });
35
38
 
36
39
  /**
@@ -55,10 +58,15 @@ export const GrokSettingsSchema = z.object({
55
58
  */
56
59
  export const APISettingsSchema = z.object({
57
60
  openai: OpenAISettingsSchema.default({
58
- model: 'gpt-4o',
61
+ model: 'gpt-4.1',
59
62
  temperature: 0.3,
60
63
  max_tokens: 4096,
61
- available_models: ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'o1-preview', 'o1-mini'],
64
+ available_models: [
65
+ 'gpt-4.1', 'gpt-4.1-mini', 'gpt-4.1-nano',
66
+ 'o3', 'o3-mini', 'o4-mini',
67
+ 'gpt-4o', 'gpt-4o-mini',
68
+ 'gpt-4-turbo', 'o1-preview', 'o1-mini',
69
+ ],
62
70
  }),
63
71
  claude: ClaudeSettingsSchema.default({
64
72
  model: 'claude-sonnet-4-20250514',
@@ -139,10 +147,15 @@ export const ConfigSchema = z.object({
139
147
  }),
140
148
  apis: APISettingsSchema.default({
141
149
  openai: {
142
- model: 'gpt-4o',
150
+ model: 'gpt-4.1',
143
151
  temperature: 0.3,
144
152
  max_tokens: 4096,
145
- available_models: ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'o1-preview', 'o1-mini'],
153
+ available_models: [
154
+ 'gpt-4.1', 'gpt-4.1-mini', 'gpt-4.1-nano',
155
+ 'o3', 'o3-mini', 'o4-mini',
156
+ 'gpt-4o', 'gpt-4o-mini',
157
+ 'gpt-4-turbo', 'o1-preview', 'o1-mini',
158
+ ],
146
159
  },
147
160
  claude: {
148
161
  model: 'claude-sonnet-4-20250514',
@@ -10,7 +10,8 @@ import type { GenerationResult } from './python.js';
10
10
  import { generateFullstackProject } from './fullstack.js';
11
11
  import { generateWebsiteProject } from './website.js';
12
12
  import type { WebsiteContentContext } from './website-context.js';
13
- import { buildWebsiteContext, validateWebsiteContext } from './website-context.js';
13
+ import { buildWebsiteContext, validateWebsiteContext, resolveBrandAssets } from './website-context.js';
14
+ import { loadWebsiteStrategy } from '../workflow/website-strategy.js';
14
15
  import {
15
16
  generateDesignTokensPackage as generateDesignTokensPackageImpl,
16
17
  generateUiPackage as generateUiPackageImpl,
@@ -403,6 +404,25 @@ export async function generateAllProject(
403
404
  console.warn(`[website-context] Warning: ${contextWarning}`);
404
405
  }
405
406
 
407
+ // Resolve brand assets (logo output path) — must happen after buildWebsiteContext
408
+ if (contentContext?.brand) {
409
+ try {
410
+ contentContext.brandAssets = await resolveBrandAssets(projectDir, contentContext.brand);
411
+ } catch {
412
+ // Non-fatal: brand assets are optional
413
+ }
414
+ }
415
+
416
+ // Load website strategy if previously generated (e.g. re-scaffold)
417
+ try {
418
+ const strategyData = await loadWebsiteStrategy(projectDir);
419
+ if (strategyData && contentContext) {
420
+ contentContext.strategy = strategyData.strategy;
421
+ }
422
+ } catch {
423
+ // Strategy won't exist on first scaffold — generated later by runPlanMode
424
+ }
425
+
406
426
  // Soft validation: log quality issues without blocking monorepo generation
407
427
  if (contentContext) {
408
428
  const validation = validateWebsiteContext(contentContext, projectName);
@@ -419,12 +439,14 @@ export async function generateAllProject(
419
439
  filesCreated.push(...fullstackResult.filesCreated);
420
440
 
421
441
  // Generate website app
442
+ // skipValidation: strategy is generated later by runPlanMode, not at scaffold time
422
443
  const websiteResult = await generateWebsiteProject(spec, projectDir, {
423
444
  baseDir: path.join(projectDir, 'apps', 'website'),
424
445
  workspaceMode: true,
425
446
  skipDocker: true, // Website runs outside Docker (npm run dev / npm start)
426
447
  skipReadme: false,
427
448
  contentContext: contentContext,
449
+ skipValidation: true,
428
450
  });
429
451
  if (!websiteResult.success) {
430
452
  return {
@@ -45,6 +45,8 @@ const ARTIFACT_DIRS: Record<string, string> = {
45
45
  resolved_commands: 'checks',
46
46
  constitution: 'governance',
47
47
  change_request: 'governance',
48
+ additional_context: 'context',
49
+ skill_generation_log: 'context',
48
50
  };
49
51
 
50
52
  /** All required subdirectories under /docs/ */
@@ -62,6 +64,7 @@ const DOCS_SUBDIRS = [
62
64
  'checks',
63
65
  'journal',
64
66
  'governance',
67
+ 'context',
65
68
  ];
66
69
 
67
70
  // ─── Helper Functions ────────────────────────────────────
@@ -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',
@@ -40,7 +40,7 @@ export interface GateResult {
40
40
  const GATE_DEFINITIONS: Record<PipelinePhase, GateDefinition> = {
41
41
  INTAKE: {
42
42
  phase: 'INTAKE',
43
- requiredArtifacts: ['master_plan', 'repo_snapshot', 'constitution'],
43
+ requiredArtifacts: ['master_plan', 'repo_snapshot'],
44
44
  requiredChecks: [],
45
45
  allowedTransitions: ['CONSENSUS_MASTER_PLAN'],
46
46
  failTransition: 'RECOVERY_LOOP',