@@ -15,6 +15,7 @@ import {
1515 getAgentsSourceDir ,
1616 getErrorMessage ,
1717 getPackageRoot ,
18+ validateAgentContent ,
1819} from "./src/paths.mjs"
1920
2021const packageRoot = getPackageRoot ( import . meta. url )
@@ -36,130 +37,15 @@ function verbose(message) {
3637 }
3738}
3839
39- /** Minimum character count for valid agent files */
40- const MIN_CONTENT_LENGTH = 100
41-
42- /** Keywords that should appear in valid agent files (case-insensitive) */
43- const REQUIRED_KEYWORDS = [ "agent" , "task" ]
44-
45- /** Required fields in YAML frontmatter */
46- const REQUIRED_FRONTMATTER_FIELDS = [ "version" , "requires" ]
47-
48- /**
49- * Parses YAML frontmatter from markdown content.
50- *
51- * Expects frontmatter to be delimited by --- at the start of the file.
52- *
53- * @param {string } content - The file content to parse
54- * @returns {{ found: boolean, fields: Record<string, string>, endIndex: number } } Parse result
55- */
56- function parseFrontmatter ( content ) {
57- // Frontmatter must start at the beginning of the file
58- if ( ! content . startsWith ( "---" ) ) {
59- return { found : false , fields : { } , endIndex : 0 }
60- }
61-
62- // Find the closing ---
63- const endMatch = content . indexOf ( "\n---" , 3 )
64- if ( endMatch === - 1 ) {
65- return { found : false , fields : { } , endIndex : 0 }
66- }
67-
68- // Extract frontmatter content (between the --- delimiters)
69- const frontmatterContent = content . slice ( 4 , endMatch )
70- const fields = { }
71-
72- // Parse simple key: value pairs
73- for ( const line of frontmatterContent . split ( "\n" ) ) {
74- const trimmed = line . trim ( )
75- if ( ! trimmed || trimmed . startsWith ( "#" ) ) continue
76-
77- const colonIndex = trimmed . indexOf ( ":" )
78- if ( colonIndex === - 1 ) continue
79-
80- const key = trimmed . slice ( 0 , colonIndex ) . trim ( )
81- let value = trimmed . slice ( colonIndex + 1 ) . trim ( )
82-
83- // Remove surrounding quotes if present
84- if (
85- ( value . startsWith ( '"' ) && value . endsWith ( '"' ) ) ||
86- ( value . startsWith ( "'" ) && value . endsWith ( "'" ) )
87- ) {
88- value = value . slice ( 1 , - 1 )
89- }
90-
91- fields [ key ] = value
92- }
93-
94- // endIndex points to the character after the closing ---\n
95- const endIndex = endMatch + 4
96-
97- return { found : true , fields, endIndex }
98- }
99-
10040/**
101- * Validates that an agent file has valid content structure.
102- *
103- * Checks that the file:
104- * 1. Has YAML frontmatter with required fields (version, requires)
105- * 2. Starts with a markdown header (# ) after frontmatter
106- * 3. Contains at least MIN_CONTENT_LENGTH characters
107- * 4. Contains at least one of the expected keywords
41+ * Validates an agent file by reading and validating its content.
10842 *
10943 * @param {string } filePath - Path to the agent file to validate
11044 * @returns {{ valid: boolean, error?: string } } Validation result with optional error message
11145 */
112- function validateAgentContent ( filePath ) {
46+ function validateAgentFile ( filePath ) {
11347 const content = readFileSync ( filePath , "utf-8" )
114-
115- // Check minimum length
116- if ( content . length < MIN_CONTENT_LENGTH ) {
117- return {
118- valid : false ,
119- error : `File too short: ${ content . length } characters (minimum ${ MIN_CONTENT_LENGTH } )` ,
120- }
121- }
122-
123- // Check for YAML frontmatter
124- const frontmatter = parseFrontmatter ( content )
125- if ( ! frontmatter . found ) {
126- return {
127- valid : false ,
128- error : "File missing YAML frontmatter (must start with ---)" ,
129- }
130- }
131-
132- // Check for required frontmatter fields
133- const missingFields = REQUIRED_FRONTMATTER_FIELDS . filter ( ( field ) => ! frontmatter . fields [ field ] )
134- if ( missingFields . length > 0 ) {
135- return {
136- valid : false ,
137- error : `Frontmatter missing required fields: ${ missingFields . join ( ", " ) } ` ,
138- }
139- }
140-
141- // Get content after frontmatter
142- const contentAfterFrontmatter = content . slice ( frontmatter . endIndex ) . trimStart ( )
143-
144- // Check for markdown header after frontmatter
145- if ( ! contentAfterFrontmatter . startsWith ( "# " ) ) {
146- return {
147- valid : false ,
148- error : "File does not have a markdown header (# ) after frontmatter" ,
149- }
150- }
151-
152- // Check for required keywords (case-insensitive)
153- const lowerContent = content . toLowerCase ( )
154- const hasKeyword = REQUIRED_KEYWORDS . some ( ( keyword ) => lowerContent . includes ( keyword ) )
155- if ( ! hasKeyword ) {
156- return {
157- valid : false ,
158- error : `File missing required keywords: ${ REQUIRED_KEYWORDS . join ( ", " ) } ` ,
159- }
160- }
161-
162- return { valid : true }
48+ return validateAgentContent ( content )
16349}
16450
16551/**
@@ -236,7 +122,7 @@ function main() {
236122 if ( DRY_RUN ) {
237123 // In dry-run mode, validate source file but don't copy
238124 verbose ( ` Validating source file (dry-run mode)...` )
239- const validation = validateAgentContent ( sourcePath )
125+ const validation = validateAgentFile ( sourcePath )
240126 if ( ! validation . valid ) {
241127 throw new Error ( `Invalid agent file content: ${ validation . error } ` )
242128 }
@@ -262,7 +148,7 @@ function main() {
262148
263149 // Validate content structure
264150 verbose ( ` Validating content structure...` )
265- const validation = validateAgentContent ( targetPath )
151+ const validation = validateAgentFile ( targetPath )
266152 if ( ! validation . valid ) {
267153 throw new Error ( `Invalid agent file content: ${ validation . error } ` )
268154 }
0 commit comments