woods 1.2.0 → 1.4.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.
Files changed (109) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +229 -0
  3. data/README.md +24 -8
  4. data/exe/woods-console +51 -6
  5. data/exe/woods-console-mcp +24 -4
  6. data/exe/woods-mcp +30 -7
  7. data/exe/woods-mcp-http +47 -6
  8. data/lib/generators/woods/install_generator.rb +13 -4
  9. data/lib/generators/woods/templates/woods.rb.tt +155 -0
  10. data/lib/tasks/woods.rake +37 -51
  11. data/lib/woods/builder.rb +174 -9
  12. data/lib/woods/cache/cache_middleware.rb +360 -31
  13. data/lib/woods/chunking/semantic_chunker.rb +334 -7
  14. data/lib/woods/console/adapters/job_adapter.rb +10 -4
  15. data/lib/woods/console/audit_logger.rb +76 -4
  16. data/lib/woods/console/bridge.rb +48 -15
  17. data/lib/woods/console/bridge_protocol.rb +44 -0
  18. data/lib/woods/console/confirmation.rb +3 -4
  19. data/lib/woods/console/console_response_renderer.rb +56 -18
  20. data/lib/woods/console/credential_index.rb +201 -0
  21. data/lib/woods/console/credential_scanner.rb +302 -0
  22. data/lib/woods/console/dispatch_pipeline.rb +138 -0
  23. data/lib/woods/console/embedded_executor.rb +682 -35
  24. data/lib/woods/console/eval_guard.rb +319 -0
  25. data/lib/woods/console/model_validator.rb +1 -3
  26. data/lib/woods/console/rack_middleware.rb +185 -29
  27. data/lib/woods/console/redactor.rb +161 -0
  28. data/lib/woods/console/response_context.rb +127 -0
  29. data/lib/woods/console/safe_context.rb +220 -23
  30. data/lib/woods/console/scope_predicate_parser.rb +131 -0
  31. data/lib/woods/console/server.rb +417 -486
  32. data/lib/woods/console/sql_noise_stripper.rb +87 -0
  33. data/lib/woods/console/sql_table_scanner.rb +213 -0
  34. data/lib/woods/console/sql_validator.rb +81 -31
  35. data/lib/woods/console/table_gate.rb +93 -0
  36. data/lib/woods/console/tool_specs.rb +552 -0
  37. data/lib/woods/console/tools/tier1.rb +3 -3
  38. data/lib/woods/console/tools/tier4.rb +7 -1
  39. data/lib/woods/dependency_graph.rb +66 -7
  40. data/lib/woods/embedding/indexer.rb +190 -6
  41. data/lib/woods/embedding/openai.rb +40 -4
  42. data/lib/woods/embedding/provider.rb +104 -8
  43. data/lib/woods/embedding/text_preparer.rb +23 -3
  44. data/lib/woods/embedding/token_counter.rb +133 -0
  45. data/lib/woods/evaluation/baseline_runner.rb +20 -2
  46. data/lib/woods/evaluation/metrics.rb +4 -1
  47. data/lib/woods/extracted_unit.rb +1 -0
  48. data/lib/woods/extractor.rb +7 -1
  49. data/lib/woods/extractors/controller_extractor.rb +10 -4
  50. data/lib/woods/extractors/mailer_extractor.rb +16 -2
  51. data/lib/woods/extractors/model_extractor.rb +6 -1
  52. data/lib/woods/extractors/phlex_extractor.rb +13 -4
  53. data/lib/woods/extractors/rails_source_extractor.rb +2 -0
  54. data/lib/woods/extractors/route_helper_resolver.rb +130 -0
  55. data/lib/woods/extractors/shared_dependency_scanner.rb +130 -2
  56. data/lib/woods/extractors/view_component_extractor.rb +12 -1
  57. data/lib/woods/extractors/view_engines/base.rb +141 -0
  58. data/lib/woods/extractors/view_engines/erb.rb +145 -0
  59. data/lib/woods/extractors/view_template_extractor.rb +92 -133
  60. data/lib/woods/flow_assembler.rb +23 -15
  61. data/lib/woods/flow_precomputer.rb +21 -2
  62. data/lib/woods/graph_analyzer.rb +3 -4
  63. data/lib/woods/index_artifact.rb +173 -0
  64. data/lib/woods/mcp/bearer_auth.rb +45 -0
  65. data/lib/woods/mcp/bootstrap_state.rb +94 -0
  66. data/lib/woods/mcp/bootstrapper.rb +337 -16
  67. data/lib/woods/mcp/config_resolver.rb +288 -0
  68. data/lib/woods/mcp/errors.rb +134 -0
  69. data/lib/woods/mcp/index_reader.rb +265 -30
  70. data/lib/woods/mcp/origin_guard.rb +132 -0
  71. data/lib/woods/mcp/provider_probe.rb +166 -0
  72. data/lib/woods/mcp/renderers/claude_renderer.rb +6 -0
  73. data/lib/woods/mcp/renderers/markdown_renderer.rb +39 -3
  74. data/lib/woods/mcp/renderers/plain_renderer.rb +16 -2
  75. data/lib/woods/mcp/server.rb +737 -137
  76. data/lib/woods/model_name_cache.rb +78 -2
  77. data/lib/woods/notion/client.rb +25 -2
  78. data/lib/woods/notion/mappers/model_mapper.rb +36 -2
  79. data/lib/woods/railtie.rb +55 -15
  80. data/lib/woods/resilience/circuit_breaker.rb +9 -2
  81. data/lib/woods/resilience/retryable_provider.rb +40 -3
  82. data/lib/woods/resolved_config.rb +299 -0
  83. data/lib/woods/retrieval/context_assembler.rb +112 -5
  84. data/lib/woods/retrieval/query_classifier.rb +1 -1
  85. data/lib/woods/retrieval/ranker.rb +55 -6
  86. data/lib/woods/retrieval/search_executor.rb +42 -13
  87. data/lib/woods/retriever.rb +330 -24
  88. data/lib/woods/session_tracer/middleware.rb +35 -1
  89. data/lib/woods/storage/graph_store.rb +39 -0
  90. data/lib/woods/storage/inapplicable_backend.rb +14 -0
  91. data/lib/woods/storage/metadata_store.rb +129 -1
  92. data/lib/woods/storage/pgvector.rb +70 -8
  93. data/lib/woods/storage/qdrant.rb +196 -5
  94. data/lib/woods/storage/snapshotter/metadata.rb +172 -0
  95. data/lib/woods/storage/snapshotter/vector.rb +238 -0
  96. data/lib/woods/storage/snapshotter.rb +24 -0
  97. data/lib/woods/storage/vector_store.rb +184 -35
  98. data/lib/woods/tasks.rb +85 -0
  99. data/lib/woods/temporal/snapshot_store.rb +49 -1
  100. data/lib/woods/token_utils.rb +44 -5
  101. data/lib/woods/unblocked/client.rb +88 -7
  102. data/lib/woods/unblocked/document_builder.rb +75 -36
  103. data/lib/woods/unblocked/exporter.rb +234 -18
  104. data/lib/woods/unblocked/rate_limiter.rb +10 -2
  105. data/lib/woods/unblocked/sync_manifest.rb +135 -0
  106. data/lib/woods/util/host_guard.rb +61 -0
  107. data/lib/woods/version.rb +1 -1
  108. data/lib/woods.rb +126 -6
  109. metadata +70 -4
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'fileutils'
5
+
6
+ module Woods
7
+ module Unblocked
8
+ # Tracks what was last pushed to an Unblocked collection so a sync can
9
+ # skip unchanged documents, re-push changed ones, and delete orphans.
10
+ #
11
+ # The manifest is the local source of truth for change detection: each
12
+ # entry records the content hash of the document we last pushed for a URI
13
+ # plus the remote +document_id+ (needed for deletes). Persisted as JSON
14
+ # alongside the extraction output and restored across CI runs via the CI
15
+ # provider's cache. A missing or corrupt file degrades to "everything is
16
+ # new" — a correct (if expensive) full sync that rebuilds the manifest.
17
+ #
18
+ # Modeled on the embedding indexer's checkpoint (load JSON → compare
19
+ # per-key hash → save JSON).
20
+ #
21
+ # @example
22
+ # manifest = SyncManifest.new(path: "tmp/woods/unblocked_sync_manifest.json",
23
+ # collection_id: "col-uuid")
24
+ # manifest.unchanged?(uri, hash) # => false on first run
25
+ # manifest.record(uri:, hash:, document_id:)
26
+ # manifest.save
27
+ #
28
+ class SyncManifest
29
+ VERSION = 1
30
+
31
+ # @param path [String] JSON file path for the manifest
32
+ # @param collection_id [String] Target collection UUID — a stored manifest
33
+ # for a *different* collection is discarded (cache-key reuse guard).
34
+ def initialize(path:, collection_id:)
35
+ @path = path
36
+ @collection_id = collection_id
37
+ @documents = load
38
+ end
39
+
40
+ # @return [Boolean] true when no documents are recorded
41
+ def empty?
42
+ @documents.empty?
43
+ end
44
+
45
+ # @param uri [String] Document URI
46
+ # @param hash [String] Content hash of the document we would push now
47
+ # @return [Boolean] true when the recorded hash matches (safe to skip)
48
+ def unchanged?(uri, hash)
49
+ entry = @documents[uri]
50
+ !entry.nil? && entry['hash'] == hash
51
+ end
52
+
53
+ # Record (or update) what we pushed for a URI.
54
+ #
55
+ # @param uri [String] Document URI
56
+ # @param hash [String, nil] Content hash pushed (nil forces a future re-push)
57
+ # @param document_id [String, nil] Remote document UUID (for later deletes)
58
+ def record(uri:, hash:, document_id:)
59
+ @documents[uri] = { 'hash' => hash, 'document_id' => document_id }
60
+ end
61
+
62
+ # @param uri [String] Document URI
63
+ # @return [String, nil] Stored remote document_id, if known
64
+ def document_id_for(uri)
65
+ @documents.dig(uri, 'document_id')
66
+ end
67
+
68
+ # URIs we have a record of that are absent from the current run's set.
69
+ #
70
+ # @param current_uris [Array<String>, Set] URIs that still exist this run
71
+ # @return [Array<String>] recorded URIs no longer present (deletion candidates)
72
+ def stale_uris(current_uris)
73
+ present = current_uris.to_a
74
+ @documents.keys - present
75
+ end
76
+
77
+ # @return [Integer] number of recorded documents
78
+ def size
79
+ @documents.size
80
+ end
81
+
82
+ # Drop a URI from the manifest (after a successful remote delete).
83
+ #
84
+ # @param uri [String] Document URI
85
+ def forget(uri)
86
+ @documents.delete(uri)
87
+ end
88
+
89
+ # Persist the manifest atomically (temp file + rename) so an interrupted
90
+ # write never leaves a torn file in the CI cache.
91
+ def save
92
+ FileUtils.mkdir_p(File.dirname(@path))
93
+ payload = JSON.generate(
94
+ 'version' => VERSION,
95
+ 'collection_id' => @collection_id,
96
+ 'documents' => @documents
97
+ )
98
+ tmp = "#{@path}.tmp"
99
+ File.write(tmp, payload)
100
+ File.rename(tmp, @path)
101
+ end
102
+
103
+ private
104
+
105
+ # Load the persisted documents, discarding data from a different
106
+ # collection, a different schema version, or an unparseable file.
107
+ # Every discard warns to stderr — the consequence (a full re-push) is
108
+ # expensive enough that operators need to know why it happened.
109
+ #
110
+ # @return [Hash{String=>Hash}] uri => { 'hash' =>, 'document_id' => }
111
+ def load
112
+ return {} unless File.exist?(@path)
113
+
114
+ parsed = JSON.parse(File.read(@path))
115
+ return discard('not a JSON object') unless parsed.is_a?(Hash)
116
+ return discard("schema version #{parsed['version'].inspect}, expected #{VERSION}") unless
117
+ parsed['version'] == VERSION
118
+ return discard("written for collection #{parsed['collection_id'].inspect}, expected #{@collection_id}") unless
119
+ parsed['collection_id'] == @collection_id
120
+
121
+ documents = parsed['documents']
122
+ documents.is_a?(Hash) ? documents : {}
123
+ rescue JSON::ParserError
124
+ discard('unparseable JSON')
125
+ end
126
+
127
+ # @param reason [String] Why the persisted manifest is unusable
128
+ # @return [Hash] empty documents hash (degrades to a full re-push)
129
+ def discard(reason)
130
+ warn "WARNING: discarding sync manifest at #{@path} (#{reason}) — next sync re-pushes all documents"
131
+ {}
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Woods
4
+ module Util
5
+ # Shared host-header / URL-host canonicalization used by {MCP::OriginGuard}
6
+ # and the {Storage::VectorStore::Qdrant} URL validator.
7
+ #
8
+ # Both components need to reject numeric IPv4 notations that `URI` and
9
+ # `getaddrinfo` accept but `IPAddr` does not — hex (`0x7f000001`),
10
+ # bare integer (`2130706433`), octal (`017700000001` or
11
+ # `0177.0.0.1`), short-form (`127.1`), mixed-radix (`0x7f.0.0.1`).
12
+ # Keeping the logic in one place prevents drift between the two
13
+ # defenses (which previously had slightly different regex lists).
14
+ module HostGuard
15
+ # Non-canonical numeric IPv4 forms that legitimate clients never
16
+ # emit but `getaddrinfo` will happily resolve — rejecting the form
17
+ # is safer than trying to intuit the intended IPv4.
18
+ NUMERIC_HOST_BYPASS = Regexp.union(
19
+ /\A0x[0-9a-f]+\z/, # hex: `0x7f000001`
20
+ /\A\d+\z/, # bare integer: `2130706433`
21
+ /\A0[0-7]+\z/, # bare octal: `017700000001`
22
+ /\A\d+\.\d+\z/, # short-form two-part: `127.1`
23
+ /\A\d+\.\d+\.\d+\z/ # short-form three-part: `127.0.1`
24
+ ).freeze
25
+
26
+ # Octets inside a four-part dotted form that tag the form as
27
+ # non-canonical: leading zero (octal interpretation), or `0x`
28
+ # prefix (hex interpretation).
29
+ SUSPICIOUS_OCTET = Regexp.union(
30
+ /\A0\d+\z/, # leading-zero octal: `0177`
31
+ /\A0x[0-9a-f]+\z/ # hex octet: `0x7f`
32
+ ).freeze
33
+
34
+ module_function
35
+
36
+ # Canonicalize a host string: downcase, strip port, strip the
37
+ # FQDN trailing dot, drop IPv6 brackets. Returns a plain host.
38
+ #
39
+ # @param host [String, nil]
40
+ # @return [String] canonical host, lowercase, without port/brackets.
41
+ def canonicalize(host)
42
+ host.to_s.downcase.sub(/:\d+\z/, '').sub(/\.\z/, '').delete('[]')
43
+ end
44
+
45
+ # Does this canonicalized host smuggle a private IP via a notation
46
+ # that `IPAddr.new` won't parse? Callers should reject any match
47
+ # rather than try to resolve it.
48
+ #
49
+ # @param canonical [String] Output of {.canonicalize}.
50
+ # @return [Boolean]
51
+ def suspicious_numeric_host?(canonical)
52
+ return true if canonical.match?(NUMERIC_HOST_BYPASS)
53
+
54
+ four_octet = canonical.match(/\A(\w+)\.(\w+)\.(\w+)\.(\w+)\z/)
55
+ return false unless four_octet
56
+
57
+ four_octet.captures.any? { |octet| octet.match?(SUSPICIOUS_OCTET) }
58
+ end
59
+ end
60
+ end
61
+ end
data/lib/woods/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Woods
4
- VERSION = '1.2.0'
4
+ VERSION = '1.4.0'
5
5
  end
