nous 0.4.0 → 0.5.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/.agents/skills/verify-nous/SKILL.md +6 -0
- data/CHANGELOG.md +23 -1
- data/CODING_STANDARDS.md +203 -0
- data/README.md +104 -15
- data/Rakefile +2 -0
- data/docs/setup/defuddle.md +38 -0
- data/docs/setup/verification.md +21 -0
- data/docs/verification/README.md +77 -0
- data/docs/verification/defuddle.md +45 -0
- data/docs/verification/failure-propagation.md +37 -0
- data/docs/verification/retained-html.md +37 -0
- data/javascript/build.mjs +24 -0
- data/javascript/defuddle.mjs +28 -0
- data/lib/nous/cli.rb +3 -3
- data/lib/nous/crawler/recursive_page_fetcher.rb +25 -17
- data/lib/nous/decode_html.rb +80 -0
- data/lib/nous/extract_html.rb +109 -0
- data/lib/nous/extractor/default.rb +0 -2
- data/lib/nous/extractor/defuddle/client.rb +57 -0
- data/lib/nous/extractor/defuddle/run_process.rb +104 -0
- data/lib/nous/extractor/defuddle.rb +23 -0
- data/lib/nous/extractor/extraction_error.rb +7 -0
- data/lib/nous/fetcher/extraction_runner.rb +24 -9
- data/lib/nous/fetcher/page_extractor.rb +15 -6
- data/lib/nous/fetcher.rb +35 -25
- data/lib/nous/version.rb +1 -1
- data/lib/nous.rb +1 -1
- data/package-lock.json +858 -0
- data/package.json +15 -0
- data/tasks/verification.rake +23 -0
- data/vendor/THIRD_PARTY_LICENSES.txt +518 -0
- data/vendor/defuddle.mjs +403 -0
- data/vendor/defuddle.mjs.LEGAL.txt +10 -0
- metadata +23 -3
- data/lib/nous/extractor/jina/client.rb +0 -59
- data/lib/nous/extractor/jina.rb +0 -26
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e4371689843d5f5275e56afaaab2630ba2a703e57e29cfb79d6d47d19040d934
|
|
4
|
+
data.tar.gz: 1402e3ab54931a99bbad484002345514142a9ade10453938c8f2feae77ef7742
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 14a0487839b29fa46c277e341e4c33b631bb22380cbb2ee91954fcff61f264856baf981f2bc8a6e20b6da3e8a81c7c26c7178db5f96d96f5be0e2a53676d72f6
|
|
7
|
+
data.tar.gz: 8e2eeb9b3e790ea85f79d877c19176569918e3030fff096950c6de7771e3ebe9556cdeb82b16a67cb8d9c6ea166c6634c3fec6c40ea8f72ffa9168b0c4fa9370
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: verify-nous
|
|
3
|
+
description: Verify Nous retained-HTML extraction and detailed fetch outcomes through offline Ruby recipes; use when changing extraction or assessing Substrate and Autodidact readiness.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Read [the verification guide](../../../docs/verification/README.md) from the repository root. Select the affected recipe, execute launch, doctor, drive, evidence capture and cleanup, and report every exit status. Follow the guide's source-identification and blocker rules. A draft or failed recipe does not certify readiness. Keep provisioning in the linked setup guide; do not add live network calls to these checks.
|
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,26 @@
|
|
|
1
|
-
## [
|
|
1
|
+
## [0.5.0] - 2026-10-25
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
- `Nous::ExtractHtml.call` extracts retained HTML without network acquisition, with
|
|
6
|
+
Markdown or plain text, source metadata, explicit decoding errors and caller byte limits.
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
|
|
10
|
+
- Defuddle is the default for URL and retained-HTML extraction. The gem bundles
|
|
11
|
+
its JavaScript dependencies and requires Node.js 22+ on macOS/Linux.
|
|
12
|
+
- Readability remains available through `Extractor::Default` and `--readability`.
|
|
13
|
+
Extraction failures do not silently switch backends.
|
|
14
|
+
|
|
15
|
+
### Removed
|
|
16
|
+
|
|
17
|
+
- Jina Reader integration (`Extractor::Jina`, `--jina`, `JINA_API_KEY`). Neither
|
|
18
|
+
remaining backend renders JavaScript pages.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- Detailed fetch results retain extraction failures and all attempted failures from
|
|
23
|
+
concurrent crawl batches, with accurate request counts and unchanged page limits.
|
|
2
24
|
|
|
3
25
|
## [0.4.0] - 2026-04-11
|
|
4
26
|
|
data/CODING_STANDARDS.md
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# Coding standards
|
|
2
|
+
|
|
3
|
+
Read these standards before implementation and include them in Anaphor's Standards
|
|
4
|
+
review. They apply to this Ruby gem and its development tools. Existing code is not
|
|
5
|
+
a blanket exception: when changing an affected boundary, address deviations or
|
|
6
|
+
record a concrete compatibility reason in the PR. This document does not itself
|
|
7
|
+
change existing public APIs.
|
|
8
|
+
|
|
9
|
+
## Responsibilities and service objects
|
|
10
|
+
|
|
11
|
+
Keep crawling, readable-content extraction, conversion, and serialization distinct.
|
|
12
|
+
Commands perform operations; fetch records describe acquired originals; pages describe
|
|
13
|
+
extracted content and provenance. Keep HTTP details in crawler and remote extractor
|
|
14
|
+
adapters. Follow the existing Zeitwerk layout; `lib/nous/primitives/` is collapsed
|
|
15
|
+
into the `Nous` namespace.
|
|
16
|
+
|
|
17
|
+
A command exposes one public instance operation, `call`, invoked through inherited
|
|
18
|
+
`.call`. Its initializer accepts explicit inputs and dependencies and stores state;
|
|
19
|
+
construction must not launch processes, perform network requests, or do business
|
|
20
|
+
work. Resolve environment-dependent defaults when the operation runs.
|
|
21
|
+
|
|
22
|
+
Application-service instance `call` methods that describe business operations must
|
|
23
|
+
contain only bareword calls to private methods named for business functions. Read
|
|
24
|
+
them as prose: one named step per line, without arguments, explicit receivers,
|
|
25
|
+
assignments, conditionals, loops, blocks, or explicit returns. A `rescue` clause
|
|
26
|
+
is the only exception; it may map an expected exception to a failure result.
|
|
27
|
+
Put decisions, resource scopes, collaborator calls, and result construction inside
|
|
28
|
+
the private steps. The final step returns the service result. For example:
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
def call
|
|
32
|
+
prepare_retained_content
|
|
33
|
+
extract_readable_content
|
|
34
|
+
build_extraction_result
|
|
35
|
+
rescue ArgumentError => error
|
|
36
|
+
failure(error)
|
|
37
|
+
end
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
A failed prerequisite must stop the sequence: raise a meaningful exception handled
|
|
41
|
+
by the service or the existing command boundary. Returning a failure from a private
|
|
42
|
+
step alone does not stop later steps. Keep resource acquisition, use and cleanup
|
|
43
|
+
in one private scope rather than spreading temporary paths across orchestration
|
|
44
|
+
steps. Do not add instance state merely to split an indivisible operation.
|
|
45
|
+
|
|
46
|
+
This rule governs business-operation services, including extraction workflows. The shared class-level dispatcher and low-level value/protocol helpers
|
|
47
|
+
are not business orchestration. Apply the rule when changing an existing service;
|
|
48
|
+
this change does not claim that all historical commands have been migrated.
|
|
49
|
+
|
|
50
|
+
Before extracting an object, name the policy, resource, or collaborator contract
|
|
51
|
+
it will own. If no separate owner exists, keep the steps private in the current
|
|
52
|
+
service. Do not extract forwarding layers to satisfy a line count: they add places
|
|
53
|
+
to navigate without reducing the caller's obligations. Name an extraction for its
|
|
54
|
+
operation, not Manager, Processor, or Utils. Keep duplication when sharing would
|
|
55
|
+
require unrelated behavior flags; those flags couple operations that change apart.
|
|
56
|
+
|
|
57
|
+
Use Sandi Metz's five-line methods, 100-line classes, and four-parameter guideline
|
|
58
|
+
as review prompts, not mechanical limits. Count independent keyword options as
|
|
59
|
+
parameters. A growing constructor suggests mixed responsibilities; hiding those
|
|
60
|
+
options in a hash does not fix that. Introduce an input object only when its data
|
|
61
|
+
has cohesive behavior or invariants. Explain justified exceptions locally in the PR.
|
|
62
|
+
|
|
63
|
+
Apply tell, don't ask: put an operation with the object that owns its behavior.
|
|
64
|
+
Use a known collaborator's interface instead of branching on `is_a?`, `kind_of?`,
|
|
65
|
+
or `respond_to?`. At an untrusted provider boundary, validate the actual protocol
|
|
66
|
+
and required data before producing normalized content. Do not spread speculative
|
|
67
|
+
type checks through in-process callers.
|
|
68
|
+
|
|
69
|
+
## Building and configuring objects
|
|
70
|
+
|
|
71
|
+
Prefer `tap` when configuring and returning the same object, and
|
|
72
|
+
`each_with_object` when accumulating into a mutable collection, instead of a
|
|
73
|
+
temporary local used only to initialize, mutate, and return that object. This
|
|
74
|
+
keeps construction and its result in one expression. Use `map` for one-to-one
|
|
75
|
+
transformations; keep named locals when they explain an intermediate result or
|
|
76
|
+
are reused. With `each_with_object`, mutate the accumulator rather than reassign
|
|
77
|
+
the block variable. These blocks belong inside private service steps, preserving
|
|
78
|
+
the bareword-only business `call` convention.
|
|
79
|
+
|
|
80
|
+
## Arguments and defaults
|
|
81
|
+
|
|
82
|
+
For each proposed argument, inspect its callers and choose in this order:
|
|
83
|
+
|
|
84
|
+
1. If no caller or accepted use case needs the choice, do not add the argument.
|
|
85
|
+
A test convenience alone does not justify expanding the public API.
|
|
86
|
+
2. If every caller must supply a value, require it.
|
|
87
|
+
3. If omission selects a normal value, give it a concrete default or default
|
|
88
|
+
expression. Do not use `nil` merely to replace it with that value later.
|
|
89
|
+
4. If absence changes the operation, document it and permit `nil`. For example,
|
|
90
|
+
`selector: nil` leaves extraction unscoped.
|
|
91
|
+
5. If automatic selection differs from an explicit override, represent that mode
|
|
92
|
+
explicitly and test both paths. Discover environment dependencies during `call`,
|
|
93
|
+
not construction, so creating the service does not launch work.
|
|
94
|
+
|
|
95
|
+
For an existing argument, record the behavior of omission, explicit `nil`, and
|
|
96
|
+
explicit values before editing. Preserve supported cases and test the changed
|
|
97
|
+
boundary. Do not silently make a strict executable override fall back to another
|
|
98
|
+
installation. Add a sentinel or null object only when those behaviors must differ;
|
|
99
|
+
otherwise it adds another state every caller must understand.
|
|
100
|
+
|
|
101
|
+
## Results, failures, and resources
|
|
102
|
+
|
|
103
|
+
Use the existing `Nous::Command` and `Nous::Command::Result` contract in
|
|
104
|
+
[lib/nous/command.rb](lib/nous/command.rb): commands return `payload`, `error`, and
|
|
105
|
+
`metadata`; callers inspect `success?` or `failure?`. Extractors expose `extract(record)`
|
|
106
|
+
and return the same result contract; do not rename that protocol as a style change. Do not copy Rails `ApplicationService`, named
|
|
107
|
+
result fields, HTTP error codes, or reporting infrastructure into this gem.
|
|
108
|
+
|
|
109
|
+
The inherited class `.call` currently converts `StandardError` into a failure
|
|
110
|
+
result, wrapping post-construction errors as `CommandError`. Preserve that behavior
|
|
111
|
+
unless an explicit API change is accepted. Direct instance `call` and extractor
|
|
112
|
+
`extract` do not receive that dispatcher protection. A failed child command does not
|
|
113
|
+
short-circuit its parent automatically: inspect its result and stop before any
|
|
114
|
+
step that depends on success. Keep stage-specific errors useful without including
|
|
115
|
+
credentials or sensitive source content. Do not turn a failure into empty success.
|
|
116
|
+
|
|
117
|
+
One owner acquires, uses, and releases each temporary resource. Keep cleanup in a
|
|
118
|
+
block or `ensure` that covers success and failure, including parsing. Do not return
|
|
119
|
+
a temporary path after its owner has released it. Never use the original input as
|
|
120
|
+
an output destination. Invoke subprocesses with argument arrays, not interpolated
|
|
121
|
+
shell strings. Make partial external effects explicit; cleanup cannot undo them all.
|
|
122
|
+
|
|
123
|
+
## Development and verification commands
|
|
124
|
+
|
|
125
|
+
Choose the command's owner before adding a file:
|
|
126
|
+
|
|
127
|
+
1. For a repeatable project check, add a named Rake task. Put live checks under
|
|
128
|
+
`verify:` and document inputs in the owning recipe so agents can discover them
|
|
129
|
+
with `bundle exec rake -T`. Create these directories when a check needs them.
|
|
130
|
+
2. Put verification assertions, fixture preparation, and cleanup in a focused
|
|
131
|
+
harness under `spec/verification/`. Keep `tasks/*.rake` limited to task inputs
|
|
132
|
+
and dispatch, so command plumbing does not obscure what the check proves.
|
|
133
|
+
3. Use `bin/` for setup and console; the existing public command lives in `exe/nous`. Do not
|
|
134
|
+
add a generic scripts directory for verification commands; it creates a second
|
|
135
|
+
entry-point convention to discover and maintain.
|
|
136
|
+
|
|
137
|
+
Keep production behavior in the library and exercise its public API from the
|
|
138
|
+
harness. Never add downloads or live provider calls to default `bundle exec rake`;
|
|
139
|
+
ordinary verification must run without external services or model provisioning.
|
|
140
|
+
|
|
141
|
+
Document required binaries, models, credentials, supported runtime versions, and
|
|
142
|
+
readiness checks in `docs/setup/`. Keep executable verification steps and evidence
|
|
143
|
+
limits in `docs/verification/`. Link both from onboarding. Install dependencies through explicit setup
|
|
144
|
+
steps, never silently during a check; readiness and behavior verification are separate results. Retain evidence with
|
|
145
|
+
the tested source revision; never claim that simulated processes prove a local
|
|
146
|
+
runtime works.
|
|
147
|
+
|
|
148
|
+
## Tests and review
|
|
149
|
+
|
|
150
|
+
Test observable outcomes through command `.call` or the owning source interface.
|
|
151
|
+
Do not assert private helper order. When a workflow has prerequisites, cover failure before dependent steps. When it
|
|
152
|
+
owns files or processes, cover cleanup and input preservation on success and
|
|
153
|
+
failure. When calls can overlap, prove their resources stay isolated. Use
|
|
154
|
+
controlled provider/process fixtures for ordinary tests and separate opt-in live
|
|
155
|
+
checks for actual compatibility. Keep fixtures' provenance and limitations visible.
|
|
156
|
+
|
|
157
|
+
For Ruby changes, run affected specs, the full RSpec suite, and Standard using
|
|
158
|
+
the Development commands in [README.md](README.md). Include an affected verification
|
|
159
|
+
recipe once one exists. Documentation-only changes need
|
|
160
|
+
link, command, and contract checks; they do not require pretending runtime behavior
|
|
161
|
+
changed. Anaphor reviews must distinguish documented rules, design heuristics, and
|
|
162
|
+
accepted requirements. A green suite does not establish compliance with every
|
|
163
|
+
principle or prove live-provider behavior.
|
|
164
|
+
|
|
165
|
+
## Retained HTML convention
|
|
166
|
+
|
|
167
|
+
`ExtractHtml` keeps six explicit inputs: original bytes, source URL, content type,
|
|
168
|
+
output representation and two independent caller budgets. This is a deliberate
|
|
169
|
+
exception to the parameter heuristic: a hash or options object without cohesive
|
|
170
|
+
behavior would only hide the same choices. The command orchestrates decoding, readable
|
|
171
|
+
selection, rendering and bounded output. `DecodeHtml` owns the byte interpretation
|
|
172
|
+
policy shared with acquired Defuddle HTML. Within `ExtractHtml`, invoke `DecodeHtml`
|
|
173
|
+
and `Defuddle::Client` with `new(...).call`: this deliberate exception to inherited
|
|
174
|
+
`.call` preserves the original unexpected exception as `UnexpectedFailure#cause`.
|
|
175
|
+
The shared dispatcher wraps those exceptions and loses their identity. Keep this
|
|
176
|
+
exception local to the retained command's diagnostic contract and cover failures
|
|
177
|
+
during dependency execution. Trust the client's normalized title and Markdown;
|
|
178
|
+
the retained command owns final rendering, emptiness and combined-byte budgets.
|
|
179
|
+
Legacy shared extractors must
|
|
180
|
+
not acquire per-record mutable state: the fetch runner calls them concurrently.
|
|
181
|
+
|
|
182
|
+
## Sources and adaptation
|
|
183
|
+
|
|
184
|
+
Adapted from Substrate's `CODING_STANDARDS.md` on October 25, 2026, which was
|
|
185
|
+
adapted from Autodidact's standards. Nous-specific responsibilities, result names,
|
|
186
|
+
extractor protocol, executable locations, and verification commands replace their
|
|
187
|
+
Substrate equivalents. Local audio conventions do not apply to Nous.
|
|
188
|
+
Retained service orchestration, explicit inputs, failure sequencing, ownership,
|
|
189
|
+
and public-interface testing. Rails controllers, Active Record, transactions,
|
|
190
|
+
Jbuilder, Sentry, Next.js, and that application's result shape are outside this
|
|
191
|
+
project's conventions. The source document provides context, not inherited
|
|
192
|
+
instructions to provision those systems.
|
|
193
|
+
|
|
194
|
+
- [Thoughtbot: Tell, Don't Ask](https://thoughtbot.com/blog/tell-dont-ask)
|
|
195
|
+
informs behavior ownership and collaborator interfaces.
|
|
196
|
+
- [Thoughtbot: Sandi Metz' Rules for Developers](https://thoughtbot.com/blog/sandi-metz-rules-for-developers)
|
|
197
|
+
supplies the size and parameter heuristics; their use as review prompts here is
|
|
198
|
+
a local convention.
|
|
199
|
+
- [Sandi Metz: The Wrong Abstraction](https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction)
|
|
200
|
+
informs the preference for cohesive objects over premature generalization.
|
|
201
|
+
|
|
202
|
+
Nullable-default policy and Rake placement are project decisions, not rules
|
|
203
|
+
attributed to these authors.
|
data/README.md
CHANGED
|
@@ -2,10 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
Crawl websites and extract readable Markdown, optimized for LLM consumption. Inspired by [sitefetch](https://github.com/egoist/sitefetch).
|
|
4
4
|
|
|
5
|
-
Nous fetches same-host pages starting from a seed URL, extracts readable content, and outputs clean Markdown as XML-tagged text or JSON. It supports concurrent crawling, glob-based URL filtering, and
|
|
5
|
+
Nous fetches same-host pages starting from a seed URL, extracts readable content, and outputs clean Markdown as XML-tagged text or JSON. It supports concurrent crawling, glob-based URL filtering, and local extraction with Defuddle by default and an explicit ruby-readability backup. Both parse acquired HTML without rendering JavaScript. Defuddle decodes acquired bytes using the same header/BOM/meta/UTF-8 precedence as retained HTML; invalid bytes produce extraction failures.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
9
|
+
Default extraction requires Node.js 22+ on PATH (macOS/Linux). The gem includes
|
|
10
|
+
the bundled Defuddle adapter; npm is only needed to rebuild it during development.
|
|
11
|
+
See [Defuddle setup](docs/setup/defuddle.md).
|
|
12
|
+
|
|
9
13
|
Add to your Gemfile:
|
|
10
14
|
|
|
11
15
|
```ruby
|
|
@@ -39,8 +43,8 @@ nous https://example.com -m "/blog/*"
|
|
|
39
43
|
# Scope extraction to a CSS selector
|
|
40
44
|
nous https://example.com -s "article.post"
|
|
41
45
|
|
|
42
|
-
# Use
|
|
43
|
-
nous https://example.com --
|
|
46
|
+
# Use the Ruby-only Readability backup
|
|
47
|
+
nous https://example.com --readability
|
|
44
48
|
|
|
45
49
|
# Debug logging
|
|
46
50
|
nous https://example.com -d
|
|
@@ -57,7 +61,7 @@ nous https://example.com -d
|
|
|
57
61
|
| `-s`, `--selector SELECTOR` | CSS selector to scope extraction | none |
|
|
58
62
|
| `-l`, `--limit N` | Maximum pages to fetch | `100` |
|
|
59
63
|
| `--timeout N` | Per-request timeout in seconds | `15` |
|
|
60
|
-
| `--
|
|
64
|
+
| `--readability` | Use the Ruby-only Readability backup | off |
|
|
61
65
|
| `-v`, `--version` | Print version and exit | off |
|
|
62
66
|
| `-h`, `--help` | Print usage and exit | off |
|
|
63
67
|
| `-d`, `--debug` | Debug logging to stderr | off |
|
|
@@ -84,9 +88,9 @@ text = Nous.serialize(pages, format: :text)
|
|
|
84
88
|
# Serialize to JSON
|
|
85
89
|
json = Nous.serialize(pages, format: :json)
|
|
86
90
|
|
|
87
|
-
#
|
|
88
|
-
pages = Nous.fetch("https://
|
|
89
|
-
extractor: Nous::Extractor::
|
|
91
|
+
# Select the Readability backup explicitly
|
|
92
|
+
pages = Nous.fetch("https://example.com",
|
|
93
|
+
extractor: Nous::Extractor::Default.new,
|
|
90
94
|
limit: 5
|
|
91
95
|
)
|
|
92
96
|
```
|
|
@@ -99,12 +103,20 @@ Use the `details: true` option to receive full fetch results including failures:
|
|
|
99
103
|
result = Nous.fetch("https://example.com", details: true)
|
|
100
104
|
|
|
101
105
|
result.pages # Array<Nous::Page> - successfully extracted pages
|
|
102
|
-
result.failures # Array<{requested_url:, error:}> - failed
|
|
106
|
+
result.failures # Array<{requested_url:, error:}> - failed acquisition or extraction
|
|
103
107
|
result.total_requested # Integer - total URLs attempted
|
|
104
108
|
result.all_succeeded? # Boolean - true if no failures
|
|
105
109
|
result.any_succeeded? # Boolean - true if at least one page extracted
|
|
106
110
|
```
|
|
107
111
|
|
|
112
|
+
Acquisition failures retain their existing messages; extraction failures retain their
|
|
113
|
+
exception objects. Detailed results include failed recursive requests as well as
|
|
114
|
+
failed extraction. Pages-only calls still return successful pages in source order.
|
|
115
|
+
A concurrent batch may already have attempted more successful pages than the limit
|
|
116
|
+
allows returning. `total_requested` counts every attempted request in that batch;
|
|
117
|
+
failures remain visible, while excess successful pages are omitted from `pages`.
|
|
118
|
+
The limit still governs retained successful pages, not failed attempts.
|
|
119
|
+
|
|
108
120
|
This is useful when you need to handle failures explicitly:
|
|
109
121
|
|
|
110
122
|
```ruby
|
|
@@ -122,13 +134,69 @@ result.pages.each do |page|
|
|
|
122
134
|
end
|
|
123
135
|
```
|
|
124
136
|
|
|
137
|
+
### Retained HTML extraction
|
|
138
|
+
|
|
139
|
+
Use saved bytes directly, without crawling or reacquiring a URL:
|
|
140
|
+
|
|
141
|
+
```ruby
|
|
142
|
+
result = Nous::ExtractHtml.call(
|
|
143
|
+
html: File.binread("saved.html"),
|
|
144
|
+
source_url: "https://example.com/article",
|
|
145
|
+
content_type: "text/html; charset=utf-8",
|
|
146
|
+
format: :text
|
|
147
|
+
)
|
|
148
|
+
raise result.error if result.failure?
|
|
149
|
+
puts result.payload.title
|
|
150
|
+
puts result.payload.content
|
|
151
|
+
puts result.metadata
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
`html:` and `source_url:` are required. The URL is provenance only; this command
|
|
155
|
+
never requests it. `content_type:` defaults to `"text/html"`; HTML and XHTML are
|
|
156
|
+
supported. `format:` defaults to `:markdown`; `:text` returns readable text with
|
|
157
|
+
collapsed whitespace and separated blocks. Defuddle is used for both formats. Legacy `Extractor::Default#extract`
|
|
158
|
+
continues producing Markdown with its existing selector behavior.
|
|
159
|
+
|
|
160
|
+
Character decoding uses a declared content-type charset first, then a UTF BOM,
|
|
161
|
+
then an HTML meta charset in the first 1024 bytes, then UTF-8. Input is treated as
|
|
162
|
+
bytes regardless of its Ruby encoding label and is never modified. Invalid byte
|
|
163
|
+
sequences and decoded NULs fail; unsupported charset names fail explicitly. A
|
|
164
|
+
conflicting meta declaration does not override a declared header charset.
|
|
165
|
+
|
|
166
|
+
The result payload remains `Nous::ExtractedContent` (`title`, `content`). Metadata
|
|
167
|
+
contains `source_url`, original `content_type`, selected `encoding`, `format`, and
|
|
168
|
+
limitations `static_html_only` and `authentication_not_detected`. Static readable
|
|
169
|
+
content does not prove that an authenticated or JavaScript-rendered page is complete.
|
|
170
|
+
|
|
171
|
+
`max_input_bytes:` defaults to 25 MiB; `max_output_bytes:` defaults to 32 MiB.
|
|
172
|
+
Both require positive integers. Input is checked before copying/parsing. Output
|
|
173
|
+
counts the combined UTF-8 bytes of title and content, excluding caller-supplied
|
|
174
|
+
provenance metadata. These are input/result guards, not peak-memory, CPU or wall-time
|
|
175
|
+
limits; callers needing those protections must retain process supervision.
|
|
176
|
+
|
|
177
|
+
Expected errors are nested under `Nous::ExtractHtml`:
|
|
178
|
+
|
|
179
|
+
| Error | Meaning |
|
|
180
|
+
| --- | --- |
|
|
181
|
+
| `InvalidContent` | Invalid encoded bytes, NULs, or invalid decoded output |
|
|
182
|
+
| `UnsupportedContent` | Unsupported media type, output format or charset |
|
|
183
|
+
| `NoContent` | No readable content or empty converted output |
|
|
184
|
+
| `InputLimitExceeded` / `OutputLimitExceeded` | Caller byte budget exceeded |
|
|
185
|
+
| `InvalidLimits` (`ArgumentError`) | Nonpositive or noninteger byte limit |
|
|
186
|
+
| `UnexpectedFailure` | Safe outward message; original exception in `cause` |
|
|
187
|
+
|
|
188
|
+
Treat diagnostic causes as private debugging data; their messages can contain
|
|
189
|
+
source content. Rails acquisition, storage, lifecycle and process supervision are
|
|
190
|
+
outside this command. See the [verification map](docs/verification/README.md) for
|
|
191
|
+
executed surfaces and remaining consumer acceptance.
|
|
192
|
+
|
|
125
193
|
### Page Structure
|
|
126
194
|
|
|
127
195
|
Each extracted page contains:
|
|
128
196
|
|
|
129
197
|
| Field | Type | Description |
|
|
130
198
|
|-------|------|-------------|
|
|
131
|
-
| `title` | String | Page title
|
|
199
|
+
| `title` | String | Page title selected by the extraction backend |
|
|
132
200
|
| `url` | String | Final URL after redirects |
|
|
133
201
|
| `pathname` | String | URL path component |
|
|
134
202
|
| `content` | String | Extracted content as Markdown |
|
|
@@ -138,7 +206,7 @@ Each extracted page contains:
|
|
|
138
206
|
|
|
139
207
|
```ruby
|
|
140
208
|
page.metadata # => {
|
|
141
|
-
# extractor: "Nous::Extractor::
|
|
209
|
+
# extractor: "Nous::Extractor::Defuddle", # Which extractor was used
|
|
142
210
|
# requested_url: "https://example.com/blog", # Original URL before redirects
|
|
143
211
|
# content_type: "text/html; charset=utf-8", # HTTP Content-Type header
|
|
144
212
|
# redirected: true # Whether redirects occurred
|
|
@@ -147,7 +215,19 @@ page.metadata # => {
|
|
|
147
215
|
|
|
148
216
|
## Extraction Backends
|
|
149
217
|
|
|
150
|
-
###
|
|
218
|
+
### Defuddle (default)
|
|
219
|
+
|
|
220
|
+
Runs the bundled Defuddle parser in Node.js against the acquired HTML and final
|
|
221
|
+
URL. Returns Markdown directly, retaining structure such as code blocks, tables,
|
|
222
|
+
and links. Supports `--selector`. Each extraction owns its process, with a
|
|
223
|
+
15-second deadline, a 64 MiB JSON response limit and a 64 KiB diagnostic limit.
|
|
224
|
+
These are separate from HTTP timeouts and retained-command byte budgets.
|
|
225
|
+
Page scripts do not execute and third-party fallback fetching is disabled.
|
|
226
|
+
Failures remain visible through `details: true`; there is no automatic switch
|
|
227
|
+
to Readability. `ExtractHtml` uses this same engine while retaining its decoding,
|
|
228
|
+
plain-text rendering and byte-budget contract.
|
|
229
|
+
|
|
230
|
+
### Readability (explicit backup)
|
|
151
231
|
|
|
152
232
|
Parses static HTML using [ruby-readability](https://github.com/cantino/ruby-readability), strips noisy elements (script, style, nav, footer), and converts to Markdown via [reverse_markdown](https://github.com/xijo/reverse_markdown). Fast and requires no external services, but cannot extract content from JS-rendered pages.
|
|
153
233
|
|
|
@@ -156,9 +236,13 @@ Title extraction uses a fallback chain:
|
|
|
156
236
|
2. Original `<title>` tag from HTML
|
|
157
237
|
3. First `<h1>` from extracted content
|
|
158
238
|
|
|
159
|
-
###
|
|
239
|
+
### Migrating from Jina
|
|
160
240
|
|
|
161
|
-
|
|
241
|
+
`Extractor::Jina` and `--jina` have been removed. Remove the extractor argument or
|
|
242
|
+
flag to use Defuddle; `JINA_API_KEY` is no longer used. This retires remote browser
|
|
243
|
+
rendering: pages requiring JavaScript may yield incomplete or no readable content.
|
|
244
|
+
Use `Extractor::Default.new` or `--readability` for the Ruby-only backup.
|
|
245
|
+
`ExtractHtml` always uses Defuddle; its result and error shapes remain unchanged.
|
|
162
246
|
|
|
163
247
|
## Output Formats
|
|
164
248
|
|
|
@@ -171,7 +255,7 @@ XML-tagged output designed for LLM context windows:
|
|
|
171
255
|
<title>Page Title</title>
|
|
172
256
|
<url>https://example.com/page</url>
|
|
173
257
|
<pathname>/page</pathname>
|
|
174
|
-
<extractor>Nous::Extractor::
|
|
258
|
+
<extractor>Nous::Extractor::Defuddle</extractor>
|
|
175
259
|
<content>
|
|
176
260
|
# Heading
|
|
177
261
|
|
|
@@ -190,7 +274,7 @@ Extracted markdown content...
|
|
|
190
274
|
"pathname": "/page",
|
|
191
275
|
"content": "# Heading\n\nExtracted markdown content...",
|
|
192
276
|
"metadata": {
|
|
193
|
-
"extractor": "Nous::Extractor::
|
|
277
|
+
"extractor": "Nous::Extractor::Defuddle",
|
|
194
278
|
"requested_url": "https://example.com/page",
|
|
195
279
|
"content_type": "text/html; charset=utf-8",
|
|
196
280
|
"redirected": false
|
|
@@ -201,6 +285,11 @@ Extracted markdown content...
|
|
|
201
285
|
|
|
202
286
|
## Development
|
|
203
287
|
|
|
288
|
+
Read [CODING_STANDARDS.md](CODING_STANDARDS.md) before implementation and include it
|
|
289
|
+
in Anaphor Standards reviews. Use the [verification map](docs/verification/README.md)
|
|
290
|
+
and project-local [verify-nous skill](.agents/skills/verify-nous/SKILL.md) for the
|
|
291
|
+
retained-HTML and failure-propagation readiness checks.
|
|
292
|
+
|
|
204
293
|
```bash
|
|
205
294
|
bin/setup # Install dependencies
|
|
206
295
|
bundle exec rspec # Run tests
|
data/Rakefile
CHANGED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Defuddle runtime
|
|
2
|
+
|
|
3
|
+
Default URL and retained-HTML extraction requires Node.js 22 or newer on PATH.
|
|
4
|
+
The process adapter currently targets macOS/Linux (POSIX process groups). Install
|
|
5
|
+
Node explicitly using your normal runtime manager. No API key or service is used.
|
|
6
|
+
Check readiness from the repository root:
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
node --version
|
|
10
|
+
node -e 'if (Number(process.versions.node.split(".")[0]) < 22) process.exit(1)'
|
|
11
|
+
printf '%s' '{"html":"<html><head><title>Probe</title></head><body><p>Readable probe.</p></body></html>","url":"https://example.com"}' | node vendor/defuddle.mjs
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Require exit zero and JSON containing title `Probe` and Markdown `Readable probe.`.
|
|
15
|
+
The published gem includes `vendor/defuddle.mjs` and third-party license notices;
|
|
16
|
+
users do not run npm. A missing Node binary produces an extraction failure.
|
|
17
|
+
`Nous.fetch(..., extractor: Nous::Extractor::Default.new)` and `--readability`
|
|
18
|
+
select the Ruby-only backup explicitly. Retained `ExtractHtml` requires Node.
|
|
19
|
+
|
|
20
|
+
## Rebuilding the bundled adapter
|
|
21
|
+
|
|
22
|
+
Maintainers use the pinned dependencies in `package-lock.json`. From the repository
|
|
23
|
+
root, provision dependencies explicitly and rebuild:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm ci --ignore-scripts
|
|
27
|
+
npm run build
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Commit the source, package manifest/lockfile, generated bundle and license notices
|
|
31
|
+
together. `javascript/build.mjs` regenerates the bundle and gathers dependency
|
|
32
|
+
license texts. Runtime extraction and verification never install dependencies.
|
|
33
|
+
Run the [Defuddle recipe](../verification/defuddle.md) after rebuilding.
|
|
34
|
+
|
|
35
|
+
The bridge uses a non-executing DOM parser, disables async extractors and rejects
|
|
36
|
+
fetch calls. It is not a browser renderer. The adapter uses a 15-second process
|
|
37
|
+
deadline and bounded output, but does not enforce a process memory limit. Callers
|
|
38
|
+
with stronger isolation requirements still own external process supervision.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Verification setup
|
|
2
|
+
|
|
3
|
+
Work from the Nous repository root. Use the Ruby declared in `.ruby-version`
|
|
4
|
+
(currently 4.0.1), the Bundler version in `Gemfile.lock` (currently 4.0.6), and
|
|
5
|
+
that lockfile's gems. With rbenv, `rbenv exec ruby -v` must identify the intended
|
|
6
|
+
Ruby; shell/system Ruby 2.6 is unsuitable. Put rbenv shims on PATH or prefix the
|
|
7
|
+
commands in the verification guide with `rbenv exec`.
|
|
8
|
+
|
|
9
|
+
Run `BUNDLE_FROZEN=true bundle check`. If dependencies are absent, run
|
|
10
|
+
`BUNDLE_FROZEN=true bundle install` once, then repeat the check. Installation may
|
|
11
|
+
need network and permission to write the gem installation. If Ruby/Bundler is
|
|
12
|
+
missing, provision those declared versions using your runtime manager first.
|
|
13
|
+
Do not update or regenerate the lockfile to make verification work. Frozen mode
|
|
14
|
+
also prevents Bundler from opportunistically rewriting platform metadata.
|
|
15
|
+
|
|
16
|
+
The recipes use RSpec, WebMock, Node.js 22+ and the bundled Defuddle adapter.
|
|
17
|
+
Follow [Defuddle setup and readiness](defuddle.md) before running them. No server,
|
|
18
|
+
browser, remote account or model is needed. HTTP in the failure recipe is a
|
|
19
|
+
controlled response fixture. WebMock blocks Ruby HTTP; the Node adapter disables
|
|
20
|
+
Defuddle async fetching and rejects fetch calls.
|
|
21
|
+
No credentials or retained private content belong in evidence.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Nous verification
|
|
2
|
+
|
|
3
|
+
The project-local [verify-nous skill](../../.agents/skills/verify-nous/SKILL.md)
|
|
4
|
+
uses this guide. Initial scope is the two selected architecture candidates:
|
|
5
|
+
retained-HTML extraction and visible extraction failures. This map does not
|
|
6
|
+
certify all crawling, rendering, serialization or consumer behavior.
|
|
7
|
+
|
|
8
|
+
| Recipe | State | Coverage |
|
|
9
|
+
| --- | --- | --- |
|
|
10
|
+
| [Defuddle](defuddle.md) | Implemented; run required | Bundled parser, selectors, process failures, concurrency and packaging |
|
|
11
|
+
| [Retained HTML](retained-html.md) | Implemented; run required | Offline content/provenance, formats, decoding, byte budgets, errors and legacy compatibility |
|
|
12
|
+
| [Failure propagation](failure-propagation.md) | Implemented; run required | Public detailed result must retain extraction failure |
|
|
13
|
+
|
|
14
|
+
## Launch
|
|
15
|
+
|
|
16
|
+
Follow [setup](../setup/verification.md). These are one-shot Ruby commands; no
|
|
17
|
+
persistent process is launched. Start an evidence directory for this run:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
mkdir -p tmp/verification
|
|
21
|
+
receipt_dir="$(mktemp -d "$PWD/tmp/verification/run-XXXXXX")"
|
|
22
|
+
export receipt_dir
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Keep the same shell variable for the commands below. Do not pipe through `tee`
|
|
26
|
+
without preserving the original exit status.
|
|
27
|
+
|
|
28
|
+
## Doctor
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
ruby -v > "$receipt_dir/ruby.log" 2>&1
|
|
32
|
+
printf '%s\n' "$?" > "$receipt_dir/ruby.exit"
|
|
33
|
+
BUNDLE_FROZEN=true bundle check > "$receipt_dir/bundle.log" 2>&1
|
|
34
|
+
printf '%s\n' "$?" > "$receipt_dir/bundle.exit"
|
|
35
|
+
git rev-parse HEAD > "$receipt_dir/head.txt"
|
|
36
|
+
git status --short > "$receipt_dir/status.txt"
|
|
37
|
+
git diff --binary HEAD > "$receipt_dir/working-tree.patch"
|
|
38
|
+
BUNDLE_FROZEN=true bundle exec ruby -Ilib -r nous -e 'puts Nous::VERSION; puts $LOADED_FEATURES.grep(%r{/lib/nous.rb$})' > "$receipt_dir/target.log" 2>&1
|
|
39
|
+
printf '%s\n' "$?" > "$receipt_dir/target.exit"
|
|
40
|
+
git ls-files --others --exclude-standard -z | BUNDLE_FROZEN=true bundle exec ruby -rdigest -e 'STDIN.read.split("\0").each { |path| puts "#{Digest::SHA256.file(path).hexdigest} #{path}" if File.file?(path) }' > "$receipt_dir/untracked.sha256"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Inspect each exit file and the target path before driving: Ruby, bundle and target
|
|
44
|
+
must exit zero and target must resolve this checkout. Untracked hashes identify
|
|
45
|
+
new recipes/harnesses; retain their contents with the change. For committed work,
|
|
46
|
+
HEAD identifies the source; for dirty work retain the patch and hashes too.
|
|
47
|
+
|
|
48
|
+
## Drive and capture
|
|
49
|
+
|
|
50
|
+
Discover opt-in checks with `bundle exec rake -T`.
|
|
51
|
+
Run each selected recipe's exact command, redirect its output to the named log,
|
|
52
|
+
and immediately record `$?` in its exit file. A nonzero product assertion stays
|
|
53
|
+
failed; do not weaken it to current behavior. For a prerequisite failure, attempt
|
|
54
|
+
one documented setup recovery, retry once, then mark blocked if unavailable.
|
|
55
|
+
|
|
56
|
+
Record a receipt table with criterion, repository/revision, command, expected,
|
|
57
|
+
observed, evidence path and pass/fail/blocked/not-run status. Keep failed logs.
|
|
58
|
+
Do not treat successful neighbors as readiness for a failed or unimplemented goal.
|
|
59
|
+
|
|
60
|
+
For Ruby or harness changes also run `BUNDLE_FROZEN=true bundle exec rspec` and
|
|
61
|
+
`BUNDLE_FROZEN=true bundle exec standardrb`, capturing output and exit status.
|
|
62
|
+
The draft readiness harness is opt-in and is not part of ordinary specs.
|
|
63
|
+
|
|
64
|
+
## Cleanup
|
|
65
|
+
|
|
66
|
+
These checks leave no server, child process, storage record or fixture changes.
|
|
67
|
+
RSpec may update the ignored `.rspec_status`; it is not evidence. Keep
|
|
68
|
+
`$receipt_dir` and all logs after cleanup; do not delete the evidence directory.
|
|
69
|
+
Never use private captures for the synthetic fixture checks.
|
|
70
|
+
|
|
71
|
+
## Coverage limits
|
|
72
|
+
|
|
73
|
+
Real-world readability quality, process CPU/memory/wall-time supervision,
|
|
74
|
+
Substrate integration, locked Autodidact compatibility and hosted acceptance remain
|
|
75
|
+
outside this map. Neither local backend renders JavaScript.
|
|
76
|
+
The caller owns immutable storage and any application lifecycle. Passing library
|
|
77
|
+
recipes alone does not certify downstream adoption.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Defuddle extraction
|
|
2
|
+
|
|
3
|
+
Status: implemented; execute on the source under review.
|
|
4
|
+
|
|
5
|
+
Outcomes: Defuddle is the default for `Nous.fetch`, `Fetcher` and `ExtractHtml`.
|
|
6
|
+
The CLI selects Defuddle unless `--readability` is supplied. Jina is removed.
|
|
7
|
+
The extractor consumes already acquired HTML and the final URL, returns title
|
|
8
|
+
and Markdown through the existing result contract, honors selectors, resolves
|
|
9
|
+
relative links, and keeps concurrent calls isolated. Empty content and runtime
|
|
10
|
+
failures remain failures; no automatic Readability fallback occurs.
|
|
11
|
+
|
|
12
|
+
Follow [launch and doctor](README.md) and [Node readiness](../setup/defuddle.md).
|
|
13
|
+
Then run:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
BUNDLE_FROZEN=true bundle exec rake verify:defuddle > "$receipt_dir/defuddle.log" 2>&1
|
|
17
|
+
printf '%s\n' "$?" > "$receipt_dir/defuddle.exit"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Require exit zero. The extractor specs exercise the actual bundled parser with
|
|
21
|
+
synthetic HTML. Client specs cover JSON/field validation, empty content and propagation of a
|
|
22
|
+
failed process result. `RunProcess` specs independently exercise raw process
|
|
23
|
+
output, nonzero exit, output limits, reaping and timeout cleanup with controlled
|
|
24
|
+
Ruby subprocesses. Missing-runtime and safe-diagnostic errors are covered through `RunProcess`. CLI specs check backend selection and observable selector behavior. Acquired-byte
|
|
25
|
+
specs cover BOM/meta decoding and meaningful invalid-byte/charset failures.
|
|
26
|
+
Run the retained-HTML and failure-propagation recipes, full suite and Standard too.
|
|
27
|
+
|
|
28
|
+
For installed-runtime and packaging compatibility, run the separate opt-in check:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
BUNDLE_FROZEN=true bundle exec rake verify:defuddle_package > "$receipt_dir/defuddle-package.log" 2>&1
|
|
32
|
+
printf '%s\n' "$?" > "$receipt_dir/defuddle-package.exit"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Require exit zero. The harness in `spec/verification/defuddle_package.rb` builds
|
|
36
|
+
and unpacks the gem in an isolated temporary directory, verifies the loaded Nous
|
|
37
|
+
path, checks Jina is absent, and asserts title/content through `ExtractHtml`.
|
|
38
|
+
It owns and cleans up the temporary package on success and failure. This verifies
|
|
39
|
+
that extraction does not resolve npm dependencies from the checkout.
|
|
40
|
+
|
|
41
|
+
Record source identity, commands, exit statuses and logs using the verification
|
|
42
|
+
guide. Runtime checks establish compatibility only with the Node/platform tested;
|
|
43
|
+
controlled subprocesses do not establish Node compatibility. Synthetic fixtures
|
|
44
|
+
do not establish extraction quality across live sites, JavaScript completeness,
|
|
45
|
+
authentication detection or downstream Substrate acceptance.
|