@@ -630,6 +630,80 @@ describe('spec-discovery', () => {
630630 } ) ;
631631 } ) ;
632632
633+ describe ( 'validateSpecStructure() - path length validation' , ( ) => {
634+ it ( 'should warn when full path exceeds Windows MAX_PATH (260)' , ( ) => {
635+ // Build a path that exceeds 260 chars total
636+ const prefix = '/a-long-project-root-directory/with/many/levels/openspec/specs/' ;
637+ const longSegments = Array . from ( { length : 8 } , ( _ , i ) => `segment-${ String ( i ) . padStart ( 2 , '0' ) } -with-extra-padding` ) ;
638+ const capability = longSegments . join ( path . sep ) ;
639+ const fullPath = prefix + longSegments . join ( '/' ) + '/spec.md' ;
640+
641+ expect ( fullPath . length ) . toBeGreaterThan ( 260 ) ;
642+
643+ const specs : DiscoveredSpec [ ] = [
644+ { capability, path : fullPath , depth : 8 } ,
645+ ] ;
646+
647+ const issues = validateSpecStructure ( specs , { validatePaths : true , maxDepth : 10 } ) ;
648+ const lengthIssues = issues . filter ( i => i . message . includes ( 'characters' ) ) ;
649+
650+ expect ( lengthIssues ) . toHaveLength ( 1 ) ;
651+ expect ( lengthIssues [ 0 ] . level ) . toBe ( 'WARNING' ) ;
652+ expect ( lengthIssues [ 0 ] . message ) . toContain ( 'Windows MAX_PATH' ) ;
653+ } ) ;
654+
655+ it ( 'should not warn for paths under 260 characters' , ( ) => {
656+ const specs : DiscoveredSpec [ ] = [
657+ { capability : path . join ( 'platform' , 'services' , 'api' ) , path : '/specs/platform/services/api/spec.md' , depth : 3 } ,
658+ ] ;
659+
660+ const issues = validateSpecStructure ( specs , { validatePaths : true , maxDepth : 4 } ) ;
661+ const lengthIssues = issues . filter ( i => i . message . includes ( 'characters' ) ) ;
662+
663+ expect ( lengthIssues ) . toHaveLength ( 0 ) ;
664+ } ) ;
665+
666+ it ( 'should warn when capability path exceeds 160 characters (even if full path under 260)' , ( ) => {
667+ // Build a capability that exceeds 160 chars but keep full path under 260
668+ const longSegments = Array . from ( { length : 6 } , ( _ , i ) => `long-segment-name-${ String ( i ) . padStart ( 2 , '0' ) } -padding` ) ;
669+ const capability = longSegments . join ( path . sep ) ;
670+
671+ expect ( capability . length ) . toBeGreaterThan ( 160 ) ;
672+
673+ // Short prefix keeps full path under 260
674+ const fullPath = '/specs/' + longSegments . join ( '/' ) + '/spec.md' ;
675+ expect ( fullPath . length ) . toBeLessThan ( 260 ) ;
676+
677+ const specs : DiscoveredSpec [ ] = [
678+ { capability, path : fullPath , depth : 6 } ,
679+ ] ;
680+
681+ const issues = validateSpecStructure ( specs , { validatePaths : true , maxDepth : 10 } ) ;
682+ const lengthIssues = issues . filter ( i => i . message . includes ( 'characters' ) ) ;
683+
684+ expect ( lengthIssues ) . toHaveLength ( 1 ) ;
685+ expect ( lengthIssues [ 0 ] . level ) . toBe ( 'WARNING' ) ;
686+ expect ( lengthIssues [ 0 ] . message ) . toContain ( 'capability path' ) ;
687+ expect ( lengthIssues [ 0 ] . message ) . toContain ( '160' ) ;
688+ } ) ;
689+
690+ it ( 'should skip path length check when validatePaths is false' , ( ) => {
691+ const prefix = '/a-long-project-root-directory/with/many/levels/openspec/specs/' ;
692+ const longSegments = Array . from ( { length : 8 } , ( _ , i ) => `segment-${ String ( i ) . padStart ( 2 , '0' ) } -with-extra-padding` ) ;
693+ const capability = longSegments . join ( path . sep ) ;
694+ const fullPath = prefix + longSegments . join ( '/' ) + '/spec.md' ;
695+
696+ const specs : DiscoveredSpec [ ] = [
697+ { capability, path : fullPath , depth : 8 } ,
698+ ] ;
699+
700+ const issues = validateSpecStructure ( specs , { validatePaths : false , maxDepth : 10 } ) ;
701+ const lengthIssues = issues . filter ( i => i . message . includes ( 'characters' ) ) ;
702+
703+ expect ( lengthIssues ) . toHaveLength ( 0 ) ;
704+ } ) ;
705+ } ) ;
706+
633707 describe ( 'validateSpecStructure() - reserved names validation' , ( ) => {
634708 it ( 'should reject reserved directory names' , ( ) => {
635709 const reservedNames = [
0 commit comments