data/lib/woods.rb CHANGED
@@ -31,20 +31,113 @@ module Woods
31
31
 
32
32
  CONFIG_MUTEX = Mutex.new
33
33
 
34
+ # Console MCP secure defaults — Layer 3 column-name redaction.
35
+ #
36
+ # Columns named here are replaced with `[REDACTED]` in every MCP tool
37
+ # response. This list targets credential columns that appear with the
38
+ # same names across Devise, Doorkeeper, Rodauth, has_secure_password,
39
+ # devise-two-factor, OAuth integrations, and hand-rolled auth code.
40
+ #
41
+ # Intentionally omitted because they cause over-redaction in apps that
42
+ # use them for non-secret purposes:
43
+ # - `key` — ActiveStorage blob keys, EAV key columns, translation keys
44
+ # - `name` — universal non-secret identifier
45
+ # - PII columns (`ssn`, `tax_id`, `dob`) — org-specific compliance concern
46
+ #
47
+ # Host apps extend this by reassigning `console_redacted_columns` to
48
+ # `Woods::DEFAULT_CONSOLE_REDACTED_COLUMNS + %w[extra_column]`, or
49
+ # override entirely to remove a default.
50
+ DEFAULT_CONSOLE_REDACTED_COLUMNS = %w[
51
+ password
52
+ password_digest
53
+ password_salt
54
+ encrypted_password
55
+ crypted_password
56
+ salt
57
+ otp_secret
58
+ encrypted_otp_secret
59
+ two_factor_secret
60
+ backup_codes
61
+ consumed_timestep
62
+ reset_password_token
63
+ confirmation_token
64
+ unlock_token
65
+ remember_token
66
+ invitation_token
67
+ access_token
68
+ refresh_token
69
+ auth_token
70
+ api_token
71
+ api_key
72
+ bearer_token
73
+ client_secret
74
+ webhook_secret
75
+ signing_secret
76
+ session_secret
77
+ private_key
78
+ encrypted_private_key
79
+ key_hash
80
+ token
81
+ secret
82
+ ].freeze
83
+
84
+ # Console MCP secure defaults — Layer 1 table-level blocking.
85
+ #
86
+ # Tables named here are rejected outright before any SQL reaches the
87
+ # database. This list targets authentication and credential storage
88
+ # tables that carry secrets or session state across all major auth
89
+ # stacks (Devise, Doorkeeper, Rodauth, has_secure_password, Sorcery,
90
+ # OmniAuth, and hand-rolled token systems).
91
+ #
92
+ # Intentionally omitted to avoid over-blocking benign apps:
93
+ # - `users` / `accounts` — many apps expose safe columns from these
94
+ # tables and operators should decide explicitly.
95
+ # - PII-heavy but auth-unrelated tables (`payments`, `addresses`) —
96
+ # org-specific compliance concern, not a universal default.
97
+ #
98
+ # To keep the defaults and add more tables:
99
+ # Woods.configure { |c| c.console_blocked_tables =
100
+ # Woods::DEFAULT_CONSOLE_BLOCKED_TABLES + %w[extra_table] }
101
+ #
102
+ # To replace the defaults entirely (including removing entries):
103
+ # Woods.configure { |c| c.console_blocked_tables = %w[only_this] }
104
+ #
105
+ # To disable Layer 1 (gate becomes inactive; other layers still apply):
106
+ # Woods.configure { |c| c.console_blocked_tables = [] }
107
+ DEFAULT_CONSOLE_BLOCKED_TABLES = %w[
108
+ sessions
109
+ api_keys
110
+ credentials
111
+ oauth_applications
112
+ oauth_access_tokens
113
+ oauth_refresh_tokens
114
+ identities
115
+ active_storage_blobs
116
+ ].freeze
117
+
34
118
  # ════════════════════════════════════════════════════════════════════════
