context_spook 1.5.1 → 1.7.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 +4 -4
- data/README.md +103 -12
- data/Rakefile +7 -8
- data/bin/context_spook +6 -3
- data/context_spook.gemspec +7 -8
- data/lib/context_spook/generator.rb +83 -45
- data/lib/context_spook/output_context.rb +13 -8
- data/lib/context_spook/toon/encoder.rb +628 -0
- data/lib/context_spook/toon/example.md +36 -66
- data/lib/context_spook/toon.rb +13 -15
- data/lib/context_spook/utils.rb +31 -4
- data/lib/context_spook/version.rb +1 -1
- data/spec/context_spook/generator_spec.rb +65 -1
- data/spec/context_spook/toon_encoder_spec.rb +90 -0
- data/spec/context_spook/toon_spec.rb +15 -16
- data/spec/examples/README.md +79 -0
- data/spec/examples/conversions/keyed-tabular-objects.json +19 -0
- data/spec/examples/conversions/keyed-tabular-objects.toon +4 -0
- data/spec/examples/conversions/nested-field-groups.json +28 -0
- data/spec/examples/conversions/nested-field-groups.toon +4 -0
- data/spec/examples/conversions/tabular-arrays.json +22 -0
- data/spec/examples/conversions/tabular-arrays.toon +4 -0
- data/spec/examples/invalid/delimiter-mismatch.toon +3 -0
- data/spec/examples/invalid/length-mismatch.toon +3 -0
- data/spec/examples/invalid/multiple-root-primitives.toon +2 -0
- data/spec/examples/valid/comments.toon +5 -0
- data/spec/examples/valid/delimiter-scoping.toon +5 -0
- data/spec/examples/valid/mixed-arrays.toon +5 -0
- data/spec/examples/valid/nested-objects.toon +9 -0
- data/spec/examples/valid/objects.toon +6 -0
- data/spec/examples/valid/pipe-delimiter.toon +3 -0
- data/spec/examples/valid/primitive-arrays.toon +3 -0
- data/spec/examples/valid/tab-delimiter.toon +3 -0
- data/spec/fixtures/encode/arrays-nested.json +120 -0
- data/spec/fixtures/encode/arrays-objects.json +160 -0
- data/spec/fixtures/encode/arrays-primitive.json +95 -0
- data/spec/fixtures/encode/arrays-tabular.json +182 -0
- data/spec/fixtures/encode/delimiters.json +214 -0
- data/spec/fixtures/encode/objects-keyed.json +160 -0
- data/spec/fixtures/encode/objects.json +288 -0
- data/spec/fixtures/encode/primitives.json +262 -0
- data/spec/fixtures/encode/whitespace.json +54 -0
- metadata +35 -18
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3992c09d3428a0268385de6fbf268f96b92ff8cc8f84cc11d464e3c1cd9c1cfe
|
|
4
|
+
data.tar.gz: d8cbd80e0ca8a3df54f464d8eb120dd8d9864b51e1aafc12b23f60e9e8b42eef
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d186a38e1e0285498700f73d790ed0caa3fd4e19e38565c21f39657320311d1bf37bf32405ea176c44e8d7e4b8ef3978d853ce35f89a1e922ecbbf739b2dafa6
|
|
7
|
+
data.tar.gz: 9a86582d2c45a68a913ae82f64e76ca2235e96d4d3250901b49eced799ff7d236b08f869fe1dc20fe4c3453c21d35e4cb7e9c8b42c67d8ed67c7bb79d3e469f5
|
data/README.md
CHANGED
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
ContextSpook is a Ruby library that collects and organizes project information
|
|
4
4
|
to help AI assistants understand codebases better. It provides a
|
|
5
5
|
domain-specific language (DSL) for describing project context, which can then
|
|
6
|
-
be exported as structured JSON data.
|
|
6
|
+
be exported as structured JSON or TOON (Token-Oriented Object Notation) data.
|
|
7
|
+
|
|
8
|
+
TOON output is produced by a native encoder conformant to the TOON v4.4
|
|
9
|
+
specification, validated against 186 official conformance fixtures.
|
|
7
10
|
|
|
8
11
|
The DSL is general-purpose and can be used for any kind of project or
|
|
9
12
|
collection of files, whether software development, documentation, research
|
|
@@ -14,7 +17,8 @@ domains.
|
|
|
14
17
|
|
|
15
18
|
## Documentation
|
|
16
19
|
|
|
17
|
-
Complete API documentation is available at:
|
|
20
|
+
Complete API documentation is available at:
|
|
21
|
+
[GitHub.io](https://flori.github.io/context_spook/)
|
|
18
22
|
|
|
19
23
|
## Installation
|
|
20
24
|
|
|
@@ -25,11 +29,13 @@ gem 'context_spook'
|
|
|
25
29
|
```
|
|
26
30
|
|
|
27
31
|
And then execute:
|
|
32
|
+
|
|
28
33
|
```bash
|
|
29
34
|
$ bundle install
|
|
30
35
|
```
|
|
31
36
|
|
|
32
37
|
Or install it yourself as:
|
|
38
|
+
|
|
33
39
|
```bash
|
|
34
40
|
$ gem install context_spook
|
|
35
41
|
```
|
|
@@ -72,7 +78,18 @@ application.
|
|
|
72
78
|
```ruby
|
|
73
79
|
File.write 'context.json', context.to_json
|
|
74
80
|
```
|
|
81
|
+
You can also generate a manifest-only context by passing `manifest: true`.
|
|
82
|
+
File entries are reduced to their path plus metadata (size, tokens, MIME
|
|
83
|
+
types, tags), giving the model a lightweight index of file paths it can
|
|
84
|
+
use with `read_file` to load only what it needs:
|
|
75
85
|
|
|
86
|
+
```ruby
|
|
87
|
+
context = ContextSpook::generate_context(manifest: true) do
|
|
88
|
+
context do
|
|
89
|
+
file 'lib/context_spook.rb'
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
```
|
|
76
93
|
#### From a context definition file
|
|
77
94
|
|
|
78
95
|
Alternatively store the block's content above to a file `.contexts/project.rb`:
|
|
@@ -136,6 +153,28 @@ Or pipe directly to another tool:
|
|
|
136
153
|
context_spook .contexts/project.rb | ollama_chat_send
|
|
137
154
|
```
|
|
138
155
|
|
|
156
|
+
You can choose the output format with the `-F` flag (JSON is the default):
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
# Generate context as TOON
|
|
160
|
+
context_spook .contexts/project.rb -F TOON > context.toon
|
|
161
|
+
|
|
162
|
+
# Save TOON directly to a file
|
|
163
|
+
context_spook .contexts/project.rb -F TOON -o context.toon
|
|
164
|
+
```
|
|
165
|
+
You can use manifest mode with `-m` to omit file content. Each file entry
|
|
166
|
+
is reduced to its path plus metadata (size, tokens, MIME types, tags),
|
|
167
|
+
giving the model a lightweight index of file paths it can use with
|
|
168
|
+
`read_file` to load only what it needs:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
# Generate manifest-only context as TOON
|
|
172
|
+
context_spook .contexts/project.rb -F TOON -m > context.toon
|
|
173
|
+
|
|
174
|
+
# Generate manifest-only context as JSON
|
|
175
|
+
context_spook .contexts/project.rb -m > context.json
|
|
176
|
+
```
|
|
177
|
+
|
|
139
178
|
The CLI tool also supports verbose output:
|
|
140
179
|
|
|
141
180
|
```bash
|
|
@@ -178,7 +217,8 @@ To estimate the size of your context without generating the large output file:
|
|
|
178
217
|
context_spook .contexts/project.rb -S
|
|
179
218
|
```
|
|
180
219
|
|
|
181
|
-
This is useful for optimizing context size before committing to large output
|
|
220
|
+
This is useful for optimizing context size before committing to large output
|
|
221
|
+
files.
|
|
182
222
|
|
|
183
223
|
This is how you can show the usage message:
|
|
184
224
|
|
|
@@ -192,8 +232,10 @@ context_spook -h
|
|
|
192
232
|
The DSL collects various types of project information:
|
|
193
233
|
|
|
194
234
|
- **Variables**: Key-value pairs (e.g., git branch)
|
|
195
|
-
- **Files**:
|
|
196
|
-
|
|
235
|
+
- **Files**: Line-numbered content, byte size, token estimate, MIME types, line
|
|
236
|
+
count, and tags (or metadata-only in manifest mode)
|
|
237
|
+
- **Commands**: Shell command outputs with size, token estimate, exit codes,
|
|
238
|
+
working directory, and tags
|
|
197
239
|
- **Metadata**: Project-wide attributes (e.g., Ruby version, coverage data)
|
|
198
240
|
|
|
199
241
|
## Intended Use Case
|
|
@@ -215,9 +257,20 @@ assistants understand:
|
|
|
215
257
|
"files": {
|
|
216
258
|
"lib/context_spook.rb": {
|
|
217
259
|
"namespace": "lib",
|
|
218
|
-
"content":
|
|
219
|
-
|
|
220
|
-
|
|
260
|
+
"content": [
|
|
261
|
+
{ "linenumber": 1, "line": "module ContextSpook" },
|
|
262
|
+
{ "linenumber": 2, "line": "" },
|
|
263
|
+
{ "linenumber": 3, "line": "require 'context_spook/version'" },
|
|
264
|
+
{ "linenumber": 4, "line": "require 'context_spook/utils'" },
|
|
265
|
+
{ "linenumber": 5, "line": "require 'context_spook/generator'" }
|
|
266
|
+
],
|
|
267
|
+
"size": 120,
|
|
268
|
+
"tokens": 35,
|
|
269
|
+
"lines": 5,
|
|
270
|
+
"content_types": [
|
|
271
|
+
"application/x-ruby",
|
|
272
|
+
"text/x-ruby"
|
|
273
|
+
],
|
|
221
274
|
"tags": [
|
|
222
275
|
"lib"
|
|
223
276
|
]
|
|
@@ -227,20 +280,58 @@ assistants understand:
|
|
|
227
280
|
"tree": {
|
|
228
281
|
"namespace": "structure",
|
|
229
282
|
"output": "lib\n├── context_spook\n│ └── generator.rb\n└── context_spook.rb\n\n2 directories, 3 files",
|
|
283
|
+
"size": 88,
|
|
284
|
+
"tokens": 26,
|
|
230
285
|
"exit_code": 0,
|
|
231
|
-
"working_directory": "/path/to/project"
|
|
286
|
+
"working_directory": "/path/to/project",
|
|
287
|
+
"tags": [
|
|
288
|
+
"project_structure"
|
|
289
|
+
]
|
|
232
290
|
}
|
|
233
291
|
},
|
|
234
292
|
"metadata": {
|
|
235
|
-
"ruby": "ruby
|
|
236
|
-
"code_coverage":
|
|
293
|
+
"ruby": "ruby 4.0.7 (2026-09-15 ...) +PRISM [x86_64-linux]",
|
|
294
|
+
"code_coverage": null
|
|
237
295
|
},
|
|
238
296
|
"variables": {
|
|
239
|
-
"branch": "
|
|
297
|
+
"branch": "master"
|
|
240
298
|
}
|
|
241
299
|
}
|
|
242
300
|
```
|
|
243
301
|
|
|
302
|
+
The same context in TOON v4 format:
|
|
303
|
+
|
|
304
|
+
```
|
|
305
|
+
files:
|
|
306
|
+
"lib/context_spook.rb":
|
|
307
|
+
namespace: lib
|
|
308
|
+
content[5]{linenumber,line}:
|
|
309
|
+
1,module ContextSpook
|
|
310
|
+
2,""
|
|
311
|
+
3,require 'context_spook/version'
|
|
312
|
+
4,require 'context_spook/utils'
|
|
313
|
+
5,require 'context_spook/generator'
|
|
314
|
+
size: 120
|
|
315
|
+
tokens: 35
|
|
316
|
+
lines: 5
|
|
317
|
+
content_types[2]: application/x-ruby,text/x-ruby
|
|
318
|
+
tags[1]: lib
|
|
319
|
+
commands:
|
|
320
|
+
tree:
|
|
321
|
+
namespace: structure
|
|
322
|
+
output: "lib\n├── context_spook\n│ └── generator.rb\n└── context_spook.rb\n\n2 directories, 3 files"
|
|
323
|
+
size: 88
|
|
324
|
+
tokens: 26
|
|
325
|
+
exit_code: 0
|
|
326
|
+
working_directory: /path/to/project
|
|
327
|
+
tags[1]: project_structure
|
|
328
|
+
metadata:
|
|
329
|
+
ruby: "ruby 4.0.7 (2026-09-15 ...) +PRISM [x86_64-linux]"
|
|
330
|
+
code_coverage: null
|
|
331
|
+
variables:
|
|
332
|
+
branch: master
|
|
333
|
+
```
|
|
334
|
+
|
|
244
335
|
## License
|
|
245
336
|
|
|
246
337
|
The gem is available as open source under the terms of the [MIT License](LICENSE)
|
data/Rakefile
CHANGED
|
@@ -31,14 +31,13 @@ GemHadar do
|
|
|
31
31
|
|
|
32
32
|
required_ruby_version '>= 3.1'
|
|
33
33
|
|
|
34
|
-
dependency 'tins',
|
|
35
|
-
dependency 'json',
|
|
36
|
-
dependency 'term-ansicolor',
|
|
37
|
-
dependency 'mize',
|
|
38
|
-
dependency 'mime-types',
|
|
39
|
-
dependency 'yaml',
|
|
40
|
-
dependency 'pathname',
|
|
41
|
-
dependency 'toon-ruby', '>= 0.1'
|
|
34
|
+
dependency 'tins', '~> 1.46'
|
|
35
|
+
dependency 'json', '~> 2.0'
|
|
36
|
+
dependency 'term-ansicolor', '~> 1.11'
|
|
37
|
+
dependency 'mize', '~> 0.6'
|
|
38
|
+
dependency 'mime-types', '~> 3.0'
|
|
39
|
+
dependency 'yaml', '~> 0.4'
|
|
40
|
+
dependency 'pathname', '~> 0.4'
|
|
42
41
|
development_dependency 'all_images', '~> 0.6'
|
|
43
42
|
development_dependency 'rspec', '~> 3.2'
|
|
44
43
|
development_dependency 'debug'
|
data/bin/context_spook
CHANGED
|
@@ -27,6 +27,7 @@ def usage
|
|
|
27
27
|
-i PATTERN Include files matching PATTERN (can be used multiple times)
|
|
28
28
|
Supports glob patterns like **, *, ?, [abc], {a,b,c}
|
|
29
29
|
-F FORMAT Output context as format JSON (the default) or TOON
|
|
30
|
+
-m Manifest mode: omit file content, carry only metadata
|
|
30
31
|
-h Show this help message
|
|
31
32
|
|
|
32
33
|
Examples:
|
|
@@ -49,19 +50,21 @@ def usage
|
|
|
49
50
|
# Generate informational output only to estimate context size
|
|
50
51
|
#{File.basename($0)} .contexts/project.rb -S
|
|
51
52
|
|
|
53
|
+
# Generate context in manifest mode (metadata only, no file content)
|
|
54
|
+
#{File.basename($0)} .contexts/project.rb -m
|
|
52
55
|
# Generate context and save to file
|
|
53
56
|
#{File.basename($0)} .contexts/project.rb -o context.json
|
|
54
57
|
EOT
|
|
55
58
|
exit 0
|
|
56
59
|
end
|
|
57
60
|
|
|
58
|
-
opts = go 'o:i:F:
|
|
61
|
+
opts = go 'o:i:F:mvSh', defaults: { ?v => true }
|
|
59
62
|
opts[?h] and usage
|
|
60
63
|
context = nil
|
|
61
64
|
output = nil
|
|
62
65
|
|
|
63
66
|
if opts[?i]
|
|
64
|
-
context = ContextSpook.generate_context(verbose: opts[?v], format: opts[?F]) do
|
|
67
|
+
context = ContextSpook.generate_context(verbose: opts[?v], format: opts[?F], manifest: opts[?m]) do
|
|
65
68
|
context do
|
|
66
69
|
opts[?i].to_a.each do |glob|
|
|
67
70
|
glob = File.expand_path(glob)
|
|
@@ -74,7 +77,7 @@ if opts[?i]
|
|
|
74
77
|
end
|
|
75
78
|
else
|
|
76
79
|
filename = ARGV.shift or fail 'require context definition file as an argument'
|
|
77
|
-
context = ContextSpook.generate_context(filename, verbose: opts[?v], format: opts[?F])
|
|
80
|
+
context = ContextSpook.generate_context(filename, verbose: opts[?v], format: opts[?F], manifest: opts[?m])
|
|
78
81
|
end
|
|
79
82
|
|
|
80
83
|
output = STDOUT
|
data/context_spook.gemspec
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
|
2
|
-
# stub: context_spook 1.
|
|
2
|
+
# stub: context_spook 1.7.0 ruby lib
|
|
3
3
|
|
|
4
4
|
Gem::Specification.new do |s|
|
|
5
5
|
s.name = "context_spook".freeze
|
|
6
|
-
s.version = "1.
|
|
6
|
+
s.version = "1.7.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]
|
|
@@ -12,19 +12,19 @@ Gem::Specification.new do |s|
|
|
|
12
12
|
s.description = "context_spook is a library that collects and organizes project\ninformation to help AI assistants understand codebases better.\n".freeze
|
|
13
13
|
s.email = "flori@ping.de".freeze
|
|
14
14
|
s.executables = ["context_spook".freeze]
|
|
15
|
-
s.extra_rdoc_files = ["README.md".freeze, "lib/context_spook.rb".freeze, "lib/context_spook/generator.rb".freeze, "lib/context_spook/output_context.rb".freeze, "lib/context_spook/toon.rb".freeze, "lib/context_spook/utils.rb".freeze, "lib/context_spook/verbose_puts.rb".freeze, "lib/context_spook/version.rb".freeze]
|
|
16
|
-
s.files = [".contexts/project.rb".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "bin/context_spook".freeze, "context_spook.gemspec".freeze, "hello_world.json".freeze, "hey_world.yaml".freeze, "lib/context_spook.rb".freeze, "lib/context_spook/generator.rb".freeze, "lib/context_spook/output_context.rb".freeze, "lib/context_spook/toon.rb".freeze, "lib/context_spook/toon/example.md".freeze, "lib/context_spook/utils.rb".freeze, "lib/context_spook/verbose_puts.rb".freeze, "lib/context_spook/version.rb".freeze, "spec/context_spook/generator_spec.rb".freeze, "spec/context_spook/toon_spec.rb".freeze, "spec/spec_helper.rb".freeze]
|
|
15
|
+
s.extra_rdoc_files = ["README.md".freeze, "lib/context_spook.rb".freeze, "lib/context_spook/generator.rb".freeze, "lib/context_spook/output_context.rb".freeze, "lib/context_spook/toon.rb".freeze, "lib/context_spook/toon/encoder.rb".freeze, "lib/context_spook/utils.rb".freeze, "lib/context_spook/verbose_puts.rb".freeze, "lib/context_spook/version.rb".freeze]
|
|
16
|
+
s.files = [".contexts/project.rb".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "bin/context_spook".freeze, "context_spook.gemspec".freeze, "hello_world.json".freeze, "hey_world.yaml".freeze, "lib/context_spook.rb".freeze, "lib/context_spook/generator.rb".freeze, "lib/context_spook/output_context.rb".freeze, "lib/context_spook/toon.rb".freeze, "lib/context_spook/toon/encoder.rb".freeze, "lib/context_spook/toon/example.md".freeze, "lib/context_spook/utils.rb".freeze, "lib/context_spook/verbose_puts.rb".freeze, "lib/context_spook/version.rb".freeze, "spec/context_spook/generator_spec.rb".freeze, "spec/context_spook/toon_encoder_spec.rb".freeze, "spec/context_spook/toon_spec.rb".freeze, "spec/examples/README.md".freeze, "spec/examples/conversions/keyed-tabular-objects.json".freeze, "spec/examples/conversions/keyed-tabular-objects.toon".freeze, "spec/examples/conversions/nested-field-groups.json".freeze, "spec/examples/conversions/nested-field-groups.toon".freeze, "spec/examples/conversions/tabular-arrays.json".freeze, "spec/examples/conversions/tabular-arrays.toon".freeze, "spec/examples/invalid/delimiter-mismatch.toon".freeze, "spec/examples/invalid/length-mismatch.toon".freeze, "spec/examples/invalid/multiple-root-primitives.toon".freeze, "spec/examples/valid/comments.toon".freeze, "spec/examples/valid/delimiter-scoping.toon".freeze, "spec/examples/valid/mixed-arrays.toon".freeze, "spec/examples/valid/nested-objects.toon".freeze, "spec/examples/valid/objects.toon".freeze, "spec/examples/valid/pipe-delimiter.toon".freeze, "spec/examples/valid/primitive-arrays.toon".freeze, "spec/examples/valid/tab-delimiter.toon".freeze, "spec/fixtures/encode/arrays-nested.json".freeze, "spec/fixtures/encode/arrays-objects.json".freeze, "spec/fixtures/encode/arrays-primitive.json".freeze, "spec/fixtures/encode/arrays-tabular.json".freeze, "spec/fixtures/encode/delimiters.json".freeze, "spec/fixtures/encode/objects-keyed.json".freeze, "spec/fixtures/encode/objects.json".freeze, "spec/fixtures/encode/primitives.json".freeze, "spec/fixtures/encode/whitespace.json".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]
|
|
20
20
|
s.required_ruby_version = Gem::Requirement.new(">= 3.1".freeze)
|
|
21
|
-
s.rubygems_version = "4.0.
|
|
21
|
+
s.rubygems_version = "4.0.22".freeze
|
|
22
22
|
s.summary = "context_spook collects project context for AI".freeze
|
|
23
|
-
s.test_files = ["spec/context_spook/generator_spec.rb".freeze, "spec/context_spook/toon_spec.rb".freeze, "spec/spec_helper.rb".freeze]
|
|
23
|
+
s.test_files = ["spec/context_spook/generator_spec.rb".freeze, "spec/context_spook/toon_encoder_spec.rb".freeze, "spec/context_spook/toon_spec.rb".freeze, "spec/spec_helper.rb".freeze]
|
|
24
24
|
|
|
25
25
|
s.specification_version = 4
|
|
26
26
|
|
|
27
|
-
s.add_development_dependency(%q<gem_hadar>.freeze, [">= 2.
|
|
27
|
+
s.add_development_dependency(%q<gem_hadar>.freeze, [">= 2.18.0".freeze])
|
|
28
28
|
s.add_development_dependency(%q<all_images>.freeze, ["~> 0.6".freeze])
|
|
29
29
|
s.add_development_dependency(%q<rspec>.freeze, ["~> 3.2".freeze])
|
|
30
30
|
s.add_development_dependency(%q<debug>.freeze, [">= 0".freeze])
|
|
@@ -36,5 +36,4 @@ Gem::Specification.new do |s|
|
|
|
36
36
|
s.add_runtime_dependency(%q<mime-types>.freeze, ["~> 3.0".freeze])
|
|
37
37
|
s.add_runtime_dependency(%q<yaml>.freeze, ["~> 0.4".freeze])
|
|
38
38
|
s.add_runtime_dependency(%q<pathname>.freeze, ["~> 0.4".freeze])
|
|
39
|
-
s.add_runtime_dependency(%q<toon-ruby>.freeze, [">= 0.1".freeze])
|
|
40
39
|
end
|
|
@@ -28,6 +28,8 @@ module ContextSpook
|
|
|
28
28
|
# during processing
|
|
29
29
|
# @param format [ String, nil ] the output format for the context, either
|
|
30
30
|
# 'JSON' or 'TOON'
|
|
31
|
+
# @param manifest [ TrueClass, FalseClass ] if true, file entries
|
|
32
|
+
# omit their content and only carry metadata
|
|
31
33
|
# @param block [ Proc ] a block containing the context definition, used if
|
|
32
34
|
# filename is nil
|
|
33
35
|
#
|
|
@@ -37,15 +39,18 @@ module ContextSpook
|
|
|
37
39
|
# @raise [ ArgumentError ] if neither a filename nor a block is provided
|
|
38
40
|
# @raise [ ArgumentError ] if an invalid format is specified
|
|
39
41
|
# @raise [ ArgumentError ] if the context definition file cannot be read or parsed
|
|
40
|
-
def self.generate_context(filename = nil, verbose: false, format: nil,
|
|
42
|
+
def self.generate_context(filename = nil, verbose: false, format: nil,
|
|
43
|
+
manifest: false, &block)
|
|
41
44
|
verbose = !!verbose
|
|
42
45
|
filename.present? ^ block or
|
|
43
46
|
raise ArgumentError, 'need either a filename or a &block argument'
|
|
44
|
-
generator =
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
generator =
|
|
48
|
+
if filename
|
|
49
|
+
source = File.read(filename)
|
|
50
|
+
Generator.send(:new, verbose:, format:, manifest:).send(:parse, source)
|
|
51
|
+
else
|
|
52
|
+
Generator.send(:new, verbose:, format:, manifest:, &block)
|
|
53
|
+
end
|
|
49
54
|
generator.output_context_size
|
|
50
55
|
generator.context
|
|
51
56
|
end
|
|
@@ -70,8 +75,21 @@ module ContextSpook
|
|
|
70
75
|
# @return [ String ] the format identifier, either 'JSON' or 'TOON'
|
|
71
76
|
attr_reader :format
|
|
72
77
|
|
|
78
|
+
# The manifest method returns whether the generator is in manifest mode,
|
|
79
|
+
# where file entries omit their content.
|
|
80
|
+
#
|
|
81
|
+
# @return [ TrueClass, FalseClass ] true if in manifest mode
|
|
82
|
+
attr_reader :manifest
|
|
83
|
+
|
|
73
84
|
private_class_method :new
|
|
74
85
|
|
|
86
|
+
# The token_estimator attribute allows setting the logic used to estimate
|
|
87
|
+
# the number of tokens in a serialized context.
|
|
88
|
+
#
|
|
89
|
+
# It expects an object that responds to #call(text), where text is the
|
|
90
|
+
# serialized context string.
|
|
91
|
+
attr_writer :token_estimator
|
|
92
|
+
|
|
75
93
|
# The initialize method sets up the object by evaluating the provided block
|
|
76
94
|
# in the object's context.
|
|
77
95
|
#
|
|
@@ -79,12 +97,14 @@ module ContextSpook
|
|
|
79
97
|
# output during processing, defaults to lfalse.
|
|
80
98
|
# @param block [ Proc ] a block of code to be evaluated within the object's context
|
|
81
99
|
# If no block is given, the method does nothing.
|
|
82
|
-
def initialize(verbose: false, format: nil, &block)
|
|
83
|
-
@verbose
|
|
84
|
-
@format
|
|
100
|
+
def initialize(verbose: false, format: nil, manifest: false, &block)
|
|
101
|
+
@verbose = !!verbose
|
|
102
|
+
@format = (format || 'JSON').upcase
|
|
103
|
+
@manifest = !!manifest
|
|
85
104
|
%w[ TOON JSON ].include?(@format) or
|
|
86
105
|
raise ArgumentError,
|
|
87
106
|
"format needs to be either JSON or TOON, was #{@format.inspect}"
|
|
107
|
+
self.token_estimator = ContextSpook::Utils.method(:estimate_tokens)
|
|
88
108
|
block and instance_eval(&block)
|
|
89
109
|
end
|
|
90
110
|
|
|
@@ -111,21 +131,18 @@ module ContextSpook
|
|
|
111
131
|
include Tins::DSLAccessor
|
|
112
132
|
include Term::ANSIColor
|
|
113
133
|
include ContextSpook::TOON
|
|
134
|
+
include ContextSpook::Utils
|
|
114
135
|
|
|
115
136
|
# The initialize method sets up the object by evaluating the provided block
|
|
116
137
|
# in the object's context.
|
|
117
138
|
#
|
|
118
|
-
# @param verbose [ TrueClass, FalseClass ] flag to enable verbose output
|
|
119
|
-
# during processing, defaults to false.
|
|
120
139
|
# @param block [ Proc ] a block of code to be evaluated within the object's context
|
|
121
140
|
# If no block is given, the method does nothing.
|
|
122
141
|
def initialize(generator:, &block)
|
|
123
142
|
@generator = generator
|
|
124
143
|
block and instance_eval(&block)
|
|
125
144
|
meta(format: generator.format)
|
|
126
|
-
if generator.
|
|
127
|
-
meta(toon_example: toon_example)
|
|
128
|
-
end
|
|
145
|
+
meta(manifest: true) if generator.manifest
|
|
129
146
|
end
|
|
130
147
|
|
|
131
148
|
# The generator method returns the generator object associated with this
|
|
@@ -137,6 +154,8 @@ module ContextSpook
|
|
|
137
154
|
|
|
138
155
|
delegate :verbose, to: :generator
|
|
139
156
|
|
|
157
|
+
delegate :manifest, to: :generator
|
|
158
|
+
|
|
140
159
|
# The namespace method creates a scoped block with a given name.
|
|
141
160
|
#
|
|
142
161
|
# @param name [ Object ] the name to scope the block with
|
|
@@ -197,11 +216,10 @@ module ContextSpook
|
|
|
197
216
|
#
|
|
198
217
|
# @param filename [ String ] the path to the JSON file to be read and parsed
|
|
199
218
|
#
|
|
200
|
-
# @return [ Object, nil ] the parsed JSON data structure or nil if the
|
|
219
|
+
# @return [ Object, nil ] the parsed JSON data structure or nil if the
|
|
220
|
+
# file cannot be read
|
|
201
221
|
def json(filename)
|
|
202
|
-
file_size =
|
|
203
|
-
File.size(filename), format: '%.2f %U', unit: ?b, prefix: 1024
|
|
204
|
-
)
|
|
222
|
+
file_size = format_size(File.size(filename))
|
|
205
223
|
verbose_puts "Read #{filename.inspect} as JSON (%s) for context." % file_size
|
|
206
224
|
JSON.load_file(filename)
|
|
207
225
|
rescue Errno::ENOENT, JSON::ParserError => e
|
|
@@ -223,9 +241,7 @@ module ContextSpook
|
|
|
223
241
|
# @return [ Object, nil ] the parsed YAML data structure or nil if the
|
|
224
242
|
# file cannot be read
|
|
225
243
|
def yaml(filename)
|
|
226
|
-
file_size =
|
|
227
|
-
File.size(filename), format: '%.2f %U', unit: ?b, prefix: 1024
|
|
228
|
-
)
|
|
244
|
+
file_size = format_size(File.size(filename))
|
|
229
245
|
verbose_puts "Read #{filename.inspect} as YAML (%s) for context." % file_size
|
|
230
246
|
YAML.load_file(filename)
|
|
231
247
|
rescue Errno::ENOENT, Psych::SyntaxError => e
|
|
@@ -244,32 +260,51 @@ module ContextSpook
|
|
|
244
260
|
# content.
|
|
245
261
|
#
|
|
246
262
|
# It reads the specified file and creates an entry in the files hash with
|
|
247
|
-
# the file's content, along with its namespace and optional tags.
|
|
263
|
+
# the file's content, along with its namespace and optional tags. In
|
|
264
|
+
# manifest mode, only metadata fields are stored.
|
|
248
265
|
#
|
|
249
266
|
# @param filename [ String ] the path to the file to be read and stored
|
|
250
267
|
# @param tags [ Array<String>, nil ] optional array of tags to associate with the file
|
|
251
268
|
#
|
|
252
269
|
# @return [ Hash ] the created file entry with content, namespace, and tags
|
|
253
270
|
def file(filename, tags: nil)
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
271
|
+
size = File.size(filename)
|
|
272
|
+
tokens = estimate_tokens(size)
|
|
273
|
+
if manifest
|
|
274
|
+
files[filename] = {
|
|
275
|
+
namespace: scope_top,
|
|
276
|
+
size: ,
|
|
277
|
+
tokens: ,
|
|
278
|
+
content_types: MIME::Types.type_for(filename)
|
|
279
|
+
.map(&:content_type).full?,
|
|
280
|
+
tags: (Array(tags) if tags),
|
|
281
|
+
}.compact
|
|
282
|
+
else
|
|
283
|
+
content = File.read(filename)
|
|
284
|
+
content_lines = content.lines
|
|
285
|
+
numbered_lines = content_lines.each_with_index.map {
|
|
286
|
+
{ linenumber: _2 + 1, line: _1.chomp }
|
|
287
|
+
}
|
|
288
|
+
files[filename] = {
|
|
289
|
+
namespace: scope_top,
|
|
290
|
+
content: numbered_lines,
|
|
291
|
+
size: ,
|
|
292
|
+
tokens: ,
|
|
293
|
+
lines: content_lines.size,
|
|
294
|
+
content_types: MIME::Types.type_for(filename)
|
|
295
|
+
.map(&:content_type).full?,
|
|
296
|
+
tags: (Array(tags) if tags),
|
|
297
|
+
}.compact
|
|
298
|
+
end
|
|
299
|
+
verbose_puts "Read #{filename.inspect} (%s, %s) for context." %
|
|
300
|
+
[ format_size(size), format_tokens(tokens) ]
|
|
267
301
|
nil
|
|
268
302
|
rescue Errno::ENOENT => e
|
|
269
303
|
verbose_puts color(208) { "Reading #{filename.inspect} caused #{e.class}: #{e}" }
|
|
270
304
|
end
|
|
271
305
|
|
|
272
|
-
# The commands method sets up a DSL accessor for provided command
|
|
306
|
+
# The commands method sets up a DSL accessor for provided command
|
|
307
|
+
# outputs.
|
|
273
308
|
#
|
|
274
309
|
# @param value [ Hash ] the hash of commands to be set
|
|
275
310
|
dsl_accessor :commands do
|
|
@@ -278,8 +313,8 @@ module ContextSpook
|
|
|
278
313
|
|
|
279
314
|
# The command method executes a shell command and stores its result.
|
|
280
315
|
#
|
|
281
|
-
# This method runs a given shell command and records the output, exit
|
|
282
|
-
# working directory, and optional tags in the commands hash.
|
|
316
|
+
# This method runs a given shell command and records the output, exit
|
|
317
|
+
# code, working directory, and optional tags in the commands hash.
|
|
283
318
|
#
|
|
284
319
|
# @param shell_command [ String ] the shell command to execute
|
|
285
320
|
# @param tags [ Array<String>, nil ] optional array of tags to associate
|
|
@@ -295,17 +330,20 @@ module ContextSpook
|
|
|
295
330
|
"Executing #{shell_command.inspect} resulted in exit code #{exit_code}."
|
|
296
331
|
}
|
|
297
332
|
end
|
|
333
|
+
output_size = output.bytesize
|
|
334
|
+
output_tokens = estimate_tokens(output_size)
|
|
298
335
|
commands[shell_command] = {
|
|
299
|
-
namespace:
|
|
336
|
+
namespace: scope_top,
|
|
300
337
|
output:,
|
|
338
|
+
size: output_size,
|
|
339
|
+
tokens: output_tokens,
|
|
301
340
|
exit_code:,
|
|
302
341
|
working_directory: Dir.pwd,
|
|
303
|
-
tags:
|
|
342
|
+
tags: (Array(tags) if tags),
|
|
304
343
|
}.compact
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
verbose_puts "Executed #{shell_command.inspect} with output (%s) for context." % output_size
|
|
344
|
+
verbose_puts "Executed #{shell_command.inspect} with output "\
|
|
345
|
+
"(%s, %s) for context." %
|
|
346
|
+
[ format_size(output_size), format_tokens(output_tokens) ]
|
|
309
347
|
nil
|
|
310
348
|
end
|
|
311
349
|
|
|
@@ -343,7 +381,7 @@ module ContextSpook
|
|
|
343
381
|
# @return [ Integer ] the size in bytes of the JSON representation of the
|
|
344
382
|
# context
|
|
345
383
|
def size
|
|
346
|
-
to_json.
|
|
384
|
+
to_json.bytesize
|
|
347
385
|
end
|
|
348
386
|
end
|
|
349
387
|
|
|
@@ -5,16 +5,21 @@
|
|
|
5
5
|
# functionality for generated context objects.
|
|
6
6
|
module ContextSpook::OutputContext
|
|
7
7
|
# The output_context_size method prints the total size of the generated
|
|
8
|
-
# context
|
|
8
|
+
# context representation and an estimated token count.
|
|
9
9
|
#
|
|
10
10
|
# This method calculates the size of the context object when serialized to
|
|
11
|
-
# JSON, formats it using binary units
|
|
12
|
-
# result to standard error.
|
|
11
|
+
# JSON (or TOON), formats it using binary units, estimates the token count
|
|
12
|
+
# based on bytes, and outputs the result to standard error.
|
|
13
13
|
def output_context_size
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
context = ''
|
|
15
|
+
if @context
|
|
16
|
+
context = @format == 'TOON' ? @context.to_toon : @context.to_json
|
|
17
|
+
end
|
|
18
|
+
formatted_size = ContextSpook::Utils.format_size(context.bytesize)
|
|
19
|
+
tokens = @token_estimator.call(context)
|
|
20
|
+
formatted_tokens = ContextSpook::Utils.format_tokens(tokens)
|
|
21
|
+
|
|
22
|
+
verbose_puts "Built #{formatted_size} (#{formatted_tokens} tokens) of #@format context in total."
|
|
18
23
|
end
|
|
19
24
|
|
|
20
25
|
# The output_context method writes the generated context to the specified
|
|
@@ -28,7 +33,7 @@ module ContextSpook::OutputContext
|
|
|
28
33
|
# defaults to STDOUT
|
|
29
34
|
def output_context(output: STDOUT)
|
|
30
35
|
if @format == 'TOON'
|
|
31
|
-
|
|
36
|
+
@context.to_toon_stream(output)
|
|
32
37
|
else
|
|
33
38
|
JSON.dump(@context.as_json, output)
|
|
34
39
|
end
|