popeye-cli 1.9.5 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +59 -0
- package/CONTRIBUTING.md +15 -1
- package/README.md +57 -0
- package/cheatsheet.md +65 -0
- package/dist/cli/commands/debug-context.d.ts +64 -0
- package/dist/cli/commands/debug-context.d.ts.map +1 -0
- package/dist/cli/commands/debug-context.js +221 -0
- package/dist/cli/commands/debug-context.js.map +1 -0
- package/dist/cli/commands/debug-prompts.d.ts +25 -0
- package/dist/cli/commands/debug-prompts.d.ts.map +1 -0
- package/dist/cli/commands/debug-prompts.js +80 -0
- package/dist/cli/commands/debug-prompts.js.map +1 -0
- package/dist/cli/commands/debug.d.ts +68 -0
- package/dist/cli/commands/debug.d.ts.map +1 -0
- package/dist/cli/commands/debug.js +543 -0
- package/dist/cli/commands/debug.js.map +1 -0
- package/dist/cli/commands/index.d.ts +1 -0
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +1 -0
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +2 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +25 -0
- package/dist/cli/interactive.js.map +1 -1
- package/dist/generators/all.d.ts.map +1 -1
- package/dist/generators/all.js +2 -0
- package/dist/generators/all.js.map +1 -1
- package/dist/generators/templates/database-docker.d.ts.map +1 -1
- package/dist/generators/templates/database-docker.js +10 -0
- package/dist/generators/templates/database-docker.js.map +1 -1
- package/dist/generators/templates/fullstack.d.ts +4 -1
- package/dist/generators/templates/fullstack.d.ts.map +1 -1
- package/dist/generators/templates/fullstack.js +6 -2
- package/dist/generators/templates/fullstack.js.map +1 -1
- package/dist/pipeline/artifact-manager.d.ts +47 -0
- package/dist/pipeline/artifact-manager.d.ts.map +1 -0
- package/dist/pipeline/artifact-manager.js +251 -0
- package/dist/pipeline/artifact-manager.js.map +1 -0
- package/dist/pipeline/artifact-validators.d.ts +29 -0
- package/dist/pipeline/artifact-validators.d.ts.map +1 -0
- package/dist/pipeline/artifact-validators.js +173 -0
- package/dist/pipeline/artifact-validators.js.map +1 -0
- package/dist/pipeline/change-request.d.ts +47 -0
- package/dist/pipeline/change-request.d.ts.map +1 -0
- package/dist/pipeline/change-request.js +91 -0
- package/dist/pipeline/change-request.js.map +1 -0
- package/dist/pipeline/check-runner.d.ts +47 -0
- package/dist/pipeline/check-runner.d.ts.map +1 -0
- package/dist/pipeline/check-runner.js +417 -0
- package/dist/pipeline/check-runner.js.map +1 -0
- package/dist/pipeline/command-resolver.d.ts +9 -0
- package/dist/pipeline/command-resolver.d.ts.map +1 -0
- package/dist/pipeline/command-resolver.js +140 -0
- package/dist/pipeline/command-resolver.js.map +1 -0
- package/dist/pipeline/consensus/consensus-runner.d.ts +44 -0
- package/dist/pipeline/consensus/consensus-runner.d.ts.map +1 -0
- package/dist/pipeline/consensus/consensus-runner.js +212 -0
- package/dist/pipeline/consensus/consensus-runner.js.map +1 -0
- package/dist/pipeline/constitution.d.ts +45 -0
- package/dist/pipeline/constitution.d.ts.map +1 -0
- package/dist/pipeline/constitution.js +82 -0
- package/dist/pipeline/constitution.js.map +1 -0
- package/dist/pipeline/gate-engine.d.ts +55 -0
- package/dist/pipeline/gate-engine.d.ts.map +1 -0
- package/dist/pipeline/gate-engine.js +270 -0
- package/dist/pipeline/gate-engine.js.map +1 -0
- package/dist/pipeline/index.d.ts +26 -0
- package/dist/pipeline/index.d.ts.map +1 -0
- package/dist/pipeline/index.js +35 -0
- package/dist/pipeline/index.js.map +1 -0
- package/dist/pipeline/migration.d.ts +15 -0
- package/dist/pipeline/migration.d.ts.map +1 -0
- package/dist/pipeline/migration.js +76 -0
- package/dist/pipeline/migration.js.map +1 -0
- package/dist/pipeline/orchestrator.d.ts +28 -0
- package/dist/pipeline/orchestrator.d.ts.map +1 -0
- package/dist/pipeline/orchestrator.js +238 -0
- package/dist/pipeline/orchestrator.js.map +1 -0
- package/dist/pipeline/packets/audit-report-builder.d.ts +11 -0
- package/dist/pipeline/packets/audit-report-builder.d.ts.map +1 -0
- package/dist/pipeline/packets/audit-report-builder.js +32 -0
- package/dist/pipeline/packets/audit-report-builder.js.map +1 -0
- package/dist/pipeline/packets/consensus-packet-builder.d.ts +35 -0
- package/dist/pipeline/packets/consensus-packet-builder.d.ts.map +1 -0
- package/dist/pipeline/packets/consensus-packet-builder.js +80 -0
- package/dist/pipeline/packets/consensus-packet-builder.js.map +1 -0
- package/dist/pipeline/packets/index.d.ts +12 -0
- package/dist/pipeline/packets/index.d.ts.map +1 -0
- package/dist/pipeline/packets/index.js +8 -0
- package/dist/pipeline/packets/index.js.map +1 -0
- package/dist/pipeline/packets/plan-packet-builder.d.ts +21 -0
- package/dist/pipeline/packets/plan-packet-builder.d.ts.map +1 -0
- package/dist/pipeline/packets/plan-packet-builder.js +27 -0
- package/dist/pipeline/packets/plan-packet-builder.js.map +1 -0
- package/dist/pipeline/packets/rca-packet-builder.d.ts +19 -0
- package/dist/pipeline/packets/rca-packet-builder.d.ts.map +1 -0
- package/dist/pipeline/packets/rca-packet-builder.js +22 -0
- package/dist/pipeline/packets/rca-packet-builder.js.map +1 -0
- package/dist/pipeline/phases/architecture.d.ts +7 -0
- package/dist/pipeline/phases/architecture.d.ts.map +1 -0
- package/dist/pipeline/phases/architecture.js +60 -0
- package/dist/pipeline/phases/architecture.js.map +1 -0
- package/dist/pipeline/phases/audit.d.ts +8 -0
- package/dist/pipeline/phases/audit.d.ts.map +1 -0
- package/dist/pipeline/phases/audit.js +144 -0
- package/dist/pipeline/phases/audit.js.map +1 -0
- package/dist/pipeline/phases/consensus-architecture.d.ts +7 -0
- package/dist/pipeline/phases/consensus-architecture.d.ts.map +1 -0
- package/dist/pipeline/phases/consensus-architecture.js +84 -0
- package/dist/pipeline/phases/consensus-architecture.js.map +1 -0
- package/dist/pipeline/phases/consensus-master-plan.d.ts +7 -0
- package/dist/pipeline/phases/consensus-master-plan.d.ts.map +1 -0
- package/dist/pipeline/phases/consensus-master-plan.js +81 -0
- package/dist/pipeline/phases/consensus-master-plan.js.map +1 -0
- package/dist/pipeline/phases/consensus-role-plans.d.ts +7 -0
- package/dist/pipeline/phases/consensus-role-plans.d.ts.map +1 -0
- package/dist/pipeline/phases/consensus-role-plans.js +85 -0
- package/dist/pipeline/phases/consensus-role-plans.js.map +1 -0
- package/dist/pipeline/phases/done.d.ts +7 -0
- package/dist/pipeline/phases/done.d.ts.map +1 -0
- package/dist/pipeline/phases/done.js +45 -0
- package/dist/pipeline/phases/done.js.map +1 -0
- package/dist/pipeline/phases/implementation.d.ts +8 -0
- package/dist/pipeline/phases/implementation.d.ts.map +1 -0
- package/dist/pipeline/phases/implementation.js +42 -0
- package/dist/pipeline/phases/implementation.js.map +1 -0
- package/dist/pipeline/phases/index.d.ts +20 -0
- package/dist/pipeline/phases/index.d.ts.map +1 -0
- package/dist/pipeline/phases/index.js +19 -0
- package/dist/pipeline/phases/index.js.map +1 -0
- package/dist/pipeline/phases/intake.d.ts +8 -0
- package/dist/pipeline/phases/intake.d.ts.map +1 -0
- package/dist/pipeline/phases/intake.js +40 -0
- package/dist/pipeline/phases/intake.js.map +1 -0
- package/dist/pipeline/phases/phase-context.d.ts +30 -0
- package/dist/pipeline/phases/phase-context.d.ts.map +1 -0
- package/dist/pipeline/phases/phase-context.js +33 -0
- package/dist/pipeline/phases/phase-context.js.map +1 -0
- package/dist/pipeline/phases/production-gate.d.ts +8 -0
- package/dist/pipeline/phases/production-gate.d.ts.map +1 -0
- package/dist/pipeline/phases/production-gate.js +84 -0
- package/dist/pipeline/phases/production-gate.js.map +1 -0
- package/dist/pipeline/phases/qa-validation.d.ts +7 -0
- package/dist/pipeline/phases/qa-validation.d.ts.map +1 -0
- package/dist/pipeline/phases/qa-validation.js +50 -0
- package/dist/pipeline/phases/qa-validation.js.map +1 -0
- package/dist/pipeline/phases/recovery-loop.d.ts +7 -0
- package/dist/pipeline/phases/recovery-loop.d.ts.map +1 -0
- package/dist/pipeline/phases/recovery-loop.js +91 -0
- package/dist/pipeline/phases/recovery-loop.js.map +1 -0
- package/dist/pipeline/phases/review.d.ts +8 -0
- package/dist/pipeline/phases/review.d.ts.map +1 -0
- package/dist/pipeline/phases/review.js +127 -0
- package/dist/pipeline/phases/review.js.map +1 -0
- package/dist/pipeline/phases/role-planning.d.ts +7 -0
- package/dist/pipeline/phases/role-planning.d.ts.map +1 -0
- package/dist/pipeline/phases/role-planning.js +75 -0
- package/dist/pipeline/phases/role-planning.js.map +1 -0
- package/dist/pipeline/phases/stuck.d.ts +7 -0
- package/dist/pipeline/phases/stuck.d.ts.map +1 -0
- package/dist/pipeline/phases/stuck.js +51 -0
- package/dist/pipeline/phases/stuck.js.map +1 -0
- package/dist/pipeline/repo-snapshot.d.ts +24 -0
- package/dist/pipeline/repo-snapshot.d.ts.map +1 -0
- package/dist/pipeline/repo-snapshot.js +343 -0
- package/dist/pipeline/repo-snapshot.js.map +1 -0
- package/dist/pipeline/role-execution-adapter.d.ts +59 -0
- package/dist/pipeline/role-execution-adapter.d.ts.map +1 -0
- package/dist/pipeline/role-execution-adapter.js +159 -0
- package/dist/pipeline/role-execution-adapter.js.map +1 -0
- package/dist/pipeline/skill-loader.d.ts +34 -0
- package/dist/pipeline/skill-loader.d.ts.map +1 -0
- package/dist/pipeline/skill-loader.js +156 -0
- package/dist/pipeline/skill-loader.js.map +1 -0
- package/dist/pipeline/skills/defaults.d.ts +16 -0
- package/dist/pipeline/skills/defaults.d.ts.map +1 -0
- package/dist/pipeline/skills/defaults.js +189 -0
- package/dist/pipeline/skills/defaults.js.map +1 -0
- package/dist/pipeline/type-defs/artifacts.d.ts +202 -0
- package/dist/pipeline/type-defs/artifacts.d.ts.map +1 -0
- package/dist/pipeline/type-defs/artifacts.js +66 -0
- package/dist/pipeline/type-defs/artifacts.js.map +1 -0
- package/dist/pipeline/type-defs/audit.d.ts +256 -0
- package/dist/pipeline/type-defs/audit.d.ts.map +1 -0
- package/dist/pipeline/type-defs/audit.js +54 -0
- package/dist/pipeline/type-defs/audit.js.map +1 -0
- package/dist/pipeline/type-defs/checks.d.ts +81 -0
- package/dist/pipeline/type-defs/checks.d.ts.map +1 -0
- package/dist/pipeline/type-defs/checks.js +38 -0
- package/dist/pipeline/type-defs/checks.js.map +1 -0
- package/dist/pipeline/type-defs/enums.d.ts +43 -0
- package/dist/pipeline/type-defs/enums.d.ts.map +1 -0
- package/dist/pipeline/type-defs/enums.js +55 -0
- package/dist/pipeline/type-defs/enums.js.map +1 -0
- package/dist/pipeline/type-defs/index.d.ts +12 -0
- package/dist/pipeline/type-defs/index.d.ts.map +1 -0
- package/dist/pipeline/type-defs/index.js +12 -0
- package/dist/pipeline/type-defs/index.js.map +1 -0
- package/dist/pipeline/type-defs/packets.d.ts +806 -0
- package/dist/pipeline/type-defs/packets.d.ts.map +1 -0
- package/dist/pipeline/type-defs/packets.js +109 -0
- package/dist/pipeline/type-defs/packets.js.map +1 -0
- package/dist/pipeline/type-defs/snapshot.d.ts +52 -0
- package/dist/pipeline/type-defs/snapshot.d.ts.map +1 -0
- package/dist/pipeline/type-defs/snapshot.js +35 -0
- package/dist/pipeline/type-defs/snapshot.js.map +1 -0
- package/dist/pipeline/type-defs/state.d.ts +449 -0
- package/dist/pipeline/type-defs/state.d.ts.map +1 -0
- package/dist/pipeline/type-defs/state.js +88 -0
- package/dist/pipeline/type-defs/state.js.map +1 -0
- package/dist/pipeline/types.d.ts +16 -0
- package/dist/pipeline/types.d.ts.map +1 -0
- package/dist/pipeline/types.js +16 -0
- package/dist/pipeline/types.js.map +1 -0
- package/dist/types/audit.d.ts +6 -6
- package/dist/workflow/index.d.ts.map +1 -1
- package/dist/workflow/index.js +48 -0
- package/dist/workflow/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/ARBITRATOR.md +137 -0
- package/skills/ARCHITECT.md +167 -0
- package/skills/AUDITOR.md +128 -0
- package/skills/AUDIT_REPORT_SCHEMA.md +20 -0
- package/skills/BACKEND_PROGRAMMER.md +95 -0
- package/skills/CONSENSUS_PACKET_SCHEMA.md +166 -0
- package/skills/DB_EXPERT.md +106 -0
- package/skills/DEBUGGER.md +286 -0
- package/skills/DISPATCHER.md +157 -0
- package/skills/FRONTEND_PROGRAMMER.md +84 -0
- package/skills/JOURNALIST.md +247 -0
- package/skills/MARKETING_EXPERT.md +23 -0
- package/skills/PHASE_GATE_ENGINE_SPEC.md +171 -0
- package/skills/PLAN_PACKET_SCHEMA.md +222 -0
- package/skills/POPEYE_CONSTITUTION.md +177 -0
- package/skills/POPEYE_FULL_AUTONOMY_PIPELINE.md +537 -0
- package/skills/PRODUCTION_READINESS_SCHEMA.md +19 -0
- package/skills/QA_TESTER.md +40 -0
- package/skills/RCA_PACKET_SCHEMA.md +22 -0
- package/skills/RELEASE_MANAGER.md +60 -0
- package/skills/REVIEWER.md +133 -0
- package/skills/SOCIAL_EXPERT.md +22 -0
- package/skills/UI_UX_SPECIALIST.md +22 -0
- package/skills/WEBSITE_PROGRAMMER.md +37 -0
- package/src/cli/commands/debug-context.ts +265 -0
- package/src/cli/commands/debug-prompts.ts +91 -0
- package/src/cli/commands/debug.ts +662 -0
- package/src/cli/commands/index.ts +1 -0
- package/src/cli/index.ts +2 -0
- package/src/cli/interactive.ts +27 -0
- package/src/generators/all.ts +2 -0
- package/src/generators/templates/database-docker.ts +10 -0
- package/src/generators/templates/fullstack.ts +6 -2
- package/src/pipeline/artifact-manager.ts +339 -0
- package/src/pipeline/artifact-validators.ts +224 -0
- package/src/pipeline/change-request.ts +119 -0
- package/src/pipeline/check-runner.ts +504 -0
- package/src/pipeline/command-resolver.ts +168 -0
- package/src/pipeline/consensus/consensus-runner.ts +317 -0
- package/src/pipeline/constitution.ts +109 -0
- package/src/pipeline/gate-engine.ts +347 -0
- package/src/pipeline/index.ts +82 -0
- package/src/pipeline/migration.ts +91 -0
- package/src/pipeline/orchestrator.ts +314 -0
- package/src/pipeline/packets/audit-report-builder.ts +47 -0
- package/src/pipeline/packets/consensus-packet-builder.ts +112 -0
- package/src/pipeline/packets/index.ts +15 -0
- package/src/pipeline/packets/plan-packet-builder.ts +52 -0
- package/src/pipeline/packets/rca-packet-builder.ts +38 -0
- package/src/pipeline/phases/architecture.ts +73 -0
- package/src/pipeline/phases/audit.ts +193 -0
- package/src/pipeline/phases/consensus-architecture.ts +104 -0
- package/src/pipeline/phases/consensus-master-plan.ts +100 -0
- package/src/pipeline/phases/consensus-role-plans.ts +105 -0
- package/src/pipeline/phases/done.ts +68 -0
- package/src/pipeline/phases/implementation.ts +48 -0
- package/src/pipeline/phases/index.ts +21 -0
- package/src/pipeline/phases/intake.ts +54 -0
- package/src/pipeline/phases/phase-context.ts +86 -0
- package/src/pipeline/phases/production-gate.ts +113 -0
- package/src/pipeline/phases/qa-validation.ts +63 -0
- package/src/pipeline/phases/recovery-loop.ts +118 -0
- package/src/pipeline/phases/review.ts +149 -0
- package/src/pipeline/phases/role-planning.ts +92 -0
- package/src/pipeline/phases/stuck.ts +62 -0
- package/src/pipeline/repo-snapshot.ts +395 -0
- package/src/pipeline/role-execution-adapter.ts +238 -0
- package/src/pipeline/skill-loader.ts +192 -0
- package/src/pipeline/skills/defaults.ts +215 -0
- package/src/pipeline/type-defs/artifacts.ts +81 -0
- package/src/pipeline/type-defs/audit.ts +67 -0
- package/src/pipeline/type-defs/checks.ts +47 -0
- package/src/pipeline/type-defs/enums.ts +62 -0
- package/src/pipeline/type-defs/index.ts +12 -0
- package/src/pipeline/type-defs/packets.ts +131 -0
- package/src/pipeline/type-defs/snapshot.ts +55 -0
- package/src/pipeline/type-defs/state.ts +165 -0
- package/src/pipeline/types.ts +16 -0
- package/src/workflow/index.ts +48 -0
- package/tests/cli/commands/debug.test.ts +376 -0
- package/tests/pipeline/artifact-manager.test.ts +183 -0
- package/tests/pipeline/artifact-validators.test.ts +207 -0
- package/tests/pipeline/change-request.test.ts +180 -0
- package/tests/pipeline/check-runner.test.ts +157 -0
- package/tests/pipeline/command-resolver.test.ts +159 -0
- package/tests/pipeline/consensus-runner.test.ts +206 -0
- package/tests/pipeline/consensus-scoring.test.ts +163 -0
- package/tests/pipeline/constitution.test.ts +122 -0
- package/tests/pipeline/gate-engine.test.ts +195 -0
- package/tests/pipeline/migration.test.ts +133 -0
- package/tests/pipeline/orchestrator.test.ts +614 -0
- package/tests/pipeline/packets/builders.test.ts +347 -0
- package/tests/pipeline/repo-snapshot.test.ts +189 -0
- package/tests/pipeline/role-execution-adapter.test.ts +299 -0
- package/tests/pipeline/skill-loader.test.ts +186 -0
- package/tests/pipeline/start-env-checks.test.ts +123 -0
- package/tests/pipeline/types.test.ts +156 -0
package/src/cli/interactive.ts
CHANGED
|
@@ -847,6 +847,7 @@ function showHelp(): void {
|
|
|
847
847
|
['/db [action]', 'Database management (status/configure/apply)'],
|
|
848
848
|
['/doctor', 'Run database and project readiness checks'],
|
|
849
849
|
['/review', 'Run post-build audit/review with findings and recovery'],
|
|
850
|
+
['/debug', 'Start interactive debugging session (use /back to return)'],
|
|
850
851
|
['/clear', 'Clear screen'],
|
|
851
852
|
['/exit', 'Exit Popeye'],
|
|
852
853
|
];
|
|
@@ -1028,6 +1029,11 @@ async function handleInput(input: string, state: SessionState): Promise<boolean>
|
|
|
1028
1029
|
await handleReviewSlashCommand(state, args);
|
|
1029
1030
|
break;
|
|
1030
1031
|
|
|
1032
|
+
case '/debug':
|
|
1033
|
+
case '/dbg':
|
|
1034
|
+
await handleDebugSlashCommand(state);
|
|
1035
|
+
break;
|
|
1036
|
+
|
|
1031
1037
|
default:
|
|
1032
1038
|
printError(`Unknown command: ${cmd}`);
|
|
1033
1039
|
printInfo('Type /help for available commands');
|
|
@@ -1208,6 +1214,27 @@ async function handleReviewSlashCommand(state: SessionState, args: string[] = []
|
|
|
1208
1214
|
}
|
|
1209
1215
|
}
|
|
1210
1216
|
|
|
1217
|
+
/**
|
|
1218
|
+
* Handle /debug slash command - start interactive debugging session
|
|
1219
|
+
*/
|
|
1220
|
+
async function handleDebugSlashCommand(state: SessionState): Promise<void> {
|
|
1221
|
+
if (!state.projectDir) {
|
|
1222
|
+
printError('No active project. Create or resume a project first.');
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
try {
|
|
1227
|
+
const { runDebugSession } = await import('./commands/debug.js');
|
|
1228
|
+
await runDebugSession({
|
|
1229
|
+
projectDir: state.projectDir,
|
|
1230
|
+
language: state.language,
|
|
1231
|
+
});
|
|
1232
|
+
printInfo('Returned to main Popeye session.');
|
|
1233
|
+
} catch (err) {
|
|
1234
|
+
printError(err instanceof Error ? err.message : 'Debug session failed');
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1211
1238
|
/**
|
|
1212
1239
|
* Handle /overview command - full project plan and milestone review
|
|
1213
1240
|
*/
|
package/src/generators/all.ts
CHANGED
|
@@ -150,6 +150,8 @@ export function generateAllDockerComposeWithDb(projectName: string): string {
|
|
|
150
150
|
dockerfile: Dockerfile
|
|
151
151
|
ports:
|
|
152
152
|
- "8000:8000"
|
|
153
|
+
env_file:
|
|
154
|
+
- ./apps/backend/.env
|
|
153
155
|
environment:
|
|
154
156
|
- DEBUG=false
|
|
155
157
|
- FRONTEND_URL=http://frontend:80
|
|
@@ -211,5 +213,13 @@ DB_VECTOR_REQUIRED=true
|
|
|
211
213
|
|
|
212
214
|
# Admin Wizard
|
|
213
215
|
ADMIN_SETUP_TOKEN=change-me-to-a-random-string
|
|
216
|
+
|
|
217
|
+
# JWT Configuration
|
|
218
|
+
SECRET_KEY=change-me-in-production
|
|
219
|
+
|
|
220
|
+
# Google OAuth2 (optional - uncomment to enable)
|
|
221
|
+
# GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
|
|
222
|
+
# GOOGLE_CLIENT_SECRET=your-client-secret
|
|
223
|
+
# GOOGLE_REDIRECT_URI=http://localhost:8000/api/v1/auth/google/callback
|
|
214
224
|
`;
|
|
215
225
|
}
|
|
@@ -495,15 +495,19 @@ DATABASE_URL=sqlite:///./data/app.db
|
|
|
495
495
|
|
|
496
496
|
/**
|
|
497
497
|
* Generate UI spec placeholder for fullstack project
|
|
498
|
+
*
|
|
499
|
+
* @param projectName - The project name
|
|
500
|
+
* @param brandColor - Optional brand primary color hex (e.g. '#2563EB')
|
|
498
501
|
*/
|
|
499
|
-
export function generateUiSpec(projectName: string): string {
|
|
502
|
+
export function generateUiSpec(projectName: string, brandColor?: string): string {
|
|
503
|
+
const primary = brandColor || '#3B82F6';
|
|
500
504
|
return JSON.stringify(
|
|
501
505
|
{
|
|
502
506
|
name: projectName,
|
|
503
507
|
version: '1.0',
|
|
504
508
|
theme: {
|
|
505
509
|
colors: {
|
|
506
|
-
primary
|
|
510
|
+
primary,
|
|
507
511
|
secondary: '#6B7280',
|
|
508
512
|
accent: '#10B981',
|
|
509
513
|
},
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Artifact Manager — manages immutable versioned artifacts under /docs/.
|
|
3
|
+
* Supports both Markdown and JSON content types (P0-C).
|
|
4
|
+
* Implements version chains via group_id + previous_id (P1-2).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { randomUUID } from 'node:crypto';
|
|
8
|
+
import { createHash } from 'node:crypto';
|
|
9
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync } from 'node:fs';
|
|
10
|
+
import { join, relative } from 'node:path';
|
|
11
|
+
|
|
12
|
+
import type {
|
|
13
|
+
ArtifactType,
|
|
14
|
+
ArtifactEntry,
|
|
15
|
+
ArtifactRef,
|
|
16
|
+
ContentType,
|
|
17
|
+
PipelinePhase,
|
|
18
|
+
} from './types.js';
|
|
19
|
+
|
|
20
|
+
// ─── Constants ───────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
/** Directory mappings: artifact type -> subdirectory under /docs/ */
|
|
23
|
+
const ARTIFACT_DIRS: Record<string, string> = {
|
|
24
|
+
master_plan: 'master-plan',
|
|
25
|
+
architecture: 'architecture',
|
|
26
|
+
role_plan: 'role-plans',
|
|
27
|
+
consensus: 'consensus',
|
|
28
|
+
arbitration: 'arbitration',
|
|
29
|
+
audit_report: 'audit',
|
|
30
|
+
rca_report: 'incidents',
|
|
31
|
+
production_readiness: 'production',
|
|
32
|
+
release_notes: 'release',
|
|
33
|
+
deployment: 'release',
|
|
34
|
+
rollback: 'release',
|
|
35
|
+
repo_snapshot: 'snapshots',
|
|
36
|
+
build_check: 'checks',
|
|
37
|
+
test_check: 'checks',
|
|
38
|
+
lint_check: 'checks',
|
|
39
|
+
typecheck_check: 'checks',
|
|
40
|
+
placeholder_scan: 'checks',
|
|
41
|
+
qa_validation: 'role-plans',
|
|
42
|
+
review_decision: 'consensus',
|
|
43
|
+
stuck_report: 'incidents',
|
|
44
|
+
journalist_trace: 'journal',
|
|
45
|
+
resolved_commands: 'checks',
|
|
46
|
+
constitution: 'governance',
|
|
47
|
+
change_request: 'governance',
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/** All required subdirectories under /docs/ */
|
|
51
|
+
const DOCS_SUBDIRS = [
|
|
52
|
+
'master-plan',
|
|
53
|
+
'architecture',
|
|
54
|
+
'role-plans',
|
|
55
|
+
'consensus',
|
|
56
|
+
'arbitration',
|
|
57
|
+
'audit',
|
|
58
|
+
'incidents',
|
|
59
|
+
'production',
|
|
60
|
+
'release',
|
|
61
|
+
'snapshots',
|
|
62
|
+
'checks',
|
|
63
|
+
'journal',
|
|
64
|
+
'governance',
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
// ─── Helper Functions ────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
function computeSha256(content: string): string {
|
|
70
|
+
return createHash('sha256').update(content, 'utf-8').digest('hex');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function shortId(): string {
|
|
74
|
+
return randomUUID().split('-')[0];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function formatDate(): string {
|
|
78
|
+
return new Date().toISOString().split('T')[0];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function getExtension(contentType: ContentType): string {
|
|
82
|
+
return contentType === 'json' ? '.json' : '.md';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ─── Artifact Manager ────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
export interface ArtifactManagerOptions {
|
|
88
|
+
projectDir: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export class ArtifactManager {
|
|
92
|
+
private readonly projectDir: string;
|
|
93
|
+
private readonly docsDir: string;
|
|
94
|
+
|
|
95
|
+
constructor(options: ArtifactManagerOptions) {
|
|
96
|
+
this.projectDir = options.projectDir;
|
|
97
|
+
this.docsDir = join(options.projectDir, 'docs');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Ensure all /docs/ subdirectories exist */
|
|
101
|
+
ensureDocsStructure(): void {
|
|
102
|
+
if (!existsSync(this.docsDir)) {
|
|
103
|
+
mkdirSync(this.docsDir, { recursive: true });
|
|
104
|
+
}
|
|
105
|
+
for (const subdir of DOCS_SUBDIRS) {
|
|
106
|
+
const dirPath = join(this.docsDir, subdir);
|
|
107
|
+
if (!existsSync(dirPath)) {
|
|
108
|
+
mkdirSync(dirPath, { recursive: true });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Create an artifact from Markdown content */
|
|
114
|
+
createArtifactText(
|
|
115
|
+
type: ArtifactType,
|
|
116
|
+
markdown: string,
|
|
117
|
+
phase: PipelinePhase,
|
|
118
|
+
groupId?: string,
|
|
119
|
+
): ArtifactEntry {
|
|
120
|
+
return this.createArtifact(type, markdown, phase, 'markdown', groupId);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Create an artifact from a JSON-serializable object */
|
|
124
|
+
createArtifactJson(
|
|
125
|
+
type: ArtifactType,
|
|
126
|
+
jsonObject: unknown,
|
|
127
|
+
phase: PipelinePhase,
|
|
128
|
+
groupId?: string,
|
|
129
|
+
): ArtifactEntry {
|
|
130
|
+
const content = JSON.stringify(jsonObject, null, 2);
|
|
131
|
+
return this.createArtifact(type, content, phase, 'json', groupId);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Core artifact creation logic */
|
|
135
|
+
private createArtifact(
|
|
136
|
+
type: ArtifactType,
|
|
137
|
+
content: string,
|
|
138
|
+
phase: PipelinePhase,
|
|
139
|
+
contentType: ContentType,
|
|
140
|
+
groupId?: string,
|
|
141
|
+
): ArtifactEntry {
|
|
142
|
+
this.ensureDocsStructure();
|
|
143
|
+
|
|
144
|
+
const resolvedGroupId = groupId ?? randomUUID();
|
|
145
|
+
const existingArtifacts = this.listArtifacts(type);
|
|
146
|
+
const version = this.getNextVersion(resolvedGroupId, existingArtifacts);
|
|
147
|
+
const previousEntry = this.getLatestInGroup(resolvedGroupId, existingArtifacts);
|
|
148
|
+
|
|
149
|
+
const id = randomUUID();
|
|
150
|
+
const date = formatDate();
|
|
151
|
+
const sid = shortId();
|
|
152
|
+
const ext = getExtension(contentType);
|
|
153
|
+
const filename = `${type}_${sid}_v${version}_${date}${ext}`;
|
|
154
|
+
|
|
155
|
+
const subdir = ARTIFACT_DIRS[type] ?? 'misc';
|
|
156
|
+
const dirPath = join(this.docsDir, subdir);
|
|
157
|
+
if (!existsSync(dirPath)) {
|
|
158
|
+
mkdirSync(dirPath, { recursive: true });
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const filePath = join(dirPath, filename);
|
|
162
|
+
const sha256 = computeSha256(content);
|
|
163
|
+
|
|
164
|
+
writeFileSync(filePath, content, 'utf-8');
|
|
165
|
+
|
|
166
|
+
const relativePath = relative(this.projectDir, filePath);
|
|
167
|
+
|
|
168
|
+
const entry: ArtifactEntry = {
|
|
169
|
+
id,
|
|
170
|
+
type,
|
|
171
|
+
phase,
|
|
172
|
+
version,
|
|
173
|
+
path: relativePath,
|
|
174
|
+
sha256,
|
|
175
|
+
timestamp: new Date().toISOString(),
|
|
176
|
+
immutable: true,
|
|
177
|
+
content_type: contentType,
|
|
178
|
+
group_id: resolvedGroupId,
|
|
179
|
+
previous_id: previousEntry?.id,
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
return entry;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Get the file path for a given artifact type and naming components */
|
|
186
|
+
getArtifactPath(
|
|
187
|
+
type: ArtifactType,
|
|
188
|
+
sid: string,
|
|
189
|
+
version: number,
|
|
190
|
+
date: string,
|
|
191
|
+
contentType: ContentType,
|
|
192
|
+
): string {
|
|
193
|
+
const ext = getExtension(contentType);
|
|
194
|
+
const subdir = ARTIFACT_DIRS[type] ?? 'misc';
|
|
195
|
+
return join(this.docsDir, subdir, `${type}_${sid}_v${version}_${date}${ext}`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** List all artifacts, optionally filtered by type */
|
|
199
|
+
listArtifacts(type?: ArtifactType): ArtifactEntry[] {
|
|
200
|
+
// Scan for artifact JSON metadata files in a .artifacts/ dir
|
|
201
|
+
const metaDir = join(this.docsDir, '.artifacts');
|
|
202
|
+
if (!existsSync(metaDir)) {
|
|
203
|
+
return [];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const entries: ArtifactEntry[] = [];
|
|
207
|
+
const files = readdirSync(metaDir).filter((f) => f.endsWith('.json'));
|
|
208
|
+
|
|
209
|
+
for (const file of files) {
|
|
210
|
+
try {
|
|
211
|
+
const raw = readFileSync(join(metaDir, file), 'utf-8');
|
|
212
|
+
const parsed = JSON.parse(raw) as ArtifactEntry;
|
|
213
|
+
if (!type || parsed.type === type) {
|
|
214
|
+
entries.push(parsed);
|
|
215
|
+
}
|
|
216
|
+
} catch {
|
|
217
|
+
// Skip malformed metadata files
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return entries.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Verify an artifact's SHA-256 matches its stored content */
|
|
225
|
+
verifyArtifact(entry: ArtifactEntry): boolean {
|
|
226
|
+
const fullPath = join(this.projectDir, entry.path);
|
|
227
|
+
if (!existsSync(fullPath)) {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
232
|
+
const currentHash = computeSha256(content);
|
|
233
|
+
return currentHash === entry.sha256;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Get the latest artifact of a given type */
|
|
237
|
+
getLatestArtifact(type: ArtifactType): ArtifactEntry | null {
|
|
238
|
+
const all = this.listArtifacts(type);
|
|
239
|
+
if (all.length === 0) return null;
|
|
240
|
+
return all[all.length - 1];
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Get next version number for a group across existing artifacts */
|
|
244
|
+
getNextVersion(groupId: string, existingArtifacts: ArtifactEntry[]): number {
|
|
245
|
+
const groupArtifacts = existingArtifacts.filter((a) => a.group_id === groupId);
|
|
246
|
+
if (groupArtifacts.length === 0) return 1;
|
|
247
|
+
const maxVersion = Math.max(...groupArtifacts.map((a) => a.version));
|
|
248
|
+
return maxVersion + 1;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** Convert an ArtifactEntry to an ArtifactRef */
|
|
252
|
+
toArtifactRef(entry: ArtifactEntry): ArtifactRef {
|
|
253
|
+
return {
|
|
254
|
+
artifact_id: entry.id,
|
|
255
|
+
path: entry.path,
|
|
256
|
+
sha256: entry.sha256,
|
|
257
|
+
version: entry.version,
|
|
258
|
+
type: entry.type,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** Store artifact metadata for later retrieval */
|
|
263
|
+
storeArtifactMetadata(entry: ArtifactEntry): void {
|
|
264
|
+
const metaDir = join(this.docsDir, '.artifacts');
|
|
265
|
+
if (!existsSync(metaDir)) {
|
|
266
|
+
mkdirSync(metaDir, { recursive: true });
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const metaPath = join(metaDir, `${entry.id}.json`);
|
|
270
|
+
writeFileSync(metaPath, JSON.stringify(entry, null, 2), 'utf-8');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** Create an artifact and store its metadata in one step */
|
|
274
|
+
createAndStoreText(
|
|
275
|
+
type: ArtifactType,
|
|
276
|
+
markdown: string,
|
|
277
|
+
phase: PipelinePhase,
|
|
278
|
+
groupId?: string,
|
|
279
|
+
): ArtifactEntry {
|
|
280
|
+
const entry = this.createArtifactText(type, markdown, phase, groupId);
|
|
281
|
+
this.storeArtifactMetadata(entry);
|
|
282
|
+
return entry;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** Create a JSON artifact and store its metadata in one step */
|
|
286
|
+
createAndStoreJson(
|
|
287
|
+
type: ArtifactType,
|
|
288
|
+
jsonObject: unknown,
|
|
289
|
+
phase: PipelinePhase,
|
|
290
|
+
groupId?: string,
|
|
291
|
+
): ArtifactEntry {
|
|
292
|
+
const entry = this.createArtifactJson(type, jsonObject, phase, groupId);
|
|
293
|
+
this.storeArtifactMetadata(entry);
|
|
294
|
+
return entry;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** Update /docs/INDEX.md with current artifact listing */
|
|
298
|
+
updateIndex(artifacts: ArtifactEntry[]): void {
|
|
299
|
+
this.ensureDocsStructure();
|
|
300
|
+
|
|
301
|
+
const lines: string[] = [
|
|
302
|
+
'# Documentation Index',
|
|
303
|
+
'',
|
|
304
|
+
`> Auto-generated by Popeye Pipeline — ${new Date().toISOString()}`,
|
|
305
|
+
'',
|
|
306
|
+
'## Artifacts',
|
|
307
|
+
'',
|
|
308
|
+
'| Type | Version | Path | Phase | Timestamp |',
|
|
309
|
+
'|------|---------|------|-------|-----------|',
|
|
310
|
+
];
|
|
311
|
+
|
|
312
|
+
const sorted = [...artifacts].sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
313
|
+
|
|
314
|
+
for (const a of sorted) {
|
|
315
|
+
lines.push(`| ${a.type} | v${a.version} | ${a.path} | ${a.phase} | ${a.timestamp} |`);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
lines.push('');
|
|
319
|
+
const indexPath = join(this.docsDir, 'INDEX.md');
|
|
320
|
+
writeFileSync(indexPath, lines.join('\n'), 'utf-8');
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** Get the latest artifact entry in a specific group */
|
|
324
|
+
private getLatestInGroup(
|
|
325
|
+
groupId: string,
|
|
326
|
+
existingArtifacts: ArtifactEntry[],
|
|
327
|
+
): ArtifactEntry | null {
|
|
328
|
+
const groupArtifacts = existingArtifacts
|
|
329
|
+
.filter((a) => a.group_id === groupId)
|
|
330
|
+
.sort((a, b) => a.version - b.version);
|
|
331
|
+
if (groupArtifacts.length === 0) return null;
|
|
332
|
+
return groupArtifacts[groupArtifacts.length - 1];
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/** Factory function */
|
|
337
|
+
export function createArtifactManager(projectDir: string): ArtifactManager {
|
|
338
|
+
return new ArtifactManager({ projectDir });
|
|
339
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Artifact Completeness Validators — deterministic structural checks.
|
|
3
|
+
* Runs BEFORE LLM review in consensus phases to catch obvious issues.
|
|
4
|
+
* Each validator checks for required sections, minimum content length,
|
|
5
|
+
* and structural integrity specific to its artifact type.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ArtifactType } from './types.js';
|
|
9
|
+
|
|
10
|
+
// ─── Types ───────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
export interface ValidationResult {
|
|
13
|
+
valid: boolean;
|
|
14
|
+
errors: string[];
|
|
15
|
+
warnings: string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// ─── Section Patterns ────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
/** Regex patterns for detecting markdown sections (case-insensitive) */
|
|
21
|
+
function hasSection(content: string, patterns: RegExp[]): boolean {
|
|
22
|
+
return patterns.some((p) => p.test(content));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function findMissingSections(
|
|
26
|
+
content: string,
|
|
27
|
+
required: { name: string; patterns: RegExp[] }[],
|
|
28
|
+
): string[] {
|
|
29
|
+
const missing: string[] = [];
|
|
30
|
+
for (const { name, patterns } of required) {
|
|
31
|
+
if (!hasSection(content, patterns)) {
|
|
32
|
+
missing.push(name);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return missing;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ─── Per-Type Validators ─────────────────────────────────
|
|
39
|
+
|
|
40
|
+
function validateMasterPlan(content: string): ValidationResult {
|
|
41
|
+
const errors: string[] = [];
|
|
42
|
+
const warnings: string[] = [];
|
|
43
|
+
|
|
44
|
+
if (content.length < 200) {
|
|
45
|
+
errors.push('Master plan is too short (min 200 characters)');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const missing = findMissingSections(content, [
|
|
49
|
+
{ name: 'Goals/Objectives', patterns: [/#+\s*(goals?|objectives?)/i, /\bgoals?\b.*:/i] },
|
|
50
|
+
{ name: 'Milestones', patterns: [/#+\s*milestones?/i, /\bmilestone\s+\d/i] },
|
|
51
|
+
{ name: 'Success Criteria', patterns: [/#+\s*success\s+criteria/i, /\bsuccess\s+criteria\b/i, /#+\s*acceptance\s+criteria/i] },
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
for (const section of missing) {
|
|
55
|
+
errors.push(`Missing required section: ${section}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Check for empty sections (heading followed by another heading or end)
|
|
59
|
+
const emptyHeadings = content.match(/^(#+\s+.+)\n(?=#+\s+|\s*$)/gm);
|
|
60
|
+
if (emptyHeadings && emptyHeadings.length > 2) {
|
|
61
|
+
warnings.push(`${emptyHeadings.length} potentially empty sections detected`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function validateArchitecture(content: string): ValidationResult {
|
|
68
|
+
const errors: string[] = [];
|
|
69
|
+
const warnings: string[] = [];
|
|
70
|
+
|
|
71
|
+
if (content.length < 200) {
|
|
72
|
+
errors.push('Architecture document is too short (min 200 characters)');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const missing = findMissingSections(content, [
|
|
76
|
+
{ name: 'Components/Modules', patterns: [/#+\s*(components?|modules?|services?)/i, /\bcomponent\b/i] },
|
|
77
|
+
{ name: 'Data Flow/Contracts', patterns: [/#+\s*(data\s+flow|contracts?|api|interfaces?)/i, /\bcontract\b/i, /\bdata\s+flow\b/i] },
|
|
78
|
+
{ name: 'Tech Stack', patterns: [/#+\s*(tech\s+stack|technology|stack)/i, /\btech\s+stack\b/i] },
|
|
79
|
+
]);
|
|
80
|
+
|
|
81
|
+
for (const section of missing) {
|
|
82
|
+
errors.push(`Missing required section: ${section}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Must reference at least one file path
|
|
86
|
+
const hasFilePath = /(?:src\/|app\/|pages\/|lib\/|\.ts|\.js|\.py|\.go)/.test(content);
|
|
87
|
+
if (!hasFilePath) {
|
|
88
|
+
warnings.push('Architecture should reference at least one file path');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function validateRolePlan(content: string): ValidationResult {
|
|
95
|
+
const errors: string[] = [];
|
|
96
|
+
const warnings: string[] = [];
|
|
97
|
+
|
|
98
|
+
if (content.length < 100) {
|
|
99
|
+
errors.push('Role plan is too short (min 100 characters)');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const missing = findMissingSections(content, [
|
|
103
|
+
{ name: 'Tasks/Responsibilities', patterns: [/#+\s*(tasks?|responsibilities?|work\s+items?)/i, /\btask\b/i] },
|
|
104
|
+
{ name: 'Dependencies', patterns: [/#+\s*(dependenc|prerequisites?|requires?)/i, /\bdepend/i] },
|
|
105
|
+
{ name: 'Acceptance Criteria', patterns: [/#+\s*(acceptance|done\s+when|completion)/i, /\bacceptance\b/i, /\bdone\s+when\b/i] },
|
|
106
|
+
]);
|
|
107
|
+
|
|
108
|
+
for (const section of missing) {
|
|
109
|
+
errors.push(`Missing required section: ${section}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Should reference a role name
|
|
113
|
+
const rolePatterns = /\b(DISPATCHER|ARCHITECT|DB_EXPERT|BACKEND|FRONTEND|WEBSITE|QA_TESTER|REVIEWER|AUDITOR|JOURNALIST)\b/i;
|
|
114
|
+
if (!rolePatterns.test(content)) {
|
|
115
|
+
warnings.push('Role plan should reference the role name');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function validateQaValidation(content: string): ValidationResult {
|
|
122
|
+
const errors: string[] = [];
|
|
123
|
+
const warnings: string[] = [];
|
|
124
|
+
|
|
125
|
+
const missing = findMissingSections(content, [
|
|
126
|
+
{ name: 'Test Results', patterns: [/#+\s*(test\s+results?|results?)/i, /\btest\s+results?\b/i, /\bpass(?:ed|ing)?\b/i] },
|
|
127
|
+
{ name: 'Coverage', patterns: [/#+\s*coverage/i, /\bcoverage\b/i, /\d+\s*%/] },
|
|
128
|
+
]);
|
|
129
|
+
|
|
130
|
+
for (const section of missing) {
|
|
131
|
+
errors.push(`Missing required section: ${section}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Should contain pass/fail counts
|
|
135
|
+
const hasPassFail = /\b\d+\s*(pass|fail|error|skip)/i.test(content);
|
|
136
|
+
if (!hasPassFail) {
|
|
137
|
+
warnings.push('QA validation should include pass/fail counts');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function validateAuditReport(content: string): ValidationResult {
|
|
144
|
+
const errors: string[] = [];
|
|
145
|
+
const warnings: string[] = [];
|
|
146
|
+
|
|
147
|
+
// Try JSON parsing
|
|
148
|
+
try {
|
|
149
|
+
const parsed = JSON.parse(content);
|
|
150
|
+
if (!Array.isArray(parsed.findings)) {
|
|
151
|
+
errors.push('Audit report must have a "findings" array');
|
|
152
|
+
}
|
|
153
|
+
if (typeof parsed.overall_status !== 'string') {
|
|
154
|
+
errors.push('Audit report must have "overall_status"');
|
|
155
|
+
}
|
|
156
|
+
if (typeof parsed.system_risk_score !== 'number') {
|
|
157
|
+
errors.push('Audit report must have "system_risk_score"');
|
|
158
|
+
}
|
|
159
|
+
} catch {
|
|
160
|
+
// Not JSON — check for markdown-style audit
|
|
161
|
+
if (!content.includes('findings') && !content.includes('finding')) {
|
|
162
|
+
errors.push('Audit report must contain findings');
|
|
163
|
+
}
|
|
164
|
+
if (!content.includes('status') && !content.includes('PASS') && !content.includes('FAIL')) {
|
|
165
|
+
errors.push('Audit report must contain overall status');
|
|
166
|
+
}
|
|
167
|
+
if (!content.includes('risk') && !content.includes('score')) {
|
|
168
|
+
warnings.push('Audit report should include risk score');
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ─── Validator Registry ──────────────────────────────────
|
|
176
|
+
|
|
177
|
+
const VALIDATORS: Partial<Record<ArtifactType, (content: string) => ValidationResult>> = {
|
|
178
|
+
master_plan: validateMasterPlan,
|
|
179
|
+
architecture: validateArchitecture,
|
|
180
|
+
role_plan: validateRolePlan,
|
|
181
|
+
qa_validation: validateQaValidation,
|
|
182
|
+
audit_report: validateAuditReport,
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// ─── Public API ──────────────────────────────────────────
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Validate artifact content completeness based on type-specific rules.
|
|
189
|
+
* Returns a ValidationResult with errors (blocking) and warnings (non-blocking).
|
|
190
|
+
*
|
|
191
|
+
* Args:
|
|
192
|
+
* type: The artifact type to validate against.
|
|
193
|
+
* content: The artifact content string.
|
|
194
|
+
*
|
|
195
|
+
* Returns:
|
|
196
|
+
* ValidationResult with valid flag, errors, and warnings.
|
|
197
|
+
*/
|
|
198
|
+
export function validateArtifactCompleteness(
|
|
199
|
+
type: ArtifactType,
|
|
200
|
+
content: string,
|
|
201
|
+
): ValidationResult {
|
|
202
|
+
const validator = VALIDATORS[type];
|
|
203
|
+
if (!validator) {
|
|
204
|
+
// No validator for this type — pass by default
|
|
205
|
+
return { valid: true, errors: [], warnings: [] };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (!content || content.trim().length === 0) {
|
|
209
|
+
return {
|
|
210
|
+
valid: false,
|
|
211
|
+
errors: [`${type} artifact has empty content`],
|
|
212
|
+
warnings: [],
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return validator(content);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Get all artifact types that have validators.
|
|
221
|
+
*/
|
|
222
|
+
export function getValidatableArtifactTypes(): ArtifactType[] {
|
|
223
|
+
return Object.keys(VALIDATORS) as ArtifactType[];
|
|
224
|
+
}
|