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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +229 -0
- data/README.md +24 -8
- data/exe/woods-console +51 -6
- data/exe/woods-console-mcp +24 -4
- data/exe/woods-mcp +30 -7
- data/exe/woods-mcp-http +47 -6
- data/lib/generators/woods/install_generator.rb +13 -4
- data/lib/generators/woods/templates/woods.rb.tt +155 -0
- data/lib/tasks/woods.rake +37 -51
- data/lib/woods/builder.rb +174 -9
- data/lib/woods/cache/cache_middleware.rb +360 -31
- data/lib/woods/chunking/semantic_chunker.rb +334 -7
- data/lib/woods/console/adapters/job_adapter.rb +10 -4
- data/lib/woods/console/audit_logger.rb +76 -4
- data/lib/woods/console/bridge.rb +48 -15
- data/lib/woods/console/bridge_protocol.rb +44 -0
- data/lib/woods/console/confirmation.rb +3 -4
- data/lib/woods/console/console_response_renderer.rb +56 -18
- data/lib/woods/console/credential_index.rb +201 -0
- data/lib/woods/console/credential_scanner.rb +302 -0
- data/lib/woods/console/dispatch_pipeline.rb +138 -0
- data/lib/woods/console/embedded_executor.rb +682 -35
- data/lib/woods/console/eval_guard.rb +319 -0
- data/lib/woods/console/model_validator.rb +1 -3
- data/lib/woods/console/rack_middleware.rb +185 -29
- data/lib/woods/console/redactor.rb +161 -0
- data/lib/woods/console/response_context.rb +127 -0
- data/lib/woods/console/safe_context.rb +220 -23
- data/lib/woods/console/scope_predicate_parser.rb +131 -0
- data/lib/woods/console/server.rb +417 -486
- data/lib/woods/console/sql_noise_stripper.rb +87 -0
- data/lib/woods/console/sql_table_scanner.rb +213 -0
- data/lib/woods/console/sql_validator.rb +81 -31
- data/lib/woods/console/table_gate.rb +93 -0
- data/lib/woods/console/tool_specs.rb +552 -0
- data/lib/woods/console/tools/tier1.rb +3 -3
- data/lib/woods/console/tools/tier4.rb +7 -1
- data/lib/woods/dependency_graph.rb +66 -7
- data/lib/woods/embedding/indexer.rb +190 -6
- data/lib/woods/embedding/openai.rb +40 -4
- data/lib/woods/embedding/provider.rb +104 -8
- data/lib/woods/embedding/text_preparer.rb +23 -3
- data/lib/woods/embedding/token_counter.rb +133 -0
- data/lib/woods/evaluation/baseline_runner.rb +20 -2
- data/lib/woods/evaluation/metrics.rb +4 -1
- data/lib/woods/extracted_unit.rb +1 -0
- data/lib/woods/extractor.rb +7 -1
- data/lib/woods/extractors/controller_extractor.rb +10 -4
- data/lib/woods/extractors/mailer_extractor.rb +16 -2
- data/lib/woods/extractors/model_extractor.rb +6 -1
- data/lib/woods/extractors/phlex_extractor.rb +13 -4
- data/lib/woods/extractors/rails_source_extractor.rb +2 -0
- data/lib/woods/extractors/route_helper_resolver.rb +130 -0
- data/lib/woods/extractors/shared_dependency_scanner.rb +130 -2
- data/lib/woods/extractors/view_component_extractor.rb +12 -1
- data/lib/woods/extractors/view_engines/base.rb +141 -0
- data/lib/woods/extractors/view_engines/erb.rb +145 -0
- data/lib/woods/extractors/view_template_extractor.rb +92 -133
- data/lib/woods/flow_assembler.rb +23 -15
- data/lib/woods/flow_precomputer.rb +21 -2
- data/lib/woods/graph_analyzer.rb +3 -4
- data/lib/woods/index_artifact.rb +173 -0
- data/lib/woods/mcp/bearer_auth.rb +45 -0
- data/lib/woods/mcp/bootstrap_state.rb +94 -0
- data/lib/woods/mcp/bootstrapper.rb +337 -16
- data/lib/woods/mcp/config_resolver.rb +288 -0
- data/lib/woods/mcp/errors.rb +134 -0
- data/lib/woods/mcp/index_reader.rb +265 -30
- data/lib/woods/mcp/origin_guard.rb +132 -0
- data/lib/woods/mcp/provider_probe.rb +166 -0
- data/lib/woods/mcp/renderers/claude_renderer.rb +6 -0
- data/lib/woods/mcp/renderers/markdown_renderer.rb +39 -3
- data/lib/woods/mcp/renderers/plain_renderer.rb +16 -2
- data/lib/woods/mcp/server.rb +737 -137
- data/lib/woods/model_name_cache.rb +78 -2
- data/lib/woods/notion/client.rb +25 -2
- data/lib/woods/notion/mappers/model_mapper.rb +36 -2
- data/lib/woods/railtie.rb +55 -15
- data/lib/woods/resilience/circuit_breaker.rb +9 -2
- data/lib/woods/resilience/retryable_provider.rb +40 -3
- data/lib/woods/resolved_config.rb +299 -0
- data/lib/woods/retrieval/context_assembler.rb +112 -5
- data/lib/woods/retrieval/query_classifier.rb +1 -1
- data/lib/woods/retrieval/ranker.rb +55 -6
- data/lib/woods/retrieval/search_executor.rb +42 -13
- data/lib/woods/retriever.rb +330 -24
- data/lib/woods/session_tracer/middleware.rb +35 -1
- data/lib/woods/storage/graph_store.rb +39 -0
- data/lib/woods/storage/inapplicable_backend.rb +14 -0
- data/lib/woods/storage/metadata_store.rb +129 -1
- data/lib/woods/storage/pgvector.rb +70 -8
- data/lib/woods/storage/qdrant.rb +196 -5
- data/lib/woods/storage/snapshotter/metadata.rb +172 -0
- data/lib/woods/storage/snapshotter/vector.rb +238 -0
- data/lib/woods/storage/snapshotter.rb +24 -0
- data/lib/woods/storage/vector_store.rb +184 -35
- data/lib/woods/tasks.rb +85 -0
- data/lib/woods/temporal/snapshot_store.rb +49 -1
- data/lib/woods/token_utils.rb +44 -5
- data/lib/woods/unblocked/client.rb +88 -7
- data/lib/woods/unblocked/document_builder.rb +75 -36
- data/lib/woods/unblocked/exporter.rb +234 -18
- data/lib/woods/unblocked/rate_limiter.rb +10 -2
- data/lib/woods/unblocked/sync_manifest.rb +135 -0
- data/lib/woods/util/host_guard.rb +61 -0
- data/lib/woods/version.rb +1 -1
- data/lib/woods.rb +126 -6
- 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
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, :
|
|
44
|
-
:
|
|
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
|
-
|
|
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.
|
|
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-
|
|
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: '
|
|
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: '
|
|
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:
|