popeye-cli 1.7.0 → 1.9.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 (174) hide show
  1. package/README.md +148 -7
  2. package/cheatsheet.md +440 -0
  3. package/dist/cli/commands/db.d.ts +10 -0
  4. package/dist/cli/commands/db.d.ts.map +1 -0
  5. package/dist/cli/commands/db.js +240 -0
  6. package/dist/cli/commands/db.js.map +1 -0
  7. package/dist/cli/commands/doctor.d.ts +18 -0
  8. package/dist/cli/commands/doctor.d.ts.map +1 -0
  9. package/dist/cli/commands/doctor.js +255 -0
  10. package/dist/cli/commands/doctor.js.map +1 -0
  11. package/dist/cli/commands/index.d.ts +3 -0
  12. package/dist/cli/commands/index.d.ts.map +1 -1
  13. package/dist/cli/commands/index.js +3 -0
  14. package/dist/cli/commands/index.js.map +1 -1
  15. package/dist/cli/commands/review.d.ts +31 -0
  16. package/dist/cli/commands/review.d.ts.map +1 -0
  17. package/dist/cli/commands/review.js +156 -0
  18. package/dist/cli/commands/review.js.map +1 -0
  19. package/dist/cli/index.d.ts.map +1 -1
  20. package/dist/cli/index.js +4 -1
  21. package/dist/cli/index.js.map +1 -1
  22. package/dist/cli/interactive.d.ts.map +1 -1
  23. package/dist/cli/interactive.js +218 -61
  24. package/dist/cli/interactive.js.map +1 -1
  25. package/dist/generators/admin-wizard.d.ts +25 -0
  26. package/dist/generators/admin-wizard.d.ts.map +1 -0
  27. package/dist/generators/admin-wizard.js +123 -0
  28. package/dist/generators/admin-wizard.js.map +1 -0
  29. package/dist/generators/all.d.ts.map +1 -1
  30. package/dist/generators/all.js +10 -3
  31. package/dist/generators/all.js.map +1 -1
  32. package/dist/generators/database.d.ts +58 -0
  33. package/dist/generators/database.d.ts.map +1 -0
  34. package/dist/generators/database.js +229 -0
  35. package/dist/generators/database.js.map +1 -0
  36. package/dist/generators/fullstack.d.ts.map +1 -1
  37. package/dist/generators/fullstack.js +23 -7
  38. package/dist/generators/fullstack.js.map +1 -1
  39. package/dist/generators/index.d.ts +2 -0
  40. package/dist/generators/index.d.ts.map +1 -1
  41. package/dist/generators/index.js +2 -0
  42. package/dist/generators/index.js.map +1 -1
  43. package/dist/generators/templates/admin-wizard-python.d.ts +32 -0
  44. package/dist/generators/templates/admin-wizard-python.d.ts.map +1 -0
  45. package/dist/generators/templates/admin-wizard-python.js +425 -0
  46. package/dist/generators/templates/admin-wizard-python.js.map +1 -0
  47. package/dist/generators/templates/admin-wizard-react.d.ts +48 -0
  48. package/dist/generators/templates/admin-wizard-react.d.ts.map +1 -0
  49. package/dist/generators/templates/admin-wizard-react.js +554 -0
  50. package/dist/generators/templates/admin-wizard-react.js.map +1 -0
  51. package/dist/generators/templates/database-docker.d.ts +23 -0
  52. package/dist/generators/templates/database-docker.d.ts.map +1 -0
  53. package/dist/generators/templates/database-docker.js +221 -0
  54. package/dist/generators/templates/database-docker.js.map +1 -0
  55. package/dist/generators/templates/database-python.d.ts +54 -0
  56. package/dist/generators/templates/database-python.d.ts.map +1 -0
  57. package/dist/generators/templates/database-python.js +723 -0
  58. package/dist/generators/templates/database-python.js.map +1 -0
  59. package/dist/generators/templates/database-typescript.d.ts +34 -0
  60. package/dist/generators/templates/database-typescript.d.ts.map +1 -0
  61. package/dist/generators/templates/database-typescript.js +232 -0
  62. package/dist/generators/templates/database-typescript.js.map +1 -0
  63. package/dist/generators/templates/fullstack.d.ts.map +1 -1
  64. package/dist/generators/templates/fullstack.js +29 -0
  65. package/dist/generators/templates/fullstack.js.map +1 -1
  66. package/dist/generators/templates/index.d.ts +5 -0
  67. package/dist/generators/templates/index.d.ts.map +1 -1
  68. package/dist/generators/templates/index.js +5 -0
  69. package/dist/generators/templates/index.js.map +1 -1
  70. package/dist/state/index.d.ts +10 -0
  71. package/dist/state/index.d.ts.map +1 -1
  72. package/dist/state/index.js +21 -0
  73. package/dist/state/index.js.map +1 -1
  74. package/dist/types/audit.d.ts +623 -0
  75. package/dist/types/audit.d.ts.map +1 -0
  76. package/dist/types/audit.js +240 -0
  77. package/dist/types/audit.js.map +1 -0
  78. package/dist/types/database-runtime.d.ts +86 -0
  79. package/dist/types/database-runtime.d.ts.map +1 -0
  80. package/dist/types/database-runtime.js +61 -0
  81. package/dist/types/database-runtime.js.map +1 -0
  82. package/dist/types/database.d.ts +85 -0
  83. package/dist/types/database.d.ts.map +1 -0
  84. package/dist/types/database.js +71 -0
  85. package/dist/types/database.js.map +1 -0
  86. package/dist/types/index.d.ts +2 -0
  87. package/dist/types/index.d.ts.map +1 -1
  88. package/dist/types/index.js +4 -0
  89. package/dist/types/index.js.map +1 -1
  90. package/dist/types/workflow.d.ts +36 -0
  91. package/dist/types/workflow.d.ts.map +1 -1
  92. package/dist/types/workflow.js +7 -0
  93. package/dist/types/workflow.js.map +1 -1
  94. package/dist/workflow/audit-analyzer.d.ts +58 -0
  95. package/dist/workflow/audit-analyzer.d.ts.map +1 -0
  96. package/dist/workflow/audit-analyzer.js +420 -0
  97. package/dist/workflow/audit-analyzer.js.map +1 -0
  98. package/dist/workflow/audit-mode.d.ts +28 -0
  99. package/dist/workflow/audit-mode.d.ts.map +1 -0
  100. package/dist/workflow/audit-mode.js +169 -0
  101. package/dist/workflow/audit-mode.js.map +1 -0
  102. package/dist/workflow/audit-recovery.d.ts +61 -0
  103. package/dist/workflow/audit-recovery.d.ts.map +1 -0
  104. package/dist/workflow/audit-recovery.js +242 -0
  105. package/dist/workflow/audit-recovery.js.map +1 -0
  106. package/dist/workflow/audit-reporter.d.ts +65 -0
  107. package/dist/workflow/audit-reporter.d.ts.map +1 -0
  108. package/dist/workflow/audit-reporter.js +301 -0
  109. package/dist/workflow/audit-reporter.js.map +1 -0
  110. package/dist/workflow/audit-scanner.d.ts +87 -0
  111. package/dist/workflow/audit-scanner.d.ts.map +1 -0
  112. package/dist/workflow/audit-scanner.js +768 -0
  113. package/dist/workflow/audit-scanner.js.map +1 -0
  114. package/dist/workflow/db-setup-runner.d.ts +63 -0
  115. package/dist/workflow/db-setup-runner.d.ts.map +1 -0
  116. package/dist/workflow/db-setup-runner.js +336 -0
  117. package/dist/workflow/db-setup-runner.js.map +1 -0
  118. package/dist/workflow/db-state-machine.d.ts +30 -0
  119. package/dist/workflow/db-state-machine.d.ts.map +1 -0
  120. package/dist/workflow/db-state-machine.js +51 -0
  121. package/dist/workflow/db-state-machine.js.map +1 -0
  122. package/dist/workflow/index.d.ts +7 -0
  123. package/dist/workflow/index.d.ts.map +1 -1
  124. package/dist/workflow/index.js +7 -0
  125. package/dist/workflow/index.js.map +1 -1
  126. package/package.json +1 -1
  127. package/src/cli/commands/db.ts +281 -0
  128. package/src/cli/commands/doctor.ts +273 -0
  129. package/src/cli/commands/index.ts +3 -0
  130. package/src/cli/commands/review.ts +187 -0
  131. package/src/cli/index.ts +6 -0
  132. package/src/cli/interactive.ts +174 -4
  133. package/src/generators/admin-wizard.ts +146 -0
  134. package/src/generators/all.ts +10 -3
  135. package/src/generators/database.ts +286 -0
  136. package/src/generators/fullstack.ts +26 -9
  137. package/src/generators/index.ts +12 -0
  138. package/src/generators/templates/admin-wizard-python.ts +431 -0
  139. package/src/generators/templates/admin-wizard-react.ts +560 -0
  140. package/src/generators/templates/database-docker.ts +227 -0
  141. package/src/generators/templates/database-python.ts +734 -0
  142. package/src/generators/templates/database-typescript.ts +238 -0
  143. package/src/generators/templates/fullstack.ts +29 -0
  144. package/src/generators/templates/index.ts +5 -0
  145. package/src/state/index.ts +28 -0
  146. package/src/types/audit.ts +294 -0
  147. package/src/types/database-runtime.ts +69 -0
  148. package/src/types/database.ts +84 -0
  149. package/src/types/index.ts +29 -0
  150. package/src/types/workflow.ts +20 -0
  151. package/src/workflow/audit-analyzer.ts +491 -0
  152. package/src/workflow/audit-mode.ts +240 -0
  153. package/src/workflow/audit-recovery.ts +284 -0
  154. package/src/workflow/audit-reporter.ts +370 -0
  155. package/src/workflow/audit-scanner.ts +873 -0
  156. package/src/workflow/db-setup-runner.ts +391 -0
  157. package/src/workflow/db-state-machine.ts +58 -0
  158. package/src/workflow/index.ts +7 -0
  159. package/tests/cli/commands/review.test.ts +52 -0
  160. package/tests/generators/admin-wizard-orchestrator.test.ts +64 -0
  161. package/tests/generators/admin-wizard-templates.test.ts +366 -0
  162. package/tests/generators/cross-phase-integration.test.ts +383 -0
  163. package/tests/generators/database.test.ts +456 -0
  164. package/tests/generators/fe-be-db-integration.test.ts +613 -0
  165. package/tests/types/audit.test.ts +250 -0
  166. package/tests/types/database-runtime.test.ts +158 -0
  167. package/tests/types/database.test.ts +187 -0
  168. package/tests/workflow/audit-analyzer.test.ts +281 -0
  169. package/tests/workflow/audit-mode.test.ts +114 -0
  170. package/tests/workflow/audit-recovery.test.ts +237 -0
  171. package/tests/workflow/audit-reporter.test.ts +254 -0
  172. package/tests/workflow/audit-scanner.test.ts +270 -0
  173. package/tests/workflow/db-setup-runner.test.ts +211 -0
  174. package/tests/workflow/db-state-machine.test.ts +117 -0
