diff --git a/agentic.gemspec b/agentic.gemspec index cbda719..026659e 100644 --- a/agentic.gemspec +++ b/agentic.gemspec @@ -19,13 +19,14 @@ Gem::Specification.new do |spec| spec.metadata["changelog_uri"] = "https://github.com/codenamev/agentic/tree/main/CHANGELOG.md" spec.metadata["allowed_push_host"] = "https://rubygems.org" - # Specify which files should be added to the gem when it is released. - # The `git ls-files -z` loads the files in the RubyGem that have been added into git. - gemspec = File.basename(__FILE__) + # Ship only what the runtime needs. `git ls-files` keeps this honest about + # what is tracked; the allowlist keeps session transcripts, local settings, + # docs, examples and planning artefacts out of the package. + # spec/gem_contents_spec.rb asserts the built file list stays inside it. spec.files = IO.popen(%w[git ls-files -z], chdir: __dir__, err: IO::NULL) do |ls| - ls.readlines("\x0", chomp: true).reject do |f| - (f == gemspec) || - f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile]) + ls.readlines("\x0", chomp: true).select do |f| + f.start_with?("lib/", "exe/", "sig/") || + %w[LICENSE.txt README.md CHANGELOG.md].include?(f) end end spec.bindir = "exe" diff --git a/spec/gem_contents_spec.rb b/spec/gem_contents_spec.rb new file mode 100644 index 0000000..23012ef --- /dev/null +++ b/spec/gem_contents_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require "rubygems" + +# Pack-contents check: the gem must only ship runtime files. The published +# 0.2.0 carried .claude/settings.local.json and session transcripts because +# the gemspec used a denylist over `git ls-files`. This pins the allowlist so +# that regression fails CI instead of landing on rubygems. +RSpec.describe "agentic.gemspec contents" do + let(:allowed_prefixes) { %w[lib/ exe/ sig/] } + let(:allowed_files) { %w[LICENSE.txt README.md CHANGELOG.md] } + + let(:gemspec) { Gem::Specification.load(File.expand_path("../agentic.gemspec", __dir__)) } + + it "ships only runtime files" do + stray = gemspec.files.reject do |f| + allowed_prefixes.any? { |prefix| f.start_with?(prefix) } || allowed_files.include?(f) + end + expect(stray).to be_empty, "gem would ship non-runtime files:\n #{stray.join("\n ")}" + end + + it "ships the library and the executable" do + expect(gemspec.files).to include("lib/agentic.rb", "lib/agentic/version.rb") + expect(gemspec.executables).to include("agentic") + end + + it "never ships session transcripts, local settings or planning docs" do + expect(gemspec.files.grep(%r{\A(\.claude|\.claude-sessions|\.architecture|docs|examples|reports|plugins|benchmark)/})).to be_empty + expect(gemspec.files.grep(/\.local\.json\z|\.jsonl\z/)).to be_empty + expect(gemspec.files.grep(/\.md\z/).reject { |f| f.start_with?("lib/") } - allowed_files).to be_empty + end +end