context_spook 0.0.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 46a28bdfcceb8928f53b232ce6d2e69d64632ff171eb6493edfa8c4471ffff43
4
- data.tar.gz: 0ea5102521b157be1198bf9e440e28530f023565a4d2ae3c5988ddd685f00912
3
+ metadata.gz: 4088e8a4f8de00062dcea1b56b696dd1190731c0c30944a95c60a14cad995fa9
4
+ data.tar.gz: 996fb30d8a2542a387451416ece827dd1b8062344317ef798db803c216d39e17
5
5
  SHA512:
6
- metadata.gz: 8d351e068d6a174857d3dd9d5d413e71a4cb6d84fe271595076bd72870e2e10e539e90475556f1d0381048e092360061ee81d8585ce60935431d8c049af0d24a
7
- data.tar.gz: f3a2f224075862ed04c87bd5ca695a410510b42a4974fd061edeb99025712de4fff2691f7401da1e6e1f977041f9fb9b5a6ad25b641e441746fff367206c6f09
6
+ metadata.gz: df6ce1080b5b98a1e279f945cb0e5934592ea586c15abc24234b1c44aaa0d5aac55308227c2966a1e2e9912f620f4d13a5bda0940726028f589aa0ee522dc3fd
7
+ data.tar.gz: 2b787ae025a227d47f7d6ab53d850bc0c2084a6872e16bb2b60d122965d3dcdb17531fc4118371de82da0edcdd29b7a4ebe143b55019d3116bb424117edb85f4
data/README.md CHANGED
@@ -32,8 +32,46 @@ $ gem install context_spook
32
32
 
33
33
  ## Usage
34
34
 
35
- Create a `contexts/project.rb` file that describes your project context using
36
- the DSL in a context definition file:
35
+ ### Programmatic Usage
36
+
37
+ #### Directly in Ruby
38
+
39
+ Now you can generate context from a block directly in Ruby using the DSL:
40
+
41
+ ```ruby
42
+ context = ContextSpook::generate_context do
43
+ context do
44
+ variable branch: `git rev-parse --abbrev-ref HEAD`.chomp
45
+
46
+ namespace "structure" do
47
+ command "tree", tags: %w[ project_structure ]
48
+ end
49
+
50
+ namespace "lib" do
51
+ Dir['lib/**/*.rb'].each do |filename|
52
+ file filename, tags: 'lib'
53
+ end
54
+ end
55
+
56
+ # ... rest of your context definition, see below for full example
57
+ end
58
+ end
59
+ ```
60
+
61
+ This approach can be used to dynamically generate a context when it is not
62
+ configurable via a user context definition file, or as a fallback when users
63
+ have not yet created such files.
64
+
65
+ Afterwards you can store the context as JSON in Ruby or send it to another
66
+ application.
67
+
68
+ ```ruby
69
+ File.write 'context.json', context.to_json
70
+ ```
71
+
72
+ #### From a context definition file
73
+
74
+ Alternatively store the block's content above to a file `contexts/project.rb`:
37
75
 
38
76
  ```ruby
39
77
  # contexts/project.rb
@@ -41,7 +79,7 @@ context do
41
79
  variable branch: `git rev-parse --abbrev-ref HEAD`.chomp
42
80
 
43
81
  namespace "structure" do
44
- command "tree lib", tags: %w[ project_structure ]
82
+ command "tree", tags: %w[ project_structure ]
45
83
  end
46
84
 
47
85
  namespace "lib" do
@@ -68,13 +106,12 @@ context do
68
106
 
69
107
  meta ruby: RUBY_DESCRIPTION
70
108
 
71
- meta code_coverage: JSON.load_file('coverage/coverage_context.json') rescue nil
109
+ meta code_coverage: json('coverage/coverage_context.json')
72
110
  end
73
111
  ```
74
112
 
75
- ### Programmatic Usage
76
-
77
- Now you can generate the context from the file, and store it as JSON in Ruby.
113
+ Now you can generate the context from the file, and store it as JSON in Ruby or
114
+ send it to another application.
78
115
 