@@ -0,0 +1,187 @@
1
+ /**
2
+ * CLI command: popeye review
3
+ *
4
+ * Runs a post-build audit/review of the project, producing a structured
5
+ * report with findings and optional recovery tasks.
6
+ *
7
+ * Pattern follows doctor.ts command factory.
8
+ */
9
+
10
+ import { Command } from 'commander';
11
+ import path from 'node:path';
12
+ import {
13
+ printHeader,
14
+ printSuccess,
15
+ printError,
16
+ printWarning,
17
+ printInfo,
18
+ printKeyValue,
19
+ printSection,
20
+ } from '../output.js';
21
+ import { runAuditMode, type AuditModeRunOptions } from '../../workflow/audit-mode.js';
22
+ import type { AuditModeResult, ComponentKind } from '../../types/audit.js';
23
+
24
+ // ---------------------------------------------------------------------------
25
+ // Run audit (exported for testability + slash command reuse)
26
+ // ---------------------------------------------------------------------------
27
+
28
+ /**
29
+ * Execute the audit and print results to the console.
30
+ *
31
+ * @param projectDir - Absolute path to the project directory.
32
+ * @param options - CLI options.
33
+ * @returns The audit result.
34
+ */
35
+ export async function runReview(
36
+ projectDir: string,
37
+ options: {
38
+ depth?: number;
39
+ strict?: boolean;
40
+ format?: 'json' | 'md' | 'both';
41
+ recover?: boolean;
42
+ target?: string;
43
+ } = {}
44
+ ): Promise<AuditModeResult> {
45
+ printHeader('Project Audit / Review');
46
+
47
+ const auditOptions: AuditModeRunOptions = {
48
+ projectDir,
49
+ depth: options.depth ?? 2,
50
+ runTests: true,
51
+ strict: options.strict ?? false,
52
+ format: options.format ?? 'both',
53
+ autoRecover: options.recover ?? true,
54
+ target: (options.target ?? 'all') as 'all' | ComponentKind,
55
+ onProgress: (stage, message) => {
56
+ printInfo(`[${stage}] ${message}`);
57
+ },
58
+ };
59
+
60
+ const result = await runAuditMode(auditOptions);
61
+
62
+ if (!result.success) {
63
+ printError(`Audit failed: ${result.error}`);
64
+ return result;
65
+ }
66
+
67
+ // Print summary
68
+ console.log();
69
+ printSection('Summary');
70
+ printKeyValue('Project', result.summary.projectName);
71
+ printKeyValue('Language', result.summary.language);
72
+ printKeyValue('Source files', result.summary.totalSourceFiles);
73
+ printKeyValue('Test files', result.summary.totalTestFiles);
74
+ printKeyValue('Lines of code', result.summary.totalLinesOfCode);
75
+ printKeyValue('Components', result.summary.componentCount);
76
+
77
+ // Print score
78
+ console.log();
79
+ printSection('Audit Score');
80
+ const score = result.audit.overallScore;
81
+ if (score >= 80) {
82
+ printSuccess(`Overall: ${score}/100`);
83
+ } else if (score >= 60) {
84
+ printWarning(`Overall: ${score}/100`);
85
+ } else {
86
+ printError(`Overall: ${score}/100`);
87
+ }
88
+
89
+ // Print finding counts
90
+ if (result.audit.criticalCount > 0) {
91
+ printError(`Critical: ${result.audit.criticalCount}`);
92
+ }
93
+ if (result.audit.majorCount > 0) {
94
+ printWarning(`Major: ${result.audit.majorCount}`);
95
+ }
96
+ if (result.audit.minorCount > 0) {
97
+ printInfo(`Minor: ${result.audit.minorCount}`);
98
+ }
99
+ if (result.audit.infoCount > 0) {
100
+ printInfo(`Info: ${result.audit.infoCount}`);
101
+ }
102
+
103
+ // Recommendation
104
+ console.log();
105
+ const rec = result.audit.recommendation;
106
+ if (rec === 'pass') {
107
+ printSuccess(`Recommendation: ${rec}`);
108
+ } else if (rec === 'fix-and-recheck') {
109
+ printWarning(`Recommendation: ${rec}`);
110
+ } else {
111
+ printError(`Recommendation: ${rec}`);
112
+ }
113
+
114
+ // Report paths
115
+ if (Object.keys(result.reportPaths).length > 0) {
116
+ console.log();
117
+ printSection('Reports');
118
+ if (result.reportPaths.auditMd) {
119
+ printInfo(`Markdown: ${result.reportPaths.auditMd}`);
120
+ }
121
+ if (result.reportPaths.auditJson) {
122
+ printInfo(`JSON: ${result.reportPaths.auditJson}`);
123
+ }
124
+ if (result.reportPaths.recoveryMd) {
125
+ printWarning(`Recovery plan: ${result.reportPaths.recoveryMd}`);
126
+ }
127
+ }
128
+
129
+ // Recovery info
130
+ if (result.recovery) {
131
+ console.log();
132
+ printSection('Recovery Plan');
133
+ printWarning(
134
+ `${result.recovery.milestones.length} recovery milestone(s), estimated ${result.recovery.estimatedEffort}`
135
+ );
136
+ if (auditOptions.autoRecover) {
137
+ printSuccess('Recovery milestones injected — run /resume to execute.');
138
+ } else {
139
+ printInfo('Run without --no-recover to auto-inject recovery milestones.');
140
+ }
141
+ }
142
+
143
+ return result;
144
+ }
145
+
146
+ // ---------------------------------------------------------------------------
147
+ // Commander command factory
148
+ // ---------------------------------------------------------------------------
149
+
150
+ /**
151
+ * Create the `popeye review` CLI command.
152
+ *
153
+ * @returns Commander command instance.
154
+ */
155
+ export function createReviewCommand(): Command {
156
+ const cmd = new Command('review')
157
+ .alias('audit')
158
+ .description('Run a post-build audit/review of the project')
159
+ .argument('[directory]', 'Project directory', '.')
160
+ .option('-d, --depth <level>', 'Audit depth: 1=shallow, 2=standard, 3=deep', '2')
161
+ .option('-s, --strict', 'Enable strict mode (higher standards)', false)
162
+ .option('-f, --format <type>', 'Output format: json, md, both', 'both')
163
+ .option('--no-recover', 'Skip auto-injection of recovery milestones')
164
+ .option('-t, --target <kind>', 'Audit target: all, frontend, backend, website', 'all')
165
+ .action(async (directory: string, opts: Record<string, string | boolean>) => {
166
+ const projectDir = path.resolve(directory);
167
+
168
+ try {
169
+ const result = await runReview(projectDir, {
170
+ depth: parseInt(opts.depth as string, 10),
171
+ strict: opts.strict as boolean,
172
+ format: opts.format as 'json' | 'md' | 'both',
173
+ recover: opts.recover as boolean,
174
+ target: opts.target as string,
175
+ });
176
+
177
+ if (!result.success) {
178
+ process.exit(1);
179
+ }
180
+ } catch (err) {
181
+ printError(err instanceof Error ? err.message : 'Audit failed');
182
+ process.exit(1);
183
+ }
184
+ });
185
+
186
+ return cmd;
187
+ }
package/src/cli/index.ts CHANGED
@@ -15,6 +15,9 @@ import {
15
15
  createResetCommand,
16
16
  createCancelCommand,
17
17
  createConfigCommand,
18
+ createDbCommand,
19
+ createDoctorCommand,
20
+ createReviewCommand,
18
21
  } from './commands/index.js';
