Skip to content

Release v5.11.0 — security hardening, coverage closure, CI matrix on macOS#8

Merged
edtadros merged 92 commits into
masterfrom
feat/network-mcp
May 26, 2026
Merged

Release v5.11.0 — security hardening, coverage closure, CI matrix on macOS#8
edtadros merged 92 commits into
masterfrom
feat/network-mcp

Conversation

@edtadros
Copy link
Copy Markdown
Contributor

Bundles 84 commits since v5.10.0. Full curated changelog in CHANGELOG.md (## [5.11.0] — 2026-05-26).

Highlights

Security hardening

  • Path traversal in gnosys export closed (slugify category + assertWithin resolve+prefix check at every write site; A.5)
  • Shell injection eliminated — execSync(\cp -a "${path}"`)` → argv arrays (A.8)
  • .env and gnosys.db (+ WAL sidecars) created mode 0600, parent dirs 0700 (A.11)
  • HTTP MCP transport: bearer-token enforcement off-loopback, CORS Origin allowlist, bounded request bodies, idle-session reaper, per-session McpServer isolation (14.1–14.8)
  • SSRF: safeFetch/isSafeUrl block loopback, RFC1918, link-local, IPv6, integer-encoded IPs, with per-hop redirect re-check (A.7)
  • Prompt-injection-resistant ask synthesis (rule 8 in synthesize.md); no MCP exfiltration primitive (A.9)

Coverage closure — every C.1 target file ≥80% lines:

  • ingest.ts 17% → 100% · dream.ts 29% → 95% · db.ts 81% → 88% · remote.ts 74% → 80% · mcpHttp.ts 89% (already there)
  • Suite grew 1217 → 1375 tests (+158 across the review)

Tooling

  • Biome adopted as the linter (useImportType enforced; pragmatic ruleset; lint clean)
  • CI matrix now Node 18/20/22/24 × Linux + macOS (8 jobs; coverage gated to ubuntu-Node-24)
  • prebuild clean step (orphan-free dist/); knip wired (no dead exports); jszip declared
  • Structured logger (src/lib/log.ts with GNOSYS_LOG_FORMAT=json / GNOSYS_LOG_FILE)
  • Sourcemap-trimmed publish (tsconfig.publish.json + prepublishOnly): tarball 7.4 MB → 1.9 MB unpacked

Docs

  • Generated docs/mcp-tools.md + docs/cli.md (source-of-truth from src/index.ts/src/cli.ts)
  • docs/threat-model.md, docs/adr/ (12 ADRs backfilled), docs/source-of-truth.md, docs/coverage-baseline.md
  • SECURITY.md "Update integrity" section; CHANGELOG Historical-versions note

Metadata

  • repository.url canonicalized for npm provenance
  • Keywords expanded (model-context-protocol, agent-memory)
  • README documents both bins (gnosys + gnosys-mcp) and the optional native deps

No breaking changes. SemVer minor bump.

edtadros and others added 30 commits May 24, 2026 08:13
…(v5.12 Phase A foundation)

tool/prompt/resource registrations are now collected as typed thunks and replayed onto a server via registerCapabilities(s) — enabling a fresh McpServer per HTTP session (handlers reference shared module-global state, so no per-session state needed). stdio replays onto the singleton before connect; behavior-identical. Verified: tsc clean, full suite 1118/1118, live stdio smoke lists 51 tools incl gnosys_add.
…(v5.12 Phase A + C)

New mcpHttp.ts: Node http server hosting StreamableHTTPServerTransport with stateful per-session McpServers (built via registerCapabilities), /health probe, and a bearer-token auth gate (Phase C). serve --transport http|--host|--port|--token; main() branches on GNOSYS_TRANSPORT; default binds 127.0.0.1 (use a tailnet addr to share). stdio stays the zero-config default. 8 tests (incl auth + concurrent sessions); full suite 1126/1126; live 2-client smoke verified.

Phase C (auth + binding) delivered alongside A. Roots-notification auto-discovery is stdio-only for now; HTTP clients pass projectRoot per call (the gnosys pattern).
Adapt the existing Dockerfile/compose to run 'serve --transport http' on :7777: GNOSYS_HOME=/data on a host-local named volume (never SMB), non-root user, /health HEALTHCHECK, EXPOSE 7777, GNOSYS_SERVE_TOKEN for bearer auth. Add docs/network-mcp.md (Mac launchd vs Docker, client config, security, backup). NOTE: image not build-tested locally — Docker daemon was down; compose config validated, directives verified.
…ocal one (v5.12 Phase E)

centralize.ts uses SQLite's online backup API to write a consistent gnosys.db into a target dir (handles WAL, safe while in use). 'gnosys centralize --to <dir> [--force]' for seeding a Docker volume / new host. 4 tests.
…5.12 Phase B)

mcpClientConfig.ts writes the URL-based MCP entry (with optional bearer header) into Cursor (.cursor/mcp.json) or Claude Desktop config, merging with existing servers. 'gnosys connect --url <url> [--token] [--ide] [--dir] [--print]'. Additive — does not touch the existing local-stdio setup flow. 5 tests.
Route the error paths of 10 central-DB-reading tools (gnosys_reinforce,
gnosys_stale, gnosys_lens, gnosys_timeline, gnosys_stats, gnosys_graph,
gnosys_dream, gnosys_export, gnosys_stores, gnosys_recall) plus the
gnosys_read legacy file-read path through the existing formatMcpError
helper. Previously these tools relied on the raw SDK error wrapper, so a
corrupted central DB surfaced "database disk image is malformed" instead
of the actionable corruptionRecoveryInstructions() — and gnosys_read
could leak an absolute filesystem path on read failure.

Error envelope shape is unchanged ({ content: [{type:"text"}], isError:
true }); only message normalization is added. No tool names, schemas, or
success-path output changed.

Review task 1.1 (review_passed). tsc clean; error tests green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The shipped README previously documented zero tools by name (only a
generic "50+ memory tools" claim), leaving 19 of 51 registered tools
undiscoverable from the npm page. Adds a "## MCP Tool Reference" table
covering all 51 tools, each with the first sentence of its registration
description. Registered<->documented diff is now clean in both
directions (0 missing, 0 phantom).

Review task 1.3 (review_passed). Parity loop emits no MISSING lines.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
main() was invoked unguarded at module top level, so importing the entry
module booted the whole server — the reason the 51-tool surface had zero
schema tests. Guard the call with an ESM script check
(fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) so the
module is importable without side effects; binary behavior is unchanged.

Add src/test/mcp-fuzz.test.ts: connects an in-memory MCP client to a
server built from registerCapabilities and asserts every tool with
required fields rejects {} (missing required) and wrong-typed input.
Runtime rejection itself is SDK-guaranteed (safeParseAsync ->
McpError InvalidParams); this locks it under test.

Oversize-string cases intentionally omitted: content fields
(gnosys_add, ingest, bootstrap) use unbounded z.string() by design.

Review task 1.4 (review_passed). tsc + build clean; npm test green
(1136 tests); importing dist/index.js no longer boots the server.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add src/test/mcp-http-replay.test.ts: starts the HTTP transport with the
real registerCapabilities registry, opens two concurrent client sessions,
and asserts both list the identical full tool surface (>=51 tools,
including gnosys_discover/recall/add/ingest_file). The existing
v512-mcpHttp.test.ts only exercised a one-tool stub server and a session
count, so the replayable-registration invariant (_registrations ->
registerCapabilities per session) was untested.

Review task 1.6 (review_passed). tsc + build clean; test green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
history, semantic-search, lens, timeline, links, graph, and ask lacked
a --json mode, so they could not be consumed programmatically. Each now
declares --json and emits structured JSON to stdout via the existing
outputResult() helper; human output stays the default and the upgrade
notice stays on stderr (no banner contamination).

JSON shapes: history {memoryPath,entries|diff}, lens {count,items},
timeline {period,count,entries}, links {memoryPath,outgoing,backlinks},
graph {totalLinks,orphanedLinks,nodes}, semantic-search {query,count,
results}, ask {question,answer,sources,deepQueryUsed} (streaming off).

Review task 2.4 (review_passed). tsc + build clean; cli-json and
cli-parity tests green; npm test 1137 passed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
search.db (persistent), the centralize backup source, and the two
readonly opens (legacy-store check in cli.ts, embeddings copy in
migrate.ts) opened without a busy_timeout, so a concurrent writer would
raise SQLITE_BUSY immediately instead of waiting. Add
busy_timeout = 5000 to each. Journal mode is left unchanged — search.db
intentionally avoids WAL for sandbox/network-FS portability — and the
:memory: opens are untouched. The central gnosys.db already sets WAL +
busy_timeout=10000.

Review task 3.4 (review_passed). tsc clean; search + db-recovery green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
getIdsModifiedSince() runs SELECT ... WHERE modified > ? OR created > ?
for multi-machine sync deltas, which was a full table scan — memories is
the one growing table whose WHERE columns weren't fully indexed. Add
idx_memories_modified and idx_memories_created to SCHEMA_SQL (run on
every open via IF NOT EXISTS, so existing DBs pick them up with no
migration bump). EXPLAIN QUERY PLAN now shows MULTI-INDEX OR using both
indexes instead of SCAN.

Review task 3.5 (review_passed). tsc clean; central-db + db-recovery green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New src/test/db-recovery-extended.test.ts covers four realistic failure
modes the existing db-recovery.test.ts did not:
- SIGKILL mid-transaction: forked child holds an open WAL transaction,
  parent SIGKILLs; reopen asserts integrity_check=ok and the uncommitted
  rows are rolled back.
- Full disk: injects SQLITE_FULL and asserts a clear error that
  isCorruptionError() correctly classifies as non-corruption.
- Corrupted FTS index: drops memories_fts; searchFts falls back to LIKE
  (db.ts FTS catch path) instead of throwing.
- Missing better-sqlite3: vi.doMock makes the import fail; isAvailable()
  is false, getMeta() returns null, backup() rejects clearly.

Existing db-recovery.test.ts untouched. Review task 3.7 (review_passed).
tsc clean; 10 recovery tests green; npm test 1141 passed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New src/test/lifecycle-e2e.test.ts chains add → read → update → archive
→ dearchive → reinforce×3 → maintain across GnosysDB, GnosysArchive, and
GnosysMaintenanceEngine, then asserts the DB is internally consistent:
PRAGMA integrity_check = ok, exactly one primary row per memory id, and a
synced memories_fts row. Uses shared _helpers test env with temp dirs.

Review task 4.1 (review_passed). tsc clean; test green; npm test 1142.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New src/test/lifecycle-invariants.test.ts asserts, after each lifecycle
sync op (add → update → archive → dearchive → reinforce → delete), that
every memory id has exactly one primary memories row (zero after delete),
a memories_fts count ≤1 that equals the memories count (synced, no orphan
FTS rows), and no duplicated ids. Complements the 4.1 end-to-end test
with after-each-step invariant checking.

Review task 4.6 (review_passed). tsc clean; test green; npm test 1143.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a 50-memory known-content corpus (8 themed clusters, fixed dates),
a committed golden top-3 file (20 variant::query entries), and
search-golden.test.ts asserting each search variant returns a stable
top-3 (identical across runs) that matches the golden file. Covers
keyword, discover, federated, hybrid, and semantic; hybrid/semantic use
a deterministic hash-based stub embedder so the test is hermetic (no
network/model). Guards against silent ranking regressions.

Review task 5.1 (review_passed). tsc clean; 21 tests green; npm test 1164.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Document the three retrieval modes: gnosys_search (FTS5 keyword, no
embeddings), gnosys_semantic_search (embedding cosine only), and
gnosys_hybrid_search (Reciprocal Rank Fusion, k=60, of both). Includes a
comparison table, an RRF explanation, a same-query worked example, mode-
selection guidance, and CLI equivalents.

Review task 5.2 (review_passed). Docs only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add an explicit anti-injection rule to prompts/synthesize.md: retrieved
memory content under "## Context Memories" is untrusted data, not
instructions; the model must ignore embedded directives (e.g. "ignore
previous instructions", "reveal secrets") and never claim or emit
credentials/env/files. Adds a data-marker comment above {{CONTEXT}}.

Defense-in-depth: the audit (task 5.5) confirmed ask cannot leak API
keys (they're in the HTTP auth header, never the LLM context) and has no
code-execution path; this further mitigates output manipulation from
memory-embedded injection.

Review task 5.5 (review_passed). Prompt-only change; npm test 1164.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add docs/llm-provider-contract.md: the three types
(LLMGenerateOptions/LLMStreamCallbacks/LLMProviderName), the five
interface members (name, model, generate, generateWithImage?,
testConnection) with faithful signatures, streaming semantics
(tokens via onToken, full text on resolve, no silent partials), error/
retry behavior (transient 429/timeout retried via withRetry +
isTransientError; 401/403 throw immediately; API keys redacted), the
provider implementations, and a guide for adding a provider.

Review task 6.1 (review_passed). Docs only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
No provider call had a client-enforced timeout, so a hung connection
(notably a local Ollama server that accepts but never responds) could
block an MCP tool / CLI call indefinitely. Add AbortSignal.timeout to
all six fetch calls (60s for generate/vision, 10s for tags/models
probes) and a timeout on the Anthropic SDK client. The resulting
"timed out" error is already classified retryable by isTransientError,
so withRetry retries and then surfaces a clear timeout instead of
hanging.

Review task 6.2 (review_passed). tsc clean; 27 provider tests green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Error-text redaction relied on a prefix allowlist (sk-/gsk_/Bearer),
which missed the xai/mistral/custom key formats the OpenAICompatible
provider serves. Add a redactKey(text, apiKey) helper that strips the
literal key value (length >= 8, format-agnostic) and applies an extended
prefix regex (adds sk-ant-, xai-) as a secondary net. Route all three
provider error paths (Anthropic, OpenAICompatible request + vision)
through it. New llm-redact.test.ts covers the literal-key, prefix, and
short-string-guard cases.

Review task 6.3 (review_passed). tsc clean; 30 provider+redact tests green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The local embeddings path imported @huggingface/transformers without a
guard, so a missing optional dep threw a raw ERR_MODULE_NOT_FOUND stack.
Wrap the dynamic import and rethrow a clear one-liner: "Local embeddings
require @huggingface/transformers. Install it with: npm install
@huggingface/transformers" (matching the Whisper hint in audioExtract).
New embeddings-optional-dep.test.ts covers missing (asserts the hint, not
the raw error) and installed (mocked pipeline → 384-dim Float32Array).

Review task 6.5 (review_passed). tsc clean; 2 tests green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There is a per-call output cap (maxTokens, default 4096) but no per-call
input cap and no daily/cumulative cost cap or spend tracking. Per task
6.6's "document why there isn't" branch, add docs/cost-and-limits.md
covering: caps that exist, caps that don't (by design), that spend bills
to the user's own provider account (set limits in the provider billing
console), and how to bound cost — local providers ($0), Dream Mode
defaulting to local Ollama, budget-tier models, and lower maxTokens.

Review task 6.6 (review_passed). Docs only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New ingest-fixtures.test.ts drives 8 hostile/malformed inputs through
ingestFile and asserts each is handled gracefully (success or a clear
Error — never a crash, OOM, or hang): normal PDF, 0-byte, UTF-8 BOM,
100MB-over-cap text (generated at runtime, hits the maxFileSizeMb cap),
corrupt DOCX, non-existent path, and a PDF with embedded JS (handled
without executing JS). Encrypted PDF is skipped with a TODO. Only two
sub-1KB PDFs are committed; large fixtures are generated and cleaned up.

Review task 7.1 (review_passed). tsc clean; 7 passed + 1 skipped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
extractDocxText handed the buffer straight to mammoth, which decompresses
all zip entries into memory — so a <=100MB DOCX whose word/document.xml
is highly compressible could expand to tens of GB and OOM the process
(the 100MB input cap bounds the file, not the decompressed size). Add a
central-directory size check: JSZip.loadAsync parses entry metadata
(without decompressing payloads), sum uncompressedSize, and reject totals
over 200MB with a clear "possible zip bomb" error before mammoth runs.

New docx-bomb.test.ts: a 210MB-decompressed bomb is rejected without OOM,
and billion-laughs entities don't expand (xmldom is non-validating).

Review task 7.2 (review_passed). tsc clean; 2 tests green (~3s).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
chunkSplitter.ts is a pure function (no clock/random) but had no test
coverage. Add chunk-splitter.test.ts asserting splitIntoChunks returns
deeply-equal output across repeated calls for varied inputs (empty,
short, many-paragraph, oversized), stability across repetitions, and a
stable fnv1a content hash. Guards identical-input -> identical-chunks
against future regressions. No production change.

Review task 7.3 (review_passed). tsc clean; 6 tests green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New ingest-special-paths.test.ts ingests temp files whose names contain
spaces, unicode (café), emoji (🎉), and trailing whitespace, asserting
each ingests cleanly (>=1 memory). Node fs/path handle UTF-8 natively and
the ffmpeg path uses execFileSync argv (7.5), so these already worked;
this locks it against regressions. No production change.

Review task 7.6 (review_passed). tsc clean; 4 tests green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ings)

webIngest's URL guard only validated the initial URL and explicitly
allowed loopback, so SSRF was possible via (a) a public URL that
302-redirects to 127.0.0.1 / 169.254.169.254 (fetch followed redirects
unchecked) and (b) direct loopback/encoded-IP hosts.

- isSafeUrl: block loopback by default (opt-in allowLoopback), 0.0.0.0,
  hex hosts (0x7f000001 / 0x7f.0.0.1), all-numeric decimal IPs
  (2130706433), IPv6 ULA fc00::/7 + link-local fe80::/10; keep
  metadata + private IPv4 + non-http(s) scheme blocks. Uses node:net isIP.
- safeFetch: redirect:"manual" with per-hop re-validation (max 5),
  replacing all raw fetch() calls in sitemap + page fetching.
- New webingest-ssrf.test.ts: 14 blocked vectors, public URL allowed,
  loopback opt-in, and a mocked redirect-to-metadata that is rejected.

Review task 7.7 (review_passed). tsc clean; 17 SSRF tests green; npm test 1205.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New remote-two-machine.test.ts runs the full cross-machine sequence
against two GnosysDB instances sharing one temp NAS dir: A push → B pull
→ B edit/push → A pull → both edit offline → B push → A
sync(skip-and-flag). Asserts correct propagation at each step, exactly
one flagged unresolved conflict, and no data loss (A retains its local
v3-from-A while the conflict is recorded, not silently overwritten).

Review task 9.1 (review_passed). tsc clean; test green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…otent)