35
119
  # Configuration
36
120
  # ════════════════════════════════════════════════════════════════════════
37
121
 
38
- class Configuration
122
+ class Configuration # rubocop:disable Metrics/ClassLength
39
123
  attr_accessor :embedding_model, :include_framework_sources, :gem_configs,
40
124
  :vector_store, :metadata_store, :graph_store, :embedding_provider, :log_level,
41
125
  :vector_store_options, :metadata_store_options, :embedding_options,
42
- :concurrent_extraction, :precompute_flows, :enable_snapshots,
43
- :session_tracer_enabled, :session_store, :session_id_proc, :session_exclude_paths,
44
- :console_mcp_enabled, :console_mcp_path, :console_redacted_columns,
126
+ :concurrent_extraction, :precompute_flows, :extract_navigation_edges, :enable_snapshots,
127
+ :session_tracer_enabled, :session_tracer_allow_production,
128
+ :session_store, :session_id_proc, :session_exclude_paths,
129
+ :console_mcp_enabled, :console_mcp_path, :console_mcp_token,
130
+ :console_mcp_allowed_origins,
131
+ :console_redacted_columns,
132
+ :console_redacted_key_values, :console_embedded_read_tools,
133
+ :console_blocked_tables, :console_disabled_scanner_patterns,
134
+ :console_credential_defense_enabled,
135
+ :console_credential_rotation_warning, :console_unsafe_eval_enabled,
136
+ :console_unsafe_eval_confirmation, :console_unsafe_eval_audit_log_path,
45
137
  :notion_api_token, :notion_database_ids,
