@@ -294,19 +294,24 @@ const PATTERN_TO_FUNCTION: Record<string, string> = {
294294} ;
295295
296296describe ( 'computePersonalityProfile — cognitive functions' , ( ) => {
297- it ( 'scores all 8 functions from mean confidence of their mapped pattern category' , ( ) => {
297+ it ( 'scores each function by its relative share of pattern instances, not mean confidence' , ( ) => {
298+ // 8 instances total: ni gets 2 (2x the 1/8 "fair share" -> capped at 100), six other
299+ // functions get exactly 1 each (exactly fair share -> 50/moderate), fe gets 0 (-> null).
300+ // Confidence is deliberately uniform (all 80) to prove the score no longer tracks it —
301+ // this is the fix for the old formula's "everything lands >=65 because confidence has a
302+ // 70 floor" bug (see computeCognitiveFunctions' doc comment in ../personality.ts).
298303 const facets : PersonalityFacetInput [ ] = [
299304 facet ( {
300305 effectivePatterns : [
301- ep ( { category : 'structured-planning' , confidence : 90 } ) ,
302- ep ( { category : 'structured-planning' , confidence : 70 } ) , // ni: (90+70)/2 = 80
303- ep ( { category : 'context-gathering' , confidence : 60 } ) , // ne: 60
304- ep ( { category : 'domain-expertise' , confidence : 40 } ) , // si: 40
305- ep ( { category : 'incremental-implementation' , confidence : 100 } ) , // se: 100
306- ep ( { category : 'systematic-debugging' , confidence : 55 } ) , // ti: 55
307- ep ( { category : 'verification-workflow' , confidence : 65 } ) , // te: 65
308- ep ( { category : 'self-correction' , confidence : 20 } ) , // fi: 20
309- ep ( { category : ' effective-tooling' , confidence : 75 } ) , // fe: 75
306+ ep ( { category : 'structured-planning' , confidence : 80 } ) , // ni
307+ ep ( { category : 'structured-planning' , confidence : 80 } ) , // ni (2nd instance)
308+ ep ( { category : 'context-gathering' , confidence : 80 } ) , // ne
309+ ep ( { category : 'domain-expertise' , confidence : 80 } ) , // si
310+ ep ( { category : 'incremental-implementation' , confidence : 80 } ) , // se
311+ ep ( { category : 'systematic-debugging' , confidence : 80 } ) , // ti
312+ ep ( { category : 'verification-workflow' , confidence : 80 } ) , // te
313+ ep ( { category : 'self-correction' , confidence : 80 } ) , // fi
314+ // effective-tooling (fe) deliberately absent
310315 ] ,
311316 } ) ,
312317 ] ;
@@ -316,15 +321,44 @@ describe('computePersonalityProfile — cognitive functions', () => {
316321 // Stable order check
317322 expect ( profile . cognitiveFunctions . map ( f => f . key ) ) . toEqual ( [ 'ni' , 'ne' , 'si' , 'se' , 'ti' , 'te' , 'fi' , 'fe' ] ) ;
318323
319- expect ( cogFn ( profile . cognitiveFunctions , 'ni' ) . score ) . toBe ( 80 ) ;
324+ expect ( cogFn ( profile . cognitiveFunctions , 'ni' ) . score ) . toBe ( 100 ) ;
320325 expect ( cogFn ( profile . cognitiveFunctions , 'ni' ) . sampleSize ) . toBe ( 2 ) ;
321- expect ( cogFn ( profile . cognitiveFunctions , 'ne' ) . score ) . toBe ( 60 ) ;
322- expect ( cogFn ( profile . cognitiveFunctions , 'si' ) . score ) . toBe ( 40 ) ;
323- expect ( cogFn ( profile . cognitiveFunctions , 'se' ) . score ) . toBe ( 100 ) ;
324- expect ( cogFn ( profile . cognitiveFunctions , 'ti' ) . score ) . toBe ( 55 ) ;
325- expect ( cogFn ( profile . cognitiveFunctions , 'te' ) . score ) . toBe ( 65 ) ;
326- expect ( cogFn ( profile . cognitiveFunctions , 'fi' ) . score ) . toBe ( 20 ) ;
327- expect ( cogFn ( profile . cognitiveFunctions , 'fe' ) . score ) . toBe ( 75 ) ;
326+ for ( const key of [ 'ne' , 'si' , 'se' , 'ti' , 'te' , 'fi' ] ) {
327+ expect ( cogFn ( profile . cognitiveFunctions , key ) . score ) . toBe ( 50 ) ;
328+ expect ( cogFn ( profile . cognitiveFunctions , key ) . sampleSize ) . toBe ( 1 ) ;
329+ }
330+
331+ const fe = cogFn ( profile . cognitiveFunctions , 'fe' ) ;
332+ expect ( fe . score ) . toBeNull ( ) ;
333+ expect ( fe . sampleSize ) . toBe ( 0 ) ;
334+ expect ( fe . band ) . toBeUndefined ( ) ;
335+ } ) ;
336+
337+ it ( 'differentiates functions purely by relative frequency even when confidence is identical' , ( ) => {
338+ // se: 2 instances, fi: 1 instance, plus 6 one-off fillers spread across the other
339+ // categories so neither se nor fi's share hits the 2x-fair-share cap (which would make
340+ // both saturate at 100 and hide the ordering this test is checking for).
341+ const facets : PersonalityFacetInput [ ] = [
342+ facet ( {
343+ effectivePatterns : [
344+ ep ( { category : 'incremental-implementation' , confidence : 95 } ) , // se
345+ ep ( { category : 'incremental-implementation' , confidence : 95 } ) , // se
346+ ep ( { category : 'self-correction' , confidence : 95 } ) , // fi
347+ ep ( { category : 'structured-planning' , confidence : 95 } ) , // ni filler
348+ ep ( { category : 'context-gathering' , confidence : 95 } ) , // ne filler
349+ ep ( { category : 'domain-expertise' , confidence : 95 } ) , // si filler
350+ ep ( { category : 'systematic-debugging' , confidence : 95 } ) , // ti filler
351+ ep ( { category : 'verification-workflow' , confidence : 95 } ) , // te filler
352+ ep ( { category : 'effective-tooling' , confidence : 95 } ) , // fe filler
353+ ] ,
354+ } ) ,
355+ ] ;
356+ const profile = computePersonalityProfile ( facets , [ ] , '2026-W29' , '__all__' ) ;
357+ const se = cogFn ( profile . cognitiveFunctions , 'se' ) ;
358+ const fi = cogFn ( profile . cognitiveFunctions , 'fi' ) ;
359+ expect ( se . score ) . not . toBeNull ( ) ;
360+ expect ( fi . score ) . not . toBeNull ( ) ;
361+ expect ( se . score ! ) . toBeGreaterThan ( fi . score ! ) ;
328362 } ) ;
329363
330364 it ( 'is null with sampleSize 0 for a function whose category has zero pattern instances' , ( ) => {
@@ -338,15 +372,29 @@ describe('computePersonalityProfile — cognitive functions', () => {
338372 expect ( fe . band ) . toBeUndefined ( ) ;
339373 } ) ;
340374
341- it ( 'maps each of the 8 known categories to its documented function independently ' , ( ) => {
375+ it ( 'scores an isolated single-category sample at 100 — its entire share of the total ' , ( ) => {
342376 for ( const [ category , fn ] of Object . entries ( PATTERN_TO_FUNCTION ) ) {
343377 const facets : PersonalityFacetInput [ ] = [
344378 facet ( { effectivePatterns : [ ep ( { category, confidence : 88 } ) ] } ) ,
345379 ] ;
346380 const profile = computePersonalityProfile ( facets , [ ] , '2026-W29' , '__all__' ) ;
347- expect ( cogFn ( profile . cognitiveFunctions , fn ) . score ) . toBe ( 88 ) ;
381+ expect ( cogFn ( profile . cognitiveFunctions , fn ) . score ) . toBe ( 100 ) ;
348382 }
349383 } ) ;
384+
385+ it ( 'returns all-null functions when there are zero effective pattern instances' , ( ) => {
386+ const facets : PersonalityFacetInput [ ] = [ facet ( { effectivePatterns : [ ] } ) ] ;
387+ const profile = computePersonalityProfile ( facets , [ ] , '2026-W29' , '__all__' ) ;
388+ for ( const f of profile . cognitiveFunctions ) {
389+ expect ( f . score ) . toBeNull ( ) ;
390+ expect ( f . sampleSize ) . toBe ( 0 ) ;
391+ }
392+ } ) ;
393+
394+ it ( 'sets cognitiveFunctionScoringMode to formula (the only mode this pure function knows about)' , ( ) => {
395+ const profile = computePersonalityProfile ( [ facet ( ) ] , [ ] , '2026-W29' , '__all__' ) ;
396+ expect ( profile . cognitiveFunctionScoringMode ) . toBe ( 'formula' ) ;
397+ } ) ;
350398} ) ;
351399
352400// ── MBTI derivation ──────────────────────────────────────────────────────────
0 commit comments