New remote-resume.test.ts simulates a mid-push kill (12 memories seeded,
5 copied to the NAS DB, lastSync unchanged), then re-runs push() and
asserts: remote integrity_check ok, all 12 present exactly once (no
duplicates from re-pushing the 5), per-memory content matches, and a
second push is idempotent (pushed=0). Locks the crash-safe, idempotent
resume behavior (atomic INSERT OR REPLACE per memory + lastSync gating).

Review task 9.4 (review_passed). tsc clean; test green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
edtadros and others added 23 commits May 25, 2026 18:07
dream.ts was in the coverage exclude list despite having 5 test files,
hiding a C.1 target. Remove it from exclude (maintenance/recall/llm
excludes kept) so its real coverage (28.5%) is measured. Thresholds
still pass (overall 57.7% stmts).

Baseline vs 80% goal: mcpHttp 89% (pass); db 77%, remote 74%, dream 29%,
ingest 17% (tracked debt — dedicated coverage effort recommended).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cover four pure-logic lib modules that had no tests: retry (96%),
heartbeat (82%), progress (73%), modelValidation buildRequest (50%;
I/O paths justified). Un-exclude retry.ts from coverage (pure logic,
was wrongly bundled with llm). Typed fetch mocks keep tsc --noEmit
clean. 10 external/IO/template/type-only files justified (no unit test).