46
138
  :unblocked_api_token, :unblocked_collection_id, :unblocked_repo_url,
47
- :cache_store, :cache_options
139
+ :cache_store, :cache_options,
140
+ :dump_retention_count
48
141
  attr_reader :max_context_tokens, :similarity_threshold, :extractors, :pretty_json, :context_format,
49
142
  :cache_enabled
50
143
 
@@ -60,15 +153,41 @@ module Woods
60
153
  @pretty_json = true
61
154
  @concurrent_extraction = false
62
155
  @precompute_flows = false
156
+ @extract_navigation_edges = true
63
157
  @enable_snapshots = false
64
158
  @context_format = :markdown
65
159
  @session_tracer_enabled = false
160
+ @session_tracer_allow_production = false
66
161
  @session_store = nil
67
162
  @session_id_proc = nil
68
163
  @session_exclude_paths = []
69
164
  @console_mcp_enabled = false
70
165
  @console_mcp_path = '/mcp/console'
71
- @console_redacted_columns = []
166
+ # Accept token from config or env var. Nil by default — the railtie
167
+ # refuses to wire the middleware in production without a real token
168
+ # and only warns loudly in non-prod when unset.
169
+ @console_mcp_token = ENV.fetch('WOODS_CONSOLE_MCP_TOKEN', nil)
170
+ # Origins allowed to reach the embedded console MCP. Loopback only
171
+ # by default; override in host initializers for tunneled or internal
172
+ # dashboard access.
173
+ @console_mcp_allowed_origins = %w[
174
+ http://localhost
175
+ http://127.0.0.1
176
+ http://[::1]
177
+ ]
178
+ @console_redacted_columns = DEFAULT_CONSOLE_REDACTED_COLUMNS.dup
179
+ @console_redacted_key_values = []
180
+ @console_embedded_read_tools = false
181
+ @console_blocked_tables = DEFAULT_CONSOLE_BLOCKED_TABLES.dup
182
+ @console_disabled_scanner_patterns = []
183
+ @console_credential_defense_enabled = true
184
+ @console_credential_rotation_warning = true
185
+ @console_unsafe_eval_enabled = nil # nil = fall back to env WOODS_CONSOLE_UNSAFE_EVAL
186
+ # Required collaborators when the opt-in is on. Both default to nil;
187
+ # the server refuses to boot with the opt-in set unless the host has
188
+ # wired them (fail-closed — see Server.build_embedded).
189
+ @console_unsafe_eval_confirmation = nil
190
+ @console_unsafe_eval_audit_log_path = nil
72
191
  @notion_api_token = nil
