popeye-cli 1.10.0 → 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/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/PHASE_GATE_ENGINE_SPEC.md +113 -20
- package/skills/POPEYE_FULL_AUTONOMY_PIPELINE.md +66 -13
- 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/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
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Artifact Validators tests — completeness checks for each artifact type.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
import {
|
|
7
|
+
validateArtifactCompleteness,
|
|
8
|
+
getValidatableArtifactTypes,
|
|
9
|
+
} from '../../src/pipeline/artifact-validators.js';
|
|
10
|
+
|
|
11
|
+
describe('validateArtifactCompleteness', () => {
|
|
12
|
+
describe('master_plan', () => {
|
|
13
|
+
it('should pass for complete master plan', () => {
|
|
14
|
+
const content = [
|
|
15
|
+
'# Master Plan',
|
|
16
|
+
'## Goals',
|
|
17
|
+
'Build a comprehensive todo application with real-time sync capabilities.',
|
|
18
|
+
'The application should support multiple users and collaborative editing features.',
|
|
19
|
+
'## Milestones',
|
|
20
|
+
'Milestone 1: Setup project structure, install dependencies, configure CI/CD pipeline',
|
|
21
|
+
'Milestone 2: Core features including task CRUD, real-time updates, and user auth',
|
|
22
|
+
'## Success Criteria',
|
|
23
|
+
'All tests pass with 80% coverage, app deploys successfully to production environment.',
|
|
24
|
+
].join('\n');
|
|
25
|
+
|
|
26
|
+
const result = validateArtifactCompleteness('master_plan', content);
|
|
27
|
+
expect(result.valid).toBe(true);
|
|
28
|
+
expect(result.errors).toHaveLength(0);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should fail for empty content', () => {
|
|
32
|
+
const result = validateArtifactCompleteness('master_plan', '');
|
|
33
|
+
expect(result.valid).toBe(false);
|
|
34
|
+
expect(result.errors[0]).toContain('empty content');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should fail for too-short content', () => {
|
|
38
|
+
const result = validateArtifactCompleteness('master_plan', '# Plan\nGoals: TBD');
|
|
39
|
+
expect(result.valid).toBe(false);
|
|
40
|
+
expect(result.errors.some((e) => e.includes('too short'))).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should fail when missing Goals section', () => {
|
|
44
|
+
const content = [
|
|
45
|
+
'# Master Plan',
|
|
46
|
+
'Some text that is long enough to pass the minimum length check.',
|
|
47
|
+
'## Milestones',
|
|
48
|
+
'Milestone 1: Setup project structure and dependencies.',
|
|
49
|
+
'## Success Criteria',
|
|
50
|
+
'All tests pass and the application deploys.',
|
|
51
|
+
].join('\n');
|
|
52
|
+
|
|
53
|
+
const result = validateArtifactCompleteness('master_plan', content);
|
|
54
|
+
expect(result.valid).toBe(false);
|
|
55
|
+
expect(result.errors.some((e) => e.includes('Goals'))).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should fail when missing Milestones section', () => {
|
|
59
|
+
const content = [
|
|
60
|
+
'# Master Plan',
|
|
61
|
+
'## Goals',
|
|
62
|
+
'Build a comprehensive todo application with real-time sync.',
|
|
63
|
+
'The application should support multiple users.',
|
|
64
|
+
'## Success Criteria',
|
|
65
|
+
'All tests pass and the application deploys successfully.',
|
|
66
|
+
].join('\n');
|
|
67
|
+
|
|
68
|
+
const result = validateArtifactCompleteness('master_plan', content);
|
|
69
|
+
expect(result.valid).toBe(false);
|
|
70
|
+
expect(result.errors.some((e) => e.includes('Milestones'))).toBe(true);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('architecture', () => {
|
|
75
|
+
it('should pass for complete architecture', () => {
|
|
76
|
+
const content = [
|
|
77
|
+
'# Architecture',
|
|
78
|
+
'## Components',
|
|
79
|
+
'Frontend: React SPA in src/app/ with component-based architecture using TypeScript.',
|
|
80
|
+
'Backend: FastAPI in src/server/ with layered architecture (routes, services, models).',
|
|
81
|
+
'## Data Flow',
|
|
82
|
+
'REST API contracts between FE and BE, with JSON payloads and OpenAPI specification.',
|
|
83
|
+
'## Tech Stack',
|
|
84
|
+
'React 18, TypeScript 5.x, FastAPI 0.100+, PostgreSQL 15, Redis for caching.',
|
|
85
|
+
].join('\n');
|
|
86
|
+
|
|
87
|
+
const result = validateArtifactCompleteness('architecture', content);
|
|
88
|
+
expect(result.valid).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should fail when missing components section', () => {
|
|
92
|
+
const content = [
|
|
93
|
+
'# Architecture',
|
|
94
|
+
'Some long enough content for the min length check.',
|
|
95
|
+
'## Data Flow',
|
|
96
|
+
'REST API between FE and BE using src/api/ routes.',
|
|
97
|
+
'## Tech Stack',
|
|
98
|
+
'React, TypeScript, FastAPI',
|
|
99
|
+
].join('\n');
|
|
100
|
+
|
|
101
|
+
const result = validateArtifactCompleteness('architecture', content);
|
|
102
|
+
expect(result.valid).toBe(false);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should warn when no file paths referenced', () => {
|
|
106
|
+
const content = [
|
|
107
|
+
'# Architecture',
|
|
108
|
+
'## Components',
|
|
109
|
+
'Frontend and Backend modules communicating via REST protocol for data exchange.',
|
|
110
|
+
'The component architecture follows a modular design pattern with clear separation.',
|
|
111
|
+
'## Contracts',
|
|
112
|
+
'JSON-based API contracts define the interface between all system components clearly.',
|
|
113
|
+
'## Tech Stack',
|
|
114
|
+
'React for the frontend, TypeScript for type safety, and a backend runtime environment.',
|
|
115
|
+
].join('\n');
|
|
116
|
+
|
|
117
|
+
const result = validateArtifactCompleteness('architecture', content);
|
|
118
|
+
expect(result.warnings.some((w) => w.includes('file path'))).toBe(true);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('role_plan', () => {
|
|
123
|
+
it('should pass for complete role plan', () => {
|
|
124
|
+
const content = [
|
|
125
|
+
'# FRONTEND_PROGRAMMER Role Plan',
|
|
126
|
+
'## Tasks',
|
|
127
|
+
'- Build login page',
|
|
128
|
+
'- Implement dashboard',
|
|
129
|
+
'## Dependencies',
|
|
130
|
+
'Requires API contracts from BACKEND_PROGRAMMER.',
|
|
131
|
+
'## Acceptance Criteria',
|
|
132
|
+
'All pages render, tests pass.',
|
|
133
|
+
].join('\n');
|
|
134
|
+
|
|
135
|
+
const result = validateArtifactCompleteness('role_plan', content);
|
|
136
|
+
expect(result.valid).toBe(true);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should fail for too-short role plan', () => {
|
|
140
|
+
const result = validateArtifactCompleteness('role_plan', '# Plan');
|
|
141
|
+
expect(result.valid).toBe(false);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe('qa_validation', () => {
|
|
146
|
+
it('should pass with test results and coverage', () => {
|
|
147
|
+
const content = [
|
|
148
|
+
'# QA Validation',
|
|
149
|
+
'## Test Results',
|
|
150
|
+
'45 tests passing, 0 failing',
|
|
151
|
+
'## Coverage',
|
|
152
|
+
'Overall coverage: 87%',
|
|
153
|
+
].join('\n');
|
|
154
|
+
|
|
155
|
+
const result = validateArtifactCompleteness('qa_validation', content);
|
|
156
|
+
expect(result.valid).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('should fail without test results section', () => {
|
|
160
|
+
const content = '# QA\n## Coverage\n80% coverage';
|
|
161
|
+
const result = validateArtifactCompleteness('qa_validation', content);
|
|
162
|
+
expect(result.valid).toBe(false);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe('audit_report (JSON)', () => {
|
|
167
|
+
it('should pass for valid JSON audit report', () => {
|
|
168
|
+
const content = JSON.stringify({
|
|
169
|
+
findings: [{ id: '1', severity: 'P2' }],
|
|
170
|
+
overall_status: 'PASS',
|
|
171
|
+
system_risk_score: 25,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const result = validateArtifactCompleteness('audit_report', content);
|
|
175
|
+
expect(result.valid).toBe(true);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('should fail when missing findings array', () => {
|
|
179
|
+
const content = JSON.stringify({
|
|
180
|
+
overall_status: 'PASS',
|
|
181
|
+
system_risk_score: 25,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const result = validateArtifactCompleteness('audit_report', content);
|
|
185
|
+
expect(result.valid).toBe(false);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe('unknown artifact types', () => {
|
|
190
|
+
it('should pass for artifact types without validators', () => {
|
|
191
|
+
const result = validateArtifactCompleteness('release_notes', 'Any content');
|
|
192
|
+
expect(result.valid).toBe(true);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe('getValidatableArtifactTypes', () => {
|
|
197
|
+
it('should return artifact types with registered validators', () => {
|
|
198
|
+
const types = getValidatableArtifactTypes();
|
|
199
|
+
expect(types).toContain('master_plan');
|
|
200
|
+
expect(types).toContain('architecture');
|
|
201
|
+
expect(types).toContain('role_plan');
|
|
202
|
+
expect(types).toContain('qa_validation');
|
|
203
|
+
expect(types).toContain('audit_report');
|
|
204
|
+
expect(types.length).toBe(5);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
});
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Change Request tests — builder, routing, formatting.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
import {
|
|
7
|
+
buildChangeRequest,
|
|
8
|
+
routeChangeRequest,
|
|
9
|
+
formatChangeRequest,
|
|
10
|
+
} from '../../src/pipeline/change-request.js';
|
|
11
|
+
import type { ArtifactRef } from '../../src/pipeline/types.js';
|
|
12
|
+
|
|
13
|
+
const mockRef: ArtifactRef = {
|
|
14
|
+
artifact_id: 'test-artifact',
|
|
15
|
+
path: 'docs/test.md',
|
|
16
|
+
sha256: 'abc',
|
|
17
|
+
version: 1,
|
|
18
|
+
type: 'repo_snapshot',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
describe('buildChangeRequest', () => {
|
|
22
|
+
it('should create a CR with generated ID and timestamp', () => {
|
|
23
|
+
const cr = buildChangeRequest({
|
|
24
|
+
originPhase: 'REVIEW',
|
|
25
|
+
requestedBy: 'REVIEWER',
|
|
26
|
+
changeType: 'config',
|
|
27
|
+
description: 'Config files changed',
|
|
28
|
+
justification: 'Drift detected',
|
|
29
|
+
affectedArtifacts: [mockRef],
|
|
30
|
+
affectedPhases: ['IMPLEMENTATION'],
|
|
31
|
+
riskLevel: 'medium',
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
expect(cr.cr_id).toMatch(/^CR-/);
|
|
35
|
+
expect(cr.timestamp).toBeTruthy();
|
|
36
|
+
expect(cr.status).toBe('proposed');
|
|
37
|
+
expect(cr.origin_phase).toBe('REVIEW');
|
|
38
|
+
expect(cr.requested_by).toBe('REVIEWER');
|
|
39
|
+
expect(cr.change_type).toBe('config');
|
|
40
|
+
expect(cr.impact_analysis.risk_level).toBe('medium');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should generate unique IDs for different CRs', () => {
|
|
44
|
+
const cr1 = buildChangeRequest({
|
|
45
|
+
originPhase: 'AUDIT',
|
|
46
|
+
requestedBy: 'AUDITOR',
|
|
47
|
+
changeType: 'architecture',
|
|
48
|
+
description: 'Arch change',
|
|
49
|
+
justification: 'Finding',
|
|
50
|
+
affectedArtifacts: [],
|
|
51
|
+
affectedPhases: [],
|
|
52
|
+
riskLevel: 'high',
|
|
53
|
+
});
|
|
54
|
+
const cr2 = buildChangeRequest({
|
|
55
|
+
originPhase: 'AUDIT',
|
|
56
|
+
requestedBy: 'AUDITOR',
|
|
57
|
+
changeType: 'scope',
|
|
58
|
+
description: 'Scope change',
|
|
59
|
+
justification: 'Finding 2',
|
|
60
|
+
affectedArtifacts: [],
|
|
61
|
+
affectedPhases: [],
|
|
62
|
+
riskLevel: 'low',
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
expect(cr1.cr_id).not.toBe(cr2.cr_id);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should include impact analysis', () => {
|
|
69
|
+
const cr = buildChangeRequest({
|
|
70
|
+
originPhase: 'REVIEW',
|
|
71
|
+
requestedBy: 'REVIEWER',
|
|
72
|
+
changeType: 'scope',
|
|
73
|
+
description: 'Scope expanded',
|
|
74
|
+
justification: 'New requirements',
|
|
75
|
+
affectedArtifacts: [mockRef],
|
|
76
|
+
affectedPhases: ['CONSENSUS_MASTER_PLAN', 'IMPLEMENTATION'],
|
|
77
|
+
riskLevel: 'high',
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
expect(cr.impact_analysis.affected_artifacts).toHaveLength(1);
|
|
81
|
+
expect(cr.impact_analysis.affected_phases).toContain('CONSENSUS_MASTER_PLAN');
|
|
82
|
+
expect(cr.impact_analysis.risk_level).toBe('high');
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('routeChangeRequest', () => {
|
|
87
|
+
it('should route scope changes to CONSENSUS_MASTER_PLAN', () => {
|
|
88
|
+
const cr = buildChangeRequest({
|
|
89
|
+
originPhase: 'REVIEW',
|
|
90
|
+
requestedBy: 'REVIEWER',
|
|
91
|
+
changeType: 'scope',
|
|
92
|
+
description: 'test',
|
|
93
|
+
justification: 'test',
|
|
94
|
+
affectedArtifacts: [],
|
|
95
|
+
affectedPhases: [],
|
|
96
|
+
riskLevel: 'low',
|
|
97
|
+
});
|
|
98
|
+
expect(routeChangeRequest(cr)).toBe('CONSENSUS_MASTER_PLAN');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should route architecture changes to CONSENSUS_ARCHITECTURE', () => {
|
|
102
|
+
const cr = buildChangeRequest({
|
|
103
|
+
originPhase: 'AUDIT',
|
|
104
|
+
requestedBy: 'AUDITOR',
|
|
105
|
+
changeType: 'architecture',
|
|
106
|
+
description: 'test',
|
|
107
|
+
justification: 'test',
|
|
108
|
+
affectedArtifacts: [],
|
|
109
|
+
affectedPhases: [],
|
|
110
|
+
riskLevel: 'high',
|
|
111
|
+
});
|
|
112
|
+
expect(routeChangeRequest(cr)).toBe('CONSENSUS_ARCHITECTURE');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should route dependency changes to CONSENSUS_ROLE_PLANS', () => {
|
|
116
|
+
const cr = buildChangeRequest({
|
|
117
|
+
originPhase: 'REVIEW',
|
|
118
|
+
requestedBy: 'REVIEWER',
|
|
119
|
+
changeType: 'dependency',
|
|
120
|
+
description: 'test',
|
|
121
|
+
justification: 'test',
|
|
122
|
+
affectedArtifacts: [],
|
|
123
|
+
affectedPhases: [],
|
|
124
|
+
riskLevel: 'medium',
|
|
125
|
+
});
|
|
126
|
+
expect(routeChangeRequest(cr)).toBe('CONSENSUS_ROLE_PLANS');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should route config changes to QA_VALIDATION', () => {
|
|
130
|
+
const cr = buildChangeRequest({
|
|
131
|
+
originPhase: 'REVIEW',
|
|
132
|
+
requestedBy: 'REVIEWER',
|
|
133
|
+
changeType: 'config',
|
|
134
|
+
description: 'test',
|
|
135
|
+
justification: 'test',
|
|
136
|
+
affectedArtifacts: [],
|
|
137
|
+
affectedPhases: [],
|
|
138
|
+
riskLevel: 'low',
|
|
139
|
+
});
|
|
140
|
+
expect(routeChangeRequest(cr)).toBe('QA_VALIDATION');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should route requirement changes to CONSENSUS_MASTER_PLAN', () => {
|
|
144
|
+
const cr = buildChangeRequest({
|
|
145
|
+
originPhase: 'AUDIT',
|
|
146
|
+
requestedBy: 'AUDITOR',
|
|
147
|
+
changeType: 'requirement',
|
|
148
|
+
description: 'test',
|
|
149
|
+
justification: 'test',
|
|
150
|
+
affectedArtifacts: [],
|
|
151
|
+
affectedPhases: [],
|
|
152
|
+
riskLevel: 'high',
|
|
153
|
+
});
|
|
154
|
+
expect(routeChangeRequest(cr)).toBe('CONSENSUS_MASTER_PLAN');
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe('formatChangeRequest', () => {
|
|
159
|
+
it('should format CR as markdown', () => {
|
|
160
|
+
const cr = buildChangeRequest({
|
|
161
|
+
originPhase: 'REVIEW',
|
|
162
|
+
requestedBy: 'REVIEWER',
|
|
163
|
+
changeType: 'config',
|
|
164
|
+
description: 'Config files changed during implementation',
|
|
165
|
+
justification: 'Detected by snapshot diff',
|
|
166
|
+
affectedArtifacts: [mockRef],
|
|
167
|
+
affectedPhases: ['IMPLEMENTATION'],
|
|
168
|
+
riskLevel: 'medium',
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const md = formatChangeRequest(cr);
|
|
172
|
+
expect(md).toContain('# Change Request');
|
|
173
|
+
expect(md).toContain(cr.cr_id);
|
|
174
|
+
expect(md).toContain('proposed');
|
|
175
|
+
expect(md).toContain('config');
|
|
176
|
+
expect(md).toContain('REVIEWER');
|
|
177
|
+
expect(md).toContain('medium');
|
|
178
|
+
expect(md).toContain('Config files changed');
|
|
179
|
+
});
|
|
180
|
+
});
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check Runner tests — sanitization, timeout, result capture, placeholder scan.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
6
|
+
import { mkdirSync, rmSync, writeFileSync, existsSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { runCheck, runAllChecks, runPlaceholderScan } from '../../src/pipeline/check-runner.js';
|
|
9
|
+
|
|
10
|
+
const TEST_DIR = join(process.cwd(), '.test-check-runner');
|
|
11
|
+
|
|
12
|
+
describe('CheckRunner', () => {
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true });
|
|
15
|
+
mkdirSync(TEST_DIR, { recursive: true });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('runCheck', () => {
|
|
23
|
+
it('should return pass for successful commands', async () => {
|
|
24
|
+
const result = await runCheck('test', 'echo "ok"', TEST_DIR);
|
|
25
|
+
expect(result.check_type).toBe('test');
|
|
26
|
+
expect(result.status).toBe('pass');
|
|
27
|
+
expect(result.exit_code).toBe(0);
|
|
28
|
+
expect(result.duration_ms).toBeGreaterThanOrEqual(0);
|
|
29
|
+
expect(result.timestamp).toBeDefined();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should return fail for unsuccessful commands', async () => {
|
|
33
|
+
const result = await runCheck('build', 'exit 1', TEST_DIR);
|
|
34
|
+
expect(result.status).toBe('fail');
|
|
35
|
+
expect(result.exit_code).not.toBe(0);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should reject dangerous commands', async () => {
|
|
39
|
+
const result = await runCheck('test', 'rm -rf /', TEST_DIR);
|
|
40
|
+
expect(result.status).toBe('fail');
|
|
41
|
+
expect(result.exit_code).toBe(-1);
|
|
42
|
+
expect(result.stderr_summary).toContain('Command rejected');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should reject sudo commands', async () => {
|
|
46
|
+
const result = await runCheck('build', 'sudo apt install something', TEST_DIR);
|
|
47
|
+
expect(result.status).toBe('fail');
|
|
48
|
+
expect(result.stderr_summary).toContain('Command rejected');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should respect timeout', async () => {
|
|
52
|
+
const result = await runCheck('test', 'sleep 10', TEST_DIR, 500);
|
|
53
|
+
// Should either timeout or be killed
|
|
54
|
+
expect(result.status).toBe('fail');
|
|
55
|
+
}, 10000);
|
|
56
|
+
|
|
57
|
+
it('should capture stderr summary', async () => {
|
|
58
|
+
const result = await runCheck('lint', 'echo "error" >&2 && exit 1', TEST_DIR);
|
|
59
|
+
expect(result.status).toBe('fail');
|
|
60
|
+
expect(result.stderr_summary).toContain('error');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('runAllChecks', () => {
|
|
65
|
+
it('should run all provided commands', async () => {
|
|
66
|
+
const results = await runAllChecks({
|
|
67
|
+
build: 'echo "build ok"',
|
|
68
|
+
test: 'echo "test ok"',
|
|
69
|
+
lint: 'echo "lint ok"',
|
|
70
|
+
resolved_from: 'test',
|
|
71
|
+
}, TEST_DIR);
|
|
72
|
+
|
|
73
|
+
expect(results.length).toBe(5); // build, test, lint, typecheck(skip), migration(skip)
|
|
74
|
+
expect(results.filter((r) => r.status === 'pass')).toHaveLength(3);
|
|
75
|
+
expect(results.filter((r) => r.status === 'skip')).toHaveLength(2);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should skip missing commands', async () => {
|
|
79
|
+
const results = await runAllChecks({
|
|
80
|
+
resolved_from: 'test',
|
|
81
|
+
}, TEST_DIR);
|
|
82
|
+
|
|
83
|
+
expect(results.every((r) => r.status === 'skip')).toBe(true);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('runPlaceholderScan', () => {
|
|
88
|
+
it('should detect TODO in source files', () => {
|
|
89
|
+
mkdirSync(join(TEST_DIR, 'src'), { recursive: true });
|
|
90
|
+
writeFileSync(
|
|
91
|
+
join(TEST_DIR, 'src', 'app.ts'),
|
|
92
|
+
'// TODO: implement this\nconst x = 1;\n',
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const result = runPlaceholderScan(TEST_DIR);
|
|
96
|
+
expect(result.status).toBe('fail');
|
|
97
|
+
expect(result.check_type).toBe('placeholder_scan');
|
|
98
|
+
expect(result.stderr_summary).toContain('TODO');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should detect FIXME in source files', () => {
|
|
102
|
+
mkdirSync(join(TEST_DIR, 'src'), { recursive: true });
|
|
103
|
+
writeFileSync(
|
|
104
|
+
join(TEST_DIR, 'src', 'util.ts'),
|
|
105
|
+
'const value = 0; // FIXME: broken\n',
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const result = runPlaceholderScan(TEST_DIR);
|
|
109
|
+
expect(result.status).toBe('fail');
|
|
110
|
+
expect(result.stderr_summary).toContain('FIXME');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should detect lorem ipsum', () => {
|
|
114
|
+
mkdirSync(join(TEST_DIR, 'src'), { recursive: true });
|
|
115
|
+
writeFileSync(
|
|
116
|
+
join(TEST_DIR, 'src', 'page.tsx'),
|
|
117
|
+
'const text = "Lorem ipsum dolor sit amet";\n',
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const result = runPlaceholderScan(TEST_DIR);
|
|
121
|
+
expect(result.status).toBe('fail');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should pass when no placeholders found', () => {
|
|
125
|
+
mkdirSync(join(TEST_DIR, 'src'), { recursive: true });
|
|
126
|
+
writeFileSync(
|
|
127
|
+
join(TEST_DIR, 'src', 'clean.ts'),
|
|
128
|
+
'export function add(a: number, b: number): number {\n return a + b;\n}\n',
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const result = runPlaceholderScan(TEST_DIR);
|
|
132
|
+
expect(result.status).toBe('pass');
|
|
133
|
+
expect(result.exit_code).toBe(0);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should respect allowlist', () => {
|
|
137
|
+
mkdirSync(join(TEST_DIR, 'src'), { recursive: true });
|
|
138
|
+
writeFileSync(
|
|
139
|
+
join(TEST_DIR, 'src', 'app.ts'),
|
|
140
|
+
'// TODO: allowed\n',
|
|
141
|
+
);
|
|
142
|
+
writeFileSync(
|
|
143
|
+
join(TEST_DIR, '.popeye-placeholder-allowlist'),
|
|
144
|
+
'src/app.ts\n',
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
const result = runPlaceholderScan(TEST_DIR);
|
|
148
|
+
expect(result.status).toBe('pass');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should handle missing scan directories gracefully', () => {
|
|
152
|
+
// No src, app, pages, etc. directories
|
|
153
|
+
const result = runPlaceholderScan(TEST_DIR);
|
|
154
|
+
expect(result.status).toBe('pass');
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
});
|