Full suite: 1275 passed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ys (C.4)

resolver-routing.test.ts wrote to the developer's real
~/.config/gnosys/projects.json (backup/restore was crash-fragile). Isolate
it via a per-test GNOSYS_CONFIG_DIR tmpdir; drop the real-registry mutation.
Verified: the real projects.json is byte-identical before/after the test.

- resolver.ts: getRegistryPath() delegates to getProjectRegistryPath() so
  the GNOSYS_CONFIG_DIR hook applies (removes duplicated home-path logic)
- setup-ui-screen10.test.ts: fake home /home/gnosys-test (was /Users/edward)

Supervised edits to 2 existing test files + 1 production change (resolver).
tsc clean; full suite 1275 passed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add os: [ubuntu-latest, macos-latest] to the build-and-test matrix
(runs-on: matrix.os) so Node 18/20/22/24 are exercised on both Linux and
macOS per C.7. Re-gate the 4 coverage steps to ubuntu-latest + Node 24
only, so coverage runs once and the coverage-report artifact isn't
uploaded by two jobs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
acceptance.test.ts covered Central brain, Federated search, Dream Mode,
and Obsidian export, but had no happy-path smoke for MCP server, Web KB,
or multi-machine sync (each only depth-tested in dedicated suites). Add
acceptance-features.test.ts with three subprocess/API-level smokes:

- MCP server: spawn real dist/index.js (stdio, isolated HOME) and
  round-trip listTools + gnosys_init/gnosys_add_structured/gnosys_search
- Web KB: buildIndexSync + search returns hits
- Multi-machine sync: RemoteSync push A -> pull B propagates a memory

Every documented "What you get" feature now has an acceptance happy path.
tsc clean; full suite 1278 passed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add src/lib/log.ts with logError/Warn/Info/Debug; env-driven sinks:
- default: plain text to stderr (UX unchanged)
- GNOSYS_LOG_FORMAT=json -> JSON lines to stderr
- GNOSYS_LOG_FILE=<path> -> append JSON lines to file
- GNOSYS_LOG_LEVEL gates emission

JSON records carry timestamp/level/message/error.{name,message,stack}
plus any context. Logger is best-effort (never throws on file errors).

Migrate 2 representative error sites (db open fallback; dream scheduler);
remaining stderr.write/console.error sites adopt gradually. NEW log.test.ts.

tsc clean; full suite 1283 passed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
51 published 5.x versions vs 37 CHANGELOG entries:
- Backfill 5.4.1 (remote-first, ULID, 10-bug sweep) and 5.4.3 (postinstall
  visibility, upgrade nudge, CODE_OF_CONDUCT) — both real npm releases
  with no prior entry.
