Compare commits

..
Author SHA1 Message Date
morluto 800a3f24b7 fix(process)!: reject unsupported one-column terminals 2026-10-28 23:22:07 +08:00
gensokyo-dev 6c895eeb8e fix(process)!: redact sensitive terminal echoes before retaining Evidence (#7804)
Seal sensitive terminal chunks before deferred rendering, apply one bounded
capture-local literal matcher, and retain exact redacted byte accounting.
Keep private input facts in explicit fields and separate comparison policies.
Retain distinct terminal failures once and verify structured refusal facts.

Fixes #7782

BREAKING CHANGE: sensitive interaction data is empty with sensitive and input_bytes fields instead of a byte-count placeholder. Sensitive raw_data is also redacted. Read input_bytes and recapture both comparison sides when migrating older private-input Evidence.

Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis)
2026-10-28 14:39:34 +00:00
ShuaixinHuang 8f97a45483 fix(plist): end OpenStep comments at native line separators (#7793) 2026-10-28 22:23:16 +08:00
Dragolone 6fd5d347c7 fix(browser): ignore repeated CSP directives within one policy (#7798)
CSP3 parsing keeps the first directive of each name in a policy and ignores
later repeats; Chrome reports "Ignoring duplicate Content-Security-Policy
directive". parseCsp flattened every policy's directives into one stream, so
a header such as `script-src 'self'; script-src 'unsafe-inline'` reported the
ignored `'unsafe-inline'` directive as declared and counted nonces and hashes
from it. Track directive names per policy so only the effective directive is
retained, while the same name in separate comma-separated or repeated-field
policies is still listed once per policy.
2026-10-28 22:23:00 +08:00
ShuaixinHuang b14b94f0f5 fix(asn1): reject non-minimal OID subidentifiers (#7800) 2026-10-28 22:22:43 +08:00
Ysopwnableandgensokyo-dev 7b6d872a25 docs: celebrate 400,000 GitHub stars 🎉 (#7801)
Co-authored-by: gensokyo-dev <[email protected]>
2026-10-28 22:12:43 +08:00
gensokyo-dev b9a8f8e9f2 docs(update): align error catalog with current recovery (#7795) 2026-10-28 21:04:12 +08:00
gensokyo-dev 23e7c4e8e2 fix(setup): preserve specific skill path recovery (#7791) 2026-10-28 20:49:05 +08:00
gensokyo-dev ac545adae2 fix(apk): classify fixed inspection allowances as nonretryable (#7784) 2026-10-28 20:13:58 +08:00
ShuaixinHuang 9593bc536b fix(protobuf): reject malformed descriptor field declarations (#7730) 2026-10-28 19:52:11 +08:00
JG Tromp 7c389cbefc fix(javascript): keep both-direction feature traces from crossing shared containers (#7743)
* fix(javascript): keep both-direction feature traces from crossing shared containers

trace_application_feature with direction "both" walked the application
graph as if it were undirected. From a seed, one outgoing or incoming step
into a shared container (an asset or the artifact root) followed by steps
in the other direction reached every sibling of that container, so a trace
seeded on one storage key returned the whole connected graph.

"both" now returns the union of the outgoing and incoming closures. Each
walk keeps its own predecessor chains, so a terminal path never changes
direction midway. "outgoing" and "incoming" are unchanged.

* test(javascript): check both-direction trace paths end to end

The existing tests checked predecessor maps but no returned path. Add a
trace whose seed reaches one storage terminal outgoing and another
incoming, and assert each path's complete node_ids and edge_ids. A
pathTo that reads only the first walk's map returns a one-node path for
the incoming terminal and fails this test.

* fix(javascript): confine only ascended containers in both-direction traces

Making "both" the union of separate outgoing and incoming walks dropped
modules that a feature reaches by crossing directions. In the Notes
known-answer evaluation, the notes:export channel is pointed at by both
the preload asset (invokes) and the handler (handles); its preload,
renderer and csv modules are reached only through relationships that
change direction, so the trace kept main.js alone and the feature_modules
claim failed in tests/boundary/mcp/agentKnownAnswerMcp.test.ts.

Walk both directions in one traversal again, and confine only the
containment case the change was meant to stop: a node reached by stepping
up a contains edge from one of its children follows only further contains
steps up. A shared asset or artifact root then no longer leads into its
other children, while relationships across the feature are kept. The
Notes trace returns csv.js, main.js, preload.js and renderer.js again
(30 nodes; the previous main returned the whole 32-node graph).
outgoing and incoming traces are unchanged, and paths come from one
predecessor map again, as before this branch.
2026-10-28 19:51:59 +08:00
JG Tromp 8b74f30bf9 fix(javascript): stop double-expanding application projection estimates (#7744)
* fix(javascript): stop double-expanding application projection estimates

estimateJavaScriptStaticApplicationProjection multiplied the per-record
metadata allowance (2 KB plus the path) by the 8x expansion that it also
applies to checkpoint bytes. A projected record serializes to about 0.46 KB,
so the allowance alone already covers retention; expanded again, the
charge was about 25x the serialized application relationships.

The charge accumulates in the parent's shared result budget. On a
synthetic 2.8 MB Vite-style build (88 files, one 456 KB chunk), the first
10 files used 2.0 GB of a 2.08 GB budget while the parent heap held about
300 MiB. The other 78 files lost their application relationships and were
offered a 0-byte semantic budget. On a 201-file build the charges exceeded
the budget at file 93 and the analysis failed with resource_constraint,
leaving the remaining sources without static facts.

Keep the 8x expansion for checkpoint and repeated bytes and count the
per-record allowance once. The same builds now refuse 14 and 102
projections instead of 78 and a failed run; peak RSS stays at 1.2 GB and
2.0 GB under a 4.2 GB parent heap limit.

workerFailureComposition simulated 64 KiB of parent result budget, which
the corrected charge for its one-finding checkpoint fits. Lower that
headroom to 32 KiB so the case still exercises a refused projection.

* test(javascript): cover application projections near the shared parent budget

The estimate test compares one file's charge with its serialized graph,
but no test put several sources against one parent result budget. Replay
one recorded static checkpoint for five sources against an exact budget:
sources 1-3 fit, source 4 misses by half a source and keeps its static
facts with the reported required and budget bytes, and the last source
fits again once no checkpoint reserve is held back. A worker that charges
the projection twice, or not at all, passes the existing tests and fails
this one.

* test(javascript): cover a malformed source before a valid one under the shared budget

The testing guide asks representation-expansion and capacity changes for
a large malformed input followed by a valid input, and for escaped or
expanded representations.

Under an exact parent budget, a 3 MiB checkpoint cut off inside a string
comes first and is rejected as unreadable output with no static facts.
The valid source after it keeps its application relationships; it fits
only if the rejected bytes took no charge. A worker that charges the
static checkpoint on receipt, before parsing, passes the existing tests
and fails this one.

The estimate ratio test also runs with a nested source path and storage
keys holding quotes, backslashes, a tab and multibyte text, which expand
in serialized JSON; the charge stays within 3x to 8x of the serialized
application graph (5.3x).
2026-10-28 19:51:38 +08:00
JG Tromp 7aa943c14b fix(javascript): resolve Vite build-output preload dependencies (#7745)
* fix(javascript): resolve Vite build-output preload dependencies

With an absolute base, Vite writes __vite__mapDeps entries relative to the
build output directory (for example assets/chunk.js), and its preload helper
prefixes the configured base at runtime. REA emitted these entries as
dynamic-import module specifiers, so a scheme-less path without a leading
dot resolved as a bare package and every entry was reported as an
unresolved "external" module, even when the build output directory was
selected.

The static analyzer now marks such entries with a vite-output: specifier,
as Webpack chunk loads are marked with chunk:. Entries starting with ./,
../ or /, and scheme-qualified entries, keep their previous handling. The
graph resolves a marked entry as a URL path under each ancestor directory
of the importing file; exactly one ancestor holding the file establishes
the build output directory. Otherwise the entry is not-found (or
unavailable when several ancestors match) with a limitation that names
the build output directory.

On a synthetic 7-file Vite build, the 12 preload entries that were
"external" now resolve when the build output directory is selected, and
are not-found with that guidance when only assets/ is selected.

* fix(javascript): keep unanalyzed Vite output candidates

resolveViteOutputDependency considered only resolved ancestors. A file
that exists under an ancestor but was not analyzed (for example
dist/assets/lazy.css for the entry assets/lazy.css) resolves there as
unavailable, and that result was replaced by the root's not-found with a
limitation saying that no ancestor holds the file.

Keep resolved and unavailable ancestor candidates together. One candidate
is returned with its own path and limitations; several are reported as
unavailable with every candidate path and limitation. Only when no
ancestor can hold the file does the root's not-found result stand.
2026-10-28 19:46:45 +08:00
Dragolone 33698e6b31 fix(apktool): read Apktool 2.9+ metadata in its line-oriented dialect (#7747)
Apktool 2.9.0 replaced SnakeYAML with the YamlWriter that 3.x still uses:
apktool.yml starts with an untagged `version: 2.x.y` line and strings are
written unquoted with Java-style escapes. Only a `version: 3.x.y` header
selected the producer dialect, so 2.9.0-2.12.x output went through YAML
core parsing: a versionName of `1.0 #beta` became `1.0`, escaped quotes kept
their backslashes, an empty name became null, and `1.0: beta` made the whole
decode fail as invalid metadata.

Select the line-oriented reader for 2.9.0 and later as well. Older tagged
SnakeYAML documents keep YAML scalar semantics.
2026-10-28 19:46:11 +08:00
Dragolone fea4f2e397 fix(javascript): test literals for Next page paths in linear time (#7748)
Every source string literal without an established Next root was tested
against /(?:^|\/)app\/(?:[\s\S]*\/)?page(?:\.[cm]?[jt]sx?|-[^/?#]+\.js|)(?:[?#].*)?$/u.
The unanchored search restarted at every app/ segment and rescanned to the
end of the literal, and each page? or page# candidate scanned to the next
line terminator before $ failed. A long literal therefore took quadratic
time on the analysis thread: about 6 s for a 256 KB literal of repeated
app/ segments, and 3.5 s for 224 KB of x/page? followed by a newline.

The first app/ segment admits every later page segment, and . cannot cross
a line terminator, so a query or fragment after the page filename starts on
the last line. Test the tail after the first app/ segment one page segment
at a time instead; results are unchanged.
2026-10-28 19:45:59 +08:00
ShuaixinHuang 09225b08e1 feat(artifacts): read Godot 3 PCK version 1 archives (#7781) 2026-10-28 19:43:43 +08:00
gensokyo-dev c77735fa83 fix(setup)!: bind managed registrations to selected installation
Use one command owner for setup, client writers, Gemini policy and CLI defaults.
Migrate recognized runner registrations with backups and remove runner prewarm.
Retain unknown identities and exercise real offline npm, CLI and MCP workflows.

Fixes #7757

BREAKING CHANGE: managed MCP registrations use the selected absolute runtime
and REA entry script, including package-runner cache paths. Keep that installation
available or restore it before setup; unknown registrations require confirmed
manual repair. Setup no longer returns prewarm or emits its progress events.

Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis)
2026-10-28 11:34:28 +00:00
Cipherandgensokyo-dev 18a8e71c9f feat(artifacts): add U-Boot legacy uImage reader (part of #7129) (#7234)
* feat(artifacts): add U-Boot legacy uImage reader (part of #7129)

Recognize legacy uImages by their 0x27051956 magic and inventory them as containers: 64-byte header facts (arch/os/type/compression raw and named, load/entry addresses, name, timestamp) with header and data CRC32 verified, stored payload extractable as payload.bin, and multi-file images split from the stored size table into image-<n>.bin. Compressed payloads stay as stored; nothing is executed or decompressed. Wires the format through classification, the artifact reader dispatch, extraction, and the evidence/snapshot format vocabularies. Related to #7129 (uImage part only); HEX/SREC and FIT are out of scope.

* fix(uimage): preserve aligned component ranges and exact header facts

* fix(uimage): retain buffered members through consumption and cancellation

* fix(uimage): preserve active read and validation failure precedence

---------

Co-authored-by: gensokyo-dev <[email protected]>
2026-10-28 19:11:41 +08:00
gensokyo-dev 06d1247836 fix(evidence): preserve residual unknown constraint diagnostics (#7770) 2026-10-28 18:50:33 +08:00
ShuaixinHuang f483eab543 fix(captures): exclude normalized native numeric values (#7764) 2026-10-28 18:46:30 +08:00
gensokyo-dev df7119aeed fix(mcp): distinguish validation from connection loss (#7761)
* fix(mcp): distinguish validation from connection loss

* fix(mcp): reuse parsed HTTP disconnect facts
2026-10-28 18:17:45 +08:00
gensokyo-dev 60bad9ed06 fix(hopper)!: admit macOS arguments before application handoff (#7732)
Admit the final macOS Hopper launcher arguments before application handoff.
Keep prepared-image source identity, return precise typed refusal locations,
and preserve bootstrap/lease cleanup. Update native CLI/MCP recovery
coverage and the installation guide.

Fixes #7727

BREAKING CHANGE: macOS Hopper launches refuse double quotes, backslashes
and line breaks in final helper paths or arguments before application
handoff. Select literal-safe launcher/runtime/database paths and loader
arguments, or a compatible provider. Original executable names can remain
unchanged when their prepared image path is literal-safe.

Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis)
2026-10-28 09:12:30 +00:00
gensokyo-dev 3b242491a4 fix(cli): attribute keyed archive errors to archive argument (#7731)
* fix(cli): attribute keyed archive errors to archive argument

* test(cli): assert input-error projection reference reuse
2026-10-28 16:48:20 +08:00
blankymandgensokyo-dev e78ac1a55b feat(artifacts): read UnityFS AssetBundle containers (#7699)
* feat(artifacts): read UnityFS AssetBundle containers

inspect-artifact and extract-artifact now recognize the UnityFS signature
and treat format versions 6 through 9 as containers. Header facts (format
version, player/engine strings, declared size, dataflags, blocks-info
bounds and the declared 16-byte data hash) are retained alongside a
directory of CAB-* and .resS members. Member bytes stream on demand:
only the blocks covering the requested member are read and decompressed
with bounded expansion and cancellation.

Bundles declaring asset-bundle encryption keep header facts and report
unknown directory coverage. LZMA blocks-info reports unknown coverage;
LZMA member reads fail as unavailable while the directory stays
readable. Malformed counts, out-of-stream members and declared sizes
beyond the file fail as format errors. UnityFS classifies by bytes in
root detection, inventory graph construction and desktop inspection,
which emits a unityfs-header fact.

Validation: tsc --noEmit, oxlint (no new findings on touched files),
17 reader tests covering LZ4HC/uncompressed tables, blocks-info at end,
format 6 without padding alignment, multi-block members, malformed
input, unsupported compression, cancellation and abandoned streams;
full artifacts/domain/conformance suites pass. Verified against one
real YooAsset bundle (Unity 2021.3.32f1, format 8, LZ4HC): the
streamed SerializedFile contained its managed assembly byte-identical
to a UnityPy extraction. Other producer variants unverified.

* fix(unityfs): own bounded member reads and preserve partial evidence

* refactor(unityfs): clarify producer revision flag decisions

* fix(artifacts): preserve coverage authority and bound UnityFS block work

---------

Co-authored-by: gensokyo-dev <[email protected]>
2026-10-28 16:47:39 +08:00
gensokyo-dev 6a226bd092 fix(apple): validate keyed archive selectors consistently (#7728) 2026-10-28 16:01:17 +08:00
gensokyo-dev 2b8fb387b6 fix(windows): resolve default tools before process launch (#7725)
Resolve Windows ADB, Apktool and update commands against the selected
PATH/PATHEXT before launch. Keep explicit paths and relative PATH choices,
report lookup failures, and reuse absolute batch invocation and ownership.

Add native launch/argv regressions, real npm metadata probes and focused
CI ownership so these boundaries retain Windows verification on the fast tier.

Fixes #7724

Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis)
2026-10-28 07:27:48 +00:00
gensokyo-dev 0e4b806db1 fix(cli): preserve missing-selection Markdown token windows (#7726) 2026-10-28 15:23:00 +08:00
gensokyo-dev a0a1962e06 fix(cli): validate byte queries before opening targets (#7723) 2026-10-28 15:00:01 +08:00
gensokyo-dev b0fb51cb4c fix(environment): share inline state byte admission (#7721)
Reuse shared padded-base64 facts and derive encoded admission from the existing decoded-byte budget. Refuse overflow before decoded allocation and retain padded syntax. Cover capacity, compact HTTP replies and live MCP state readback.

Fixes #7719

Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
2026-10-28 06:48:26 +00:00
5ae77656e7 feat(workflows): expose guided prompts through CLI and MCP (#6995)
* feat(workflows): expose guided prompts through CLI and MCP

* fix(workflows): address guided workflow review findings

* fix(workflows): preserve scoped contracts and actionable validation

* test(workflows): allow bounded CLI startup on shared hosts

* test(workflows): audit guidance effects and content provenance

* perf(workflows): defer rendering and separate catalog scope

---------

Co-authored-by: Deepak-Kambala <[email protected]>
Co-authored-by: gensokyo-dev <[email protected]>
2026-10-28 14:12:39 +08:00
gensokyo-dev b594fbc3e7 fix(apple): scope application roots to the inventory format (#7714) 2026-10-28 13:28:03 +08:00
gensokyo-dev 8397a0c9eb fix(plugins): align listing metadata with REA branding (#7711)
Use one packaged icon for Codex and Claude, and align listing descriptions with the repository About.
2026-10-28 05:16:41 +00:00
gensokyo-dev fbb0e79354 fix(cli): reject empty observation duration selections (#7710) 2026-10-28 12:57:14 +08:00
Aadityaandgensokyo-dev c36971953c feat(artifacts): inventory and extract Intel HEX and Motorola S-record regions (#7136)
* feat(artifacts): inventory and extract Intel HEX and Motorola S-record regions

open_binary refused Intel HEX and S-record programming files as an
unsupported format, so inspect_artifact and extract_artifact could not
answer which bytes load at which addresses.

Recognize both encodings by a fully valid first record and decode them
statically into address-contiguous regions, each an inventory entry named
0x<start>-0x<end>.bin and extractable as raw bytes. Root format_metadata
records the encoding and variant, record counts, unique and duplicate
bytes, address bounds, termination, declared start address, S0 header and
validated S5/S6 count. Gaps are never filled; start addresses are declared
facts, not proven entry points.

Checksum, record-type, byte-count, line-length, overlap-conflict, address
wrap and post-termination errors fail closed with the line number; a
missing termination record leaves coverage unknown. Decoding is bounded by
a 128 MiB file ceiling (decoded bytes are at most half the file and are
reserved after the size check) and 65,536 address-discontiguous runs.

Refs #7129

* fix(artifacts): refuse Intel HEX extended address records with a non-zero address

* fix(artifacts): bound HEX region copies and retain stream cancellation

---------

Co-authored-by: gensokyo-dev <[email protected]>
2026-10-28 12:35:55 +08:00
gensokyo-dev ad7d4c1f4d docs(errors): align MCP validation status and verification columns (#7708) 2026-10-28 12:19:25 +08:00
gensokyo-dev b69d8c5467 docs(changelog): repair historical release heading links (#7707) 2026-10-28 12:12:42 +08:00
gensokyo-dev c01ed783ac fix(elf): share section-table presence validation with notes (#7706) 2026-10-28 12:08:25 +08:00
d3b31a0e29 feat(cli): report opt-in command timings (#7042)
* feat(cli): report opt-in command timings

* fix(cli): time standalone MCP doctor

* fix(cli): retain timing on doctor errors

* fix(cli): preserve formatted version output with command timings

---------

Co-authored-by: dhruvac29 <[email protected]>
Co-authored-by: gensokyo-dev <[email protected]>
2026-10-28 11:53:29 +08:00
gensokyo-dev 3d5c73f35c fix(cli): retain JSONL parser diagnostics under token counting (#7705) 2026-10-28 11:48:31 +08:00
gensokyo-dev e556134302 fix(deps): patch fast-uri, qs and HTTP/3 certifi (#7704)
Refresh compatible security fixes and verified dependency notices. Keep
historical HTTP/3 guest verification separate from the refreshed trust store.

Fixes #7700

Assisted-by: Aya (gensokyo-skills:aya-source-investigation)
2026-10-28 03:34:36 +00:00
aa6f6436c9 feat(cli): add CSV and TSV analysis views (#7254)
* feat(cli): add CSV and TSV analysis views

Co-authored-by: Copilot <[email protected]>

* Update tests/boundary/cli/filteredArrayOutput.test.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Update src/cli.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* fix(cli): address delimited export review feedback

* fix(cli): format delimited output block in cliResultOutput

* fix(cli): retain delimited view uncertainty and align completions

---------

Co-authored-by: Copilot <[email protected]>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: gensokyo-dev <[email protected]>
2026-10-28 11:27:40 +08:00
40b9c03f99 feat(environment): serve query numbers, counted values and made objects (#7332)
* feat(environment): serve the writes callers make without recorded form fields

- A create member a recording sent empty ("", null, false, [] or {}) is no
  longer required; a call that leaves it out is applied as sending that value
  (body.blank).
- List views render envelope members that list the listed entities' ids
  (Paperless's `all`), so creates in such collections are confirmed.
- Updates move their entity in collections kept sorted by a field they write,
  and a failed whole-collection read after an update can teach that order.
- A later create that stores an unruled field otherwise is withheld at the
  create, and an item read may fill values a create's answer had not loaded.
- An unsent field respelled between null, [] and {} is unchanged.
- The skill asks for one create with every field and one with only what a
  person must give.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* docs(skill): show how to read signed-in pages before recording

Agents guessed capture keys (artifacts, dom, final) to see pages behind
sign-in, which inspect_web_page cannot reach.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): accept every empty spelling recorded creates sent

A create that fills only what a person must give sends `null` where the
full create sent a string, and `null` or `false` for the same flag. The
merged plan now accepts each recorded empty value, and a call that leaves
the member out is applied as the earliest call sent it.

Also adds the spec folder for this change.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* docs(skill): keep the vary-fields step neutral on UI or API writes

Agents read "the form" as a cue to write through the UI, where some
applications' request bodies are not exposed; a body-unusable UI write
now re-records once with request steps.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* docs(environment): record verification for blank members and sorted writes

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): address review of blank members and sorted writes

- Bulk updates move their entities in turn against the others' earlier
  values and install the final order once, as replay does.
- A member's recorded empty values stay accepted once a later call teaches
  its default; a left-out member with a default keeps the default.
- Write answers spell empty lists and maps as their recorded answers did.
- A rename can teach a sort when the creates chose one by a field the
  update did not write, such as ids that ascend with titles.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* feat(environment): serve the writes that later reads explain

- An item read whose object is marked deleted (deleted, isDeleted or
  is_deleted true) agrees with an absent entity, as EspoCRM answers a
  deleted record; such an object never seeds or adopts an entity.
- maxSize, max_results and maxResults are page sizes, for whole-collection
  list lines and the list query's limit role.
- An item view also shows the fields only reads after its type's first write
  show, when no initial entity holds them (Vikunja's task subscription),
  without requiring them; write answers leave such a field out for entities
  without it, and it no longer withholds the write as incomplete-entities.
- The skill asks updates to change the fields a person would change, and
  expects a deleted object to read back as 404 or marked deleted.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): answer sent empty members as sent, and keep a placed create's order

- A write answer spells an empty list or map the call did not send as its
  recorded answer did; one the call sent is answered as sent, so a create
  sending `[]` is not answered `null` because the first recorded create was.
- A read listing the whole collection that placed a create leaves the create
  only the sorted inserts whose order that read follows, so a later rename
  cannot teach a sort an earlier read ruled out.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): answer bulk updates without fields only later reads show

A bulk update's answer renders each entity with the same allowance a single
update's answer has: a field only reads after the type's first write showed,
which no initial entity holds, is left out for an entity without it, so a
bulk update of a seeded task is no longer refused for lacking it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* docs(environment): correct how the EspoCRM contact update task passes

The agent trajectories show gpt-oss passes it by creating a replacement contact
and deleting the original, since the served update refuses emailAddress; the
update itself is refused in both builds.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): answer a read of a served-deleted entity marked deleted

An application that soft-deletes still answers a deleted record by id, marked
deleted. Serving now keeps an entity a served delete removed from a type whose
recorded item reads show that, until a reset or load and within the size
data/add loads, and answers a read of it with its last fields and the flag
true, instead of refusing the read.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): keep unloaded create answers, check bulk answers with sent members, narrow inserts only on collection-order reads

- A create whose item read loaded values its answer had not records them
  (`observation.read.unloaded`), and a served create's answer keeps them as
  the recorded answer showed them, a zero-time owner included, while the state
  holds the read's.
- Replay checks a bulk update's recorded answers with each target's sent
  members, as serving answers them, so echoed `null` and `[]` no longer make
  the plan body-unusable.
- A whole-collection read whose query sorts it shows the query's order, so it
  no longer narrows a create's sorted insert candidates.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* docs(environment): record the census with main's unknown-clock rule merged

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* feat(environment): serve query numbers, counted values and made objects

- A query parameter accepts a number or boolean, which serving sends as its
  JSON text, as agents send page sizes and flags.
- A sequence may start at the value its first create took above every value
  its type held (Vikunja's per-project task index), and a later create's
  composed, template or started-sequence rule is adopted when it also spells
  the first create's value.
- A made-object rule explains an object the application makes with each
  created entity: members the request stamped, members holding the entity's
  own id, and counts one past the held objects' values.
- A create's answer names the id of the one object a one-element array member
  holds, so bulk creates link to their type.
- The skill reads each listed object by id before writing.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* feat(environment): let a linked create take path values it stores

A create linked to its type's own collection, whose path has no parameters,
accepts any path values when its plan stores each one in a field, as a Vikunja
task stores its project; replay and serving apply it alike.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): learn a sort from a rename only when moving its entities reaches it

Serving moves only the entities a write changes in a collection kept sorted,
so replay now tries a sort after a rename only when the entities no write the
read exercised changed already follow it. Seeded folders listed out of title
order no longer teach a title sort that serving would answer with another
order.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): keep unloaded create answers a sent value did not echo, and learn no sort from query-sorted reads

- A served create keeps a value its recorded answer had not loaded unless the
  call sent that member and the plan stores it as sent, so a create sending
  `active: false` is answered `false` as recorded while the state holds the
  read's `true`.
- A rename no longer teaches a sort from a whole-collection read its query
  sorts, whose ties need not follow the collection's own order.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* docs(environment): verify query values, counted values and made objects

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): learn the soft-delete flag in the state view and serve by it

Extraction now records the member later item reads set true on a record the
application soft-deleted, and the item view carries it as `deleted_flag`.
Serving reads that fact instead of scanning recorded bodies itself, so one
owner decides what a soft delete looks like.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* docs(environment): record the call-values checks at #7274 93821024

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): learn the soft-delete flag from item reads with a query too

A client may read a deleted record back only with a query, such as a
cache-busting parameter. Every successful read of the item tool now shows the
flag; only plain later reads still add fields.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* docs(environment): record the call-values checks at #7274 45832406

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): learn sorts only from orders a list read confirmed

- A sort retry after a failed whole-collection read requires the entities no
  write changed since a list last showed them to follow it already, rather than
  excusing every entity the update's tool ever wrote: an earlier rename a list
  confirmed in place stays where it was.
- An update takes its creates' sort only from a create a list read showed
  placed; replay still tries a sort a create guessed from the initial order.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): drop entities read back as deleted and answer soft deletes as deleted

- A held entity a later read shows marked deleted is dropped, not adopted with
  the marked object's fields.
- Bulk-update replay renders answers with the optional and later-only fields
  serving uses, so a bulk update of created and seeded entities replays.
- A served delete of a soft-deleting type answers with the entity marked
  deleted, single and bulk.
- A deleted flag only query reads showed is a field of the item view, so a
  served tombstone keeps it where live records omit it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): check made objects as recorded and keep their ids and clocks honest

- A made object's clock counts as a write clock, so a capture with no clock
  anchor withholds the create instead of stamping 1970.
- Members holding the entity's identity follow the id written, generated or
  sent, through the type's id field.
- A later create's made object is checked as recorded, before its own members
  are rewritten, so one naming another entity withholds the plan.
- A later create's template is adopted only when it spells that create's value
  too, not just when the first create's text holds its source value.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): move updated entities only by placed sorts and keep query-sorted reads from confirming order

Replay moved an updated entity by a sort a create only guessed, while the
update plan exported only placed sorts, so a read could confirm an update
by a move serving would not make. Both now use the placed sort. A passing
list read clears its entities from the moved set only when the state
decides the order of every entity it lists, so a read whose query sorts
it no longer hides an earlier move from a later sort check.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* docs(environment): count six withheld plans that explain more, not seven

The bulk task create counted among them is served on this branch.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): try a learned sort by moving each pending write in turn, as serving does

#retrySort sorted the whole collection, which a list could match while
serving, moving each renamed entity in turn against the others' values
before their own writes, gave another order. The state now records the
values each write replaced, and the retry replays the writes since a
list in collection order last showed their entities.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): answer a served delete with the deleted flag its recorded answer showed

An application may answer a delete with the record as it was and mark it
deleted only on later reads. Delete answers, single and bulk, now take
the flag's value from the recorded answer; reads of a served-deleted
entity still show it marked deleted.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* chore(environment): set aside two hunks next to lines main's saved-values change rewrites

Main's #7267 changes the lines beside the blank-member docs and the
empty-spelling check in explainUpdate. Setting them aside lets that merge
apply cleanly; the commit after it puts them back on top of main's text.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* chore(environment): restore the set-aside hunks on main and bump the skill to 51

Puts the blank-member docs and the empty-spelling check back beside main's
slug and composed text and heldSlugRule. Main is at skill 50 with
different text, so this branch's skill changes take 51.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* chore(environment): bump the skill to 52 above #7260's 51

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* chore(environment): put back the call-values hunks main's merge took its side for, and bump the skill to 53

The merge of main kept main's side where it overlapped this branch, so it
could commit without a conflict. This restores this branch's side next to
main's: made-object agreement beside the & slug spellings, sequence starts
in the earlier evidence (now built by earlierOf), the combined value
imports, and recording step 2 beside main's UI-first step 3. Main is at
skill 50 and #7274 at 52, so this branch takes 53.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): keep sorts a list showed in place ruled out, and created answers' nested members as recorded

A whole list in collection order that showed a renamed entity where a
sort by the renamed field would have moved it now rules that sort out, so
a later rename of the same entity cannot teach it. A create's answer
leaves out members its item read added inside an object the answer
showed, as the recorded answer did; the state still stores them.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): keep a later create's made object only with clocks its request stamped

followsMadeObjects only checked that a made object's clocks parsed, so a
later create whose object showed an older clock still passed, while serving
stamps the write's clock. Its clocks must now be in the rule's format and
within the stamp window of that create's request.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): compare soft-deleted read fields

* fix(environment): reject contradictory learned sequences

* fix(environment): preserve initially deleted item observations

* fix(environment): index stored deleted entities separately

* fix(environment): replay lists that include deleted records

* fix(environment): make no count of a made-object member earlier creates held alike

A count only a later create shows starts at the exemplar's value, which is
right only while the exemplar is the one create accepted: creates accepted
after it showed the member unchanged, which a count would have moved. The
merge now refuses such a count, as the top-level sequence check does.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: morluto <[email protected]>
2026-10-28 07:51:03 +08:00
PM baec375c34 fix(cli): validate outline-depth before numeric coercion (#7505)
Fixes morluto/rea#7425

Signed-off-by: pqminh27 <[email protected]>
2026-10-28 05:16:49 +08:00
PM 423d190026 fix(cli): preserve Markdown table edge spaces (#7506)
Fixes morluto/rea#7432

Signed-off-by: pqminh27 <[email protected]>
2026-10-28 05:16:28 +08:00
Beqa 6efb384261 docs(uninstall): clarify malformed shared config retention (#7507)
Uninstall retains unverifiable shared registration storage without blocking
REA-owned purge; document that exception beside the stop-before-removal rule.

Fixes #7320
2026-10-28 05:16:13 +08:00
5fcf0d2dcc fix(browser): save from gzip, deflate and cached responses within a bound (#7361)
A saved response's decoded size is bounded by its received size, or the
declared length of one served from cache, times 1032 for each gzip or deflate
coding, the most DEFLATE expands. Grafana answers folder creates gzipped, so a
UI step saving a new folder's uid was refused. Other codings and unknown or
zero sizes stay refused before the body is read.

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: morluto <[email protected]>
2026-10-28 05:08:27 +08:00
7bc6c5bf61 feat(environment): serve the writes callers make without recorded form fields (#7260)
* feat(environment): serve the writes callers make without recorded form fields

- A create member a recording sent empty ("", null, false, [] or {}) is no
  longer required; a call that leaves it out is applied as sending that value
  (body.blank).
- List views render envelope members that list the listed entities' ids
  (Paperless's `all`), so creates in such collections are confirmed.
- Updates move their entity in collections kept sorted by a field they write,
  and a failed whole-collection read after an update can teach that order.
- A later create that stores an unruled field otherwise is withheld at the
  create, and an item read may fill values a create's answer had not loaded.
- An unsent field respelled between null, [] and {} is unchanged.
- The skill asks for one create with every field and one with only what a
  person must give.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* docs(skill): show how to read signed-in pages before recording

Agents guessed capture keys (artifacts, dom, final) to see pages behind
sign-in, which inspect_web_page cannot reach.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): accept every empty spelling recorded creates sent

A create that fills only what a person must give sends `null` where the
full create sent a string, and `null` or `false` for the same flag. The
merged plan now accepts each recorded empty value, and a call that leaves
the member out is applied as the earliest call sent it.

Also adds the spec folder for this change.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* docs(skill): keep the vary-fields step neutral on UI or API writes

Agents read "the form" as a cue to write through the UI, where some
applications' request bodies are not exposed; a body-unusable UI write
now re-records once with request steps.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* docs(environment): record verification for blank members and sorted writes

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): address review of blank members and sorted writes

- Bulk updates move their entities in turn against the others' earlier
  values and install the final order once, as replay does.
- A member's recorded empty values stay accepted once a later call teaches
  its default; a left-out member with a default keeps the default.
- Write answers spell empty lists and maps as their recorded answers did.
- A rename can teach a sort when the creates chose one by a field the
  update did not write, such as ids that ascend with titles.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): answer sent empty members as sent, and keep a placed create's order

- A write answer spells an empty list or map the call did not send as its
  recorded answer did; one the call sent is answered as sent, so a create
  sending `[]` is not answered `null` because the first recorded create was.
- A read listing the whole collection that placed a create leaves the create
  only the sorted inserts whose order that read follows, so a later rename
  cannot teach a sort an earlier read ruled out.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): keep unloaded create answers, check bulk answers with sent members, narrow inserts only on collection-order reads

- A create whose item read loaded values its answer had not records them
  (`observation.read.unloaded`), and a served create's answer keeps them as
  the recorded answer showed them, a zero-time owner included, while the state
  holds the read's.
- Replay checks a bulk update's recorded answers with each target's sent
  members, as serving answers them, so echoed `null` and `[]` no longer make
  the plan body-unusable.
- A whole-collection read whose query sorts it shows the query's order, so it
  no longer narrows a create's sorted insert candidates.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): learn a sort from a rename only when moving its entities reaches it

Serving moves only the entities a write changes in a collection kept sorted,
so replay now tries a sort after a rename only when the entities no write the
read exercised changed already follow it. Seeded folders listed out of title
order no longer teach a title sort that serving would answer with another
order.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): keep unloaded create answers a sent value did not echo, and learn no sort from query-sorted reads

- A served create keeps a value its recorded answer had not loaded unless the
  call sent that member and the plan stores it as sent, so a create sending
  `active: false` is answered `false` as recorded while the state holds the
  read's `true`.
- A rename no longer teaches a sort from a whole-collection read its query
  sorts, whose ties need not follow the collection's own order.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): learn sorts only from orders a list read confirmed

- A sort retry after a failed whole-collection read requires the entities no
  write changed since a list last showed them to follow it already, rather than
  excusing every entity the update's tool ever wrote: an earlier rename a list
  confirmed in place stays where it was.
- An update takes its creates' sort only from a create a list read showed
  placed; replay still tries a sort a create guessed from the initial order.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): move updated entities only by placed sorts and keep query-sorted reads from confirming order

Replay moved an updated entity by a sort a create only guessed, while the
update plan exported only placed sorts, so a read could confirm an update
by a move serving would not make. Both now use the placed sort. A passing
list read clears its entities from the moved set only when the state
decides the order of every entity it lists, so a read whose query sorts
it no longer hides an earlier move from a later sort check.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): try a learned sort by moving each pending write in turn, as serving does

#retrySort sorted the whole collection, which a list could match while
serving, moving each renamed entity in turn against the others' values
before their own writes, gave another order. The state now records the
values each write replaced, and the retry replays the writes since a
list in collection order last showed their entities.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* chore(environment): set aside two hunks next to lines main's saved-values change rewrites

Main's #7267 changes the lines beside the blank-member docs and the
empty-spelling check in explainUpdate. Setting them aside lets that merge
apply cleanly; the commit after it puts them back on top of main's text.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* chore(environment): restore the set-aside hunks on main and bump the skill to 51

Puts the blank-member docs and the empty-spelling check back beside main's
slug and composed text and heldSlugRule. Main is at skill 50 with
different text, so this branch's skill changes take 51.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): keep sorts a list showed in place ruled out, and created answers' nested members as recorded

A whole list in collection order that showed a renamed entity where a
sort by the renamed field would have moved it now rules that sort out, so
a later rename of the same entity cannot teach it. A create's answer
leaves out members its item read added inside an object the answer
showed, as the recorded answer did; the state still stores them.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: morluto <[email protected]>
2026-10-28 05:07:28 +08:00
aa210b7ccc feat(environment): serve the writes that later reads explain (#7274)
* feat(environment): serve the writes callers make without recorded form fields

- A create member a recording sent empty ("", null, false, [] or {}) is no
  longer required; a call that leaves it out is applied as sending that value
  (body.blank).
- List views render envelope members that list the listed entities' ids
  (Paperless's `all`), so creates in such collections are confirmed.
- Updates move their entity in collections kept sorted by a field they write,
  and a failed whole-collection read after an update can teach that order.
- A later create that stores an unruled field otherwise is withheld at the
  create, and an item read may fill values a create's answer had not loaded.
- An unsent field respelled between null, [] and {} is unchanged.
- The skill asks for one create with every field and one with only what a
  person must give.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* docs(skill): show how to read signed-in pages before recording

Agents guessed capture keys (artifacts, dom, final) to see pages behind
sign-in, which inspect_web_page cannot reach.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): accept every empty spelling recorded creates sent

A create that fills only what a person must give sends `null` where the
full create sent a string, and `null` or `false` for the same flag. The
merged plan now accepts each recorded empty value, and a call that leaves
the member out is applied as the earliest call sent it.

Also adds the spec folder for this change.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* docs(skill): keep the vary-fields step neutral on UI or API writes

Agents read "the form" as a cue to write through the UI, where some
applications' request bodies are not exposed; a body-unusable UI write
now re-records once with request steps.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* docs(environment): record verification for blank members and sorted writes

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): address review of blank members and sorted writes

- Bulk updates move their entities in turn against the others' earlier
  values and install the final order once, as replay does.
- A member's recorded empty values stay accepted once a later call teaches
  its default; a left-out member with a default keeps the default.
- Write answers spell empty lists and maps as their recorded answers did.
- A rename can teach a sort when the creates chose one by a field the
  update did not write, such as ids that ascend with titles.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* feat(environment): serve the writes that later reads explain

- An item read whose object is marked deleted (deleted, isDeleted or
  is_deleted true) agrees with an absent entity, as EspoCRM answers a
  deleted record; such an object never seeds or adopts an entity.
- maxSize, max_results and maxResults are page sizes, for whole-collection
  list lines and the list query's limit role.
- An item view also shows the fields only reads after its type's first write
  show, when no initial entity holds them (Vikunja's task subscription),
  without requiring them; write answers leave such a field out for entities
  without it, and it no longer withholds the write as incomplete-entities.
- The skill asks updates to change the fields a person would change, and
  expects a deleted object to read back as 404 or marked deleted.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): answer sent empty members as sent, and keep a placed create's order

- A write answer spells an empty list or map the call did not send as its
  recorded answer did; one the call sent is answered as sent, so a create
  sending `[]` is not answered `null` because the first recorded create was.
- A read listing the whole collection that placed a create leaves the create
  only the sorted inserts whose order that read follows, so a later rename
  cannot teach a sort an earlier read ruled out.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): answer bulk updates without fields only later reads show

A bulk update's answer renders each entity with the same allowance a single
update's answer has: a field only reads after the type's first write showed,
which no initial entity holds, is left out for an entity without it, so a
bulk update of a seeded task is no longer refused for lacking it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* docs(environment): correct how the EspoCRM contact update task passes

The agent trajectories show gpt-oss passes it by creating a replacement contact
and deleting the original, since the served update refuses emailAddress; the
update itself is refused in both builds.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): answer a read of a served-deleted entity marked deleted

An application that soft-deletes still answers a deleted record by id, marked
deleted. Serving now keeps an entity a served delete removed from a type whose
recorded item reads show that, until a reset or load and within the size
data/add loads, and answers a read of it with its last fields and the flag
true, instead of refusing the read.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): keep unloaded create answers, check bulk answers with sent members, narrow inserts only on collection-order reads

- A create whose item read loaded values its answer had not records them
  (`observation.read.unloaded`), and a served create's answer keeps them as
  the recorded answer showed them, a zero-time owner included, while the state
  holds the read's.
- Replay checks a bulk update's recorded answers with each target's sent
  members, as serving answers them, so echoed `null` and `[]` no longer make
  the plan body-unusable.
- A whole-collection read whose query sorts it shows the query's order, so it
  no longer narrows a create's sorted insert candidates.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* docs(environment): record the census with main's unknown-clock rule merged

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): learn a sort from a rename only when moving its entities reaches it

Serving moves only the entities a write changes in a collection kept sorted,
so replay now tries a sort after a rename only when the entities no write the
read exercised changed already follow it. Seeded folders listed out of title
order no longer teach a title sort that serving would answer with another
order.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): keep unloaded create answers a sent value did not echo, and learn no sort from query-sorted reads

- A served create keeps a value its recorded answer had not loaded unless the
  call sent that member and the plan stores it as sent, so a create sending
  `active: false` is answered `false` as recorded while the state holds the
  read's `true`.
- A rename no longer teaches a sort from a whole-collection read its query
  sorts, whose ties need not follow the collection's own order.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): learn the soft-delete flag in the state view and serve by it

Extraction now records the member later item reads set true on a record the
application soft-deleted, and the item view carries it as `deleted_flag`.
Serving reads that fact instead of scanning recorded bodies itself, so one
owner decides what a soft delete looks like.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): learn the soft-delete flag from item reads with a query too

A client may read a deleted record back only with a query, such as a
cache-busting parameter. Every successful read of the item tool now shows the
flag; only plain later reads still add fields.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): learn sorts only from orders a list read confirmed

- A sort retry after a failed whole-collection read requires the entities no
  write changed since a list last showed them to follow it already, rather than
  excusing every entity the update's tool ever wrote: an earlier rename a list
  confirmed in place stays where it was.
- An update takes its creates' sort only from a create a list read showed
  placed; replay still tries a sort a create guessed from the initial order.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): drop entities read back as deleted and answer soft deletes as deleted

- A held entity a later read shows marked deleted is dropped, not adopted with
  the marked object's fields.
- Bulk-update replay renders answers with the optional and later-only fields
  serving uses, so a bulk update of created and seeded entities replays.
- A served delete of a soft-deleting type answers with the entity marked
  deleted, single and bulk.
- A deleted flag only query reads showed is a field of the item view, so a
  served tombstone keeps it where live records omit it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): move updated entities only by placed sorts and keep query-sorted reads from confirming order

Replay moved an updated entity by a sort a create only guessed, while the
update plan exported only placed sorts, so a read could confirm an update
by a move serving would not make. Both now use the placed sort. A passing
list read clears its entities from the moved set only when the state
decides the order of every entity it lists, so a read whose query sorts
it no longer hides an earlier move from a later sort check.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): try a learned sort by moving each pending write in turn, as serving does

#retrySort sorted the whole collection, which a list could match while
serving, moving each renamed entity in turn against the others' values
before their own writes, gave another order. The state now records the
values each write replaced, and the retry replays the writes since a
list in collection order last showed their entities.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): answer a served delete with the deleted flag its recorded answer showed

An application may answer a delete with the record as it was and mark it
deleted only on later reads. Delete answers, single and bulk, now take
the flag's value from the recorded answer; reads of a served-deleted
entity still show it marked deleted.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* chore(environment): set aside two hunks next to lines main's saved-values change rewrites

Main's #7267 changes the lines beside the blank-member docs and the
empty-spelling check in explainUpdate. Setting them aside lets that merge
apply cleanly; the commit after it puts them back on top of main's text.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* chore(environment): restore the set-aside hunks on main and bump the skill to 51

Puts the blank-member docs and the empty-spelling check back beside main's
slug and composed text and heldSlugRule. Main is at skill 50 with
different text, so this branch's skill changes take 51.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* chore(environment): bump the skill to 52 above #7260's 51

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): keep sorts a list showed in place ruled out, and created answers' nested members as recorded

A whole list in collection order that showed a renamed entity where a
sort by the renamed field would have moved it now rules that sort out, so
a later rename of the same entity cannot teach it. A create's answer
leaves out members its item read added inside an object the answer
showed, as the recorded answer did; the state still stores them.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(environment): compare soft-deleted read fields

* fix(environment): preserve initially deleted item observations

* fix(environment): index stored deleted entities separately

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: morluto <[email protected]>
2026-10-28 05:05:21 +08:00
Wenyu Chiou ef11f77836 fix(evidence): Reject non-file destinations before overwrite advice (#7495)
Check destination kind before overwrite policy and cover directory recovery,
regular-file controls, symlink modes and preservation.

Fixes #7444.

Independent review: APPROVE for patch SHA-256
3d65eb23d01da9b6df786ad0db337dcff79d85923f23678a5168babb88c16080,
relayed by the coordinating thread (reviewer identity not supplied in the
delegated packet). Triggers: file I/O, error handling, three shipping files.

Validation: baseline/candidate runtime builds and actual synthetic CLI probes;
initial focused run 70 passed, 2 Windows EPERM fixture failures, 3 skipped.
Final changed filesystem file 42 passed, 5 skipped; symlink semantics and
full aggregate checks remain unverified. Targeted format/lint/discovery passed.
2026-10-28 03:41:26 +08:00
0cd7ef456b feat(plugins): make Claude Code and Codex installs self-contained (#7514)
Ship Claude Code and Codex from one release-pinned npm artifact with bundled runtime dependencies, extraction-only MCP qualification, plugin-aware recovery, and synchronized release metadata.

Integrates #2815 and the original proposal #1977. The original commits remain available on the PR branch; retain their author and assistant credit in this squash.

Validation: required CI passed, real Codex/Claude loading verified, and reviewed packaging/recovery findings addressed.

Co-authored-by: GamerDubz <[email protected]>
Co-authored-by: morluto <[email protected]>
Co-authored-by: OpenAI Codex <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-27 17:13:17 +00:00
gensokyo-dev 269630f4e1 docs: archive historical verification and release inventories (#7519)
Retire redundant historical JSON copies from the active documentation tree after verifying identical published release snapshots and an immutable route-report archive. Preserve current release data, original evidence, release publication logic and historical access.

Validation: 48 release tests; documentation conformance and rendered archive link/digest; Git blob/SHA-256 checks; selected current-head CI passed. Greptile: 5/5 with no actionable findings. Codex GitHub review unavailable due quota.

Closes #7517.
2026-10-28 00:54:54 +08:00
gensokyo-dev 65f1c44734 refactor: retire superseded test-only runtime entrypoints (#7515)
Migrate MCP session tests to the named lifecycle output schema and preserve raw reply envelopes. Move R8 type/alias checks to the production field resolver and recovery guidance checks to their scoped factory; remove superseded test-only exports and an unused import.

Validation: 99 focused tests, an exact-old-schema negative control, full selected CI including all 8 shards and source tests, and Greptile source review passed. Fresh-head real stdio MCP analyzed [email protected] and retained a complete authenticated Evidence bundle after close; compact/full catalogs are unchanged. Full local typed lint hit the 3 GiB memory guard; scoped typed lint passed. Codex review quota exhausted.

Fixes #7513.
2026-10-28 00:22:43 +08:00
gensokyo-dev 6eb37a7749 fix(ci): restore qualification and reduce validation overhead
Use one typed-checker worker by default without changing semantic scope or caller overrides. Reuse the bounded CLI worker for pure argument validation while retaining fresh-process coverage and all upstream cases.

Keep registration tests independent of real package-runner downloads while asserting the actual prewarm command and startup context. Pass the advertised binary_session definition by name to package verification, retaining default SDK validation and complete catalog/contract checks while avoiding compilation of unrelated tool outputs. Preserve all four installed-tarball scenarios and existing deadlines.

Strengthen Hermes preview operation/backup assertions and clarify physical-path routing documentation.

Assisted-by: Suika (gensokyo-skills:suika-scatter-gather-planning)
2026-10-28 00:20:07 +08:00
S1MonandS1Mon bcc6775dc1 fix(cli): reject non-decimal Android overload index tokens (#7493)
Android overload selection declared a plain number, so the CLI argument
parser coerced the raw token with Number before the schema ran. An empty
token therefore arrived as 0, and hexadecimal, exponent, signed, and
float spellings arrived as their numeric value, leaving both commands to
proceed to artifact admission on a selector the caller never wrote as a
decimal integer.

Reuse the existing decimal token parser that the other integer CLI
options already use. Omitted and explicit decimal zero keep their current
meanings, and the published schema for overloadIndex is unchanged.

Validation: npm run compile, the cli-boundary vitest project for
tests/boundary/cli/cliIntegerArguments.test.ts (56 passed), npm run
typecheck, npm run lint, npm run lint:typed, npm run format:check, npm
run metadata:check, npm run verify:architecture-guards, and npm run
knip:production. The new cases fail against the base source and pass with
the change.

Fixes #7430.

Co-authored-by: S1Mon <[email protected]>
2026-10-27 23:25:32 +08:00
Pirro Zani 313af7119c fix(website): keep phone navigation labels from splitting mid-word (#7497)
The phone header gave each link an equal minmax(0, 1fr) column and
allowed overflow-wrap: anywhere, so single-word labels such as
"Showcases", "Anleitungen", "Руководства" and "Посібники" broke between
letters at 320-430px. Links now share the row as flex items that never
shrink below their longest word. Labels wrap only at spaces, and a word
wider than the whole row can still break, preserving the enlarged-text
fit from #3598.

Refs #3597
2026-10-27 23:25:04 +08:00
gensokyo-dev a95b83c2b0 perf(javascript): cache callable result syntax by AST identity (#7508)
Cache direct return/yield source expressions by immutable AST identity for all existing static and semantic consumers. Weak keys bound retention to AST lifetime; mutable analysis facts and Evidence remain unchanged. Refs #6311; full CastleIO analysis still times out.

Validation: 225 focused tests; baseline cache-regression controls; build, serial check:fast and test discovery passed. CastleIO direct-syntax facts equal with 78,272 warm lookups reduced from 2.72-2.81s to 13.0-13.4ms. Complete sourcemap-codec CLI analyses retained byte-identical Evidence; no end-to-end speedup or token reduction claimed.

CI: source 20,888 passed / 15 skipped; seven built-dependent shards, static, docs, package builds, Linux native baseline and real JS recovery CLI/MCP passed. Shard 6 Hermes path-preview failures both reproduce on exact main b9d1a61dd; Portable tests fails downstream. Greptile 5/5, no findings; Codex quota exhausted. Authorized admin bypass for unrelated baseline CI and unchecked human readiness; no human review attestation made.
2026-10-27 22:53:42 +08:00
morluto 7f90549916 chore(android): remove unused type exports 2026-10-27 22:52:33 +08:00
morluto b3fd0aa3ee test(setup): align Hermes symlink preview expectations 2026-10-27 22:52:33 +08:00
Beqa b9d1a61dd5 docs(ghidra): clarify Objective-C Message Analyzer libobjc log (#7249)
Clarify the Objective-C analyzer message, coverage limitation, structured failure diagnostics, and existing opt-out/startup resource controls.

Verification: exact documentation change applied on main 79d2fc054; docs:check passed. Installed Ghidra 12.2 analyzer/scheduler and REA error/launch owners inspected. No real macOS target reproduction; original failure remains unknown. PR CI and Greptile passed; Codex review unavailable.
2026-10-27 22:28:29 +08:00
Adolanium 79d2fc0540 fix(doctor): classify an unreadable skill identity as unknown (#7090)
rea doctor reported a skill copy with an unreadable or unresolved path as identity state unknown but classified its skill:identity check as config_drift, which claims the copy has drifted. docs/installation.md keeps an unreadable skill identity unknown, and the sibling registration checks already classify unknown state as unknown. The skill check and the DoctorScope fallback now do the same.
2026-10-27 21:34:05 +08:00
Saiyam Jain 097cd2c988 fix(browser): report non-absolute export paths at their input field (#7097) 2026-10-27 21:33:49 +08:00
84f62f76e5 fix(hermes): preserve external YAML anchor consumers (#5152)
* fix(hermes): preserve consumers of anchored registrations

Keep original alias values and comments before setup or uninstall replaces an anchored REA node. Preserve original nested bindings and retained sharing through the common YAML writer.

Fixes #3325

* test(hermes): assert retained alias object identity

* fix(hermes): preserve removed keys and surviving alias owners

* test(cli): restore retained-view cancellation phase coverage

* fix(setup): retain copied Hermes anchor comments

* chore(hermes): reverse incidental merge hook changes

Reverse the merge relative to its included-main parent to restore exact upstream bytes. Reapply only the independently reviewed shared-owner and regression patch in a normal follow-up commit. Keep all ordinary history and normal hooks; no remote push of the rejected hook drift.

Refs #3325

* fix(hermes): retain reviewed shared-owner integration

* chore(hermes): reverse incidental merge hook changes

Reverse the merge relative to its included-main parent to restore exact upstream bytes. Reapply only the independently reviewed shared-owner and regression patch in a normal follow-up commit. Keep all ordinary history and normal hooks; no remote push of the rejected hook drift.

Refs #3325

* fix(hermes): retain exact reviewed shared-owner regression

* test(native): align plist command fixtures with main

Adopt the two maintained-main fixture files as of
dd198d037f. Distinguish exact XML lint
validation from conversion commands and retain truthful provenance,
source-value, format, limitations and unknown-scalar assertions.

The original seven provenance failures reproduce before this repair.
The affected three-file group passes all 86 checks afterward. Product
code and installed-package bytes remain unchanged.

Co-authored-by: morluto <[email protected]>

* fix(native): retain bound-target dispatch failure contracts

Adopt morluto's canonical Apple dispatch metadata owner and bounded MCP
and cleanup controls from 41e10b240c.
Keep fresh admission separate from already-bound target changes and
retain access-denied classification, exact physical causes and cleanup
ownership. Update the two stale unit expectations exposed by Source CI.

The original unit pair reproduces before the correction. Current focused
regressions, package verification and installed feature controls pass.
Only the canonical Apple owner and related controls are ported; this does
not integrate unrelated later-main changes or create another contribution.

Co-authored-by: morluto <[email protected]>

* test: align plist provenance and package listing fixtures

Keep the exact XML lint invocation in CLI provenance and include the
required CLI environment owner in the synthetic package member list.
Preserve binary provenance and all LF, CRLF, literal-CR and bytecode checks.

* revert(hermes): restore canonical main before reviewed replay

This reverts commit 575a10d194, reversing
changes made to bb17e90f27.

* test(cli): retain full cancellation oracle with prepared projection

---------

Co-authored-by: hhang <[email protected]>
Co-authored-by: gensokyo-dev <[email protected]>
Co-authored-by: morluto <[email protected]>
2026-10-27 21:33:17 +08:00
Afloat 095a51cf38 fix(browser): reject invalid UTF-8 in HTTP CDP discovery (#4524) 2026-10-27 21:33:01 +08:00
Jeff d47efe1a54 fix(native): reject mixed dyld refusal diagnostics (#5778)
* fix(native): classify confirmed dSYM dyld refusals

* style(native): format target refusal handling

* fix(native): confirm precise Mach-O layout refusals before export fallback

* test(cli): observe cancellation at stable source resolution boundary

* fix(native): reject contradictory dyld layout refusals

* fix(native): preserve mixed dyld diagnostic failures

* fix(native): retain the evidence behind contradictory layout refusals

When the imports and exports refusals disagree, or a refusal disagrees with
the parsed Mach-O header, inspect_macho returned a generic output error that
kept only one dyld_info capture. It dropped the header file type and the other
refusal, so the failure could not show why the admitted diagnostic was
rejected.

The contradiction now has its own error. Its diagnostics keep the parsed file
type and every admitted refusal (mode, admitted layout, exit code and output),
and the captured output is still the conflicting command. The test checks the
reason and the retained diagnostics for each contradiction case.

* test(native): allow time for real Mach-O CLI and MCP controls
2026-10-27 21:32:13 +08:00
Ahmed cbeeeda97b fix(javascript): resolve preceding exported variable initializers and computed method keys (#5387)
fix(javascript): resolve variable initializers before computed method keys
2026-10-27 21:31:59 +08:00
Chip Li b4118b9683 docs(hermes): explain nested-home routing and preview limits (#4145)
* docs(hermes): clarify sticky profiles for nested homes

* docs(hermes): qualify nested homes beneath default symlinks

* docs(hermes): cover canonicalized ancestor paths

* docs(hermes): clarify both missing-path containment errors
2026-10-27 21:31:40 +08:00
morluto ec3f84b626 test(ci): avoid pinning skip warning wording 2026-10-27 21:26:38 +08:00
1201c25542 fix(ci): align static checks and regression fixtures with current contracts (#7349)
* fix(ci): retire unused internal analysis declarations

* test: use an admitted app bundle for provider routing

(cherry picked from commit 00aa17f12a)

* test: keep bundle routing coverage outside oversized group

(cherry picked from commit 8658353b0d)

* fix(ci): align regression fixtures with current admission contracts

* test: bind extraction close fixtures to their ZIP digest

(cherry picked from commit 626d0250db)

* test(cli): match typed analysis outcomes in function detail fixture

* test(ghidra): preserve empty Java property separator framing

* test(electron): retain unknown IPC ownership for native module gaps

* fix(electron): share source-scoped call path identity

* test(captures): assert safe source identity on SAZ refusal

* test(apple): match root occurrence kind to inventory manifest

* fix(examples): align directory root occurrence identity

* test(electron): align equivalent fixture assertions with upstream

* test(process): assert retained capture root identity

* test(windows): declare the PE fixture optional header

* test(ci): refresh owned registration and verifier fixtures

* test(setup): use owned registrations for refresh fixtures

* test(process): control unavailable ownership in CLI cleanup fixture

* test(process): guard controlled compiler output arguments

* test(setup): align equivalent entrypoint guards with upstream

* test(process): release the cleanup target through an owned gate

* test(ci): align semantic identities and owned fixture facts

* test(pcap): retain the upstream DNS wire fixture branch

* test(pcap): use the canonical wire ownership fixture

* test(pcap): assert exact DNS wire query labels

* test(ci): preserve admission and ownership fixture prerequisites

* test(ci): align authenticated fixture and cleanup boundaries

* test(windows): align native artifact fixtures with upstream

* test(android): request complete evidence for inline MCP contracts

* test: avoid pinning diagnostic wording

* test: verify cleanup diagnostics at their source

* test: strengthen evidence and cleanup checks

* test: avoid pinning registration guidance

---------

Co-authored-by: Plastic-time <[email protected]>
Co-authored-by: morluto <[email protected]>
2026-10-27 21:24:30 +08:00
497 changed files with 25474 additions and 126585 deletions

No files matched your search

+21
View File
@@ -0,0 +1,21 @@
{
"name": "rea",
"interface": {
"displayName": "REA"
},
"plugins": [
{
"name": "rea",
"source": {
"source": "npm",
"package": "rea-agents",
"version": "6.8.0"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "Developer Tools"
}
]
}
@@ -2,7 +2,7 @@
name: reverse-engineer-anything
description: Reverse engineer native, managed, Electron/JavaScript, packaged, firmware, and browser targets with REA, and build agent environments (AgentEnv bundles) of web applications from a live instance or a capture. Use shipped-artifact or requested runtime evidence to explain features, compare versions, decompile code, or guide a reconstruction. Skip REA for ordinary source-repository architecture analysis.
metadata:
version: "50"
version: "53"
---
# REA
@@ -17,6 +17,9 @@ readiness or provider commands.
Installing this skill supplies instructions; it does not register the REA MCP
server, install analysis engines, or add tools to an already-running agent.
The exception is a Claude Code or Codex plugin install: the plugin bundles
this skill and registers its own `rea` server, outside the state that
`rea setup` and `rea doctor` manage.
If REA tools are available and their registration is not known to be stale,
proceed directly to the target. Do not run diagnostics or setup before every
@@ -37,42 +40,23 @@ repeat or known not to have started. If it may already have changed the
target, or it fails again, tell the user and ask them to restart the MCP
client. If REA could not start its connection, retry once, then tell the user
and ask them to restart the MCP client. Do not retry a startup failure that
returns the same unchanged result. Run `rea doctor` when REA could not start
returns the same unchanged result. For plugin installs, follow
[plugin recovery](references/connection-and-recovery.md#rea-installed-as-a-client-plugin).
For setup-owned registrations, run `rea doctor` when REA could not start
or tools stay missing after that restart. For `cleanup_incomplete`, a retryable
capacity limit, or `configuration_invalid`, follow the recovery guide's ladder
before asking for a restart. Read that guide before a second repair attempt.
1. Diagnose without changing REA or client configuration. `npx` may download
the package and write to npm's cache. For Codex, run
`npx -y rea-agents@latest doctor --client codex --json`. Substitute the current
supported client: `claude_code`, `claude_desktop`, `codex`, `cursor`,
`gemini_cli`, `windsurf`, `devin`, `opencode`, `antigravity`, `copilot_cli`,
`commandcode`, `vscode`, `grok_build`, `omp`, or `grok_bot`. `grok_bot` has no local
file registration: doctor reports the Grok Bot chat step, and setup does not
write `mcp.json`. If the client is unknown, use `doctor --json` and
inspect its registration results before choosing a setup scope.
2. Distinguish the reason. Missing, malformed, or stale registration needs a
scoped configuration repair. An aligned registration with no tools in the
active session needs a restart/reconnection; doctor cannot prove that the
current agent has connected. A missing provider affects only tasks requiring
that provider: static JavaScript inspection needs neither Hopper nor Ghidra,
and Android inspection has separate bring-your-own JADX/Java prerequisites.
3. For a configuration repair, prepare the read-only plan:
`npx -y rea-agents@latest setup --client codex --dry-run --json`.
Use the current client's ID, show its exact proposed paths, backups, and
changes, and obtain approval before setup writes configuration or installs
Hopper. Setup normally installs the matching bundled skill too; include that
replacement in the reviewed plan. After approval, apply the same scope with
`npx -y rea-agents@latest setup --client codex --yes`. Add `--install-hopper`
only if that separate installation was needed and explicitly approved.
4. Restart/reconnect the affected agent. Verify that REA tools actually appear
in the session, then resume the original investigation. If they remain
absent, inspect the client's MCP launch error rather than repeating setup.
Choose the installation owner before repairing registration. Plugin installs use
[plugin recovery](references/connection-and-recovery.md#rea-installed-as-a-client-plugin);
only setup-owned registrations use the guide's
[registration repair plan](references/connection-and-recovery.md#repair-registration-when-needed).
A plugin-only doctor result of `missing` does not call for `rea setup`.
REA setup never installs or upgrades Node.js, npm, Homebrew, Java, Ghidra, IDA,
JADX, Kuna, Binwalk, or Unblob. Use existing prerequisites; do not install unrelated
software to repair MCP registration. For an unsupported client, use manual stdio
registration with a version-pinned `rea-agents` package or continue through the CLI.
registration with an existing installation’s absolute runtime and entry script, or continue through the CLI.
A concrete CLI fallback for an operator-supplied JavaScript tree or ASAR is:
@@ -85,8 +69,11 @@ graph, limitations, and unknowns with the same care as an MCP result; the CLI
returns the Evidence record directly. This fallback does not establish that
MCP is configured. Native CLI tasks still require their selected engine.
Prefer an existing `rea` CLI. When none is installed and REA package acquisition
is within the authorized scope, use `npx -y rea-agents@latest` in its place.
Configuration repair starts with a read-only setup plan; show paths, backups and
is within the authorized scope, use `npx -y rea-agents@latest` in its place from a
trusted directory outside the target project, with absolute target paths. Managed
setup binds the current installation directly; retain its runtime and package
cache entry, or restore that installation before repairing its registration.
For setup-owned registration, configuration repair starts with a read-only setup plan; show paths, backups and
skill replacement before applying authorized changes. Preserve existing approval
and target-execution boundaries.
@@ -17,7 +17,7 @@ does not establish which server an approval wildcard covers. See
| Observation | Meaning and next step |
| --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| No REA tools in the active session | Inspect registration with `rea doctor --client codex --json`. A skill installation alone does not register MCP. |
| No REA tools in the active session | If REA was installed as a client plugin, follow the plugin section below. Otherwise inspect registration with `rea doctor --client codex --json`. A skill installation alone does not register MCP. |
| Missing, malformed or stale registration | Prepare a scoped setup plan; apply only authorized changes with backups. |
| Aligned registration, but no connected tools | Restart/reconnect the client. Doctor checks registration; it cannot establish the current agent's connection. Inspect the client's launch error if reconnection fails. |
| MCP connection dropped or REA could not start | If the connection drops, retry that call once only when it is safe to repeat or known not to have started. If it may already have changed the target, or the same failure returns, tell the user REA's connection failed and ask them to restart the MCP client. If REA could not start its connection, retry once, then tell the user and ask them to restart the MCP client. Do not retry a startup failure that returns the same unchanged result. Run `rea doctor` when REA could not start or tools stay missing after restart. Doctor cannot restore a live connection by itself. |
@@ -116,6 +116,32 @@ is not a recommendation to change it. Do not invent a setting. Host memory and
- `invalid_request`: correct the reported fields from the current schema. Do
not repeat the unchanged call.
## REA installed as a client plugin
A Claude Code or Codex plugin owns its `rea` server and this skill. Doctor's
`--client` and `--skill` checks and `rea setup` read and write only
setup-owned state, so for a plugin-only install they report `missing`, and
applying setup would add a second `rea` server. If this skill was loaded from
a plugin, or the client lists a `rea` plugin, do not prepare a setup plan for
missing tools. Instead:
- Check that the plugin is installed and enabled with the client's own
listing: `claude plugin list` and `/mcp` in Claude Code, `codex plugin list`
in Codex.
- Restart or reconnect the client, then read its launch error for the `rea`
server if tools are still absent. The plugin starts its bundled runtime; a missing build or version mismatch
requires a reinstall, or a fresh build when developing from source.
- Check analysis engines through the connected server's
`binary_session.result.tool_availability`. `rea doctor --provider <id> --json`
from a shell reports that shell's environment, which can differ from the
environment the client gave the plugin server. If you still run it through
the package runner, use the version pinned in the plugin manifest's server
entry (`rea-agents@<version>`), not `@latest`, so it matches the server the
plugin starts.
Use the setup repair below only when the user wants a setup-owned registration
instead of the plugin, and remove or disable the plugin first.
## Repair registration when needed
For Codex:
@@ -130,7 +156,10 @@ Use the current supported client's ID: `claude_code`, `claude_desktop`, `codex`,
`copilot_cli`, `commandcode`, `qwen_code`, `vscode`, `trae`, `grok_build`, `omp`, `pi`,
`hermes`, `qoder`, `droid`, `zcode`, `dsh`, `freebuff`, or `grok_bot`. If unknown, inspect `doctor --json` before choosing a
scope. `grok_bot` uses a chat registration step rather than local `mcp.json`.
For unsupported clients, use version-pinned manual stdio registration or CLI.
For unsupported clients, use manual stdio registration with the selected
installation’s absolute runtime and entry script, or continue through the CLI.
Acquire packages from a trusted directory outside the target project. Keep a
package-runner installation and its runtime available while registrations use it.
Show the plan's exact paths, backups, changes and bundled-skill replacement.
Obtain approval for writes when that concrete scope is not already authorized,
@@ -23,6 +23,14 @@ again if a hint names a gap, then writes the bundle with
- Map the application first with `inspect_web_page` (and `capture_web_screenshot`
when layout matters): the login form's locators, and the API calls behind the
pages that list and edit the entities the user asked about.
- `inspect_web_page` cannot sign in. To see pages behind sign-in, run a short
`capture_browser_scenario` that signs in and opens them, with
`"capture": {"after_each_step": ["accessibility"]}` (`"dom"` for markup).
Read a step's tree with `inspect_analysis_view`, `view: {kind:
"browser-scenario", collection: "text", step_index, artifact:
"accessibility", offset: 0, limit: 2048}`, paging with `offset`. Capture
options are only `after_each_step`, `at_end`, `events`, `network` and
`limits`.
## Record a session an environment can learn writes from
@@ -124,8 +132,10 @@ client with it. Match `wait_for` locators to exactly one element.
as a secret reference), submit, then `wait_for` an element only a signed-in
user sees.
2. Read with `request` steps: each collection the environment needs, twice
before any write, so fields that change on their own show up; one item of
each.
before any write, so fields that change on their own show up; then each
object a list shows by id, up to ten per collection. An object known only
from a list that leaves out fields its own read shows cannot be read by id
or updated in the environment.
3. Write the way the application's UI does: fill its forms and click its
buttons, so each request carries the fields the UI sends, including ones
the server would otherwise derive. Give the click or key press that submits
@@ -145,8 +155,17 @@ client with it. Match `wait_for` locators to exactly one element.
read 2, list; never several writes before their reads.
4. Leave more than a second between a create and its first edit
(`wait_for_timeout` with `duration_ms: 1100`).
5. Update both objects and read each back; delete both and read each back
(expect 404) and the list.
5. Update both objects and read each back, changing the fields a person
would change (a name, an email address, a status, a due date) rather than
only a description: a served update accepts only the members a recorded
update stored. Delete both and read each back (expect 404, or the object
marked deleted) and the list.
6. Vary what the two creates send, however you write them: the first every
field the application takes, the second only what a person must give (its
title or name), with the other fields left out or empty. A served create
requires each member both creates filled in and lets a caller leave out one
a create left out or sent empty, so an agent that names only a title is not
refused for fields it had no reason to set.
About `request` steps:
@@ -226,7 +245,7 @@ withheld with its hint:
| `no-call` | Re-record once with a write that succeeds. |
| `side-effect` | Stop: the application changes other entities. |
| `contradicted` | Stop: a later read disagrees with the modelled effect. |
| `body-unusable` | Stop: the body is not a JSON object. |
| `body-unusable` | Re-record a UI write once with `request` steps (its body was not exposed); else stop. |
Use new scratch names on each attempt, and delete what an earlier attempt
created before recording again (through the UI, or a `request` step with the
@@ -652,3 +652,20 @@ page cannot prove absence in unknown coverage. Retained views are historical,
not fresh filesystem checks. No decryption, Godot resource decoding, GDScript
decompilation or target execution is provided. See
[Godot PCK](https://github.com/morluto/rea/blob/main/docs/godot-pck.md).
UnityFS AssetBundle containers (`.bundle`, `.ab`, YooAsset packages and other
byte-identified files) list `CAB-*` SerializedFiles and `.resS` members with
header and node facts; none/LZ4/LZ4HC member blocks stream on demand. Bundles
declaring asset-bundle encryption keep header facts with unknown directory
coverage; LZMA member bytes retain named occurrences with unavailable hashes.
Virtual member offsets are not physical carrier locations. Format 6–8 readers
cache one decoded block and support retained filtered/paged views. No
decryption or SerializedFile object decoding is provided. See
[UnityFS](https://github.com/morluto/rea/blob/main/docs/unityfs.md).
U-Boot legacy uImages use the same inspection/extraction workflows. Prefer a
retained summary and selected inventory views for header facts or component
IDs. Header addresses/CPU/OS are declarations; payloads remain as stored.
Multi-file components retain their aligned physical ranges; the full payload
is also materialized. Unknown name text retains its original 32 bytes. See
[U-Boot uImage](https://github.com/morluto/rea/blob/main/docs/uimage.md).
+21
View File
@@ -0,0 +1,21 @@
{
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
"name": "rea",
"description": "Reverse engineer anything with agents, from app behavior down to native binaries.",
"owner": {
"name": "morluto",
"url": "https://github.com/morluto"
},
"plugins": [
{
"name": "rea",
"description": "Reverse engineer anything with agents, from app behavior down to native binaries.",
"source": {
"source": "npm",
"package": "rea-agents",
"version": "6.8.0"
},
"category": "development"
}
]
}
+29
View File
@@ -0,0 +1,29 @@
{
"name": "rea",
"version": "6.8.0",
"description": "Reverse engineer anything with agents, from app behavior down to native binaries.",
"author": {
"name": "morluto",
"url": "https://github.com/morluto"
},
"homepage": "https://rea.tools/",
"repository": "https://github.com/morluto/rea",
"license": "MIT",
"keywords": [
"reverse-engineering",
"binary-analysis",
"decompiler",
"mcp-server"
],
"skills": "./skills",
"mcpServers": {
"rea": {
"command": "node",
"args": [
"${CLAUDE_PLUGIN_ROOT}/scripts/plugin-mcp-launcher.mjs",
"6.8.0"
]
}
},
"icon": "./assets/rea-plugin.png"
}
+13
View File
@@ -0,0 +1,13 @@
{
"mcpServers": {
"rea": {
"command": "node",
"args": [
"./scripts/plugin-mcp-launcher.mjs",
"6.8.0"
],
"cwd": ".",
"startup_timeout_sec": 30
}
}
}
+40
View File
@@ -0,0 +1,40 @@
{
"name": "rea",
"version": "6.8.0",
"description": "Reverse engineer anything with agents, from app behavior down to native binaries.",
"author": {
"name": "morluto",
"url": "https://github.com/morluto"
},
"homepage": "https://rea.tools/",
"repository": "https://github.com/morluto/rea",
"license": "MIT",
"keywords": [
"reverse-engineering",
"binary-analysis",
"decompiler",
"mcp-server"
],
"skills": "./skills",
"mcpServers": "./.codex-plugin/mcp.json",
"interface": {
"displayName": "REA",
"shortDescription": "Reverse engineer anything",
"longDescription": "Reverse engineer anything with agents, from app behavior down to native binaries.",
"developerName": "morluto",
"category": "Developer Tools",
"capabilities": [
"Interactive",
"Read",
"Write"
],
"websiteURL": "https://rea.tools/",
"defaultPrompt": [
"Use REA to explain how this binary implements a feature.",
"Use REA to compare two versions of this application.",
"Use REA to recover the sources from this JavaScript bundle."
],
"composerIcon": "./assets/rea-plugin.png",
"logo": "./assets/rea-plugin.png"
}
}
+6
View File
@@ -753,6 +753,11 @@ jobs:
- name: Run native Apple and process boundary regressions
run: >-
npm run test:focused --
tests/boundary/providers/hopper/hopperMacLaunchArguments.test.ts
tests/acceptance/providers/hopperStartupRecovery.test.ts
tests/boundary/providers/hopper/hopperClient.helperFailure.test.ts
tests/boundary/providers/hopper/bridgeBootstrapCleanup.test.ts
tests/boundary/providers/hopper/bridgeTargetLease.integration.test.ts
tests/acceptance/artifactDmgAttachmentRecoveryCli.test.ts
tests/boundary/providers/native/nativeDmgArtifactReader.test.ts
tests/process-global/dmgCancellationReason.test.ts
@@ -901,6 +906,7 @@ jobs:
tests/boundary/process/packageNpmInvocation.test.ts
tests/boundary/process/packedBridgeVerification.test.ts
tests/boundary/process/windowsOutputCapture.test.ts
tests/boundary/process/windowsExecutableSelection.test.ts
tests/acceptance/webModuleCli.test.ts
tests/acceptance/webSourceLocationCli.test.ts
tests/boundary/cli/referenceSourceImportStatus.test.ts
+4 -1
View File
@@ -10,7 +10,10 @@
"node_modules/**",
"CHANGELOG.md",
"package-lock.json",
".claude-plugin/plugin.json",
".codex-plugin/plugin.json",
"_reference/**",
"third_party/**"
"third_party/**",
".codex-plugin/mcp.json"
]
}
+2 -2
View File
@@ -2683,7 +2683,7 @@ Read the [6.1 to 6.2 migration guide](docs/migration-6.2.md) before upgrading. R
* enforce conventional pull request titles ([3a21f16](https://github.com/morluto/rea/commit/3a21f16c1832430765083636511a8c12dbccf2dd))
* verify installed package with Linux Hopper ([4f97f69](https://github.com/morluto/rea/commit/4f97f699e4d5471557d1ce0ff91e144065d06a61))
## [0.3.0](https://github.com/morluto/rea/compare/rea-agents-0.2.1...rea-agents-0.3.0) (2026-07-12)
## [0.3.0](https://github.com/morluto/rea/compare/rea-0.2.1...rea-agents-0.3.0) (2026-07-12)
### Features
@@ -2736,7 +2736,7 @@ Read the [6.1 to 6.2 migration guide](docs/migration-6.2.md) before upgrading. R
* redesign and localize README ([051b6db](https://github.com/morluto/rea/commit/051b6db65687fd14826b6462cd623feb2504f123))
* redesign and localize README ([910eda6](https://github.com/morluto/rea/commit/910eda6f323ba1151b0771fe5de6c60a91e3d46d))
## [0.2.0](https://github.com/morluto/rea/compare/rea-0.1.0...rea-0.2.0) (2026-07-12)
## [0.2.0](https://github.com/morluto/rea/releases/tag/rea-0.2.0) (2026-07-12)
### Features
+6 -3
View File
@@ -83,12 +83,15 @@ npx rea-agents setup
Choose your agents, review the proposed changes, and approve them. Setup adds
REA's MCP server and matching workflow instructions, with backups of existing
configuration. Restart your agent afterward.
configuration. Restart your agent afterward. Setup records the selected runtime and REA
entry script directly. If acquired through a package runner, keep its cache entry
available. Run package acquisition from a trusted directory outside the target
project. Installation and recovery details are in the setup guide below.
Setup supports Claude Code, Codex, Cursor, Gemini CLI, Grok Build and
[other agents](docs/installation.md#supported-agents). See
[installation and setup](docs/installation.md) for provider configuration and
manual MCP registration.
manual MCP registration and Claude Code/Codex plugin installation.
MCP uses compact input schemas by default to reduce context overhead. Set
`REA_MCP_INPUT_SCHEMA_PROFILE=full` in your MCP registration when you need the
@@ -425,7 +428,7 @@ Report vulnerabilities through [SECURITY.md](SECURITY.md).
## Star history
🎉 **300,000 GitHub stars — thank you!**
🎉 **400,000 GitHub stars — thank you!**
Thanks to everyone using REA, reporting bugs, requesting features, testing builds, and contributing fixes.
+10
View File
@@ -0,0 +1,10 @@
# Plugin icon
`rea-plugin.png` is a square adaptation of the left panel of REA's GitHub
social preview, retrieved on 2026-10-28:
https://repository-images.githubusercontent.com/1209966933/1a3fef9a-be4b-41f4-8830-a94c0e8288cc
The image was adapted with imagegen, preserving the cyan REA lettering,
pixel typography, orange rule, and taglines. Both plugin manifests reference
this shared asset; only the PNG is included in the npm package.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

+11 -4
View File
@@ -406,7 +406,8 @@ returns that record's recorded response. Selection compares the supplied path
parameters against the record's path segments at the parameter indexes, then
the supplied query values against the record's query string, and keeps the
record with the most matches; ties keep manifest order. A query argument accepts
one string or an array of strings for repeated values; the array is matched
one string, a number or boolean sent as its JSON text, or an array of strings for
repeated values; the array is matched
against every occurrence in captured order. The raw recorded query remains the
replay evidence. The result reports
where the answer came from:
@@ -613,8 +614,9 @@ shape and carries no annotations.
Path parameters are required strings whose
description names the segment index, the templating rule and the observed
values (sampled within the list budget above; the manifest keeps them all);
query parameter names accept an optional string or array of strings (for
repeated values in captured order); write methods accept an
query parameter names accept an optional string, number or boolean (a number or
boolean is sent as its JSON text) or array of strings (for repeated values in
captured order); write methods accept an
optional `body` object described by the recorded top-level key types. Each
input keeps its own argument: a query name equal to a path parameter's name is
advertised as `query_<name>`, a query name longer than 64 characters as
@@ -768,7 +770,12 @@ sending a `derived` rule's member to an entity that does not hold the value the
recorded call found, a nested clock on an entity holding no such object, and a
rendered answer missing a field the recorded response carried, and an update or delete of an entity the state does not hold answers
the recorded 400, 404 or 410 of the tool or of an item view of the type, if
any. A refused call leaves the state unchanged. Values a write generates are
any. A served delete of a type whose item view has a `deleted_flag` (the
member, `deleted`, `isDeleted` or `is_deleted`, its reads set `true` on a
record the application soft-deleted, as EspoCRM does) keeps the entity until
`data/reset` or `data/add`, within the size `data/add` loads, so a read of it by
id answers its last fields with that flag `true`. A refused call leaves the
state unchanged. Values a write generates are
deterministic: the clock starts at `write_clock` and advances one second per
served write, sequences take one past both the largest held value and the
largest they generated, and UUIDs and tokens derive from the state Evidence
+7
View File
@@ -239,6 +239,13 @@ serialized size of the report together with its limitations (which repeat
archive names) is then checked against an 8 MiB output budget (the exact check
is authoritative); an over-budget report is refused, not truncated.
Signing blocks admit at most 4096 ID/value pairs; at most 4096 signing-relevant
META-INF entries are retained. These fixed counts, signer/lineage counts and the
report byte budget return nonretryable `resource_constraint` errors naming the
effective allowance. This release has no runtime override for them. Waiting for
an active scan cannot raise a fixed count, and complete CLI JSON cannot raise the
report budget. Occupied scan-queue capacity remains retryable after a scan finishes.
The whole-file reader allocates the selected snapshot and bounded read scratch;
total process memory exceeds the selected file's size. It also computes the complete
file SHA-256, so inspection is not header-only. Content digests are recomputed
+6 -5
View File
@@ -100,12 +100,13 @@ still suppresses all string projection, including in this mode.
- **Metadata comes from apktool.yml as apktool printed it.** Version codes
keep their own spelling (`'7'` versus `7`); `package_name` is read from
the decoded manifest, not inferred.
Apktool 3's emitted `version: 3.x.y` header selects its single-line string
dialect: literal hashes, single quotes, empty strings, and the text `null`
stay part of the value; producer backslashes and Unicode escapes decode
once. Older/tagged documents retain YAML scalar semantics; double-quoted
values also retain YAML string semantics.
The `version: 2.9.0`-or-later header that Apktool 2.9+ and 3 emit selects
their single-line string dialect: literal hashes, single quotes, empty
strings, and the text `null` stay part of the value; producer backslashes
and Unicode escapes decode once. Older/tagged documents retain YAML scalar
semantics; double-quoted values also retain YAML string semantics.
This distinction follows the released
[2.9.0 metadata writer](https://github.com/iBotPeaches/Apktool/blob/b64e257c9b689eb9630ad0241f2fe4f4a749955b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/VersionInfo.java#L38-L42),
[3.0.3 metadata writer](https://github.com/iBotPeaches/Apktool/blob/18b5e99cb56ff9451e8aa55b065dcf5bbd616975/brut.apktool/apktool-lib/src/main/java/brut/androlib/meta/VersionInfo.java#L49-L55)
and [string escape owner](https://github.com/iBotPeaches/Apktool/blob/18b5e99cb56ff9451e8aa55b065dcf5bbd616975/brut.j.yaml/src/main/java/brut/yaml/YamlStringEscapeUtils.java#L55-L116).
- **The manifest is the decoded text**, bounded at 512 KiB and reported as
+2 -1
View File
@@ -415,7 +415,8 @@ allowance; exhaustion fails the observation explicitly instead of guessing scope
Response metadata parses repeated CDP header field lines independently. The
Link projection retains their declarations in order. `csp.directives` lists the
directives of every CSP policy in order, across field lines and comma-separated
policies, without recording policy boundaries, so a directive name can repeat.
policies, without recording policy boundaries, so a directive name can repeat
across policies. As in CSP3, a repeated name within one policy is ignored.
Single-valued policy fields combine their lines as one comma-separated value,
as RFC 9110 specifies. Each link keeps its target in `href` and
its relationship context in `context_url` with a separate `context_scope`;
+5 -4
View File
@@ -530,10 +530,11 @@ id the application assigns:
that names no string or number fails the step, so later steps never send a
missing value, as does a response over 4,000,000 characters: a request step
stops reading and aborts its fetch there. A UI step reads a body only when
its positive received size is known, at most 12,000,000 bytes, and its
Content-Encoding is absent or identity. Compressed responses and unknown or
zero cache sizes are refused before materializing the body; decoded text
over the character limit also fails. The UI action may already have applied.
its decoded size is bounded at 12,000,000 bytes or less: its received size,
or the declared Content-Length of one served from cache, times 1032 for each
gzip or deflate coding, the most DEFLATE expands. Other codings and unknown
or zero sizes are refused before materializing the body; decoded text over
the character limit also fails. The UI action may already have applied.
- `{saved:name}` sends a saved value in a request step's `path`, including its
query, and in a `goto` destination's `url`, percent-encoded; a destination's
structured `query` values are sent as declared. A saved value that makes a
+69
View File
@@ -51,6 +51,15 @@ module page. See
[JavaScript application workflows](javascript-application-workflows.md) for
views, feature traces and comparisons that take the same saved Evidence.
Add `--timings` to a command to write one JSON timing record to stderr. For
example, `rea inspect-elf-notes program --json --timings > result.json` keeps
stdout as a parseable result and writes
`{"rea_timings":{"version":1,"command_ms":42}}` to stderr. `command_ms`
measures CLI command dispatch and result delivery; it is not provider-only or
analysis-only time. The flag is off by default, and the record omits stages
that REA did not measure. Root version reporting does not dispatch a command
and has no command timing record.
## JSON Lines record output
Use `--format jsonl` to consume row-oriented results one record per line:
@@ -241,6 +250,17 @@ Formats 2, 3 and 4 retain header, directory and integrity facts. Compact deliver
keeps complete Evidence in the requested bundle. See [Godot PCK](godot-pck.md)
for embedded packs, batch queries and unknown encrypted coverage.
Inspect a UnityFS AssetBundle without running Unity:
```sh
rea inspect-artifact content.bundle --detail summary --evidence_output evidence.json --json
rea extract-artifact content.bundle --json
```
Format versions 6 through 8 retain header, block and directory facts with
none/LZ4/LZ4HC block decompression. See [UnityFS](unityfs.md) for encryption
and LZMA coverage boundaries.
Inspect a Node.js SEA carrier without running it:
```sh
@@ -251,6 +271,26 @@ rea extract-artifact ./application --json
See [Node.js SEA](node-sea.md) for supported layouts, script and asset paths,
fuse observations, and the real-fixture verification lane.
Inventory and extract Intel HEX or S-record regions:
```sh
rea inspect-artifact ./firmware.hex --json
rea extract-artifact ./firmware.hex --json
```
See [Intel HEX and S-record images](hex-records.md) for recognition, region
names, metadata and limits.
Inspect and extract stored U-Boot legacy uImage payloads and components:
```sh
rea inspect-artifact ./firmware.bin --detail summary --evidence_output firmware.json --json
rea extract-artifact ./firmware.bin --json
```
See [U-Boot uImage](uimage.md) for verified CRCs, physical component ranges,
unknown names, resource limits and retained views. Compressed bytes stay stored.
Inspect local model declarations without loading weights:
```bash
@@ -972,6 +1012,35 @@ values instead of replacing them with an object label. Table widths are measured
iteratively, so an admitted row page is not limited by the JavaScript function
argument count.
`--format csv` and `--format tsv` export one selected row collection. Select an
exact array path with `--filter-output` and provide comma-separated direct
scalar columns with `--columns`; column order is the output column order:
```bash
rea inspect-analysis-view request.json --format csv \
--filter-output normalized_result.rows --columns name,address
```
The first line is a REA metadata record,
`# rea-delimited-v1 <JSON>`. It carries the selected path, view request, limitations and unknowns,
coverage, source coverage and completeness, command, and duration. A metadata
value of `null` means that the selected row collection did not provide that
fact; it does not mean complete coverage. The following line is the header.
CSV records use CRLF line endings; TSV records use LF. Cells containing the
delimiter, quotes, or line breaks are quoted and embedded quotes are doubled.
Values are emitted as UTF-8 without Unicode normalization. Missing and `null`
cells are empty; booleans and finite numbers use their ordinary text forms.
String cells and headers that begin with spreadsheet formula syntax are refused
with a structured error; JSON retains those values unchanged.
Delimited output does not flatten nested Evidence or nested field values.
It returns a structured error and a nonzero status if the filter does not
select exactly one row array, a row is not an object, or a selected cell is
nested or formula-leading. JSON remains the complete canonical output; use it
to retain fields outside the selected columns or preserve exact formula-leading
text. Token counting and token windows are not
available for CSV/TSV because slicing would corrupt records or metadata.
`--filter-output` selects fields from successful command data. Classified failures
retain their complete typed diagnostic projection even when the selector matches
a field such as `code` or `message`, or names a missing field. `--full-output`
+57 -16
View File
@@ -14,12 +14,17 @@ interpreter: the npm launcher's `#!/usr/bin/env node` still searches `PATH` for
Node. Running that command successfully in a terminal does not establish that
the client can launch it.
For direct JavaScript installations, setup registers the selected runtime
For JavaScript installations, setup registers the selected runtime
executable and REA script by absolute path. On POSIX, it prefers executable
`node` or `bun` and REA aliases on setup's `PATH` only when they resolve to the
same selected files. This preserves manager-owned aliases across version updates;
otherwise setup uses the selected paths directly. Package-runner registrations keep
their selected runner. Setup does not copy your ambient environment into client
otherwise setup uses the selected paths directly. This also applies when setup
runs through a package runner: retain the selected cache entry and runtime while
the registration uses them. Clearing either makes startup fail without resolving
another package from the client's project. Restore the same installation before
repairing it; an unidentified registration remains untouched. See
[installation and recovery](installation.md#package-runners-and-global-installation).
Setup does not copy your ambient environment into client
settings. To refresh an older registration, preview
`rea setup --client <client-id> --skill=false --dry-run`, then run the same command
without `--dry-run` and approve the displayed changes. Setup preserves unrelated
@@ -497,17 +502,51 @@ comments and unrelated settings. It resolves that file from `HERMES_HOME`, else
from the platform default Hermes itself uses — `%LOCALAPPDATA%\hermes` on
Windows, `~/.hermes` elsewhere — including any `HERMES_DATA_DIR_SUFFIX`. Hermes
connects an entry unless `enabled` is false, so setup states `enabled: true`.
Aliases whose anchor is defined in the `rea` entry retain their original resolved
values when setup refreshes or uninstall removes that entry. Setup does not
redirect another server's alias to REA's new launcher or policy.
This also covers an anchor on the removed registration key. References to
definitions outside the entry keep sharing the original value, including when
another definition later reuses the same anchor name.
Hermes personal skills install under the active Hermes home's `skills` directory.
Setup, doctor and uninstall follow the root's `active_profile` selection, as
plain Hermes CLI commands do. An explicit `HERMES_HOME` pointing to
`profiles/<name>` selects that named profile directly. An invalid, missing or
deleted sticky profile is reported instead of writing into the default home;
it also remains an unresolved skill selection, so setup cannot report a
successful empty installation. Overrides nested beneath the native Hermes home
use that native home's sticky selection and profile root together, matching
[Hermes's root rules](https://github.com/NousResearch/hermes-agent/blob/748c79a1c6873124d84071cdabb4062a75913e1f/hermes_constants.py#L83).
Repair the profile or run `hermes profile use default` before retrying.
Setup, doctor and uninstall share REA's sticky-profile resolver.
A custom `HERMES_HOME` outside the platform default home
reads its own `active_profile`. An override physically inside the default home
uses that default home's sticky selection and profile root together, including
when the final override directory does not yet exist.
For example, with `HERMES_HOME=~/.hermes/sub` and `~/.hermes/active_profile` set
to `coder`, setup selects `~/.hermes/profiles/coder/config.yaml` and that
profile's skills directory. It does not read `~/.hermes/sub/active_profile`.
Without an outer named selection, this override uses `~/.hermes/sub/config.yaml`.
Use a home outside the platform default tree for an independent sticky selection.
This describes REA's current routing, not full Hermes CLI equivalence.
At Hermes commit `748c79a1`, the
[sticky lookup](https://github.com/NousResearch/hermes-agent/blob/748c79a1c6873124d84071cdabb4062a75913e1f/hermes_cli/main.py#L612-L632)
also reads the outer selection, but
[`resolve_profile_env`](https://github.com/NousResearch/hermes-agent/blob/748c79a1c6873124d84071cdabb4062a75913e1f/hermes_cli/profiles.py#L2431-L2461)
resolves it beneath the configured `HERMES_HOME`. For nested overrides, confirm
the profile Hermes actually uses and point `HERMES_HOME` directly at that
`profiles/<name>` directory before setup. Compatibility is tracked in
[#3939](https://github.com/morluto/rea/issues/3939).
REA resolves existing ancestor directories before checking physical containment,
including symlinks and junctions, even when the override directory does not exist.
An override beneath a symlinked default home therefore uses the default home's
sticky selection. If `~/.hermes/link` points outside the default home,
`HERMES_HOME=~/.hermes/link/new` instead uses that override root, whether or not
`new` exists. Previewed destinations retain the configured path spelling.
An unreadable path or dangling alias leaves the home unresolved; repair that path
before configuring Hermes. Inspect setup's previewed destinations before applying
changes.
An explicit `HERMES_HOME` pointing to `profiles/<name>` selects that named
profile directly, bypassing sticky selection. An invalid, missing or deleted
sticky profile is reported instead of writing into the default home; repair the
profile or run `hermes profile use default` before retrying. It also remains an
unresolved skill selection, so setup cannot report a successful empty installation.
REA follows `HERMES_HOME` whitespace trimming, environment-variable expansion,
and home expansion before resolving that profile. Setup previews the resolved
configuration and skill destinations; doctor and uninstall use those same paths.
@@ -602,6 +641,12 @@ boundaries. FreeBuff MCP loading and skill discovery have not been verified
with an installed native FreeBuff client/version; filesystem tests cover REA's
editing, discovery, and ownership behavior only. Automatic applicability
requires the FreeBuff profile directory even when the shared MCP file exists.
When uninstall cannot establish FreeBuff or another shared-storage reader from
product discovery, a malformed or unreadable shared MCP file is retained
unchanged and does not stop other REA removals, including `--purge-data`. When
FreeBuff or ZCode product applicability is established or unresolved, the same
malformed shared file stops uninstall before removals. See
[Diagnose, update, and remove](installation.md#diagnose-update-and-remove).
FreeBuff setup support requires `[email protected]` or later. Older versions
reject `rea setup --client freebuff` as an unsupported client. See the
@@ -705,10 +750,6 @@ can override an inherited disable. Lower files and other server entries remain
unchanged; a legacy server named `servers` that conflicts with the native table
requires manual repair before setup can write.
When Hermes aliases refer to an anchored REA entry, refreshing or removing REA
retains the aliases' original resolved values. Setup does not redirect another
server's alias to REA's new launcher or policy.
With no client selected, the interactive skill choice targets the shared
`~/.agents/skills` copy. Selecting a client targets its personal destination
when supported. Use `rea setup --skill-client shared --skill` for the shared
+22
View File
@@ -292,3 +292,25 @@ Use [`inspect_devicetree`](devicetree.md) / `rea inspect-devicetree` on an
extracted DTB for its node/property tree, reservations, hardware summary and
selected phandle relationships. FDT v16/v17 inspection is portable and requires
no external tool or disassembler.
## Inspect Intel HEX and S-record images
Use `inspect-artifact` / `inspect_artifact` on an Intel HEX or Motorola S-record
file for its address regions, declared start address and record facts, then
`extract-artifact` / `extract_artifact` for one raw file per region. Decoding is
portable and requires no external tool. See
[Intel HEX and S-record images](hex-records.md).
## Inspect U-Boot legacy uImages
`inspect-artifact` / `extract-artifact` recognize a legacy uImage by its
`0x27051956` magic and inventory it without running or decompressing it.
The root facts record the 64-byte header (architecture, OS, type, and
compression as raw values and names, load and entry addresses, image name,
and timestamp) with the header and data CRC32 verified; the stored payload
is extractable as `payload.bin`, and multi-file images additionally expose
each sub-image as `image-<n>.bin` from the stored size table. Declared
addresses are facts from the header, not proven entry points, and compressed
payloads stay as stored. Intel HEX/S-record and FIT images are separate
readers and are not covered here. See [U-Boot legacy uImage](uimage.md) for
component padding, preserved names, resource policies and verification scope.
+12 -3
View File
@@ -1,9 +1,18 @@
# Godot PCK archives
`inspect-artifact` and `extract-artifact` read ordinary Godot PCK formats 2, 3
`inspect-artifact` and `extract-artifact` read ordinary Godot PCK formats 1, 2, 3
and 4 without running the game or requiring Godot. Official Godot 4.4.1, 4.6.1
and 4.7.1 produced these respective formats in verification; other engine
releases have not been verified. Format 1 is unsupported.
and 4.7.1 produced formats 2, 3 and 4 respectively in verification; other engine
releases have not been verified. Format 1 compatibility follows the
[Godot 3.5 loader](https://github.com/godotengine/godot/blob/3.5/core/io/file_access_pack.cpp#L201-L242)
and [exporter](https://github.com/godotengine/godot/blob/3.5/editor/editor_export.cpp#L1094-L1164)
layout and is covered by synthetic standalone and embedded fixtures; an actual
Godot 3 export has not been exercised.
Format 1 uses an 84-byte header, no pack or member flags, and absolute member
offsets in the complete source file, including an embedded executable carrier.
Its reported flags and file base are zero because those fields are absent.
Zero-offset removal records remain unsupported; REA does not apply patches.
Inspection retains the original header and directory facts, including engine
version, flags, declared file count, source paths, offsets and declared MD5.
+129
View File
@@ -0,0 +1,129 @@
# Intel HEX and S-record images
REA inventories Intel HEX (I8HEX, I16HEX, I32HEX) and Motorola S-record (S19,
S28, S37) programming files through `inspect-artifact` / `inspect_artifact`, and
materializes their decoded bytes through `extract-artifact` /
`extract_artifact`. Decoding is static and needs no external tool.
```sh
rea inspect-artifact ./firmware.hex --json
rea inspect-artifact ./firmware.s37 --detail summary --evidence_output evidence.json --json
rea extract-artifact ./firmware.hex --json
```
For MCP, open the file with `open_binary`, then call `inspect_artifact`. Summary
delivery retains the complete Evidence; use `parent_evidence_id` with
`inspect_analysis_view` to read selected regions and fields. `extract_artifact`
returns a fresh `output_root` containing one raw file per region.
## Recognition
A file is recognized only when its first line, starting at byte 0, is a fully
valid record: correct start character, hex digits, byte count, checksum, known
record type and fixed length. The file name does not matter. A byte-order mark,
a leading blank line or an invalid first record leaves the file unrecognized,
so it falls through to plain `file` classification. Recognition never searches
for a record later in the file. The container formats are `intel-hex` and
`srec`.
## Regions and metadata
Each inventory entry is one address-contiguous region of decoded bytes, named
`0x<start>-0x<end>.bin` with an inclusive end address of eight lowercase hex
digits, for example `0x08000000-0x0800001f.bin`. Regions are listed in ascending
address order. Out-of-order records are sorted into regions; gaps separate
regions and are never filled.
Region `format_metadata` (`hex-record-region`) records `start_address`,
`end_address`, `length`, `record_count`, and `first_line`/`last_line`: the
1-based line span of the contributing data records.
Root `format_metadata` (`hex-records`) records:
| Field | Meaning |
| ------------------------------------------------- | --------------------------------------------------------------------------------- |
| `encoding`, `variant` | `intel-hex` or `srec`; the variant derived from the record types seen, or `mixed` |
| `line_count`, `record_count`, `data_record_count` | Lines read, non-blank records, and data records |
| `data_bytes`, `duplicate_bytes`, `region_count` | Unique decoded bytes, bytes rewritten with identical values, and regions |
| `lowest_address`, `highest_address` | Address bounds of the decoded bytes, or `null` without data |
| `termination` | Whether Intel `01` or S-record `S7`/`S8`/`S9` was present |
| `start_address`, `start_address_record` | The Intel `05` EIP or the `S7`/`S8`/`S9` address, with its record type |
| `start_segment_cs`, `start_segment_ip` | The Intel `03` CS:IP pair |
| `header_hex`, `header_text` | S-record `S0` data as hex, and as text only when every byte is printable ASCII |
| `declared_data_record_count` | The last `S5`/`S6` count, validated against the data records before it |
`variant` is `null` for an S-record file in which no data or termination
record establishes an address width. Start addresses are declared facts, not
proven entry points.
## Facts, limitations and failures
| Situation | Outcome |
| ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------- |
| Gaps between regions | Separate regions; never zero-filled |
| Out-of-order records | Sorted into regions |
| An address written twice with the same value | Accepted and counted in `duplicate_bytes` |
| An address written twice with different values | Fails, naming the address and the line spans of both writes |
| Checksum mismatch, odd hex length, non-hex character, byte count disagreeing with the line | Fails with the line, declared and computed values, and record type |
| Line longer than 521 (Intel) or 514 (S-record) characters, excluding the line ending | Fails before the line is decoded |
| Intel type `06` or higher, `S4`, wrong fixed length for Intel `01`–`05` or `S5`–`S9` | Fails |
| Missing Intel `01` or `S7`–`S9` | Regions are returned; coverage is unknown and the file may be truncated |
| A non-blank record after termination, or a second `S0` | Fails; blank lines are allowed anywhere |
| `S5`/`S6` count different from the data records before it | Fails |
| Termination type not matching the data address width | Reported as a limitation; the declared fact is kept |
| Conflicting repeated `03`/`05` start records | Fails; identical repeats are allowed |
| An Intel data record whose 16-bit offset wraps past `0xFFFF` | Fails: producers disagree on whether the address wraps or continues linearly |
| I16HEX data above `0xFFFFF`, or data crossing the S1/S2/S3 address width | Fails |
Lowercase hex, LF and CRLF line endings, and trailing spaces or tabs are
accepted. A CR not followed by LF is an invalid character. Failures apply to
the whole file and return no partial regions.
## Limits
The file must be at most 128 MiB. Every data byte needs two hex digits, so the
decoder reserves at most half the file size for decoded bytes before reading,
and stores only written addresses. The decoder retains at most 65,536
address-discontiguous record runs before merging them into regions. A run is a
sequence of consecutive records whose addresses continue each other. Both are
fixed resource bounds, not format limits, and fail as limits (`file-size` and
`capacity`) with no runtime override. Configurable ceilings are tracked in
[#6176](https://github.com/morluto/rea/issues/6176). The line-length limits are
the one-byte record count: Intel HEX allows 255 data bytes, while S-record
counts include address and checksum bytes.
Decoded regions are cached only for the reader's lifetime. Each open checks
its admitted carrier identity again. Region streams copy at most 64 KiB per
chunk, so consumers cannot mutate cached bytes and opening a large region does
not copy it in full. Caller cancellation keeps its exact reason, and closing
the reader waits for active region streams to close.
## Analyzing a region
Inventory does not establish the CPU, byte order or memory map, or that a
declared start address is executable. To disassemble a region, extract it and
open the region file with `open_binary` using format `raw-binary`, an
architecture you choose, and `entry_point` set to the region start address;
raw-binary maps file offset 0 at `entry_point`. Each region is opened
separately, so references between regions are not resolved. A caller-selected
Ghidra language and base address for raw-binary imports is tracked in
[#4246](https://github.com/morluto/rea/issues/4246); Thumb-only Cortex-M images
may be refused or decoded in ARM state until
[#5744](https://github.com/morluto/rea/issues/5744) is resolved.
## Verification
Fixtures with computed checksums cover supported record types, checksums,
termination, address gaps and overlaps, resource refusals, inventory, extraction
and reader lifecycle. CLI and stdio MCP tests exercise the same artifact
provider and advertised schemas, including retained Evidence views.
GNU objcopy 2.40 output was also checked on Linux: the system `true` ELF was
converted to Intel HEX and S-record, with additional address-rebased I32HEX
and S37 cases. Each yielded 13 regions. A 19,124,817-byte Intel HEX conversion
of the system Python 3.11 ELF yielded 11 regions containing 6,799,261 decoded
bytes. Public CLI inspection and extraction matched GNU BFD address ranges
and independently exported section bytes. Adjacent GNU sections were joined
without filling address gaps. These are real producer outputs from system
applications, not vendor-firmware or target-execution verification; srec_cat
and other producers remain unverified. No general speedup is claimed.
+6 -1
View File
@@ -40,7 +40,12 @@ Record the image digest, REA source revision and these dependency versions:
- OpenSSL and cURL advertising HTTP3 and HTTP2 in `curl --version`.
The verified guest used Python 3.14.7, cURL 8.22.0, ngtcp2 1.25.0 and nghttp3
1.18.0. Other dependency profiles need verification. Back up the guest's
1.18.0. The current requirements update certifi from 2026.7.22 to 2026.10.19
to remove the compromised emSign trust root described in
[GHSA-6q48-6c7w-9gw2](https://github.com/certifi/python-certifi/security/advisories/GHSA-6q48-6c7w-9gw2).
The revised dependency graph has not been rerun in that sealed guest; the
historical verification does not cover this update. Other dependency profiles
need verification. Back up the guest's
`/etc/hosts` before adding `127.0.0.1 lab.test`. Reserve guest TCP/UDP loopback
ports 18443 and 19443. These changes belong only to the disposable guest.
+14 -14
View File
@@ -47,20 +47,20 @@ For runtime instrumentation, see the [Frida guide](./frida-instrumentation.md).
## Choose a guide
| Task | Guides |
| --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Identify a desktop installation | [Runtime candidates, evidence and follow-up files](desktop-applications.md) |
| Inspect native functions, references, or types | [Native binaries](native-investigation.md), [Go metadata](go-binaries.md), [Rust dependencies](rust-binaries.md), [CPython pyc files](python-pyc.md), [PE certificates](pe-certificates.md), [PE imports](pe-imports.md), [PE TLS/load config](pe-load-config.md), [ELF notes](elf-notes.md), [Linux kernel modules](kernel-modules.md), [Apple IPS](apple-ips.md), [Erlang BEAM](erlang-beam.md), [offline ELF layout](binary-diagnostics.md), [recorded crashes](recorded-crashes.md) |
| Inspect native functions, references, or types | [Native binaries](native-investigation.md), [Go metadata](go-binaries.md), [Rust dependencies](rust-binaries.md), [CPython pyc files](python-pyc.md), [PE certificates](pe-certificates.md), [PE imports](pe-imports.md), [PE TLS/load config](pe-load-config.md), [ELF notes](elf-notes.md), [Apple IPS](apple-ips.md), [Erlang BEAM](erlang-beam.md), [offline ELF layout](binary-diagnostics.md), [recorded crashes](recorded-crashes.md) |
| Select a native provider or import format | [Ghidra semantics](ghidra-provider.md), [Windows](windows-ghidra-p0.md), [DOS](ghidra-dos.md), [raw bytes](raw-binary.md), [GameCube DOL](ghidra-dol.md), [MIPS](ghidra-mips.md), [PSP](ghidra-psp.md), [NativeAOT](ghidra-nativeaot.md), [IDA](ida-provider.md), [Kuna](kuna-provider.md), [JEB](jeb-analysis.md) |
| Analyze JavaScript or Electron artifacts | [Static analysis](javascript-artifact-reconstruction.md), [tracing and comparisons](javascript-application-workflows.md), [application graph](javascript-application-graph.md), [source recovery](javascript-recovery.md) |
| Compare static JavaScript with runtime observations | [Runtime attribution](javascript-runtime-observation.md), [reconciliation](javascript-runtime-reconciliation.md), [Electron observation](electron-observation.md), [Electron fuses](electron-fuses.md) |
| Inspect assemblies, packages, or firmware | [Godot PCK](godot-pck.md), [.NET](managed-code-analysis.md), [Python packages](python-packaged-applications.md), [Android](android-analysis.md), [APK signatures](apk-signature.md), [Apktool resources](apktool-resource-analysis.md), [ADB acquisition](adb-device-analysis.md), [Apple](apple-application-analysis.md), [Harmony](harmony-analysis.md), [firmware](firmware-analysis.md), [WASM](wasm-artifacts.md), [EVM](evm-bytecode.md) |
| Observe or compare a browser scenario | [CDP observation](browser-observation.md), [scenarios](browser-scenario-contract.md), [runtime evidence](web-runtime.md), [historical captures](web-network-captures.md) |
| Serve recorded behavior as an agent environment | [Agent environments](agent-environments.md) |
| Trace captured website source | [Source locations](web-source-location.md), [module traces](website-module-trace.md), [script export](website-script-export.md) |
| Capture and compare a command | [Process capture](process-capture.md) |
| Use CLI/MCP and retain evidence | [CLI](cli.md), [MCP contracts](mcp-contracts.md), [agent prompts](mcp-prompts.md), [obligation ledgers](reconstruction-obligation-ledgers.md) |
| Task | Guides |
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Identify a desktop installation | [Runtime candidates, evidence and follow-up files](desktop-applications.md) |
| Inspect native functions, references, or types | [Native binaries](native-investigation.md), [Go metadata](go-binaries.md), [Rust dependencies](rust-binaries.md), [CPython pyc files](python-pyc.md), [PE certificates](pe-certificates.md), [PE imports](pe-imports.md), [PE TLS/load config](pe-load-config.md), [ELF notes](elf-notes.md), [Linux kernel modules](kernel-modules.md), [Apple IPS](apple-ips.md), [Erlang BEAM](erlang-beam.md), [offline ELF layout](binary-diagnostics.md), [recorded crashes](recorded-crashes.md) |
| Inspect native functions, references, or types | [Native binaries](native-investigation.md), [Go metadata](go-binaries.md), [Rust dependencies](rust-binaries.md), [CPython pyc files](python-pyc.md), [PE certificates](pe-certificates.md), [PE imports](pe-imports.md), [PE TLS/load config](pe-load-config.md), [ELF notes](elf-notes.md), [Apple IPS](apple-ips.md), [Erlang BEAM](erlang-beam.md), [offline ELF layout](binary-diagnostics.md), [recorded crashes](recorded-crashes.md) |
| Select a native provider or import format | [Ghidra semantics](ghidra-provider.md), [Windows](windows-ghidra-p0.md), [DOS](ghidra-dos.md), [raw bytes](raw-binary.md), [GameCube DOL](ghidra-dol.md), [MIPS](ghidra-mips.md), [PSP](ghidra-psp.md), [NativeAOT](ghidra-nativeaot.md), [IDA](ida-provider.md), [Kuna](kuna-provider.md), [JEB](jeb-analysis.md) |
| Analyze JavaScript or Electron artifacts | [Static analysis](javascript-artifact-reconstruction.md), [tracing and comparisons](javascript-application-workflows.md), [application graph](javascript-application-graph.md), [source recovery](javascript-recovery.md) |
| Compare static JavaScript with runtime observations | [Runtime attribution](javascript-runtime-observation.md), [reconciliation](javascript-runtime-reconciliation.md), [Electron observation](electron-observation.md), [Electron fuses](electron-fuses.md) |
| Inspect assemblies, packages, or firmware | [Godot PCK](godot-pck.md), [UnityFS](unityfs.md), [U-Boot uImage](uimage.md), [.NET](managed-code-analysis.md), [Python packages](python-packaged-applications.md), [Android](android-analysis.md), [APK signatures](apk-signature.md), [Apktool resources](apktool-resource-analysis.md), [ADB acquisition](adb-device-analysis.md), [Apple](apple-application-analysis.md), [Harmony](harmony-analysis.md), [firmware](firmware-analysis.md), [WASM](wasm-artifacts.md), [EVM](evm-bytecode.md) |
| Observe or compare a browser scenario | [CDP observation](browser-observation.md), [scenarios](browser-scenario-contract.md), [runtime evidence](web-runtime.md), [historical captures](web-network-captures.md) |
| Serve recorded behavior as an agent environment | [Agent environments](agent-environments.md) |
| Trace captured website source | [Source locations](web-source-location.md), [module traces](website-module-trace.md), [script export](website-script-export.md) |
| Capture and compare a command | [Process capture](process-capture.md) |
| Use CLI/MCP and retain evidence | [CLI](cli.md), [MCP contracts](mcp-contracts.md), [agent prompts](mcp-prompts.md), [obligation ledgers](reconstruction-obligation-ledgers.md) |
For development, use [testing](testing.md), [client verification](client-verification.md),
[tool design](tool-design.md), [admission limits and recovery](design/limits-and-recovery.md),
+170 -61
View File
@@ -3,13 +3,14 @@
Run REA, connect your selected agents, and check the tools needed for your task.
Setup previews configuration changes and asks for approval before applying them.
| Task | Section |
| --------------------------------- | --------------------------------------------------------------------------------------------------- |
| Connect an agent | [Start setup](#start-setup), then [review changes](#review-setup-changes) |
| Diagnose one task | [Scoped readiness checks](#check-readiness-for-your-task) |
| Configure an analysis engine | [Hopper](#hopper), [Ghidra](#ghidra), the [IDA guide](ida-provider.md), or [Kuna](kuna-provider.md) |
| Update or remove REA | [Maintenance](#diagnose-update-and-remove) |
| Resolve a client-specific problem | [Client configuration](client-configuration.md) |
| Task | Section |
| ---------------------------------- | --------------------------------------------------------------------------------------------------- |
| Connect an agent | [Start setup](#start-setup), then [review changes](#review-setup-changes) |
| Install a Claude Code/Codex plugin | [Agent plugins and marketplaces](#agent-plugins-and-marketplaces) |
| Diagnose one task | [Scoped readiness checks](#check-readiness-for-your-task) |
| Configure an analysis engine | [Hopper](#hopper), [Ghidra](#ghidra), the [IDA guide](ida-provider.md), or [Kuna](kuna-provider.md) |
| Update or remove REA | [Maintenance](#diagnose-update-and-remove) |
| Resolve a client-specific problem | [Client configuration](client-configuration.md) |
## Start setup
@@ -169,7 +170,9 @@ Setup leaves existing skill copies in other locations untouched.
Setup refuses symbolic links at the selected skill directory or its managed
`references` directory, matching uninstall and update's ownership rules. Use a
regular skill directory, or select `--skill=false` to configure the client
registration without installing the bundled skill.
registration without installing the bundled skill. A non-directory ancestor is
reported as `ENOTDIR`; preserve the blocking file and correct the directory path
before rerunning setup.
The final selected directories are checked before approval and again before
setup changes any client or Hopper installation. Unselected client destinations
do not block other setup choices. Doctor reports linked or non-directory skill
@@ -272,7 +275,7 @@ After reviewing an update plan, rerun that same prefixed setup command without
`--dry-run` and approve its exact changes. Profile removal likewise needs the
prefix on `claude mcp remove --scope user rea` and any `rea uninstall` command;
keep the broader client scope of uninstall in mind. Restart the selected
profile after changing its pinned registration.
profile after changing its registration.
`claude mcp remove` removes only the registration. To remove REA from this
profile without running the broader uninstall flow, first back up the profile's
@@ -322,30 +325,26 @@ commands. Record an existing selection first if you intend to restore it after
finishing. The configuration path below is the default profile's
`~/.claude.json`; a different active profile uses its own config root instead.
Register with the variable unset:
<!-- x-release-please-start-version -->
Register with the variable unset, substituting the absolute runtime and entry
script from your existing installation or setup dry-run:
```bash
claude mcp add --scope local rea -- npx -y rea-agents@6.8.0 mcp
claude mcp add --scope local rea -- "/absolute/path/to/node" "/absolute/path/to/rea/scripts/rea.mjs" mcp
```
On native Windows, use the same project directory and wrap the package-runner
shim so the stdio client can launch it:
On native Windows, use the same project directory and absolute paths:
```powershell
claude mcp add --scope local rea -- cmd.exe /d /c npx -y rea-agents@6.8.0 mcp
claude mcp add --scope local rea -- "C:\Program Files\nodejs\node.exe" "C:\tools\rea\scripts\rea.mjs" mcp
```
<!-- x-release-please-end -->
Local scope stores the entry under that directory's path in `~/.claude.json`,
so other projects don't load it. This route doesn't install the bundled skill.
Avoid `--scope project`, which writes `.mcp.json` into the repository where it
can be committed or copied. `rea doctor` and `rea uninstall` read only the
user-level entry, so they don't report or remove a local-scope registration;
remove it with `claude mcp remove --scope local rea` from the same directory.
`rea update` doesn't refresh it either, so it stays on the version it names.
`rea update` doesn't refresh it either, so it continues using the selected installation.
To move it to a newer release, first back up the project entry in
`~/.claude.json` and record its environment and other metadata. In particular,
preserve caller-selected overrides such as `REA_MCP_INPUT_SCHEMA_PROFILE` and
@@ -448,10 +447,15 @@ Lexically equivalent spellings of that default directory, including a trailing
separator, a `.` segment, or a repeated separator, share its purge result.
The comparison preserves POSIX `..` traversal and literal backslashes.
A client configuration that is malformed, unreadable, or at an unsafe
path stops the operation before anything is removed, and a client that fails
while being updated stops the remaining removals. Uninstall reports each
candidate `configuration_path` with its outcome. A client with multiple files
keeps their individual `actions` alongside its aggregate status and detail,
path stops the operation before anything is removed when that client's
product applicability is established or unresolved. Shared registration
storage such as `~/.agents/mcp.json` whose product cannot be established
(for example when FreeBuff and ZCode discovery directories are absent) is
retained unchanged and does not block other REA removals, including cache
and state deletion with `--purge-data`. A client that fails while being
updated stops the remaining removals. Uninstall reports each candidate
`configuration_path` with its outcome. A client with multiple files keeps
their individual `actions` alongside its aggregate status and detail,
including earlier removals when a later file fails. The paths name
the discovered configuration candidates, not resolved physical file identities.
For JSON clients, uninstall removes every repeated `rea` member in the effective
@@ -547,8 +551,8 @@ non-TTY, and JSON modes without applying configuration changes.
## Released package and main
Repository main documents its current code and generated catalog. `@latest`
selects the npm release, and persistent MCP registrations are pinned to the
version that performed setup. Installing newer instructions does not update a
selects the npm release, and managed MCP registrations bind the installation
that performed setup. Installing newer instructions does not update a
running server or its registration.
The npm release checked on **2026-10-11** was **6.4.0**, published from
@@ -851,9 +855,13 @@ stage `connection`.
If macOS denies Apple Events, allow the application that starts REA under
**System Settings → Privacy & Security → Automation**, then retry. REA releases
the target reservation when the native helper reports its complete authorization
rejection and its arguments cannot alter the helper's generated AppleScript.
Arguments containing quotes, backslashes, or line breaks remain unchanged but
cannot establish that a failed handoff opened no document. Other failed handoffs
rejection after literal argument admission. Before application handoff, REA refuses
double quotes, backslashes, or line breaks in the final launcher path or arguments,
with `details.launch_rejection.boundary: "argument_encoding"` and the affected source/index.
Select literal-safe launcher/runtime paths and loader arguments, or another
compatible provider. REA preserves original file names and evidence: a quoted
executable name can still work when its prepared image path is literal-safe;
database paths must themselves be representable. Other failed handoffs
retain ownership until document closure is confirmed. If a timed-out bridge
becomes ready later, `close_binary` connects to that run, authenticates its
identity, and requests document shutdown. Once its listener is ready, the bridge
@@ -1091,14 +1099,15 @@ rea providers --json
```
`rea setup` can configure supported Windows agents and install the bundled
REA skill after approval. Direct registrations use Node to launch REA's entry
script; package-runner registrations use the pinned `npx` or `bunx` command. Hopper
REA skill after approval. Managed registrations use the absolute selected Node or
Bun runtime and REA entry script, including when setup ran through a package runner. Hopper
installation remains unavailable on Windows. Setup never installs Ghidra,
Java, or Python. It preserves valid detected Ghidra/JDK settings in agent
registrations. See [Windows Ghidra P0](windows-ghidra-p0.md) for provider diagnostics.
The separate `rea mcp add` builtin currently uses Incur's package-runner child
launcher, which can fail on Windows with `MCP_ADD_FAILED: spawn npx ENOENT`
The separate `rea mcp add` builtin delegates most clients to Incur's package-runner
child launcher. Run that acquisition from a trusted directory; prefer managed
`rea setup` when supported. That child launcher can fail on Windows with `MCP_ADD_FAILED: spawn npx ENOENT`
before registering a client. REA's setup path is unaffected. For a client supported
by setup, review `rea setup --client opencode --skill=false --dry-run --json`
(substitute its client ID), then use the same scoped command without `--dry-run`
@@ -1436,6 +1445,21 @@ current setting. For Windows PE resource analysis, the exact name is
`WindowsResourceReference`. Disabling an analyzer skips the facts it would have
produced.
Ghidra's Objective-C Message Analyzer can log
`Required Objective-C data type not found in data type manager` /
`Try adding libobjc.dylib` when a Mach-O links Objective-C runtime symbols but
defines no class metadata (thin Cocoa glue in a C++ binary is a common case).
That text is non-fatal Ghidra analyzer output: auto-analysis continues, and the
analyzer simply skips `objc_msgSend` call-target recovery. REA's headless import
does not accept a "add this library" option, and on modern macOS
`/usr/lib/libobjc.A.dylib` exists only in the dyld shared cache, so there is no
on-disk library to add. Treat the line as a coverage limitation, not an REA
failure; check the session's `failure_kind` (and related provider errors) for
any real abort. To silence that analyzer, set
`REA_GHIDRA_DISABLED_ANALYZERS="Objective-C Message Analyzer"`. For large
executables that miss the startup deadline instead, raise
`REA_GHIDRA_STARTUP_TIMEOUT_MS` and the [Ghidra heap](#ghidra-heap-and-cpu-controls).
### Ghidra heap and CPU controls
Set resource controls in the environment that launches the CLI or MCP server,
@@ -1537,6 +1561,87 @@ read-only operations, digest identity, and runtime cleanup. The separate
ordinary user. It requires the built Windows native bundle and an existing
Ghidra/JDK installation; neither is inferred from startup alone.
## Agent plugins and marketplaces
REA's npm package is the shared Claude Code and Codex plugin artifact. It
contains `.claude-plugin/plugin.json`, `.codex-plugin/plugin.json`, and the
canonical generated `skills/` tree. Both manifests select `./skills` and run a plain Node bootstrap pinned to
that exact package version. The client must have a supported standalone `node`
on its PATH. Claude expands its documented `CLAUDE_PLUGIN_ROOT` in the script
argument; Codex resolves `cwd: "."` from the installed plugin root before
launching the relative script. The bootstrap verifies the package version and runs its bundled runtime in
the same process, preserving stdio and REA's provider/worker cleanup. It does
not start another package runner or download a different runtime. The existing release skill
generator also pins CLI recovery commands to the same version.
The repository's single-plugin marketplaces are declared in
`.claude-plugin/marketplace.json` and `.agents/plugins/marketplace.json`.
Both select an exact `rea-agents` npm version instead of mutable source-checkout
contents. Release Please updates their selectors alongside the plugin manifests.
No plugin-only publication or generated skill files committed to Git are needed.
The qualified release archive contains a runtime-only manifest and
`npm-shrinkwrap.json` plus bundled runtime dependencies, including PTY packages
for each supported OS and CPU; development dependencies and development-only overrides
stay in the source manifest and lockfile. Both clients receive this same archive.
This distribution becomes installable after the first qualified npm release
containing these plugin manifests. Earlier published packages are not changed
by this source proposal. For local source development, run `npm ci && npm run build:cached` to generate
the runtime and packaged skill before loading the repository as a plugin.
A source plugin uses the locally built runtime and generated skill; rebuild after
source changes. Missing builds and mismatched version pins fail with an actionable
error. The authored `.agents/skills/` tree is not the plugin's shipped skill layout.
Windows source workflows that need native controls still require the
[Windows source-checkout build](https://github.com/morluto/rea/blob/main/native/windows/README.md#build).
In Claude Code:
```text
/plugin marketplace add morluto/rea
/plugin install rea@rea
```
In Codex CLI, add the marketplace and install REA:
```bash
codex plugin marketplace add morluto/rea
codex plugin add rea@rea
```
Each release updates both manifest versions and their pinned server version
together, which is when installed copies refresh the bundled skill and move to
the new server. A plugin does not run `rea setup`, so provider
configuration still follows the sections above. Use the plugin or a
`rea setup` registration for a client, not both, to avoid two `rea` servers.
`rea doctor` does not see plugin-owned state. Its client and skill checks read
only the registrations and skill locations that `rea setup` manages, so for a
plugin-only install `rea doctor --client <id>` and `rea doctor --skill` report
`missing` and recommend `rea setup`. Do not follow that remediation. Check the
plugin with the client instead (`claude plugin list` and `/mcp` in Claude
Code, `codex plugin list` in Codex).
For analysis engines, ask the connected server: have the agent call
`binary_session` and read `result.tool_availability`, which reports what the
server the plugin started can use, with reasons and remediation. See
[MCP contracts](mcp-contracts.md).
A plugin does not put `rea` on your `PATH`. To check an engine from a
terminal, use the package runner at the version the plugin pins:
<!-- x-release-please-start-version -->
```bash
npx -y rea-agents@6.8.0 doctor --provider ghidra --json
```
<!-- x-release-please-end -->
If your installed plugin is older, use the version in its manifest instead.
That report describes the terminal's environment, not the plugin server's.
The server inherits the client's environment, so a setting such as
`GHIDRA_INSTALL_DIR` that exists only in your terminal does not reach it.
## MCP Registry
REA is published in the official MCP Registry as `io.github.morluto/rea`. Registry
@@ -1547,40 +1652,45 @@ For a client that supports Registry discovery, search for `io.github.morluto/rea
and select the npm package. The published metadata launches the existing stdio
server through `npx` with the `mcp` command.
For a client that requires manual configuration, use:
For supported clients, prefer `rea setup`: it records the absolute JavaScript
runtime and entry script of the REA installation performing setup. Client starts
then use that installation directly, keeping the client's working directory
without running npm or Bun package resolution there. Registry clients control
their own package acquisition; Registry metadata cannot name local absolute paths.
<!-- x-release-please-start-version -->
For manual stdio configuration, substitute the absolute paths of an existing
installation (use the command shown in a setup dry-run):
```json
{
"mcpServers": {
"rea": {
"command": "npx",
"args": ["-y", "rea-agents@6.8.0", "mcp"]
"command": "/absolute/path/to/node",
"args": ["/absolute/path/to/rea/scripts/rea.mjs", "mcp"]
}
}
}
```
<!-- x-release-please-end -->
Run current setup to migrate recognized older `npx`/`bunx` registrations, with
backups, then restart the client. Unrelated or unidentified registrations remain
untouched. Setup does not prewarm a separate package-runner command.
Persistent registrations should use one exact package version. `rea setup`
writes the same package-runner shape, or `bunx rea-agents@<version> mcp` when
setup runs through `bunx`, and pins it to the exact version that performed
setup. Run current setup to refresh an older registration, then restart the
client.
Package runners key their package cache by the requested spec, so the entry
installed for the `@latest` invocation that ran setup cannot serve the pinned
registration. Setup therefore materializes the pinned entry once, best-effort
with a bounded timeout, before finishing. When that step cannot run, for
example offline, the first client start performs the one-time install inside
the client's MCP startup deadline and may need one more restart.
If setup itself runs from a package-runner cache, the registration depends on
that cache entry and the selected runtime remaining installed. Removing either
makes startup fail; it does not download or resolve another package. Restore the
same installation and rerun setup. Changing package requests, cache roots, or
runtimes can create a different installation identity. If the old entry cannot be
identified, restore it first or manually remove the confirmed REA registration
before running setup again. A global installation provides a more durable path.
## Package runners and global installation
A package-runner invocation can use a REA version installed in the current project. To
request the latest release explicitly, use:
Package runners can select project-local packages and read project configuration,
even for explicit version requests. Acquire REA from a trusted working directory,
outside the target project, and pass absolute target paths to analysis commands.
REA cannot validate package acquisition that happens before its entrypoint starts.
To request the latest release explicitly, use:
```bash
npx rea-agents@latest setup
@@ -1597,16 +1707,14 @@ For an intentional rollback, make the package request explicit:
npm exec --yes --package=rea-agents@2.4.0 -- rea setup
```
Setup continues to pin persistent MCP registrations to the exact version that
performed setup. Running current setup later migrates unversioned or older
managed registrations through the normal reviewed setup transaction.
Setup binds persistent MCP registrations to the installation that performed setup.
Running current setup later migrates recognized legacy package-runner registrations
through the normal reviewed setup transaction.
With Bun, `bunx rea-agents setup` (or `bunx rea-agents@latest setup`) is the
equivalent entry point. Setup run through `npx` registers
`npx -y rea-agents@<version> mcp`; setup run through `bunx` or `bun x` registers
`bunx rea-agents@<version> mcp`. On Windows, setup wraps either command in
`cmd.exe /d /c` so clients can start the runner's command shim. Neither
registration points into the runner's temporary package cache.
equivalent acquisition entry point. Managed registrations use the selected Bun
executable and REA entry script directly. Windows registrations likewise use
absolute runtime and script paths, without a package-runner command shim.
REA supports Node.js 22.x (>=22.23.4), 24.x (>=24.22.1), and 26.12.1+. Node.js 23, 25, earlier 26.x releases, and prereleases are unsupported. It uses the npm already paired with that runtime and never upgrades Node.js, npm, or Homebrew.
@@ -1640,8 +1748,9 @@ Review the plan, approve the selected client changes, then restart those clients
This registers the installed entrypoint directly; installing the global package
alone does not replace existing `npx` registrations. Each client still has its own
REA process and analysis state. Direct registrations follow the installed CLI;
use `rea update` to update it. Running setup through `npx` again selects the pinned
package-runner command. Do not register paths inside npm's temporary package cache.
use `rea update` to update it. Running setup through `npx` again selects the
installation that executes setup, potentially in npm's temporary package cache;
retain that cache entry for as long as the registration uses it.
### npm workspace errors before REA starts
+4
View File
@@ -156,6 +156,10 @@ Select one literal seed kind: node ID, route, string, API, IPC channel, module,
or native export. Matching is exact or literal substring matching; regular
expressions and executable predicates are not accepted. The trace includes all
matching seeds and the complete graph reachable in the selected direction.
`both` follows relationships in either direction, except that a container
reached through `contains` from one of its children only leads further up its
containment chain, so a shared asset or artifact root does not pull in its
other children.
The result contains the matching basis, complete reachable subgraph, terminal
paths, authority summaries, and native handoffs. A handoff binds
@@ -364,6 +364,13 @@ Selected bounded text is then parsed as inert data to recover:
relative to the calling module; string worker and service-worker paths use
the document base URL or Node's working directory, which static analysis
does not establish, so they keep their expression and stay unresolved);
- Vite preload dependency tables (`__vite__mapDeps`): `./` entries resolve
relative to the importing chunk; base-relative entries such as
`assets/chunk.js` carry a `vite-output:` specifier and resolve under the one
ancestor directory of the importer that holds them, the build output
directory. A file found there but not analyzed, such as a stylesheet, stays
`unavailable` with its path. Selecting only the assets directory leaves them
`not-found`;
- route, network endpoint, storage, and vendor-marker observations;
- local source-map declarations and original
source names and content digests;
+9
View File
@@ -301,6 +301,15 @@ unknown metadata. Missing omission counts remain null. Summary output carries
every matching record; actual transport admission still applies. Omitted
`detail` preserves complete canonical bundle delivery; filters require summary.
Residual-unknown mutations reject missing Evidence or unknown references and
unproven verified resolutions with nonretryable `invalid_request` diagnostics.
`details.constraint` carries the validator's `kind`, owning `unknown_id`, and
missing `evidence_id` or `related_unknown_id` where applicable. Correct the
reported input or supply the required retained Evidence; unchanged retries do
not repair these constraints. Rejected mutations leave retained state unchanged.
Revision conflicts remain retryable `revision_conflict`; broken revision histories
remain `evidence_integrity_mismatch`.
`list_unknowns` returns every matching current head by default. For a smaller
inline response, supply `offset` and `limit`, then follow `next_offset` until it
is null. `total` counts all matching heads; each page preserves complete records
+14
View File
@@ -4,6 +4,20 @@ REA exposes six provider-neutral investigation workflows through the MCP
`prompts` capability. They complement the generated tool catalog; they do not add, remove, or
invoke tools by themselves.
Clients that expose tools but not prompts can use the target-free
`guided_workflows` tool with `action: "list"` or `action: "render"`. CLI callers
can use `rea guided-workflows` to list workflows, or pass `--action render`,
`--workflow-id ID`, and `--arguments-json '{"feature":"offline search"}'` to
render one. Both paths use the same contracts and renderer as native prompts;
rendering returns guidance only and does not inspect a target, start analysis,
or change session state.
The list contains workflow identifiers, descriptions, and argument requirements;
render returns only the selected workflow's text. Unknown argument names and
non-string values are rejected with field paths. `action: "list"` accepts no
workflow identifier or arguments. The MCP adapter respects the same
`REA_MCP_TOOL_FAMILIES` scope as native prompts, including render requests.
## Prompt inventory
| Prompt | Purpose |
+29 -3
View File
@@ -224,7 +224,30 @@ child output. Child output remains in the capture Evidence on stdout.
resource limits, and normalization settings have defaults; see
`processScenarioSchema` for the exact contract. Event times are milliseconds
from launch, must be ordered, and must fall within `timeout_ms`. Inputs marked
`sensitive` are sent to the process but persisted as a byte-count placeholder.
`sensitive` are sent to the process but persisted with empty `data`,
`sensitive: true`, and `input_bytes`. Exact nonempty CR/LF-separated input
literals are removed from retained terminal text, including cross-chunk and
VT-styled echoes. Rendering uses that redacted stream, so intermediate terminal
states and serialized state cannot restore removed bytes. Normalized text is
checked again. Encoded, transformed, or arbitrarily edited values are not a
secret-detection contract; their privacy remains unknown. Unmarked inputs and
caller-selected environment values remain evidence.
The capture-local matcher admits at most 65,536 prefix states and 8,388,608
construction work units. Projection uses at most 32 MiB of reusable position
indexes and 67,108,864 work units across one capture. Capacity failures return a
non-retryable resource error and withhold terminal text in partial observations.
If output admission leaves gaps, the sensitive capture withholds retained
terminal text rather than interpreting separated fragments as a complete stream.
`raw_terminal.redacted_bytes` reports bytes removed from admitted original
chunks; these bytes still consume `output_bytes` alongside retained safe bytes.
Private display effects remain unknown, with frame order and provenance retained.
This changes the sensitive-input Evidence representation: consumers of the old
`<redacted-input:N-bytes>` placeholder must read `input_bytes` instead. Existing
saved captures remain readable and are not rewritten; old files may still
contain previously echoed input. A private-input comparison policy version
separates old and new captures; reacquire both sides for comparable observations.
Environment overrides are recorded in the scenario commitment; inherited
values are not copied into Evidence.
@@ -234,7 +257,9 @@ A bare executable name uses the scenario's `PATH` override, then the inherited
An explicitly empty `PATH` searches only the working directory. Executable,
working-directory, and observation paths preserve native `symlink/..` traversal.
Terminal dimensions must satisfy `columns × (rows + scrollback) ≤ 1,000,000`.
Terminal width must be at least two columns: the pinned xterm renderer cannot
represent a one-column screen. This applies to the initial size and every resize.
Terminal dimensions must also satisfy `columns × (rows + scrollback) ≤ 1,000,000`.
Every scheduled resize uses the same selected scrollback and must fit this
working-buffer budget. REA rejects oversized scenarios before launching the
command; it never clamps the requested dimensions. This limit is independent
@@ -294,7 +319,8 @@ own classification until its exact resources are released.
Terminal frame `data` is the comparison text after the selected normalization.
New captures also preserve `raw_data` when normalization changes a PTY chunk;
otherwise `data` is already the original text. Older captures may lack the
otherwise `data` is already that text. For sensitive captures, both fields
contain only the safe projection; no unredacted backup is retained. Older captures may lack the
original text for changed chunks. Both values participate in Evidence identity,
while comparisons and trace assertions use the normalized text. Retaining the
original adds at most `limits.output_bytes` of text, because it comes from the
+1 -1
View File
@@ -350,7 +350,7 @@ npx rea-agents@latest setup
## سجل النجوم
🎉 **300,000 نجمة على GitHub — شكرًا لكم!**
🎉 **400,000 نجمة على GitHub — شكرًا لكم!**
شكرًا لكل من يستخدم REA، ويبلّغ عن الأخطاء، ويقترح ميزات، ويختبر إصدارات البناء، ويساهم بالإصلاحات.
+1 -1
View File
@@ -348,7 +348,7 @@ Melde Sicherheitslücken gemäß [SECURITY.md](../../SECURITY.md).
## Sternverlauf
🎉 **300.000 GitHub-Sterne – vielen Dank!**
🎉 **400.000 GitHub-Sterne – vielen Dank!**
Danke an alle, die REA nutzen, Fehler melden, Funktionswünsche äußern, Builds testen und Fehlerbehebungen beitragen.
+1 -1
View File
@@ -352,7 +352,7 @@ npx rea-agents@latest setup
## Ιστορικό αστεριών
🎉 **300.000 αστέρια στο GitHub — σας ευχαριστούμε!**
🎉 **400.000 αστέρια στο GitHub — σας ευχαριστούμε!**
Ευχαριστούμε όλους όσοι χρησιμοποιούν το REA, αναφέρουν σφάλματα, ζητούν νέες λειτουργίες, δοκιμάζουν builds και συνεισφέρουν διορθώσεις.
+1 -1
View File
@@ -348,7 +348,7 @@ Informa de vulnerabilidades siguiendo [SECURITY.md](../../SECURITY.md).
## Historial de estrellas
🎉 **300.000 estrellas en GitHub: ¡gracias!**
🎉 **400.000 estrellas en GitHub: ¡gracias!**
Gracias a quienes usan REA, informan de errores, solicitan funciones, prueban compilaciones y contribuyen con correcciones.
+1 -1
View File
@@ -333,7 +333,7 @@ npx rea-agents@latest setup
## تاریخچهٔ ستاره‌ها
🎉 **۳۰۰٬۰۰۰ ستارهٔ GitHub — سپاسگزاریم!**
🎉 **۴۰۰٬۰۰۰ ستارهٔ GitHub — سپاسگزاریم!**
از همهٔ کسانی که از REA استفاده می‌کنند، خطاها را گزارش می‌دهند، قابلیت‌های جدید پیشنهاد می‌کنند، buildها را آزمایش می‌کنند و در بهبود پروژه مشارکت دارند سپاسگزاریم.
+1 -1
View File
@@ -345,7 +345,7 @@ Signalez les vulnérabilités via [SECURITY.md](../../SECURITY.md).
## Historique des étoiles
🎉 **300 000 étoiles GitHub — merci !**
🎉 **400 000 étoiles GitHub — merci !**
Merci à toutes les personnes qui utilisent REA, signalent des bugs, proposent des fonctionnalités, testent les builds et contribuent des correctifs.
+1 -1
View File
@@ -366,7 +366,7 @@ Laporkan kerentanan melalui [SECURITY.md](../../SECURITY.md).
## Riwayat Bintang
🎉 **300.000 bintang GitHub, terima kasih!**
🎉 **400.000 bintang GitHub, terima kasih!**
Terima kasih kepada semua orang yang menggunakan REA, melaporkan bug, meminta fitur, menguji build, dan berkontribusi dalam perbaikan.
+1 -1
View File
@@ -345,7 +345,7 @@ Segnala vulnerabilità tramite [SECURITY.md](../../SECURITY.md).
## Cronologia delle stelle
🎉 **300.000 stelle su GitHub — grazie!**
🎉 **400.000 stelle su GitHub — grazie!**
Grazie a tutti coloro che usano REA, segnalano bug, propongono funzionalità, testano le build e contribuiscono con correzioni.
+1 -1
View File
@@ -350,7 +350,7 @@ npx rea-agents@latest setup
## スター履歴
🎉 **GitHub スター 300,000 件、ありがとうございます!**
🎉 **GitHub スター 400,000 件、ありがとうございます!**
REA の利用、バグ報告、機能の要望、ビルドのテスト、修正への貢献に感謝します。
+1 -1
View File
@@ -350,7 +350,7 @@ npx rea-agents@latest setup
## 스타 기록
🎉 **GitHub 스타 300,000개, 감사합니다!**
🎉 **GitHub 스타 400,000개, 감사합니다!**
REA를 사용하고, 버그를 보고하고, 기능을 요청하고, 빌드를 테스트하고, 수정에 기여해 주신 모든 분께 감사드립니다.
+1 -1
View File
@@ -355,7 +355,7 @@ npx rea-agents@latest setup
## Star ചരിത്രം
🎉 **250,000 GitHub stars — നന്ദി!**
🎉 **400,000 GitHub stars — നന്ദി!**
REA ഉപയോഗിക്കുന്നവർക്കും, bugs റിപ്പോർട്ട് ചെയ്യുന്നവർക്കും, features ആവശ്യപ്പെടുന്നവർക്കും, builds പരിശോധിക്കുന്നവർക്കും, fixes സംഭാവന ചെയ്യുന്നവർക്കും നന്ദി.
+1 -1
View File
@@ -348,7 +348,7 @@ Zgłaszaj podatności zgodnie z [SECURITY.md](../../SECURITY.md).
## Historia gwiazdek
🎉 **300 000 gwiazdek na GitHubie — dziękujemy!**
🎉 **400 000 gwiazdek na GitHubie — dziękujemy!**
Dziękujemy wszystkim, którzy używają REA, zgłaszają błędy, proponują nowe funkcje, testują kompilacje i przesyłają poprawki.
+1 -1
View File
@@ -348,7 +348,7 @@ Relate vulnerabilidades conforme [SECURITY.md](../../SECURITY.md).
## Histórico de estrelas
🎉 **300.000 estrelas no GitHub — muito obrigado!**
🎉 **400.000 estrelas no GitHub — muito obrigado!**
Obrigado a todos que usam o REA, relatam bugs, sugerem funcionalidades, testam builds e contribuem com correções.
+1 -1
View File
@@ -429,7 +429,7 @@ Raportează vulnerabilitățile conform [SECURITY.md](../../SECURITY.md).
## Istoricul stelelor
🎉 **300.000 de stele pe GitHub — vă mulțumim!**
🎉 **400.000 de stele pe GitHub — vă mulțumim!**
Mulțumim tuturor celor care folosesc REA, raportează erori, propun funcționalități,
testează versiuni și contribuie cu remedieri.
+1 -1
View File
@@ -348,7 +348,7 @@ npx rea-agents@latest setup
## История звёзд
🎉 **300 000 звёзд на GitHub — спасибо!**
🎉 **400 000 звёзд на GitHub — спасибо!**
Спасибо всем, кто использует REA, сообщает об ошибках, предлагает новые функции, тестирует сборки и помогает с исправлениями.
+1 -1
View File
@@ -348,7 +348,7 @@ npx rea-agents@latest setup
## ประวัติดาว
🎉 **300,000 ดาวบน GitHub — ขอบคุณทุกคน!**
🎉 **400,000 ดาวบน GitHub — ขอบคุณทุกคน!**
ขอบคุณทุกคนที่ใช้ REA รายงานบั๊ก เสนอฟีเจอร์ ทดสอบบิลด์ และช่วยแก้ไข
+1 -1
View File
@@ -344,7 +344,7 @@ Güvenlik açıklarını [SECURITY.md](../../SECURITY.md) üzerinden bildirin.
## Yıldız geçmişi
🎉 **GitHub'da 300.000 yıldız — teşekkürler!**
🎉 **GitHub'da 400.000 yıldız — teşekkürler!**
REA'yı kullanan, hata bildiren, özellik talep eden, derlemeleri test eden ve düzeltmelere katkıda bulunan herkese teşekkürler.
+1 -1
View File
@@ -348,7 +348,7 @@ npx rea-agents@latest setup
## Історія зірок
🎉 **300 000 зірок на GitHub — дякуємо!**
🎉 **400 000 зірок на GitHub — дякуємо!**
Дякуємо всім, хто використовує REA, повідомляє про помилки, пропонує нові функції, тестує збірки й допомагає з виправленнями.
+1 -1
View File
@@ -432,7 +432,7 @@ Security vulnerabilities [SECURITY.md](../../SECURITY.md) ke zariye report karei
## Stars ki history
🎉 **300,000 GitHub stars — shukriya!**
🎉 **400,000 GitHub stars — shukriya!**
REA use karne, bugs report karne, features suggest karne, builds test karne aur
fixes contribute karne wale sab logon ka shukriya.
+1 -1
View File
@@ -348,7 +348,7 @@ Báo cáo lỗ hổng theo [SECURITY.md](../../SECURITY.md).
## Lịch sử sao
🎉 **300.000 sao trên GitHub — xin cảm ơn!**
🎉 **400.000 sao trên GitHub — xin cảm ơn!**
Cảm ơn mọi người đã sử dụng REA, báo lỗi, đề xuất tính năng, kiểm thử bản dựng và đóng góp bản sửa lỗi.
+1 -1
View File
@@ -348,7 +348,7 @@ npx rea-agents@latest setup
## Star 歷史
🎉 **GitHub Star 達到 300,000 個,感謝大家!**
🎉 **GitHub Star 達到 400,000 個,感謝大家!**
感謝每一位使用 REA、報告錯誤、提出功能需求、測試建置和貢獻修復的朋友。
+1 -1
View File
@@ -348,7 +348,7 @@ npx rea-agents@latest setup
## Star 历史
🎉 **GitHub Star 达到 300,000 个,感谢大家!**
🎉 **GitHub Star 达到 400,000 个,感谢大家!**
感谢每一位使用 REA、报告错误、提出功能需求、测试构建和贡献修复的朋友。
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
-112
View File
@@ -1,112 +0,0 @@
{
"schemaVersion": 1,
"repository": "morluto/rea",
"version": "6.7.0",
"base": "2c4338db1fdb7773e05376301c558cff2a0b4b80",
"target": "b83b9caf4a5f0241a5fd370792f1aed1bffbb2cf",
"commits": [
{
"sha": "8cbaf6c7021b97dd4de6b5771c35fbed06b240e9",
"subject": "fix(tests): isolate command finalization module mocks",
"body": "fix(tests): isolate command finalization module mocks\n\nMove the five registered WASM finalization cases into the existing per-file isolated project. Preserve all result, exit-code, observation and cleanup assertions. A preloaded shared CLI graph reproduces three failures before the move; the isolated covered packet passes.\n\nFixes #4959\n",
"parents": ["2c4338db1fdb7773e05376301c558cff2a0b4b80"],
"authors": [
{
"name": "morluto",
"email": "[email protected]",
"user": {
"login": "morluto",
"__typename": "User"
}
}
],
"pullRequests": []
},
{
"sha": "6b180783ed04ebc87548c8385c80e05725804c47",
"subject": "fix(tests): budget CLI coverage from parsed options",
"body": "fix(tests): budget CLI coverage from parsed options\n\nUse the pinned Vitest parser for coverage and shard flags. Keep ordinary CLI workers at 384 MiB and covered workers at the tested 512 MiB with unchanged concurrency. Retain complete coverage and add actual-config regressions for false, provider-only and spaced-shard options.\n\nValidation: 27 focused cases; 16 covered cases with CLI preloaded; 5 docs checks; typecheck/lint; exact discovery of 1578 files. Measured covered control: 22.18s elapsed, 46.52s CPU, about 2.33 GiB sampled summed process-family RSS on macOS arm64. Full Linux CI pending. No runtime/device verification claimed.\n\nFixes #4960\n",
"parents": ["8cbaf6c7021b97dd4de6b5771c35fbed06b240e9"],
"authors": [
{
"name": "morluto",
"email": "[email protected]",
"user": {
"login": "morluto",
"__typename": "User"
}
}
],
"pullRequests": []
},
{
"sha": "bb212ffecb47e129cc569b9867b2b6e7c9b65685",
"subject": "fix(tests): inherit server identity deadline policy",
"body": "fix(tests): inherit server identity deadline policy\n\nRemove per-case ten-second overrides from MCP catalog and identity checks. Keep the suite-owned bounded ordinary and coverage deadlines, all assertions, and session cleanup.\n\nFixes #4967\n",
"parents": ["6b180783ed04ebc87548c8385c80e05725804c47"],
"authors": [
{
"name": "morluto",
"email": "[email protected]",
"user": {
"login": "morluto",
"__typename": "User"
}
}
],
"pullRequests": []
},
{
"sha": "b83b9caf4a5f0241a5fd370792f1aed1bffbb2cf",
"subject": "fix(setup): edit the duplicate JSON member that clients read (#4957)",
"body": "fix(setup): edit the duplicate JSON member that clients read (#4957)\n\nJSON.parse, JSONC parse, and native Claude Code keep the last duplicate\nobject member, and parseClientConfiguration therefore validates the last\none. The shared setup/uninstall serializer instead edited text through\njsonc-parser's modify and findNodeAtLocation, which select the first\nmatching member, so setup and uninstall on configurations with repeated\nserver tables or rea members wrote into a shadowed member and failed\nreadback.\n\nLocate every JSON text edit by the effective (last) member in one place,\nreplacing the first-member modify path. Removing an effective member\nthat shadows an earlier duplicate would activate that duplicate, whatever\nits ownership, so the serializer now returns a tagged shadowed_member\nfailure and setup and uninstall refuse before writing a backup or the\nconfiguration.\n\nFixes #4952\n\nCo-authored-by: Claude Opus 5.5 <[email protected]>",
"parents": ["bb212ffecb47e129cc569b9867b2b6e7c9b65685"],
"authors": [
{
"name": "Kaoru0822",
"email": "[email protected]",
"user": {
"login": "kaoru0822-kitauji",
"__typename": "User"
}
},
{
"name": "Claude Opus 5.5",
"email": "[email protected]",
"user": {
"login": "claude",
"__typename": "User"
}
}
],
"pullRequests": [4957]
}
],
"pullRequests": [
{
"number": 4957,
"title": "fix(setup): edit the duplicate JSON member that clients read",
"url": "https://github.com/morluto/rea/pull/4957",
"author": {
"login": "kaoru0822-kitauji",
"__typename": "User"
},
"mergeCommit": {
"oid": "b83b9caf4a5f0241a5fd370792f1aed1bffbb2cf"
},
"issues": [
{
"number": 4952,
"url": "https://github.com/morluto/rea/issues/4952",
"author": {
"login": "xu91102",
"__typename": "User"
},
"repository": {
"nameWithOwner": "morluto/rea"
}
}
]
}
]
}
+19 -1
View File
@@ -349,7 +349,10 @@ tag remains in main's ancestry. Keep the released tag unchanged.
The synchronization must include `.release-please-manifest.json`,
`package.json`, both root versions in `package-lock.json`, `CHANGELOG.md`,
`server.json`, and the versioned documentation examples. Run
`server.json`, both plugin manifests (`.claude-plugin/plugin.json` and
`.codex-plugin/plugin.json`), the Codex server declaration (`.codex-plugin/mcp.json`), both
marketplaces (`.claude-plugin/marketplace.json` and
`.agents/plugins/marketplace.json`), and the versioned documentation examples. Run
`npm run docs:generate`, `npm run docs:check`, and the release configuration
tests against the combined tree. After the merge, verify that the released
tag is an ancestor of main:
@@ -383,3 +386,18 @@ tag. Do not dispatch a fresh publish phase to repair an already-created
release: Release Please will not create the same release again. Do not move
the tag, delete the release, or unpublish npm as a retry. A defective public
package requires a reviewed correction and a new version.
### Plugin artifact metadata
Package verification selects files with npm, then packs an owned staging directory.
The release manifest omits development dependencies and development-only overrides;
its generated `npm-shrinkwrap.json` preserves the runtime dependency resolutions
and integrity values from `package-lock.json`. Runtime or unresolved overrides
fail packaging rather than being dropped. Source metadata is never rewritten.
The archive bundles production dependencies because Codex fetches npm plugins
without installing dependencies. The producer includes all locked PTY platform
archives after checking their integrity, without re-resolving the runtime tree.
Optional Frida stays outside the bundle so ordinary npm installation can retain
its host-specific installer. This also lets Claude install locked dependencies under its plugin policy while
retaining development security overrides. Both plugin marketplaces select the
same qualified npm artifact, which the release workflow publishes unchanged.
@@ -0,0 +1,13 @@
# Implementation plan
1. **Query values.** Widen the query property schema and spell numbers and booleans as JSON text where a call enters
serving.
2. **Sequence start.** `start` on sequence rules and their schema; the exemplar records integer fields above every held
value; serving and replay floor the next value at `start`.
3. **Later rules.** Merging a later create adopts its `composed`, `template` or started `sequence` rule when it spells
the exemplar's value.
4. **Made objects.** The `made-object` rule and schema; its explanation, merge, later-call check, counts taken, serving
and own members.
5. **Bulk link.** `respondedIds` reads the object of a one-element array member; `anyLinkedSource` lets a linked
create take any path values its plan stores in fields, in replay and serving.
6. **Skill, tests and docs.**
@@ -0,0 +1,46 @@
# Serve the calls agents make and the values applications count
Issue: https://github.com/morluto/rea/issues/4933
## Outcome
Eval agents' trajectories on rebuilt environments, and the write plans of the recordings they ran on, showed three
gaps REA could close:
- **Query values.** Served tools typed each query parameter as a string or an array of strings, while agents send page
sizes and flags as JSON numbers and booleans (`maxSize: 50`, `withItems: true`). The MCP server refused those calls
before REA saw them: 63 of 418 stored eval runs hit it, in EspoCRM, Homebox and PhotoPrism, and 18 never got the tool
to answer.
- **Counted and made values.** Vikunja numbers a project's tasks past those deleted earlier, so a created task's
`index` is not one past what the state holds; its `identifier` spells that index; and each created task gets a
`subscription` object the application makes with it. A second create disagreed with the first on all three, so
task creates were withheld.
- **Bulk creates.** Vikunja's UI creates a task through `POST /projects/{id}/tasks/bulk`, sending and answering the
task wrapped in `tasks: [ … ]`. The answer's id sat inside the array, so REA linked the call to no type.
The diagnosis also found that a recorded update stores only what it sent (EspoCRM's updates never changed an email
address), and that an object known only from a list that leaves out fields its own read shows can be neither read by
id nor updated. Both are recording gaps the skill now addresses.
## Behaviour
- **Query values.** A query parameter accepts a number or boolean besides a string or an array of strings, and serving
sends it as its JSON text.
- **Sequences from a start.** A field the first create showed above every value its type held, which a later create
shows one past the first's, is a `sequence` with that `start`: serving never goes below it.
- **Rules that spell the first create.** A field the plan kept as shown takes a later create's rule when that rule also
spells the first create's value: a `composed` or `template` string, or the `sequence` above.
- **Made objects.** A created object no other entity of the type holds otherwise, with a member the request stamped, is
a `made-object`: stamped members take the write's clock, members holding the entity's identity take its id, counts
go one past the largest value held objects show from a `start`, and other members stay as recorded.
- **Linked bulk creates.** A create's answer names the id of the one object a one-element array member holds. A linked
create accepts any path values when it creates into its type's own collection, whose path has no parameters, and
stores each path value in a field, as a Vikunja task stores its project: no path value changes where it creates.
- **Skill.** Recordings read each listed object by id, up to ten per collection, before writing.
Plans and views that need none of this are unchanged.
## Acceptance
- No write tool of the 130 recorded captures goes from served to withheld.
- Rebuilt from the same recordings, the branch passes at least the write tasks #7274 passes.
@@ -0,0 +1,13 @@
# Tasks
- [x] Accept numbers and booleans for query parameters.
- [x] Start a sequence from the value its first create took.
- [x] Adopt a later create's rule that spells the first create's value.
- [x] Learn, check and serve objects made with an entity.
- [x] Link creates whose answer wraps the entity in a one-element array, and let a linked create take path values
it stores.
- [x] Ask recordings to read each listed object by id.
- [x] Add view, serving, MCP boundary and unit tests.
- [x] Document the rules and the query argument types.
- [x] Measure the recorded captures and served write tasks.
- [x] Run static, formatting, documentation and test gates.
@@ -0,0 +1,88 @@
# Verification
Executed on macOS with Node v24.22.1 on this branch merged with #7274 at `45832406` (main `8706068b`), and on Ubuntu
24.04 VMs for the task runs.
## Diagnosis
The 418 eval runs stored on the comparison VMs, with their agent trajectories:
- 63 runs had a call refused before REA saw it because a query value came as a JSON number or boolean (`maxSize`,
`pageSize`, `withItems`, `isLocation`, PhotoPrism's `count`, `offset`, `merged`, `public`), and in 18 the agent never
got that tool to answer.
- EspoCRM's contact update fails in every run: the served update accepts only the members recorded updates stored
(`description`, `name`, `lastName`), and a seeded contact known only from lists can be neither read by id nor
answered for. On #7274, gpt-oss passes the task by creating a replacement contact and deleting the original.
- Vikunja's task creates were withheld by an unexplained `index`, `identifier` and `subscription` (main's recording),
or had no state target, being bulk creates (four recordings).
## Gates
- `npm run -s check:fast`, `format:check`, `scan:todos`, `docs:check`, `verify:test-discovery`, `mcp-catalog:check` and
`jscpd`: passed. `npx knip` reports `staticFileUrlPath` and `ObjcClassImportBindingCoverage`, which main `8706068b`
added and this branch does not touch.
- `npx vitest run --project domain --project services`: 11,430 passed, 6 skipped. `--project adapters`: 7,437 passed, 19
skipped, 3 failed: the Inspector cancellation test and two AppKit asset-catalog tests, which fail the same way on
#7274 at `e29e9c74`.
- `npm run -s test:focused --` the environment build, export and serve CLI boundaries, the analysis-view CLI and MCP
boundaries and the environment serve MCP boundary: passed. `outputSchemaProfile`, `toolSchemaValidity`,
`mcpSchemaValidation` and `outputSchemaSemanticConstraints`: passed; the compact catalog stays under its frame.
## Tests
- View: two task creates at a project path learn `index` as a `sequence` from 15, `identifier` as a `template` around
it and `subscription` as a `made-object` whose `id` counts from 21 and whose `entity_id` holds the task's own id; one
create alone keeps `index` as recorded; a bulk create whose body and answer wrap the task in `tasks` links to the
tasks type and is served; an object with no member the request stamped makes no rule.
- Serving: created tasks take index 15 then 16, `#15` then `#16`, and a subscription numbered 22 holding the second
task's id; a bulk create answers in its envelope, and one into another project stores that project.
- Query values: a number or boolean query value is spelled as its JSON text, and a list paged by a number answers as
with the same text; through the MCP server, `maxSize: 2` and `withItems: true` replay their recording exactly. The
served card of the `renamed` fixture grows 637 bytes, 91 for each of its seven query parameters.
- The seven behaviour tests fail on #7274 at `4ede1305`.
## Write plans and views over recorded captures
The 130 captures #7274 measured, built by #7274 at `45832406` and by this branch: 615 plans, 332 served and 333.
- No write goes from served to withheld. Newly served: Vikunja's bulk task create in #7260's final recording.
- Vikunja's task create in main's recording is now explained, by its counter, template and made subscription, but
main's `unknown-clock` withholds it in both builds, as that capture has no clock anchor. Measured on #7274 at
`4ede1305`, before that rule, it was served too (339 to 341).
- Six plans that stay withheld explain more: Homebox entity creates learn `assetId` as a counter from 5 in four
recordings, and Vikunja task creates in two.
- In #7260's two Vikunja recordings whose UI writes lost their bodies, the bulk create now counts as a task write, so
the scratch task it made is no longer held as seeded (35 to 34 and 36 to 35 initial entities), a read tool that read
only that task replays its recordings, and later reads check fewer lines. Every other view is unchanged.
## The same recordings, built by #7274 and by this branch
Claude Code (`claude-sonnet-5-5`) recordings from earlier rounds, rebuilt by #7274 at `e839af3f` (its last two merges
change no census) and by this branch, their write tasks run with `fireworks_ai/gpt-oss-120b` then
`fireworks_ai/glm-5p3-flash`, each checked from the environment's state:
| Recording | Write tasks | #7274 | This branch |
| ---------------------------------------------------------- | ----------- | ----------- | ----------- |
| Vikunja: #7260 final (bulk creates) | 3 + 2 | 1/5 1/5 | 2/5 2/5 |
| Vikunja: main (two), saved-values (two), #7260 (first two) | 6 × (3 + 2) | 6/30 6/30 | 6/30 6/30 |
| EspoCRM: main, #7260 (three), saved-values | 5 × 3 | 15/15 10/15 | 15/15 10/15 |
| Homebox: main, #7260 final, saved-values (two) | 4 × 2 | 6/8 6/8 | 6/8 6/8 |
| Total | 58 | 28/58 23/58 | 29/58 24/58 |
No recording passes fewer tasks with this branch.
- **Vikunja, bulk creates:** the create task passes with both models. The served bulk create numbers the task and makes
its subscription, and creates into Lighthouse Rota though the recording created into the inbox.
- **Refused calls:** agents' calls were refused at input validation 34 times with #7274 (30 in Vikunja) and once with
this branch, and agents made 286 tool calls against 317.
- **Main's `unknown-clock`** withholds Vikunja's task create and Homebox's tag create in main's recordings in both
builds, so those tasks fail in both. Rebuilt by #7274 at `4ede1305`, before that rule, this branch without the
linked-path change made main's Vikunja create task pass with both models (1/3 to 2/3) and changed no other result
among the recordings that finished.
## Not verified
- A fresh recording with skill 52, which reads each listed object by id.
- Path parameters given as numbers stay refused: they are required properties, which REA keeps free of combinators.
- A counter one create alone shows, and linked creates into a collection whose path has parameters, which still take
only their recorded path values.
@@ -0,0 +1,10 @@
# Implementation plan
1. **Blank members.** `WriteBody.blank` and its schema; creates explain empty members as blank; merges join them;
created fields apply a left-out blank member as sent.
2. **Listed ids.** `listed_ids` on list views and its schema; the gate learns them; list rendering sets them.
3. **Sorted collections.** The replayed state moves an entity whose sorted field changed; update plans carry the
type's sort; serving moves updated entities; a failed whole-collection read after an update can teach the sort.
4. **Observations.** A create's item read-back accepts unloaded answer values; updates treat empty-container
respellings as unchanged; later creates check unruled fields.
5. **Skill, schema, tests and docs.**
@@ -0,0 +1,56 @@
# Serve the writes callers make without recorded form fields
Issue: https://github.com/morluto/rea/issues/4933
## Outcome
Agents now record writes through an application's UI, and environments built that way refused most of the creates
natural-language tasks ask for, or withheld them:
- A form sends every field it shows, the ones left alone as `""`, `null`, `false`, `[]` or `{}`. Every member a
recorded create sent was required, so a call naming only a title was refused (Homebox tags required `color` and
`icon`, Linkding bookmarks `notes`, `unread` and `shared`, EspoCRM contacts about twenty members).
- Paperless-ngx answers lists with `all`, the ids of the listed entities in list order. The state did not render it,
so the first list read after every Paperless create contradicted the create.
- Grafana lists folders by title and Paperless lists tags by name. An update kept its entity where it was, so a rename
that sorted elsewhere contradicted the update, and a served rename listed in the wrong order.
- A Vikunja create answers before it loads what it stored: the owner with empty fields and Go's zero time, and empty
lists as `[]` where reads show `null`. The later item read was not taken as the observation, so it contradicted the
create; an update whose answer spelled an empty list `[]` was unexplained.
## Behaviour
- **Blank members.** A create member a recorded call sent empty is not required. A call that leaves it out is applied
as sending that value (`body.blank`), so the entity, the answer and every rule reading the member come out as for
the recorded call. A member a later call left out takes the default it showed instead. A call may send a member as
any recorded call sent it empty, such as `null` where another stored a string, even once a default is learned, and
one leaving it out without a default is applied as the earliest did. Updates are unchanged.
- **Listed ids.** A list view names top-level envelope members that list two or more listed entities' identity values
in list order (`listed_ids`). A rendered list sets them to its own entities' values, unless it is a page of more
matches, which keeps the recorded value as a partial page's total does.
- **Sorted collections.** An update that writes the field its type's creates are sorted by moves its entity to where
the new value sorts, in replay and serving (a bulk update moving each entity in turn), and its plan carries that
`insert`. When a read listing the whole collection in collection order (not sorted by its query) fails after an
update and the type's creates are not sorted by a
field the update writes, replay tries the collection sorted by such a field when the entities no exercised write
changed already follow it, as serving moves only written entities, and unless a create was placed by a sort
that excludes it or a whole-collection read in collection order (not sorted by its query) that placed a create did
not follow it; on a match, the type's creates and updates take that order.
- **Unloaded answers.** A create's later item read is taken as its observation when it differs from the answer only in
values the answer had not loaded: empty, zero, `false` or Go's zero time, or objects whose members are each the same
or loaded so; the plan points to those values (`observation.read.unloaded`), which a served create's answer keeps as
the recorded answer showed them, unless the call sent one the plan stores as sent, while the state holds the read's. An unsent update field that changes only between
`null`, `[]` and `{}` is unchanged. Write answers, bulk answers and replay's checks of them included, spell empty
lists and maps the call did not send as their recorded answers did, and a sent one as sent.
- **Unruled fields.** A later create that stored a field no rule writes otherwise than the plan keeps it is
`unexplained-change` at the create, so its recorded entity is held and later reads check other writes as before.
- **Skill.** The two scratch creates differ in what they send, the second only what a person must give, however they
are made; the skill shows how to read signed-in pages' accessibility trees from a scenario, and re-records once with
request steps a UI write whose body the browser did not expose.
Plans and views that need none of this are unchanged.
## Acceptance
- No write tool of the 109 recorded captures goes from served to withheld.
- Rebuilt from the same recordings, the branch passes at least the write tasks main passes.
@@ -0,0 +1,11 @@
# Tasks
- [x] Learn blank create members and apply them when a call leaves them out.
- [x] Learn and render list envelope members listing the listed ids.
- [x] Move updated entities in sorted collections, in replay and serving, and learn a sort from a rename.
- [x] Take item read-backs over unloaded create answers and empty-container respellings.
- [x] Withhold later creates that store an unruled field otherwise.
- [x] Add view, serving and unit tests.
- [x] Document the rules and view member, and update the skill.
- [x] Measure the recorded captures and served write tasks.
- [x] Run static, formatting, documentation and test gates.
@@ -0,0 +1,124 @@
# Verification
Executed on macOS with Node v24.22.1 at `5a3c6762` (main `99062fea`), and on Ubuntu 24.04 VMs with snap Chromium for
the agent recordings.
## Gates
- `npm run -s check:fast`, `format:check`, `scan:todos`, `docs:check`, `verify:test-discovery`, `mcp-catalog:check` and
`jscpd`: passed. `npx knip` reports one unused export, `isToolFamilyId` in `src/contracts/toolFamilies.ts`, which this
branch does not touch and main reports too.
- `npx vitest run --project domain --project services --project adapters`: 18,281 passed, 25 skipped, 1 failed ("stops
Inspector target authorization after the current stat settles"), which fails the same way on main in this environment.
- `npm run -s test:focused --` the environment build, export and serve CLI boundaries, the analysis-view CLI and MCP
boundaries and the environment serve MCP boundary: 7 files, 75 passed. `outputSchemaProfile.test.ts`: 4 passed; the
full catalog grows 648 bytes and the compact catalog, 554,640 bytes, does not change.
## Tests
- View: form creates whose empty members differ require only the member both filled and apply the others as sent; a
create with only a last name, sending `null` for strings and for a flag another create sent `false`, merges and is
served; a rename moves a label in a collection its
creates sort by name; a rename that sorts first teaches a folder collection's title order to its creates and updates;
a list envelope's `all` is learned and creates there are confirmed; a create answer with an unloaded owner and `[]`
for `null` takes its item read; an update answer spelling an empty list `[]` is unchanged; a later create storing a
derived field otherwise is withheld at the create and later deletes stay confirmed.
- Serving: a label created from its name alone stores the empty members as sent and is inserted by name, and one
without the required name is refused; renamed labels and folders move to where their new names sort, and a bulk
rename of two labels moves each; a created tag's id appears in the list envelope's `all` in list order; a note sent
`null` as one recording did is accepted after a later call taught `""` as its default, which a note left out takes;
a created and an updated task answer `assignees: []` as their recordings did while reads show `null`.
- Review cases: a member's recorded empty value stays accepted once a default is learned; a rename teaches title order
where the creates fit both id and title order; moving written entities in turn sorts `[a, b, c, d]` with `b→h` and
`c→i` as `[a, d, h, i]`. The plan and serving tests for these fail on the branch's first head `0ceff6fa`.
## Write plans over recorded captures
The 109 captures the saved-values branch measured: 50 published captures and 59 Claude Code recorded from a prompt,
built by main and by this branch.
| Captures | Write plans | Served on main | This branch |
| ------------------------- | ----------- | -------------- | ----------- |
| 50 published | 187 | 70 | 70 |
| 59 recorded from a prompt | 337 | 177 | 209 |
No write goes from served to withheld. The 32 newly served are 23 Paperless creates (tags, correspondents and document
types, every recording), 3 Vikunja label creates, a Vikunja task update, Grafana's folder rename and delete, a Homebox
tag delete, and a Linkding bundle update and delete. 65 plans require fewer members and 66 carry `body.blank`; 30 plans
change `insert`, mostly updates that now move their entity by the field their type's creates sort by.
## Agent-built environments
Claude Code (`claude-sonnet-5-5`) built an environment of each of seven applications from the same prompt, as the
saved-values branch measured, and each bundle's write tasks ran with `fireworks_ai/gpt-oss-120b` and
`fireworks_ai/glm-5p3-flash`. Each task checks its result from the environment's state, including that nothing else
changed.
The Homebox and Linkding task specs used until now checked for seed objects an earlier round's recordings left behind
("Rec Fragile", `scratch-two.example.test`), which no current recording holds, so their "nothing else changed" check
could not pass. They now check the current seed. With the corrected specs, main passes both Homebox and both Linkding
tasks on the saved-values branch's recordings, so earlier rounds' 0/2 for those applications measured the specs, not
the required members.
**The same recordings, built by main and by this branch** (gpt-oss, GLM):
| Recording | Write tasks | Main | This branch |
| ----------------------------- | ----------- | --------- | ----------- |
| Paperless, saved-values | 3 | 1/3 2/3 | 3/3 3/3 |
| Paperless, main (two) | 3 + 3 | 3/6 3/6 | 5/6 5/6 |
| Grafana, saved-values | 2 | 0/2 0/2 | 1/2 1/2 |
| Grafana, main (two) | 2 + 2 | 3/4 3/4 | 4/4 3/4 |
| Vikunja, saved-values + main | 5 + 5 | 3/10 3/10 | 3/10 3/10 |
| EspoCRM, saved-values + main | 3 + 3 | 1/6 1/6 | 1/6 1/6 |
| Homebox, saved-values + main | 2 + 2 | 2/4 2/4 | 2/4 2/4 |
| Linkding, saved-values + main | 2 + 2 | 2/4 2/4 | 2/4 2/4 |
| Miniflux, saved-values | 3 | 3/3 3/3 | 3/3 3/3 |
| Total | 42 | 18 19 | 24 23 |
No recording passes fewer tasks with this branch. Paperless creates pass once served. The saved-values Grafana
recording's rename is served and passes; its create still needs the saved-values branch's composed address.
Vikunja's task create, EspoCRM's leftover contact (held as seeded data, so every "nothing else changed" check fails)
and the main Homebox recording, which wrote no tags, are unchanged.
**Fresh recordings.** Each application was recorded afresh with main (skill 49) and with this branch. The branch was
recorded again for the four applications whose first branch recording passed fewer tasks, and a third time after the
skill's sixth step was reworded; the final skill's recordings are the last column (served writes, then tasks):
| Application | Write tasks | Main | Branch, first two recordings | Branch, final skill |
| ----------- | ----------- | -------------------- | ---------------------------- | -------------------- |
| Paperless | 3 | 2, 1/3 2/3 | 8, 1/3 2/3 | |
| Linkding | 2 | 0, 0/2 0/2 | 5, 2/2 2/2 | |
| Miniflux | 3 | no bundle | 4, 3/3 3/3 | |
| Grafana | 2 | 3, 2/2 2/2 | 1, 0/2 1/2; 3, 1/2 2/2 | 0, 0/2 0/2 |
| Vikunja | 3 + 2 | 3, 1/3 1/3 + 2/2 2/2 | 0, 0/5 0/5; 0, 0/5 0/5 | 1, 1/3 1/3 + 0/2 0/2 |
| EspoCRM | 3 | 2, 1/3 1/3 | 2, 1/3 1/3; 2, 1/3 1/3 | 2, 1/3 1/3 |
| Homebox | 2 | 4, 2/2 2/2 | 1, 0/2 0/2; 1, 0/2 0/2 | 3, 2/2 2/2 |
Every fresh recording builds the same plans with main and with this branch, except Paperless (main serves 0 of the
branch recording's writes and 2 of 3 of its own), Vikunja's main recording (3 to 4) and the EspoCRM recordings below;
the per-application differences are what each agent recorded:
- **Paperless:** the branch recording serves creates, renames and deletes of tags, correspondents and document types.
Its two tag creates used a light and a dark color, whose derived `text_color` differs, so the tag create stays
withheld and the tasks, which create a tag, score as on main.
- **Linkding:** main's agent recorded no writes. The branch's second bookmark create sent only `url` and `title`, so
the served create requires only those.
- **Miniflux:** main's agent stopped and asked for an API key. The branch's agent created a token in the UI and sent it
as a literal header value, which REA recorded as written; the instance and its token were destroyed afterwards.
- **EspoCRM:** following the sixth step, the branch's second contact sent only a last name, with `null` for strings the
first stored and for flags it sent `false`. That merge was refused until the empty-spelling fix in this PR; with it,
the create requires only `lastName` and `name` and its task passes, as on main.
- **Vikunja:** the first two branch agents wrote through the UI, whose request bodies Chromium did not expose, so every
write was `body-unusable`. The sixth step then read "fill every field the form offers"; reworded to say how the
creates differ, not where they are made, the third agent wrote with request steps. It recorded no label writes.
- **Homebox:** the first two branch agents recorded item writes only, the third tags as main's did.
- **Grafana:** the branch agents created folders with request steps and their own uids. The second create's address
needs the saved-values branch's composed-address fixes to be explained, so the create is withheld here.
## Not verified
- One to three fresh recordings per application and version; agent runs vary more than the builds do.
- Request bodies Chromium does not expose for some UI writes (Vikunja's); the skill now re-records such a write once
with request steps.
- Paperless derives `text_color` from `color`, so a create whose two recorded colors differ in brightness stays
withheld; EspoCRM's create still requires `name` and `emailAddressData`, which it derives from other members.
@@ -0,0 +1,10 @@
# Implementation plan
1. **Soft deletes.** `markedDeleted` reads the deleted flags; item-read checks, adoption and replay's resync treat a
marked object as absent.
2. **Page sizes.** Add the names to the paging and query `limit` sets.
3. **Later fields.** Extraction collects the members later plain item reads show; the gate adds those no merged entity
holds to the item view without requiring them.
4. **Answers.** `laterItemFields` names the item view's fields no initial entity holds; write answers and the verdict's
`incomplete-entities` check skip them for entities without them.
5. **Skill, tests and docs.**
@@ -0,0 +1,42 @@
# Serve the writes that later reads explain
Issue: https://github.com/morluto/rea/issues/4933
## Outcome
Environments built from a prompt withheld some writes in every recording of an application, though the recordings read
each write back:
- EspoCRM keeps a deleted record. A read of it answers `200` with the record and `deleted: true`, which replay took
as the record still being there, so contact and task deletes were contradicted or left unconfirmed.
- EspoCRM pages its lists with `maxSize`, which REA did not know as a page size, so a list read after a delete was not
taken as listing the whole collection and confirmed nothing.
- Vikunja shows `subscription` only on tasks created after the recording started. The tasks' item view did not show
it, so reads of created tasks disagreed with the state and contradicted the task update before them; and an update
whose recorded answer shows it could not answer for a seeded task, which lacks it.
## Behaviour
- **Soft deletes.** An item read whose object is marked deleted (`deleted`, `isDeleted` or `is_deleted` set to `true`)
agrees with an entity the state does not hold, as a `404` does. Replay drops the entity such a read names, and such
an object never seeds or adopts an entity. The item view records the flag its reads set as `deleted_flag`, and
serving keeps an entity a served delete removed from such a type until a reset or load, so a read of it by id answers
its last fields with the flag `true`, as the application does.
- **Page sizes.** `maxSize`, `max_results` and `maxResults` are page sizes, for a list read's whole-collection
membership and for a list query's `limit` role.
- **Fields only later reads show.** An item view also shows the members that reads after its type's first write show
of the entities they name, when no initial entity holds them. They are never required. An update or delete that
answers with the entity, a bulk update's answer included, leaves such a field out when the entity does not hold it,
and the field no longer makes the write `incomplete-entities`. A field some initial entity holds keeps the existing rule: an answer showing it is
refused for an entity without it.
- **Skill.** Updates change the fields a person would change (a name, an email address, a status, a due date) rather
than only a description, since a served update accepts only the members a recorded update stored, and a deleted
object may read back marked deleted instead of `404`.
Plans and views that need none of this are unchanged.
## Acceptance
- No write tool of the 130 recorded captures goes from served to withheld, and no plan's rules, body or `insert`
changes.
- Rebuilt from the same recordings, the branch passes at least the write tasks #7260 passes.
@@ -0,0 +1,11 @@
# Tasks
- [x] Treat item reads of objects marked deleted as absent in checks, adoption and replay.
- [x] Answer a read of a served-deleted entity of such a type marked deleted, until a reset or load.
- [x] Learn `maxSize`, `max_results` and `maxResults` as page sizes.
- [x] Add fields only later item reads show to item views, unrequired.
- [x] Leave those fields out of write answers for entities without them, and stop them withholding updates.
- [x] Add view, serving and unit tests.
- [x] Document the rules and update the skill.
- [x] Measure the recorded captures and served write tasks.
- [x] Run static, formatting, documentation and test gates.
@@ -0,0 +1,84 @@
# Verification
Executed on macOS with Node v24.22.1 on this branch, built on #7260's head `053264bd` (main `662ec2e4`), and on
Ubuntu 24.04 VMs for the task runs.
## Gates
- `npm run -s check:fast`, `scan:todos`, `docs:check`, `verify:test-discovery`, `mcp-catalog:check` and `jscpd`:
passed.
- `npx oxfmt --check .` reports `scripts/verify-apk-signature.mjs` only, which this branch does not touch and
`053264bd` reports too. `npx knip` reports `isToolFamilyId` in `src/contracts/toolFamilies.ts` only, as main does.
- `npx vitest run --project domain --project services --project adapters`: 18,420 passed, 25 skipped, 1 failed ("stops
Inspector target authorization after the current stat settles"), which fails the same way on main in this
environment.
- `npm run -s test:focused --` the environment build, export and serve CLI boundaries, the analysis-view CLI and MCP
boundaries and the environment serve MCP boundary: 7 files, 75 passed.
## Tests
- View: a contact delete whose item read answers the record with `deleted: true` is served, confirmed by that read and
the list; only a `true` `deleted`, `isDeleted` or `is_deleted` marks an object deleted; a delete is confirmed by a
list read paged with `maxSize`; a field only created tasks show joins the task item view without being required; a
task update whose answers show it is served.
- Serving: a deleted contact leaves the served list; an update of a seeded task, single or bulk, answers without the
field only created tasks show (the bulk case fails on `4515a000`, before the review fix).
- The six behaviour tests fail on `053264bd`.
- The existing serving test that refuses to give chapter rows to a book the state holds without them still passes. A
first version of the answer rule left out every field the item view does not require, which served that update; the
rule now covers only fields no initial entity holds.
## Write plans and views over recorded captures
The 109 captures #7260 measured and #7260's 21 fresh recordings, built by `053264bd` and by this branch:
| Captures | Write plans | Served on #7260 | This branch |
| ------------------------- | ----------- | --------------- | ----------- |
| 50 published | 187 | 70 | 71 |
| 80 recorded from a prompt | 428 | 258 | 268 |
- No write goes from served to withheld, and no plan's rules, body or `insert` changes. The 11 newly served are
EspoCRM contact deletes in seven recordings, the published EspoCRM capture's task delete, and Vikunja task updates
in three recordings.
- With main `c6893d1a` merged, whose `unknown-clock` withholds clock writes without an anchor on both builds, #7260
serves 322 and this branch 332. One of the Vikunja task updates is among the writes it withholds, so this branch
gains 10, and none goes from served to withheld.
- 73 list views learn a page size they held as `inert`, valid only for its recorded values, as `limit`: 72 EspoCRM
lists (`maxSize`) and one Umami list (`maxResults`). One EspoCRM stream list now counts a line paged only by
`maxSize` as listing the whole collection and explains its other line's query; no view explains fewer lines.
- Six item views gain a field only later reads show, never required: Vikunja's tasks `subscription` in four recordings,
a Homebox entity's `children` and Mattermost's posts `has_reactions`. Every other view is unchanged.
## The same recordings, built by #7260 and by this branch
Claude Code (`claude-sonnet-5-5`) recordings from earlier rounds, rebuilt by each version, their write tasks run with
`fireworks_ai/gpt-oss-120b` then `fireworks_ai/glm-5p3-flash`, each checked from the environment's state:
| Recording | Write tasks | #7260 | This branch |
| ---------------------------------------------- | ----------- | ----------- | ----------- |
| EspoCRM: main, #7260 (three), saved-values | 5 × 3 | 5/15 5/15 | 15/15 10/15 |
| EspoCRM: saved-values, leftover contact | 3 | 0/3 0/3 | 0/3 0/3 |
| Vikunja: main, #7260 final, saved-values (two) | 4 × (3 + 2) | 8/20 8/20 | 8/20 8/20 |
| Homebox: main | 2 | 2/2 2/2 | 2/2 2/2 |
| Total | 40 | 15/40 15/40 | 25/40 20/40 |
No recording passes fewer tasks with this branch.
- **EspoCRM:** the contact delete passes with both models on all five recordings. The contact update passes with
gpt-oss on all five only by replacing the contact: the served update refuses `emailAddress`, which no recorded update
stored, so the agent created a contact with the new address and deleted the old one, which the task's check, by name
and address, accepts. The update itself is refused in both builds, as is a read of a seeded contact by id, since the
state knows seeded contacts only from list reads, which lack fields the item reads show. GLM's update fails in both
builds the same way, and it often ends a turn at the eval agent's 1,024-token completion limit.
- **EspoCRM, leftover contact:** the recording started with a contact an earlier attempt left, held as seeded data, so
every "nothing else changed" check fails in both builds.
- **Vikunja:** the newly served task update accepts the members the recorded update stored (`title`, `description`,
`priority`), so the task asking to mark a task done is refused in both builds. The skill now asks for updates that
change a status, an email address or a due date. Task creates are unchanged.
- **Homebox:** the entity view's new `children` field changes no task.
## Not verified
- An application answering `deleted: true` for a record it still lists, as a trash view might: such a read counts as
absent. No recorded capture does this.
- The skill's update wording, which no fresh recording has used yet.
+5 -1
View File
@@ -210,7 +210,11 @@ changing a callback solely to satisfy its declared `void` return type.
Ordinary Knip remains. `check`, `check:pr`, and `check:ci:deep` run reachability
then typed lint sequentially after cached work. Typed lint builds the project
type graph and stays separate from fast feedback. Its default Go settings are
`GOMAXPROCS=2` and `GOMEMLIMIT=1024MiB`; caller-selected settings take precedence.
`GOMAXPROCS=1` and `GOMEMLIMIT=1024MiB`; caller-selected settings take precedence.
One Go worker reuses checker caches across the file queue without changing the
file inventory or TypeScript project scope. The Go memory target is soft and
does not cap the complete process family's memory; measure the full invocation
when enforcing a host budget.
Routine iterations and rebases need focused regressions and relevant checks.
Before handing off a PR, run `npm run check` and generated-document checks when
+72
View File
@@ -0,0 +1,72 @@
# U-Boot legacy uImage containers
`inspect-artifact` and `extract-artifact` recognize the big-endian
`0x27051956` legacy uImage signature regardless of filename. They read local
stored bytes without booting, executing, decompressing or modifying the image.
FIT is a different format; its analysis is outside this reader.
The same domain header/table facts and artifact reader serve CLI and MCP.
The root is an archive with unknown executable architecture. The header's CPU,
OS, type, compression, load address and entry address are declarations, not proof of
compatible or executable payloads. Raw numeric identifiers remain available
alongside names; unrecognized identifiers have the name `unknown`.
The header's CRC32 and the declared payload's CRC32 must verify before a
successful inventory. `payload.bin` contains the complete stored payload.
Multi-file images also expose `image-0.bin`, `image-1.bin`, and so on. These
component names are generated from table order. Components follow the zero-terminated big-endian size table, with four-byte
padding after every nonfinal component. Their physical source ranges and
content hashes retain precise provenance. Extraction writes the complete
payload and every component, so multi-file output can approach twice the input
size. Compressed payloads remain compressed.
The fixed 32-byte name field is retained as `name_bytes_hex`. `name` contains
the UTF-8 bytes before the first NUL, or null when those bytes cannot be decoded
exactly. For example, a producer can truncate a multibyte character at the
32-byte boundary; replacement characters do not establish a source name.
## Bounded work and retained facts
The reader admits at most 4,096 components, a resource policy rather than a
format maximum. It reads at most 16,388 bytes for the size table and refuses an
excess count as a capacity limit before its remaining payload CRC scan.
Shared artifact identity hashing has separate cost. The
same prefix participates in the native incremental CRC32 calculation; no
custom bytewise CRC table or full-payload buffer is retained. Remaining CRC
reads and member streams use 64 KiB chunks. Parsed facts are cached for one reader. Carrier state is checked during
directory preparation, at member open and after stream completion; metadata
getters return the retained facts. Close joins active member
pipelines and final validation before releasing the descriptor; failed cleanup
remains owned for retry.
Prefer a retained summary when only a few facts are needed:
```sh
rea inspect-artifact firmware.bin --detail summary \
--evidence_output firmware.evidence.json --json
rea extract-artifact firmware.bin --json
```
With MCP, `inspect_artifact` with `detail: "summary"` retains the complete
Evidence. Use `inspect_analysis_view` and its `parent_evidence_id` for exact
logical paths, batches of occurrence/content IDs, selected fields and pages.
Coverage and limitations remain attached. Retained views are historical
observations tied to the saved Evidence. Complete Evidence remains available;
these views do not narrow the examined inventory.
## Verification scope
The public OpenWrt 24.10.0 GL-AR150 initramfs kernel is checked against its
[published digest](https://downloads.openwrt.org/releases/24.10.0/targets/ath79/generic/sha256sums):
`027c60a7061462d8145b11efa23ab02b334acb9a23a215f9095860b632ace4ff`.
Independent `mkimage`/`dumpimage` 2023.01 provide producer and extraction
oracles. Generated containers use real host ELF files, system metadata and
stored gzip bytes; their declared ARM64, VxWorks or RISC-V labels do not prove
boot/runtime compatibility. U-Boot's identifier and alignment definitions are
pinned to commit `ece349ade2973e220f524ce59e59711cc919263f`.
Source and compiled CLI/full/compact MCP tests cover aligned component ranges,
unknown identifiers/text, malformed/truncated inputs, CRC failures, capacity,
source changes, cancellation, pending reads, final validation and cleanup
retry. Real producer results, complete-task cost measurements and limitations
are reported in the PR. No target runtime or decompressor behavior is verified.
+86
View File
@@ -0,0 +1,86 @@
# UnityFS AssetBundle archives
`inspect-artifact` and `extract-artifact` read standalone UnityFS AssetBundle
containers without running Unity or the producer application. Header format
versions 6 through 8 are supported. Version 9 adds a different block-table
layout and is unsupported until independently verified.
Inspection retains the header facts: format version, player and engine version
strings, declared size, dataflags, blocks-and-directory table bounds, the
producer's 16-byte data hash as declared, and the resolved data-area offset.
Members (`CAB-*` SerializedFiles, `.resS` resource streams, shared assets)
retain declared source paths, virtual stream offsets, sizes and raw node flags.
The directory flag identifies directories. Virtual offsets are not physical
carrier locations: member `source_location` is null. Member reads only examine
their covering blocks. Uncompressed ranges stream directly; compressed ranges
reuse at most one completed decoded block. Independent 64 KiB output chunks
preserve cache ownership. Carrier freshness is checked when a member is opened,
including when a cached block suffices, and after its stream completes. Reader close cancels and
joins outstanding streams before releasing its descriptor.
Data blocks compressed with none, LZ4 or LZ4HC are supported. LZMA-compressed
blocks and blocks-info tables are reported as unsupported coverage or an
`unavailable` member read; the directory and any readable members are still
retained. Bundles declaring asset-bundle encryption keep their header facts
but report unknown directory coverage; no decryption is attempted. The reused
0x200 encryption/padding flag is interpreted by producer revision. Ambiguous
revisions retain unknown layout coverage. Unread block counts and data offsets
are null, never invented zeroes. A declared
size that exceeds the input file, node paths outside the block stream, and
counts that cannot fit their table are malformed-input errors.
## Resource bounds
These are resource policies, not format maxima. Encoded and decoded directory
buffers are each limited to 16 MiB. Buffers, parsed rows, retained entries and
path projections share a conservative 64 MiB admission budget, reserved before
allocation. Each LZ4 decode allows at most 64 MiB of output and its bounded
encoded representation; plain blocks stream without this allocation. A reader
admits four active member streams. The shared archive plaintext-work ceiling
is the larger of 256 MiB and 500 times carrier size: declared stream bytes,
readable logical occurrences (including aliases), and cumulative block-decode
attempts are each charged against that ceiling. Empty-output blocks retain
their source count and offsets but are removed from member read plans. A separate
member-I/O budget allows four times the larger of 1,024 and the sum of nonempty
blocks, directory nodes, and readable 64 KiB plaintext chunks. It admits ordinary
passes while bounding repeated tiny-block reads independently of output bytes. Unavailable content is retained
as named occurrences with unknown coverage and no invented content hash.
Use existing summary delivery and retained inventory views for selected fields,
filters and paging; full Evidence remains available without repeating the graph.
## CLI
```sh
rea inspect-artifact assets/hotfix.bundle --detail summary \
--evidence_output evidence/hotfix.json --json
rea extract-artifact assets/hotfix.bundle --json
```
Detection is byte-based on the `UnityFS\0` signature, so `.bundle`, `.ab`,
`assets_*` YooAsset bundles and extensionless files all classify as `unityfs`
containers. Compact delivery keeps complete Evidence in the requested bundle.
## Verification scope
Synthetic fixtures cover format 6–8 layouts, padding independent of header
alignment, directories, malformed tables, allocation admission, overlapping
members, unsupported LZMA, cancellation inside decoding, paused iteration,
close during I/O, cleanup retry, empty blocks with unsupported codecs, cache
freshness and independent output buffers.
Compiled CLI and full/compact MCP tests cover inventory and extraction.
A public UnityPy `atlas_test` bundle (Unity 2018.4.11f1, format 6) is verified
against independent UnityPy 1.24.2 raw member bytes. Source SHA-256:
`61ee5afb5f5c64f0a4f65f039d09b2509198363e713423289c895067ac215177`.
The source is pinned to UnityPy commit
`417d59522a998fdee9a9c2b29fe35f3063222457`. The same public corpus supplies
`banner_1` (2018.4.4f1) and `char_118_yuki.ab` (5.6.7f1), whose raw members
also match the independent oracle. `xinzexi_2_n_tex` (2017.4.37f1) uses LZMA
data: its member names and sizes are retained with unavailable hashes and
unknown coverage. UnityPy-produced repacks additionally verify format 7/8,
padded and unpadded front tables, end tables, and unsupported LZMA directories.
These repacks are library-produced tests, not Unity engine executions. The PR
records producer parameters, source digests and measured task costs; synthetic
fixtures alone do not establish real Unity version coverage. SerializedFile objects, textures and runtime
behavior are outside this container reader's verification scope.
+8 -6
View File
@@ -15,7 +15,7 @@ harm_rank,location,trigger,current_copy,user_risk,proposed_replacement,implement
11,src/domain/errors.ts:ArtifactOperationError,Artifact inventory or extraction fails,"Artifact {operation} failed: {reason} at {logicalPath} (declared_sha256=...)",Exposes paths hashes and internal reason,"Reason-specific plain message with safe recovery step.",implemented,focused assertions passed; full `npm run check` passed
12,src/domain/errors.ts:EvidenceFileError,Evidence file operation fails,"Evidence bundle {operation} failed: {reason}",Internal reason token gives weak recovery,"Reason-specific plain message with safe recovery step.",implemented,focused assertions passed; full `npm run check` passed
13,src/domain/errors.ts:EvidenceIntegrityError,Evidence validation fails,Raw constructor message,May expose validation internals and lacks recovery,"Evidence is invalid or has changed. Recreate or re-import it, then try again.",implemented,focused assertions passed; full `npm run check` passed
14,src/domain/errors.ts:UnknownRegistryError,Evidence-state mutation fails,"Residual unknown registry mutation failed: {reason}",Exposes internal subsystem and state token,"Missing IDs identify the session and retry; concurrent revision conflicts refresh current state.",implemented,focused assertions passed; full `npm run check` passed
14,src/domain/analysisErrorPresentation.ts:UnknownRegistryError,Residual-unknown reference or graph constraint fails,Specific graph constraint and missing reference ID,Unchanged retries cannot repair missing references or insufficient proof,Correct the named reference or proof constraint; revision conflicts retain refresh recovery.,implemented,96 focused tests passed; real stdio correction and atomic-rejection controls passed
15,src/domain/errors.ts:ProcessCaptureError,Process capture fails,Raw constructor message,May expose process and cleanup internals,"Process capture could not complete. Check command access and limits, then try again.",implemented,focused assertions passed; full `npm run check` passed
16,src/domain/errors.ts:AnalysisTimeoutError,Analysis deadline expires,"REA's {operation} operation reached its {timeoutMs}ms server/provider deadline; extending only the client deadline does not change this budget",Returned server deadline is distinct from a host timeout that discards the response,"Inspect partial Evidence, provider diagnostics, and reported cleanup, then follow the returned remediation. Change a request field or REA_* setting only when the returned remediation explicitly recommends that change. Restart REA when that text says the value is read at startup. A mention that a control does not raise the deadline is not a recommendation to change it. When it names no setting, reduce supported work; do not invent one. JavaScript names analysis_timeout_ms and exact source_paths; omit an unneeded filter rather than passing an empty array.",implemented,generic projection does not invent a setting; producer remediation names analysis_timeout_ms or the applicable REA_* value; initialization and the shipped skill teach the same split
17,src/domain/errors.ts:HopperTimeoutError,Provider deadline expires,"Hopper did not respond within {timeoutMs}ms",Exposes provider and internal deadline,"Analysis took too long. Try a smaller request, then run `rea doctor` if it continues.",implemented,focused assertions passed; full `npm run check` passed
@@ -44,13 +44,15 @@ harm_rank,location,trigger,current_copy,user_risk,proposed_replacement,implement
32,src/application/Setup.ts:configureDetectedClients(backup),Agent configuration backup fails,"{clientName} configuration backup verification failed; no successful configuration was reported.",Exposes machine client key and obscures no-write guarantee,"Agent configuration could not be backed up, so no change was made. Check file permissions, then rerun setup.",implemented,focused assertions passed
33,src/application/Setup.ts:configureDetectedClients(write),Agent configuration write fails,"{clientName} configuration write verification failed; no successful configuration was reported.",Exposes machine client key and gives no recovery,"Agent configuration could not be updated. Check file permissions, then rerun setup.",implemented,focused assertions passed
34,src/application/Setup.ts:configureDetectedClients(readback),Written agent configuration cannot be verified,"{clientName} configuration readback verification failed; no successful configuration was reported.",Exposes machine client key and omits backup recovery,"Agent configuration could not be verified after writing. The original was restored and its backup at {backupPath} was retained. Repair the configuration file or restore that backup, then rerun setup. Without a backup: Agent configuration could not be verified. Repair the configuration file, then rerun setup.",implemented,focused assertions passed
35,src/application/Setup.ts:installSkill,Skill install or semantic readback fails,Agent skill installation or readback failed.,No useful recovery action,"REA analysis skill could not be installed or verified. Check permissions for `~/.agents/skills`, then rerun setup.",implemented,focused assertions passed
36,src/main.ts:serveStdio.onerror,Established MCP client connection reports an error,MCP stdio transport error,Exposes transport implementation and gives no recovery,"Retry the tool call once only when it is safe to repeat or known not to have started. If it may already have changed the target, or it fails again, tell the user REA lost its MCP client connection and ask them to restart the MCP client.",implemented,verified by injected transport onerror runtime test
35,src/application/SetupSkill.ts:SetupSkillInstallError,Skill install or semantic readback fails,Stage and selected path; separate failure reason and recovery action,A non-directory path is not a demonstrated permission failure,Correct the reported failure; directory blockers retain regular-directory and --skill=false guidance.,implemented,"Public CLI ancestor/root refusal, preserved caller file, complete install and idempotent rerun; focused recovery tests"
36,src/main/reportMcpTransportError.ts:reportMcpTransportError,MCP pipe breaks or connection resets,"Retry the tool call once only when it is safe to repeat or known not to have started. If it may already have changed the target, or it fails again, tell the user REA lost its MCP client connection and ask them to restart the MCP client.",Retry safety and restart guidance for known disconnection,Keep current copy,implemented,injected EPIPE recovery regression
36,src/main/reportMcpTransportError.ts:reportMcpTransportError,MCP message fails schema validation,MCP message validation failed. Check the protocol fields before retrying a message that is safe to repeat. Continue using the connection while it remains responsive.,Validation failure does not establish connection loss,Keep current copy,implemented,public stdio invalid-ID controls followed by successful ping and file analysis
36,src/main/reportMcpTransportError.ts:reportMcpTransportError,MCP error has no known validation or disconnection identity,MCP reported an error with unknown connection state. Check whether the client is still connected before following connection-loss recovery.,Connection state remains unknown,Keep current copy,implemented,injected unknown-error recovery regression
37,src/main.ts:serveStdio throw,MCP connection cannot be created,Failed to start MCP stdio transport,Exposes transport implementation and gives no recovery,"Retry once. If it fails again, tell the user REA could not start its MCP connection and ask them to restart the MCP client, then run `rea doctor`.",implemented,verified by injected transport startup-throw runtime test
38,src/main.ts:requestShutdown catch,Connection or analysis session close rejects,Failed to shut down cleanly,Does not explain duplicate-process risk or recovery,REA could not close cleanly. End the REA process before starting it again.,implemented,verified by injected handle-close rejection runtime test
39,src/main.ts:entrypoint catch,Unhandled rejection escapes run(),Unexpected server startup failure,Vague and lacks diagnostic recovery,"Do not repeat this startup. Tell the user REA could not start and ask them to restart the MCP client, then run `rea doctor`.",implemented,verified by injected entrypoint rejection test
40,src/application/Upgrade.ts:unknown-install-method,Running package is not owned by detectable global npm installation,Update manually with: npm install --global rea-agents@latest,Already gives exact safe recovery,Keep current copy,unchanged,existing focused assertion passed
41,src/application/Upgrade.ts:install,npm global install subprocess exits unsuccessfully,npm install --global rea-agents@latest failed.,Does not identify bounded likely checks before retry,"REA could not update through npm. Check npm registry access and global install permissions, then run: npm install --global rea-agents@latest",implemented,focused assertion passed
40,src/application/Update.ts:unknown-install-method,Running package is not owned by detectable global npm installation,Update manually with: npm install --global rea-agents@latest,Old global-npm advice does not identify the owning installation,"Update this installation with the package manager or source checkout that owns it. For a one-off latest invocation, use npx rea-agents@latest.",implemented,36 Update tests passed; public CLI unowned-installation refusal verified with isolated package-manager fixtures
41,src/application/Update.ts:install,npm global install subprocess exits unsuccessfully,npm install --global rea-agents@latest failed.,Old registry/global-install advice omits the actual version prefix and installer failure,"REA could not install {version} in {prefix}: {failure}. Repair this npm installation, then rerun rea update.",implemented,36 Update tests passed; public CLI owned npm fixture failure preserved version prefix and installer reason
42,src/application/Uninstall.ts:removeClient read,Existing agent configuration cannot be read,Configuration could not be read.,No recovery step,"Configuration could not be read. Check file permissions, then rerun uninstall.",implemented,verified by injected filesystem read failure test
43,src/application/Uninstall.ts:removeClient parse,Agent configuration is malformed,Malformed configuration was preserved without mutation.,Does not name required repair format or retry,"Configuration is not valid JSON/TOML and was not changed. Repair it, then rerun uninstall.",implemented,focused JSON assertion passed; TOML path uses same branch
44,src/application/Uninstall.ts:removeClient backup,Configuration backup copy fails,Configuration backup failed; no mutation was attempted.,No recovery step,"Configuration could not be backed up, so no change was made. Check file permissions, then rerun uninstall.",implemented,verified by injected filesystem backup failure test
@@ -114,7 +116,7 @@ Then rerun this installer.",implemented,verified by shell branch inspection
88,install.sh:binary absent,npm exits successfully without executable,the rea command was not installed at local path,Leaks local path and no recovery,"npm completed without installing the rea command. Check the npm global bin directory and PATH, then retry.",implemented,verified by exact absent-installed-command test
89,install.sh:version read,Installed rea executable cannot report its version,could not read the installed REA version.,No recovery instruction,"The installed REA version could not be read. Reinstall the requested version, then retry.",implemented,verified by exact unreadable-installed-version test
90,install.sh:version mismatch,Installed rea reports a different version,installed version ACTUAL does not match EXPECTED.,Low risk; gives exact integrity mismatch,Keep current copy,unchanged,verified by exact installer test
90.5,@modelcontextprotocol/server:tool input validation,Caller sends arguments that violate a declared tool schema,"Canonical invalid_request with the SDK diagnostic plus recovery.kind=inspect_input_contract and a bounded instruction to read the advertised schema or call tools/list",Keeps field/type correction while telling the agent how to recover; diagnostics over 4 KiB stay on the existing retained-error path,Normalize ordinary validation failures and retain oversized diagnostics,verified by real in-memory and stdio MCP client/server calls with wrong argument types
90.5,@modelcontextprotocol/server:tool input validation,Caller sends arguments that violate a declared tool schema,"Canonical invalid_request with the SDK diagnostic plus recovery.kind=inspect_input_contract and a bounded instruction to read the advertised schema or call tools/list",Keeps field/type correction while telling the agent how to recover; diagnostics over 4 KiB stay on the existing retained-error path,Normalize ordinary validation failures and retain oversized diagnostics,implemented,verified by real in-memory and stdio MCP client/server calls with wrong argument types
90.6,incur:unknown CLI command,Caller enters an unknown REA command,"'COMMAND' is not a command for 'rea'. Suggested command: rea --help",Low risk; identifies input and exact discovery action,Keep framework-generated copy,unchanged,verified by real packaged dispatcher invocation
90.7,src/ghidra/GhidraProviderClient.ts:projectPrimarySessionError,One procedure's decompile reaches REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS,"Ghidra decompilation of {entry} exceeded the {N}-second limit set by REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS. Only this procedure's decompilation was stopped; the Ghidra session remains usable.",Low risk; names the setting and keeps the session,Keep current copy,implemented,provider projection test and real-provider lane
91,src/application/binaryDiagnostics/ByteEntropyService.ts,Invalid file or window,"Expected a regular file.; Selected file could not be found.; Selected byte window starts beyond the end of the file.; Selected byte window extends beyond the end of the file.; File size exceeds exact numeric offset representation.",Invalid selection cannot produce complete entropy Evidence,Choose a readable regular file and a window within its byte size,implemented,entropy service and MCP boundary tests
1 harm_rank location trigger current_copy user_risk proposed_replacement implementation_status verification_result
15 11 src/domain/errors.ts:ArtifactOperationError Artifact inventory or extraction fails Artifact {operation} failed: {reason} at {logicalPath} (declared_sha256=...) Exposes paths hashes and internal reason Reason-specific plain message with safe recovery step. implemented focused assertions passed; full `npm run check` passed
16 12 src/domain/errors.ts:EvidenceFileError Evidence file operation fails Evidence bundle {operation} failed: {reason} Internal reason token gives weak recovery Reason-specific plain message with safe recovery step. implemented focused assertions passed; full `npm run check` passed
17 13 src/domain/errors.ts:EvidenceIntegrityError Evidence validation fails Raw constructor message May expose validation internals and lacks recovery Evidence is invalid or has changed. Recreate or re-import it, then try again. implemented focused assertions passed; full `npm run check` passed
18 14 src/domain/errors.ts:UnknownRegistryError src/domain/analysisErrorPresentation.ts:UnknownRegistryError Evidence-state mutation fails Residual-unknown reference or graph constraint fails Residual unknown registry mutation failed: {reason} Specific graph constraint and missing reference ID Exposes internal subsystem and state token Unchanged retries cannot repair missing references or insufficient proof Missing IDs identify the session and retry; concurrent revision conflicts refresh current state. Correct the named reference or proof constraint; revision conflicts retain refresh recovery. implemented focused assertions passed; full `npm run check` passed 96 focused tests passed; real stdio correction and atomic-rejection controls passed
19 15 src/domain/errors.ts:ProcessCaptureError Process capture fails Raw constructor message May expose process and cleanup internals Process capture could not complete. Check command access and limits, then try again. implemented focused assertions passed; full `npm run check` passed
20 16 src/domain/errors.ts:AnalysisTimeoutError Analysis deadline expires REA's {operation} operation reached its {timeoutMs}ms server/provider deadline; extending only the client deadline does not change this budget Returned server deadline is distinct from a host timeout that discards the response Inspect partial Evidence, provider diagnostics, and reported cleanup, then follow the returned remediation. Change a request field or REA_* setting only when the returned remediation explicitly recommends that change. Restart REA when that text says the value is read at startup. A mention that a control does not raise the deadline is not a recommendation to change it. When it names no setting, reduce supported work; do not invent one. JavaScript names analysis_timeout_ms and exact source_paths; omit an unneeded filter rather than passing an empty array. implemented generic projection does not invent a setting; producer remediation names analysis_timeout_ms or the applicable REA_* value; initialization and the shipped skill teach the same split
21 17 src/domain/errors.ts:HopperTimeoutError Provider deadline expires Hopper did not respond within {timeoutMs}ms Exposes provider and internal deadline Analysis took too long. Try a smaller request, then run `rea doctor` if it continues. implemented focused assertions passed; full `npm run check` passed
44 32 src/application/Setup.ts:configureDetectedClients(backup) Agent configuration backup fails {clientName} configuration backup verification failed; no successful configuration was reported. Exposes machine client key and obscures no-write guarantee Agent configuration could not be backed up, so no change was made. Check file permissions, then rerun setup. implemented focused assertions passed
45 33 src/application/Setup.ts:configureDetectedClients(write) Agent configuration write fails {clientName} configuration write verification failed; no successful configuration was reported. Exposes machine client key and gives no recovery Agent configuration could not be updated. Check file permissions, then rerun setup. implemented focused assertions passed
46 34 src/application/Setup.ts:configureDetectedClients(readback) Written agent configuration cannot be verified {clientName} configuration readback verification failed; no successful configuration was reported. Exposes machine client key and omits backup recovery Agent configuration could not be verified after writing. The original was restored and its backup at {backupPath} was retained. Repair the configuration file or restore that backup, then rerun setup. Without a backup: Agent configuration could not be verified. Repair the configuration file, then rerun setup. implemented focused assertions passed
47 35 src/application/Setup.ts:installSkill src/application/SetupSkill.ts:SetupSkillInstallError Skill install or semantic readback fails Agent skill installation or readback failed. Stage and selected path; separate failure reason and recovery action No useful recovery action A non-directory path is not a demonstrated permission failure REA analysis skill could not be installed or verified. Check permissions for `~/.agents/skills`, then rerun setup. Correct the reported failure; directory blockers retain regular-directory and --skill=false guidance. implemented focused assertions passed Public CLI ancestor/root refusal, preserved caller file, complete install and idempotent rerun; focused recovery tests
48 36 src/main.ts:serveStdio.onerror src/main/reportMcpTransportError.ts:reportMcpTransportError Established MCP client connection reports an error MCP pipe breaks or connection resets MCP stdio transport error Retry the tool call once only when it is safe to repeat or known not to have started. If it may already have changed the target, or it fails again, tell the user REA lost its MCP client connection and ask them to restart the MCP client. Exposes transport implementation and gives no recovery Retry safety and restart guidance for known disconnection Retry the tool call once only when it is safe to repeat or known not to have started. If it may already have changed the target, or it fails again, tell the user REA lost its MCP client connection and ask them to restart the MCP client. Keep current copy implemented verified by injected transport onerror runtime test injected EPIPE recovery regression
49 36 src/main/reportMcpTransportError.ts:reportMcpTransportError MCP message fails schema validation MCP message validation failed. Check the protocol fields before retrying a message that is safe to repeat. Continue using the connection while it remains responsive. Validation failure does not establish connection loss Keep current copy implemented public stdio invalid-ID controls followed by successful ping and file analysis
50 36 src/main/reportMcpTransportError.ts:reportMcpTransportError MCP error has no known validation or disconnection identity MCP reported an error with unknown connection state. Check whether the client is still connected before following connection-loss recovery. Connection state remains unknown Keep current copy implemented injected unknown-error recovery regression
51 37 src/main.ts:serveStdio throw MCP connection cannot be created Failed to start MCP stdio transport Exposes transport implementation and gives no recovery Retry once. If it fails again, tell the user REA could not start its MCP connection and ask them to restart the MCP client, then run `rea doctor`. implemented verified by injected transport startup-throw runtime test
52 38 src/main.ts:requestShutdown catch Connection or analysis session close rejects Failed to shut down cleanly Does not explain duplicate-process risk or recovery REA could not close cleanly. End the REA process before starting it again. implemented verified by injected handle-close rejection runtime test
53 39 src/main.ts:entrypoint catch Unhandled rejection escapes run() Unexpected server startup failure Vague and lacks diagnostic recovery Do not repeat this startup. Tell the user REA could not start and ask them to restart the MCP client, then run `rea doctor`. implemented verified by injected entrypoint rejection test
54 40 src/application/Upgrade.ts:unknown-install-method src/application/Update.ts:unknown-install-method Running package is not owned by detectable global npm installation Update manually with: npm install --global rea-agents@latest Already gives exact safe recovery Old global-npm advice does not identify the owning installation Keep current copy Update this installation with the package manager or source checkout that owns it. For a one-off latest invocation, use npx rea-agents@latest. unchanged implemented existing focused assertion passed 36 Update tests passed; public CLI unowned-installation refusal verified with isolated package-manager fixtures
55 41 src/application/Upgrade.ts:install src/application/Update.ts:install npm global install subprocess exits unsuccessfully npm install --global rea-agents@latest failed. Does not identify bounded likely checks before retry Old registry/global-install advice omits the actual version prefix and installer failure REA could not update through npm. Check npm registry access and global install permissions, then run: npm install --global rea-agents@latest REA could not install {version} in {prefix}: {failure}. Repair this npm installation, then rerun rea update. implemented focused assertion passed 36 Update tests passed; public CLI owned npm fixture failure preserved version prefix and installer reason
56 42 src/application/Uninstall.ts:removeClient read Existing agent configuration cannot be read Configuration could not be read. No recovery step Configuration could not be read. Check file permissions, then rerun uninstall. implemented verified by injected filesystem read failure test
57 43 src/application/Uninstall.ts:removeClient parse Agent configuration is malformed Malformed configuration was preserved without mutation. Does not name required repair format or retry Configuration is not valid JSON/TOML and was not changed. Repair it, then rerun uninstall. implemented focused JSON assertion passed; TOML path uses same branch
58 44 src/application/Uninstall.ts:removeClient backup Configuration backup copy fails Configuration backup failed; no mutation was attempted. No recovery step Configuration could not be backed up, so no change was made. Check file permissions, then rerun uninstall. implemented verified by injected filesystem backup failure test
116
117
118
119
120
121
122
File diff suppressed because it is too large. Load diff
@@ -4,10 +4,14 @@ Issue [#4772](https://github.com/morluto/rea/issues/4772). Qualified runtime
`b49ee40bf330b7bf1c5180b14dcc104c1c613a8e`, tree `0e5f8fe2feae1796c340c07c26e0e28e5d4328d8`,
integrates main `c8ed32853881b08aa83459d4da55a28a4e09b0c8`. Recorded at
`2026-10-22T08:30:58.269Z`. Subsequent verification-only
commits change these reports. The [JSON record](javascript-route-inventory-2026-10-21.json)
commits updated these reports. The [archived JSON record](https://github.com/morluto/rea/blob/7d0efef62d56d0a10134bea62d709302daa1565f/docs/verification/javascript-route-inventory-2026-10-21.json)
pins original inputs, exact commands, profiles, failed controls and historical revisions.
Current-head GitHub CI and automated review must be checked separately.
The full JSON archive is 2,681,223 bytes, with SHA-256
`9d7ea2582a6f33ff0741578aec9f01b9327b3256ce50c582437164868e544d51`. It remains available in Git
at the linked immutable revision; this page retains the historical summary.
The latest main contracts, selected environment and ASAR admission remain shared
by CLI and MCP. The staged-code hook runs lint fixes before formatting, resolving the
producing order behind the earlier CI format failure.
+85 -24
View File
@@ -670,12 +670,12 @@ Every entity cites its `record_ordinals`.
`views` lists one plan per read tool in tool order:
| Shape | Plan |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `item` | The entity object at `pointer` (the root, one member, or `/0` or `/<member>/0`), the ordered `fields` the view shows and the `required_fields` every recording had, and `envelope_ordinal`: the recording whose response, with the entity replaced, every item response shares, leaving out the `per_response_members` each response there mints: a top-level UUID or clock that differed on every repeat of every concrete path the view answers, with no write between, and never stayed the same on a repeat elsewhere; list views name theirs too |
| `list` | The array at `pointer`, its `fields`, `membership` (`all`: every entity of the collection in state order, when each recording listed exactly that, had no query or only paging parameters (`limit`, `offset`, `page`, `per_page`, `perpage`, `page_size`, `pagesize`, `size`, `start`, `_start`, `_end`), no item-total member (`total`, `count`, `totalCount`, `item_count`, `totalcount`, `itemcount`, `numberOfElements`, but not `total_pages` or `per_page`; a number or a string of digits; top-level, or in a top-level object beside the list such as `pageInfo.totalRows`) differing from the number listed, and no `has_more`, `next` or next-page token member set; `recorded`: the entities each recording listed, except on the lines `all_record_ordinals` names, which each meet those conditions on their own and list every entity), and `counters` that equal the number listed |
| `document` | The latest initial value of a list tool whose responses hold no identified list, when its initial reads share one request line; held in `documents` with that `path`, whether or not the capture writes it, unless its value changed across any recorded write between those reads (`fields-differ`); its `per_response_members` are the members of the value each response on that path mints |
| `recorded` | A `reason` the tool replays its recordings instead |
| Shape | Plan |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `item` | The entity object at `pointer` (the root, one member, or `/0` or `/<member>/0`), the ordered `fields` the view shows and the `required_fields` every recording had (the fields include those only reads after the type's first write show, of entities that hold its identity, when no initial entity holds them, as Vikunja adds `subscription` to the tasks it creates; they are never required), and `envelope_ordinal`: the recording whose response, with the entity replaced, every item response shares, leaving out the `per_response_members` each response there mints: a top-level UUID or clock that differed on every repeat of every concrete path the view answers, with no write between, and never stayed the same on a repeat elsewhere; list views name theirs too |
| `list` | The array at `pointer`, its `fields`, `membership` (`all`: every entity of the collection in state order, when each recording listed exactly that, had no query or only paging parameters (`limit`, `offset`, `page`, `per_page`, `perpage`, `page_size`, `pagesize`, `maxsize`, `max_results`, `maxresults`, `size`, `start`, `_start`, `_end`, ignoring case), no item-total member (`total`, `count`, `totalCount`, `item_count`, `totalcount`, `itemcount`, `numberOfElements`, but not `total_pages` or `per_page`; a number or a string of digits; top-level, or in a top-level object beside the list such as `pageInfo.totalRows`) differing from the number listed, and no `has_more`, `next` or next-page token member set; `recorded`: the entities each recording listed, except on the lines `all_record_ordinals` names, which each meet those conditions on their own and list every entity), `counters` that equal the number listed, and `listed_ids`: top-level members that list two or more listed entities' identity values in list order (Paperless's `all`), which a rendered list sets to its own unless it is a page of more matches |
| `document` | The latest initial value of a list tool whose responses hold no identified list, when its initial reads share one request line; held in `documents` with that `path`, whether or not the capture writes it, unless its value changed across any recorded write between those reads (`fields-differ`); its `per_response_members` are the members of the value each response on that path mints |
| `recorded` | A `reason` the tool replays its recordings instead |
An item or list plan is kept only when it reproduces the latest initial
recording of each request line from the state, comparing JSON values without
@@ -712,7 +712,7 @@ exactly what it recorded from the collection's initial entities:
| `search` | `q`, `query`, `search`, `searchTerm`, `term`, `keyword`(s) | entities with one of `fields` containing the value, ignoring case; the fields are those it matched in the listed items, URLs only where nothing else matched; a term that matched no field leaves the parameter inert until another line's term does |
| `sort` | a name asking for an order in any common spelling: `sort`, `_sort`, `sort[]`, `sort[0][field]`, `sorters[...]`, `sortBy`, `sort-column`, `sortKey`, `order`, `orderBy`, `ordering` | ordered by the field the value names (`name`, `-name`, `name:desc`, `name-ASC`): numbers by value, text ignoring case, null last ascending, ties in state order |
| `direction` | a direction name (`dir`, `direction`, `sortDir`, `sortOrder`, `sortDirection`, `order_direction`, json-server's `_order`, and `order`) given `asc`, `desc`, `ascending` or `descending`; given anything else, `order` is a `sort` and the others are ordinary parameters | the sort's direction |
| `limit` | `limit`, `per_page`, `perPage`, `page_size`, `size`, `count`, `take`, `itemsPerPage` | page size; zero or a negative value lists every match |
| `limit` | `limit`, `per_page`, `perPage`, `page_size`, `size`, `count`, `take`, `itemsPerPage`, `maxSize`, `max_results`, `maxResults` | page size; zero or a negative value lists every match |
| `offset` | `offset`, `start`, `_start`, `skip` | matches skipped before the page |
| `page` | `page` | page number, counted from `first` (1 or 0, whichever explains the line) |
| `inert` | any that names no order or direction; never a true `reverse`, or a paging value that is not a safe integer | changes nothing for its recorded `values`; other values are not computed |
@@ -733,8 +733,12 @@ order. Its `effect` is `create` for a `POST` to a type's
plus one trailing parameter, or to that item path plus one action segment,
`delete` for a `DELETE` to it, `document` for a `PUT`, `PATCH` or `POST` to a
document's path, an `update` or `delete` of each entity a write at the
collection names by id in its body, and otherwise `none` with reason
`no-state-target`. A plan is built from the tool's first successful call: its
collection names by id in its body, a `create` for a `POST` whose answer names
an id (at its top level, in an object member or in the one object of a
one-element array member) that a later item read of a type first shows (served
only with the path values its recorded call sent, unless it creates into the
type's own collection, whose path has no parameters, and a `path` rule stores
each of its path values), and otherwise `none` with reason `no-state-target`. A plan is built from the tool's first successful call: its
request body must be a usable JSON object (as response bodies are usable), and
the written entity must show in that call's response or in a later read before
the next write. Each field of a created entity gets the first rule that
@@ -743,21 +747,38 @@ value's shape), `echo` of a body member (`sorted` when the application stored
its strings in code-point order rather than as sent), `copy` of a body string of
8 or more characters, a `path` value, a `slug` of a body member, a `clock` value within 10
minutes of the request in its recorded format, a `sequence` one past the
largest value of its collection or type, a `unique` UUID, opaque token or
largest value of its collection or type, and never below its `start`, a `unique` UUID, opaque token or
integer of 10 or more digits that no entity of the type holds (where every one
holds a distinct integer of the same length, as time-based ids do), a
`nested-clock` object whose clocks the request stamped, a `template` around
`nested-clock` object whose clocks the request stamped, a `made-object` no
other entity of the type holds otherwise, whose `clocks` the request stamped and
whose other members hold the entity's own identity (`own`), one past the largest
value held objects show (`counts`, each from a `start`), or what every held
object shows, a `template` around
a generated value, a `reference` to an entity, or a `composed` string; other
fields keep the observed value. When the first read of a created entity before
the next write shows fields the create's answer lacks, or its clocks to another
precision, the observed entity takes them (`observation.read`). An item read
the next write shows fields the create's answer lacks, its clocks to another
precision, or values the answer had not loaded (an empty, zero or `false` value,
Go's zero time, or an object whose members are each loaded so), the observed
entity takes them (`observation.read`, whose `unloaded` points to the values the
answer had not loaded). An item read
that agrees with the answer otherwise lends its fields and clock spellings and
no longer confirms the create; one that disagrees or omits an answer field its
item view renders still checks it. Clock agreement uses the coarser recorded
precision, including microseconds. A list read
only adds fields the answer lacks and still checks it. Serving answers a create
in its answer's shape and clock precision and stores the fields of both with
the item read-back's clock precision. A `reference` is an object holding fields of the
in its answer's shape and clock precision, with empty lists and maps the call
did not send spelled (`null`, `[]` or `{}`) as its answer spelled them and the
values it had not loaded as it answered them (unless the call sent one the plan
stores as sent), leaving out members the read added inside an object the answer
showed, and stores the fields of both,
as the item read-back shows them, with its clock precision. Updates
answer empty lists and maps they did not send as their answers spelled them
too; a member a call sent is answered as sent. An update or delete that answers
with the entity leaves out a field only later reads showed, which no initial
entity holds, when the entity does not hold it, as Vikunja's reads of tasks it
did not create show no `subscription`. A `reference` is an object
holding fields of the
entity a sent member names, as the state holds it (a parent beside `parentId`):
the member is accepted with any value, serving looks the entity up and refuses a
call naming one the environment does not hold or holds in more than one
@@ -768,9 +789,21 @@ when, explained on its own, it agrees with it: both calls show the same fields
but for a reference whose member a call without it did not send (one an earlier
create added stays), every field both rule they rule alike (an echo one showed
sorted stays sorted, unless a call stored its strings out of order as sent),
and a field neither rules holds one value. Members either call sent are accepted, only those every call sent are
and a field neither rules holds one value, unless the later call's rule for it
spells the earlier calls' value too: a `composed` or `template` string, or a
`sequence` started at that value (`start`), as Vikunja numbers a project's tasks
past those deleted before the capture. Two `made-object` rules agree in their
clocks and own members, and a count only the later call shows starts at the
first object's value; a later create's made object must hold clocks its own
request stamped, in the rule's format. Members either call sent are accepted, only those every call sent are
required, and an echoed member a call left out takes the value that call stored
(`body.defaults`). A clock rule widens to `trimmed` only when no held entity or
(`body.defaults`). A member a create sent empty (`""`, `null`, `false`, `[]` or
`{}`), as a form sends a field left as it was, is not required: a call that
leaves it out is applied as sending that value (`body.blank`), unless a call
that left it out showed a default, which it then keeps. A call may send it as any
recorded call sent it empty, even after a default is learned, and one leaving it
out is applied as the earliest did, as when one create sent `false` and another
`null`. A clock rule widens to `trimmed` only when no held entity or
earlier call showed that clock at a fixed width, and a slug to spelling `&` as
the word (`ampersand: "and"`) only when no call the plan accepted nor entity the
type held dropped one. A later
@@ -783,7 +816,8 @@ or leaves out is `unexplained-change`. A create whose observed reference
differs from the
entity the plan rebuilds, whether or not it sent the reference member, is
`unexplained-change`. So is a create that left out an echoed member yet shows a
value different from the learned default.
value different from the learned default, or that stored a field no rule writes
otherwise than the plan keeps it.
A `composed` string is literal text and parts read from the fields other rules
write: a field's value, its slug, or a clock's calendar digits
(`YYYYMMDDhhmmss`, `YYYYMMDD`, `YYYY-MM-DD` or `YYYY/MM/DD`, in the clock's
@@ -821,7 +855,8 @@ a newly learned slug. Any other member stored
differently is accepted only with its
recorded value (`body.fixed`) and stores the value the call that showed it
recorded (the `value` of its `constant` rule), and a field it changed without
sending must be a clock, a nested clock, an increment, a slug, a fresh `unique`
sending (an empty list or map one answer spells `null` and another `[]` or `{}`
is unchanged) must be a clock, a nested clock, an increment, a slug, a fresh `unique`
value, a `stamp` of the acting user, a value `derived` from the one member it
stores nowhere, a `reference` to the entity a sent member names (kept by a call that names the entity the field already repeats), a `composed` string whose parts read members it stored as sent
or fields it left as they were and spell the value before the call too, or for
@@ -831,7 +866,7 @@ value or set must hold for every call of the tool, and an action is served
only once a call found what it sets already set. A write is served only
with the query its first call sent, which serving applies to a call that
leaves a parameter out and the catalog advertises as required. Every member the first call sent is required, except by `PATCH` and
`POST` updates.
`POST` updates and members a create sent empty.
A read on a line `query` answers is checked against the query evaluated over
the replayed state. It covers the collection only when every parameter sorts or
@@ -846,8 +881,15 @@ collection when the path names no held entity), and taints what it wrote at or
above. A read the served environment would answer from the state is compared
with its recording as the read plans are, as the whole response it would serve:
the entity or list rendered into the envelope of the item view's or the line's
base recording, with the members the state still holds. A 400, 404 or 410 (or a
`null` body) agrees with an entity the copy does not hold. A match confirms
base recording, with the members the state still holds. A 400, 404 or 410, a
`null` body, or an object marked deleted (`deleted`, `isDeleted` or `is_deleted`
`true`, as EspoCRM answers a read of a deleted record) agrees with an entity the
copy does not hold; such a read drops a held entity it names and adopts none,
while an initial read keeps the record it shows held, as its item read answers it.
The item view records the flag its reads set as `deleted_flag`, a field it shows
even when only a query read showed it, which serving answers by: later reads of
a served-deleted entity show it marked deleted, and a served delete answers with
the flag as its recorded answer showed it. A match confirms
the writes that changed its entity or collection since it was last read, less
those whose entity a read since showed absent, which no longer count for the
collection; a mismatch contradicts them, or, when an unmodelled write came after them, is
@@ -855,11 +897,30 @@ checked only on the members they sent; a mismatch no write explains marks the re
`side-effect`. A plan is `served` when a read confirmed it and none
contradicted it; otherwise its `reason` is the first of its build reason
(`no-call`, `body-unusable`, `no-observation`, `unexplained-change`),
`contradicted` (with `contradicted_by`), `side-effect`, `unconfirmed` and `incomplete-entities` (an update or delete that answers with the entity, when no initial entity holds every field its recorded answer shows, counting for an update the fields its rules write, so it could answer only for entities the environment created). An otherwise served plan whose learned rules generate clock values is withheld with `unknown-clock` when no global clock start is established; its learned rules and confirming reads remain retained.
`contradicted` (with `contradicted_by`), `side-effect`, `unconfirmed` and `incomplete-entities` (an update or delete that answers with the entity, when no initial entity holds every field its recorded answer shows but those only later reads showed, counting for an update the fields its rules write, so it could answer only for entities the environment created). An otherwise served plan whose learned rules generate clock values is withheld with `unknown-clock` when no global clock start is established; its learned rules and confirming reads remain retained.
`checked_record_ordinals` lists the confirming reads, `insert` places created
entities (sorted by a field the collection's recorded order shows, else last),
and `write_clock` is the end of the last timed record, to the next second, when
all request completions are known; otherwise it is null.
all request completions are known; otherwise it is null. An
update that writes the field its type's creates are sorted by moves its entity
to where the new value sorts (its own `insert`); a bulk update moves each entity
it writes in turn, as replay applies them. When a read listing the whole
collection in its own order, not one its query sorts, fails after an update and the type's creates are not sorted by a field
that update writes, the view tries the order serving gives when each write since
a list in the collection's own order showed its entity moves it in turn by such a
field, against the others' values before their own writes, when
the entities no write has changed since a list in the collection's own order last
showed them already follow it, as serving moves only the entities a write
changes, and unless an earlier read listing the whole collection in its own
order showed a write that changed such a field where moving it by that order
would not have put it, a create was placed by a sort that excludes it, or a read listing the whole
collection in its own order, not one its query sorts, that placed a create did
not follow it; on a match, the type's creates
and updates take that order, as Grafana keeps folders by title though no earlier
read could tell that from creation order, or from id order where both ascend.
An update moves its entity by its creates' sort only when a list read showed a
create placed, in replay as in serving, so no read confirms an update by a sort
a create only guessed from the initial order.
Values in the state are recorded data, so the view refuses the same parents as
the manifest, including those inspected with `sensitive_values`, and a
+8 -8
View File
@@ -2695,11 +2695,11 @@ [email protected] | runtime | MIT
Package: https://www.npmjs.com/package/fast-string-width/v/3.0.2
Archive: https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz
Integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==
[email protected].8 | runtime | BSD-3-Clause
[email protected].9 | runtime | BSD-3-Clause
Lockfile path: node_modules/fast-uri
Package: https://www.npmjs.com/package/fast-uri/v/3.1.8
Archive: https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz
Integrity: sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==
Package: https://www.npmjs.com/package/fast-uri/v/3.1.9
Archive: https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.9.tgz
Integrity: sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==
[email protected] | runtime | MIT
Lockfile path: node_modules/fast-wrap-ansi
Package: https://www.npmjs.com/package/fast-wrap-ansi/v/0.2.2
@@ -3775,11 +3775,11 @@ [email protected] | development | MIT
Package: https://www.npmjs.com/package/pure-rand/v/8.4.2
Archive: https://registry.npmjs.org/pure-rand/-/pure-rand-8.4.2.tgz
Integrity: sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng==
[email protected].0 | development | BSD-3-Clause
[email protected].1 | development | BSD-3-Clause
Lockfile path: node_modules/qs
Package: https://www.npmjs.com/package/qs/v/6.16.0
Archive: https://registry.npmjs.org/qs/-/qs-6.16.0.tgz
Integrity: sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==
Package: https://www.npmjs.com/package/qs/v/6.16.1
Archive: https://registry.npmjs.org/qs/-/qs-6.16.1.tgz
Integrity: sha512-5Px6J7rfHBAGUhNQ3es4YzcOdBBw0SDcVtThJml4RdnKw4tKs2epA0kPZ+K8X+WiHktT93RHVcQ/3ym5aTCgtg==
[email protected] | runtime | MIT
Lockfile path: node_modules/quick-format-unescaped
Package: https://www.npmjs.com/package/quick-format-unescaped/v/4.0.4
+4 -4
View File
@@ -1,7 +1,7 @@
REA third-party npm runtime copyright, license and NOTICE texts
Collected from integrity-verified locked npm archives. Release-specific supplements and upstream notice gaps are identified in their entries.
External engines are separately obtained; see THIRD_PARTY_NOTICES.md.
Runtime dependency fingerprint (SHA-256): 31bd2b9c5690c9d963d7dcf25da2fec9bc16a3d4724f661cf92489dd106fdbe1
Runtime dependency fingerprint (SHA-256): 09f8570cf168ddd3ad7c877dbcdd1ef5013de3af884a37fd27e6da3b99b49664
===== @babel/[email protected] =====
Declared license: MIT
@@ -3926,10 +3926,10 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
===== [email protected].8 =====
===== [email protected].9 =====
Declared license: BSD-3-Clause
Archive: https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz
Integrity: sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==
Archive: https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.9.tgz
Integrity: sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==
Scope: npm runtime dependency, including optional platform packages.
--- package/LICENSE ---
+6 -6
View File
@@ -7776,9 +7776,9 @@
}
},
"node_modules/fast-uri": {
"version": "3.1.8",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz",
"integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==",
"version": "3.1.9",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.9.tgz",
"integrity": "sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==",
"funding": [
{
"type": "github",
@@ -10729,9 +10729,9 @@
"license": "MIT"
},
"node_modules/qs": {
"version": "6.16.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz",
"integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==",
"version": "6.16.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.16.1.tgz",
"integrity": "sha512-5Px6J7rfHBAGUhNQ3es4YzcOdBBw0SDcVtThJml4RdnKw4tKs2epA0kPZ+K8X+WiHktT93RHVcQ/3ym5aTCgtg==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
+8 -2
View File
@@ -99,7 +99,13 @@
"bridge/pwndbg/core_context.py",
"third_party/pwndbg/README.md",
"third_party/pwndbg/LICENSE.md",
"licenses"
"licenses",
"assets/rea-plugin.png",
".claude-plugin/plugin.json",
".codex-plugin/plugin.json",
".codex-plugin/mcp.json",
"scripts/plugin-mcp-launcher.mjs",
"npm-shrinkwrap.json"
],
"scripts": {
"release:notes": "node scripts/release-notes.mjs",
@@ -381,7 +387,7 @@
},
"overrides": {
"typed-rest-client": {
"qs": "6.16.0"
"qs": "6.16.1"
},
"vitepress": {
"vite": "6.4.4"
+30
View File
@@ -31,6 +31,36 @@
"type": "json",
"path": "server.json",
"jsonpath": "$.packages[0].version"
},
{
"type": "json",
"path": ".claude-plugin/plugin.json",
"jsonpath": "$.version"
},
{
"type": "json",
"path": ".claude-plugin/plugin.json",
"jsonpath": "$.mcpServers.rea.args[1]"
},
{
"type": "json",
"path": ".codex-plugin/plugin.json",
"jsonpath": "$.version"
},
{
"type": "json",
"path": ".codex-plugin/mcp.json",
"jsonpath": "$.mcpServers.rea.args[1]"
},
{
"type": "json",
"path": ".claude-plugin/marketplace.json",
"jsonpath": "$.plugins[0].source.version"
},
{
"type": "json",
"path": ".agents/plugins/marketplace.json",
"jsonpath": "$.plugins[0].source.version"
}
]
}
+10 -1
View File
@@ -1,7 +1,12 @@
import { execFileSync } from "node:child_process";
import { appendFileSync } from "node:fs";
import { matchesGlob } from "node:path";
import { CI_FLAGS as flags, MANUAL_VERIFIERS, SCOPES } from "./scopes.mjs";
import {
CI_FLAGS as flags,
MANUAL_VERIFIERS,
SCOPES,
WINDOWS_EXECUTABLE_SELECTION_INPUTS,
} from "./scopes.mjs";
import { planTestSelection } from "./test-selection.mjs";
// Test files select the host/runtime lanes that execute their focused suites.
@@ -206,6 +211,10 @@ if (tier === "reused") {
.split("\0")
.filter(Boolean);
for (const file of files) {
selected.package ||= matchesAny(
file,
WINDOWS_EXECUTABLE_SELECTION_INPUTS,
);
if (matchesAny(file, SCOPES.reverse_tools)) selected.reverse_tools = true;
if (
/^(README(?:_[^/]+)?\.md|AGENTS\.md|CONTRIBUTING\.md|CHANGELOG\.md|SECURITY\.md|native\/windows\/README\.md|\.github\/pull_request_template\.md)$/.test(
+18
View File
@@ -1,3 +1,13 @@
// These boundaries require native Windows launch verification even where
// broad process ownership would otherwise suppress host fanout on the fast tier.
export const WINDOWS_EXECUTABLE_SELECTION_INPUTS = [
"src/process/WindowsExecutable*.ts",
"src/adb/AdbConfiguration.ts",
"src/apktool/ApktoolConfiguration.ts",
"src/application/UpdateCommandRunner.ts",
"tests/boundary/process/windowsExecutableSelection.test.ts",
];
// Ownership of the native-host, real-provider and website verification lanes.
// Shared build inputs are classified once in plan.mjs rather than repeated in
// every workflow.
@@ -67,6 +77,13 @@ export const SCOPES = {
],
// Apple artifact CLI/MCP lane on macOS, including its focused regressions.
apple: [
"src/hopper/BridgeLauncher.ts",
"src/domain/hopperErrors.ts",
"tests/boundary/providers/hopper/hopperMacLaunchArguments.test.ts",
"tests/acceptance/providers/hopperStartupRecovery.test.ts",
"tests/boundary/providers/hopper/hopperClient.helperFailure.test.ts",
"tests/boundary/providers/hopper/bridgeBootstrapCleanup.test.ts",
"tests/boundary/providers/hopper/bridgeTargetLease.integration.test.ts",
"src/artifacts/NativeDmg*.ts",
"src/native/Apple*",
"src/native/NativeMacOS*",
@@ -101,6 +118,7 @@ export const SCOPES = {
],
// Four-host installed-package matrix and the Windows curated lane.
package: [
...WINDOWS_EXECUTABLE_SELECTION_INPUTS,
// Both host compilers must be exercised when their installation, options,
// or entrypoints change; Linux's accelerated checker cannot stand in for Go.
"scripts/run-typescript.*",
+2 -1
View File
@@ -91,7 +91,8 @@ try {
typed
? {
...process.env,
GOMAXPROCS: process.env.GOMAXPROCS ?? "2",
// One worker reuses its checker caches across the full file queue.
GOMAXPROCS: process.env.GOMAXPROCS ?? "1",
GOMEMLIMIT: process.env.GOMEMLIMIT ?? "1024MiB",
}
: process.env,
@@ -66,6 +66,7 @@ class Generator:
first.request.path = "http://user:[email protected]/a?token=ordinary#fragment"
first.response = http.Response.make(200, b"\x00\xfeanswer", [(b"Set-Cookie", b"session=cookie-secret"), (b"Location", b"https://redirect-user:[email protected]/b?token=ordinary")])
first.metadata = {"big_integer": 9007199254740993, "nonfinite": float("inf"), "isLosslessNumber": True, "value": "ordinary-extension", "response": {"headers": [["Authorization", "ordinary-extension-credential-name"]]}}
first.metadata["numeric_spellings"] = {"small": 1e-7, "large": 1e20, "ordinary": 42}
first.websocket = WebSocketData(messages=[WebSocketMessage(2, True, b"\x00\xfdmessage", 123.5)])
first.backup()
first.comment = "caller-selected-fixture"
+3 -2
View File
@@ -1,7 +1,8 @@
# Exercised runtime graph: CPython 3.14.7, Linux x86_64 guest.
# Base runtime graph: CPython 3.14.7, Linux x86_64 guest.
# The certifi trust-store refresh has not been rerun in that guest.
aioquic==1.2.0
attrs==26.1.0
certifi==2026.7.22
certifi==2026.10.19
cffi==2.1.1
cryptography==50.0.2
pycparser==3.11
+35
View File
@@ -0,0 +1,35 @@
interface PackageManifest {
name: string;
version: string;
devDependencies?: Record<string, string>;
dependencies?: Record<string, string>;
overrides?: Record<string, unknown>;
// npm manifests are extensible; preserve metadata outside this projection.
[key: string]: unknown;
}
interface LockedPackage {
version?: string;
dev?: boolean;
// Keep unmodified integrity, resolution, platform, and other lock-entry facts.
[key: string]: unknown;
}
interface PackageLock {
name: string;
version: string;
lockfileVersion: number;
packages: Record<string, LockedPackage>;
// Preserve other npm lock metadata rather than dropping unfamiliar fields.
[key: string]: unknown;
}
export function pluginPackageMetadata<Manifest extends PackageManifest>(
manifest: Manifest,
lock: PackageLock,
): {
packageJson: Omit<Manifest, "devDependencies" | "overrides"> & {
bundleDependencies: string[];
};
shrinkwrap: PackageLock;
};
+41
View File
@@ -0,0 +1,41 @@
/** Project the development manifest and lock into the shared runtime artifact. */
export function pluginPackageMetadata(manifest, lock) {
if (
lock.lockfileVersion !== 3 ||
lock.name !== manifest.name ||
lock.version !== manifest.version ||
lock.packages?.[""]?.version !== manifest.version
)
throw new Error("Plugin packaging requires the matching v3 package lock");
// Claude skips dependency installation when a published manifest has
// overrides. Preserve development security overrides in the source owner,
// and reject any future runtime override rather than silently discarding it.
for (const name of Object.keys(manifest.overrides ?? {})) {
const matches = Object.entries(lock.packages).filter(([path]) =>
path.endsWith(`node_modules/${name}`),
);
if (matches.length === 0 || matches.some(([, entry]) => entry.dev !== true))
throw new Error(`Cannot omit a runtime or unresolved override: ${name}`);
}
const packageJson = { ...manifest };
delete packageJson.devDependencies;
delete packageJson.overrides;
// Codex's npm source fetches the archive without installing dependencies.
// Keep optional Frida outside the bundle so ordinary npm installs can still
// run its host-specific installer. PTY binaries are transitive optionals of
// a runtime dependency and are included for every supported platform by the
// archive producer.
packageJson.bundleDependencies = Object.keys(manifest.dependencies ?? {});
const root = { ...lock.packages[""] };
delete root.devDependencies;
const shrinkwrap = {
...lock,
packages: Object.fromEntries(
Object.entries(lock.packages).filter(([, entry]) => entry.dev !== true),
),
};
shrinkwrap.packages[""] = root;
return { packageJson, shrinkwrap };
}
+59
View File
@@ -4,6 +4,37 @@ import { z } from "zod";
const shaSchema = z.string().regex(/^[a-f0-9]{40}$/u);
const versionSchema = z.object({ version: z.string() });
const manifestSchema = z.object({ ".": z.string() });
const PLUGIN_MANIFEST_PATHS = [
".claude-plugin/plugin.json",
".codex-plugin/plugin.json",
];
const inlinePluginManifestSchema = versionSchema.extend({
mcpServers: z.object({
rea: z.object({
command: z.literal("node"),
args: z.tuple([
z.literal("${CLAUDE_PLUGIN_ROOT}/scripts/plugin-mcp-launcher.mjs"),
z.string(),
]),
}),
}),
});
const codexPluginManifestSchema = versionSchema.extend({
mcpServers: z.literal("./.codex-plugin/mcp.json"),
});
const codexServerConfigSchema = z.object({
mcpServers: z.object({
rea: z.object({
command: z.literal("node"),
args: z.tuple([
z.literal("./scripts/plugin-mcp-launcher.mjs"),
z.string(),
]),
cwd: z.literal("."),
startup_timeout_sec: z.number().int().positive(),
}),
}),
});
const sectionsSchema = z.object({
versioning: z.string().optional(),
"changelog-sections": z.array(
@@ -189,6 +220,26 @@ async function validateCandidate(git, sha, version, commits) {
["server.json", server.version],
["server.json rea-agents package", npmPackage.version],
);
// A stale manifest keeps the previous plugin cache key or launches the
// previous server, so both are version authority like server.json.
const launches = [];
for (const path of PLUGIN_MANIFEST_PATHS) {
const manifest =
path === ".codex-plugin/plugin.json"
? await readJson(git, sha, path, codexPluginManifestSchema)
: await readJson(git, sha, path, inlinePluginManifestSchema);
versions.push([path, manifest.version]);
const server =
path === ".codex-plugin/plugin.json"
? await readJson(
git,
sha,
".codex-plugin/mcp.json",
codexServerConfigSchema,
)
: manifest;
launches.push([path, server.mcpServers.rea.args[1]]);
}
// Catalogs are generated and checked from this candidate by the release job;
// they are not version authority stored in the immutable source tree.
const generated = await git([
@@ -210,6 +261,14 @@ async function validateCandidate(git, sha, version, commits) {
);
}
}
// The launcher checks the scalar pin against the runtime in its own package.
for (const [location, launched] of launches) {
if (launched !== version) {
throw new Error(
`${location}@${sha} launches version ${launched ?? "no version"}; release branch requires ${version}.`,
);
}
}
const notes = releaseNotes(
await git(["show", `${sha}:CHANGELOG.md`]),
version,
+64 -1
View File
@@ -369,6 +369,60 @@ export function verifyReleaseNotes(raw, text, body) {
};
}
const VERSION_ONLY_PLUGIN_METADATA = new Set([
".claude-plugin/plugin.json",
".codex-plugin/plugin.json",
".codex-plugin/mcp.json",
".claude-plugin/marketplace.json",
".agents/plugins/marketplace.json",
]);
/** Serialize plugin metadata with only its release-owned version fields blanked. */
function pluginMetadataWithoutVersions(path, text) {
const manifest = JSON.parse(text);
if (manifest === null || typeof manifest !== "object") return text;
if (path.endsWith("marketplace.json")) {
if (Array.isArray(manifest.plugins))
for (const plugin of manifest.plugins)
if (
plugin?.name === "rea" &&
plugin?.source?.source === "npm" &&
plugin.source.package === "rea-agents" &&
typeof plugin.source.version === "string"
)
plugin.source.version = "";
return JSON.stringify(manifest);
}
if ("version" in manifest) manifest.version = "";
const launch = manifest.mcpServers?.rea?.args;
// Only the scalar pinned version is release-owned; the launcher owns the
// fixed package name and the executable/path remain product behavior.
if (Array.isArray(launch) && typeof launch[1] === "string") launch[1] = "";
return JSON.stringify(manifest);
}
/**
* Plugin metadata also carries behavior, so a release may change only
* its version, pinned server launch, and exact REA marketplace version. Any other difference, an unreadable
* side, or a manifest added by the release counts as a product change.
*/
async function changesOnlyPluginVersions(run, path, from, to) {
try {
return (
pluginMetadataWithoutVersions(
path,
await run("git", ["show", `${from}:${path}`]),
) ===
pluginMetadataWithoutVersions(
path,
await run("git", ["show", `${to}:${path}`]),
)
);
} catch {
return false;
}
}
/** Reject stale inventories and product changes made after the editorial target. */
export async function verifyInventoryHistory(run, raw, source) {
const inventory = releaseInventorySchema.parse(raw);
@@ -437,7 +491,16 @@ export async function verifyInventoryHistory(run, raw, source) {
)
.split("\0")
.filter(Boolean);
const product = paths.filter((path) => !allowed.has(path));
const product = [];
for (const path of paths) {
if (allowed.has(path)) continue;
if (
VERSION_ONLY_PLUGIN_METADATA.has(path) &&
(await changesOnlyPluginVersions(run, path, inventory.target, resolved))
)
continue;
product.push(path);
}
if (product.length)
throw new Error(
`Product changed after notes target; regenerate inventory: ${product.join(", ")}`,
+8 -1
View File
@@ -36,9 +36,16 @@ export const verifyCompleteToolCatalog = async (client, options) => {
);
}
}
const sessionDefinition = listed.tools.find(
({ name }) => name === "binary_session",
);
if (sessionDefinition === undefined)
throw new Error("packaged MCP omitted the binary_session definition");
const status = await client.callTool(
{ name: "binary_session", arguments: {} },
options,
// The SDK otherwise compiles every listed output schema on the first call.
// Keep its default validation for the actual named tool being exercised.
{ ...options, toolDefinition: sessionDefinition },
);
const expanded = requireMcpLifecycleResult(status, "binary_session");
const availability = expanded.tool_availability;
@@ -0,0 +1,8 @@
import { writeFileSync } from "node:fs";
// Keep argument boundaries intact so verifier consumers never parse shell display text.
writeFileSync(
process.env.REA_NPX_LOG,
JSON.stringify(process.argv.slice(2)) + "\n",
);
process.stdout.write("│ ✓ Cursor: isolated │\n");
+48
View File
@@ -0,0 +1,48 @@
#!/usr/bin/env node
import { access, readFile } from "node:fs/promises";
import { fileURLToPath } from "node:url";
// Release Please owns the scalar pin in each client declaration. The runtime
// and skill come from the same installed artifact, without another npx child.
const [version, ...extra] = process.argv.slice(2);
try {
if (
extra.length !== 0 ||
!/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/u.test(version ?? "")
)
throw new Error("requires one exact rea-agents@VERSION pin");
const bundledPackage = JSON.parse(
await readFile(new URL("../package.json", import.meta.url), "utf8"),
);
if (
bundledPackage.name !== "rea-agents" ||
bundledPackage.version !== version
)
throw new Error(
"version pin does not match its installed rea-agents package; reinstall the plugin",
);
await access(new URL("../dist/mcp/main.js", import.meta.url)).catch(
(cause) => {
if (cause?.code !== "ENOENT") throw cause;
throw new Error(
"runtime is missing; reinstall the plugin, or run npm ci && npm run build:cached in a source checkout",
{ cause },
);
},
);
} catch (cause) {
process.stderr.write(`REA plugin ${cause.message}.\n`);
process.exitCode = 1;
}
if (process.exitCode !== 1) {
process.argv = [
process.execPath,
fileURLToPath(new URL("./rea.mjs", import.meta.url)),
"mcp",
];
await import("./rea.mjs");
}
+28 -9
View File
@@ -68,15 +68,34 @@ if (!(await compiledRuntimeExists(runtimeFiles))) {
await runMcpHttpEntrypoint(args.slice(1));
} else if (isMcpDoctorMode) {
guardClosedStdout();
const { runProductionMcpDoctorCli } =
await import("../dist/cli-runtime/mcpDoctor.js");
const result = await runProductionMcpDoctorCli(doctorArguments, {
dispatcherPath: fileURLToPath(import.meta.url),
packageRoot,
stdoutIsTerminal: process.stdout.isTTY === true,
});
writeStdout(result.output);
process.exitCode = result.exitCode;
// Doctor runs outside Incur, so handle its global timing flag here. Keep a
// token consumed by --format as a value, even when it looks like a flag.
const outputValues = new Set(
builtinArguments
.splitIncurBuiltinArguments(doctorArguments)
.outputValues.map(({ index }) => index),
);
const doctorCliArguments = doctorArguments.filter(
(argument, index) => argument !== "--timings" || outputValues.has(index),
);
const timingRequested = doctorCliArguments.length !== doctorArguments.length;
const started = performance.now();
try {
const { runProductionMcpDoctorCli } =
await import("../dist/cli-runtime/mcpDoctor.js");
const result = await runProductionMcpDoctorCli(doctorCliArguments, {
dispatcherPath: fileURLToPath(import.meta.url),
packageRoot,
stdoutIsTerminal: process.stdout.isTTY === true,
});
writeStdout(result.output);
process.exitCode = result.exitCode;
} finally {
if (timingRequested)
process.stderr.write(
`${JSON.stringify({ rea_timings: { version: 1, command_ms: Math.round(performance.now() - started) } })}\n`,
);
}
} else {
const {
cliConfigurationError,
+6 -2
View File
@@ -1,6 +1,6 @@
import { chmod, mkdir, symlink, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { pathToFileURL } from "node:url";
import { fileURLToPath, pathToFileURL } from "node:url";
import { supportedClients } from "../dist/application/SupportedClients.js";
/** Create an isolated prefix, fake PATH, client configs, and environment. */
@@ -41,7 +41,7 @@ export async function verifyPackageEnvironment({
await writeFile(swVers, "#!/bin/sh\necho 14.5\n");
await writeFile(
npx,
'#!/bin/sh\nprintf "%s\\n" "$*" > "$REA_NPX_LOG"\necho "│ ✓ Cursor: isolated │"\n',
'#!/bin/sh\nexec "$REA_VERIFY_NODE" "$REA_VERIFY_NPX_SCRIPT" "$@"\n',
);
await writeFile(hopper, "#!/bin/sh\nexit 0\n");
await Promise.all([
@@ -98,6 +98,10 @@ export async function verifyPackageEnvironment({
REA_ANALYSIS_PROVIDER: "auto",
...packageHopperEnvironment(root),
REA_NPX_LOG: npxLog,
REA_VERIFY_NODE: process.execPath,
REA_VERIFY_NPX_SCRIPT: fileURLToPath(
new URL("./lib/verify-package-runner-fixture.mjs", import.meta.url),
),
REA_REFERENCE_SECRET_PATTERNS_JSON: JSON.stringify([".env"]),
};
return {
+11 -6
View File
@@ -5,11 +5,12 @@ import {
requireMcpToolError,
} from "./lib/mcp-verifier-results.mjs";
import assert from "node:assert/strict";
import { join } from "node:path";
import { readFile, rm } from "node:fs/promises";
import { dirname, join } from "node:path";
import { readFile, realpath, rm } from "node:fs/promises";
import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import { PRODUCT_IDENTITY } from "../dist/identity.js";
import { resolveMcpStartupBudgets } from "../dist/config/mcpStartupBudgets.js";
import { parseEvidence } from "../dist/domain/evidence.js";
import * as prompts from "./verify-package-prompts.mjs";
@@ -239,7 +240,7 @@ const verifyMcpInlineArtifactEvidence = async (
}
};
/** Connect to the packaged MCP server and verify catalog and Evidence composition. */
/** Check CLI diagnostics, then verify catalog and Evidence through the installed plugin. */
export async function verifyPackageMcp({
commands,
cli,
@@ -249,7 +250,7 @@ export async function verifyPackageMcp({
}) {
const diagnosed = JSON.parse(
(
await commands.exec(cli, ["mcp", "doctor", "--json"], {
await commands.exec(process.execPath, [cli, "mcp", "doctor", "--json"], {
env: environment,
timeout: resolveMcpStartupBudgets(environment).doctor.milliseconds,
})
@@ -261,9 +262,13 @@ export async function verifyPackageMcp({
diagnosed.inventory?.tools?.expected
)
throw new Error("packaged production MCP doctor failed");
const scriptsDirectory = dirname(await realpath(cli));
const transport = new StdioClientTransport({
command: cli,
args: ["mcp"],
command: process.execPath,
args: [
join(scriptsDirectory, "plugin-mcp-launcher.mjs"),
PRODUCT_IDENTITY.packageVersion,
],
env: {
...environment,
},
+209 -10
View File
@@ -1,11 +1,12 @@
import { importVerifierSource } from "./lib/verifier-source-loader.mjs";
import { mkdir } from "node:fs/promises";
import { join } from "node:path";
import { access, copyFile, mkdir, readFile, writeFile } from "node:fs/promises";
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
import { FULL_CATALOG_IDENTITY } from "../dist/catalogIdentity.js";
import { parseTarListing } from "./lib/verify-package-listing.mjs";
import { verifyPackedBridge } from "./verify-packed-bridge.mjs";
import { pluginPackageMetadata } from "./lib/plugin-package.mjs";
const { parseSkillManifestMetadata } = await importVerifierSource(
new URL("../src/domain/skillManifest.ts", import.meta.url).href,
@@ -16,13 +17,108 @@ const { parseSkillManifestMetadata } = await importVerifierSource(
export async function verifyPackagePack({ root, workspace, commands }) {
const packDirectory = join(workspace, "pack");
await mkdir(packDirectory);
const stagingDirectory = join(workspace, "plugin-package");
await mkdir(stagingDirectory);
const [{ files }] = JSON.parse(
(await commands.execNpm(["pack", "--dry-run", "--json"], { cwd: root }))
.stdout,
);
// Reuse npm's file selection, then project metadata in an owned directory.
// Source files and the development lock are never rewritten by packaging.
for (const { path } of files) {
const source = resolve(root, path);
const contained = relative(root, source);
if (
contained === ".." ||
contained.startsWith(`..${sep}`) ||
isAbsolute(contained)
)
throw new Error(`npm selected a path outside the package: ${path}`);
const destination = join(stagingDirectory, contained);
await mkdir(dirname(destination), { recursive: true });
await copyFile(source, destination);
}
const { packageJson, shrinkwrap } = pluginPackageMetadata(
JSON.parse(await readFile(join(root, "package.json"), "utf8")),
JSON.parse(await readFile(join(root, "package-lock.json"), "utf8")),
);
await writeFile(
join(stagingDirectory, "package.json"),
`${JSON.stringify(packageJson, null, 2)}\n`,
);
await writeFile(
join(stagingDirectory, "npm-shrinkwrap.json"),
`${JSON.stringify(shrinkwrap, null, 2)}\n`,
);
// Materialize exactly the locked runtime tree without executing installers.
await commands.execNpm(
["ci", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund"],
{ cwd: stagingDirectory },
);
if (packageJson.dependencies?.["@lydell/node-pty"] !== undefined) {
// npm ci filters foreign-platform optional packages. Include the exact locked
// PTY archives as well, without re-resolving the installed dependency tree.
const ptyManifest = JSON.parse(
await readFile(
join(stagingDirectory, "node_modules/@lydell/node-pty/package.json"),
"utf8",
),
);
const platformDirectory = join(workspace, "plugin-platform-packages");
await mkdir(platformDirectory);
for (const dependency of Object.keys(ptyManifest.optionalDependencies)) {
const destination = join(stagingDirectory, "node_modules", dependency);
if (
await access(destination).then(
() => true,
(cause) => {
if (cause.code !== "ENOENT") throw cause;
return false;
},
)
)
continue;
const locked = shrinkwrap.packages[`node_modules/${dependency}`];
if (locked?.version === undefined || locked.integrity === undefined)
throw new Error(
`Plugin platform dependency is not locked: ${dependency}`,
);
const [archive] = JSON.parse(
(
await commands.execNpm(
[
"pack",
"--ignore-scripts",
"--json",
"--pack-destination",
platformDirectory,
`${dependency}@${locked.version}`,
],
{ cwd: platformDirectory },
)
).stdout,
);
if (archive.integrity !== locked.integrity)
throw new Error(
`Plugin platform archive differs from the lock: ${dependency}`,
);
await mkdir(destination, { recursive: true });
await commands.exec("tar", [
"-xzf",
join(platformDirectory, archive.filename),
"-C",
destination,
"--strip-components=1",
]);
}
}
const tarball = join(
packDirectory,
(
await commands.execNpm(
["pack", "--silent", "--pack-destination", packDirectory],
{
cwd: root,
cwd: stagingDirectory,
},
)
).stdout.trim(),
@@ -34,6 +130,54 @@ export async function verifyPackagePack({ root, workspace, commands }) {
(await commands.exec("tar", ["-xOf", tarball, "package/package.json"]))
.stdout,
);
if (packedManifest.mcpName !== "io.github.morluto/rea")
throw new Error("package did not retain its MCP Registry ownership marker");
const packedLock = JSON.parse(
(
await commands.exec("tar", [
"-xOf",
tarball,
"package/npm-shrinkwrap.json",
])
).stdout,
);
if (
packedManifest.overrides !== undefined ||
packedManifest.devDependencies !== undefined ||
![2, 3].includes(packedLock.lockfileVersion) ||
packedLock.version !== packedManifest.version ||
packedLock.packages?.[""].version !== packedManifest.version ||
JSON.stringify(packedLock.packages?.[""].dependencies) !==
JSON.stringify(packedManifest.dependencies) ||
JSON.stringify(packedLock.packages?.[""].optionalDependencies) !==
JSON.stringify(packedManifest.optionalDependencies)
)
throw new Error(
"package is not installable under the Claude plugin dependency policy",
);
for (const dependency of Object.keys(packedManifest.dependencies))
if (
!packedFiles.includes(`package/node_modules/${dependency}/package.json`)
)
throw new Error(
`Plugin runtime dependency is not bundled: ${dependency}`,
);
const pty = JSON.parse(
(
await commands.exec("tar", [
"-xOf",
tarball,
"package/node_modules/@lydell/node-pty/package.json",
])
).stdout,
);
for (const dependency of Object.keys(pty.optionalDependencies))
if (
!packedFiles.includes(`package/node_modules/${dependency}/package.json`)
)
throw new Error(
`Plugin PTY platform dependency is missing: ${dependency}`,
);
if (
packedManifest.scripts?.postinstall !== undefined ||
packedManifest.dependencies?.["node-pty"] !== undefined ||
@@ -45,14 +189,16 @@ export async function verifyPackagePack({ root, workspace, commands }) {
packedManifest.bin?.["rea-agents"] !== "scripts/rea.mjs"
)
throw new Error("package did not expose both rea command entry points");
if (packedManifest.mcpName !== "io.github.morluto/rea")
throw new Error("package did not retain its MCP Registry ownership marker");
if (!packedFiles.includes("package/scripts/lib/cli-process-environment.mjs"))
throw new Error("package omitted the CLI process environment owner");
if (packedFiles.some((path) => path.startsWith("package/.agents/")))
throw new Error(
"package included authored skill sources instead of only the generated bundle",
);
if (packedFiles.some((path) => path.startsWith("package/src/")))
throw new Error(
"Packaged runtime must not masquerade as a source checkout",
);
if (packedFiles.some((path) => path.startsWith("package/.cache/")))
throw new Error("package included development cache or test metadata");
const skill = (
@@ -71,13 +217,66 @@ export async function verifyPackagePack({ root, workspace, commands }) {
throw new Error(
"packaged skill metadata did not match the catalog shipped in the package",
);
if (
packedFiles.some(
(path) => path.includes("__pycache__") || path.endsWith(".pyc"),
for (const path of [
".claude-plugin/plugin.json",
".codex-plugin/plugin.json",
]) {
const manifest = JSON.parse(
(await commands.exec("tar", ["-xOf", tarball, `package/${path}`])).stdout,
);
const serverConfig = path.startsWith(".codex")
? JSON.parse(
(
await commands.exec("tar", [
"-xOf",
tarball,
"package/.codex-plugin/mcp.json",
])
).stdout,
)
: manifest;
const expectedArgs = [
`${path.startsWith(".claude") ? "${CLAUDE_PLUGIN_ROOT}/" : "./"}scripts/plugin-mcp-launcher.mjs`,
packedManifest.version,
];
if (
manifest.version !== packedManifest.version ||
manifest.skills !== "./skills" ||
(path.startsWith(".codex")
? manifest.mcpServers !== "./.codex-plugin/mcp.json" ||
serverConfig.mcpServers?.rea?.cwd !== "." ||
serverConfig.mcpServers?.rea?.startup_timeout_sec !== 30
: false) ||
serverConfig.mcpServers?.rea?.command !== "node" ||
JSON.stringify(serverConfig.mcpServers?.rea?.args) !==
JSON.stringify(expectedArgs)
)
) {
throw new Error("package contained generated Python bytecode");
throw new Error(
`Packaged plugin ${path} does not match the exact runtime and canonical skill release`,
);
const icons = path.startsWith(".codex")
? [manifest.interface?.logo, manifest.interface?.composerIcon]
: [manifest.icon];
for (const icon of icons)
if (
icon !== "./assets/rea-plugin.png" ||
!packedFiles.includes(`package/${icon.slice(2)}`)
)
throw new Error(`Packaged plugin ${path} is missing its shared icon`);
}
if (packedFiles.includes("package/.mcp.json"))
throw new Error("Plugin metadata must not register a project MCP server");
if (!packedFiles.includes("package/.codex-plugin/mcp.json"))
throw new Error("Packaged Codex MCP server declaration is missing");
if (!packedFiles.includes("package/scripts/plugin-mcp-launcher.mjs"))
throw new Error("Packaged plugin launcher is missing");
if (
skill.includes(`${packedManifest.name}@latest`) ||
!skill.includes(`${packedManifest.name}@${packedManifest.version}`)
)
throw new Error(
"Packaged plugin skill recovery is not pinned to the shipped runtime",
);
await verifyPackedBridge({ tarball, packedFiles, commands });
return { tarball };
}
Loaded 100 of 497 files, more files were not shown because too many files have changed in this diff. Show more