19
22
  import { startInteractiveMode } from './interactive.js';
20
23
  import { printError } from './output.js';
@@ -54,6 +57,9 @@ export function createProgram(): Command {
54
57
  program.addCommand(createResetCommand());
55
58
  program.addCommand(createCancelCommand());
56
59
  program.addCommand(createConfigCommand());
60
+ program.addCommand(createDbCommand());
61
+ program.addCommand(createDoctorCommand());
62
+ program.addCommand(createReviewCommand());
57
63
 
58
64
  // Interactive mode command
59
65
  program
@@ -844,6 +844,9 @@ function showHelp(): void {
844
844
  ['/new <idea>', 'Force start a new project (skips existing check)'],
845
845
  ['/resume', 'Resume interrupted project'],
846
846
  ['/overview [fix]', 'Project review with analysis; fix to auto-discover docs'],
847
+ ['/db [action]', 'Database management (status/configure/apply)'],
848
+ ['/doctor', 'Run database and project readiness checks'],
849
+ ['/review', 'Run post-build audit/review with findings and recovery'],
847
850
  ['/clear', 'Clear screen'],
848
851
  ['/exit', 'Exit Popeye'],
849
852
  ];
@@ -1012,6 +1015,19 @@ async function handleInput(input: string, state: SessionState): Promise<boolean>
1012
1015
  }