73
192
  @notion_database_ids = {}
74
193
  @unblocked_api_token = nil
@@ -77,6 +196,7 @@ module Woods
77
196
  @cache_enabled = false
78
197
  @cache_store = nil # :redis, :solid_cache, :memory, or a CacheStore instance
79
198
  @cache_options = {} # { redis: client, cache: store, ttl: { embeddings: 86400, ... } }
199
+ @dump_retention_count = 3
80
200
  end
81
201
 
82
202
  # @return [Pathname, String] Output directory, defaulting to Rails.root/tmp/woods
metadata CHANGED
@@ -1,29 +1,63 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: woods
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Leah Armstrong
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-03-27 00:00:00.000000000 Z
11
+ date: 2026-06-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mcp
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 0.9.2
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '1.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 0.9.2
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: msgpack
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '1.5'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '1.5'
47
+ - !ruby/object:Gem::Dependency
48
+ name: prism
15
49
  requirement: !ruby/object:Gem::Requirement
16
50
  requirements:
17
51
  - - "~>"
18
52
  - !ruby/object:Gem::Version
19
- version: '0.6'
53
+ version: '1.4'
20
54
  type: :runtime
21
55
  prerelease: false
22
56
  version_requirements: !ruby/object:Gem::Requirement
23
57
  requirements:
24
58
  - - "~>"
25
59
  - !ruby/object:Gem::Version
26
- version: '0.6'
60
+ version: '1.4'
27
61
  - !ruby/object:Gem::Dependency
28
62
  name: railties
29
63
  requirement: !ruby/object:Gem::Requirement
@@ -69,6 +103,7 @@ files:
69
103
  - lib/generators/woods/pgvector_generator.rb
70
104
  - lib/generators/woods/templates/add_pgvector_to_woods.rb.erb
71
105
  - lib/generators/woods/templates/create_woods_tables.rb.erb
106
+ - lib/generators/woods/templates/woods.rb.tt
72
107
  - lib/tasks/woods.rake
73
108
  - lib/tasks/woods_evaluation.rake
74
109
  - lib/woods.rb
@@ -91,15 +126,27 @@ files:
91
126
  - lib/woods/console/adapters/solid_queue_adapter.rb
92
127
  - lib/woods/console/audit_logger.rb
93
128
  - lib/woods/console/bridge.rb
129
+ - lib/woods/console/bridge_protocol.rb
94
130
  - lib/woods/console/confirmation.rb
95
131
  - lib/woods/console/connection_manager.rb
96
132
  - lib/woods/console/console_response_renderer.rb
133
+ - lib/woods/console/credential_index.rb
134
+ - lib/woods/console/credential_scanner.rb
135
+ - lib/woods/console/dispatch_pipeline.rb
97
136
  - lib/woods/console/embedded_executor.rb
137
+ - lib/woods/console/eval_guard.rb
98
138
  - lib/woods/console/model_validator.rb
99
139
  - lib/woods/console/rack_middleware.rb
140
+ - lib/woods/console/redactor.rb
141
+ - lib/woods/console/response_context.rb
100
142
  - lib/woods/console/safe_context.rb
143
+ - lib/woods/console/scope_predicate_parser.rb
101
144
  - lib/woods/console/server.rb
145
+ - lib/woods/console/sql_noise_stripper.rb
146
+ - lib/woods/console/sql_table_scanner.rb
102
147
  - lib/woods/console/sql_validator.rb
148
+ - lib/woods/console/table_gate.rb
149
+ - lib/woods/console/tool_specs.rb
103
150
  - lib/woods/console/tools/tier1.rb
104
151
  - lib/woods/console/tools/tier2.rb
105
152
  - lib/woods/console/tools/tier3.rb
@@ -123,6 +170,7 @@ files:
123
170
  - lib/woods/embedding/openai.rb
124
171
  - lib/woods/embedding/provider.rb
125
172
  - lib/woods/embedding/text_preparer.rb
173
+ - lib/woods/embedding/token_counter.rb
126
174
  - lib/woods/evaluation/baseline_runner.rb
127
175
  - lib/woods/evaluation/evaluator.rb
128
176
  - lib/woods/evaluation/metrics.rb
@@ -159,6 +207,7 @@ files:
159
207
  - lib/woods/extractors/rails_source_extractor.rb
160
208
  - lib/woods/extractors/rake_task_extractor.rb
161
209
  - lib/woods/extractors/route_extractor.rb
210
+ - lib/woods/extractors/route_helper_resolver.rb
162
211
  - lib/woods/extractors/scheduled_job_extractor.rb
163
212
  - lib/woods/extractors/serializer_extractor.rb
164
213
  - lib/woods/extractors/service_extractor.rb
@@ -168,6 +217,8 @@ files:
168
217
  - lib/woods/extractors/test_mapping_extractor.rb
169
218
  - lib/woods/extractors/validator_extractor.rb
170
219
  - lib/woods/extractors/view_component_extractor.rb
220
+ - lib/woods/extractors/view_engines/base.rb
221
+ - lib/woods/extractors/view_engines/erb.rb
171
222
  - lib/woods/extractors/view_template_extractor.rb
172
223
  - lib/woods/feedback/gap_detector.rb
173
224
  - lib/woods/feedback/store.rb
@@ -183,8 +234,15 @@ files:
183
234
  - lib/woods/formatting/gpt_adapter.rb
184
235
  - lib/woods/formatting/human_adapter.rb
185
236
  - lib/woods/graph_analyzer.rb
237
+ - lib/woods/index_artifact.rb
238
+ - lib/woods/mcp/bearer_auth.rb
239
+ - lib/woods/mcp/bootstrap_state.rb
186
240
  - lib/woods/mcp/bootstrapper.rb
241
+ - lib/woods/mcp/config_resolver.rb
242
+ - lib/woods/mcp/errors.rb
187
243
  - lib/woods/mcp/index_reader.rb
244
+ - lib/woods/mcp/origin_guard.rb
245
+ - lib/woods/mcp/provider_probe.rb
188
246
  - lib/woods/mcp/renderers/claude_renderer.rb
189
247
  - lib/woods/mcp/renderers/json_renderer.rb
190
248
  - lib/woods/mcp/renderers/markdown_renderer.rb
@@ -210,6 +268,7 @@ files:
210
268
  - lib/woods/resilience/circuit_breaker.rb
211
269
  - lib/woods/resilience/index_validator.rb
212
270
  - lib/woods/resilience/retryable_provider.rb
271
+ - lib/woods/resolved_config.rb
213
272
  - lib/woods/retrieval/context_assembler.rb
214
273
  - lib/woods/retrieval/query_classifier.rb
215
274
  - lib/woods/retrieval/ranker.rb
@@ -230,10 +289,15 @@ files:
230
289
  - lib/woods/session_tracer/solid_cache_store.rb
231
290
  - lib/woods/session_tracer/store.rb
232
291
  - lib/woods/storage/graph_store.rb
292
+ - lib/woods/storage/inapplicable_backend.rb
233
293
  - lib/woods/storage/metadata_store.rb
234
294
  - lib/woods/storage/pgvector.rb
235
295
  - lib/woods/storage/qdrant.rb
296
+ - lib/woods/storage/snapshotter.rb
297
+ - lib/woods/storage/snapshotter/metadata.rb
298
+ - lib/woods/storage/snapshotter/vector.rb
236
299
  - lib/woods/storage/vector_store.rb
300
+ - lib/woods/tasks.rb
237
301
  - lib/woods/temporal/json_snapshot_store.rb
238
302
  - lib/woods/temporal/snapshot_store.rb
239
303
  - lib/woods/token_utils.rb
@@ -241,6 +305,8 @@ files:
241
305
  - lib/woods/unblocked/document_builder.rb
242
306
  - lib/woods/unblocked/exporter.rb
243
307
  - lib/woods/unblocked/rate_limiter.rb
308
+ - lib/woods/unblocked/sync_manifest.rb
309
+ - lib/woods/util/host_guard.rb
244
310
  - lib/woods/version.rb
245
311
  homepage: https://github.com/lost-in-the/woods
246
312
  licenses: