@@ -2029,6 +2029,141 @@ The agent handles various tasks and operations in the system.
20292029 } )
20302030 } )
20312031
2032+ describe ( "shipped agent files validation" , ( ) => {
2033+ it ( "should validate all shipped agent files pass content validation" , async ( ) => {
2034+ const { AGENT_NAMES , validateAgentContent } = await import ( "../src/paths.mjs" )
2035+
2036+ const agentsDir = join ( process . cwd ( ) , "agents" )
2037+
2038+ for ( const agentName of AGENT_NAMES ) {
2039+ const filePath = join ( agentsDir , `${ agentName } .md` )
2040+
2041+ // Verify file exists
2042+ expect ( existsSync ( filePath ) ) . toBe ( true )
2043+
2044+ // Read and validate content
2045+ const content = readFileSync ( filePath , "utf-8" )
2046+ const result = validateAgentContent ( content )
2047+
2048+ // Should pass validation
2049+ expect ( result . valid ) . toBe ( true )
2050+ if ( ! result . valid ) {
2051+ // Provide helpful error message if validation fails
2052+ throw new Error ( `Agent ${ agentName } .md failed validation: ${ result . error } ` )
2053+ }
2054+ }
2055+ } )
2056+
2057+ it ( "should validate all shipped agent files have valid frontmatter" , async ( ) => {
2058+ const { AGENT_NAMES , parseFrontmatter, REQUIRED_FRONTMATTER_FIELDS } = await import (
2059+ "../src/paths.mjs"
2060+ )
2061+
2062+ const agentsDir = join ( process . cwd ( ) , "agents" )
2063+
2064+ for ( const agentName of AGENT_NAMES ) {
2065+ const filePath = join ( agentsDir , `${ agentName } .md` )
2066+ const content = readFileSync ( filePath , "utf-8" )
2067+
2068+ // Parse frontmatter
2069+ const frontmatter = parseFrontmatter ( content )
2070+
2071+ // Should have frontmatter
2072+ expect ( frontmatter . found ) . toBe ( true )
2073+
2074+ // Should have all required fields
2075+ for ( const field of REQUIRED_FRONTMATTER_FIELDS ) {
2076+ expect ( frontmatter . fields [ field ] ) . toBeDefined ( )
2077+ expect ( frontmatter . fields [ field ] ) . not . toBe ( "" )
2078+ }
2079+
2080+ // Verify version field is valid semver format
2081+ const versionField = frontmatter . fields . version
2082+ expect ( versionField ) . toMatch ( / ^ \d + \. \d + \. \d + $ / )
2083+
2084+ // Verify requires field is a valid version constraint
2085+ const requiresField = frontmatter . fields . requires
2086+ expect ( requiresField ) . toMatch ( / ^ [ > = < ^ ~ ] * \d + \. \d + \. \d + $ / )
2087+ }
2088+ } )
2089+
2090+ it ( "should validate all shipped agent files have markdown headers after frontmatter" , async ( ) => {
2091+ const { AGENT_NAMES , parseFrontmatter } = await import ( "../src/paths.mjs" )
2092+
2093+ const agentsDir = join ( process . cwd ( ) , "agents" )
2094+
2095+ for ( const agentName of AGENT_NAMES ) {
2096+ const filePath = join ( agentsDir , `${ agentName } .md` )
2097+ const content = readFileSync ( filePath , "utf-8" )
2098+
2099+ // Parse frontmatter to get endIndex
2100+ const frontmatter = parseFrontmatter ( content )
2101+ expect ( frontmatter . found ) . toBe ( true )
2102+
2103+ // Get content after frontmatter
2104+ const contentAfterFrontmatter = content . slice ( frontmatter . endIndex ) . trimStart ( )
2105+
2106+ // Should start with a markdown header
2107+ expect ( contentAfterFrontmatter . startsWith ( "# " ) ) . toBe ( true )
2108+
2109+ // Extract and verify the header title is meaningful
2110+ const firstLineEnd = contentAfterFrontmatter . indexOf ( "\n" )
2111+ const headerLine =
2112+ firstLineEnd !== - 1
2113+ ? contentAfterFrontmatter . slice ( 0 , firstLineEnd )
2114+ : contentAfterFrontmatter
2115+ expect ( headerLine . length ) . toBeGreaterThan ( 2 ) // More than just "# "
2116+ }
2117+ } )
2118+
2119+ it ( "should validate all shipped agent files contain required keywords" , async ( ) => {
2120+ const { AGENT_NAMES , REQUIRED_KEYWORDS } = await import ( "../src/paths.mjs" )
2121+
2122+ const agentsDir = join ( process . cwd ( ) , "agents" )
2123+
2124+ for ( const agentName of AGENT_NAMES ) {
2125+ const filePath = join ( agentsDir , `${ agentName } .md` )
2126+ const content = readFileSync ( filePath , "utf-8" )
2127+ const lowerContent = content . toLowerCase ( )
2128+
2129+ // Should contain at least one required keyword
2130+ const hasKeyword = REQUIRED_KEYWORDS . some ( ( keyword : string ) =>
2131+ lowerContent . includes ( keyword ) ,
2132+ )
2133+ expect ( hasKeyword ) . toBe ( true )
2134+ }
2135+ } )
2136+
2137+ it ( "should validate all shipped agent files meet minimum content length" , async ( ) => {
2138+ const { AGENT_NAMES , MIN_CONTENT_LENGTH } = await import ( "../src/paths.mjs" )
2139+
2140+ const agentsDir = join ( process . cwd ( ) , "agents" )
2141+
2142+ for ( const agentName of AGENT_NAMES ) {
2143+ const filePath = join ( agentsDir , `${ agentName } .md` )
2144+ const content = readFileSync ( filePath , "utf-8" )
2145+
2146+ // Should be above minimum length
2147+ expect ( content . length ) . toBeGreaterThanOrEqual ( MIN_CONTENT_LENGTH )
2148+ }
2149+ } )
2150+
2151+ it ( "should validate shipped agent count matches AGENT_NAMES" , async ( ) => {
2152+ const { AGENT_NAMES } = await import ( "../src/paths.mjs" )
2153+
2154+ const agentsDir = join ( process . cwd ( ) , "agents" )
2155+ const files = readdirSync ( agentsDir ) . filter ( ( f ) => f . endsWith ( ".md" ) )
2156+
2157+ // Number of .md files should match expected agent names
2158+ expect ( files . length ) . toBe ( AGENT_NAMES . length )
2159+
2160+ // All expected agents should exist as files
2161+ for ( const agentName of AGENT_NAMES ) {
2162+ expect ( files ) . toContain ( `${ agentName } .md` )
2163+ }
2164+ } )
2165+ } )
2166+
20322167 describe ( "full install/uninstall cycle" , ( ) => {
20332168 it ( "should install and then cleanly uninstall" , async ( ) => {
20342169 // Create scripts
0 commit comments