- Remove duplicate 5.4.3 bullets misplaced under 5.5.0.
- Add a Historical versions note disclosing the 15 pre-5.2.16 + 5.2.x
  patch gaps (5.0.0-5.2.15 plus 5.2.17/18/21) as tracked via git tags;
  note 5.2.13-15 were CHANGELOG-only and never published to npm.

Every 5.x published version is now entry'd or honestly disclosed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add scripts/gen-mcp-tools.mjs (scans regTool(...) registrations in
src/index.ts, sorts alphabetically, escapes pipes) and the generated
docs/mcp-tools.md (50 tools). npm run docs:mcp-tools regenerates;
regenerating produces a byte-identical file (no drift today).

README's curated tool table untouched (still useful on the npm landing
page); the generated doc is the in-repo source of truth.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirror of E.4 for CLI commands. Add scripts/gen-cli-docs.mjs (scans
Commander .command()/.description() pairs in src/cli.ts, handles
multiline descriptions, preserves registration order) and committed
docs/cli.md (103 sections). npm run docs:cli regenerates;
regenerating produces a byte-identical file (no drift today).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add docs/adr/ with README index and 8 short (150-300 words each)
architectural-decision records sourced from the corresponding Gnosys
memory entries:

  0001 MCP-First Architecture            (dec-009)
  0002 Layered Multi-Store Architecture  (deci-030)
  0003 Why Not RAG                       (dec-001)
  0004 TypeScript Implementation         (dec-010)
  0005 DB-only Architecture              (deci-032)
  0006 Built-in Server + Obsidian        (dec-011)
  0007 Open Source from Day One          (dec-005)
  0008 Automated npm Publish (OIDC)      (deci-033)

Each ADR: Status/Date/Memory header + Context/Decision/Consequences
sections. Gnosys memory remains the rolling source-of-truth; these are
stable snapshots for new contributors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add docs/source-of-truth.md — single page naming the canonical home for
each kind of content (quickstart→README, full guide→gnosys.ai,
CLI/MCP refs→generated docs, decisions→Gnosys memory + docs/adr/,
security→SECURITY.md + docs/threat-model.md, …). Includes rules of
thumb so contributors never duplicate-maintain the same info in two
places.