1013
1016
  break;
1014
1017
 
1018
+ case '/db':
1019
+ await handleDbSlashCommand(state, args);
1020
+ break;
1021
+
1022
+ case '/doctor':
1023
+ await handleDoctorSlashCommand(state);
1024
+ break;
1025
+
1026
+ case '/review':
1027
+ case '/audit':
1028
+ await handleReviewSlashCommand(state, args);
1029
+ break;
1030
+
1015
1031
  default:
1016
1032
  printError(`Unknown command: ${cmd}`);
1017
1033
  printInfo('Type /help for available commands');
@@ -1055,6 +1071,143 @@ async function handleStatus(state: SessionState): Promise<void> {
1055
1071
  console.log(summary);
1056
1072
  }
1057
1073
 
1074
+ /**
1075
+ * Handle /db slash command - database management
1076
+ */
1077
+ async function handleDbSlashCommand(state: SessionState, args: string[]): Promise<void> {
1078
+ if (!state.projectDir) {
1079
+ printError('No active project. Create or resume a project first.');
1080
+ return;
1081
+ }
1082
+
1083
+ const action = args[0] || 'status';
1084
+
1085
+ switch (action) {
1086
+ case 'status': {
1087
+ try {
1088
+ const { loadProject } = await import('../state/index.js');
1089
+ const { DEFAULT_DB_CONFIG } = await import('../types/database.js');
1090
+ const projectState = await loadProject(state.projectDir);
1091
+ const dbConfig = projectState.dbConfig || { ...DEFAULT_DB_CONFIG, designed: false };
1092
+
1093
+ console.log();
1094
+ printInfo('Database Status:');
1095
+ console.log(` Designed: ${dbConfig.designed ? 'Yes' : 'No'}`);
1096
+ console.log(` Status: ${dbConfig.status}`);
1097
+ console.log(` Mode: ${dbConfig.mode || 'not set'}`);
1098
+ console.log(` Vector: ${dbConfig.vectorRequired ? 'Yes' : 'No'}`);
1099
+ console.log(` Migrations: ${dbConfig.migrationsApplied}`);
1100
+ if (dbConfig.lastError) {
1101
+ printError(` Last Error: ${dbConfig.lastError}`);
1102
+ }
1103
+ console.log();
1104
+ } catch (err) {
1105
+ printError(err instanceof Error ? err.message : 'Failed to load project state');
1106
+ }
1107
+ break;
1108
+ }
1109
+ case 'configure': {
1110
+ printInfo('Use "popeye db configure" from the CLI for interactive configuration.');
1111
+ printInfo('Or set DATABASE_URL in apps/backend/.env manually.');
1112
+ break;
1113
+ }
1114
+ case 'apply': {
1115
+ printInfo('Use "popeye db apply" from the CLI to run the setup pipeline.');
1116
+ break;
1117
+ }
1118
+ default:
1119
+ printError(`Unknown db action: ${action}`);
1120
+ printInfo('Usage: /db [status|configure|apply]');
1121
+ }
1122
+ }
1123
+
1124
+ /**
1125
+ * Handle /doctor slash command - readiness checks
1126
+ */
1127
+ async function handleDoctorSlashCommand(state: SessionState): Promise<void> {
1128
+ if (!state.projectDir) {
1129
+ printError('No active project. Create or resume a project first.');
1130
+ return;
1131
+ }
1132
+
1133
+ try {
1134
+ const { runDoctorChecks } = await import('./commands/doctor.js');
1135
+
1136
+ console.log();
1137
+ printInfo('Running readiness checks...');
1138
+ console.log();
1139
+
1140
+ const result = await runDoctorChecks(state.projectDir);
1141
+
1142
+ for (const check of result.checks) {
1143
+ const label = check.passed ? '[PASS]' : check.severity === 'info' ? '[SKIP]' : '[FAIL]';
1144
+ if (check.passed) {
1145
+ printSuccess(` ${label} ${check.name}: ${check.message}`);
1146
+ } else if (check.severity === 'info') {
1147
+ printInfo(` ${label} ${check.name}: ${check.message}`);
1148
+ } else if (check.severity === 'warning') {
1149
+ printWarning(` ${label} ${check.name}: ${check.message}`);
1150
+ } else {
1151
+ printError(` ${label} ${check.name}: ${check.message}`);
1152
+ }
1153
+ }
1154
+
1155
+ console.log();
1156
+ if (result.healthy) {
1157
+ printSuccess('All critical checks passed.');
1158
+ } else {
1159
+ printWarning('Some critical checks failed. See above for details.');
1160
+ }
1161
+ } catch (err) {
1162
+ printError(err instanceof Error ? err.message : 'Doctor checks failed');
1163
+ }
1164
+ }
1165
+
1166
+ /**
1167
+ * Handle /review or /audit slash command - post-build project audit
1168
+ */
1169
+ async function handleReviewSlashCommand(state: SessionState, args: string[] = []): Promise<void> {
1170
+ if (!state.projectDir) {
1171
+ printError('No active project. Create or resume a project first.');
1172
+ return;
1173
+ }
1174
+
1175
+ // Parse CLI-style flags from args
1176
+ const options: {
1177
+ depth?: number;
1178
+ strict?: boolean;
1179
+ format?: 'json' | 'md' | 'both';
1180
+ recover?: boolean;
1181
+ target?: string;
1182
+ } = {};
1183
+
1184
+ for (let i = 0; i < args.length; i++) {
1185
+ const arg = args[i];
1186
+ if ((arg === '--depth' || arg === '-d') && args[i + 1]) {
1187
+ options.depth = parseInt(args[++i], 10);
1188
+ } else if (arg === '--strict' || arg === '-s') {
1189
+ options.strict = true;
1190
+ } else if ((arg === '--format' || arg === '-f') && args[i + 1]) {
1191
+ options.format = args[++i] as 'json' | 'md' | 'both';
1192
+ } else if (arg === '--no-recover') {
1193
+ options.recover = false;
1194
+ } else if (arg === '--recover') {
1195
+ options.recover = true;
1196
+ } else if ((arg === '--target' || arg === '-t') && args[i + 1]) {
1197
+ options.target = args[++i];
1198
+ }
1199
+ }
1200
+
1201
+ try {
1202
+ const { runReview } = await import('./commands/review.js');
1203
+
1204
+ console.log();
1205
+ await runReview(state.projectDir, options);
1206
+ } catch (err) {
1207
+ printError(err instanceof Error ? err.message : 'Audit failed');
1208
+ }
1209
+ }
1210
+
1058
1211
  /**
1059
1212
  * Handle /overview command - full project plan and milestone review
1060
1213
  */
