Files
magnus919_agent-skills/scripts/test-gen-llms-txt.rb
T
Magnus Hedemarkandfactory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> ed3d09fabd fix(test): correct duplicate-catalog name fixtures for case-sensitive fs
test_rejects_duplicate_catalog_names failed on case-sensitive CI
filesystems (Ubuntu ext4): the flat-layout fixture wrote both duplicate/
and Duplicate/ with frontmatter name: duplicate, so Duplicate/SKILL.md
tripped the name-match guard ('name must match directory name
"Duplicate"') before the duplicate-catalog check ran, hiding the
expected 'duplicate catalog name "duplicate"' error. Local macOS APFS
collapsed the two dirs and passed, concealing the defect.

Detect filesystem case-sensitivity with a Probe/probe probe. On
case-sensitive filesystems write duplicate/ (name: duplicate) and
Duplicate/ (name: Duplicate) so both pass the name-match guard and the
downcased-name collision fires the duplicate-catalog error. On
case-insensitive filesystems the dirs collapse into one, so write the
single dir with name: duplicate and assert the generator succeeds.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
2026-08-14 17:44:11 -04:00

199 lines
5.8 KiB
Ruby

#!/usr/bin/env ruby
# frozen_string_literal: true
require "fileutils"
require "minitest/autorun"
require "open3"
require "rbconfig"
require "tmpdir"
class GenLlmsTxtTest < Minitest::Test
GENERATOR = File.expand_path("gen-llms-txt.rb", __dir__)
def test_generation_and_drift_detection
Dir.mktmpdir("gen-llms-txt") do |root|
install_generator(root)
write_skill(root, "omega", <<~YAML)
---
name: omega
description: |-
First line
second line
---
YAML
write_skill(root, "zulu", <<~YAML)
---
name: zulu
description: Zulu bundle.
---
YAML
write_skill(root, "zulu/skills/helper", <<~YAML)
---
name: helper
description: Nested helper that must be excluded.
---
YAML
stdout, stderr, status = run_generator(root)
refute status.success?
assert_empty stdout
assert_includes stderr, "llms.txt is missing"
assert_includes stderr, "ruby scripts/gen-llms-txt.rb --write"
stdout, stderr, status = run_generator(root, "--write")
assert status.success?, stderr
assert_includes stdout, "Wrote llms.txt (2 skills)."
assert_empty stderr
expected = <<~TEXT
# agent-skills
## Skills
- [omega](omega/SKILL.md): First line second line
- [zulu](zulu/SKILL.md): Zulu bundle.
TEXT
llms_path = File.join(root, "llms.txt")
assert_equal expected, File.binread(llms_path)
refute_includes File.binread(llms_path), "helper"
stdout, stderr, status = run_generator(root)
assert status.success?, stderr
assert_includes stdout, "llms.txt is current (2 skills)."
assert_empty stderr
File.open(llms_path, "a") { |file| file.write("drift\n") }
stdout, stderr, status = run_generator(root)
refute status.success?
assert_empty stdout
assert_includes stderr, "llms.txt is out of date"
assert_includes stderr, "ruby scripts/gen-llms-txt.rb --write"
_stdout, stderr, status = run_generator(root, "--write")
assert status.success?, stderr
assert_equal expected, File.binread(llms_path)
end
end
def test_rejects_missing_description
Dir.mktmpdir("gen-llms-txt") do |root|
install_generator(root)
write_skill(root, "invalid", <<~YAML)
---
name: invalid
---
YAML
_stdout, stderr, status = run_generator(root, "--write")
refute status.success?
assert_includes stderr, "invalid/SKILL.md: description must be a nonempty string"
end
end
def test_rejects_missing_name
Dir.mktmpdir("gen-llms-txt") do |root|
install_generator(root)
write_skill(root, "invalid", <<~YAML)
---
description: Missing name fixture.
---
YAML
_stdout, stderr, status = run_generator(root, "--write")
refute status.success?
assert_includes stderr, "invalid/SKILL.md: name must match directory name \"invalid\""
end
end
def test_rejects_mismatched_name
Dir.mktmpdir("gen-llms-txt") do |root|
install_generator(root)
write_skill(root, "expected-name", <<~YAML)
---
name: wrong-name
description: Mismatched name fixture.
---
YAML
_stdout, stderr, status = run_generator(root, "--write")
refute status.success?
assert_includes stderr, "expected-name/SKILL.md: name must match directory name \"expected-name\""
end
end
def test_rejects_duplicate_catalog_names
Dir.mktmpdir("gen-llms-txt") do |root|
install_generator(root)
if case_sensitive_fs?(root)
# Case-sensitive filesystem: "duplicate" and "Duplicate" are distinct
# directories, so the generator processes both. Each fixture must pass
# the name-match guard first (name matches its own directory name), then
# the downcased catalog names collide and the duplicate-catalog guard
# (not the name-match guard) fires.
write_skill(root, "duplicate", <<~YAML)
---
name: duplicate
description: Duplicate fixture.
---
YAML
write_skill(root, "Duplicate", <<~YAML)
---
name: Duplicate
description: Duplicate fixture.
---
YAML
_stdout, stderr, status = run_generator(root, "--write")
refute status.success?
assert_includes stderr, "duplicate catalog name \"duplicate\""
else
# Case-insensitive filesystem (e.g. macOS APFS): the two names collapse
# into one directory, so the generator emits a single entry and must not
# report a spurious duplicate.
write_skill(root, "duplicate", <<~YAML)
---
name: duplicate
description: Duplicate fixture.
---
YAML
stdout, stderr, status = run_generator(root, "--write")
assert status.success?, stderr
assert_includes stdout, "Wrote llms.txt (1 skills)."
end
end
end
private
# True when the filesystem under `root` distinguishes case in directory names.
# macOS APFS (case-insensitive) collapses "Probe"/"probe"; Ubuntu ext4
# (case-sensitive) keeps them distinct.
def case_sensitive_fs?(root)
FileUtils.mkdir_p(File.join(root, "Probe"))
!Dir.exist?(File.join(root, "probe"))
end
def install_generator(root)
FileUtils.mkdir_p(File.join(root, "scripts"))
FileUtils.cp(GENERATOR, File.join(root, "scripts", "gen-llms-txt.rb"))
end
def run_generator(root, *args)
Open3.capture3(
RbConfig.ruby,
"scripts/gen-llms-txt.rb",
*args,
chdir: root
)
end
def write_skill(root, relative_dir, content)
directory = File.join(root, relative_dir)
FileUtils.mkdir_p(directory)
File.write(File.join(directory, "SKILL.md"), content)
end
end