microsoft/hve-core

Public

mirrored fromhttps://github.com/microsoft/hve-coreAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
feat/networking-agent

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

scripts/tests/collections/Validate-Collections.Tests.ps1

1187lines · modecode

1#Requires -Modules Pester
2# Copyright (c) Microsoft Corporation.
3# SPDX-License-Identifier: MIT
4
5BeforeAll {
6 . $PSScriptRoot/../../collections/Validate-Collections.ps1
7}
8
9Describe 'Test-KindSuffix' {
10 It 'Returns empty for valid agent path' {
11 $result = Test-KindSuffix -Kind 'agent' -ItemPath '.github/agents/rpi-agent.agent.md' -RepoRoot $TestDrive
12 $result | Should -BeNullOrEmpty
13 }
14
15 It 'Returns empty for valid prompt path' {
16 $result = Test-KindSuffix -Kind 'prompt' -ItemPath '.github/prompts/gen-plan.prompt.md' -RepoRoot $TestDrive
17 $result | Should -BeNullOrEmpty
18 }
19
20 It 'Returns empty for valid instruction path' {
21 $result = Test-KindSuffix -Kind 'instruction' -ItemPath '.github/instructions/csharp.instructions.md' -RepoRoot $TestDrive
22 $result | Should -BeNullOrEmpty
23 }
24
25 It 'Returns empty for valid skill path with SKILL.md' {
26 $skillDir = Join-Path $TestDrive '.github/skills/video-to-gif'
27 New-Item -ItemType Directory -Path $skillDir -Force | Out-Null
28 Set-Content -Path (Join-Path $skillDir 'SKILL.md') -Value '# Skill'
29
30 $result = Test-KindSuffix -Kind 'skill' -ItemPath '.github/skills/video-to-gif' -RepoRoot $TestDrive
31 $result | Should -BeNullOrEmpty
32 }
33
34 It 'Returns error for invalid agent suffix' {
35 $result = Test-KindSuffix -Kind 'agent' -ItemPath '.github/agents/bad.prompt.md' -RepoRoot $TestDrive
36 $result | Should -Match "kind 'agent' expects"
37 }
38
39 It 'Returns error for invalid prompt suffix' {
40 $result = Test-KindSuffix -Kind 'prompt' -ItemPath '.github/prompts/bad.agent.md' -RepoRoot $TestDrive
41 $result | Should -Match "kind 'prompt' expects"
42 }
43
44 It 'Returns error when SKILL.md missing for skill kind' {
45 $emptySkillDir = Join-Path $TestDrive '.github/skills/no-skill'
46 New-Item -ItemType Directory -Path $emptySkillDir -Force | Out-Null
47
48 $result = Test-KindSuffix -Kind 'skill' -ItemPath '.github/skills/no-skill' -RepoRoot $TestDrive
49 $result | Should -Match "kind 'skill' expects SKILL.md"
50 }
51}
52
53Describe 'Get-CollectionItemKey' {
54 It 'Builds correct composite key' {
55 $result = Get-CollectionItemKey -Kind 'agent' -ItemPath '.github/agents/rpi-agent.agent.md'
56 $result | Should -Be 'agent|.github/agents/rpi-agent.agent.md'
57 }
58
59 It 'Builds key for instruction kind' {
60 $result = Get-CollectionItemKey -Kind 'instruction' -ItemPath '.github/instructions/csharp.instructions.md'
61 $result | Should -Be 'instruction|.github/instructions/csharp.instructions.md'
62 }
63}
64
65Describe 'Invoke-CollectionValidation - repo-specific path rejection' {
66 BeforeAll {
67 Import-Module PowerShell-Yaml -ErrorAction Stop
68
69 $script:repoRoot = Join-Path $TestDrive 'repo'
70 $script:collectionsDir = Join-Path $script:repoRoot 'collections'
71
72 # Create artifact directories and files
73 $instrDir = Join-Path $script:repoRoot '.github/instructions'
74 $agentsDir = Join-Path $script:repoRoot '.github/agents'
75 $sharedDir = Join-Path $instrDir 'shared'
76 $hveCoreAgentsDir = Join-Path $agentsDir 'hve-core'
77
78 New-Item -ItemType Directory -Path $instrDir -Force | Out-Null
79 New-Item -ItemType Directory -Path $agentsDir -Force | Out-Null
80 New-Item -ItemType Directory -Path $sharedDir -Force | Out-Null
81 New-Item -ItemType Directory -Path $hveCoreAgentsDir -Force | Out-Null
82
83 # Root-level (repo-specific) files
84 Set-Content -Path (Join-Path $instrDir 'workflows.instructions.md') -Value '---\ndescription: repo-specific\n---'
85 Set-Content -Path (Join-Path $agentsDir 'internal.agent.md') -Value '---\ndescription: repo-specific agent\n---'
86
87 # Subdirectory (collection-scoped) files
88 Set-Content -Path (Join-Path $sharedDir 'hve-core-location.instructions.md') -Value '---\ndescription: shared\n---'
89 Set-Content -Path (Join-Path $hveCoreAgentsDir 'rpi-agent.agent.md') -Value '---\ndescription: distributable agent\n---'
90 }
91
92 BeforeEach {
93 # Clear collection files between tests to prevent cross-contamination
94 if (Test-Path $script:collectionsDir) {
95 Remove-Item -Path $script:collectionsDir -Recurse -Force
96 }
97 New-Item -ItemType Directory -Path $script:collectionsDir -Force | Out-Null
98 }
99
100 It 'Fails validation for root-level instruction' {
101 $manifest = [ordered]@{
102 id = 'test-reject-instr'
103 name = 'Test Reject Instruction'
104 description = 'Tests repo-specific instruction rejection'
105 items = @(
106 [ordered]@{
107 path = '.github/instructions/workflows.instructions.md'
108 kind = 'instruction'
109 }
110 )
111 }
112 $yaml = ConvertTo-Yaml -Data $manifest
113 Set-Content -Path (Join-Path $script:collectionsDir 'test-reject-instr.collection.yml') -Value $yaml
114
115 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
116 $result.Success | Should -BeFalse
117 $result.ErrorCount | Should -BeGreaterOrEqual 1
118 }
119
120 It 'Passes validation for instruction in subdirectory' {
121 $manifest = [ordered]@{
122 id = 'test-allow-location'
123 name = 'Test Allow Location'
124 description = 'Tests that subdirectory instructions are allowed'
125 items = @(
126 [ordered]@{
127 path = '.github/instructions/shared/hve-core-location.instructions.md'
128 kind = 'instruction'
129 }
130 )
131 }
132 $yaml = ConvertTo-Yaml -Data $manifest
133 Set-Content -Path (Join-Path $script:collectionsDir 'test-allow-location.collection.yml') -Value $yaml
134
135 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
136 $result.Success | Should -BeTrue
137 }
138
139 It 'Fails validation for root-level agent' {
140 $manifest = [ordered]@{
141 id = 'test-reject-agent'
142 name = 'Test Reject Agent'
143 description = 'Tests repo-specific agent rejection'
144 items = @(
145 [ordered]@{
146 path = '.github/agents/internal.agent.md'
147 kind = 'agent'
148 }
149 )
150 }
151 $yaml = ConvertTo-Yaml -Data $manifest
152 Set-Content -Path (Join-Path $script:collectionsDir 'test-reject-agent.collection.yml') -Value $yaml
153
154 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
155 $result.Success | Should -BeFalse
156 $result.ErrorCount | Should -BeGreaterOrEqual 1
157 }
158
159 It 'Passes validation for agent in subdirectory' {
160 $manifest = [ordered]@{
161 id = 'test-allow-agent'
162 name = 'Test Allow Agent'
163 description = 'Tests that subdirectory agents pass'
164 items = @(
165 [ordered]@{
166 path = '.github/agents/hve-core/rpi-agent.agent.md'
167 kind = 'agent'
168 }
169 )
170 }
171 $yaml = ConvertTo-Yaml -Data $manifest
172 Set-Content -Path (Join-Path $script:collectionsDir 'test-allow-agent.collection.yml') -Value $yaml
173
174 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
175 $result.Success | Should -BeTrue
176 }
177}
178
179Describe 'Invoke-CollectionValidation - collection-level maturity' {
180 BeforeAll {
181 Import-Module PowerShell-Yaml -ErrorAction Stop
182
183 $script:repoRoot = Join-Path $TestDrive 'maturity-repo'
184 $script:collectionsDir = Join-Path $script:repoRoot 'collections'
185
186 # Create a valid artifact for items to reference
187 $agentsDir = Join-Path $script:repoRoot '.github/agents/test'
188 New-Item -ItemType Directory -Path $agentsDir -Force | Out-Null
189 Set-Content -Path (Join-Path $agentsDir 'test.agent.md') -Value '---\ndescription: test agent\n---'
190 }
191
192 BeforeEach {
193 if (Test-Path $script:collectionsDir) {
194 Remove-Item -Path $script:collectionsDir -Recurse -Force
195 }
196 New-Item -ItemType Directory -Path $script:collectionsDir -Force | Out-Null
197 }
198
199 It 'Passes validation for collection with maturity: experimental' {
200 $manifest = [ordered]@{
201 id = 'test-maturity-experimental'
202 name = 'Test'
203 description = 'Tests experimental maturity'
204 maturity = 'experimental'
205 items = @(
206 [ordered]@{
207 path = '.github/agents/test/test.agent.md'
208 kind = 'agent'
209 }
210 )
211 }
212 $yaml = ConvertTo-Yaml -Data $manifest
213 Set-Content -Path (Join-Path $script:collectionsDir 'test-maturity-experimental.collection.yml') -Value $yaml
214
215 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
216 $result.Success | Should -BeTrue
217 }
218
219 It 'Passes validation for collection with maturity: stable' {
220 $manifest = [ordered]@{
221 id = 'test-maturity-stable'
222 name = 'Test'
223 description = 'Tests stable maturity'
224 maturity = 'stable'
225 items = @(
226 [ordered]@{
227 path = '.github/agents/test/test.agent.md'
228 kind = 'agent'
229 }
230 )
231 }
232 $yaml = ConvertTo-Yaml -Data $manifest
233 Set-Content -Path (Join-Path $script:collectionsDir 'test-maturity-stable.collection.yml') -Value $yaml
234
235 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
236 $result.Success | Should -BeTrue
237 }
238
239 It 'Passes validation for collection with maturity: preview' {
240 $manifest = [ordered]@{
241 id = 'test-maturity-preview'
242 name = 'Test'
243 description = 'Tests preview maturity'
244 maturity = 'preview'
245 items = @(
246 [ordered]@{
247 path = '.github/agents/test/test.agent.md'
248 kind = 'agent'
249 }
250 )
251 }
252 $yaml = ConvertTo-Yaml -Data $manifest
253 Set-Content -Path (Join-Path $script:collectionsDir 'test-maturity-preview.collection.yml') -Value $yaml
254
255 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
256 $result.Success | Should -BeTrue
257 }
258
259 It 'Passes validation for collection with maturity: deprecated' {
260 $manifest = [ordered]@{
261 id = 'test-maturity-deprecated'
262 name = 'Test'
263 description = 'Tests deprecated maturity'
264 maturity = 'deprecated'
265 items = @(
266 [ordered]@{
267 path = '.github/agents/test/test.agent.md'
268 kind = 'agent'
269 }
270 )
271 }
272 $yaml = ConvertTo-Yaml -Data $manifest
273 Set-Content -Path (Join-Path $script:collectionsDir 'test-maturity-deprecated.collection.yml') -Value $yaml
274
275 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
276 $result.Success | Should -BeTrue
277 }
278
279 It 'Fails validation for collection with invalid maturity: beta' {
280 $manifest = [ordered]@{
281 id = 'test-maturity-beta'
282 name = 'Test'
283 description = 'Tests invalid maturity'
284 maturity = 'beta'
285 items = @(
286 [ordered]@{
287 path = '.github/agents/test/test.agent.md'
288 kind = 'agent'
289 }
290 )
291 }
292 $yaml = ConvertTo-Yaml -Data $manifest
293 Set-Content -Path (Join-Path $script:collectionsDir 'test-maturity-beta.collection.yml') -Value $yaml
294
295 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
296 $result.Success | Should -BeFalse
297 $result.ErrorCount | Should -BeGreaterOrEqual 1
298 }
299
300 It 'Passes validation for collection with omitted maturity' {
301 $manifest = [ordered]@{
302 id = 'test-maturity-omitted'
303 name = 'Test'
304 description = 'Tests omitted maturity'
305 items = @(
306 [ordered]@{
307 path = '.github/agents/test/test.agent.md'
308 kind = 'agent'
309 }
310 )
311 }
312 $yaml = ConvertTo-Yaml -Data $manifest
313 Set-Content -Path (Join-Path $script:collectionsDir 'test-maturity-omitted.collection.yml') -Value $yaml
314
315 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
316 $result.Success | Should -BeTrue
317 }
318}
319
320Describe 'Invoke-CollectionValidation - collection-to-folder name consistency' {
321 BeforeAll {
322 Import-Module PowerShell-Yaml -ErrorAction Stop
323
324 $script:repoRoot = Join-Path $TestDrive 'folder-consistency-repo'
325 $script:collectionsDir = Join-Path $script:repoRoot 'collections'
326
327 # Matching folder structure
328 $matchDir = Join-Path $script:repoRoot '.github/agents/my-collection'
329 New-Item -ItemType Directory -Path $matchDir -Force | Out-Null
330 Set-Content -Path (Join-Path $matchDir 'match.agent.md') -Value '---\ndescription: matching agent\n---'
331
332 # Mismatched folder structure
333 $mismatchDir = Join-Path $script:repoRoot '.github/agents/wrong-folder'
334 New-Item -ItemType Directory -Path $mismatchDir -Force | Out-Null
335 Set-Content -Path (Join-Path $mismatchDir 'mismatch.agent.md') -Value '---\ndescription: mismatched agent\n---'
336
337 # Shared folder structure
338 $sharedDir = Join-Path $script:repoRoot '.github/instructions/shared'
339 New-Item -ItemType Directory -Path $sharedDir -Force | Out-Null
340 Set-Content -Path (Join-Path $sharedDir 'shared.instructions.md') -Value '---\ndescription: shared instruction\n---'
341
342 # hve-core folder structure (cross-collection reference allowed without warning)
343 $hveCoreDir = Join-Path $script:repoRoot '.github/agents/hve-core'
344 New-Item -ItemType Directory -Path $hveCoreDir -Force | Out-Null
345 Set-Content -Path (Join-Path $hveCoreDir 'core.agent.md') -Value '---\ndescription: hve-core agent\n---'
346 }
347
348 BeforeEach {
349 if (Test-Path $script:collectionsDir) {
350 Remove-Item -Path $script:collectionsDir -Recurse -Force
351 }
352 New-Item -ItemType Directory -Path $script:collectionsDir -Force | Out-Null
353 }
354
355 It 'Passes when collection-id matches folder name' {
356 Mock Write-Host {}
357
358 $manifest = [ordered]@{
359 id = 'my-collection'
360 name = 'My Collection'
361 description = 'Collection with matching folder'
362 items = @(
363 [ordered]@{
364 path = '.github/agents/my-collection/match.agent.md'
365 kind = 'agent'
366 }
367 )
368 }
369 $yaml = ConvertTo-Yaml -Data $manifest
370 Set-Content -Path (Join-Path $script:collectionsDir 'my-collection.collection.yml') -Value $yaml
371
372 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
373 $result.Success | Should -BeTrue
374 $result.ErrorCount | Should -Be 0
375 Should -Not -Invoke Write-Host -ParameterFilter {
376 $Object -match 'WARN collection.*my-collection'
377 }
378 }
379
380 It 'Warns but does not fail when collection-id does not match folder name' {
381 Mock Write-Host {}
382
383 $manifest = [ordered]@{
384 id = 'my-collection'
385 name = 'My Collection'
386 description = 'Collection with mismatched folder'
387 items = @(
388 [ordered]@{
389 path = '.github/agents/wrong-folder/mismatch.agent.md'
390 kind = 'agent'
391 }
392 )
393 }
394 $yaml = ConvertTo-Yaml -Data $manifest
395 Set-Content -Path (Join-Path $script:collectionsDir 'my-collection.collection.yml') -Value $yaml
396
397 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
398 $result.Success | Should -BeTrue
399 $result.ErrorCount | Should -Be 0
400 Should -Invoke Write-Host -ParameterFilter {
401 $Object -match 'WARN collection.*wrong-folder'
402 }
403 }
404
405 It 'Allows items from hve-core/ folder in any collection' {
406 Mock Write-Host {}
407
408 $manifest = [ordered]@{
409 id = 'my-collection'
410 name = 'My Collection'
411 description = 'Collection referencing hve-core item'
412 items = @(
413 [ordered]@{
414 path = '.github/agents/hve-core/core.agent.md'
415 kind = 'agent'
416 }
417 )
418 }
419 $yaml = ConvertTo-Yaml -Data $manifest
420 Set-Content -Path (Join-Path $script:collectionsDir 'my-collection.collection.yml') -Value $yaml
421
422 # Register hve-core as a known collection ID (mirrors real-world hve-core.collection.yml)
423 $hveCoreManifest = [ordered]@{
424 id = 'hve-core'
425 name = 'HVE Core'
426 description = 'HVE Core collection'
427 items = @()
428 }
429 $hveYaml = ConvertTo-Yaml -Data $hveCoreManifest
430 Set-Content -Path (Join-Path $script:collectionsDir 'hve-core.collection.yml') -Value $hveYaml
431 Set-Content -Path (Join-Path $script:collectionsDir 'hve-core.collection.md') -Value '# HVE Core'
432
433 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
434 $result.Success | Should -BeTrue
435 $result.ErrorCount | Should -Be 0
436 Should -Not -Invoke Write-Host -ParameterFilter {
437 $Object -match 'WARN collection'
438 }
439 }
440
441 It 'Allows items from shared/ folder in any collection' {
442 Mock Write-Host {}
443
444 $manifest = [ordered]@{
445 id = 'my-collection'
446 name = 'My Collection'
447 description = 'Collection referencing shared item'
448 items = @(
449 [ordered]@{
450 path = '.github/instructions/shared/shared.instructions.md'
451 kind = 'instruction'
452 }
453 )
454 }
455 $yaml = ConvertTo-Yaml -Data $manifest
456 Set-Content -Path (Join-Path $script:collectionsDir 'my-collection.collection.yml') -Value $yaml
457
458 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
459 $result.Success | Should -BeTrue
460 $result.ErrorCount | Should -Be 0
461 Should -Not -Invoke Write-Host -ParameterFilter {
462 $Object -match 'WARN collection'
463 }
464 }
465
466 It 'Allows hve-core-all to reference items from any folder' {
467 Mock Write-Host {}
468
469 $manifest = [ordered]@{
470 id = 'hve-core-all'
471 name = 'HVE Core All'
472 description = 'Aggregate collection'
473 items = @(
474 [ordered]@{
475 path = '.github/agents/my-collection/match.agent.md'
476 kind = 'agent'
477 },
478 [ordered]@{
479 path = '.github/agents/wrong-folder/mismatch.agent.md'
480 kind = 'agent'
481 },
482 [ordered]@{
483 path = '.github/instructions/shared/shared.instructions.md'
484 kind = 'instruction'
485 },
486 [ordered]@{
487 path = '.github/agents/hve-core/core.agent.md'
488 kind = 'agent'
489 }
490 )
491 }
492 $yaml = ConvertTo-Yaml -Data $manifest
493 Set-Content -Path (Join-Path $script:collectionsDir 'hve-core-all.collection.yml') -Value $yaml
494 Set-Content -Path (Join-Path $script:collectionsDir 'hve-core-all.collection.md') -Value '# All'
495
496 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
497 $result.Success | Should -BeTrue
498 $result.ErrorCount | Should -Be 0
499 Should -Not -Invoke Write-Host -ParameterFilter {
500 $Object -match 'WARN collection'
501 }
502 }
503
504 It 'Emits warning output for mismatched folder name without failing' {
505 Mock Write-Host {}
506
507 $manifest = [ordered]@{
508 id = 'my-collection'
509 name = 'My Collection'
510 description = 'Mismatch for warning output test'
511 items = @(
512 [ordered]@{
513 path = '.github/agents/wrong-folder/mismatch.agent.md'
514 kind = 'agent'
515 }
516 )
517 }
518 $yaml = ConvertTo-Yaml -Data $manifest
519 Set-Content -Path (Join-Path $script:collectionsDir 'my-collection.collection.yml') -Value $yaml
520
521 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
522 # Advisory warning uses Write-Host WARN; validation still passes
523 $result.Success | Should -BeTrue
524 $result.ErrorCount | Should -Be 0
525 Should -Invoke Write-Host -ParameterFilter {
526 $Object -match 'WARN collection.*wrong-folder'
527 }
528 }
529}
530
531Describe 'Invoke-CollectionValidation - error paths' {
532 BeforeAll {
533 Import-Module PowerShell-Yaml -ErrorAction Stop
534
535 $script:repoRoot = Join-Path $TestDrive 'error-repo'
536 $script:collectionsDir = Join-Path $script:repoRoot 'collections'
537
538 # Create valid artifacts for reference
539 $agentsDir = Join-Path $script:repoRoot '.github/agents/test'
540 New-Item -ItemType Directory -Path $agentsDir -Force | Out-Null
541 Set-Content -Path (Join-Path $agentsDir 'a.agent.md') -Value '---\ndescription: agent a\n---'
542 Set-Content -Path (Join-Path $agentsDir 'b.agent.md') -Value '---\ndescription: agent b\n---'
543
544 $instrDir = Join-Path $script:repoRoot '.github/instructions/test'
545 New-Item -ItemType Directory -Path $instrDir -Force | Out-Null
546 Set-Content -Path (Join-Path $instrDir 'test.instructions.md') -Value '---\ndescription: test\n---'
547 }
548
549 BeforeEach {
550 if (Test-Path $script:collectionsDir) {
551 Remove-Item -Path $script:collectionsDir -Recurse -Force
552 }
553 New-Item -ItemType Directory -Path $script:collectionsDir -Force | Out-Null
554 }
555
556 It 'Returns success with zero collections when directory is empty' {
557 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
558 $result.Success | Should -BeTrue
559 $result.CollectionCount | Should -Be 0
560 }
561
562 It 'Fails when required field is missing' {
563 $yaml = @"
564name: No ID Collection
565description: Missing id field
566items:
567 - path: .github/agents/test/a.agent.md
568 kind: agent
569"@
570 Set-Content -Path (Join-Path $script:collectionsDir 'no-id.collection.yml') -Value $yaml
571
572 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
573 $result.Success | Should -BeFalse
574 }
575
576 It 'Fails for invalid id format' {
577 $manifest = [ordered]@{
578 id = 'INVALID_ID!'
579 name = 'Bad ID'
580 description = 'Invalid id format'
581 items = @(
582 [ordered]@{
583 path = '.github/agents/test/a.agent.md'
584 kind = 'agent'
585 }
586 )
587 }
588 $yaml = ConvertTo-Yaml -Data $manifest
589 Set-Content -Path (Join-Path $script:collectionsDir 'bad-id.collection.yml') -Value $yaml
590
591 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
592 $result.Success | Should -BeFalse
593 }
594
595 It 'Fails for duplicate ids across collections' {
596 $manifest = [ordered]@{
597 id = 'dup-id'
598 name = 'First'
599 description = 'First collection'
600 items = @(
601 [ordered]@{
602 path = '.github/agents/test/a.agent.md'
603 kind = 'agent'
604 }
605 )
606 }
607 $yaml = ConvertTo-Yaml -Data $manifest
608 Set-Content -Path (Join-Path $script:collectionsDir 'dup1.collection.yml') -Value $yaml
609 Set-Content -Path (Join-Path $script:collectionsDir 'dup2.collection.yml') -Value $yaml
610
611 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
612 $result.Success | Should -BeFalse
613 }
614
615 It 'Fails when item path does not exist' {
616 $manifest = [ordered]@{
617 id = 'missing-path'
618 name = 'Missing'
619 description = 'Item path missing'
620 items = @(
621 [ordered]@{
622 path = '.github/agents/test/nonexistent.agent.md'
623 kind = 'agent'
624 }
625 )
626 }
627 $yaml = ConvertTo-Yaml -Data $manifest
628 Set-Content -Path (Join-Path $script:collectionsDir 'missing-path.collection.yml') -Value $yaml
629
630 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
631 $result.Success | Should -BeFalse
632 }
633
634 It 'Fails when item has no kind' {
635 $yaml = @"
636id: no-kind
637name: No Kind
638description: Item missing kind
639items:
640 - path: .github/agents/test/a.agent.md
641"@
642 Set-Content -Path (Join-Path $script:collectionsDir 'no-kind.collection.yml') -Value $yaml
643
644 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
645 $result.Success | Should -BeFalse
646 }
647
648 It 'Fails for invalid item maturity' {
649 $manifest = [ordered]@{
650 id = 'bad-item-mat'
651 name = 'Bad Item Maturity'
652 description = 'Item with invalid maturity'
653 items = @(
654 [ordered]@{
655 path = '.github/agents/test/a.agent.md'
656 kind = 'agent'
657 maturity = 'alpha'
658 }
659 )
660 }
661 $yaml = ConvertTo-Yaml -Data $manifest
662 Set-Content -Path (Join-Path $script:collectionsDir 'bad-item-mat.collection.yml') -Value $yaml
663
664 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
665 $result.Success | Should -BeFalse
666 }
667
668 It 'Fails for kind-suffix mismatch' {
669 $manifest = [ordered]@{
670 id = 'suffix-mismatch'
671 name = 'Suffix Mismatch'
672 description = 'Agent path with wrong suffix'
673 items = @(
674 [ordered]@{
675 path = '.github/instructions/test/test.instructions.md'
676 kind = 'agent'
677 }
678 )
679 }
680 $yaml = ConvertTo-Yaml -Data $manifest
681 Set-Content -Path (Join-Path $script:collectionsDir 'suffix-mismatch.collection.yml') -Value $yaml
682
683 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
684 $result.Success | Should -BeFalse
685 }
686
687 It 'Fails for instruction kind with wrong suffix' {
688 $manifest = [ordered]@{
689 id = 'instr-suffix'
690 name = 'Instruction Suffix'
691 description = 'Instruction item with agent suffix'
692 items = @(
693 [ordered]@{
694 path = '.github/agents/test/a.agent.md'
695 kind = 'instruction'
696 }
697 )
698 }
699 $yaml = ConvertTo-Yaml -Data $manifest
700 Set-Content -Path (Join-Path $script:collectionsDir 'instr-suffix.collection.yml') -Value $yaml
701
702 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
703 $result.Success | Should -BeFalse
704 }
705
706 It 'Detects duplicate artifact keys at distinct paths' {
707 # Two agents at different paths that resolve to the same artifact key
708 $agentsDir2 = Join-Path $script:repoRoot '.github/agents/other'
709 New-Item -ItemType Directory -Path $agentsDir2 -Force | Out-Null
710 Set-Content -Path (Join-Path $agentsDir2 'a.agent.md') -Value '---\ndescription: same name\n---'
711
712 $manifest = [ordered]@{
713 id = 'dup-artifact'
714 name = 'Dup Artifact'
715 description = 'Same artifact key from different paths'
716 items = @(
717 [ordered]@{
718 path = '.github/agents/test/a.agent.md'
719 kind = 'agent'
720 },
721 [ordered]@{
722 path = '.github/agents/other/a.agent.md'
723 kind = 'agent'
724 }
725 )
726 }
727 $yaml = ConvertTo-Yaml -Data $manifest
728 Set-Content -Path (Join-Path $script:collectionsDir 'dup-artifact.collection.yml') -Value $yaml
729
730 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
731 $result.Success | Should -BeFalse
732 }
733
734 It 'Detects shared item missing canonical entry' {
735 # Two collections share the same item but neither is hve-core-all;
736 # hve-core-all exists but does not include a.agent.md - Check 4 fires.
737 $manifest1 = [ordered]@{
738 id = 'share-one'
739 name = 'Share One'
740 description = 'First sharer'
741 items = @(
742 [ordered]@{
743 path = '.github/agents/test/a.agent.md'
744 kind = 'agent'
745 }
746 )
747 }
748 $manifest2 = [ordered]@{
749 id = 'share-two'
750 name = 'Share Two'
751 description = 'Second sharer'
752 items = @(
753 [ordered]@{
754 path = '.github/agents/test/a.agent.md'
755 kind = 'agent'
756 }
757 )
758 }
759 $canonical = [ordered]@{
760 id = 'hve-core-all'
761 name = 'All'
762 description = 'Canonical - missing a.agent.md'
763 items = @(
764 [ordered]@{
765 path = '.github/agents/test/b.agent.md'
766 kind = 'agent'
767 },
768 [ordered]@{
769 path = '.github/instructions/test/test.instructions.md'
770 kind = 'instruction'
771 }
772 )
773 }
774 $yaml1 = ConvertTo-Yaml -Data $manifest1
775 $yaml2 = ConvertTo-Yaml -Data $manifest2
776 $yaml3 = ConvertTo-Yaml -Data $canonical
777 Set-Content -Path (Join-Path $script:collectionsDir 'share-one.collection.yml') -Value $yaml1
778 Set-Content -Path (Join-Path $script:collectionsDir 'share-two.collection.yml') -Value $yaml2
779 Set-Content -Path (Join-Path $script:collectionsDir 'hve-core-all.collection.yml') -Value $yaml3
780 Set-Content -Path (Join-Path $script:collectionsDir 'hve-core-all.collection.md') -Value '# All'
781
782 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
783 $result.Success | Should -BeFalse
784 }
785
786 It 'Detects maturity conflict with canonical collection' {
787 # hve-core-all has the item as stable, another collection has it as experimental
788 $canonical = [ordered]@{
789 id = 'hve-core-all'
790 name = 'All'
791 description = 'Canonical collection'
792 items = @(
793 [ordered]@{
794 path = '.github/agents/test/a.agent.md'
795 kind = 'agent'
796 maturity = 'stable'
797 }
798 )
799 }
800 $other = [ordered]@{
801 id = 'conflict-col'
802 name = 'Conflict'
803 description = 'Conflicting maturity'
804 items = @(
805 [ordered]@{
806 path = '.github/agents/test/a.agent.md'
807 kind = 'agent'
808 maturity = 'experimental'
809 }
810 )
811 }
812 $yaml1 = ConvertTo-Yaml -Data $canonical
813 $yaml2 = ConvertTo-Yaml -Data $other
814 Set-Content -Path (Join-Path $script:collectionsDir 'hve-core-all.collection.yml') -Value $yaml1
815 Set-Content -Path (Join-Path $script:collectionsDir 'conflict-col.collection.yml') -Value $yaml2
816
817 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
818 $result.Success | Should -BeFalse
819 }
820}
821
822Describe 'Invoke-CollectionValidation - new checks' {
823 BeforeAll {
824 Import-Module PowerShell-Yaml -ErrorAction Stop
825
826 $script:repoRoot = Join-Path $TestDrive 'new-checks-repo'
827 $script:collectionsDir = Join-Path $script:repoRoot 'collections'
828
829 # Standard artifact - used by most tests
830 $agentsDir = Join-Path $script:repoRoot '.github/agents/test'
831 New-Item -ItemType Directory -Path $agentsDir -Force | Out-Null
832 Set-Content -Path (Join-Path $agentsDir 'a.agent.md') -Value '---' -Force
833
834 # Orphan artifact - on disk but not necessarily in manifests
835 $orphanDir = Join-Path $script:repoRoot '.github/agents/orphan'
836 New-Item -ItemType Directory -Path $orphanDir -Force | Out-Null
837 Set-Content -Path (Join-Path $orphanDir 'orphan.agent.md') -Value '---' -Force
838 }
839
840 BeforeEach {
841 if (Test-Path $script:collectionsDir) { Remove-Item -Path $script:collectionsDir -Recurse -Force }
842 New-Item -ItemType Directory -Path $script:collectionsDir -Force | Out-Null
843
844 # Reset agent dirs to pristine state - prevents artifact leakage between tests
845 $agentsBaseDir = Join-Path $script:repoRoot '.github/agents'
846 if (Test-Path $agentsBaseDir) { Remove-Item -Path $agentsBaseDir -Recurse -Force }
847 New-Item -ItemType Directory -Path (Join-Path $agentsBaseDir 'test') -Force | Out-Null
848 Set-Content -Path (Join-Path $agentsBaseDir 'test/a.agent.md') -Value '---' -Force
849 New-Item -ItemType Directory -Path (Join-Path $agentsBaseDir 'orphan') -Force | Out-Null
850 Set-Content -Path (Join-Path $agentsBaseDir 'orphan/orphan.agent.md') -Value '---' -Force
851 }
852
853 # Check 3: companion .collection.md
854
855 It 'Warns but passes when .collection.md companion is missing' {
856 $manifest = [ordered]@{
857 id = 'no-companion'; name = 'No Companion'; description = 'Missing companion md'
858 items = @([ordered]@{ path = '.github/agents/test/a.agent.md'; kind = 'agent' })
859 }
860 Set-Content -Path (Join-Path $script:collectionsDir 'no-companion.collection.yml') -Value (ConvertTo-Yaml -Data $manifest)
861 $canonical = [ordered]@{
862 id = 'hve-core-all'; name = 'All'; description = 'Canonical'
863 items = @(
864 [ordered]@{ path = '.github/agents/test/a.agent.md'; kind = 'agent' },
865 [ordered]@{ path = '.github/agents/orphan/orphan.agent.md'; kind = 'agent' }
866 )
867 }
868 Set-Content -Path (Join-Path $script:collectionsDir 'hve-core-all.collection.yml') -Value (ConvertTo-Yaml -Data $canonical)
869 Set-Content -Path (Join-Path $script:collectionsDir 'hve-core-all.collection.md') -Value '# All'
870
871 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
872 $result.Success | Should -BeTrue
873 $result.ErrorCount | Should -Be 0
874 }
875
876 It 'Passes cleanly when .collection.md companion is present' {
877 $manifest = [ordered]@{
878 id = 'has-companion'; name = 'Has Companion'; description = 'With md'
879 items = @([ordered]@{ path = '.github/agents/test/a.agent.md'; kind = 'agent' })
880 }
881 Set-Content -Path (Join-Path $script:collectionsDir 'has-companion.collection.yml') -Value (ConvertTo-Yaml -Data $manifest)
882 Set-Content -Path (Join-Path $script:collectionsDir 'has-companion.collection.md') -Value '# Has Companion'
883 $canonical = [ordered]@{
884 id = 'hve-core-all'; name = 'All'; description = 'Canonical'
885 items = @(
886 [ordered]@{ path = '.github/agents/test/a.agent.md'; kind = 'agent' },
887 [ordered]@{ path = '.github/agents/orphan/orphan.agent.md'; kind = 'agent' }
888 )
889 }
890 Set-Content -Path (Join-Path $script:collectionsDir 'hve-core-all.collection.yml') -Value (ConvertTo-Yaml -Data $canonical)
891 Set-Content -Path (Join-Path $script:collectionsDir 'hve-core-all.collection.md') -Value '# All'
892
893 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
894 $result.Success | Should -BeTrue
895 }
896
897 # Check 2: intra-collection duplicate
898
899 It 'Fails when the same item appears twice in one collection' {
900 $manifest = [ordered]@{
901 id = 'intra-dup'; name = 'Intra Dup'; description = 'Dup item'
902 items = @(
903 [ordered]@{ path = '.github/agents/test/a.agent.md'; kind = 'agent' },
904 [ordered]@{ path = '.github/agents/test/a.agent.md'; kind = 'agent' }
905 )
906 }
907 Set-Content -Path (Join-Path $script:collectionsDir 'intra-dup.collection.yml') -Value (ConvertTo-Yaml -Data $manifest)
908
909 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
910 $result.Success | Should -BeFalse
911 $result.ErrorCount | Should -BeGreaterOrEqual 1
912 }
913
914 It 'Passes when all items in a collection are distinct' {
915 $agentsDir2 = Join-Path $script:repoRoot '.github/agents/test2'
916 New-Item -ItemType Directory -Path $agentsDir2 -Force | Out-Null
917 Set-Content -Path (Join-Path $agentsDir2 'b.agent.md') -Value '---' -Force
918
919 $manifest = [ordered]@{
920 id = 'distinct-items'; name = 'Distinct'; description = 'Distinct items'
921 items = @(
922 [ordered]@{ path = '.github/agents/test/a.agent.md'; kind = 'agent' },
923 [ordered]@{ path = '.github/agents/test2/b.agent.md'; kind = 'agent' }
924 )
925 }
926 $canonical = [ordered]@{
927 id = 'hve-core-all'; name = 'All'; description = 'Canonical'
928 items = @(
929 [ordered]@{ path = '.github/agents/test/a.agent.md'; kind = 'agent' },
930 [ordered]@{ path = '.github/agents/test2/b.agent.md'; kind = 'agent' },
931 [ordered]@{ path = '.github/agents/orphan/orphan.agent.md'; kind = 'agent' }
932 )
933 }
934 Set-Content -Path (Join-Path $script:collectionsDir 'distinct-items.collection.yml') -Value (ConvertTo-Yaml -Data $manifest)
935 Set-Content -Path (Join-Path $script:collectionsDir 'distinct-items.collection.md') -Value '# Distinct'
936 Set-Content -Path (Join-Path $script:collectionsDir 'hve-core-all.collection.yml') -Value (ConvertTo-Yaml -Data $canonical)
937 Set-Content -Path (Join-Path $script:collectionsDir 'hve-core-all.collection.md') -Value '# All'
938
939 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
940 $result.Success | Should -BeTrue
941 }
942
943 # Check 4: hve-core-all coverage
944
945 It 'Fails when a themed collection item is absent from hve-core-all' {
946 $manifest = [ordered]@{
947 id = 'themed-only'; name = 'Themed Only'; description = 'Item not in hve-core-all'
948 items = @([ordered]@{ path = '.github/agents/test/a.agent.md'; kind = 'agent' })
949 }
950 # Canonical exists but does NOT include a.agent.md - only orphan - so Check 4 fires
951 $canonical = [ordered]@{
952 id = 'hve-core-all'; name = 'All'; description = 'Canonical - missing themed item'
953 items = @([ordered]@{ path = '.github/agents/orphan/orphan.agent.md'; kind = 'agent' })
954 }
955 Set-Content -Path (Join-Path $script:collectionsDir 'themed-only.collection.yml') -Value (ConvertTo-Yaml -Data $manifest)
956 Set-Content -Path (Join-Path $script:collectionsDir 'themed-only.collection.md') -Value '# Themed'
957 Set-Content -Path (Join-Path $script:collectionsDir 'hve-core-all.collection.yml') -Value (ConvertTo-Yaml -Data $canonical)
958 Set-Content -Path (Join-Path $script:collectionsDir 'hve-core-all.collection.md') -Value '# All'
959
960 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
961 $result.Success | Should -BeFalse
962 $result.ErrorCount | Should -BeGreaterOrEqual 1
963 }
964
965 It 'Passes when all themed items are present in hve-core-all' {
966 $themed = [ordered]@{
967 id = 'themed-covered'; name = 'Themed Covered'; description = 'Covered by canonical'
968 items = @([ordered]@{ path = '.github/agents/test/a.agent.md'; kind = 'agent' })
969 }
970 $canonical = [ordered]@{
971 id = 'hve-core-all'; name = 'All'; description = 'Canonical'
972 items = @(
973 [ordered]@{ path = '.github/agents/test/a.agent.md'; kind = 'agent' },
974 [ordered]@{ path = '.github/agents/orphan/orphan.agent.md'; kind = 'agent' }
975 )
976 }
977 Set-Content -Path (Join-Path $script:collectionsDir 'themed-covered.collection.yml') -Value (ConvertTo-Yaml -Data $themed)
978 Set-Content -Path (Join-Path $script:collectionsDir 'themed-covered.collection.md') -Value '# Themed Covered'
979 Set-Content -Path (Join-Path $script:collectionsDir 'hve-core-all.collection.yml') -Value (ConvertTo-Yaml -Data $canonical)
980 Set-Content -Path (Join-Path $script:collectionsDir 'hve-core-all.collection.md') -Value '# All'
981
982 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
983 $result.Success | Should -BeTrue
984 }
985
986 # Check 1: orphan detection
987
988 It 'Fails when an on-disk artifact is absent from hve-core-all' {
989 # manifest and canonical cover a.agent.md but NOT orphan/orphan.agent.md
990 $manifest = [ordered]@{
991 id = 'partial-coverage'; name = 'Partial'; description = 'Missing orphan'
992 items = @([ordered]@{ path = '.github/agents/test/a.agent.md'; kind = 'agent' })
993 }
994 $canonical = [ordered]@{
995 id = 'hve-core-all'; name = 'All'; description = 'Canonical - missing orphan'
996 items = @([ordered]@{ path = '.github/agents/test/a.agent.md'; kind = 'agent' })
997 }
998 Set-Content -Path (Join-Path $script:collectionsDir 'partial-coverage.collection.yml') -Value (ConvertTo-Yaml -Data $manifest)
999 Set-Content -Path (Join-Path $script:collectionsDir 'partial-coverage.collection.md') -Value '# Partial'
1000 Set-Content -Path (Join-Path $script:collectionsDir 'hve-core-all.collection.yml') -Value (ConvertTo-Yaml -Data $canonical)
1001 Set-Content -Path (Join-Path $script:collectionsDir 'hve-core-all.collection.md') -Value '# All'
1002
1003 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
1004 $result.Success | Should -BeFalse
1005 $result.ErrorCount | Should -BeGreaterOrEqual 1
1006 }
1007
1008 It 'Warns but passes when artifact is in hve-core-all but not in any themed collection' {
1009 # Themed covers only a.agent.md; canonical covers both - orphan is canonical-only
1010 $themed = [ordered]@{
1011 id = 'themed-partial'; name = 'Themed Partial'; description = 'Missing orphan in themed'
1012 items = @([ordered]@{ path = '.github/agents/test/a.agent.md'; kind = 'agent' })
1013 }
1014 $canonical = [ordered]@{
1015 id = 'hve-core-all'; name = 'All'; description = 'Canonical - covers orphan'
1016 items = @(
1017 [ordered]@{ path = '.github/agents/test/a.agent.md'; kind = 'agent' },
1018 [ordered]@{ path = '.github/agents/orphan/orphan.agent.md'; kind = 'agent' }
1019 )
1020 }
1021 Set-Content -Path (Join-Path $script:collectionsDir 'themed-partial.collection.yml') -Value (ConvertTo-Yaml -Data $themed)
1022 Set-Content -Path (Join-Path $script:collectionsDir 'themed-partial.collection.md') -Value '# Themed Partial'
1023 Set-Content -Path (Join-Path $script:collectionsDir 'hve-core-all.collection.yml') -Value (ConvertTo-Yaml -Data $canonical)
1024 Set-Content -Path (Join-Path $script:collectionsDir 'hve-core-all.collection.md') -Value '# All'
1025
1026 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
1027 $result.Success | Should -BeTrue
1028 $result.ErrorCount | Should -Be 0
1029 }
1030}
1031
1032Describe 'Invoke-CollectionValidation - marker validation' -Tag 'Unit' {
1033 BeforeAll {
1034 $script:repoRoot = Join-Path $TestDrive 'marker-validation'
1035 $script:collectionsDir = Join-Path $script:repoRoot 'collections'
1036 # Create artifact directories
1037 $agentsDir = Join-Path $script:repoRoot '.github/agents/test'
1038 New-Item -ItemType Directory -Path $agentsDir -Force | Out-Null
1039 Set-Content -Path (Join-Path $agentsDir 'a.agent.md') -Value '---' -Force
1040 $orphanDir = Join-Path $script:repoRoot '.github/agents/orphan'
1041 New-Item -ItemType Directory -Path $orphanDir -Force | Out-Null
1042 Set-Content -Path (Join-Path $orphanDir 'orphan.agent.md') -Value '---' -Force
1043 }
1044
1045 BeforeEach {
1046 if (Test-Path $script:collectionsDir) {
1047 Remove-Item -Path $script:collectionsDir -Recurse -Force
1048 }
1049 New-Item -ItemType Directory -Path $script:collectionsDir -Force | Out-Null
1050 }
1051
1052 It 'Passes when collection.md has valid matched marker pairs' {
1053 $manifest = [ordered]@{
1054 id = 'valid-markers'; name = 'Valid Markers'; description = 'Matched markers'
1055 items = @([ordered]@{ path = '.github/agents/test/a.agent.md'; kind = 'agent' })
1056 }
1057 Set-Content -Path (Join-Path $script:collectionsDir 'valid-markers.collection.yml') -Value (ConvertTo-Yaml -Data $manifest)
1058 $mdContent = @"
1059# Valid Markers
1060
1061<!-- BEGIN AUTO-GENERATED ARTIFACTS -->
1062Generated content.
1063<!-- END AUTO-GENERATED ARTIFACTS -->
1064"@
1065 Set-Content -Path (Join-Path $script:collectionsDir 'valid-markers.collection.md') -Value $mdContent
1066 $canonical = [ordered]@{
1067 id = 'hve-core-all'; name = 'All'; description = 'Canonical'
1068 items = @(
1069 [ordered]@{ path = '.github/agents/test/a.agent.md'; kind = 'agent' },
1070 [ordered]@{ path = '.github/agents/orphan/orphan.agent.md'; kind = 'agent' }
1071 )
1072 }
1073 Set-Content -Path (Join-Path $script:collectionsDir 'hve-core-all.collection.yml') -Value (ConvertTo-Yaml -Data $canonical)
1074 Set-Content -Path (Join-Path $script:collectionsDir 'hve-core-all.collection.md') -Value '# All'
1075
1076 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
1077 $result.Success | Should -BeTrue
1078 $result.ErrorCount | Should -Be 0
1079 }
1080
1081 It 'Warns but passes when begin marker exists without end marker' {
1082 $manifest = [ordered]@{
1083 id = 'begin-only'; name = 'Begin Only'; description = 'Missing end'
1084 items = @([ordered]@{ path = '.github/agents/test/a.agent.md'; kind = 'agent' })
1085 }
1086 Set-Content -Path (Join-Path $script:collectionsDir 'begin-only.collection.yml') -Value (ConvertTo-Yaml -Data $manifest)
1087 $mdContent = @"
1088# Begin Only
1089
1090<!-- BEGIN AUTO-GENERATED ARTIFACTS -->
1091Content without end marker.
1092"@
1093 Set-Content -Path (Join-Path $script:collectionsDir 'begin-only.collection.md') -Value $mdContent
1094 $canonical = [ordered]@{
1095 id = 'hve-core-all'; name = 'All'; description = 'Canonical'
1096 items = @(
1097 [ordered]@{ path = '.github/agents/test/a.agent.md'; kind = 'agent' },
1098 [ordered]@{ path = '.github/agents/orphan/orphan.agent.md'; kind = 'agent' }
1099 )
1100 }
1101 Set-Content -Path (Join-Path $script:collectionsDir 'hve-core-all.collection.yml') -Value (ConvertTo-Yaml -Data $canonical)
1102 Set-Content -Path (Join-Path $script:collectionsDir 'hve-core-all.collection.md') -Value '# All'
1103
1104 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
1105 $result.Success | Should -BeTrue
1106 $result.ErrorCount | Should -Be 0
1107 }
1108
1109 It 'Warns but passes when end marker exists without begin marker' {
1110 $manifest = [ordered]@{
1111 id = 'end-only'; name = 'End Only'; description = 'Missing begin'
1112 items = @([ordered]@{ path = '.github/agents/test/a.agent.md'; kind = 'agent' })
1113 }
1114 Set-Content -Path (Join-Path $script:collectionsDir 'end-only.collection.yml') -Value (ConvertTo-Yaml -Data $manifest)
1115 $mdContent = @"
1116# End Only
1117
1118Content without begin marker.
1119<!-- END AUTO-GENERATED ARTIFACTS -->
1120"@
1121 Set-Content -Path (Join-Path $script:collectionsDir 'end-only.collection.md') -Value $mdContent
1122 $canonical = [ordered]@{
1123 id = 'hve-core-all'; name = 'All'; description = 'Canonical'
1124 items = @(
1125 [ordered]@{ path = '.github/agents/test/a.agent.md'; kind = 'agent' },
1126 [ordered]@{ path = '.github/agents/orphan/orphan.agent.md'; kind = 'agent' }
1127 )
1128 }
1129 Set-Content -Path (Join-Path $script:collectionsDir 'hve-core-all.collection.yml') -Value (ConvertTo-Yaml -Data $canonical)
1130 Set-Content -Path (Join-Path $script:collectionsDir 'hve-core-all.collection.md') -Value '# All'
1131
1132 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
1133 $result.Success | Should -BeTrue
1134 $result.ErrorCount | Should -Be 0
1135 }
1136
1137 It 'Does not warn when collection.md has no markers (backward compat)' {
1138 $manifest = [ordered]@{
1139 id = 'no-markers'; name = 'No Markers'; description = 'Legacy no markers'
1140 items = @([ordered]@{ path = '.github/agents/test/a.agent.md'; kind = 'agent' })
1141 }
1142 Set-Content -Path (Join-Path $script:collectionsDir 'no-markers.collection.yml') -Value (ConvertTo-Yaml -Data $manifest)
1143 Set-Content -Path (Join-Path $script:collectionsDir 'no-markers.collection.md') -Value '# No Markers - legacy content without any markers'
1144 $canonical = [ordered]@{
1145 id = 'hve-core-all'; name = 'All'; description = 'Canonical'
1146 items = @(
1147 [ordered]@{ path = '.github/agents/test/a.agent.md'; kind = 'agent' },
1148 [ordered]@{ path = '.github/agents/orphan/orphan.agent.md'; kind = 'agent' }
1149 )
1150 }
1151 Set-Content -Path (Join-Path $script:collectionsDir 'hve-core-all.collection.yml') -Value (ConvertTo-Yaml -Data $canonical)
1152 Set-Content -Path (Join-Path $script:collectionsDir 'hve-core-all.collection.md') -Value '# All'
1153
1154 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
1155 $result.Success | Should -BeTrue
1156 $result.ErrorCount | Should -Be 0
1157 }
1158
1159 It 'Warns but passes when markers appear in wrong order' {
1160 $manifest = [ordered]@{
1161 id = 'reversed'; name = 'Reversed'; description = 'Wrong order'
1162 items = @([ordered]@{ path = '.github/agents/test/a.agent.md'; kind = 'agent' })
1163 }
1164 Set-Content -Path (Join-Path $script:collectionsDir 'reversed.collection.yml') -Value (ConvertTo-Yaml -Data $manifest)
1165 $mdContent = @"
1166# Reversed
1167
1168<!-- END AUTO-GENERATED ARTIFACTS -->
1169Content.
1170<!-- BEGIN AUTO-GENERATED ARTIFACTS -->
1171"@
1172 Set-Content -Path (Join-Path $script:collectionsDir 'reversed.collection.md') -Value $mdContent
1173 $canonical = [ordered]@{
1174 id = 'hve-core-all'; name = 'All'; description = 'Canonical'
1175 items = @(
1176 [ordered]@{ path = '.github/agents/test/a.agent.md'; kind = 'agent' },
1177 [ordered]@{ path = '.github/agents/orphan/orphan.agent.md'; kind = 'agent' }
1178 )
1179 }
1180 Set-Content -Path (Join-Path $script:collectionsDir 'hve-core-all.collection.yml') -Value (ConvertTo-Yaml -Data $canonical)
1181 Set-Content -Path (Join-Path $script:collectionsDir 'hve-core-all.collection.md') -Value '# All'
1182
1183 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
1184 $result.Success | Should -BeTrue
1185 $result.ErrorCount | Should -Be 0
1186 }
1187}