@@ -1734,11 +1887,27 @@ async function handleResume(state: SessionState, args: string[]): Promise<void>
1734
1887
  return;
1735
1888
  }
1736
1889
 
1737
- // Discover all projects (registered + scanned in current directory)
1738
- console.log();
1739
- printInfo('Scanning for projects...');
1890
+ // Reason: If there's already an active project with pending work (e.g., from /review recovery),
1891
+ // skip project discovery and go straight to resuming.
1892
+ if (state.projectDir) {
1893
+ const activeStatus = await getWorkflowStatus(state.projectDir);
1894
+ if (activeStatus.exists && activeStatus.state) {
1895
+ const { phase, status: pStatus } = activeStatus.state;
1896
+ const hasPendingWork = phase !== 'complete' || pStatus !== 'complete';
1897
+ if (hasPendingWork) {
1898
+ printInfo(`Resuming active project: ${activeStatus.state.name}`);
1899
+ // Fall through to the resume logic below (skip discovery)
1900
+ }
1901
+ }
1902
+ }
1903
+
1904
+ // Only discover projects if no active project with pending work
1905
+ if (!state.projectDir || (await getWorkflowStatus(state.projectDir)).state?.phase === 'complete') {
1906
+ // Discover all projects (registered + scanned in current directory)
1907
+ console.log();
1908
+ printInfo('Scanning for projects...');
1740
1909
 
1741
- const { all: allProjects } = await discoverProjects(state.projectDir || process.cwd());
1910
+ const { all: allProjects } = await discoverProjects(state.projectDir || process.cwd());
1742
1911
 
1743
1912
  // If projects found, let user select one
1744
1913
  if (allProjects.length > 0) {
@@ -1815,6 +1984,7 @@ async function handleResume(state: SessionState, args: string[]): Promise<void>
1815
1984
  console.log();
1816
1985
  printInfo(`Selected: ${selectedProject.name}`);
1817
1986
  }
1987
+ } // end: project discovery block
1818
1988
 
1819
1989
  // Now check for formal project state at the selected/current directory
1820
1990
  if (!state.projectDir) {
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Admin Wizard generator orchestration module
3
+ * Creates the admin wizard backend + frontend layers for fullstack projects
4
+ */
5
+
6
+ import { promises as fs } from 'node:fs';
7
+ import path from 'node:path';
8
+ import {
9
+ generateAdminAuthMiddleware,
10
+ generateMiddlewareInit,
11
+ generateAdminDbRoutes,
12
+ } from './templates/admin-wizard-python.js';
13
+ import {
14
+ generateUseAdminApiHook,
15
+ generateDbStatusBanner,
16
+ generateConnectionForm,
17
+ generateMigrationProgress,
18
+ generateDbSetupStepper,
19
+ generateAdminIndex,
20
+ } from './templates/admin-wizard-react.js';
21
+
22
+ /** Python dependencies required by the admin wizard */
23
+ export const ADMIN_WIZARD_PYTHON_DEPS = ['python-multipart>=0.0.7'];
24
+
25
+ /**
26
+ * Create a directory if it doesn't exist
27
+ */
28
+ async function ensureDir(dirPath: string): Promise<void> {
29
+ await fs.mkdir(dirPath, { recursive: true });
30
+ }
31
+
32
+ /**
33
+ * Write a file with content
34
+ */
35
+ async function writeFile(filePath: string, content: string): Promise<void> {
36
+ await fs.writeFile(filePath, content, 'utf-8');
37
+ }
38
+
39
+ /**
40
+ * Generate the complete admin wizard layer (backend middleware/routes + frontend components)
41
+ *
42
+ * Creates middleware/, routes/admin_db.py in the backend, and admin/ components
43
+ * in the frontend. Also augments requirements.txt with admin dependencies.
44
+ *
45
+ * @param projectDir - Root project directory (contains apps/)
46
+ * @param packageName - Python package name (snake_case)
47
+ * @returns List of absolute file paths created
48
+ */
49
+ export async function generateAdminWizardLayer(
50
+ projectDir: string,
51
+ packageName: string
52
+ ): Promise<string[]> {
53
+ const backendDir = path.join(projectDir, 'apps', 'backend');
54
+ const srcPkgDir = path.join(backendDir, 'src', packageName);
55
+ const frontendDir = path.join(projectDir, 'apps', 'frontend');
56
+ const filesCreated: string[] = [];
57
+
58
+ // Ensure directories
59
+ await ensureDir(path.join(srcPkgDir, 'middleware'));
60
+ await ensureDir(path.join(frontendDir, 'src', 'admin'));
61
+
62
+ // Define all files to generate
63
+ const files: Array<{ path: string; content: string }> = [
64
+ // Backend: middleware
65
+ {
66
+ path: path.join(srcPkgDir, 'middleware', '__init__.py'),
67
+ content: generateMiddlewareInit(),
68
+ },
69
+ {
70
+ path: path.join(srcPkgDir, 'middleware', 'admin_auth.py'),
71
+ content: generateAdminAuthMiddleware(),
72
+ },
73
+ // Backend: admin routes
74
+ {
75
+ path: path.join(srcPkgDir, 'routes', 'admin_db.py'),
76
+ content: generateAdminDbRoutes(packageName),
77
+ },
78
+ // Frontend: admin components
79
+ {
80
+ path: path.join(frontendDir, 'src', 'admin', 'useAdminApi.ts'),
81
+ content: generateUseAdminApiHook(),
82
+ },
83
+ {
84
+ path: path.join(frontendDir, 'src', 'admin', 'DbStatusBanner.tsx'),
85
+ content: generateDbStatusBanner(),
86
+ },
87
+ {
88
+ path: path.join(frontendDir, 'src', 'admin', 'ConnectionForm.tsx'),
89
+ content: generateConnectionForm(),
90
+ },
91
+ {
92
+ path: path.join(frontendDir, 'src', 'admin', 'MigrationProgress.tsx'),
93
+ content: generateMigrationProgress(),
94
+ },
95
+ {
96
+ path: path.join(frontendDir, 'src', 'admin', 'DbSetupStepper.tsx'),
97
+ content: generateDbSetupStepper(),
98
+ },
99
+ {
100
+ path: path.join(frontendDir, 'src', 'admin', 'index.ts'),
101
+ content: generateAdminIndex(),
102
+ },
103
+ ];
104
+
105
+ // Write all files
106
+ for (const file of files) {
107
+ await writeFile(file.path, file.content);
108
+ filesCreated.push(file.path);
109
+ }
110
+
111
+ // Augment requirements.txt with admin deps
112
+ const reqPath = path.join(backendDir, 'requirements.txt');
113
+ try {
114
+ const existingReqs = await fs.readFile(reqPath, 'utf-8');
115
+ if (!existingReqs.includes('# Admin')) {
116
+ const adminSection = `\n# Admin\n${ADMIN_WIZARD_PYTHON_DEPS.join('\n')}\n`;
117
+ await writeFile(reqPath, existingReqs.trimEnd() + adminSection);
118
+ }
119
+ } catch {
120
+ // requirements.txt doesn't exist yet - will be created by the main generator
121
+ }
122
+
123
+ return filesCreated;
124
+ }
125
+
126
+ /**
127
+ * Get the list of relative file paths generated by the admin wizard layer
128
+ *
129
+ * @param packageName - Python package name (snake_case)
130
+ * @returns List of relative file paths
131
+ */
132
+ export function getAdminWizardFiles(packageName: string): string[] {
133
+ return [
134
+ // Backend
135
+ `apps/backend/src/${packageName}/middleware/__init__.py`,
136
+ `apps/backend/src/${packageName}/middleware/admin_auth.py`,
137
+ `apps/backend/src/${packageName}/routes/admin_db.py`,
138
+ // Frontend
139
+ 'apps/frontend/src/admin/useAdminApi.ts',
140
+ 'apps/frontend/src/admin/DbStatusBanner.tsx',
141
+ 'apps/frontend/src/admin/ConnectionForm.tsx',
142
+ 'apps/frontend/src/admin/MigrationProgress.tsx',
143
+ 'apps/frontend/src/admin/DbSetupStepper.tsx',
144
+ 'apps/frontend/src/admin/index.ts',
145
+ ];
146
+ }
@@ -16,6 +16,9 @@ import {
16
16
  generateUiPackage as generateUiPackageImpl,
17
17
  } from './shared-packages.js';
18
18
  import type { BrandColorOptions } from './shared-packages.js';
19
+ import { generateAllDockerComposeWithDb } from './templates/database-docker.js';
20
+ import { getAdminWizardFiles } from './admin-wizard.js';
21
+ import { getDatabaseFiles } from './database.js';
19
22
 
20
23
  /**
21
24
  * Options for all project generation
@@ -479,14 +482,14 @@ export async function generateAllProject(
479
482
  path: path.join(projectDir, '.popeye', 'workspace.json'),
480
483
  content: generateAllWorkspaceJson(projectName),
481
484
  },
482
- // Docker compose (override to include website)
485
+ // Docker compose (override to include website + postgres)
483
486
  {
484
487
  path: path.join(projectDir, 'docker-compose.yml'),
485
- content: generateAllDockerCompose(projectName),
488
+ content: generateAllDockerComposeWithDb(projectName),
486
489
  },
487
490
  {
488
491
  path: path.join(projectDir, 'infra', 'docker', 'docker-compose.yml'),
489
- content: generateAllDockerCompose(projectName),
492
+ content: generateAllDockerComposeWithDb(projectName),
490
493
  },
491
494
  // README
492
495
  {
@@ -562,6 +565,10 @@ export function getAllProjectFiles(projectName: string): string[] {
562
565
  'packages/ui/src/button.tsx',
563
566
  'packages/ui/src/card.tsx',
564
567
  'packages/contracts/.gitkeep',
568
+ // Database layer
569
+ ...getDatabaseFiles(packageName, 'sqlalchemy'),
570
+ // Admin wizard layer
571
+ ...getAdminWizardFiles(packageName),
565
572
  ];
566
573
  }
567
574