79
116
  ```ruby
80
117
  context = ContextSpook::generate_context('contexts/project.rb')
@@ -129,11 +166,13 @@ assistants understand:
129
166
  "content": "...",
130
167
  "size": 1234,
131
168
  "lines": 56,
132
- "tags": ["lib"]
169
+ "tags": [
170
+ "lib"
171
+ ]
133
172
  }
134
173
  },
135
174
  "commands": {
136
- "tree lib": {
175
+ "tree": {
137
176
  "namespace": "structure",
138
177
  "output": "lib\n├── context_spook\n│ └── generator.rb\n└── context_spook.rb\n\n2 directories, 3 files",
139
178
  "exit_code": 0,
@@ -142,7 +181,7 @@ assistants understand:
142
181
  },
143
182
  "metadata": {
144
183
  "ruby": "ruby 3.1.0 ...",
145
- "code_coverage": { ... }
184
+ "code_coverage": {}
146
185
  },
147
186
  "variables": {
148
187
  "branch": "main"
data/Rakefile CHANGED
@@ -29,6 +29,7 @@ GemHadar do
29
29
  dependency 'tins', '~>1.39'
30
30
  dependency 'json', '~>2.0'
31
31
  dependency 'term-ansicolor', '~> 1.11'
32
+ dependency 'mize', '~> 0.6'
32
33
  development_dependency 'all_images', '~> 0.6'
33
34
  development_dependency 'rspec', '~> 3.2'
34
35
  development_dependency 'debug'
data/bin/context_spook CHANGED
@@ -4,8 +4,4 @@ require 'context_spook'
4
4
 
5
5
  filename = ARGV.shift or fail 'require context definition file as first argument'
6
6
  context_json = ContextSpook.generate_context(filename).to_json
7
- context_json_size = Tins::Unit.format(
8
- context_json.size, format: '%.2f %U', unit: ?b, prefix: 1024
9
- )
10
- STDERR.puts "Now outputting #{context_json_size} of JSON context in total."
11
7
  puts context_json
@@ -1,9 +1,9 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: context_spook 0.0.1 ruby lib
2
+ # stub: context_spook 0.2.0 ruby lib
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "context_spook".freeze
6
- s.version = "0.0.1".freeze
6
+ s.version = "0.2.0".freeze
7
7
 
8
8
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
9
9
  s.require_paths = ["lib".freeze]
@@ -13,7 +13,7 @@ Gem::Specification.new do |s|
13
13
  s.email = "flori@ping.de".freeze
14
14
  s.executables = ["context_spook".freeze]
15
15
  s.extra_rdoc_files = ["README.md".freeze, "lib/context_spook.rb".freeze, "lib/context_spook/generator.rb".freeze, "lib/context_spook/version.rb".freeze]
16
- s.files = ["Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "bin/context_spook".freeze, "context_spook.gemspec".freeze, "contexts/project.rb".freeze, "lib/context_spook.rb".freeze, "lib/context_spook/generator.rb".freeze, "lib/context_spook/version.rb".freeze, "spec/context_spook/generator_spec.rb".freeze, "spec/spec_helper.rb".freeze]
16
+ s.files = ["Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "bin/context_spook".freeze, "context_spook.gemspec".freeze, "contexts/project.rb".freeze, "hello_world.json".freeze, "lib/context_spook.rb".freeze, "lib/context_spook/generator.rb".freeze, "lib/context_spook/version.rb".freeze, "spec/context_spook/generator_spec.rb".freeze, "spec/spec_helper.rb".freeze]
17
17
  s.homepage = "https://github.com/flori/context_spook".freeze
18
18
  s.licenses = ["MIT".freeze]
19
19
  s.rdoc_options = ["--title".freeze, "ContextSpook - context_spook collects project context for AI".freeze, "--main".freeze, "README.md".freeze]
@@ -32,4 +32,5 @@ Gem::Specification.new do |s|
32
32
  s.add_runtime_dependency(%q<tins>.freeze, ["~> 1.39".freeze])
33
33
  s.add_runtime_dependency(%q<json>.freeze, ["~> 2.0".freeze])
34
34
  s.add_runtime_dependency(%q<term-ansicolor>.freeze, ["~> 1.11".freeze])
35
+ s.add_runtime_dependency(%q<mize>.freeze, ["~> 0.6".freeze])
35
36
  end
data/contexts/project.rb CHANGED
@@ -2,7 +2,7 @@ context do
2
2
  variable branch: `git rev-parse --abbrev-ref HEAD`.chomp
3
3
 
4
4
  namespace "structure" do
5
- command "tree lib", tags: %w[ project_structure ]
5
+ command "tree", tags: %w[ project_structure ]
6
6
  end
7
7
 
8
8
  namespace "lib" do
@@ -35,5 +35,9 @@ context do
35
35
 
36
36
  meta ruby: RUBY_DESCRIPTION
37
37
 
38
- meta code_coverage: JSON.load_file('coverage/coverage_context.json') rescue nil
38
+ meta hello_world: json('hello_world.json')
39
+
40
+ meta nixda_json: json('nixda_json.json')
41
+
42
+ meta code_coverage: json('coverage/coverage_context.json')
39
43
  end
data/hello_world.json ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "hello": "world"
3
+ }
@@ -1,7 +1,7 @@
1
1
  require 'tins/xt'
2
2
  require 'term/ansicolor'
3
3
  require 'json'
4
-
4
+ require 'mize'
5
5
 
6
6
  # The ContextSpook module serves as a namespace container for collecting and
7
7
  # organizing project information for AI assistance.
@@ -19,8 +19,16 @@ module ContextSpook
19
19
  #
20
20
  # @return [ ContextSpook::Generator::Context ] the context object generated
21
21
  # from the file contents
22
- def self.generate_context(filename)
23
- Generator.send(:new).send(:parse, File.read(filename)).context
22
+ def self.generate_context(filename = nil, &block)
23
+ filename.present? ^ block or
24
+ raise ArgumentError, 'need either a filename or a &block argument'
25
+ generator = if filename
26
+ Generator.send(:new).send(:parse, File.read(filename))
27
+ else
28
+ Generator.send(:new, &block)
29
+ end
30
+ generator.output_context_size
31
+ generator.context
24
32
  end
25
33
 
26
34
  # The Generator class provides a DSL parser that interprets context
@@ -52,6 +60,20 @@ module ContextSpook
52
60
  end
53
61
  end
54
62
 
63
+ # The output_context_size method prints the total size of the generated
64
+ # context JSON representation.
65
+ #
66
+ # This method calculates the size of the context object when serialized to
67
+ # JSON, formats it using binary units (KiB, MiB, etc.), and outputs the
68
+ # result to standard error.
69
+ def output_context_size
70
+ context_size = @context&.size.to_i
71
+ json_content_size = Tins::Unit.format(
72
+ context_size, format: '%.2f %U', unit: ?b, prefix: 1024
73
+ )
74
+ STDERR.puts "Built #{json_content_size} of JSON context in total."
75
+ end
76
+
55
77
  # The Context class represents and manages project context data, providing
56
78
  # structured storage for file contents, command outputs, variables, and
57
79
  # metadata that can be serialized to JSON for AI assistance.
@@ -117,6 +139,29 @@ module ContextSpook
117
139
  nil
118
140
  end
119
141
 
142
+ # The json method reads and parses a JSON file, returning the parsed data
143
+ # structure.
144
+ #
145
+ # This method attempts to load a JSON file from the specified path and
146
+ # returns the resulting Ruby data structure. It provides verbose output
147
+ # about the file size when successfully reading the file. In case of file
148
+ # not found errors, it outputs a colored warning message to standard
149
+ # error and returns nil.
150
+ #
151
+ # @param filename [ String ] the path to the JSON file to be read and parsed
152
+ #
153
+ # @return [ Object, nil ] the parsed JSON data structure or nil if the file cannot be read
154
+ def json(filename)
155
+ file_size = Tins::Unit.format(
156
+ File.size(filename), format: '%.2f %U', unit: ?b, prefix: 1024
157
+ )
158
+ STDERR.puts "Read #{filename.inspect} as JSON (%s) for context." % file_size
159
+ JSON.load_file(filename)
160
+ rescue Errno::ENOENT => e
161
+ STDERR.puts color(208) { "Reading #{filename.inspect} as JSON caused #{e.class}: #{e}" }
162
+ nil
163
+ end
164
+
120
165
  # The files method sets up a DSL accessor for providing files.
121
166
  #
122
167
  # @param default [ Hash ] the default files hash
@@ -191,6 +236,7 @@ module ContextSpook
191
236
  # The to_json method converts the object to a JSON representation by
192
237
  # first generating its hash form and then serializing that hash into JSON
193
238
  # format.
239
+ memoize method:
194
240
  def to_json(*)
195
241
  as_json.to_json(*)
196
242
  end
@@ -207,6 +253,19 @@ module ContextSpook
207
253
  variables:
208
254
  }
209
255
  end
256
+
257
+ # The size method calculates and returns the byte size of the JSON
258
+ # representation of the context.
259
+ #
260
+ # This method determines the size in bytes of the JSON-serialized version
261
+ # of the context object, which is useful for understanding the total data
262
+ # payload being sent to an AI assistant.
263
+ #
264
+ # @return [ Integer ] the size in bytes of the JSON representation of the
265
+ # context
266
+ def size
267
+ to_json.size
268
+ end
210
269
  end
211
270
 
212
271
  private
@@ -1,6 +1,6 @@
1
1
  module ContextSpook
2
2
  # ContextSpook version
3
- VERSION = '0.0.1'
3
+ VERSION = '0.2.0'
4
4
  VERSION_ARRAY = VERSION.split('.').map(&:to_i) # :nodoc:
5
5
  VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
6
6
  VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
@@ -5,19 +5,46 @@ describe ContextSpook::Generator do
5
5
  ContextSpook.generate_context('contexts/project.rb')
6
6
  end
7
7
 
8
- it 'context can be generated' do
8
+ it 'context can be generated from block' do
9
+ expect_any_instance_of(described_class).to\
10
+ receive(:output_context_size).and_call_original
11
+ context = ContextSpook.generate_context do
12
+ context do
13
+ variable foo: 'bar'
14
+ metadata version: '1.0'
15
+ end
16
+ end
17
+ expect(context).to be_a described_class::Context
18
+ expect(context.variables[:foo]).to eq 'bar'
19
+ expect(context.metadata[:version]).to eq '1.0'
20
+ end
21
+
22
+ it 'context can be generated from filename' do
23
+ expect_any_instance_of(described_class).to\
24
+ receive(:output_context_size).and_call_original
9
25
  expect(context).to be_a described_class::Context
10
26
  expect(context.metadata[:ruby]).to eq RUBY_DESCRIPTION
11
27
  end
12
28
 
29
+ it 'could handle premature output_context_size calls' do
30
+ expect_any_instance_of(described_class).to\
31
+ receive(:output_context_size).and_call_original
32
+ described_class.send(:new).output_context_size
33
+ end
34
+
35
+ it 'cannot do from block and filename' do
36
+ expect {
37
+ ContextSpook.generate_context('contexts/project.rb') { }
38
+ }.to raise_error(ArgumentError, /need either a filename or a &block/)
39
+ end
40
+
13
41
  it 'context be transformed to JSON if loaded' do
14
42
  context_as_json = context.to_json
15
- expect(context_as_json.size).to be > 1024
43
+ expect(context.size).to be > 1024
16
44
  expect(JSON(context_as_json)).to be_a Hash
17
45
  end
18
46
 
19
47
  describe 'Context' do
20
-
21
48
  it 'can have variables' do
22
49
  expect(context.variables[:branch]).to be_present
23
50
  end
@@ -33,7 +60,7 @@ describe ContextSpook::Generator do
33
60
  end
34
61
 
35
62
  it 'can have commands' do
36
- command = context.commands['tree lib']
63
+ command = context.commands['tree']
37
64
  expect(command).to be_present
38
65
  expect(command[:working_directory]).to eq Dir.pwd
39
66
  expect(command[:exit_code]).to be_present
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: context_spook
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Florian Frank
@@ -121,6 +121,20 @@ dependencies:
121
121
  - - "~>"
122
122
  - !ruby/object:Gem::Version
123
123
  version: '1.11'
124
+ - !ruby/object:Gem::Dependency
125
+ name: mize
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: '0.6'
131
+ type: :runtime
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - "~>"
136
+ - !ruby/object:Gem::Version
137
+ version: '0.6'
124
138
  description: |
125
139
  context_spook is a library that collects and organizes project
126
140
  information to help AI assistants understand codebases better.
@@ -141,6 +155,7 @@ files:
141
155
  - bin/context_spook
142
156
  - context_spook.gemspec
143
157
  - contexts/project.rb
158
+ - hello_world.json
144
159
  - lib/context_spook.rb
145
160
  - lib/context_spook/generator.rb
146
161
  - lib/context_spook/version.rb