Final task of the 222-task review.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NEW src/test/ingest-structured.test.ts (21 tests) covering the
GnosysIngestion.ingest() LLM-structuring path with a stubbed provider:

- provider-missing error paths for every envVarMap arm
  (anthropic / openai / groq / xai / mistral / custom / ollama /
  lmstudio / unknown)
- JSON parsing variants: bare, json-fenced, plain-fenced,
  prose + fenced
- prototype-pollution sanitization (__proto__, constructor,
  prototype keys stripped)
- tag-registry validation + proposedNewTags surfacing
- field defaults when LLM returns minimal JSON
- configOverride: fresh provider resolution + missing-provider
  fallback
- isLLMAvailable / providerName getters

Coverage: ingest.ts now 100% statements / 91.93% branches /
100% functions / 100% lines (gate: ≥80%). No source changes;
no existing test files modified. Full suite 1304 passed, 1 skipped;
tsc --noEmit clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NEW src/test/dream-coverage.test.ts (29 tests) covering the four
uncovered regions of dream.ts with mocked LLM provider and mocked
desktopNotify:

Orchestrator (GnosysDreamEngine.dream):
- DB-unavailable early exit
- Too-few-memories early exit
- Provider-init error → dream_provider_unreachable audit
  + incrementDreamConsecutiveFailures
- Layer-4 desktop notify at consecutive-failure threshold
- All-phases happy path with stubbed LLM
- Abort at shouldStop checkpoint
- Max runtime exceeded path
- finalize resets consecutive-failures when LLM work succeeded

Phase implementations:
- decaySweep: skip recent / skip tiny delta / update stale
- critiquMemory rule arms (low conf, never-reinforced+old,
  short content, no tags, no relevance, invalid tags)
- llmCritique branches (ok / review / needs-update / malformed)
- generateSummaries: create / skip-unchanged / update
- summarizeCategory provider-error swallow
- discoverRelationships: self-ref filter, low-confidence
  filter, dedup of existing pairs
- findRelationships malformed-JSON branch

formatDreamReport: happy / aborted / empty paths.

DreamScheduler: prototype-pollution allowlist, disabled / not-
designated no-ops, designated idle trigger (fake timers),
recordActivity abort, stop+abort, isDesignatedMachine exception
swallow, getLocalMachineId hostname fallback + meta cache,
isDreaming, checkIdle rejection swallow.

Coverage: dream.ts now 91.68% statements / 78.04% branches /
95% functions / 95.42% lines (gate: ≥80% lines). No source
changes; existing dream test files (dream-resume, phase7d.dream,
phase9b.dream-prefs-sync, v594-dream-provider-inheritance,
v594-dream-state) untouched. Full suite 1333 passed, 1 skipped;
tsc --noEmit clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NEW src/test/remote-coverage.test.ts (28 tests) covering five
previously-uncovered branch groups in remote.ts:

resolve() edge cases:
- merged content applied to both DBs
- merged without payload falls to Invalid choice
- invalid choice strings
- remote unreachable
- memory not found on either side
- localDb.insertMemory throw caught and reported

migrate() partial failures:
- happy path with projects + memories + META_LAST_SYNC
- remote unreachable returns clean error
- per-project insert failure continues the loop
- per-memory insert failure continues the loop

getMachineId / resolveHostname:
- HOSTNAME env var
- COMPUTERNAME fallback
- os.hostname() fallback
- os.hostname() throw → unknown- prefix
- v5.9.5 self-heal: stale unknown- id overwritten;
  dream_machine_id healed when pointed at stale
- self-heal kept when hostname still cannot resolve
- stable cached non-stale id
- host- prefix is NOT treated as stale

getStatus SQLITE_BUSY:
- friendly message on SQLITE_BUSY
- non-busy sqlite errors rethrow

formatStatus + validateLocation + closeRemote:
- formatStatus: not configured / unreachable / conflicts / message
- validateLocation: create-dir warning, high-latency warning,
  sqlite setMeta failure
- closeRemote clears the cached remoteDb handle

Coverage: remote.ts now 80.83% statements / 75% branches /
95.65% functions / 80.61% lines (gate: ≥80% lines). No source
changes; existing remote test files (remote, remote-audit-sync,
remote-resume, remote-two-machine) untouched. Full suite 1361
passed, 1 skipped; tsc --noEmit clean.

Note: identified dead code at line ~778 (resolve('merged') with
no base memory) — unreachable because line ~760 early-returns
when both sides are null. Flagged for OPEN-track cleanup; no
source patch in this coverage-only task.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NEW src/test/db-coverage.test.ts (14 tests) covering the plan-named
audit/dream-result query helpers:

getRecentDreamRuns:
- Default DESC ordering with parsed details
- limit truncation
- sinceIso filter
- JSON-parse catch (returns details: {} on bad JSON)
- failuresOnly: errors > 0 OR providerUnreachable arms
- failuresOnly: false returns all
- started fallback when startedAt is missing
- Default-limit happy path

getLastSuccessfulDreamRun:
- Empty audit_log → null
- Only failed runs → null
- Mixed success/fail → most recent successful row
- decay-only counts as successful
- relationships-only counts as successful
- summaries-only counts as successful

Coverage: db.ts now 85.06% statements / 78.07% branches / 92.3%
functions / 88.47% lines (gate: ≥80% lines; baseline 81.26% from
CC.1-CC.3 side effects, now +7.21 pp). No source changes; all 6
existing db test files (db-recovery, db-recovery-extended,
phase8a.central-db, phase9a.sandbox, v511-db-schema,
v593-no-central-db-pollution) untouched. Full suite 1375 passed,
1 skipped; tsc --noEmit clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NEW gnosys-public/docs/coverage-baseline.md — closes the CC follow-up
track by documenting the post-CC.1-CC.4 coverage state. All five
C.1 target files now meet the ≥80% lines gate:

| File          | C.1 Baseline | Post-CC.4 | Δ Lines |
|---------------|--------------|-----------|---------|
| mcpHttp.ts    | 89%          | 92.42%    | +3.42   |
| ingest.ts     | 17%          | 100%      | +83     |
| dream.ts      | 29%          | 95.42%    | +66.42  |
| remote.ts     | 74%          | 80.61%    | +6.61   |
| db.ts         | 77%          | 88.47%    | +11.47  |

The doc also records per-file statement/branch/function/line columns,
overall totals, and attribution of each lift to its CC task
(ingest-structured.test.ts / dream-coverage.test.ts /
remote-coverage.test.ts / db-coverage.test.ts).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…L.1)

Curates the 84 commits between v5.10.0 and HEAD into the four
Keep-a-Changelog sub-sections under ## [Unreleased]:

Added (15 bullet groups):
- Network-hosted MCP transport (v5.12 Phases A-E): serve --transport
  http, capability replay thunks, gnosys connect, gnosys centralize,
  Docker support.
- Structured logging (D.5) with text/JSON/file sinks.
- Audit rows for remote sync.
- HTTP CORS guard.
- Atomic config writes.
- Preference key validation with did-you-mean hints.
- --json on 7 read-only CLI commands.
- Provenance: source_file in reads.
- Export excluded-archived count.
- gnosys upgrade PM detection.
- npm discoverability keywords.
- Documentation and ADRs (E.2-E.8, A.13, CC.5).
- Acceptance smokes (C.9).
- Test coverage expansion (CC.1-CC.4 + others).

Changed: CI matrix Linux+macOS (C.7), Node 18/20 CI, Biome (B.2),
dep cleanup (B.3/B.4), DB-only history, CHANGELOG backfill (E.2),
README updates, DB index perf.

Fixed: path traversal (A.5), shell injection (A.8), file perms 0600
(A.11), clean dist (20.13), legacy schema migration, npm provenance,
README/pkg fixes, MCP error envelopes, machine ID, busy timeout,
embeddings hint, LLM timeouts, HTTP session cleanup.

Security: HTTP auth on non-loopback, DoS body limits, SSRF parity
(17.4), DOCX zip-bomb guard, API key redaction, prompt injection
hardening, CORS default-deny.

The ## [Unreleased] header carries no date; the date is added when
the release is cut (REL.3).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The threat model already linked back to SECURITY.md from its header;
this adds the missing forward reference so the two docs are properly
cross-linked.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…N.2)

Adds a new ## Documentation section between Project Structure and
Testing that points future contributors to the content map (user-
facing site vs in-repo source of truth vs Gnosys memory).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NEW gnosys-public/tsconfig.publish.json extends the dev tsconfig
with sourceMap:false and declarationMap:false. Declarations (.d.ts)
are preserved so TypeScript consumers still get types.

Wires a publish-specific build pipeline in package.json:
- prebuild:publish — clean dist mirror of the existing prebuild
- build:publish    — tsc -p tsconfig.publish.json
- prepublishOnly   — now invokes build:publish (was: npm run build)

The dev workflow is unchanged: 'npm run build' still uses tsconfig.json
and emits sourcemaps + declaration maps for local debugging.

Impact: npm tarball unpacked size drops dramatically — 1.9 MB / 255
files in the publish build, down from ~7.4 MB with all 516+ .map
sidecars. Faster installs, smaller footprint on disk for the
~80 MB-equivalent-size of all .map files removed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…* (OPEN.4 round 1)

Round 1 of the gradual D.5 follow-on migration. Converts exactly 10
catch-block / fatal-startup console.error calls to structured
logError/logWarn from src/lib/log.js. UI prints (per-chunk ingest
progress, user-facing summaries) are deliberately left as
console.error.

Sites converted:
- src/sandbox/server.ts:678 → logError(new Error('Failed to open
  GnosysDB'), { module: 'sandbox', op: 'openDb', hint })
- src/sandbox/server.ts:680 → logWarn(`Network path may be
  unavailable`, { module: 'sandbox', dbDir })
- src/sandbox/server.ts:709 → logError(err, { module: 'sandbox',
  op: 'dreamInit' })
- src/lib/projectIdentity.ts:144 → logWarn(...) — project-move
  notification (not a caught exception)
- src/lib/chat/index.ts:101 → logError(new Error('Session not
  found: ...'), { module: 'chat', op: 'resume' })
- src/cli.ts:313, 354, 400, 443, 527 → logError(err, { module:
  'cli', op: <command name> }) where <command name> is derived
  from the surrounding program.command() block (discover, discover,
  search, search, list)

Net effect: total src/ console.error count drops from 232 → 222
(−10). Each modified file imports logError/logWarn from
'../lib/log.js' (relative path adjusted per file depth).

Future rounds (OPEN.4.2, .3, ...) can continue the migration at
~10 high-signal sites per round; dream.ts progress prints and
genuine UI output remain out of scope.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds 4 new ADRs to docs/adr/ following the canonical 0001-0008
format (Status / Date / Memory metadata + Context / Decision /
Consequences sections, 187-210 words each):

- 0009-remote-first-reads.md — Remote-First Reads, Local-as-
  Offline-Only Cache (deci-037; supersedes deci-034). NAS is
  the source of truth; local DB is an offline-resilience cache,
  not a performance layer.
- 0010-prompt-injection-threat-model.md — Prompt Injection Threat
  Model (deci-01KSGSX8SJXAVAY7EV2VS9YJJP, from task A.9). Bounded
  accepted risk; defend at the Gnosys boundary (no exfiltration
  primitives, SSRF guards, API-key redaction, provenance fields)
  without stripping legitimate instruction-like content.
- 0011-readme-positioning.md — README Positioning: No Competitor
  Comparisons (deci-01KSGRQ4GEGPHJQMYDD3V2XCWK, from task 20.27).
  README stays minimal; gnosys.ai is the canonical positioning
  surface.
- 0012-categorized-tag-registry.md — Categorized Tag Registry
  (dec-006). Tags live in .gnosys/tags.yml under named categories
  (domain, type, concern, status-tag); LLM proposes new tags but
  user approves before they're added; orthogonal to directory
  categories.

docs/adr/README.md index updated with 4 new rows.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Upgrades the legacy 'Is better-sqlite3 installed?' phrasing to parity
with the @huggingface/transformers install hint at
src/lib/embeddings.ts:66.

Sites updated:
- src/index.ts:2312 — Archive-not-available MCP tool response
- src/cli.ts:3720 — Archive command catch
- src/cli.ts:6380, 6429, 6492 — 'Error: GnosysDB not available' catches
- src/sandbox/server.ts:679 — logError hint field (already migrated
  to log.* in OPEN.4; this commit updates only the hint text)

New phrasing (consistent across all 6 sites):
  '... Install it with: npm install better-sqlite3'

Acceptance gate: grep -c 'npm install better-sqlite3' in src/ runtime
error paths is now 6 (plan target: ≥4). Legacy phrasing count: 0.

Pure string replacement; +6/-6 lines across 3 files. No structural
changes, no test files touched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@socket-security
Copy link
Copy Markdown

socket-security Bot commented May 26, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​biomejs/​biome@​2.4.1510010010098100

View full report

@edtadros edtadros force-pushed the feat/network-mcp branch 2 times, most recently from 59c144e to b7dc75b Compare May 26, 2026 19:17
84 commits since v5.10.0. Highlights:

Security hardening — path-traversal in export blocked (assertWithin),
shell injection eliminated (argv arrays), .env/gnosys.db at mode 0600,
HTTP MCP auth+CORS+body-limits+idle-reaper, SSRF safeFetch, ask layer
prompt-injection-resistant.

Coverage closure — every C.1 target file ≥80%: ingest.ts 100%, dream.ts
95%, db.ts 88%, remote.ts 80%, mcpHttp.ts 89%.

Tooling — Biome lint, Node 20/22/24 × Linux+macOS CI matrix (Node 18
dropped; past EOL April 2025, toolchain needs node:util.styleText
which is Node 20.12+), prebuild dist clean, knip dead-code, structured
logger (GNOSYS_LOG_*), sourcemap-trimmed publish (tarball 7.4MB →
1.9MB), updated package metadata (keywords, repo URL canonicalized,
optional-deps documented).

Docs — generated docs/mcp-tools.md + docs/cli.md, threat-model.md,
12 ADRs (decisions backfilled from Gnosys memory), source-of-truth map,
SECURITY.md update-integrity section, CHANGELOG historical-versions note.

engines.node raised: >=18.0.0 → >=20.12.0. README prereq updated.

See CHANGELOG.md ## [5.11.0] section for the full curated list.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@edtadros edtadros force-pushed the feat/network-mcp branch from b7dc75b to 64a40a1 Compare May 26, 2026 19:29
@edtadros edtadros merged commit f5de945 into master May 26, 2026
10 checks passed
edtadros added a commit that referenced this pull request May 30, 2026
Bring in the GitHub merge commit for v5.11.0 before pushing the
feat/network-mcp fast-forward line.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant