diff --git a/README.md b/README.md index ee952317..dde6d0eb 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,21 @@ Semantic search for Claude Code, Codex, Cursor, opencode, and Oh My Pi (OMP) conversations. Remember past discussions, decisions, and patterns. +## Recall admission + +Search rechecks the current record-exclusion policy and configured credential scanner, +including rows indexed before the policy changed. Excluded hits do not consume the +requested result limit. A stale whole-conversation summary is omitted if any of its +source records or tool calls is excluded; safe neighboring exchanges remain searchable. + +Conversation opt-out markers are read from decoded user text, including JSON escapes. +Assistant quotations and tool outputs do not opt out the conversation. Tool-result +blocks inside a user envelope are still tool output. Malformed legacy text retains +conservative marker handling, and unreadable or oversized records fail closed. +Ranged reads retain physical line coordinates without materializing unrelated message bodies. +Original transcripts are never rewritten. Source and policy changes during a search +abort the result rather than returning a partially validated view. + ## Testimonial From an AI coding assistant's perspective: @@ -214,7 +229,7 @@ In Codex and opencode, the skill guides the agent to use the episodic-memory MCP ## API Configuration -By default, episodic-memory uses your Claude Code authentication for Claude Code summarization. Codex-indexed sessions with a session ID are summarized through `codex app-server` by creating an ephemeral `thread/fork`, so the summary can use Codex session context and reasoning summaries without appending to the original rollout. +By default, episodic-memory uses your Claude Code authentication for Claude Code summarization. Codex-indexed sessions with a session ID are summarized through `codex app-server` by creating an ephemeral `thread/fork`, so the summary can use Codex session context and reasoning summaries without appending to the original rollout. A Codex transcript without a session ID is summarized from its admitted text in an isolated ephemeral `thread/start`; it is never sent to Claude. To route summarization through a custom Anthropic-compatible endpoint or override the model: @@ -294,7 +309,7 @@ These pass through unchanged to episodic-memory's summarizer subprocess. Because These settings only affect episodic-memory's summarization calls, not your interactive Claude Code or Codex sessions. -Codex summarization requires `codex-cli 0.130.0` or newer. If Codex app-server summarization is unavailable, sync logs the reason and falls back to transcript-text summarization. +Codex summarization requires `codex-cli 0.130.0` or newer; isolated transcript-only summaries require `0.154.0` or newer. If Codex app-server summarization is unavailable, sync records a retryable summary failure rather than switching providers. ### What's Affected @@ -417,14 +432,14 @@ open output.html ## Excluding Conversations -Conversations containing this marker anywhere in their content will be archived but not indexed: +Conversations with this marker in a decoded user instruction are excluded before new archive copying, parsing, and indexing: ``` DO NOT INDEX THIS CHAT ``` **Automatic exclusions:** -- Conversations where Claude generates summaries (marker in system prompt) +- Summarizer-generated conversations carrying the marker in their prompt - Meta-conversations about conversation processing **Use cases:** @@ -433,7 +448,117 @@ Conversations containing this marker anywhere in their content will be archived - Test or experimental sessions - Any conversation you don't want searchable -The marker can appear in any message (user or assistant) and excludes the entire conversation from the search index. +The marker in user instructions prevents new derived copies and indexing; an assistant quotation or tool result does not opt out the conversation. It does not delete an existing archive or index entry. + +### Persistent record exclusions + +`record-exclusions.json` in the existing configuration directory defines exact +records that must not enter new derived archives, parser output, summaries, or +database insertions. It is independent of `CONVERSATION_SEARCH_EXCLUDE_PROJECTS`; +that environment variable cannot replace or disable record exclusions. + +```json +{ + "version": 1, + "exchanges": [ + { + "session_id": "00000000-1111-4222-8333-444444444444", + "transcript": "rollout-00000000-1111-4222-8333-444444444444.jsonl", + "line_start": 4, + "line_end": 5 + } + ], + "tool_calls": [ + { + "session_id": "00000000-1111-4222-8333-444444444444", + "transcript": "rollout-00000000-1111-4222-8333-444444444444.jsonl", + "call_id": "call-example" + } + ] +} +``` + +Line ranges are inclusive physical JSONL coordinates in the original transcript. +Excluded records become empty lines in a derived copy, preserving later line +numbers. Session identity, exact transcript basename and native tool-call identity, not +a machine-specific absolute path, bind the policy. Sibling subagent transcripts +sharing a parent session are separate subjects. Safe text blocks remain when only a tool block +is excluded. Original transcripts are never changed. + +Malformed, unreadable, symlinked or oversized policy files reject admission. +When a policy is present, unchanged source timestamps do not bypass a fresh +admission decision. Source/archive aliasing and changing input are rejected. +The marker scan and record stream use the same descriptor; source identity is +checked during and after streaming. Direct database insertion checks both the +transcript identity and historical session metadata, which may name an ancestor. +The foreground sync CLI exits nonzero for per-file admission errors and does not +claim successful synchronization or start embedding migration after those errors. +Full, session and incremental indexing, repair, MCP reads and the display CLI use +the same admission boundary. Display pagination retains physical line numbers. +An absent policy retains ordinary behavior; deleting the policy intentionally +removes its protection, so maintain it as private operator configuration. + +Summarization with a record policy uses admitted transcript text, never a resumed +source session. Codex retains its selected provider through an ephemeral fresh +context and fails rather than silently falling back to another provider. Claude +receives the admitted text without session resume. Policy-enabled Codex summaries +require Codex 0.154.0 or newer. The isolated summary path disables inherited +tools, plugins, hooks, and skills, and refuses to proceed if the configured +MCP or skill boundary cannot be established. It uses a temporary empty working +directory; provider error payloads are suppressed and the directory is removed +after process termination. + +### Optional local secret screening + +Add an optional `screening` object to the same policy to reject newly detected +content before derived archive writes, reads, summaries and database insertion: + +```json +"screening": { + "engine": "gitleaks", + "executable": "/absolute/resolved/path/to/gitleaks", + "sha256": "<64 lowercase hex characters from the trusted executable>", + "timeout_ms": 2000, + "max_record_bytes": 1048576 +} +``` + +Keep the existing `version`, `exchanges` and `tool_calls` fields. Install Gitleaks +through a trusted package source, resolve its executable symlink and verify its provenance before pinning its +SHA-256. On migration or upgrade, explicitly replace the host-local path and +digest after verifying the new executable. The scanner must be a regular, +executable file owned by the current user or root and not group/world-writable. +Do not copy credential stores or an old host's executable-path assumption. + +Exact exclusions run first. Each remaining physical record is screened locally +with the pinned Gitleaks built-in rules. Nested JSON strings are decoded while +retaining key/value assignment context; separate exchange messages are screened +separately. A finding rejects the operation rather than silently dropping a new +message and breaking conversation pairing. Missing/changed executables, byte or +nesting limits, timeouts and malformed or contradictory reports fail closed. +Verification reports scanner rejections and scanner unavailability separately +from transcript corruption, even when a summary is missing. A broad repair +refuses to start while either screening failure remains. +No findings, input, scanner diagnostics or secret fingerprints appear in errors. +The scanner receives stdin in a private empty working directory with no inherited +environment/configuration and with inline allow comments disabled. Its temporary +directory is removed after execution. Original transcripts and previously +published archives remain unchanged when admission fails. + +Screening launches a bounded subprocess per record; it is not an unbounded bulk +collection service. Measure a finite representative batch before large imports. +No automatic sync or background job is enabled by this setting. Before enabling +it, replace all policy consumers with this version through their native lifecycle: +older strict-schema consumers reject the additional field. Installing a plugin +does not replace already-loaded code, so do not enable the field while protected +old consumers still require the previous schema. + +Without `screening`, this policy remains an exact exclusion mechanism. Even with +screening, pattern matching does not prove universal unknown-secret absence, +purge preexisting derived data, rewrite original +sessions, or retroactively filter already-loaded code. Old randomly generated +tool-row IDs need verified native source-call mapping before policy migration; +do not broaden those entries to whole sessions or claim a complete migration. ## MCP Server diff --git a/dist/db.js b/dist/db.js index 83f546ad..a5bba7e3 100644 --- a/dist/db.js +++ b/dist/db.js @@ -1,4 +1,5 @@ import Database from 'better-sqlite3'; +import { admitExchange } from './record-admission.js'; import path from 'path'; import fs from 'fs'; import * as sqliteVec from 'sqlite-vec'; @@ -186,6 +187,10 @@ export function initDatabase() { return db; } export function insertExchange(db, exchange, embedding, toolNames) { + const admitted = admitExchange(exchange); + if (!admitted) + return; + exchange = admitted; const now = Date.now(); const stmt = db.prepare(` INSERT OR REPLACE INTO exchanges diff --git a/dist/index-cli.js b/dist/index-cli.js index 950a4c7d..28219ede 100644 --- a/dist/index-cli.js +++ b/dist/index-cli.js @@ -65,13 +65,16 @@ async function main() { console.log(`Missing summaries: ${issues.missing.length}`); console.log(`Orphaned entries: ${issues.orphaned.length}`); console.log(`Outdated files: ${issues.outdated.length}`); + console.log(`Screening-rejected archives: ${issues.screeningRejected.length}`); + console.log(`Screening-unavailable archives: ${issues.screeningUnavailable.length}`); console.log(`Corrupted files: ${issues.corrupted.length}`); if (issues.missing.length > 0) { console.log('\nMissing summaries:'); issues.missing.forEach(m => console.log(` ${m.path}`)); } - if (issues.missing.length + issues.orphaned.length + issues.outdated.length + issues.corrupted.length > 0) { - console.log('\nRun with --repair to fix these issues.'); + if (issues.missing.length + issues.orphaned.length + issues.outdated.length + + issues.screeningRejected.length + issues.screeningUnavailable.length + issues.corrupted.length > 0) { + console.log('\nReview the reported categories before repair; --repair may regenerate embeddings and AI summaries.'); process.exit(1); } else { @@ -81,6 +84,11 @@ async function main() { case 'repair': console.log('Verifying conversation index...'); const repairIssues = await verifyIndex(); + if (repairIssues.screeningRejected.length + repairIssues.screeningUnavailable.length > 0) { + console.error('Record screening failures require review; repair was not started.'); + process.exitCode = 1; + break; + } if (repairIssues.missing.length + repairIssues.orphaned.length + repairIssues.outdated.length > 0) { await repairIndex(repairIssues); } diff --git a/dist/indexer.js b/dist/indexer.js index 8afa1d35..c5c42e13 100644 --- a/dist/indexer.js +++ b/dist/indexer.js @@ -1,4 +1,5 @@ import fs from 'fs'; +import { archiveAdmittedConversation, readRecordExclusions } from './record-admission.js'; import path from 'path'; import { initDatabase, insertExchange } from './db.js'; import { parseConversation } from './parser.js'; @@ -26,6 +27,7 @@ function sessionIdForSummary(exchanges) { return exchanges.find(exchange => exchange.sessionId)?.sessionId; } export async function indexConversations(limitToProject, maxConversations, concurrency = 1, noSummaries = false) { + readRecordExclusions(); console.log('Initializing database...'); const db = initDatabase(); console.log('Loading embedding model...'); @@ -74,15 +76,16 @@ export async function indexConversations(limitToProject, maxConversations, concu let exchanges; try { // Copy to archive (ensure parent dirs exist for subagent files) - if (!fs.existsSync(archivePath)) { - fs.mkdirSync(path.dirname(archivePath), { recursive: true }); - fs.copyFileSync(sourcePath, archivePath); + if (await archiveAdmittedConversation(sourcePath, archivePath)) console.log(` Archived: ${file}`); - } // Parse conversation exchanges = await parseConversation(sourcePath, project, archivePath); } catch (error) { + if (error.code !== 'ENOENT') { + db.close(); + throw error; + } console.log(` Skipped ${file} (read failed: ${error instanceof Error ? error.message : error})`); continue; } @@ -159,6 +162,7 @@ export async function indexConversations(limitToProject, maxConversations, concu console.log(`\nāœ… Indexing complete! Conversations: ${conversationsProcessed}, Exchanges: ${totalExchanges}`); } export async function indexSession(sessionId, concurrency = 1, noSummaries = false) { + readRecordExclusions(); console.log(`Indexing session: ${sessionId}`); // Find the conversation file for this session const sourceDirs = getConversationSourceDirs(); @@ -187,15 +191,14 @@ export async function indexSession(sessionId, concurrency = 1, noSummaries = fal // Archive + parse — source may vanish mid-run (Claude Code cleanup). let exchanges; try { - if (!fs.existsSync(archivePath)) { - fs.mkdirSync(path.dirname(archivePath), { recursive: true }); - fs.copyFileSync(sourcePath, archivePath); - } + await archiveAdmittedConversation(sourcePath, archivePath); exchanges = await parseConversation(sourcePath, project, archivePath); } catch (error) { - console.log(`Skipped ${file} (read failed: ${error instanceof Error ? error.message : error})`); db.close(); + if (error.code !== 'ENOENT') + throw error; + console.log(`Skipped ${file} (read failed: ${error instanceof Error ? error.message : error})`); break; } if (exchanges.length > 0) { @@ -249,6 +252,7 @@ export async function indexSession(sessionId, concurrency = 1, noSummaries = fal } } export async function indexUnprocessed(concurrency = 1, noSummaries = false) { + readRecordExclusions(); console.log('Finding unprocessed conversations...'); if (concurrency > 1) console.log(`Concurrency: ${concurrency}`); @@ -284,9 +288,7 @@ export async function indexUnprocessed(concurrency = 1, noSummaries = false) { try { fs.mkdirSync(path.dirname(archivePath), { recursive: true }); // Refresh the archive when the source may have grown beyond what we've seen. - if (!fs.existsSync(archivePath) || maxIndexedLine > 0) { - fs.copyFileSync(sourcePath, archivePath); - } + await archiveAdmittedConversation(sourcePath, archivePath); // Parse and filter to exchanges past the high-water mark const exchanges = await parseConversation(sourcePath, project, archivePath); const newExchanges = maxIndexedLine > 0 @@ -297,6 +299,10 @@ export async function indexUnprocessed(concurrency = 1, noSummaries = false) { unprocessed.push({ project, file, sourcePath, archivePath, summaryPath, exchanges: newExchanges }); } catch (error) { + if (error.code !== 'ENOENT') { + db.close(); + throw error; + } console.log(` Skipped ${file} (read failed: ${error instanceof Error ? error.message : error})`); continue; } diff --git a/dist/mcp-server.js b/dist/mcp-server.js index e3a9adac..a6862460 100644 --- a/dist/mcp-server.js +++ b/dist/mcp-server.js @@ -6,7 +6,11 @@ var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __commonJS = (cb, mod) => function __require() { - return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; + try { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; + } catch (e) { + throw mod = 0, e; + } }; var __export = (target, all) => { for (var name in all) @@ -29,9 +33,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge mod )); -// node_modules/ajv/dist/compile/codegen/code.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/codegen/code.js var require_code = __commonJS({ - "node_modules/ajv/dist/compile/codegen/code.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/codegen/code.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.regexpCode = exports.getEsmExportName = exports.getProperty = exports.safeStringify = exports.stringify = exports.strConcat = exports.addCodeArg = exports.str = exports._ = exports.nil = exports._Code = exports.Name = exports.IDENTIFIER = exports._CodeOrName = void 0; @@ -183,9 +187,9 @@ var require_code = __commonJS({ } }); -// node_modules/ajv/dist/compile/codegen/scope.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/codegen/scope.js var require_scope = __commonJS({ - "node_modules/ajv/dist/compile/codegen/scope.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/codegen/scope.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ValueScope = exports.ValueScopeName = exports.Scope = exports.varKinds = exports.UsedValueState = void 0; @@ -328,9 +332,9 @@ var require_scope = __commonJS({ } }); -// node_modules/ajv/dist/compile/codegen/index.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/codegen/index.js var require_codegen = __commonJS({ - "node_modules/ajv/dist/compile/codegen/index.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/codegen/index.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.or = exports.and = exports.not = exports.CodeGen = exports.operators = exports.varKinds = exports.ValueScopeName = exports.ValueScope = exports.Scope = exports.Name = exports.regexpCode = exports.stringify = exports.getProperty = exports.nil = exports.strConcat = exports.str = exports._ = void 0; @@ -1048,9 +1052,9 @@ var require_codegen = __commonJS({ } }); -// node_modules/ajv/dist/compile/util.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/util.js var require_util = __commonJS({ - "node_modules/ajv/dist/compile/util.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/util.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.checkStrictMode = exports.getErrorPath = exports.Type = exports.useFunc = exports.setEvaluated = exports.evaluatedPropsToName = exports.mergeEvaluated = exports.eachItem = exports.unescapeJsonPointer = exports.escapeJsonPointer = exports.escapeFragment = exports.unescapeFragment = exports.schemaRefOrVal = exports.schemaHasRulesButRef = exports.schemaHasRules = exports.checkUnknownRules = exports.alwaysValidSchema = exports.toHash = void 0; @@ -1215,9 +1219,9 @@ var require_util = __commonJS({ } }); -// node_modules/ajv/dist/compile/names.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/names.js var require_names = __commonJS({ - "node_modules/ajv/dist/compile/names.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/names.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var codegen_1 = require_codegen(); @@ -1254,9 +1258,9 @@ var require_names = __commonJS({ } }); -// node_modules/ajv/dist/compile/errors.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/errors.js var require_errors = __commonJS({ - "node_modules/ajv/dist/compile/errors.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/errors.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.extendErrors = exports.resetErrorsCount = exports.reportExtraError = exports.reportError = exports.keyword$DataError = exports.keywordError = void 0; @@ -1376,9 +1380,9 @@ var require_errors = __commonJS({ } }); -// node_modules/ajv/dist/compile/validate/boolSchema.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/validate/boolSchema.js var require_boolSchema = __commonJS({ - "node_modules/ajv/dist/compile/validate/boolSchema.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/validate/boolSchema.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.boolOrEmptySchema = exports.topBoolOrEmptySchema = void 0; @@ -1427,9 +1431,9 @@ var require_boolSchema = __commonJS({ } }); -// node_modules/ajv/dist/compile/rules.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/rules.js var require_rules = __commonJS({ - "node_modules/ajv/dist/compile/rules.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/rules.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getRules = exports.isJSONType = void 0; @@ -1458,9 +1462,9 @@ var require_rules = __commonJS({ } }); -// node_modules/ajv/dist/compile/validate/applicability.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/validate/applicability.js var require_applicability = __commonJS({ - "node_modules/ajv/dist/compile/validate/applicability.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/validate/applicability.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.shouldUseRule = exports.shouldUseGroup = exports.schemaHasRulesForType = void 0; @@ -1481,9 +1485,9 @@ var require_applicability = __commonJS({ } }); -// node_modules/ajv/dist/compile/validate/dataType.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/validate/dataType.js var require_dataType = __commonJS({ - "node_modules/ajv/dist/compile/validate/dataType.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/validate/dataType.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.reportTypeError = exports.checkDataTypes = exports.checkDataType = exports.coerceAndCheckDataType = exports.getJSONTypes = exports.getSchemaTypes = exports.DataType = void 0; @@ -1665,9 +1669,9 @@ var require_dataType = __commonJS({ } }); -// node_modules/ajv/dist/compile/validate/defaults.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/validate/defaults.js var require_defaults = __commonJS({ - "node_modules/ajv/dist/compile/validate/defaults.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/validate/defaults.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.assignDefaults = void 0; @@ -1702,9 +1706,9 @@ var require_defaults = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/code.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/code.js var require_code2 = __commonJS({ - "node_modules/ajv/dist/vocabularies/code.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/code.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.validateUnion = exports.validateArray = exports.usePattern = exports.callValidateCode = exports.schemaProperties = exports.allSchemaProperties = exports.noPropertyInData = exports.propertyInData = exports.isOwnProperty = exports.hasPropFunc = exports.reportMissingProp = exports.checkMissingProp = exports.checkReportMissingProp = void 0; @@ -1775,13 +1779,13 @@ var require_code2 = __commonJS({ exports.callValidateCode = callValidateCode; var newRegExp = (0, codegen_1._)`new RegExp`; function usePattern({ gen, it: { opts } }, pattern) { - const u3 = opts.unicodeRegExp ? "u" : ""; + const u = opts.unicodeRegExp ? "u" : ""; const { regExp } = opts.code; - const rx = regExp(pattern, u3); + const rx = regExp(pattern, u); return gen.scopeValue("pattern", { key: rx.toString(), ref: rx, - code: (0, codegen_1._)`${regExp.code === "new RegExp" ? newRegExp : (0, util_2.useFunc)(gen, regExp)}(${pattern}, ${u3})` + code: (0, codegen_1._)`${regExp.code === "new RegExp" ? newRegExp : (0, util_2.useFunc)(gen, regExp)}(${pattern}, ${u})` }); } exports.usePattern = usePattern; @@ -1835,9 +1839,9 @@ var require_code2 = __commonJS({ } }); -// node_modules/ajv/dist/compile/validate/keyword.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/validate/keyword.js var require_keyword = __commonJS({ - "node_modules/ajv/dist/compile/validate/keyword.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/validate/keyword.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.validateKeywordUsage = exports.validSchemaType = exports.funcKeywordCode = exports.macroKeywordCode = void 0; @@ -1953,9 +1957,9 @@ var require_keyword = __commonJS({ } }); -// node_modules/ajv/dist/compile/validate/subschema.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/validate/subschema.js var require_subschema = __commonJS({ - "node_modules/ajv/dist/compile/validate/subschema.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/validate/subschema.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.extendSubschemaMode = exports.extendSubschemaData = exports.getSubschema = void 0; @@ -2044,7 +2048,7 @@ var require_fast_deep_equal = __commonJS({ if (a === b2) return true; if (a && b2 && typeof a == "object" && typeof b2 == "object") { if (a.constructor !== b2.constructor) return false; - var length, i, keys; + var length, i, keys2; if (Array.isArray(a)) { length = a.length; if (length != b2.length) return false; @@ -2055,13 +2059,13 @@ var require_fast_deep_equal = __commonJS({ if (a.constructor === RegExp) return a.source === b2.source && a.flags === b2.flags; if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b2.valueOf(); if (a.toString !== Object.prototype.toString) return a.toString() === b2.toString(); - keys = Object.keys(a); - length = keys.length; + keys2 = Object.keys(a); + length = keys2.length; if (length !== Object.keys(b2).length) return false; for (i = length; i-- !== 0; ) - if (!Object.prototype.hasOwnProperty.call(b2, keys[i])) return false; + if (!Object.prototype.hasOwnProperty.call(b2, keys2[i])) return false; for (i = length; i-- !== 0; ) { - var key = keys[i]; + var key = keys2[i]; if (!equal(a[key], b2[key])) return false; } return true; @@ -2159,9 +2163,9 @@ var require_json_schema_traverse = __commonJS({ } }); -// node_modules/ajv/dist/compile/resolve.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/resolve.js var require_resolve = __commonJS({ - "node_modules/ajv/dist/compile/resolve.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/resolve.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getSchemaRefs = exports.resolveUrl = exports.normalizeId = exports._getFullPath = exports.getFullPath = exports.inlineRef = void 0; @@ -2315,9 +2319,9 @@ var require_resolve = __commonJS({ } }); -// node_modules/ajv/dist/compile/validate/index.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/validate/index.js var require_validate = __commonJS({ - "node_modules/ajv/dist/compile/validate/index.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/validate/index.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getData = exports.KeywordCxt = exports.validateFunctionCode = void 0; @@ -2823,9 +2827,9 @@ var require_validate = __commonJS({ } }); -// node_modules/ajv/dist/runtime/validation_error.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/runtime/validation_error.js var require_validation_error = __commonJS({ - "node_modules/ajv/dist/runtime/validation_error.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/runtime/validation_error.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var ValidationError = class extends Error { @@ -2839,9 +2843,9 @@ var require_validation_error = __commonJS({ } }); -// node_modules/ajv/dist/compile/ref_error.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/ref_error.js var require_ref_error = __commonJS({ - "node_modules/ajv/dist/compile/ref_error.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/ref_error.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var resolve_1 = require_resolve(); @@ -2856,9 +2860,9 @@ var require_ref_error = __commonJS({ } }); -// node_modules/ajv/dist/compile/index.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/index.js var require_compile = __commonJS({ - "node_modules/ajv/dist/compile/index.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/index.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.resolveSchema = exports.getCompilingSchema = exports.resolveRef = exports.compileSchema = exports.SchemaEnv = void 0; @@ -3080,9 +3084,9 @@ var require_compile = __commonJS({ } }); -// node_modules/ajv/dist/refs/data.json +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/refs/data.json var require_data = __commonJS({ - "node_modules/ajv/dist/refs/data.json"(exports, module) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/refs/data.json"(exports, module) { module.exports = { $id: "https://raw.githubusercontent.com/ajv-validator/ajv/master/lib/refs/data.json#", description: "Meta-schema for $data reference (JSON AnySchema extension proposal)", @@ -3258,8 +3262,8 @@ var require_utils = __commonJS({ } return ind; } - function removeDotSegments(path3) { - let input2 = path3; + function removeDotSegments(path4) { + let input2 = path4; const output2 = []; let nextSlash = -1; let len = 0; @@ -3668,8 +3672,8 @@ var require_schemes = __commonJS({ } if (wsComponent.resourceName) { const queryIndex = wsComponent.resourceName.indexOf("?"); - const path3 = queryIndex === -1 ? wsComponent.resourceName : wsComponent.resourceName.slice(0, queryIndex); - wsComponent.path = path3 && path3 !== "/" ? path3 : void 0; + const path4 = queryIndex === -1 ? wsComponent.resourceName : wsComponent.resourceName.slice(0, queryIndex); + wsComponent.path = path4 && path4 !== "/" ? path4 : void 0; wsComponent.query = queryIndex === -1 ? void 0 : wsComponent.resourceName.slice(queryIndex + 1); wsComponent.resourceName = void 0; } @@ -4217,9 +4221,9 @@ var require_fast_uri = __commonJS({ } }); -// node_modules/ajv/dist/runtime/uri.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/runtime/uri.js var require_uri = __commonJS({ - "node_modules/ajv/dist/runtime/uri.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/runtime/uri.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var uri = require_fast_uri(); @@ -4228,9 +4232,9 @@ var require_uri = __commonJS({ } }); -// node_modules/ajv/dist/core.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/core.js var require_core = __commonJS({ - "node_modules/ajv/dist/core.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/core.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CodeGen = exports.Name = exports.nil = exports.stringify = exports.str = exports._ = exports.KeywordCxt = void 0; @@ -4839,9 +4843,9 @@ var require_core = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/core/id.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/core/id.js var require_id = __commonJS({ - "node_modules/ajv/dist/vocabularies/core/id.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/core/id.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var def = { @@ -4854,9 +4858,9 @@ var require_id = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/core/ref.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/core/ref.js var require_ref = __commonJS({ - "node_modules/ajv/dist/vocabularies/core/ref.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/core/ref.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.callRef = exports.getValidate = void 0; @@ -4976,9 +4980,9 @@ var require_ref = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/core/index.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/core/index.js var require_core2 = __commonJS({ - "node_modules/ajv/dist/vocabularies/core/index.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/core/index.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var id_1 = require_id(); @@ -4997,9 +5001,9 @@ var require_core2 = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/validation/limitNumber.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/validation/limitNumber.js var require_limitNumber = __commonJS({ - "node_modules/ajv/dist/vocabularies/validation/limitNumber.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/validation/limitNumber.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var codegen_1 = require_codegen(); @@ -5029,9 +5033,9 @@ var require_limitNumber = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/validation/multipleOf.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/validation/multipleOf.js var require_multipleOf = __commonJS({ - "node_modules/ajv/dist/vocabularies/validation/multipleOf.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/validation/multipleOf.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var codegen_1 = require_codegen(); @@ -5057,9 +5061,9 @@ var require_multipleOf = __commonJS({ } }); -// node_modules/ajv/dist/runtime/ucs2length.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/runtime/ucs2length.js var require_ucs2length = __commonJS({ - "node_modules/ajv/dist/runtime/ucs2length.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/runtime/ucs2length.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); function ucs2length(str) { @@ -5083,9 +5087,9 @@ var require_ucs2length = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/validation/limitLength.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/validation/limitLength.js var require_limitLength = __commonJS({ - "node_modules/ajv/dist/vocabularies/validation/limitLength.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/validation/limitLength.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var codegen_1 = require_codegen(); @@ -5115,9 +5119,9 @@ var require_limitLength = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/validation/pattern.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/validation/pattern.js var require_pattern = __commonJS({ - "node_modules/ajv/dist/vocabularies/validation/pattern.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/validation/pattern.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var code_1 = require_code2(); @@ -5135,12 +5139,12 @@ var require_pattern = __commonJS({ error: error61, code(cxt) { const { gen, data, $data, schema, schemaCode, it } = cxt; - const u3 = it.opts.unicodeRegExp ? "u" : ""; + const u = it.opts.unicodeRegExp ? "u" : ""; if ($data) { const { regExp } = it.opts.code; const regExpCode = regExp.code === "new RegExp" ? (0, codegen_1._)`new RegExp` : (0, util_1.useFunc)(gen, regExp); const valid = gen.let("valid"); - gen.try(() => gen.assign(valid, (0, codegen_1._)`${regExpCode}(${schemaCode}, ${u3}).test(${data})`), () => gen.assign(valid, false)); + gen.try(() => gen.assign(valid, (0, codegen_1._)`${regExpCode}(${schemaCode}, ${u}).test(${data})`), () => gen.assign(valid, false)); cxt.fail$data((0, codegen_1._)`!${valid}`); } else { const regExp = (0, code_1.usePattern)(cxt, schema); @@ -5152,9 +5156,9 @@ var require_pattern = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/validation/limitProperties.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/validation/limitProperties.js var require_limitProperties = __commonJS({ - "node_modules/ajv/dist/vocabularies/validation/limitProperties.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/validation/limitProperties.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var codegen_1 = require_codegen(); @@ -5181,9 +5185,9 @@ var require_limitProperties = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/validation/required.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/validation/required.js var require_required = __commonJS({ - "node_modules/ajv/dist/vocabularies/validation/required.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/validation/required.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var code_1 = require_code2(); @@ -5263,9 +5267,9 @@ var require_required = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/validation/limitItems.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/validation/limitItems.js var require_limitItems = __commonJS({ - "node_modules/ajv/dist/vocabularies/validation/limitItems.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/validation/limitItems.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var codegen_1 = require_codegen(); @@ -5292,9 +5296,9 @@ var require_limitItems = __commonJS({ } }); -// node_modules/ajv/dist/runtime/equal.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/runtime/equal.js var require_equal = __commonJS({ - "node_modules/ajv/dist/runtime/equal.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/runtime/equal.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var equal = require_fast_deep_equal(); @@ -5303,9 +5307,9 @@ var require_equal = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/validation/uniqueItems.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/validation/uniqueItems.js var require_uniqueItems = __commonJS({ - "node_modules/ajv/dist/vocabularies/validation/uniqueItems.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/validation/uniqueItems.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var dataType_1 = require_dataType(); @@ -5370,9 +5374,9 @@ var require_uniqueItems = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/validation/const.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/validation/const.js var require_const = __commonJS({ - "node_modules/ajv/dist/vocabularies/validation/const.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/validation/const.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var codegen_1 = require_codegen(); @@ -5399,9 +5403,9 @@ var require_const = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/validation/enum.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/validation/enum.js var require_enum = __commonJS({ - "node_modules/ajv/dist/vocabularies/validation/enum.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/validation/enum.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var codegen_1 = require_codegen(); @@ -5448,9 +5452,9 @@ var require_enum = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/validation/index.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/validation/index.js var require_validation = __commonJS({ - "node_modules/ajv/dist/vocabularies/validation/index.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/validation/index.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var limitNumber_1 = require_limitNumber(); @@ -5486,9 +5490,9 @@ var require_validation = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/applicator/additionalItems.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/additionalItems.js var require_additionalItems = __commonJS({ - "node_modules/ajv/dist/vocabularies/applicator/additionalItems.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/additionalItems.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.validateAdditionalItems = void 0; @@ -5539,9 +5543,9 @@ var require_additionalItems = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/applicator/items.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/items.js var require_items = __commonJS({ - "node_modules/ajv/dist/vocabularies/applicator/items.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/items.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.validateTuple = void 0; @@ -5583,10 +5587,10 @@ var require_items = __commonJS({ }); function checkStrictTuple(sch) { const { opts, errSchemaPath } = it; - const l = schArr.length; - const fullTuple = l === sch.minItems && (l === sch.maxItems || sch[extraItems] === false); + const l3 = schArr.length; + const fullTuple = l3 === sch.minItems && (l3 === sch.maxItems || sch[extraItems] === false); if (opts.strictTuples && !fullTuple) { - const msg = `"${keyword}" is ${l}-tuple, but minItems or maxItems/${extraItems} are not specified or different at path "${errSchemaPath}"`; + const msg = `"${keyword}" is ${l3}-tuple, but minItems or maxItems/${extraItems} are not specified or different at path "${errSchemaPath}"`; (0, util_1.checkStrictMode)(it, msg, opts.strictTuples); } } @@ -5596,9 +5600,9 @@ var require_items = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/applicator/prefixItems.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/prefixItems.js var require_prefixItems = __commonJS({ - "node_modules/ajv/dist/vocabularies/applicator/prefixItems.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/prefixItems.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var items_1 = require_items(); @@ -5613,9 +5617,9 @@ var require_prefixItems = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/applicator/items2020.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/items2020.js var require_items2020 = __commonJS({ - "node_modules/ajv/dist/vocabularies/applicator/items2020.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/items2020.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var codegen_1 = require_codegen(); @@ -5648,9 +5652,9 @@ var require_items2020 = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/applicator/contains.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/contains.js var require_contains = __commonJS({ - "node_modules/ajv/dist/vocabularies/applicator/contains.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/contains.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var codegen_1 = require_codegen(); @@ -5742,9 +5746,9 @@ var require_contains = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/applicator/dependencies.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/dependencies.js var require_dependencies = __commonJS({ - "node_modules/ajv/dist/vocabularies/applicator/dependencies.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/dependencies.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.validateSchemaDeps = exports.validatePropertyDeps = exports.error = void 0; @@ -5836,9 +5840,9 @@ var require_dependencies = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/applicator/propertyNames.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/propertyNames.js var require_propertyNames = __commonJS({ - "node_modules/ajv/dist/vocabularies/applicator/propertyNames.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/propertyNames.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var codegen_1 = require_codegen(); @@ -5879,9 +5883,9 @@ var require_propertyNames = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/applicator/additionalProperties.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/additionalProperties.js var require_additionalProperties = __commonJS({ - "node_modules/ajv/dist/vocabularies/applicator/additionalProperties.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/additionalProperties.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var code_1 = require_code2(); @@ -5985,9 +5989,9 @@ var require_additionalProperties = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/applicator/properties.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/properties.js var require_properties = __commonJS({ - "node_modules/ajv/dist/vocabularies/applicator/properties.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/properties.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var validate_1 = require_validate(); @@ -6043,9 +6047,9 @@ var require_properties = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/applicator/patternProperties.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/patternProperties.js var require_patternProperties = __commonJS({ - "node_modules/ajv/dist/vocabularies/applicator/patternProperties.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/patternProperties.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var code_1 = require_code2(); @@ -6117,9 +6121,9 @@ var require_patternProperties = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/applicator/not.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/not.js var require_not = __commonJS({ - "node_modules/ajv/dist/vocabularies/applicator/not.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/not.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var util_1 = require_util(); @@ -6148,9 +6152,9 @@ var require_not = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/applicator/anyOf.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/anyOf.js var require_anyOf = __commonJS({ - "node_modules/ajv/dist/vocabularies/applicator/anyOf.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/anyOf.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var code_1 = require_code2(); @@ -6165,9 +6169,9 @@ var require_anyOf = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/applicator/oneOf.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/oneOf.js var require_oneOf = __commonJS({ - "node_modules/ajv/dist/vocabularies/applicator/oneOf.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/oneOf.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var codegen_1 = require_codegen(); @@ -6223,9 +6227,9 @@ var require_oneOf = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/applicator/allOf.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/allOf.js var require_allOf = __commonJS({ - "node_modules/ajv/dist/vocabularies/applicator/allOf.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/allOf.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var util_1 = require_util(); @@ -6250,9 +6254,9 @@ var require_allOf = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/applicator/if.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/if.js var require_if = __commonJS({ - "node_modules/ajv/dist/vocabularies/applicator/if.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/if.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var codegen_1 = require_codegen(); @@ -6319,9 +6323,9 @@ var require_if = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/applicator/thenElse.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/thenElse.js var require_thenElse = __commonJS({ - "node_modules/ajv/dist/vocabularies/applicator/thenElse.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/thenElse.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var util_1 = require_util(); @@ -6337,9 +6341,9 @@ var require_thenElse = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/applicator/index.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/index.js var require_applicator = __commonJS({ - "node_modules/ajv/dist/vocabularies/applicator/index.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/index.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var additionalItems_1 = require_additionalItems(); @@ -6385,9 +6389,9 @@ var require_applicator = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/format/format.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/format/format.js var require_format = __commonJS({ - "node_modules/ajv/dist/vocabularies/format/format.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/format/format.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var codegen_1 = require_codegen(); @@ -6475,9 +6479,9 @@ var require_format = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/format/index.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/format/index.js var require_format2 = __commonJS({ - "node_modules/ajv/dist/vocabularies/format/index.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/format/index.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var format_1 = require_format(); @@ -6486,9 +6490,9 @@ var require_format2 = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/metadata.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/metadata.js var require_metadata = __commonJS({ - "node_modules/ajv/dist/vocabularies/metadata.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/metadata.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.contentVocabulary = exports.metadataVocabulary = void 0; @@ -6509,9 +6513,9 @@ var require_metadata = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/draft7.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/draft7.js var require_draft7 = __commonJS({ - "node_modules/ajv/dist/vocabularies/draft7.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/draft7.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var core_1 = require_core2(); @@ -6531,9 +6535,9 @@ var require_draft7 = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/discriminator/types.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/discriminator/types.js var require_types = __commonJS({ - "node_modules/ajv/dist/vocabularies/discriminator/types.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/discriminator/types.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DiscrError = void 0; @@ -6545,9 +6549,9 @@ var require_types = __commonJS({ } }); -// node_modules/ajv/dist/vocabularies/discriminator/index.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/discriminator/index.js var require_discriminator = __commonJS({ - "node_modules/ajv/dist/vocabularies/discriminator/index.js"(exports) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/discriminator/index.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var codegen_1 = require_codegen(); @@ -6650,9 +6654,9 @@ var require_discriminator = __commonJS({ } }); -// node_modules/ajv/dist/refs/json-schema-draft-07.json +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/refs/json-schema-draft-07.json var require_json_schema_draft_07 = __commonJS({ - "node_modules/ajv/dist/refs/json-schema-draft-07.json"(exports, module) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/refs/json-schema-draft-07.json"(exports, module) { module.exports = { $schema: "http://json-schema.org/draft-07/schema#", $id: "http://json-schema.org/draft-07/schema#", @@ -6807,9 +6811,9 @@ var require_json_schema_draft_07 = __commonJS({ } }); -// node_modules/ajv/dist/ajv.js +// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/ajv.js var require_ajv = __commonJS({ - "node_modules/ajv/dist/ajv.js"(exports, module) { + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/ajv.js"(exports, module) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MissingRefError = exports.ValidationError = exports.CodeGen = exports.Name = exports.nil = exports.stringify = exports.str = exports._ = exports.KeywordCxt = exports.Ajv = void 0; @@ -6843,11 +6847,5821 @@ var require_ajv = __commonJS({ module.exports.Ajv = Ajv2; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = Ajv2; - var validate_1 = require_validate(); + var validate_1 = require_validate(); + Object.defineProperty(exports, "KeywordCxt", { enumerable: true, get: function() { + return validate_1.KeywordCxt; + } }); + var codegen_1 = require_codegen(); + Object.defineProperty(exports, "_", { enumerable: true, get: function() { + return codegen_1._; + } }); + Object.defineProperty(exports, "str", { enumerable: true, get: function() { + return codegen_1.str; + } }); + Object.defineProperty(exports, "stringify", { enumerable: true, get: function() { + return codegen_1.stringify; + } }); + Object.defineProperty(exports, "nil", { enumerable: true, get: function() { + return codegen_1.nil; + } }); + Object.defineProperty(exports, "Name", { enumerable: true, get: function() { + return codegen_1.Name; + } }); + Object.defineProperty(exports, "CodeGen", { enumerable: true, get: function() { + return codegen_1.CodeGen; + } }); + var validation_error_1 = require_validation_error(); + Object.defineProperty(exports, "ValidationError", { enumerable: true, get: function() { + return validation_error_1.default; + } }); + var ref_error_1 = require_ref_error(); + Object.defineProperty(exports, "MissingRefError", { enumerable: true, get: function() { + return ref_error_1.default; + } }); + } +}); + +// node_modules/ajv-formats/dist/formats.js +var require_formats = __commonJS({ + "node_modules/ajv-formats/dist/formats.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.formatNames = exports.fastFormats = exports.fullFormats = void 0; + function fmtDef(validate2, compare) { + return { validate: validate2, compare }; + } + exports.fullFormats = { + // date: http://tools.ietf.org/html/rfc3339#section-5.6 + date: fmtDef(date5, compareDate), + // date-time: http://tools.ietf.org/html/rfc3339#section-5.6 + time: fmtDef(getTime(true), compareTime), + "date-time": fmtDef(getDateTime(true), compareDateTime), + "iso-time": fmtDef(getTime(), compareIsoTime), + "iso-date-time": fmtDef(getDateTime(), compareIsoDateTime), + // duration: https://tools.ietf.org/html/rfc3339#appendix-A + duration: /^P(?!$)((\d+Y)?(\d+M)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+S)?)?|(\d+W)?)$/, + uri, + "uri-reference": /^(?:[a-z][a-z0-9+\-.]*:)?(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'"()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*)?(?:\?(?:[a-z0-9\-._~!$&'"()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'"()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i, + // uri-template: https://tools.ietf.org/html/rfc6570 + "uri-template": /^(?:(?:[^\x00-\x20"'<>%\\^`{|}]|%[0-9a-f]{2})|\{[+#./;?&=,!@|]?(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\*)?(?:,(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\*)?)*\})*$/i, + // For the source: https://gist.github.com/dperini/729294 + // For test cases: https://mathiasbynens.be/demo/url-regex + url: /^(?:https?|ftp):\/\/(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u{00a1}-\u{ffff}]+-)*[a-z0-9\u{00a1}-\u{ffff}]+)(?:\.(?:[a-z0-9\u{00a1}-\u{ffff}]+-)*[a-z0-9\u{00a1}-\u{ffff}]+)*(?:\.(?:[a-z\u{00a1}-\u{ffff}]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?$/iu, + email: /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i, + hostname: /^(?=.{1,253}\.?$)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[-0-9a-z]{0,61}[0-9a-z])?)*\.?$/i, + // optimized https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9780596802837/ch07s16.html + ipv4: /^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/, + ipv6: /^((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))$/i, + regex, + // uuid: http://tools.ietf.org/html/rfc4122 + uuid: /^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i, + // JSON-pointer: https://tools.ietf.org/html/rfc6901 + // uri fragment: https://tools.ietf.org/html/rfc3986#appendix-A + "json-pointer": /^(?:\/(?:[^~/]|~0|~1)*)*$/, + "json-pointer-uri-fragment": /^#(?:\/(?:[a-z0-9_\-.!$&'()*+,;:=@]|%[0-9a-f]{2}|~0|~1)*)*$/i, + // relative JSON-pointer: http://tools.ietf.org/html/draft-luff-relative-json-pointer-00 + "relative-json-pointer": /^(?:0|[1-9][0-9]*)(?:#|(?:\/(?:[^~/]|~0|~1)*)*)$/, + // the following formats are used by the openapi specification: https://spec.openapis.org/oas/v3.0.0#data-types + // byte: https://github.com/miguelmota/is-base64 + byte, + // signed 32 bit integer + int32: { type: "number", validate: validateInt32 }, + // signed 64 bit integer + int64: { type: "number", validate: validateInt64 }, + // C-type float + float: { type: "number", validate: validateNumber }, + // C-type double + double: { type: "number", validate: validateNumber }, + // hint to the UI to hide input strings + password: true, + // unchecked string payload + binary: true + }; + exports.fastFormats = { + ...exports.fullFormats, + date: fmtDef(/^\d\d\d\d-[0-1]\d-[0-3]\d$/, compareDate), + time: fmtDef(/^(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)$/i, compareTime), + "date-time": fmtDef(/^\d\d\d\d-[0-1]\d-[0-3]\dt(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)$/i, compareDateTime), + "iso-time": fmtDef(/^(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)?$/i, compareIsoTime), + "iso-date-time": fmtDef(/^\d\d\d\d-[0-1]\d-[0-3]\d[t\s](?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)?$/i, compareIsoDateTime), + // uri: https://github.com/mafintosh/is-my-json-valid/blob/master/formats.js + uri: /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/)?[^\s]*$/i, + "uri-reference": /^(?:(?:[a-z][a-z0-9+\-.]*:)?\/?\/)?(?:[^\\\s#][^\s#]*)?(?:#[^\\\s]*)?$/i, + // email (sources from jsen validator): + // http://stackoverflow.com/questions/201323/using-a-regular-expression-to-validate-an-email-address#answer-8829363 + // http://www.w3.org/TR/html5/forms.html#valid-e-mail-address (search for 'wilful violation') + email: /^[a-z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/i + }; + exports.formatNames = Object.keys(exports.fullFormats); + function isLeapYear(year) { + return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0); + } + var DATE = /^(\d\d\d\d)-(\d\d)-(\d\d)$/; + var DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + function date5(str) { + const matches = DATE.exec(str); + if (!matches) + return false; + const year = +matches[1]; + const month = +matches[2]; + const day = +matches[3]; + return month >= 1 && month <= 12 && day >= 1 && day <= (month === 2 && isLeapYear(year) ? 29 : DAYS[month]); + } + function compareDate(d1, d2) { + if (!(d1 && d2)) + return void 0; + if (d1 > d2) + return 1; + if (d1 < d2) + return -1; + return 0; + } + var TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i; + function getTime(strictTimeZone) { + return function time3(str) { + const matches = TIME.exec(str); + if (!matches) + return false; + const hr = +matches[1]; + const min = +matches[2]; + const sec = +matches[3]; + const tz = matches[4]; + const tzSign = matches[5] === "-" ? -1 : 1; + const tzH = +(matches[6] || 0); + const tzM = +(matches[7] || 0); + if (tzH > 23 || tzM > 59 || strictTimeZone && !tz) + return false; + if (hr <= 23 && min <= 59 && sec < 60) + return true; + const utcMin = min - tzM * tzSign; + const utcHr = hr - tzH * tzSign - (utcMin < 0 ? 1 : 0); + return (utcHr === 23 || utcHr === -1) && (utcMin === 59 || utcMin === -1) && sec < 61; + }; + } + function compareTime(s1, s2) { + if (!(s1 && s2)) + return void 0; + const t1 = (/* @__PURE__ */ new Date("2020-01-01T" + s1)).valueOf(); + const t2 = (/* @__PURE__ */ new Date("2020-01-01T" + s2)).valueOf(); + if (!(t1 && t2)) + return void 0; + return t1 - t2; + } + function compareIsoTime(t1, t2) { + if (!(t1 && t2)) + return void 0; + const a1 = TIME.exec(t1); + const a2 = TIME.exec(t2); + if (!(a1 && a2)) + return void 0; + t1 = a1[1] + a1[2] + a1[3]; + t2 = a2[1] + a2[2] + a2[3]; + if (t1 > t2) + return 1; + if (t1 < t2) + return -1; + return 0; + } + var DATE_TIME_SEPARATOR = /t|\s/i; + function getDateTime(strictTimeZone) { + const time3 = getTime(strictTimeZone); + return function date_time(str) { + const dateTime = str.split(DATE_TIME_SEPARATOR); + return dateTime.length === 2 && date5(dateTime[0]) && time3(dateTime[1]); + }; + } + function compareDateTime(dt1, dt2) { + if (!(dt1 && dt2)) + return void 0; + const d1 = new Date(dt1).valueOf(); + const d2 = new Date(dt2).valueOf(); + if (!(d1 && d2)) + return void 0; + return d1 - d2; + } + function compareIsoDateTime(dt1, dt2) { + if (!(dt1 && dt2)) + return void 0; + const [d1, t1] = dt1.split(DATE_TIME_SEPARATOR); + const [d2, t2] = dt2.split(DATE_TIME_SEPARATOR); + const res = compareDate(d1, d2); + if (res === void 0) + return void 0; + return res || compareTime(t1, t2); + } + var NOT_URI_FRAGMENT = /\/|:/; + var URI = /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i; + function uri(str) { + return NOT_URI_FRAGMENT.test(str) && URI.test(str); + } + var BYTE = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/gm; + function byte(str) { + BYTE.lastIndex = 0; + return BYTE.test(str); + } + var MIN_INT32 = -(2 ** 31); + var MAX_INT32 = 2 ** 31 - 1; + function validateInt32(value) { + return Number.isInteger(value) && value <= MAX_INT32 && value >= MIN_INT32; + } + function validateInt64(value) { + return Number.isInteger(value); + } + function validateNumber() { + return true; + } + var Z_ANCHOR = /[^\\]\\Z/; + function regex(str) { + if (Z_ANCHOR.test(str)) + return false; + try { + new RegExp(str); + return true; + } catch (e) { + return false; + } + } + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/compile/codegen/code.js +var require_code3 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/compile/codegen/code.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.regexpCode = exports.getEsmExportName = exports.getProperty = exports.safeStringify = exports.stringify = exports.strConcat = exports.addCodeArg = exports.str = exports._ = exports.nil = exports._Code = exports.Name = exports.IDENTIFIER = exports._CodeOrName = void 0; + var _CodeOrName = class { + }; + exports._CodeOrName = _CodeOrName; + exports.IDENTIFIER = /^[a-z$_][a-z$_0-9]*$/i; + var Name = class extends _CodeOrName { + constructor(s) { + super(); + if (!exports.IDENTIFIER.test(s)) + throw new Error("CodeGen: name must be a valid identifier"); + this.str = s; + } + toString() { + return this.str; + } + emptyStr() { + return false; + } + get names() { + return { [this.str]: 1 }; + } + }; + exports.Name = Name; + var _Code = class extends _CodeOrName { + constructor(code) { + super(); + this._items = typeof code === "string" ? [code] : code; + } + toString() { + return this.str; + } + emptyStr() { + if (this._items.length > 1) + return false; + const item = this._items[0]; + return item === "" || item === '""'; + } + get str() { + var _a3; + return (_a3 = this._str) !== null && _a3 !== void 0 ? _a3 : this._str = this._items.reduce((s, c) => `${s}${c}`, ""); + } + get names() { + var _a3; + return (_a3 = this._names) !== null && _a3 !== void 0 ? _a3 : this._names = this._items.reduce((names, c) => { + if (c instanceof Name) + names[c.str] = (names[c.str] || 0) + 1; + return names; + }, {}); + } + }; + exports._Code = _Code; + exports.nil = new _Code(""); + function _2(strs, ...args) { + const code = [strs[0]]; + let i = 0; + while (i < args.length) { + addCodeArg(code, args[i]); + code.push(strs[++i]); + } + return new _Code(code); + } + exports._ = _2; + var plus = new _Code("+"); + function str(strs, ...args) { + const expr = [safeStringify(strs[0])]; + let i = 0; + while (i < args.length) { + expr.push(plus); + addCodeArg(expr, args[i]); + expr.push(plus, safeStringify(strs[++i])); + } + optimize(expr); + return new _Code(expr); + } + exports.str = str; + function addCodeArg(code, arg) { + if (arg instanceof _Code) + code.push(...arg._items); + else if (arg instanceof Name) + code.push(arg); + else + code.push(interpolate(arg)); + } + exports.addCodeArg = addCodeArg; + function optimize(expr) { + let i = 1; + while (i < expr.length - 1) { + if (expr[i] === plus) { + const res = mergeExprItems(expr[i - 1], expr[i + 1]); + if (res !== void 0) { + expr.splice(i - 1, 3, res); + continue; + } + expr[i++] = "+"; + } + i++; + } + } + function mergeExprItems(a, b2) { + if (b2 === '""') + return a; + if (a === '""') + return b2; + if (typeof a == "string") { + if (b2 instanceof Name || a[a.length - 1] !== '"') + return; + if (typeof b2 != "string") + return `${a.slice(0, -1)}${b2}"`; + if (b2[0] === '"') + return a.slice(0, -1) + b2.slice(1); + return; + } + if (typeof b2 == "string" && b2[0] === '"' && !(a instanceof Name)) + return `"${a}${b2.slice(1)}`; + return; + } + function strConcat(c1, c2) { + return c2.emptyStr() ? c1 : c1.emptyStr() ? c2 : str`${c1}${c2}`; + } + exports.strConcat = strConcat; + function interpolate(x2) { + return typeof x2 == "number" || typeof x2 == "boolean" || x2 === null ? x2 : safeStringify(Array.isArray(x2) ? x2.join(",") : x2); + } + function stringify(x2) { + return new _Code(safeStringify(x2)); + } + exports.stringify = stringify; + function safeStringify(x2) { + return JSON.stringify(x2).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029"); + } + exports.safeStringify = safeStringify; + function getProperty(key) { + return typeof key == "string" && exports.IDENTIFIER.test(key) ? new _Code(`.${key}`) : _2`[${key}]`; + } + exports.getProperty = getProperty; + function getEsmExportName(key) { + if (typeof key == "string" && exports.IDENTIFIER.test(key)) { + return new _Code(`${key}`); + } + throw new Error(`CodeGen: invalid export name: ${key}, use explicit $id name mapping`); + } + exports.getEsmExportName = getEsmExportName; + function regexpCode(rx) { + return new _Code(rx.toString()); + } + exports.regexpCode = regexpCode; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/compile/codegen/scope.js +var require_scope2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/compile/codegen/scope.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.ValueScope = exports.ValueScopeName = exports.Scope = exports.varKinds = exports.UsedValueState = void 0; + var code_1 = require_code3(); + var ValueError = class extends Error { + constructor(name) { + super(`CodeGen: "code" for ${name} not defined`); + this.value = name.value; + } + }; + var UsedValueState; + (function(UsedValueState2) { + UsedValueState2[UsedValueState2["Started"] = 0] = "Started"; + UsedValueState2[UsedValueState2["Completed"] = 1] = "Completed"; + })(UsedValueState || (exports.UsedValueState = UsedValueState = {})); + exports.varKinds = { + const: new code_1.Name("const"), + let: new code_1.Name("let"), + var: new code_1.Name("var") + }; + var Scope = class { + constructor({ prefixes, parent } = {}) { + this._names = {}; + this._prefixes = prefixes; + this._parent = parent; + } + toName(nameOrPrefix) { + return nameOrPrefix instanceof code_1.Name ? nameOrPrefix : this.name(nameOrPrefix); + } + name(prefix) { + return new code_1.Name(this._newName(prefix)); + } + _newName(prefix) { + const ng = this._names[prefix] || this._nameGroup(prefix); + return `${prefix}${ng.index++}`; + } + _nameGroup(prefix) { + var _a3, _b; + if (((_b = (_a3 = this._parent) === null || _a3 === void 0 ? void 0 : _a3._prefixes) === null || _b === void 0 ? void 0 : _b.has(prefix)) || this._prefixes && !this._prefixes.has(prefix)) { + throw new Error(`CodeGen: prefix "${prefix}" is not allowed in this scope`); + } + return this._names[prefix] = { prefix, index: 0 }; + } + }; + exports.Scope = Scope; + var ValueScopeName = class extends code_1.Name { + constructor(prefix, nameStr) { + super(nameStr); + this.prefix = prefix; + } + setValue(value, { property, itemIndex }) { + this.value = value; + this.scopePath = (0, code_1._)`.${new code_1.Name(property)}[${itemIndex}]`; + } + }; + exports.ValueScopeName = ValueScopeName; + var line = (0, code_1._)`\n`; + var ValueScope = class extends Scope { + constructor(opts) { + super(opts); + this._values = {}; + this._scope = opts.scope; + this.opts = { ...opts, _n: opts.lines ? line : code_1.nil }; + } + get() { + return this._scope; + } + name(prefix) { + return new ValueScopeName(prefix, this._newName(prefix)); + } + value(nameOrPrefix, value) { + var _a3; + if (value.ref === void 0) + throw new Error("CodeGen: ref must be passed in value"); + const name = this.toName(nameOrPrefix); + const { prefix } = name; + const valueKey = (_a3 = value.key) !== null && _a3 !== void 0 ? _a3 : value.ref; + let vs = this._values[prefix]; + if (vs) { + const _name = vs.get(valueKey); + if (_name) + return _name; + } else { + vs = this._values[prefix] = /* @__PURE__ */ new Map(); + } + vs.set(valueKey, name); + const s = this._scope[prefix] || (this._scope[prefix] = []); + const itemIndex = s.length; + s[itemIndex] = value.ref; + name.setValue(value, { property: prefix, itemIndex }); + return name; + } + getValue(prefix, keyOrRef) { + const vs = this._values[prefix]; + if (!vs) + return; + return vs.get(keyOrRef); + } + scopeRefs(scopeName, values = this._values) { + return this._reduceValues(values, (name) => { + if (name.scopePath === void 0) + throw new Error(`CodeGen: name "${name}" has no value`); + return (0, code_1._)`${scopeName}${name.scopePath}`; + }); + } + scopeCode(values = this._values, usedValues, getCode) { + return this._reduceValues(values, (name) => { + if (name.value === void 0) + throw new Error(`CodeGen: name "${name}" has no value`); + return name.value.code; + }, usedValues, getCode); + } + _reduceValues(values, valueCode, usedValues = {}, getCode) { + let code = code_1.nil; + for (const prefix in values) { + const vs = values[prefix]; + if (!vs) + continue; + const nameSet = usedValues[prefix] = usedValues[prefix] || /* @__PURE__ */ new Map(); + vs.forEach((name) => { + if (nameSet.has(name)) + return; + nameSet.set(name, UsedValueState.Started); + let c = valueCode(name); + if (c) { + const def = this.opts.es5 ? exports.varKinds.var : exports.varKinds.const; + code = (0, code_1._)`${code}${def} ${name} = ${c};${this.opts._n}`; + } else if (c = getCode === null || getCode === void 0 ? void 0 : getCode(name)) { + code = (0, code_1._)`${code}${c}${this.opts._n}`; + } else { + throw new ValueError(name); + } + nameSet.set(name, UsedValueState.Completed); + }); + } + return code; + } + }; + exports.ValueScope = ValueScope; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/compile/codegen/index.js +var require_codegen2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/compile/codegen/index.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.or = exports.and = exports.not = exports.CodeGen = exports.operators = exports.varKinds = exports.ValueScopeName = exports.ValueScope = exports.Scope = exports.Name = exports.regexpCode = exports.stringify = exports.getProperty = exports.nil = exports.strConcat = exports.str = exports._ = void 0; + var code_1 = require_code3(); + var scope_1 = require_scope2(); + var code_2 = require_code3(); + Object.defineProperty(exports, "_", { enumerable: true, get: function() { + return code_2._; + } }); + Object.defineProperty(exports, "str", { enumerable: true, get: function() { + return code_2.str; + } }); + Object.defineProperty(exports, "strConcat", { enumerable: true, get: function() { + return code_2.strConcat; + } }); + Object.defineProperty(exports, "nil", { enumerable: true, get: function() { + return code_2.nil; + } }); + Object.defineProperty(exports, "getProperty", { enumerable: true, get: function() { + return code_2.getProperty; + } }); + Object.defineProperty(exports, "stringify", { enumerable: true, get: function() { + return code_2.stringify; + } }); + Object.defineProperty(exports, "regexpCode", { enumerable: true, get: function() { + return code_2.regexpCode; + } }); + Object.defineProperty(exports, "Name", { enumerable: true, get: function() { + return code_2.Name; + } }); + var scope_2 = require_scope2(); + Object.defineProperty(exports, "Scope", { enumerable: true, get: function() { + return scope_2.Scope; + } }); + Object.defineProperty(exports, "ValueScope", { enumerable: true, get: function() { + return scope_2.ValueScope; + } }); + Object.defineProperty(exports, "ValueScopeName", { enumerable: true, get: function() { + return scope_2.ValueScopeName; + } }); + Object.defineProperty(exports, "varKinds", { enumerable: true, get: function() { + return scope_2.varKinds; + } }); + exports.operators = { + GT: new code_1._Code(">"), + GTE: new code_1._Code(">="), + LT: new code_1._Code("<"), + LTE: new code_1._Code("<="), + EQ: new code_1._Code("==="), + NEQ: new code_1._Code("!=="), + NOT: new code_1._Code("!"), + OR: new code_1._Code("||"), + AND: new code_1._Code("&&"), + ADD: new code_1._Code("+") + }; + var Node = class { + optimizeNodes() { + return this; + } + optimizeNames(_names, _constants) { + return this; + } + }; + var Def = class extends Node { + constructor(varKind, name, rhs) { + super(); + this.varKind = varKind; + this.name = name; + this.rhs = rhs; + } + render({ es5, _n }) { + const varKind = es5 ? scope_1.varKinds.var : this.varKind; + const rhs = this.rhs === void 0 ? "" : ` = ${this.rhs}`; + return `${varKind} ${this.name}${rhs};` + _n; + } + optimizeNames(names, constants) { + if (!names[this.name.str]) + return; + if (this.rhs) + this.rhs = optimizeExpr(this.rhs, names, constants); + return this; + } + get names() { + return this.rhs instanceof code_1._CodeOrName ? this.rhs.names : {}; + } + }; + var Assign = class extends Node { + constructor(lhs, rhs, sideEffects) { + super(); + this.lhs = lhs; + this.rhs = rhs; + this.sideEffects = sideEffects; + } + render({ _n }) { + return `${this.lhs} = ${this.rhs};` + _n; + } + optimizeNames(names, constants) { + if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects) + return; + this.rhs = optimizeExpr(this.rhs, names, constants); + return this; + } + get names() { + const names = this.lhs instanceof code_1.Name ? {} : { ...this.lhs.names }; + return addExprNames(names, this.rhs); + } + }; + var AssignOp = class extends Assign { + constructor(lhs, op, rhs, sideEffects) { + super(lhs, rhs, sideEffects); + this.op = op; + } + render({ _n }) { + return `${this.lhs} ${this.op}= ${this.rhs};` + _n; + } + }; + var Label = class extends Node { + constructor(label) { + super(); + this.label = label; + this.names = {}; + } + render({ _n }) { + return `${this.label}:` + _n; + } + }; + var Break = class extends Node { + constructor(label) { + super(); + this.label = label; + this.names = {}; + } + render({ _n }) { + const label = this.label ? ` ${this.label}` : ""; + return `break${label};` + _n; + } + }; + var Throw = class extends Node { + constructor(error61) { + super(); + this.error = error61; + } + render({ _n }) { + return `throw ${this.error};` + _n; + } + get names() { + return this.error.names; + } + }; + var AnyCode = class extends Node { + constructor(code) { + super(); + this.code = code; + } + render({ _n }) { + return `${this.code};` + _n; + } + optimizeNodes() { + return `${this.code}` ? this : void 0; + } + optimizeNames(names, constants) { + this.code = optimizeExpr(this.code, names, constants); + return this; + } + get names() { + return this.code instanceof code_1._CodeOrName ? this.code.names : {}; + } + }; + var ParentNode = class extends Node { + constructor(nodes = []) { + super(); + this.nodes = nodes; + } + render(opts) { + return this.nodes.reduce((code, n) => code + n.render(opts), ""); + } + optimizeNodes() { + const { nodes } = this; + let i = nodes.length; + while (i--) { + const n = nodes[i].optimizeNodes(); + if (Array.isArray(n)) + nodes.splice(i, 1, ...n); + else if (n) + nodes[i] = n; + else + nodes.splice(i, 1); + } + return nodes.length > 0 ? this : void 0; + } + optimizeNames(names, constants) { + const { nodes } = this; + let i = nodes.length; + while (i--) { + const n = nodes[i]; + if (n.optimizeNames(names, constants)) + continue; + subtractNames(names, n.names); + nodes.splice(i, 1); + } + return nodes.length > 0 ? this : void 0; + } + get names() { + return this.nodes.reduce((names, n) => addNames(names, n.names), {}); + } + }; + var BlockNode = class extends ParentNode { + render(opts) { + return "{" + opts._n + super.render(opts) + "}" + opts._n; + } + }; + var Root = class extends ParentNode { + }; + var Else = class extends BlockNode { + }; + Else.kind = "else"; + var If = class _If extends BlockNode { + constructor(condition, nodes) { + super(nodes); + this.condition = condition; + } + render(opts) { + let code = `if(${this.condition})` + super.render(opts); + if (this.else) + code += "else " + this.else.render(opts); + return code; + } + optimizeNodes() { + super.optimizeNodes(); + const cond = this.condition; + if (cond === true) + return this.nodes; + let e = this.else; + if (e) { + const ns = e.optimizeNodes(); + e = this.else = Array.isArray(ns) ? new Else(ns) : ns; + } + if (e) { + if (cond === false) + return e instanceof _If ? e : e.nodes; + if (this.nodes.length) + return this; + return new _If(not(cond), e instanceof _If ? [e] : e.nodes); + } + if (cond === false || !this.nodes.length) + return void 0; + return this; + } + optimizeNames(names, constants) { + var _a3; + this.else = (_a3 = this.else) === null || _a3 === void 0 ? void 0 : _a3.optimizeNames(names, constants); + if (!(super.optimizeNames(names, constants) || this.else)) + return; + this.condition = optimizeExpr(this.condition, names, constants); + return this; + } + get names() { + const names = super.names; + addExprNames(names, this.condition); + if (this.else) + addNames(names, this.else.names); + return names; + } + }; + If.kind = "if"; + var For = class extends BlockNode { + }; + For.kind = "for"; + var ForLoop = class extends For { + constructor(iteration) { + super(); + this.iteration = iteration; + } + render(opts) { + return `for(${this.iteration})` + super.render(opts); + } + optimizeNames(names, constants) { + if (!super.optimizeNames(names, constants)) + return; + this.iteration = optimizeExpr(this.iteration, names, constants); + return this; + } + get names() { + return addNames(super.names, this.iteration.names); + } + }; + var ForRange = class extends For { + constructor(varKind, name, from, to) { + super(); + this.varKind = varKind; + this.name = name; + this.from = from; + this.to = to; + } + render(opts) { + const varKind = opts.es5 ? scope_1.varKinds.var : this.varKind; + const { name, from, to } = this; + return `for(${varKind} ${name}=${from}; ${name}<${to}; ${name}++)` + super.render(opts); + } + get names() { + const names = addExprNames(super.names, this.from); + return addExprNames(names, this.to); + } + }; + var ForIter = class extends For { + constructor(loop, varKind, name, iterable) { + super(); + this.loop = loop; + this.varKind = varKind; + this.name = name; + this.iterable = iterable; + } + render(opts) { + return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts); + } + optimizeNames(names, constants) { + if (!super.optimizeNames(names, constants)) + return; + this.iterable = optimizeExpr(this.iterable, names, constants); + return this; + } + get names() { + return addNames(super.names, this.iterable.names); + } + }; + var Func = class extends BlockNode { + constructor(name, args, async) { + super(); + this.name = name; + this.args = args; + this.async = async; + } + render(opts) { + const _async = this.async ? "async " : ""; + return `${_async}function ${this.name}(${this.args})` + super.render(opts); + } + }; + Func.kind = "func"; + var Return = class extends ParentNode { + render(opts) { + return "return " + super.render(opts); + } + }; + Return.kind = "return"; + var Try = class extends BlockNode { + render(opts) { + let code = "try" + super.render(opts); + if (this.catch) + code += this.catch.render(opts); + if (this.finally) + code += this.finally.render(opts); + return code; + } + optimizeNodes() { + var _a3, _b; + super.optimizeNodes(); + (_a3 = this.catch) === null || _a3 === void 0 ? void 0 : _a3.optimizeNodes(); + (_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNodes(); + return this; + } + optimizeNames(names, constants) { + var _a3, _b; + super.optimizeNames(names, constants); + (_a3 = this.catch) === null || _a3 === void 0 ? void 0 : _a3.optimizeNames(names, constants); + (_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names, constants); + return this; + } + get names() { + const names = super.names; + if (this.catch) + addNames(names, this.catch.names); + if (this.finally) + addNames(names, this.finally.names); + return names; + } + }; + var Catch = class extends BlockNode { + constructor(error61) { + super(); + this.error = error61; + } + render(opts) { + return `catch(${this.error})` + super.render(opts); + } + }; + Catch.kind = "catch"; + var Finally = class extends BlockNode { + render(opts) { + return "finally" + super.render(opts); + } + }; + Finally.kind = "finally"; + var CodeGen = class { + constructor(extScope, opts = {}) { + this._values = {}; + this._blockStarts = []; + this._constants = {}; + this.opts = { ...opts, _n: opts.lines ? "\n" : "" }; + this._extScope = extScope; + this._scope = new scope_1.Scope({ parent: extScope }); + this._nodes = [new Root()]; + } + toString() { + return this._root.render(this.opts); + } + // returns unique name in the internal scope + name(prefix) { + return this._scope.name(prefix); + } + // reserves unique name in the external scope + scopeName(prefix) { + return this._extScope.name(prefix); + } + // reserves unique name in the external scope and assigns value to it + scopeValue(prefixOrName, value) { + const name = this._extScope.value(prefixOrName, value); + const vs = this._values[name.prefix] || (this._values[name.prefix] = /* @__PURE__ */ new Set()); + vs.add(name); + return name; + } + getScopeValue(prefix, keyOrRef) { + return this._extScope.getValue(prefix, keyOrRef); + } + // return code that assigns values in the external scope to the names that are used internally + // (same names that were returned by gen.scopeName or gen.scopeValue) + scopeRefs(scopeName) { + return this._extScope.scopeRefs(scopeName, this._values); + } + scopeCode() { + return this._extScope.scopeCode(this._values); + } + _def(varKind, nameOrPrefix, rhs, constant) { + const name = this._scope.toName(nameOrPrefix); + if (rhs !== void 0 && constant) + this._constants[name.str] = rhs; + this._leafNode(new Def(varKind, name, rhs)); + return name; + } + // `const` declaration (`var` in es5 mode) + const(nameOrPrefix, rhs, _constant) { + return this._def(scope_1.varKinds.const, nameOrPrefix, rhs, _constant); + } + // `let` declaration with optional assignment (`var` in es5 mode) + let(nameOrPrefix, rhs, _constant) { + return this._def(scope_1.varKinds.let, nameOrPrefix, rhs, _constant); + } + // `var` declaration with optional assignment + var(nameOrPrefix, rhs, _constant) { + return this._def(scope_1.varKinds.var, nameOrPrefix, rhs, _constant); + } + // assignment code + assign(lhs, rhs, sideEffects) { + return this._leafNode(new Assign(lhs, rhs, sideEffects)); + } + // `+=` code + add(lhs, rhs) { + return this._leafNode(new AssignOp(lhs, exports.operators.ADD, rhs)); + } + // appends passed SafeExpr to code or executes Block + code(c) { + if (typeof c == "function") + c(); + else if (c !== code_1.nil) + this._leafNode(new AnyCode(c)); + return this; + } + // returns code for object literal for the passed argument list of key-value pairs + object(...keyValues) { + const code = ["{"]; + for (const [key, value] of keyValues) { + if (code.length > 1) + code.push(","); + code.push(key); + if (key !== value || this.opts.es5) { + code.push(":"); + (0, code_1.addCodeArg)(code, value); + } + } + code.push("}"); + return new code_1._Code(code); + } + // `if` clause (or statement if `thenBody` and, optionally, `elseBody` are passed) + if(condition, thenBody, elseBody) { + this._blockNode(new If(condition)); + if (thenBody && elseBody) { + this.code(thenBody).else().code(elseBody).endIf(); + } else if (thenBody) { + this.code(thenBody).endIf(); + } else if (elseBody) { + throw new Error('CodeGen: "else" body without "then" body'); + } + return this; + } + // `else if` clause - invalid without `if` or after `else` clauses + elseIf(condition) { + return this._elseNode(new If(condition)); + } + // `else` clause - only valid after `if` or `else if` clauses + else() { + return this._elseNode(new Else()); + } + // end `if` statement (needed if gen.if was used only with condition) + endIf() { + return this._endBlockNode(If, Else); + } + _for(node2, forBody) { + this._blockNode(node2); + if (forBody) + this.code(forBody).endFor(); + return this; + } + // a generic `for` clause (or statement if `forBody` is passed) + for(iteration, forBody) { + return this._for(new ForLoop(iteration), forBody); + } + // `for` statement for a range of values + forRange(nameOrPrefix, from, to, forBody, varKind = this.opts.es5 ? scope_1.varKinds.var : scope_1.varKinds.let) { + const name = this._scope.toName(nameOrPrefix); + return this._for(new ForRange(varKind, name, from, to), () => forBody(name)); + } + // `for-of` statement (in es5 mode replace with a normal for loop) + forOf(nameOrPrefix, iterable, forBody, varKind = scope_1.varKinds.const) { + const name = this._scope.toName(nameOrPrefix); + if (this.opts.es5) { + const arr = iterable instanceof code_1.Name ? iterable : this.var("_arr", iterable); + return this.forRange("_i", 0, (0, code_1._)`${arr}.length`, (i) => { + this.var(name, (0, code_1._)`${arr}[${i}]`); + forBody(name); + }); + } + return this._for(new ForIter("of", varKind, name, iterable), () => forBody(name)); + } + // `for-in` statement. + // With option `ownProperties` replaced with a `for-of` loop for object keys + forIn(nameOrPrefix, obj, forBody, varKind = this.opts.es5 ? scope_1.varKinds.var : scope_1.varKinds.const) { + if (this.opts.ownProperties) { + return this.forOf(nameOrPrefix, (0, code_1._)`Object.keys(${obj})`, forBody); + } + const name = this._scope.toName(nameOrPrefix); + return this._for(new ForIter("in", varKind, name, obj), () => forBody(name)); + } + // end `for` loop + endFor() { + return this._endBlockNode(For); + } + // `label` statement + label(label) { + return this._leafNode(new Label(label)); + } + // `break` statement + break(label) { + return this._leafNode(new Break(label)); + } + // `return` statement + return(value) { + const node2 = new Return(); + this._blockNode(node2); + this.code(value); + if (node2.nodes.length !== 1) + throw new Error('CodeGen: "return" should have one node'); + return this._endBlockNode(Return); + } + // `try` statement + try(tryBody, catchCode, finallyCode) { + if (!catchCode && !finallyCode) + throw new Error('CodeGen: "try" without "catch" and "finally"'); + const node2 = new Try(); + this._blockNode(node2); + this.code(tryBody); + if (catchCode) { + const error61 = this.name("e"); + this._currNode = node2.catch = new Catch(error61); + catchCode(error61); + } + if (finallyCode) { + this._currNode = node2.finally = new Finally(); + this.code(finallyCode); + } + return this._endBlockNode(Catch, Finally); + } + // `throw` statement + throw(error61) { + return this._leafNode(new Throw(error61)); + } + // start self-balancing block + block(body, nodeCount) { + this._blockStarts.push(this._nodes.length); + if (body) + this.code(body).endBlock(nodeCount); + return this; + } + // end the current self-balancing block + endBlock(nodeCount) { + const len = this._blockStarts.pop(); + if (len === void 0) + throw new Error("CodeGen: not in self-balancing block"); + const toClose = this._nodes.length - len; + if (toClose < 0 || nodeCount !== void 0 && toClose !== nodeCount) { + throw new Error(`CodeGen: wrong number of nodes: ${toClose} vs ${nodeCount} expected`); + } + this._nodes.length = len; + return this; + } + // `function` heading (or definition if funcBody is passed) + func(name, args = code_1.nil, async, funcBody) { + this._blockNode(new Func(name, args, async)); + if (funcBody) + this.code(funcBody).endFunc(); + return this; + } + // end function definition + endFunc() { + return this._endBlockNode(Func); + } + optimize(n = 1) { + while (n-- > 0) { + this._root.optimizeNodes(); + this._root.optimizeNames(this._root.names, this._constants); + } + } + _leafNode(node2) { + this._currNode.nodes.push(node2); + return this; + } + _blockNode(node2) { + this._currNode.nodes.push(node2); + this._nodes.push(node2); + } + _endBlockNode(N1, N2) { + const n = this._currNode; + if (n instanceof N1 || N2 && n instanceof N2) { + this._nodes.pop(); + return this; + } + throw new Error(`CodeGen: not in block "${N2 ? `${N1.kind}/${N2.kind}` : N1.kind}"`); + } + _elseNode(node2) { + const n = this._currNode; + if (!(n instanceof If)) { + throw new Error('CodeGen: "else" without "if"'); + } + this._currNode = n.else = node2; + return this; + } + get _root() { + return this._nodes[0]; + } + get _currNode() { + const ns = this._nodes; + return ns[ns.length - 1]; + } + set _currNode(node2) { + const ns = this._nodes; + ns[ns.length - 1] = node2; + } + }; + exports.CodeGen = CodeGen; + function addNames(names, from) { + for (const n in from) + names[n] = (names[n] || 0) + (from[n] || 0); + return names; + } + function addExprNames(names, from) { + return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names; + } + function optimizeExpr(expr, names, constants) { + if (expr instanceof code_1.Name) + return replaceName(expr); + if (!canOptimize(expr)) + return expr; + return new code_1._Code(expr._items.reduce((items, c) => { + if (c instanceof code_1.Name) + c = replaceName(c); + if (c instanceof code_1._Code) + items.push(...c._items); + else + items.push(c); + return items; + }, [])); + function replaceName(n) { + const c = constants[n.str]; + if (c === void 0 || names[n.str] !== 1) + return n; + delete names[n.str]; + return c; + } + function canOptimize(e) { + return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants[c.str] !== void 0); + } + } + function subtractNames(names, from) { + for (const n in from) + names[n] = (names[n] || 0) - (from[n] || 0); + } + function not(x2) { + return typeof x2 == "boolean" || typeof x2 == "number" || x2 === null ? !x2 : (0, code_1._)`!${par(x2)}`; + } + exports.not = not; + var andCode = mappend(exports.operators.AND); + function and(...args) { + return args.reduce(andCode); + } + exports.and = and; + var orCode = mappend(exports.operators.OR); + function or(...args) { + return args.reduce(orCode); + } + exports.or = or; + function mappend(op) { + return (x2, y2) => x2 === code_1.nil ? y2 : y2 === code_1.nil ? x2 : (0, code_1._)`${par(x2)} ${op} ${par(y2)}`; + } + function par(x2) { + return x2 instanceof code_1.Name ? x2 : (0, code_1._)`(${x2})`; + } + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/compile/util.js +var require_util2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/compile/util.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.checkStrictMode = exports.getErrorPath = exports.Type = exports.useFunc = exports.setEvaluated = exports.evaluatedPropsToName = exports.mergeEvaluated = exports.eachItem = exports.unescapeJsonPointer = exports.escapeJsonPointer = exports.escapeFragment = exports.unescapeFragment = exports.schemaRefOrVal = exports.schemaHasRulesButRef = exports.schemaHasRules = exports.checkUnknownRules = exports.alwaysValidSchema = exports.toHash = void 0; + var codegen_1 = require_codegen2(); + var code_1 = require_code3(); + function toHash(arr) { + const hash2 = {}; + for (const item of arr) + hash2[item] = true; + return hash2; + } + exports.toHash = toHash; + function alwaysValidSchema(it, schema) { + if (typeof schema == "boolean") + return schema; + if (Object.keys(schema).length === 0) + return true; + checkUnknownRules(it, schema); + return !schemaHasRules(schema, it.self.RULES.all); + } + exports.alwaysValidSchema = alwaysValidSchema; + function checkUnknownRules(it, schema = it.schema) { + const { opts, self } = it; + if (!opts.strictSchema) + return; + if (typeof schema === "boolean") + return; + const rules = self.RULES.keywords; + for (const key in schema) { + if (!rules[key]) + checkStrictMode(it, `unknown keyword: "${key}"`); + } + } + exports.checkUnknownRules = checkUnknownRules; + function schemaHasRules(schema, rules) { + if (typeof schema == "boolean") + return !schema; + for (const key in schema) + if (rules[key]) + return true; + return false; + } + exports.schemaHasRules = schemaHasRules; + function schemaHasRulesButRef(schema, RULES) { + if (typeof schema == "boolean") + return !schema; + for (const key in schema) + if (key !== "$ref" && RULES.all[key]) + return true; + return false; + } + exports.schemaHasRulesButRef = schemaHasRulesButRef; + function schemaRefOrVal({ topSchemaRef, schemaPath }, schema, keyword, $data) { + if (!$data) { + if (typeof schema == "number" || typeof schema == "boolean") + return schema; + if (typeof schema == "string") + return (0, codegen_1._)`${schema}`; + } + return (0, codegen_1._)`${topSchemaRef}${schemaPath}${(0, codegen_1.getProperty)(keyword)}`; + } + exports.schemaRefOrVal = schemaRefOrVal; + function unescapeFragment(str) { + return unescapeJsonPointer(decodeURIComponent(str)); + } + exports.unescapeFragment = unescapeFragment; + function escapeFragment(str) { + return encodeURIComponent(escapeJsonPointer(str)); + } + exports.escapeFragment = escapeFragment; + function escapeJsonPointer(str) { + if (typeof str == "number") + return `${str}`; + return str.replace(/~/g, "~0").replace(/\//g, "~1"); + } + exports.escapeJsonPointer = escapeJsonPointer; + function unescapeJsonPointer(str) { + return str.replace(/~1/g, "/").replace(/~0/g, "~"); + } + exports.unescapeJsonPointer = unescapeJsonPointer; + function eachItem(xs, f) { + if (Array.isArray(xs)) { + for (const x2 of xs) + f(x2); + } else { + f(xs); + } + } + exports.eachItem = eachItem; + function makeMergeEvaluated({ mergeNames, mergeToName, mergeValues: mergeValues2, resultToName }) { + return (gen, from, to, toName) => { + const res = to === void 0 ? from : to instanceof codegen_1.Name ? (from instanceof codegen_1.Name ? mergeNames(gen, from, to) : mergeToName(gen, from, to), to) : from instanceof codegen_1.Name ? (mergeToName(gen, to, from), from) : mergeValues2(from, to); + return toName === codegen_1.Name && !(res instanceof codegen_1.Name) ? resultToName(gen, res) : res; + }; + } + exports.mergeEvaluated = { + props: makeMergeEvaluated({ + mergeNames: (gen, from, to) => gen.if((0, codegen_1._)`${to} !== true && ${from} !== undefined`, () => { + gen.if((0, codegen_1._)`${from} === true`, () => gen.assign(to, true), () => gen.assign(to, (0, codegen_1._)`${to} || {}`).code((0, codegen_1._)`Object.assign(${to}, ${from})`)); + }), + mergeToName: (gen, from, to) => gen.if((0, codegen_1._)`${to} !== true`, () => { + if (from === true) { + gen.assign(to, true); + } else { + gen.assign(to, (0, codegen_1._)`${to} || {}`); + setEvaluated(gen, to, from); + } + }), + mergeValues: (from, to) => from === true ? true : { ...from, ...to }, + resultToName: evaluatedPropsToName + }), + items: makeMergeEvaluated({ + mergeNames: (gen, from, to) => gen.if((0, codegen_1._)`${to} !== true && ${from} !== undefined`, () => gen.assign(to, (0, codegen_1._)`${from} === true ? true : ${to} > ${from} ? ${to} : ${from}`)), + mergeToName: (gen, from, to) => gen.if((0, codegen_1._)`${to} !== true`, () => gen.assign(to, from === true ? true : (0, codegen_1._)`${to} > ${from} ? ${to} : ${from}`)), + mergeValues: (from, to) => from === true ? true : Math.max(from, to), + resultToName: (gen, items) => gen.var("items", items) + }) + }; + function evaluatedPropsToName(gen, ps) { + if (ps === true) + return gen.var("props", true); + const props = gen.var("props", (0, codegen_1._)`{}`); + if (ps !== void 0) + setEvaluated(gen, props, ps); + return props; + } + exports.evaluatedPropsToName = evaluatedPropsToName; + function setEvaluated(gen, props, ps) { + Object.keys(ps).forEach((p) => gen.assign((0, codegen_1._)`${props}${(0, codegen_1.getProperty)(p)}`, true)); + } + exports.setEvaluated = setEvaluated; + var snippets = {}; + function useFunc(gen, f) { + return gen.scopeValue("func", { + ref: f, + code: snippets[f.code] || (snippets[f.code] = new code_1._Code(f.code)) + }); + } + exports.useFunc = useFunc; + var Type; + (function(Type2) { + Type2[Type2["Num"] = 0] = "Num"; + Type2[Type2["Str"] = 1] = "Str"; + })(Type || (exports.Type = Type = {})); + function getErrorPath(dataProp, dataPropType, jsPropertySyntax) { + if (dataProp instanceof codegen_1.Name) { + const isNumber = dataPropType === Type.Num; + return jsPropertySyntax ? isNumber ? (0, codegen_1._)`"[" + ${dataProp} + "]"` : (0, codegen_1._)`"['" + ${dataProp} + "']"` : isNumber ? (0, codegen_1._)`"/" + ${dataProp}` : (0, codegen_1._)`"/" + ${dataProp}.replace(/~/g, "~0").replace(/\\//g, "~1")`; + } + return jsPropertySyntax ? (0, codegen_1.getProperty)(dataProp).toString() : "/" + escapeJsonPointer(dataProp); + } + exports.getErrorPath = getErrorPath; + function checkStrictMode(it, msg, mode = it.opts.strictSchema) { + if (!mode) + return; + msg = `strict mode: ${msg}`; + if (mode === true) + throw new Error(msg); + it.self.logger.warn(msg); + } + exports.checkStrictMode = checkStrictMode; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/compile/names.js +var require_names2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/compile/names.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var codegen_1 = require_codegen2(); + var names = { + // validation function arguments + data: new codegen_1.Name("data"), + // data passed to validation function + // args passed from referencing schema + valCxt: new codegen_1.Name("valCxt"), + // validation/data context - should not be used directly, it is destructured to the names below + instancePath: new codegen_1.Name("instancePath"), + parentData: new codegen_1.Name("parentData"), + parentDataProperty: new codegen_1.Name("parentDataProperty"), + rootData: new codegen_1.Name("rootData"), + // root data - same as the data passed to the first/top validation function + dynamicAnchors: new codegen_1.Name("dynamicAnchors"), + // used to support recursiveRef and dynamicRef + // function scoped variables + vErrors: new codegen_1.Name("vErrors"), + // null or array of validation errors + errors: new codegen_1.Name("errors"), + // counter of validation errors + this: new codegen_1.Name("this"), + // "globals" + self: new codegen_1.Name("self"), + scope: new codegen_1.Name("scope"), + // JTD serialize/parse name for JSON string and position + json: new codegen_1.Name("json"), + jsonPos: new codegen_1.Name("jsonPos"), + jsonLen: new codegen_1.Name("jsonLen"), + jsonPart: new codegen_1.Name("jsonPart") + }; + exports.default = names; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/compile/errors.js +var require_errors2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/compile/errors.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.extendErrors = exports.resetErrorsCount = exports.reportExtraError = exports.reportError = exports.keyword$DataError = exports.keywordError = void 0; + var codegen_1 = require_codegen2(); + var util_1 = require_util2(); + var names_1 = require_names2(); + exports.keywordError = { + message: ({ keyword }) => (0, codegen_1.str)`must pass "${keyword}" keyword validation` + }; + exports.keyword$DataError = { + message: ({ keyword, schemaType }) => schemaType ? (0, codegen_1.str)`"${keyword}" keyword must be ${schemaType} ($data)` : (0, codegen_1.str)`"${keyword}" keyword is invalid ($data)` + }; + function reportError(cxt, error61 = exports.keywordError, errorPaths, overrideAllErrors) { + const { it } = cxt; + const { gen, compositeRule, allErrors } = it; + const errObj = errorObjectCode(cxt, error61, errorPaths); + if (overrideAllErrors !== null && overrideAllErrors !== void 0 ? overrideAllErrors : compositeRule || allErrors) { + addError(gen, errObj); + } else { + returnErrors(it, (0, codegen_1._)`[${errObj}]`); + } + } + exports.reportError = reportError; + function reportExtraError(cxt, error61 = exports.keywordError, errorPaths) { + const { it } = cxt; + const { gen, compositeRule, allErrors } = it; + const errObj = errorObjectCode(cxt, error61, errorPaths); + addError(gen, errObj); + if (!(compositeRule || allErrors)) { + returnErrors(it, names_1.default.vErrors); + } + } + exports.reportExtraError = reportExtraError; + function resetErrorsCount(gen, errsCount) { + gen.assign(names_1.default.errors, errsCount); + gen.if((0, codegen_1._)`${names_1.default.vErrors} !== null`, () => gen.if(errsCount, () => gen.assign((0, codegen_1._)`${names_1.default.vErrors}.length`, errsCount), () => gen.assign(names_1.default.vErrors, null))); + } + exports.resetErrorsCount = resetErrorsCount; + function extendErrors({ gen, keyword, schemaValue, data, errsCount, it }) { + if (errsCount === void 0) + throw new Error("ajv implementation error"); + const err = gen.name("err"); + gen.forRange("i", errsCount, names_1.default.errors, (i) => { + gen.const(err, (0, codegen_1._)`${names_1.default.vErrors}[${i}]`); + gen.if((0, codegen_1._)`${err}.instancePath === undefined`, () => gen.assign((0, codegen_1._)`${err}.instancePath`, (0, codegen_1.strConcat)(names_1.default.instancePath, it.errorPath))); + gen.assign((0, codegen_1._)`${err}.schemaPath`, (0, codegen_1.str)`${it.errSchemaPath}/${keyword}`); + if (it.opts.verbose) { + gen.assign((0, codegen_1._)`${err}.schema`, schemaValue); + gen.assign((0, codegen_1._)`${err}.data`, data); + } + }); + } + exports.extendErrors = extendErrors; + function addError(gen, errObj) { + const err = gen.const("err", errObj); + gen.if((0, codegen_1._)`${names_1.default.vErrors} === null`, () => gen.assign(names_1.default.vErrors, (0, codegen_1._)`[${err}]`), (0, codegen_1._)`${names_1.default.vErrors}.push(${err})`); + gen.code((0, codegen_1._)`${names_1.default.errors}++`); + } + function returnErrors(it, errs) { + const { gen, validateName, schemaEnv } = it; + if (schemaEnv.$async) { + gen.throw((0, codegen_1._)`new ${it.ValidationError}(${errs})`); + } else { + gen.assign((0, codegen_1._)`${validateName}.errors`, errs); + gen.return(false); + } + } + var E2 = { + keyword: new codegen_1.Name("keyword"), + schemaPath: new codegen_1.Name("schemaPath"), + // also used in JTD errors + params: new codegen_1.Name("params"), + propertyName: new codegen_1.Name("propertyName"), + message: new codegen_1.Name("message"), + schema: new codegen_1.Name("schema"), + parentSchema: new codegen_1.Name("parentSchema") + }; + function errorObjectCode(cxt, error61, errorPaths) { + const { createErrors } = cxt.it; + if (createErrors === false) + return (0, codegen_1._)`{}`; + return errorObject(cxt, error61, errorPaths); + } + function errorObject(cxt, error61, errorPaths = {}) { + const { gen, it } = cxt; + const keyValues = [ + errorInstancePath(it, errorPaths), + errorSchemaPath(cxt, errorPaths) + ]; + extraErrorProps(cxt, error61, keyValues); + return gen.object(...keyValues); + } + function errorInstancePath({ errorPath }, { instancePath }) { + const instPath = instancePath ? (0, codegen_1.str)`${errorPath}${(0, util_1.getErrorPath)(instancePath, util_1.Type.Str)}` : errorPath; + return [names_1.default.instancePath, (0, codegen_1.strConcat)(names_1.default.instancePath, instPath)]; + } + function errorSchemaPath({ keyword, it: { errSchemaPath } }, { schemaPath, parentSchema }) { + let schPath = parentSchema ? errSchemaPath : (0, codegen_1.str)`${errSchemaPath}/${keyword}`; + if (schemaPath) { + schPath = (0, codegen_1.str)`${schPath}${(0, util_1.getErrorPath)(schemaPath, util_1.Type.Str)}`; + } + return [E2.schemaPath, schPath]; + } + function extraErrorProps(cxt, { params, message }, keyValues) { + const { keyword, data, schemaValue, it } = cxt; + const { opts, propertyName, topSchemaRef, schemaPath } = it; + keyValues.push([E2.keyword, keyword], [E2.params, typeof params == "function" ? params(cxt) : params || (0, codegen_1._)`{}`]); + if (opts.messages) { + keyValues.push([E2.message, typeof message == "function" ? message(cxt) : message]); + } + if (opts.verbose) { + keyValues.push([E2.schema, schemaValue], [E2.parentSchema, (0, codegen_1._)`${topSchemaRef}${schemaPath}`], [names_1.default.data, data]); + } + if (propertyName) + keyValues.push([E2.propertyName, propertyName]); + } + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/compile/validate/boolSchema.js +var require_boolSchema2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/compile/validate/boolSchema.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.boolOrEmptySchema = exports.topBoolOrEmptySchema = void 0; + var errors_1 = require_errors2(); + var codegen_1 = require_codegen2(); + var names_1 = require_names2(); + var boolError = { + message: "boolean schema is false" + }; + function topBoolOrEmptySchema(it) { + const { gen, schema, validateName } = it; + if (schema === false) { + falseSchemaError(it, false); + } else if (typeof schema == "object" && schema.$async === true) { + gen.return(names_1.default.data); + } else { + gen.assign((0, codegen_1._)`${validateName}.errors`, null); + gen.return(true); + } + } + exports.topBoolOrEmptySchema = topBoolOrEmptySchema; + function boolOrEmptySchema(it, valid) { + const { gen, schema } = it; + if (schema === false) { + gen.var(valid, false); + falseSchemaError(it); + } else { + gen.var(valid, true); + } + } + exports.boolOrEmptySchema = boolOrEmptySchema; + function falseSchemaError(it, overrideAllErrors) { + const { gen, data } = it; + const cxt = { + gen, + keyword: "false schema", + data, + schema: false, + schemaCode: false, + schemaValue: false, + params: {}, + it + }; + (0, errors_1.reportError)(cxt, boolError, void 0, overrideAllErrors); + } + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/compile/rules.js +var require_rules2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/compile/rules.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.getRules = exports.isJSONType = void 0; + var _jsonTypes = ["string", "number", "integer", "boolean", "null", "object", "array"]; + var jsonTypes = new Set(_jsonTypes); + function isJSONType(x2) { + return typeof x2 == "string" && jsonTypes.has(x2); + } + exports.isJSONType = isJSONType; + function getRules() { + const groups = { + number: { type: "number", rules: [] }, + string: { type: "string", rules: [] }, + array: { type: "array", rules: [] }, + object: { type: "object", rules: [] } + }; + return { + types: { ...groups, integer: true, boolean: true, null: true }, + rules: [{ rules: [] }, groups.number, groups.string, groups.array, groups.object], + post: { rules: [] }, + all: {}, + keywords: {} + }; + } + exports.getRules = getRules; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/compile/validate/applicability.js +var require_applicability2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/compile/validate/applicability.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.shouldUseRule = exports.shouldUseGroup = exports.schemaHasRulesForType = void 0; + function schemaHasRulesForType({ schema, self }, type) { + const group = self.RULES.types[type]; + return group && group !== true && shouldUseGroup(schema, group); + } + exports.schemaHasRulesForType = schemaHasRulesForType; + function shouldUseGroup(schema, group) { + return group.rules.some((rule) => shouldUseRule(schema, rule)); + } + exports.shouldUseGroup = shouldUseGroup; + function shouldUseRule(schema, rule) { + var _a3; + return schema[rule.keyword] !== void 0 || ((_a3 = rule.definition.implements) === null || _a3 === void 0 ? void 0 : _a3.some((kwd) => schema[kwd] !== void 0)); + } + exports.shouldUseRule = shouldUseRule; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/compile/validate/dataType.js +var require_dataType2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/compile/validate/dataType.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.reportTypeError = exports.checkDataTypes = exports.checkDataType = exports.coerceAndCheckDataType = exports.getJSONTypes = exports.getSchemaTypes = exports.DataType = void 0; + var rules_1 = require_rules2(); + var applicability_1 = require_applicability2(); + var errors_1 = require_errors2(); + var codegen_1 = require_codegen2(); + var util_1 = require_util2(); + var DataType; + (function(DataType2) { + DataType2[DataType2["Correct"] = 0] = "Correct"; + DataType2[DataType2["Wrong"] = 1] = "Wrong"; + })(DataType || (exports.DataType = DataType = {})); + function getSchemaTypes(schema) { + const types = getJSONTypes(schema.type); + const hasNull = types.includes("null"); + if (hasNull) { + if (schema.nullable === false) + throw new Error("type: null contradicts nullable: false"); + } else { + if (!types.length && schema.nullable !== void 0) { + throw new Error('"nullable" cannot be used without "type"'); + } + if (schema.nullable === true) + types.push("null"); + } + return types; + } + exports.getSchemaTypes = getSchemaTypes; + function getJSONTypes(ts) { + const types = Array.isArray(ts) ? ts : ts ? [ts] : []; + if (types.every(rules_1.isJSONType)) + return types; + throw new Error("type must be JSONType or JSONType[]: " + types.join(",")); + } + exports.getJSONTypes = getJSONTypes; + function coerceAndCheckDataType(it, types) { + const { gen, data, opts } = it; + const coerceTo = coerceToTypes(types, opts.coerceTypes); + const checkTypes = types.length > 0 && !(coerceTo.length === 0 && types.length === 1 && (0, applicability_1.schemaHasRulesForType)(it, types[0])); + if (checkTypes) { + const wrongType = checkDataTypes(types, data, opts.strictNumbers, DataType.Wrong); + gen.if(wrongType, () => { + if (coerceTo.length) + coerceData(it, types, coerceTo); + else + reportTypeError(it); + }); + } + return checkTypes; + } + exports.coerceAndCheckDataType = coerceAndCheckDataType; + var COERCIBLE = /* @__PURE__ */ new Set(["string", "number", "integer", "boolean", "null"]); + function coerceToTypes(types, coerceTypes) { + return coerceTypes ? types.filter((t) => COERCIBLE.has(t) || coerceTypes === "array" && t === "array") : []; + } + function coerceData(it, types, coerceTo) { + const { gen, data, opts } = it; + const dataType = gen.let("dataType", (0, codegen_1._)`typeof ${data}`); + const coerced = gen.let("coerced", (0, codegen_1._)`undefined`); + if (opts.coerceTypes === "array") { + gen.if((0, codegen_1._)`${dataType} == 'object' && Array.isArray(${data}) && ${data}.length == 1`, () => gen.assign(data, (0, codegen_1._)`${data}[0]`).assign(dataType, (0, codegen_1._)`typeof ${data}`).if(checkDataTypes(types, data, opts.strictNumbers), () => gen.assign(coerced, data))); + } + gen.if((0, codegen_1._)`${coerced} !== undefined`); + for (const t of coerceTo) { + if (COERCIBLE.has(t) || t === "array" && opts.coerceTypes === "array") { + coerceSpecificType(t); + } + } + gen.else(); + reportTypeError(it); + gen.endIf(); + gen.if((0, codegen_1._)`${coerced} !== undefined`, () => { + gen.assign(data, coerced); + assignParentData(it, coerced); + }); + function coerceSpecificType(t) { + switch (t) { + case "string": + gen.elseIf((0, codegen_1._)`${dataType} == "number" || ${dataType} == "boolean"`).assign(coerced, (0, codegen_1._)`"" + ${data}`).elseIf((0, codegen_1._)`${data} === null`).assign(coerced, (0, codegen_1._)`""`); + return; + case "number": + gen.elseIf((0, codegen_1._)`${dataType} == "boolean" || ${data} === null + || (${dataType} == "string" && ${data} && ${data} == +${data})`).assign(coerced, (0, codegen_1._)`+${data}`); + return; + case "integer": + gen.elseIf((0, codegen_1._)`${dataType} === "boolean" || ${data} === null + || (${dataType} === "string" && ${data} && ${data} == +${data} && !(${data} % 1))`).assign(coerced, (0, codegen_1._)`+${data}`); + return; + case "boolean": + gen.elseIf((0, codegen_1._)`${data} === "false" || ${data} === 0 || ${data} === null`).assign(coerced, false).elseIf((0, codegen_1._)`${data} === "true" || ${data} === 1`).assign(coerced, true); + return; + case "null": + gen.elseIf((0, codegen_1._)`${data} === "" || ${data} === 0 || ${data} === false`); + gen.assign(coerced, null); + return; + case "array": + gen.elseIf((0, codegen_1._)`${dataType} === "string" || ${dataType} === "number" + || ${dataType} === "boolean" || ${data} === null`).assign(coerced, (0, codegen_1._)`[${data}]`); + } + } + } + function assignParentData({ gen, parentData, parentDataProperty }, expr) { + gen.if((0, codegen_1._)`${parentData} !== undefined`, () => gen.assign((0, codegen_1._)`${parentData}[${parentDataProperty}]`, expr)); + } + function checkDataType(dataType, data, strictNums, correct = DataType.Correct) { + const EQ = correct === DataType.Correct ? codegen_1.operators.EQ : codegen_1.operators.NEQ; + let cond; + switch (dataType) { + case "null": + return (0, codegen_1._)`${data} ${EQ} null`; + case "array": + cond = (0, codegen_1._)`Array.isArray(${data})`; + break; + case "object": + cond = (0, codegen_1._)`${data} && typeof ${data} == "object" && !Array.isArray(${data})`; + break; + case "integer": + cond = numCond((0, codegen_1._)`!(${data} % 1) && !isNaN(${data})`); + break; + case "number": + cond = numCond(); + break; + default: + return (0, codegen_1._)`typeof ${data} ${EQ} ${dataType}`; + } + return correct === DataType.Correct ? cond : (0, codegen_1.not)(cond); + function numCond(_cond = codegen_1.nil) { + return (0, codegen_1.and)((0, codegen_1._)`typeof ${data} == "number"`, _cond, strictNums ? (0, codegen_1._)`isFinite(${data})` : codegen_1.nil); + } + } + exports.checkDataType = checkDataType; + function checkDataTypes(dataTypes, data, strictNums, correct) { + if (dataTypes.length === 1) { + return checkDataType(dataTypes[0], data, strictNums, correct); + } + let cond; + const types = (0, util_1.toHash)(dataTypes); + if (types.array && types.object) { + const notObj = (0, codegen_1._)`typeof ${data} != "object"`; + cond = types.null ? notObj : (0, codegen_1._)`!${data} || ${notObj}`; + delete types.null; + delete types.array; + delete types.object; + } else { + cond = codegen_1.nil; + } + if (types.number) + delete types.integer; + for (const t in types) + cond = (0, codegen_1.and)(cond, checkDataType(t, data, strictNums, correct)); + return cond; + } + exports.checkDataTypes = checkDataTypes; + var typeError = { + message: ({ schema }) => `must be ${schema}`, + params: ({ schema, schemaValue }) => typeof schema == "string" ? (0, codegen_1._)`{type: ${schema}}` : (0, codegen_1._)`{type: ${schemaValue}}` + }; + function reportTypeError(it) { + const cxt = getTypeErrorContext(it); + (0, errors_1.reportError)(cxt, typeError); + } + exports.reportTypeError = reportTypeError; + function getTypeErrorContext(it) { + const { gen, data, schema } = it; + const schemaCode = (0, util_1.schemaRefOrVal)(it, schema, "type"); + return { + gen, + keyword: "type", + data, + schema: schema.type, + schemaCode, + schemaValue: schemaCode, + parentSchema: schema, + params: {}, + it + }; + } + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/compile/validate/defaults.js +var require_defaults2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/compile/validate/defaults.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.assignDefaults = void 0; + var codegen_1 = require_codegen2(); + var util_1 = require_util2(); + function assignDefaults(it, ty) { + const { properties, items } = it.schema; + if (ty === "object" && properties) { + for (const key in properties) { + assignDefault(it, key, properties[key].default); + } + } else if (ty === "array" && Array.isArray(items)) { + items.forEach((sch, i) => assignDefault(it, i, sch.default)); + } + } + exports.assignDefaults = assignDefaults; + function assignDefault(it, prop, defaultValue) { + const { gen, compositeRule, data, opts } = it; + if (defaultValue === void 0) + return; + const childData = (0, codegen_1._)`${data}${(0, codegen_1.getProperty)(prop)}`; + if (compositeRule) { + (0, util_1.checkStrictMode)(it, `default is ignored for: ${childData}`); + return; + } + let condition = (0, codegen_1._)`${childData} === undefined`; + if (opts.useDefaults === "empty") { + condition = (0, codegen_1._)`${condition} || ${childData} === null || ${childData} === ""`; + } + gen.if(condition, (0, codegen_1._)`${childData} = ${(0, codegen_1.stringify)(defaultValue)}`); + } + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/code.js +var require_code4 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/code.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.validateUnion = exports.validateArray = exports.usePattern = exports.callValidateCode = exports.schemaProperties = exports.allSchemaProperties = exports.noPropertyInData = exports.propertyInData = exports.isOwnProperty = exports.hasPropFunc = exports.reportMissingProp = exports.checkMissingProp = exports.checkReportMissingProp = void 0; + var codegen_1 = require_codegen2(); + var util_1 = require_util2(); + var names_1 = require_names2(); + var util_2 = require_util2(); + function checkReportMissingProp(cxt, prop) { + const { gen, data, it } = cxt; + gen.if(noPropertyInData(gen, data, prop, it.opts.ownProperties), () => { + cxt.setParams({ missingProperty: (0, codegen_1._)`${prop}` }, true); + cxt.error(); + }); + } + exports.checkReportMissingProp = checkReportMissingProp; + function checkMissingProp({ gen, data, it: { opts } }, properties, missing) { + return (0, codegen_1.or)(...properties.map((prop) => (0, codegen_1.and)(noPropertyInData(gen, data, prop, opts.ownProperties), (0, codegen_1._)`${missing} = ${prop}`))); + } + exports.checkMissingProp = checkMissingProp; + function reportMissingProp(cxt, missing) { + cxt.setParams({ missingProperty: missing }, true); + cxt.error(); + } + exports.reportMissingProp = reportMissingProp; + function hasPropFunc(gen) { + return gen.scopeValue("func", { + // eslint-disable-next-line @typescript-eslint/unbound-method + ref: Object.prototype.hasOwnProperty, + code: (0, codegen_1._)`Object.prototype.hasOwnProperty` + }); + } + exports.hasPropFunc = hasPropFunc; + function isOwnProperty(gen, data, property) { + return (0, codegen_1._)`${hasPropFunc(gen)}.call(${data}, ${property})`; + } + exports.isOwnProperty = isOwnProperty; + function propertyInData(gen, data, property, ownProperties) { + const cond = (0, codegen_1._)`${data}${(0, codegen_1.getProperty)(property)} !== undefined`; + return ownProperties ? (0, codegen_1._)`${cond} && ${isOwnProperty(gen, data, property)}` : cond; + } + exports.propertyInData = propertyInData; + function noPropertyInData(gen, data, property, ownProperties) { + const cond = (0, codegen_1._)`${data}${(0, codegen_1.getProperty)(property)} === undefined`; + return ownProperties ? (0, codegen_1.or)(cond, (0, codegen_1.not)(isOwnProperty(gen, data, property))) : cond; + } + exports.noPropertyInData = noPropertyInData; + function allSchemaProperties(schemaMap) { + return schemaMap ? Object.keys(schemaMap).filter((p) => p !== "__proto__") : []; + } + exports.allSchemaProperties = allSchemaProperties; + function schemaProperties(it, schemaMap) { + return allSchemaProperties(schemaMap).filter((p) => !(0, util_1.alwaysValidSchema)(it, schemaMap[p])); + } + exports.schemaProperties = schemaProperties; + function callValidateCode({ schemaCode, data, it: { gen, topSchemaRef, schemaPath, errorPath }, it }, func, context, passSchema) { + const dataAndSchema = passSchema ? (0, codegen_1._)`${schemaCode}, ${data}, ${topSchemaRef}${schemaPath}` : data; + const valCxt = [ + [names_1.default.instancePath, (0, codegen_1.strConcat)(names_1.default.instancePath, errorPath)], + [names_1.default.parentData, it.parentData], + [names_1.default.parentDataProperty, it.parentDataProperty], + [names_1.default.rootData, names_1.default.rootData] + ]; + if (it.opts.dynamicRef) + valCxt.push([names_1.default.dynamicAnchors, names_1.default.dynamicAnchors]); + const args = (0, codegen_1._)`${dataAndSchema}, ${gen.object(...valCxt)}`; + return context !== codegen_1.nil ? (0, codegen_1._)`${func}.call(${context}, ${args})` : (0, codegen_1._)`${func}(${args})`; + } + exports.callValidateCode = callValidateCode; + var newRegExp = (0, codegen_1._)`new RegExp`; + function usePattern({ gen, it: { opts } }, pattern) { + const u = opts.unicodeRegExp ? "u" : ""; + const { regExp } = opts.code; + const rx = regExp(pattern, u); + return gen.scopeValue("pattern", { + key: rx.toString(), + ref: rx, + code: (0, codegen_1._)`${regExp.code === "new RegExp" ? newRegExp : (0, util_2.useFunc)(gen, regExp)}(${pattern}, ${u})` + }); + } + exports.usePattern = usePattern; + function validateArray(cxt) { + const { gen, data, keyword, it } = cxt; + const valid = gen.name("valid"); + if (it.allErrors) { + const validArr = gen.let("valid", true); + validateItems(() => gen.assign(validArr, false)); + return validArr; + } + gen.var(valid, true); + validateItems(() => gen.break()); + return valid; + function validateItems(notValid) { + const len = gen.const("len", (0, codegen_1._)`${data}.length`); + gen.forRange("i", 0, len, (i) => { + cxt.subschema({ + keyword, + dataProp: i, + dataPropType: util_1.Type.Num + }, valid); + gen.if((0, codegen_1.not)(valid), notValid); + }); + } + } + exports.validateArray = validateArray; + function validateUnion(cxt) { + const { gen, schema, keyword, it } = cxt; + if (!Array.isArray(schema)) + throw new Error("ajv implementation error"); + const alwaysValid = schema.some((sch) => (0, util_1.alwaysValidSchema)(it, sch)); + if (alwaysValid && !it.opts.unevaluated) + return; + const valid = gen.let("valid", false); + const schValid = gen.name("_valid"); + gen.block(() => schema.forEach((_sch, i) => { + const schCxt = cxt.subschema({ + keyword, + schemaProp: i, + compositeRule: true + }, schValid); + gen.assign(valid, (0, codegen_1._)`${valid} || ${schValid}`); + const merged = cxt.mergeValidEvaluated(schCxt, schValid); + if (!merged) + gen.if((0, codegen_1.not)(valid)); + })); + cxt.result(valid, () => cxt.reset(), () => cxt.error(true)); + } + exports.validateUnion = validateUnion; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/compile/validate/keyword.js +var require_keyword2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/compile/validate/keyword.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.validateKeywordUsage = exports.validSchemaType = exports.funcKeywordCode = exports.macroKeywordCode = void 0; + var codegen_1 = require_codegen2(); + var names_1 = require_names2(); + var code_1 = require_code4(); + var errors_1 = require_errors2(); + function macroKeywordCode(cxt, def) { + const { gen, keyword, schema, parentSchema, it } = cxt; + const macroSchema = def.macro.call(it.self, schema, parentSchema, it); + const schemaRef = useKeyword(gen, keyword, macroSchema); + if (it.opts.validateSchema !== false) + it.self.validateSchema(macroSchema, true); + const valid = gen.name("valid"); + cxt.subschema({ + schema: macroSchema, + schemaPath: codegen_1.nil, + errSchemaPath: `${it.errSchemaPath}/${keyword}`, + topSchemaRef: schemaRef, + compositeRule: true + }, valid); + cxt.pass(valid, () => cxt.error(true)); + } + exports.macroKeywordCode = macroKeywordCode; + function funcKeywordCode(cxt, def) { + var _a3; + const { gen, keyword, schema, parentSchema, $data, it } = cxt; + checkAsyncKeyword(it, def); + const validate2 = !$data && def.compile ? def.compile.call(it.self, schema, parentSchema, it) : def.validate; + const validateRef = useKeyword(gen, keyword, validate2); + const valid = gen.let("valid"); + cxt.block$data(valid, validateKeyword); + cxt.ok((_a3 = def.valid) !== null && _a3 !== void 0 ? _a3 : valid); + function validateKeyword() { + if (def.errors === false) { + assignValid(); + if (def.modifying) + modifyData(cxt); + reportErrs(() => cxt.error()); + } else { + const ruleErrs = def.async ? validateAsync2() : validateSync(); + if (def.modifying) + modifyData(cxt); + reportErrs(() => addErrs(cxt, ruleErrs)); + } + } + function validateAsync2() { + const ruleErrs = gen.let("ruleErrs", null); + gen.try(() => assignValid((0, codegen_1._)`await `), (e) => gen.assign(valid, false).if((0, codegen_1._)`${e} instanceof ${it.ValidationError}`, () => gen.assign(ruleErrs, (0, codegen_1._)`${e}.errors`), () => gen.throw(e))); + return ruleErrs; + } + function validateSync() { + const validateErrs = (0, codegen_1._)`${validateRef}.errors`; + gen.assign(validateErrs, null); + assignValid(codegen_1.nil); + return validateErrs; + } + function assignValid(_await = def.async ? (0, codegen_1._)`await ` : codegen_1.nil) { + const passCxt = it.opts.passContext ? names_1.default.this : names_1.default.self; + const passSchema = !("compile" in def && !$data || def.schema === false); + gen.assign(valid, (0, codegen_1._)`${_await}${(0, code_1.callValidateCode)(cxt, validateRef, passCxt, passSchema)}`, def.modifying); + } + function reportErrs(errors) { + var _a4; + gen.if((0, codegen_1.not)((_a4 = def.valid) !== null && _a4 !== void 0 ? _a4 : valid), errors); + } + } + exports.funcKeywordCode = funcKeywordCode; + function modifyData(cxt) { + const { gen, data, it } = cxt; + gen.if(it.parentData, () => gen.assign(data, (0, codegen_1._)`${it.parentData}[${it.parentDataProperty}]`)); + } + function addErrs(cxt, errs) { + const { gen } = cxt; + gen.if((0, codegen_1._)`Array.isArray(${errs})`, () => { + gen.assign(names_1.default.vErrors, (0, codegen_1._)`${names_1.default.vErrors} === null ? ${errs} : ${names_1.default.vErrors}.concat(${errs})`).assign(names_1.default.errors, (0, codegen_1._)`${names_1.default.vErrors}.length`); + (0, errors_1.extendErrors)(cxt); + }, () => cxt.error()); + } + function checkAsyncKeyword({ schemaEnv }, def) { + if (def.async && !schemaEnv.$async) + throw new Error("async keyword in sync schema"); + } + function useKeyword(gen, keyword, result) { + if (result === void 0) + throw new Error(`keyword "${keyword}" failed to compile`); + return gen.scopeValue("keyword", typeof result == "function" ? { ref: result } : { ref: result, code: (0, codegen_1.stringify)(result) }); + } + function validSchemaType(schema, schemaType, allowUndefined = false) { + return !schemaType.length || schemaType.some((st) => st === "array" ? Array.isArray(schema) : st === "object" ? schema && typeof schema == "object" && !Array.isArray(schema) : typeof schema == st || allowUndefined && typeof schema == "undefined"); + } + exports.validSchemaType = validSchemaType; + function validateKeywordUsage({ schema, opts, self, errSchemaPath }, def, keyword) { + if (Array.isArray(def.keyword) ? !def.keyword.includes(keyword) : def.keyword !== keyword) { + throw new Error("ajv implementation error"); + } + const deps = def.dependencies; + if (deps === null || deps === void 0 ? void 0 : deps.some((kwd) => !Object.prototype.hasOwnProperty.call(schema, kwd))) { + throw new Error(`parent schema must have dependencies of ${keyword}: ${deps.join(",")}`); + } + if (def.validateSchema) { + const valid = def.validateSchema(schema[keyword]); + if (!valid) { + const msg = `keyword "${keyword}" value is invalid at path "${errSchemaPath}": ` + self.errorsText(def.validateSchema.errors); + if (opts.validateSchema === "log") + self.logger.error(msg); + else + throw new Error(msg); + } + } + } + exports.validateKeywordUsage = validateKeywordUsage; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/compile/validate/subschema.js +var require_subschema2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/compile/validate/subschema.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.extendSubschemaMode = exports.extendSubschemaData = exports.getSubschema = void 0; + var codegen_1 = require_codegen2(); + var util_1 = require_util2(); + function getSubschema(it, { keyword, schemaProp, schema, schemaPath, errSchemaPath, topSchemaRef }) { + if (keyword !== void 0 && schema !== void 0) { + throw new Error('both "keyword" and "schema" passed, only one allowed'); + } + if (keyword !== void 0) { + const sch = it.schema[keyword]; + return schemaProp === void 0 ? { + schema: sch, + schemaPath: (0, codegen_1._)`${it.schemaPath}${(0, codegen_1.getProperty)(keyword)}`, + errSchemaPath: `${it.errSchemaPath}/${keyword}` + } : { + schema: sch[schemaProp], + schemaPath: (0, codegen_1._)`${it.schemaPath}${(0, codegen_1.getProperty)(keyword)}${(0, codegen_1.getProperty)(schemaProp)}`, + errSchemaPath: `${it.errSchemaPath}/${keyword}/${(0, util_1.escapeFragment)(schemaProp)}` + }; + } + if (schema !== void 0) { + if (schemaPath === void 0 || errSchemaPath === void 0 || topSchemaRef === void 0) { + throw new Error('"schemaPath", "errSchemaPath" and "topSchemaRef" are required with "schema"'); + } + return { + schema, + schemaPath, + topSchemaRef, + errSchemaPath + }; + } + throw new Error('either "keyword" or "schema" must be passed'); + } + exports.getSubschema = getSubschema; + function extendSubschemaData(subschema, it, { dataProp, dataPropType: dpType, data, dataTypes, propertyName }) { + if (data !== void 0 && dataProp !== void 0) { + throw new Error('both "data" and "dataProp" passed, only one allowed'); + } + const { gen } = it; + if (dataProp !== void 0) { + const { errorPath, dataPathArr, opts } = it; + const nextData = gen.let("data", (0, codegen_1._)`${it.data}${(0, codegen_1.getProperty)(dataProp)}`, true); + dataContextProps(nextData); + subschema.errorPath = (0, codegen_1.str)`${errorPath}${(0, util_1.getErrorPath)(dataProp, dpType, opts.jsPropertySyntax)}`; + subschema.parentDataProperty = (0, codegen_1._)`${dataProp}`; + subschema.dataPathArr = [...dataPathArr, subschema.parentDataProperty]; + } + if (data !== void 0) { + const nextData = data instanceof codegen_1.Name ? data : gen.let("data", data, true); + dataContextProps(nextData); + if (propertyName !== void 0) + subschema.propertyName = propertyName; + } + if (dataTypes) + subschema.dataTypes = dataTypes; + function dataContextProps(_nextData) { + subschema.data = _nextData; + subschema.dataLevel = it.dataLevel + 1; + subschema.dataTypes = []; + it.definedProperties = /* @__PURE__ */ new Set(); + subschema.parentData = it.data; + subschema.dataNames = [...it.dataNames, _nextData]; + } + } + exports.extendSubschemaData = extendSubschemaData; + function extendSubschemaMode(subschema, { jtdDiscriminator, jtdMetadata, compositeRule, createErrors, allErrors }) { + if (compositeRule !== void 0) + subschema.compositeRule = compositeRule; + if (createErrors !== void 0) + subschema.createErrors = createErrors; + if (allErrors !== void 0) + subschema.allErrors = allErrors; + subschema.jtdDiscriminator = jtdDiscriminator; + subschema.jtdMetadata = jtdMetadata; + } + exports.extendSubschemaMode = extendSubschemaMode; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/compile/resolve.js +var require_resolve2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/compile/resolve.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.getSchemaRefs = exports.resolveUrl = exports.normalizeId = exports._getFullPath = exports.getFullPath = exports.inlineRef = void 0; + var util_1 = require_util2(); + var equal = require_fast_deep_equal(); + var traverse = require_json_schema_traverse(); + var SIMPLE_INLINED = /* @__PURE__ */ new Set([ + "type", + "format", + "pattern", + "maxLength", + "minLength", + "maxProperties", + "minProperties", + "maxItems", + "minItems", + "maximum", + "minimum", + "uniqueItems", + "multipleOf", + "required", + "enum", + "const" + ]); + function inlineRef(schema, limit = true) { + if (typeof schema == "boolean") + return true; + if (limit === true) + return !hasRef(schema); + if (!limit) + return false; + return countKeys(schema) <= limit; + } + exports.inlineRef = inlineRef; + var REF_KEYWORDS = /* @__PURE__ */ new Set([ + "$ref", + "$recursiveRef", + "$recursiveAnchor", + "$dynamicRef", + "$dynamicAnchor" + ]); + function hasRef(schema) { + for (const key in schema) { + if (REF_KEYWORDS.has(key)) + return true; + const sch = schema[key]; + if (Array.isArray(sch) && sch.some(hasRef)) + return true; + if (typeof sch == "object" && hasRef(sch)) + return true; + } + return false; + } + function countKeys(schema) { + let count = 0; + for (const key in schema) { + if (key === "$ref") + return Infinity; + count++; + if (SIMPLE_INLINED.has(key)) + continue; + if (typeof schema[key] == "object") { + (0, util_1.eachItem)(schema[key], (sch) => count += countKeys(sch)); + } + if (count === Infinity) + return Infinity; + } + return count; + } + function getFullPath(resolver, id = "", normalize) { + if (normalize !== false) + id = normalizeId(id); + const p = resolver.parse(id); + return _getFullPath(resolver, p); + } + exports.getFullPath = getFullPath; + function _getFullPath(resolver, p) { + const serialized = resolver.serialize(p); + return serialized.split("#")[0] + "#"; + } + exports._getFullPath = _getFullPath; + var TRAILING_SLASH_HASH = /#\/?$/; + function normalizeId(id) { + return id ? id.replace(TRAILING_SLASH_HASH, "") : ""; + } + exports.normalizeId = normalizeId; + function resolveUrl(resolver, baseId, id) { + id = normalizeId(id); + return resolver.resolve(baseId, id); + } + exports.resolveUrl = resolveUrl; + var ANCHOR = /^[a-z_][-a-z0-9._]*$/i; + function getSchemaRefs(schema, baseId) { + if (typeof schema == "boolean") + return {}; + const { schemaId, uriResolver } = this.opts; + const schId = normalizeId(schema[schemaId] || baseId); + const baseIds = { "": schId }; + const pathPrefix = getFullPath(uriResolver, schId, false); + const localRefs = {}; + const schemaRefs = /* @__PURE__ */ new Set(); + traverse(schema, { allKeys: true }, (sch, jsonPtr, _2, parentJsonPtr) => { + if (parentJsonPtr === void 0) + return; + const fullPath = pathPrefix + jsonPtr; + let innerBaseId = baseIds[parentJsonPtr]; + if (typeof sch[schemaId] == "string") + innerBaseId = addRef.call(this, sch[schemaId]); + addAnchor.call(this, sch.$anchor); + addAnchor.call(this, sch.$dynamicAnchor); + baseIds[jsonPtr] = innerBaseId; + function addRef(ref) { + const _resolve = this.opts.uriResolver.resolve; + ref = normalizeId(innerBaseId ? _resolve(innerBaseId, ref) : ref); + if (schemaRefs.has(ref)) + throw ambiguos(ref); + schemaRefs.add(ref); + let schOrRef = this.refs[ref]; + if (typeof schOrRef == "string") + schOrRef = this.refs[schOrRef]; + if (typeof schOrRef == "object") { + checkAmbiguosRef(sch, schOrRef.schema, ref); + } else if (ref !== normalizeId(fullPath)) { + if (ref[0] === "#") { + checkAmbiguosRef(sch, localRefs[ref], ref); + localRefs[ref] = sch; + } else { + this.refs[ref] = fullPath; + } + } + return ref; + } + function addAnchor(anchor2) { + if (typeof anchor2 == "string") { + if (!ANCHOR.test(anchor2)) + throw new Error(`invalid anchor "${anchor2}"`); + addRef.call(this, `#${anchor2}`); + } + } + }); + return localRefs; + function checkAmbiguosRef(sch1, sch2, ref) { + if (sch2 !== void 0 && !equal(sch1, sch2)) + throw ambiguos(ref); + } + function ambiguos(ref) { + return new Error(`reference "${ref}" resolves to more than one schema`); + } + } + exports.getSchemaRefs = getSchemaRefs; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/compile/validate/index.js +var require_validate2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/compile/validate/index.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.getData = exports.KeywordCxt = exports.validateFunctionCode = void 0; + var boolSchema_1 = require_boolSchema2(); + var dataType_1 = require_dataType2(); + var applicability_1 = require_applicability2(); + var dataType_2 = require_dataType2(); + var defaults_1 = require_defaults2(); + var keyword_1 = require_keyword2(); + var subschema_1 = require_subschema2(); + var codegen_1 = require_codegen2(); + var names_1 = require_names2(); + var resolve_1 = require_resolve2(); + var util_1 = require_util2(); + var errors_1 = require_errors2(); + function validateFunctionCode(it) { + if (isSchemaObj(it)) { + checkKeywords(it); + if (schemaCxtHasRules(it)) { + topSchemaObjCode(it); + return; + } + } + validateFunction(it, () => (0, boolSchema_1.topBoolOrEmptySchema)(it)); + } + exports.validateFunctionCode = validateFunctionCode; + function validateFunction({ gen, validateName, schema, schemaEnv, opts }, body) { + if (opts.code.es5) { + gen.func(validateName, (0, codegen_1._)`${names_1.default.data}, ${names_1.default.valCxt}`, schemaEnv.$async, () => { + gen.code((0, codegen_1._)`"use strict"; ${funcSourceUrl(schema, opts)}`); + destructureValCxtES5(gen, opts); + gen.code(body); + }); + } else { + gen.func(validateName, (0, codegen_1._)`${names_1.default.data}, ${destructureValCxt(opts)}`, schemaEnv.$async, () => gen.code(funcSourceUrl(schema, opts)).code(body)); + } + } + function destructureValCxt(opts) { + return (0, codegen_1._)`{${names_1.default.instancePath}="", ${names_1.default.parentData}, ${names_1.default.parentDataProperty}, ${names_1.default.rootData}=${names_1.default.data}${opts.dynamicRef ? (0, codegen_1._)`, ${names_1.default.dynamicAnchors}={}` : codegen_1.nil}}={}`; + } + function destructureValCxtES5(gen, opts) { + gen.if(names_1.default.valCxt, () => { + gen.var(names_1.default.instancePath, (0, codegen_1._)`${names_1.default.valCxt}.${names_1.default.instancePath}`); + gen.var(names_1.default.parentData, (0, codegen_1._)`${names_1.default.valCxt}.${names_1.default.parentData}`); + gen.var(names_1.default.parentDataProperty, (0, codegen_1._)`${names_1.default.valCxt}.${names_1.default.parentDataProperty}`); + gen.var(names_1.default.rootData, (0, codegen_1._)`${names_1.default.valCxt}.${names_1.default.rootData}`); + if (opts.dynamicRef) + gen.var(names_1.default.dynamicAnchors, (0, codegen_1._)`${names_1.default.valCxt}.${names_1.default.dynamicAnchors}`); + }, () => { + gen.var(names_1.default.instancePath, (0, codegen_1._)`""`); + gen.var(names_1.default.parentData, (0, codegen_1._)`undefined`); + gen.var(names_1.default.parentDataProperty, (0, codegen_1._)`undefined`); + gen.var(names_1.default.rootData, names_1.default.data); + if (opts.dynamicRef) + gen.var(names_1.default.dynamicAnchors, (0, codegen_1._)`{}`); + }); + } + function topSchemaObjCode(it) { + const { schema, opts, gen } = it; + validateFunction(it, () => { + if (opts.$comment && schema.$comment) + commentKeyword(it); + checkNoDefault(it); + gen.let(names_1.default.vErrors, null); + gen.let(names_1.default.errors, 0); + if (opts.unevaluated) + resetEvaluated(it); + typeAndKeywords(it); + returnResults(it); + }); + return; + } + function resetEvaluated(it) { + const { gen, validateName } = it; + it.evaluated = gen.const("evaluated", (0, codegen_1._)`${validateName}.evaluated`); + gen.if((0, codegen_1._)`${it.evaluated}.dynamicProps`, () => gen.assign((0, codegen_1._)`${it.evaluated}.props`, (0, codegen_1._)`undefined`)); + gen.if((0, codegen_1._)`${it.evaluated}.dynamicItems`, () => gen.assign((0, codegen_1._)`${it.evaluated}.items`, (0, codegen_1._)`undefined`)); + } + function funcSourceUrl(schema, opts) { + const schId = typeof schema == "object" && schema[opts.schemaId]; + return schId && (opts.code.source || opts.code.process) ? (0, codegen_1._)`/*# sourceURL=${schId} */` : codegen_1.nil; + } + function subschemaCode(it, valid) { + if (isSchemaObj(it)) { + checkKeywords(it); + if (schemaCxtHasRules(it)) { + subSchemaObjCode(it, valid); + return; + } + } + (0, boolSchema_1.boolOrEmptySchema)(it, valid); + } + function schemaCxtHasRules({ schema, self }) { + if (typeof schema == "boolean") + return !schema; + for (const key in schema) + if (self.RULES.all[key]) + return true; + return false; + } + function isSchemaObj(it) { + return typeof it.schema != "boolean"; + } + function subSchemaObjCode(it, valid) { + const { schema, gen, opts } = it; + if (opts.$comment && schema.$comment) + commentKeyword(it); + updateContext(it); + checkAsyncSchema(it); + const errsCount = gen.const("_errs", names_1.default.errors); + typeAndKeywords(it, errsCount); + gen.var(valid, (0, codegen_1._)`${errsCount} === ${names_1.default.errors}`); + } + function checkKeywords(it) { + (0, util_1.checkUnknownRules)(it); + checkRefsAndKeywords(it); + } + function typeAndKeywords(it, errsCount) { + if (it.opts.jtd) + return schemaKeywords(it, [], false, errsCount); + const types = (0, dataType_1.getSchemaTypes)(it.schema); + const checkedTypes = (0, dataType_1.coerceAndCheckDataType)(it, types); + schemaKeywords(it, types, !checkedTypes, errsCount); + } + function checkRefsAndKeywords(it) { + const { schema, errSchemaPath, opts, self } = it; + if (schema.$ref && opts.ignoreKeywordsWithRef && (0, util_1.schemaHasRulesButRef)(schema, self.RULES)) { + self.logger.warn(`$ref: keywords ignored in schema at path "${errSchemaPath}"`); + } + } + function checkNoDefault(it) { + const { schema, opts } = it; + if (schema.default !== void 0 && opts.useDefaults && opts.strictSchema) { + (0, util_1.checkStrictMode)(it, "default is ignored in the schema root"); + } + } + function updateContext(it) { + const schId = it.schema[it.opts.schemaId]; + if (schId) + it.baseId = (0, resolve_1.resolveUrl)(it.opts.uriResolver, it.baseId, schId); + } + function checkAsyncSchema(it) { + if (it.schema.$async && !it.schemaEnv.$async) + throw new Error("async schema in sync schema"); + } + function commentKeyword({ gen, schemaEnv, schema, errSchemaPath, opts }) { + const msg = schema.$comment; + if (opts.$comment === true) { + gen.code((0, codegen_1._)`${names_1.default.self}.logger.log(${msg})`); + } else if (typeof opts.$comment == "function") { + const schemaPath = (0, codegen_1.str)`${errSchemaPath}/$comment`; + const rootName = gen.scopeValue("root", { ref: schemaEnv.root }); + gen.code((0, codegen_1._)`${names_1.default.self}.opts.$comment(${msg}, ${schemaPath}, ${rootName}.schema)`); + } + } + function returnResults(it) { + const { gen, schemaEnv, validateName, ValidationError, opts } = it; + if (schemaEnv.$async) { + gen.if((0, codegen_1._)`${names_1.default.errors} === 0`, () => gen.return(names_1.default.data), () => gen.throw((0, codegen_1._)`new ${ValidationError}(${names_1.default.vErrors})`)); + } else { + gen.assign((0, codegen_1._)`${validateName}.errors`, names_1.default.vErrors); + if (opts.unevaluated) + assignEvaluated(it); + gen.return((0, codegen_1._)`${names_1.default.errors} === 0`); + } + } + function assignEvaluated({ gen, evaluated, props, items }) { + if (props instanceof codegen_1.Name) + gen.assign((0, codegen_1._)`${evaluated}.props`, props); + if (items instanceof codegen_1.Name) + gen.assign((0, codegen_1._)`${evaluated}.items`, items); + } + function schemaKeywords(it, types, typeErrors, errsCount) { + const { gen, schema, data, allErrors, opts, self } = it; + const { RULES } = self; + if (schema.$ref && (opts.ignoreKeywordsWithRef || !(0, util_1.schemaHasRulesButRef)(schema, RULES))) { + gen.block(() => keywordCode(it, "$ref", RULES.all.$ref.definition)); + return; + } + if (!opts.jtd) + checkStrictTypes(it, types); + gen.block(() => { + for (const group of RULES.rules) + groupKeywords(group); + groupKeywords(RULES.post); + }); + function groupKeywords(group) { + if (!(0, applicability_1.shouldUseGroup)(schema, group)) + return; + if (group.type) { + gen.if((0, dataType_2.checkDataType)(group.type, data, opts.strictNumbers)); + iterateKeywords(it, group); + if (types.length === 1 && types[0] === group.type && typeErrors) { + gen.else(); + (0, dataType_2.reportTypeError)(it); + } + gen.endIf(); + } else { + iterateKeywords(it, group); + } + if (!allErrors) + gen.if((0, codegen_1._)`${names_1.default.errors} === ${errsCount || 0}`); + } + } + function iterateKeywords(it, group) { + const { gen, schema, opts: { useDefaults } } = it; + if (useDefaults) + (0, defaults_1.assignDefaults)(it, group.type); + gen.block(() => { + for (const rule of group.rules) { + if ((0, applicability_1.shouldUseRule)(schema, rule)) { + keywordCode(it, rule.keyword, rule.definition, group.type); + } + } + }); + } + function checkStrictTypes(it, types) { + if (it.schemaEnv.meta || !it.opts.strictTypes) + return; + checkContextTypes(it, types); + if (!it.opts.allowUnionTypes) + checkMultipleTypes(it, types); + checkKeywordTypes(it, it.dataTypes); + } + function checkContextTypes(it, types) { + if (!types.length) + return; + if (!it.dataTypes.length) { + it.dataTypes = types; + return; + } + types.forEach((t) => { + if (!includesType(it.dataTypes, t)) { + strictTypesError(it, `type "${t}" not allowed by context "${it.dataTypes.join(",")}"`); + } + }); + narrowSchemaTypes(it, types); + } + function checkMultipleTypes(it, ts) { + if (ts.length > 1 && !(ts.length === 2 && ts.includes("null"))) { + strictTypesError(it, "use allowUnionTypes to allow union type keyword"); + } + } + function checkKeywordTypes(it, ts) { + const rules = it.self.RULES.all; + for (const keyword in rules) { + const rule = rules[keyword]; + if (typeof rule == "object" && (0, applicability_1.shouldUseRule)(it.schema, rule)) { + const { type } = rule.definition; + if (type.length && !type.some((t) => hasApplicableType(ts, t))) { + strictTypesError(it, `missing type "${type.join(",")}" for keyword "${keyword}"`); + } + } + } + } + function hasApplicableType(schTs, kwdT) { + return schTs.includes(kwdT) || kwdT === "number" && schTs.includes("integer"); + } + function includesType(ts, t) { + return ts.includes(t) || t === "integer" && ts.includes("number"); + } + function narrowSchemaTypes(it, withTypes) { + const ts = []; + for (const t of it.dataTypes) { + if (includesType(withTypes, t)) + ts.push(t); + else if (withTypes.includes("integer") && t === "number") + ts.push("integer"); + } + it.dataTypes = ts; + } + function strictTypesError(it, msg) { + const schemaPath = it.schemaEnv.baseId + it.errSchemaPath; + msg += ` at "${schemaPath}" (strictTypes)`; + (0, util_1.checkStrictMode)(it, msg, it.opts.strictTypes); + } + var KeywordCxt = class { + constructor(it, def, keyword) { + (0, keyword_1.validateKeywordUsage)(it, def, keyword); + this.gen = it.gen; + this.allErrors = it.allErrors; + this.keyword = keyword; + this.data = it.data; + this.schema = it.schema[keyword]; + this.$data = def.$data && it.opts.$data && this.schema && this.schema.$data; + this.schemaValue = (0, util_1.schemaRefOrVal)(it, this.schema, keyword, this.$data); + this.schemaType = def.schemaType; + this.parentSchema = it.schema; + this.params = {}; + this.it = it; + this.def = def; + if (this.$data) { + this.schemaCode = it.gen.const("vSchema", getData(this.$data, it)); + } else { + this.schemaCode = this.schemaValue; + if (!(0, keyword_1.validSchemaType)(this.schema, def.schemaType, def.allowUndefined)) { + throw new Error(`${keyword} value must be ${JSON.stringify(def.schemaType)}`); + } + } + if ("code" in def ? def.trackErrors : def.errors !== false) { + this.errsCount = it.gen.const("_errs", names_1.default.errors); + } + } + result(condition, successAction, failAction) { + this.failResult((0, codegen_1.not)(condition), successAction, failAction); + } + failResult(condition, successAction, failAction) { + this.gen.if(condition); + if (failAction) + failAction(); + else + this.error(); + if (successAction) { + this.gen.else(); + successAction(); + if (this.allErrors) + this.gen.endIf(); + } else { + if (this.allErrors) + this.gen.endIf(); + else + this.gen.else(); + } + } + pass(condition, failAction) { + this.failResult((0, codegen_1.not)(condition), void 0, failAction); + } + fail(condition) { + if (condition === void 0) { + this.error(); + if (!this.allErrors) + this.gen.if(false); + return; + } + this.gen.if(condition); + this.error(); + if (this.allErrors) + this.gen.endIf(); + else + this.gen.else(); + } + fail$data(condition) { + if (!this.$data) + return this.fail(condition); + const { schemaCode } = this; + this.fail((0, codegen_1._)`${schemaCode} !== undefined && (${(0, codegen_1.or)(this.invalid$data(), condition)})`); + } + error(append, errorParams, errorPaths) { + if (errorParams) { + this.setParams(errorParams); + this._error(append, errorPaths); + this.setParams({}); + return; + } + this._error(append, errorPaths); + } + _error(append, errorPaths) { + ; + (append ? errors_1.reportExtraError : errors_1.reportError)(this, this.def.error, errorPaths); + } + $dataError() { + (0, errors_1.reportError)(this, this.def.$dataError || errors_1.keyword$DataError); + } + reset() { + if (this.errsCount === void 0) + throw new Error('add "trackErrors" to keyword definition'); + (0, errors_1.resetErrorsCount)(this.gen, this.errsCount); + } + ok(cond) { + if (!this.allErrors) + this.gen.if(cond); + } + setParams(obj, assign) { + if (assign) + Object.assign(this.params, obj); + else + this.params = obj; + } + block$data(valid, codeBlock, $dataValid = codegen_1.nil) { + this.gen.block(() => { + this.check$data(valid, $dataValid); + codeBlock(); + }); + } + check$data(valid = codegen_1.nil, $dataValid = codegen_1.nil) { + if (!this.$data) + return; + const { gen, schemaCode, schemaType, def } = this; + gen.if((0, codegen_1.or)((0, codegen_1._)`${schemaCode} === undefined`, $dataValid)); + if (valid !== codegen_1.nil) + gen.assign(valid, true); + if (schemaType.length || def.validateSchema) { + gen.elseIf(this.invalid$data()); + this.$dataError(); + if (valid !== codegen_1.nil) + gen.assign(valid, false); + } + gen.else(); + } + invalid$data() { + const { gen, schemaCode, schemaType, def, it } = this; + return (0, codegen_1.or)(wrong$DataType(), invalid$DataSchema()); + function wrong$DataType() { + if (schemaType.length) { + if (!(schemaCode instanceof codegen_1.Name)) + throw new Error("ajv implementation error"); + const st = Array.isArray(schemaType) ? schemaType : [schemaType]; + return (0, codegen_1._)`${(0, dataType_2.checkDataTypes)(st, schemaCode, it.opts.strictNumbers, dataType_2.DataType.Wrong)}`; + } + return codegen_1.nil; + } + function invalid$DataSchema() { + if (def.validateSchema) { + const validateSchemaRef = gen.scopeValue("validate$data", { ref: def.validateSchema }); + return (0, codegen_1._)`!${validateSchemaRef}(${schemaCode})`; + } + return codegen_1.nil; + } + } + subschema(appl, valid) { + const subschema = (0, subschema_1.getSubschema)(this.it, appl); + (0, subschema_1.extendSubschemaData)(subschema, this.it, appl); + (0, subschema_1.extendSubschemaMode)(subschema, appl); + const nextContext = { ...this.it, ...subschema, items: void 0, props: void 0 }; + subschemaCode(nextContext, valid); + return nextContext; + } + mergeEvaluated(schemaCxt, toName) { + const { it, gen } = this; + if (!it.opts.unevaluated) + return; + if (it.props !== true && schemaCxt.props !== void 0) { + it.props = util_1.mergeEvaluated.props(gen, schemaCxt.props, it.props, toName); + } + if (it.items !== true && schemaCxt.items !== void 0) { + it.items = util_1.mergeEvaluated.items(gen, schemaCxt.items, it.items, toName); + } + } + mergeValidEvaluated(schemaCxt, valid) { + const { it, gen } = this; + if (it.opts.unevaluated && (it.props !== true || it.items !== true)) { + gen.if(valid, () => this.mergeEvaluated(schemaCxt, codegen_1.Name)); + return true; + } + } + }; + exports.KeywordCxt = KeywordCxt; + function keywordCode(it, keyword, def, ruleType) { + const cxt = new KeywordCxt(it, def, keyword); + if ("code" in def) { + def.code(cxt, ruleType); + } else if (cxt.$data && def.validate) { + (0, keyword_1.funcKeywordCode)(cxt, def); + } else if ("macro" in def) { + (0, keyword_1.macroKeywordCode)(cxt, def); + } else if (def.compile || def.validate) { + (0, keyword_1.funcKeywordCode)(cxt, def); + } + } + var JSON_POINTER = /^\/(?:[^~]|~0|~1)*$/; + var RELATIVE_JSON_POINTER = /^([0-9]+)(#|\/(?:[^~]|~0|~1)*)?$/; + function getData($data, { dataLevel, dataNames, dataPathArr }) { + let jsonPointer; + let data; + if ($data === "") + return names_1.default.rootData; + if ($data[0] === "/") { + if (!JSON_POINTER.test($data)) + throw new Error(`Invalid JSON-pointer: ${$data}`); + jsonPointer = $data; + data = names_1.default.rootData; + } else { + const matches = RELATIVE_JSON_POINTER.exec($data); + if (!matches) + throw new Error(`Invalid JSON-pointer: ${$data}`); + const up = +matches[1]; + jsonPointer = matches[2]; + if (jsonPointer === "#") { + if (up >= dataLevel) + throw new Error(errorMsg("property/index", up)); + return dataPathArr[dataLevel - up]; + } + if (up > dataLevel) + throw new Error(errorMsg("data", up)); + data = dataNames[dataLevel - up]; + if (!jsonPointer) + return data; + } + let expr = data; + const segments = jsonPointer.split("/"); + for (const segment of segments) { + if (segment) { + data = (0, codegen_1._)`${data}${(0, codegen_1.getProperty)((0, util_1.unescapeJsonPointer)(segment))}`; + expr = (0, codegen_1._)`${expr} && ${data}`; + } + } + return expr; + function errorMsg(pointerType, up) { + return `Cannot access ${pointerType} ${up} levels up, current level is ${dataLevel}`; + } + } + exports.getData = getData; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/runtime/validation_error.js +var require_validation_error2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/runtime/validation_error.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var ValidationError = class extends Error { + constructor(errors) { + super("validation failed"); + this.errors = errors; + this.ajv = this.validation = true; + } + }; + exports.default = ValidationError; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/compile/ref_error.js +var require_ref_error2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/compile/ref_error.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var resolve_1 = require_resolve2(); + var MissingRefError = class extends Error { + constructor(resolver, baseId, ref, msg) { + super(msg || `can't resolve reference ${ref} from id ${baseId}`); + this.missingRef = (0, resolve_1.resolveUrl)(resolver, baseId, ref); + this.missingSchema = (0, resolve_1.normalizeId)((0, resolve_1.getFullPath)(resolver, this.missingRef)); + } + }; + exports.default = MissingRefError; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/compile/index.js +var require_compile2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/compile/index.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.resolveSchema = exports.getCompilingSchema = exports.resolveRef = exports.compileSchema = exports.SchemaEnv = void 0; + var codegen_1 = require_codegen2(); + var validation_error_1 = require_validation_error2(); + var names_1 = require_names2(); + var resolve_1 = require_resolve2(); + var util_1 = require_util2(); + var validate_1 = require_validate2(); + var SchemaEnv = class { + constructor(env) { + var _a3; + this.refs = {}; + this.dynamicAnchors = {}; + let schema; + if (typeof env.schema == "object") + schema = env.schema; + this.schema = env.schema; + this.schemaId = env.schemaId; + this.root = env.root || this; + this.baseId = (_a3 = env.baseId) !== null && _a3 !== void 0 ? _a3 : (0, resolve_1.normalizeId)(schema === null || schema === void 0 ? void 0 : schema[env.schemaId || "$id"]); + this.schemaPath = env.schemaPath; + this.localRefs = env.localRefs; + this.meta = env.meta; + this.$async = schema === null || schema === void 0 ? void 0 : schema.$async; + this.refs = {}; + } + }; + exports.SchemaEnv = SchemaEnv; + function compileSchema(sch) { + const _sch = getCompilingSchema.call(this, sch); + if (_sch) + return _sch; + const rootId = (0, resolve_1.getFullPath)(this.opts.uriResolver, sch.root.baseId); + const { es5, lines } = this.opts.code; + const { ownProperties } = this.opts; + const gen = new codegen_1.CodeGen(this.scope, { es5, lines, ownProperties }); + let _ValidationError; + if (sch.$async) { + _ValidationError = gen.scopeValue("Error", { + ref: validation_error_1.default, + code: (0, codegen_1._)`require("ajv/dist/runtime/validation_error").default` + }); + } + const validateName = gen.scopeName("validate"); + sch.validateName = validateName; + const schemaCxt = { + gen, + allErrors: this.opts.allErrors, + data: names_1.default.data, + parentData: names_1.default.parentData, + parentDataProperty: names_1.default.parentDataProperty, + dataNames: [names_1.default.data], + dataPathArr: [codegen_1.nil], + // TODO can its length be used as dataLevel if nil is removed? + dataLevel: 0, + dataTypes: [], + definedProperties: /* @__PURE__ */ new Set(), + topSchemaRef: gen.scopeValue("schema", this.opts.code.source === true ? { ref: sch.schema, code: (0, codegen_1.stringify)(sch.schema) } : { ref: sch.schema }), + validateName, + ValidationError: _ValidationError, + schema: sch.schema, + schemaEnv: sch, + rootId, + baseId: sch.baseId || rootId, + schemaPath: codegen_1.nil, + errSchemaPath: sch.schemaPath || (this.opts.jtd ? "" : "#"), + errorPath: (0, codegen_1._)`""`, + opts: this.opts, + self: this + }; + let sourceCode; + try { + this._compilations.add(sch); + (0, validate_1.validateFunctionCode)(schemaCxt); + gen.optimize(this.opts.code.optimize); + const validateCode = gen.toString(); + sourceCode = `${gen.scopeRefs(names_1.default.scope)}return ${validateCode}`; + if (this.opts.code.process) + sourceCode = this.opts.code.process(sourceCode, sch); + const makeValidate = new Function(`${names_1.default.self}`, `${names_1.default.scope}`, sourceCode); + const validate2 = makeValidate(this, this.scope.get()); + this.scope.value(validateName, { ref: validate2 }); + validate2.errors = null; + validate2.schema = sch.schema; + validate2.schemaEnv = sch; + if (sch.$async) + validate2.$async = true; + if (this.opts.code.source === true) { + validate2.source = { validateName, validateCode, scopeValues: gen._values }; + } + if (this.opts.unevaluated) { + const { props, items } = schemaCxt; + validate2.evaluated = { + props: props instanceof codegen_1.Name ? void 0 : props, + items: items instanceof codegen_1.Name ? void 0 : items, + dynamicProps: props instanceof codegen_1.Name, + dynamicItems: items instanceof codegen_1.Name + }; + if (validate2.source) + validate2.source.evaluated = (0, codegen_1.stringify)(validate2.evaluated); + } + sch.validate = validate2; + return sch; + } catch (e) { + delete sch.validate; + delete sch.validateName; + if (sourceCode) + this.logger.error("Error compiling schema, function code:", sourceCode); + throw e; + } finally { + this._compilations.delete(sch); + } + } + exports.compileSchema = compileSchema; + function resolveRef2(root, baseId, ref) { + var _a3; + ref = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, ref); + const schOrFunc = root.refs[ref]; + if (schOrFunc) + return schOrFunc; + let _sch = resolve.call(this, root, ref); + if (_sch === void 0) { + const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref]; + const { schemaId } = this.opts; + if (schema) + _sch = new SchemaEnv({ schema, schemaId, root, baseId }); + } + if (_sch === void 0) + return; + return root.refs[ref] = inlineOrCompile.call(this, _sch); + } + exports.resolveRef = resolveRef2; + function inlineOrCompile(sch) { + if ((0, resolve_1.inlineRef)(sch.schema, this.opts.inlineRefs)) + return sch.schema; + return sch.validate ? sch : compileSchema.call(this, sch); + } + function getCompilingSchema(schEnv) { + for (const sch of this._compilations) { + if (sameSchemaEnv(sch, schEnv)) + return sch; + } + } + exports.getCompilingSchema = getCompilingSchema; + function sameSchemaEnv(s1, s2) { + return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId; + } + function resolve(root, ref) { + let sch; + while (typeof (sch = this.refs[ref]) == "string") + ref = sch; + return sch || this.schemas[ref] || resolveSchema.call(this, root, ref); + } + function resolveSchema(root, ref) { + const p = this.opts.uriResolver.parse(ref); + const refPath = (0, resolve_1._getFullPath)(this.opts.uriResolver, p); + let baseId = (0, resolve_1.getFullPath)(this.opts.uriResolver, root.baseId, void 0); + if (Object.keys(root.schema).length > 0 && refPath === baseId) { + return getJsonPointer.call(this, p, root); + } + const id = (0, resolve_1.normalizeId)(refPath); + const schOrRef = this.refs[id] || this.schemas[id]; + if (typeof schOrRef == "string") { + const sch = resolveSchema.call(this, root, schOrRef); + if (typeof (sch === null || sch === void 0 ? void 0 : sch.schema) !== "object") + return; + return getJsonPointer.call(this, p, sch); + } + if (typeof (schOrRef === null || schOrRef === void 0 ? void 0 : schOrRef.schema) !== "object") + return; + if (!schOrRef.validate) + compileSchema.call(this, schOrRef); + if (id === (0, resolve_1.normalizeId)(ref)) { + const { schema } = schOrRef; + const { schemaId } = this.opts; + const schId = schema[schemaId]; + if (schId) + baseId = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, schId); + return new SchemaEnv({ schema, schemaId, root, baseId }); + } + return getJsonPointer.call(this, p, schOrRef); + } + exports.resolveSchema = resolveSchema; + var PREVENT_SCOPE_CHANGE = /* @__PURE__ */ new Set([ + "properties", + "patternProperties", + "enum", + "dependencies", + "definitions" + ]); + function getJsonPointer(parsedRef, { baseId, schema, root }) { + var _a3; + if (((_a3 = parsedRef.fragment) === null || _a3 === void 0 ? void 0 : _a3[0]) !== "/") + return; + for (const part of parsedRef.fragment.slice(1).split("/")) { + if (typeof schema === "boolean") + return; + const partSchema = schema[(0, util_1.unescapeFragment)(part)]; + if (partSchema === void 0) + return; + schema = partSchema; + const schId = typeof schema === "object" && schema[this.opts.schemaId]; + if (!PREVENT_SCOPE_CHANGE.has(part) && schId) { + baseId = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, schId); + } + } + let env; + if (typeof schema != "boolean" && schema.$ref && !(0, util_1.schemaHasRulesButRef)(schema, this.RULES)) { + const $ref = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, schema.$ref); + env = resolveSchema.call(this, root, $ref); + } + const { schemaId } = this.opts; + env = env || new SchemaEnv({ schema, schemaId, root, baseId }); + if (env.schema !== env.root.schema) + return env; + return void 0; + } + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/refs/data.json +var require_data2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/refs/data.json"(exports, module) { + module.exports = { + $id: "https://raw.githubusercontent.com/ajv-validator/ajv/master/lib/refs/data.json#", + description: "Meta-schema for $data reference (JSON AnySchema extension proposal)", + type: "object", + required: ["$data"], + properties: { + $data: { + type: "string", + anyOf: [{ format: "relative-json-pointer" }, { format: "json-pointer" }] + } + }, + additionalProperties: false + }; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/runtime/uri.js +var require_uri2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/runtime/uri.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var uri = require_fast_uri(); + uri.code = 'require("ajv/dist/runtime/uri").default'; + exports.default = uri; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/core.js +var require_core3 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/core.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.CodeGen = exports.Name = exports.nil = exports.stringify = exports.str = exports._ = exports.KeywordCxt = void 0; + var validate_1 = require_validate2(); + Object.defineProperty(exports, "KeywordCxt", { enumerable: true, get: function() { + return validate_1.KeywordCxt; + } }); + var codegen_1 = require_codegen2(); + Object.defineProperty(exports, "_", { enumerable: true, get: function() { + return codegen_1._; + } }); + Object.defineProperty(exports, "str", { enumerable: true, get: function() { + return codegen_1.str; + } }); + Object.defineProperty(exports, "stringify", { enumerable: true, get: function() { + return codegen_1.stringify; + } }); + Object.defineProperty(exports, "nil", { enumerable: true, get: function() { + return codegen_1.nil; + } }); + Object.defineProperty(exports, "Name", { enumerable: true, get: function() { + return codegen_1.Name; + } }); + Object.defineProperty(exports, "CodeGen", { enumerable: true, get: function() { + return codegen_1.CodeGen; + } }); + var validation_error_1 = require_validation_error2(); + var ref_error_1 = require_ref_error2(); + var rules_1 = require_rules2(); + var compile_1 = require_compile2(); + var codegen_2 = require_codegen2(); + var resolve_1 = require_resolve2(); + var dataType_1 = require_dataType2(); + var util_1 = require_util2(); + var $dataRefSchema = require_data2(); + var uri_1 = require_uri2(); + var defaultRegExp = (str, flags) => new RegExp(str, flags); + defaultRegExp.code = "new RegExp"; + var META_IGNORE_OPTIONS = ["removeAdditional", "useDefaults", "coerceTypes"]; + var EXT_SCOPE_NAMES = /* @__PURE__ */ new Set([ + "validate", + "serialize", + "parse", + "wrapper", + "root", + "schema", + "keyword", + "pattern", + "formats", + "validate$data", + "func", + "obj", + "Error" + ]); + var removedOptions = { + errorDataPath: "", + format: "`validateFormats: false` can be used instead.", + nullable: '"nullable" keyword is supported by default.', + jsonPointers: "Deprecated jsPropertySyntax can be used instead.", + extendRefs: "Deprecated ignoreKeywordsWithRef can be used instead.", + missingRefs: "Pass empty schema with $id that should be ignored to ajv.addSchema.", + processCode: "Use option `code: {process: (code, schemaEnv: object) => string}`", + sourceCode: "Use option `code: {source: true}`", + strictDefaults: "It is default now, see option `strict`.", + strictKeywords: "It is default now, see option `strict`.", + uniqueItems: '"uniqueItems" keyword is always validated.', + unknownFormats: "Disable strict mode or pass `true` to `ajv.addFormat` (or `formats` option).", + cache: "Map is used as cache, schema object as key.", + serialize: "Map is used as cache, schema object as key.", + ajvErrors: "It is default now." + }; + var deprecatedOptions = { + ignoreKeywordsWithRef: "", + jsPropertySyntax: "", + unicode: '"minLength"/"maxLength" account for unicode characters by default.' + }; + var MAX_EXPRESSION = 200; + function requiredOptions(o) { + var _a3, _b, _c, _d, _e2, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0; + const s = o.strict; + const _optz = (_a3 = o.code) === null || _a3 === void 0 ? void 0 : _a3.optimize; + const optimize = _optz === true || _optz === void 0 ? 1 : _optz || 0; + const regExp = (_c = (_b = o.code) === null || _b === void 0 ? void 0 : _b.regExp) !== null && _c !== void 0 ? _c : defaultRegExp; + const uriResolver = (_d = o.uriResolver) !== null && _d !== void 0 ? _d : uri_1.default; + return { + strictSchema: (_f = (_e2 = o.strictSchema) !== null && _e2 !== void 0 ? _e2 : s) !== null && _f !== void 0 ? _f : true, + strictNumbers: (_h = (_g = o.strictNumbers) !== null && _g !== void 0 ? _g : s) !== null && _h !== void 0 ? _h : true, + strictTypes: (_k = (_j = o.strictTypes) !== null && _j !== void 0 ? _j : s) !== null && _k !== void 0 ? _k : "log", + strictTuples: (_m = (_l = o.strictTuples) !== null && _l !== void 0 ? _l : s) !== null && _m !== void 0 ? _m : "log", + strictRequired: (_p = (_o = o.strictRequired) !== null && _o !== void 0 ? _o : s) !== null && _p !== void 0 ? _p : false, + code: o.code ? { ...o.code, optimize, regExp } : { optimize, regExp }, + loopRequired: (_q = o.loopRequired) !== null && _q !== void 0 ? _q : MAX_EXPRESSION, + loopEnum: (_r = o.loopEnum) !== null && _r !== void 0 ? _r : MAX_EXPRESSION, + meta: (_s = o.meta) !== null && _s !== void 0 ? _s : true, + messages: (_t = o.messages) !== null && _t !== void 0 ? _t : true, + inlineRefs: (_u = o.inlineRefs) !== null && _u !== void 0 ? _u : true, + schemaId: (_v = o.schemaId) !== null && _v !== void 0 ? _v : "$id", + addUsedSchema: (_w = o.addUsedSchema) !== null && _w !== void 0 ? _w : true, + validateSchema: (_x = o.validateSchema) !== null && _x !== void 0 ? _x : true, + validateFormats: (_y = o.validateFormats) !== null && _y !== void 0 ? _y : true, + unicodeRegExp: (_z = o.unicodeRegExp) !== null && _z !== void 0 ? _z : true, + int32range: (_0 = o.int32range) !== null && _0 !== void 0 ? _0 : true, + uriResolver + }; + } + var Ajv2 = class { + constructor(opts = {}) { + this.schemas = {}; + this.refs = {}; + this.formats = /* @__PURE__ */ Object.create(null); + this._compilations = /* @__PURE__ */ new Set(); + this._loading = {}; + this._cache = /* @__PURE__ */ new Map(); + opts = this.opts = { ...opts, ...requiredOptions(opts) }; + const { es5, lines } = this.opts.code; + this.scope = new codegen_2.ValueScope({ scope: {}, prefixes: EXT_SCOPE_NAMES, es5, lines }); + this.logger = getLogger(opts.logger); + const formatOpt = opts.validateFormats; + opts.validateFormats = false; + this.RULES = (0, rules_1.getRules)(); + checkOptions.call(this, removedOptions, opts, "NOT SUPPORTED"); + checkOptions.call(this, deprecatedOptions, opts, "DEPRECATED", "warn"); + this._metaOpts = getMetaSchemaOptions.call(this); + if (opts.formats) + addInitialFormats.call(this); + this._addVocabularies(); + this._addDefaultMetaSchema(); + if (opts.keywords) + addInitialKeywords.call(this, opts.keywords); + if (typeof opts.meta == "object") + this.addMetaSchema(opts.meta); + addInitialSchemas.call(this); + opts.validateFormats = formatOpt; + } + _addVocabularies() { + this.addKeyword("$async"); + } + _addDefaultMetaSchema() { + const { $data, meta: meta3, schemaId } = this.opts; + let _dataRefSchema = $dataRefSchema; + if (schemaId === "id") { + _dataRefSchema = { ...$dataRefSchema }; + _dataRefSchema.id = _dataRefSchema.$id; + delete _dataRefSchema.$id; + } + if (meta3 && $data) + this.addMetaSchema(_dataRefSchema, _dataRefSchema[schemaId], false); + } + defaultMeta() { + const { meta: meta3, schemaId } = this.opts; + return this.opts.defaultMeta = typeof meta3 == "object" ? meta3[schemaId] || meta3 : void 0; + } + validate(schemaKeyRef, data) { + let v2; + if (typeof schemaKeyRef == "string") { + v2 = this.getSchema(schemaKeyRef); + if (!v2) + throw new Error(`no schema with key or ref "${schemaKeyRef}"`); + } else { + v2 = this.compile(schemaKeyRef); + } + const valid = v2(data); + if (!("$async" in v2)) + this.errors = v2.errors; + return valid; + } + compile(schema, _meta) { + const sch = this._addSchema(schema, _meta); + return sch.validate || this._compileSchemaEnv(sch); + } + compileAsync(schema, meta3) { + if (typeof this.opts.loadSchema != "function") { + throw new Error("options.loadSchema should be a function"); + } + const { loadSchema } = this.opts; + return runCompileAsync.call(this, schema, meta3); + async function runCompileAsync(_schema, _meta) { + await loadMetaSchema.call(this, _schema.$schema); + const sch = this._addSchema(_schema, _meta); + return sch.validate || _compileAsync.call(this, sch); + } + async function loadMetaSchema($ref) { + if ($ref && !this.getSchema($ref)) { + await runCompileAsync.call(this, { $ref }, true); + } + } + async function _compileAsync(sch) { + try { + return this._compileSchemaEnv(sch); + } catch (e) { + if (!(e instanceof ref_error_1.default)) + throw e; + checkLoaded.call(this, e); + await loadMissingSchema.call(this, e.missingSchema); + return _compileAsync.call(this, sch); + } + } + function checkLoaded({ missingSchema: ref, missingRef }) { + if (this.refs[ref]) { + throw new Error(`AnySchema ${ref} is loaded but ${missingRef} cannot be resolved`); + } + } + async function loadMissingSchema(ref) { + const _schema = await _loadSchema.call(this, ref); + if (!this.refs[ref]) + await loadMetaSchema.call(this, _schema.$schema); + if (!this.refs[ref]) + this.addSchema(_schema, ref, meta3); + } + async function _loadSchema(ref) { + const p = this._loading[ref]; + if (p) + return p; + try { + return await (this._loading[ref] = loadSchema(ref)); + } finally { + delete this._loading[ref]; + } + } + } + // Adds schema to the instance + addSchema(schema, key, _meta, _validateSchema = this.opts.validateSchema) { + if (Array.isArray(schema)) { + for (const sch of schema) + this.addSchema(sch, void 0, _meta, _validateSchema); + return this; + } + let id; + if (typeof schema === "object") { + const { schemaId } = this.opts; + id = schema[schemaId]; + if (id !== void 0 && typeof id != "string") { + throw new Error(`schema ${schemaId} must be string`); + } + } + key = (0, resolve_1.normalizeId)(key || id); + this._checkUnique(key); + this.schemas[key] = this._addSchema(schema, _meta, key, _validateSchema, true); + return this; + } + // Add schema that will be used to validate other schemas + // options in META_IGNORE_OPTIONS are alway set to false + addMetaSchema(schema, key, _validateSchema = this.opts.validateSchema) { + this.addSchema(schema, key, true, _validateSchema); + return this; + } + // Validate schema against its meta-schema + validateSchema(schema, throwOrLogError) { + if (typeof schema == "boolean") + return true; + let $schema; + $schema = schema.$schema; + if ($schema !== void 0 && typeof $schema != "string") { + throw new Error("$schema must be a string"); + } + $schema = $schema || this.opts.defaultMeta || this.defaultMeta(); + if (!$schema) { + this.logger.warn("meta-schema not available"); + this.errors = null; + return true; + } + const valid = this.validate($schema, schema); + if (!valid && throwOrLogError) { + const message = "schema is invalid: " + this.errorsText(); + if (this.opts.validateSchema === "log") + this.logger.error(message); + else + throw new Error(message); + } + return valid; + } + // Get compiled schema by `key` or `ref`. + // (`key` that was passed to `addSchema` or full schema reference - `schema.$id` or resolved id) + getSchema(keyRef) { + let sch; + while (typeof (sch = getSchEnv.call(this, keyRef)) == "string") + keyRef = sch; + if (sch === void 0) { + const { schemaId } = this.opts; + const root = new compile_1.SchemaEnv({ schema: {}, schemaId }); + sch = compile_1.resolveSchema.call(this, root, keyRef); + if (!sch) + return; + this.refs[keyRef] = sch; + } + return sch.validate || this._compileSchemaEnv(sch); + } + // Remove cached schema(s). + // If no parameter is passed all schemas but meta-schemas are removed. + // If RegExp is passed all schemas with key/id matching pattern but meta-schemas are removed. + // Even if schema is referenced by other schemas it still can be removed as other schemas have local references. + removeSchema(schemaKeyRef) { + if (schemaKeyRef instanceof RegExp) { + this._removeAllSchemas(this.schemas, schemaKeyRef); + this._removeAllSchemas(this.refs, schemaKeyRef); + return this; + } + switch (typeof schemaKeyRef) { + case "undefined": + this._removeAllSchemas(this.schemas); + this._removeAllSchemas(this.refs); + this._cache.clear(); + return this; + case "string": { + const sch = getSchEnv.call(this, schemaKeyRef); + if (typeof sch == "object") + this._cache.delete(sch.schema); + delete this.schemas[schemaKeyRef]; + delete this.refs[schemaKeyRef]; + return this; + } + case "object": { + const cacheKey = schemaKeyRef; + this._cache.delete(cacheKey); + let id = schemaKeyRef[this.opts.schemaId]; + if (id) { + id = (0, resolve_1.normalizeId)(id); + delete this.schemas[id]; + delete this.refs[id]; + } + return this; + } + default: + throw new Error("ajv.removeSchema: invalid parameter"); + } + } + // add "vocabulary" - a collection of keywords + addVocabulary(definitions) { + for (const def of definitions) + this.addKeyword(def); + return this; + } + addKeyword(kwdOrDef, def) { + let keyword; + if (typeof kwdOrDef == "string") { + keyword = kwdOrDef; + if (typeof def == "object") { + this.logger.warn("these parameters are deprecated, see docs for addKeyword"); + def.keyword = keyword; + } + } else if (typeof kwdOrDef == "object" && def === void 0) { + def = kwdOrDef; + keyword = def.keyword; + if (Array.isArray(keyword) && !keyword.length) { + throw new Error("addKeywords: keyword must be string or non-empty array"); + } + } else { + throw new Error("invalid addKeywords parameters"); + } + checkKeyword.call(this, keyword, def); + if (!def) { + (0, util_1.eachItem)(keyword, (kwd) => addRule.call(this, kwd)); + return this; + } + keywordMetaschema.call(this, def); + const definition = { + ...def, + type: (0, dataType_1.getJSONTypes)(def.type), + schemaType: (0, dataType_1.getJSONTypes)(def.schemaType) + }; + (0, util_1.eachItem)(keyword, definition.type.length === 0 ? (k2) => addRule.call(this, k2, definition) : (k2) => definition.type.forEach((t) => addRule.call(this, k2, definition, t))); + return this; + } + getKeyword(keyword) { + const rule = this.RULES.all[keyword]; + return typeof rule == "object" ? rule.definition : !!rule; + } + // Remove keyword + removeKeyword(keyword) { + const { RULES } = this; + delete RULES.keywords[keyword]; + delete RULES.all[keyword]; + for (const group of RULES.rules) { + const i = group.rules.findIndex((rule) => rule.keyword === keyword); + if (i >= 0) + group.rules.splice(i, 1); + } + return this; + } + // Add format + addFormat(name, format) { + if (typeof format == "string") + format = new RegExp(format); + this.formats[name] = format; + return this; + } + errorsText(errors = this.errors, { separator = ", ", dataVar = "data" } = {}) { + if (!errors || errors.length === 0) + return "No errors"; + return errors.map((e) => `${dataVar}${e.instancePath} ${e.message}`).reduce((text, msg) => text + separator + msg); + } + $dataMetaSchema(metaSchema, keywordsJsonPointers) { + const rules = this.RULES.all; + metaSchema = JSON.parse(JSON.stringify(metaSchema)); + for (const jsonPointer of keywordsJsonPointers) { + const segments = jsonPointer.split("/").slice(1); + let keywords = metaSchema; + for (const seg of segments) + keywords = keywords[seg]; + for (const key in rules) { + const rule = rules[key]; + if (typeof rule != "object") + continue; + const { $data } = rule.definition; + const schema = keywords[key]; + if ($data && schema) + keywords[key] = schemaOrData(schema); + } + } + return metaSchema; + } + _removeAllSchemas(schemas, regex) { + for (const keyRef in schemas) { + const sch = schemas[keyRef]; + if (!regex || regex.test(keyRef)) { + if (typeof sch == "string") { + delete schemas[keyRef]; + } else if (sch && !sch.meta) { + this._cache.delete(sch.schema); + delete schemas[keyRef]; + } + } + } + } + _addSchema(schema, meta3, baseId, validateSchema = this.opts.validateSchema, addSchema = this.opts.addUsedSchema) { + let id; + const { schemaId } = this.opts; + if (typeof schema == "object") { + id = schema[schemaId]; + } else { + if (this.opts.jtd) + throw new Error("schema must be object"); + else if (typeof schema != "boolean") + throw new Error("schema must be object or boolean"); + } + let sch = this._cache.get(schema); + if (sch !== void 0) + return sch; + baseId = (0, resolve_1.normalizeId)(id || baseId); + const localRefs = resolve_1.getSchemaRefs.call(this, schema, baseId); + sch = new compile_1.SchemaEnv({ schema, schemaId, meta: meta3, baseId, localRefs }); + this._cache.set(sch.schema, sch); + if (addSchema && !baseId.startsWith("#")) { + if (baseId) + this._checkUnique(baseId); + this.refs[baseId] = sch; + } + if (validateSchema) + this.validateSchema(schema, true); + return sch; + } + _checkUnique(id) { + if (this.schemas[id] || this.refs[id]) { + throw new Error(`schema with key or id "${id}" already exists`); + } + } + _compileSchemaEnv(sch) { + if (sch.meta) + this._compileMetaSchema(sch); + else + compile_1.compileSchema.call(this, sch); + if (!sch.validate) + throw new Error("ajv implementation error"); + return sch.validate; + } + _compileMetaSchema(sch) { + const currentOpts = this.opts; + this.opts = this._metaOpts; + try { + compile_1.compileSchema.call(this, sch); + } finally { + this.opts = currentOpts; + } + } + }; + Ajv2.ValidationError = validation_error_1.default; + Ajv2.MissingRefError = ref_error_1.default; + exports.default = Ajv2; + function checkOptions(checkOpts, options, msg, log = "error") { + for (const key in checkOpts) { + const opt = key; + if (opt in options) + this.logger[log](`${msg}: option ${key}. ${checkOpts[opt]}`); + } + } + function getSchEnv(keyRef) { + keyRef = (0, resolve_1.normalizeId)(keyRef); + return this.schemas[keyRef] || this.refs[keyRef]; + } + function addInitialSchemas() { + const optsSchemas = this.opts.schemas; + if (!optsSchemas) + return; + if (Array.isArray(optsSchemas)) + this.addSchema(optsSchemas); + else + for (const key in optsSchemas) + this.addSchema(optsSchemas[key], key); + } + function addInitialFormats() { + for (const name in this.opts.formats) { + const format = this.opts.formats[name]; + if (format) + this.addFormat(name, format); + } + } + function addInitialKeywords(defs) { + if (Array.isArray(defs)) { + this.addVocabulary(defs); + return; + } + this.logger.warn("keywords option as map is deprecated, pass array"); + for (const keyword in defs) { + const def = defs[keyword]; + if (!def.keyword) + def.keyword = keyword; + this.addKeyword(def); + } + } + function getMetaSchemaOptions() { + const metaOpts = { ...this.opts }; + for (const opt of META_IGNORE_OPTIONS) + delete metaOpts[opt]; + return metaOpts; + } + var noLogs = { log() { + }, warn() { + }, error() { + } }; + function getLogger(logger) { + if (logger === false) + return noLogs; + if (logger === void 0) + return console; + if (logger.log && logger.warn && logger.error) + return logger; + throw new Error("logger must implement log, warn and error methods"); + } + var KEYWORD_NAME = /^[a-z_$][a-z0-9_$:-]*$/i; + function checkKeyword(keyword, def) { + const { RULES } = this; + (0, util_1.eachItem)(keyword, (kwd) => { + if (RULES.keywords[kwd]) + throw new Error(`Keyword ${kwd} is already defined`); + if (!KEYWORD_NAME.test(kwd)) + throw new Error(`Keyword ${kwd} has invalid name`); + }); + if (!def) + return; + if (def.$data && !("code" in def || "validate" in def)) { + throw new Error('$data keyword must have "code" or "validate" function'); + } + } + function addRule(keyword, definition, dataType) { + var _a3; + const post = definition === null || definition === void 0 ? void 0 : definition.post; + if (dataType && post) + throw new Error('keyword with "post" flag cannot have "type"'); + const { RULES } = this; + let ruleGroup = post ? RULES.post : RULES.rules.find(({ type: t }) => t === dataType); + if (!ruleGroup) { + ruleGroup = { type: dataType, rules: [] }; + RULES.rules.push(ruleGroup); + } + RULES.keywords[keyword] = true; + if (!definition) + return; + const rule = { + keyword, + definition: { + ...definition, + type: (0, dataType_1.getJSONTypes)(definition.type), + schemaType: (0, dataType_1.getJSONTypes)(definition.schemaType) + } + }; + if (definition.before) + addBeforeRule.call(this, ruleGroup, rule, definition.before); + else + ruleGroup.rules.push(rule); + RULES.all[keyword] = rule; + (_a3 = definition.implements) === null || _a3 === void 0 ? void 0 : _a3.forEach((kwd) => this.addKeyword(kwd)); + } + function addBeforeRule(ruleGroup, rule, before) { + const i = ruleGroup.rules.findIndex((_rule) => _rule.keyword === before); + if (i >= 0) { + ruleGroup.rules.splice(i, 0, rule); + } else { + ruleGroup.rules.push(rule); + this.logger.warn(`rule ${before} is not defined`); + } + } + function keywordMetaschema(def) { + let { metaSchema } = def; + if (metaSchema === void 0) + return; + if (def.$data && this.opts.$data) + metaSchema = schemaOrData(metaSchema); + def.validateSchema = this.compile(metaSchema, true); + } + var $dataRef = { + $ref: "https://raw.githubusercontent.com/ajv-validator/ajv/master/lib/refs/data.json#" + }; + function schemaOrData(schema) { + return { anyOf: [schema, $dataRef] }; + } + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/core/id.js +var require_id2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/core/id.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var def = { + keyword: "id", + code() { + throw new Error('NOT SUPPORTED: keyword "id", use "$id" for schema ID'); + } + }; + exports.default = def; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/core/ref.js +var require_ref2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/core/ref.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.callRef = exports.getValidate = void 0; + var ref_error_1 = require_ref_error2(); + var code_1 = require_code4(); + var codegen_1 = require_codegen2(); + var names_1 = require_names2(); + var compile_1 = require_compile2(); + var util_1 = require_util2(); + var def = { + keyword: "$ref", + schemaType: "string", + code(cxt) { + const { gen, schema: $ref, it } = cxt; + const { baseId, schemaEnv: env, validateName, opts, self } = it; + const { root } = env; + if (($ref === "#" || $ref === "#/") && baseId === root.baseId) + return callRootRef(); + const schOrEnv = compile_1.resolveRef.call(self, root, baseId, $ref); + if (schOrEnv === void 0) + throw new ref_error_1.default(it.opts.uriResolver, baseId, $ref); + if (schOrEnv instanceof compile_1.SchemaEnv) + return callValidate(schOrEnv); + return inlineRefSchema(schOrEnv); + function callRootRef() { + if (env === root) + return callRef(cxt, validateName, env, env.$async); + const rootName = gen.scopeValue("root", { ref: root }); + return callRef(cxt, (0, codegen_1._)`${rootName}.validate`, root, root.$async); + } + function callValidate(sch) { + const v2 = getValidate(cxt, sch); + callRef(cxt, v2, sch, sch.$async); + } + function inlineRefSchema(sch) { + const schName = gen.scopeValue("schema", opts.code.source === true ? { ref: sch, code: (0, codegen_1.stringify)(sch) } : { ref: sch }); + const valid = gen.name("valid"); + const schCxt = cxt.subschema({ + schema: sch, + dataTypes: [], + schemaPath: codegen_1.nil, + topSchemaRef: schName, + errSchemaPath: $ref + }, valid); + cxt.mergeEvaluated(schCxt); + cxt.ok(valid); + } + } + }; + function getValidate(cxt, sch) { + const { gen } = cxt; + return sch.validate ? gen.scopeValue("validate", { ref: sch.validate }) : (0, codegen_1._)`${gen.scopeValue("wrapper", { ref: sch })}.validate`; + } + exports.getValidate = getValidate; + function callRef(cxt, v2, sch, $async) { + const { gen, it } = cxt; + const { allErrors, schemaEnv: env, opts } = it; + const passCxt = opts.passContext ? names_1.default.this : codegen_1.nil; + if ($async) + callAsyncRef(); + else + callSyncRef(); + function callAsyncRef() { + if (!env.$async) + throw new Error("async schema referenced by sync schema"); + const valid = gen.let("valid"); + gen.try(() => { + gen.code((0, codegen_1._)`await ${(0, code_1.callValidateCode)(cxt, v2, passCxt)}`); + addEvaluatedFrom(v2); + if (!allErrors) + gen.assign(valid, true); + }, (e) => { + gen.if((0, codegen_1._)`!(${e} instanceof ${it.ValidationError})`, () => gen.throw(e)); + addErrorsFrom(e); + if (!allErrors) + gen.assign(valid, false); + }); + cxt.ok(valid); + } + function callSyncRef() { + cxt.result((0, code_1.callValidateCode)(cxt, v2, passCxt), () => addEvaluatedFrom(v2), () => addErrorsFrom(v2)); + } + function addErrorsFrom(source) { + const errs = (0, codegen_1._)`${source}.errors`; + gen.assign(names_1.default.vErrors, (0, codegen_1._)`${names_1.default.vErrors} === null ? ${errs} : ${names_1.default.vErrors}.concat(${errs})`); + gen.assign(names_1.default.errors, (0, codegen_1._)`${names_1.default.vErrors}.length`); + } + function addEvaluatedFrom(source) { + var _a3; + if (!it.opts.unevaluated) + return; + const schEvaluated = (_a3 = sch === null || sch === void 0 ? void 0 : sch.validate) === null || _a3 === void 0 ? void 0 : _a3.evaluated; + if (it.props !== true) { + if (schEvaluated && !schEvaluated.dynamicProps) { + if (schEvaluated.props !== void 0) { + it.props = util_1.mergeEvaluated.props(gen, schEvaluated.props, it.props); + } + } else { + const props = gen.var("props", (0, codegen_1._)`${source}.evaluated.props`); + it.props = util_1.mergeEvaluated.props(gen, props, it.props, codegen_1.Name); + } + } + if (it.items !== true) { + if (schEvaluated && !schEvaluated.dynamicItems) { + if (schEvaluated.items !== void 0) { + it.items = util_1.mergeEvaluated.items(gen, schEvaluated.items, it.items); + } + } else { + const items = gen.var("items", (0, codegen_1._)`${source}.evaluated.items`); + it.items = util_1.mergeEvaluated.items(gen, items, it.items, codegen_1.Name); + } + } + } + } + exports.callRef = callRef; + exports.default = def; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/core/index.js +var require_core4 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/core/index.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var id_1 = require_id2(); + var ref_1 = require_ref2(); + var core = [ + "$schema", + "$id", + "$defs", + "$vocabulary", + { keyword: "$comment" }, + "definitions", + id_1.default, + ref_1.default + ]; + exports.default = core; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/validation/limitNumber.js +var require_limitNumber2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/validation/limitNumber.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var codegen_1 = require_codegen2(); + var ops = codegen_1.operators; + var KWDs = { + maximum: { okStr: "<=", ok: ops.LTE, fail: ops.GT }, + minimum: { okStr: ">=", ok: ops.GTE, fail: ops.LT }, + exclusiveMaximum: { okStr: "<", ok: ops.LT, fail: ops.GTE }, + exclusiveMinimum: { okStr: ">", ok: ops.GT, fail: ops.LTE } + }; + var error61 = { + message: ({ keyword, schemaCode }) => (0, codegen_1.str)`must be ${KWDs[keyword].okStr} ${schemaCode}`, + params: ({ keyword, schemaCode }) => (0, codegen_1._)`{comparison: ${KWDs[keyword].okStr}, limit: ${schemaCode}}` + }; + var def = { + keyword: Object.keys(KWDs), + type: "number", + schemaType: "number", + $data: true, + error: error61, + code(cxt) { + const { keyword, data, schemaCode } = cxt; + cxt.fail$data((0, codegen_1._)`${data} ${KWDs[keyword].fail} ${schemaCode} || isNaN(${data})`); + } + }; + exports.default = def; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/validation/multipleOf.js +var require_multipleOf2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/validation/multipleOf.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var codegen_1 = require_codegen2(); + var error61 = { + message: ({ schemaCode }) => (0, codegen_1.str)`must be multiple of ${schemaCode}`, + params: ({ schemaCode }) => (0, codegen_1._)`{multipleOf: ${schemaCode}}` + }; + var def = { + keyword: "multipleOf", + type: "number", + schemaType: "number", + $data: true, + error: error61, + code(cxt) { + const { gen, data, schemaCode, it } = cxt; + const prec = it.opts.multipleOfPrecision; + const res = gen.let("res"); + const invalid = prec ? (0, codegen_1._)`Math.abs(Math.round(${res}) - ${res}) > 1e-${prec}` : (0, codegen_1._)`${res} !== parseInt(${res})`; + cxt.fail$data((0, codegen_1._)`(${schemaCode} === 0 || (${res} = ${data}/${schemaCode}, ${invalid}))`); + } + }; + exports.default = def; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/runtime/ucs2length.js +var require_ucs2length2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/runtime/ucs2length.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + function ucs2length(str) { + const len = str.length; + let length = 0; + let pos = 0; + let value; + while (pos < len) { + length++; + value = str.charCodeAt(pos++); + if (value >= 55296 && value <= 56319 && pos < len) { + value = str.charCodeAt(pos); + if ((value & 64512) === 56320) + pos++; + } + } + return length; + } + exports.default = ucs2length; + ucs2length.code = 'require("ajv/dist/runtime/ucs2length").default'; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/validation/limitLength.js +var require_limitLength2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/validation/limitLength.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var codegen_1 = require_codegen2(); + var util_1 = require_util2(); + var ucs2length_1 = require_ucs2length2(); + var error61 = { + message({ keyword, schemaCode }) { + const comp = keyword === "maxLength" ? "more" : "fewer"; + return (0, codegen_1.str)`must NOT have ${comp} than ${schemaCode} characters`; + }, + params: ({ schemaCode }) => (0, codegen_1._)`{limit: ${schemaCode}}` + }; + var def = { + keyword: ["maxLength", "minLength"], + type: "string", + schemaType: "number", + $data: true, + error: error61, + code(cxt) { + const { keyword, data, schemaCode, it } = cxt; + const op = keyword === "maxLength" ? codegen_1.operators.GT : codegen_1.operators.LT; + const len = it.opts.unicode === false ? (0, codegen_1._)`${data}.length` : (0, codegen_1._)`${(0, util_1.useFunc)(cxt.gen, ucs2length_1.default)}(${data})`; + cxt.fail$data((0, codegen_1._)`${len} ${op} ${schemaCode}`); + } + }; + exports.default = def; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/validation/pattern.js +var require_pattern2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/validation/pattern.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var code_1 = require_code4(); + var util_1 = require_util2(); + var codegen_1 = require_codegen2(); + var error61 = { + message: ({ schemaCode }) => (0, codegen_1.str)`must match pattern "${schemaCode}"`, + params: ({ schemaCode }) => (0, codegen_1._)`{pattern: ${schemaCode}}` + }; + var def = { + keyword: "pattern", + type: "string", + schemaType: "string", + $data: true, + error: error61, + code(cxt) { + const { gen, data, $data, schema, schemaCode, it } = cxt; + const u = it.opts.unicodeRegExp ? "u" : ""; + if ($data) { + const { regExp } = it.opts.code; + const regExpCode = regExp.code === "new RegExp" ? (0, codegen_1._)`new RegExp` : (0, util_1.useFunc)(gen, regExp); + const valid = gen.let("valid"); + gen.try(() => gen.assign(valid, (0, codegen_1._)`${regExpCode}(${schemaCode}, ${u}).test(${data})`), () => gen.assign(valid, false)); + cxt.fail$data((0, codegen_1._)`!${valid}`); + } else { + const regExp = (0, code_1.usePattern)(cxt, schema); + cxt.fail$data((0, codegen_1._)`!${regExp}.test(${data})`); + } + } + }; + exports.default = def; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/validation/limitProperties.js +var require_limitProperties2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/validation/limitProperties.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var codegen_1 = require_codegen2(); + var error61 = { + message({ keyword, schemaCode }) { + const comp = keyword === "maxProperties" ? "more" : "fewer"; + return (0, codegen_1.str)`must NOT have ${comp} than ${schemaCode} properties`; + }, + params: ({ schemaCode }) => (0, codegen_1._)`{limit: ${schemaCode}}` + }; + var def = { + keyword: ["maxProperties", "minProperties"], + type: "object", + schemaType: "number", + $data: true, + error: error61, + code(cxt) { + const { keyword, data, schemaCode } = cxt; + const op = keyword === "maxProperties" ? codegen_1.operators.GT : codegen_1.operators.LT; + cxt.fail$data((0, codegen_1._)`Object.keys(${data}).length ${op} ${schemaCode}`); + } + }; + exports.default = def; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/validation/required.js +var require_required2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/validation/required.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var code_1 = require_code4(); + var codegen_1 = require_codegen2(); + var util_1 = require_util2(); + var error61 = { + message: ({ params: { missingProperty } }) => (0, codegen_1.str)`must have required property '${missingProperty}'`, + params: ({ params: { missingProperty } }) => (0, codegen_1._)`{missingProperty: ${missingProperty}}` + }; + var def = { + keyword: "required", + type: "object", + schemaType: "array", + $data: true, + error: error61, + code(cxt) { + const { gen, schema, schemaCode, data, $data, it } = cxt; + const { opts } = it; + if (!$data && schema.length === 0) + return; + const useLoop = schema.length >= opts.loopRequired; + if (it.allErrors) + allErrorsMode(); + else + exitOnErrorMode(); + if (opts.strictRequired) { + const props = cxt.parentSchema.properties; + const { definedProperties } = cxt.it; + for (const requiredKey of schema) { + if ((props === null || props === void 0 ? void 0 : props[requiredKey]) === void 0 && !definedProperties.has(requiredKey)) { + const schemaPath = it.schemaEnv.baseId + it.errSchemaPath; + const msg = `required property "${requiredKey}" is not defined at "${schemaPath}" (strictRequired)`; + (0, util_1.checkStrictMode)(it, msg, it.opts.strictRequired); + } + } + } + function allErrorsMode() { + if (useLoop || $data) { + cxt.block$data(codegen_1.nil, loopAllRequired); + } else { + for (const prop of schema) { + (0, code_1.checkReportMissingProp)(cxt, prop); + } + } + } + function exitOnErrorMode() { + const missing = gen.let("missing"); + if (useLoop || $data) { + const valid = gen.let("valid", true); + cxt.block$data(valid, () => loopUntilMissing(missing, valid)); + cxt.ok(valid); + } else { + gen.if((0, code_1.checkMissingProp)(cxt, schema, missing)); + (0, code_1.reportMissingProp)(cxt, missing); + gen.else(); + } + } + function loopAllRequired() { + gen.forOf("prop", schemaCode, (prop) => { + cxt.setParams({ missingProperty: prop }); + gen.if((0, code_1.noPropertyInData)(gen, data, prop, opts.ownProperties), () => cxt.error()); + }); + } + function loopUntilMissing(missing, valid) { + cxt.setParams({ missingProperty: missing }); + gen.forOf(missing, schemaCode, () => { + gen.assign(valid, (0, code_1.propertyInData)(gen, data, missing, opts.ownProperties)); + gen.if((0, codegen_1.not)(valid), () => { + cxt.error(); + gen.break(); + }); + }, codegen_1.nil); + } + } + }; + exports.default = def; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/validation/limitItems.js +var require_limitItems2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/validation/limitItems.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var codegen_1 = require_codegen2(); + var error61 = { + message({ keyword, schemaCode }) { + const comp = keyword === "maxItems" ? "more" : "fewer"; + return (0, codegen_1.str)`must NOT have ${comp} than ${schemaCode} items`; + }, + params: ({ schemaCode }) => (0, codegen_1._)`{limit: ${schemaCode}}` + }; + var def = { + keyword: ["maxItems", "minItems"], + type: "array", + schemaType: "number", + $data: true, + error: error61, + code(cxt) { + const { keyword, data, schemaCode } = cxt; + const op = keyword === "maxItems" ? codegen_1.operators.GT : codegen_1.operators.LT; + cxt.fail$data((0, codegen_1._)`${data}.length ${op} ${schemaCode}`); + } + }; + exports.default = def; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/runtime/equal.js +var require_equal2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/runtime/equal.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var equal = require_fast_deep_equal(); + equal.code = 'require("ajv/dist/runtime/equal").default'; + exports.default = equal; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/validation/uniqueItems.js +var require_uniqueItems2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/validation/uniqueItems.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var dataType_1 = require_dataType2(); + var codegen_1 = require_codegen2(); + var util_1 = require_util2(); + var equal_1 = require_equal2(); + var error61 = { + message: ({ params: { i, j: j2 } }) => (0, codegen_1.str)`must NOT have duplicate items (items ## ${j2} and ${i} are identical)`, + params: ({ params: { i, j: j2 } }) => (0, codegen_1._)`{i: ${i}, j: ${j2}}` + }; + var def = { + keyword: "uniqueItems", + type: "array", + schemaType: "boolean", + $data: true, + error: error61, + code(cxt) { + const { gen, data, $data, schema, parentSchema, schemaCode, it } = cxt; + if (!$data && !schema) + return; + const valid = gen.let("valid"); + const itemTypes = parentSchema.items ? (0, dataType_1.getSchemaTypes)(parentSchema.items) : []; + cxt.block$data(valid, validateUniqueItems, (0, codegen_1._)`${schemaCode} === false`); + cxt.ok(valid); + function validateUniqueItems() { + const i = gen.let("i", (0, codegen_1._)`${data}.length`); + const j2 = gen.let("j"); + cxt.setParams({ i, j: j2 }); + gen.assign(valid, true); + gen.if((0, codegen_1._)`${i} > 1`, () => (canOptimize() ? loopN : loopN2)(i, j2)); + } + function canOptimize() { + return itemTypes.length > 0 && !itemTypes.some((t) => t === "object" || t === "array"); + } + function loopN(i, j2) { + const item = gen.name("item"); + const wrongType = (0, dataType_1.checkDataTypes)(itemTypes, item, it.opts.strictNumbers, dataType_1.DataType.Wrong); + const indices = gen.const("indices", (0, codegen_1._)`{}`); + gen.for((0, codegen_1._)`;${i}--;`, () => { + gen.let(item, (0, codegen_1._)`${data}[${i}]`); + gen.if(wrongType, (0, codegen_1._)`continue`); + if (itemTypes.length > 1) + gen.if((0, codegen_1._)`typeof ${item} == "string"`, (0, codegen_1._)`${item} += "_"`); + gen.if((0, codegen_1._)`typeof ${indices}[${item}] == "number"`, () => { + gen.assign(j2, (0, codegen_1._)`${indices}[${item}]`); + cxt.error(); + gen.assign(valid, false).break(); + }).code((0, codegen_1._)`${indices}[${item}] = ${i}`); + }); + } + function loopN2(i, j2) { + const eql = (0, util_1.useFunc)(gen, equal_1.default); + const outer = gen.name("outer"); + gen.label(outer).for((0, codegen_1._)`;${i}--;`, () => gen.for((0, codegen_1._)`${j2} = ${i}; ${j2}--;`, () => gen.if((0, codegen_1._)`${eql}(${data}[${i}], ${data}[${j2}])`, () => { + cxt.error(); + gen.assign(valid, false).break(outer); + }))); + } + } + }; + exports.default = def; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/validation/const.js +var require_const2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/validation/const.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var codegen_1 = require_codegen2(); + var util_1 = require_util2(); + var equal_1 = require_equal2(); + var error61 = { + message: "must be equal to constant", + params: ({ schemaCode }) => (0, codegen_1._)`{allowedValue: ${schemaCode}}` + }; + var def = { + keyword: "const", + $data: true, + error: error61, + code(cxt) { + const { gen, data, $data, schemaCode, schema } = cxt; + if ($data || schema && typeof schema == "object") { + cxt.fail$data((0, codegen_1._)`!${(0, util_1.useFunc)(gen, equal_1.default)}(${data}, ${schemaCode})`); + } else { + cxt.fail((0, codegen_1._)`${schema} !== ${data}`); + } + } + }; + exports.default = def; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/validation/enum.js +var require_enum2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/validation/enum.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var codegen_1 = require_codegen2(); + var util_1 = require_util2(); + var equal_1 = require_equal2(); + var error61 = { + message: "must be equal to one of the allowed values", + params: ({ schemaCode }) => (0, codegen_1._)`{allowedValues: ${schemaCode}}` + }; + var def = { + keyword: "enum", + schemaType: "array", + $data: true, + error: error61, + code(cxt) { + const { gen, data, $data, schema, schemaCode, it } = cxt; + if (!$data && schema.length === 0) + throw new Error("enum must have non-empty array"); + const useLoop = schema.length >= it.opts.loopEnum; + let eql; + const getEql = () => eql !== null && eql !== void 0 ? eql : eql = (0, util_1.useFunc)(gen, equal_1.default); + let valid; + if (useLoop || $data) { + valid = gen.let("valid"); + cxt.block$data(valid, loopEnum); + } else { + if (!Array.isArray(schema)) + throw new Error("ajv implementation error"); + const vSchema = gen.const("vSchema", schemaCode); + valid = (0, codegen_1.or)(...schema.map((_x, i) => equalCode(vSchema, i))); + } + cxt.pass(valid); + function loopEnum() { + gen.assign(valid, false); + gen.forOf("v", schemaCode, (v2) => gen.if((0, codegen_1._)`${getEql()}(${data}, ${v2})`, () => gen.assign(valid, true).break())); + } + function equalCode(vSchema, i) { + const sch = schema[i]; + return typeof sch === "object" && sch !== null ? (0, codegen_1._)`${getEql()}(${data}, ${vSchema}[${i}])` : (0, codegen_1._)`${data} === ${sch}`; + } + } + }; + exports.default = def; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/validation/index.js +var require_validation2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/validation/index.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var limitNumber_1 = require_limitNumber2(); + var multipleOf_1 = require_multipleOf2(); + var limitLength_1 = require_limitLength2(); + var pattern_1 = require_pattern2(); + var limitProperties_1 = require_limitProperties2(); + var required_1 = require_required2(); + var limitItems_1 = require_limitItems2(); + var uniqueItems_1 = require_uniqueItems2(); + var const_1 = require_const2(); + var enum_1 = require_enum2(); + var validation = [ + // number + limitNumber_1.default, + multipleOf_1.default, + // string + limitLength_1.default, + pattern_1.default, + // object + limitProperties_1.default, + required_1.default, + // array + limitItems_1.default, + uniqueItems_1.default, + // any + { keyword: "type", schemaType: ["string", "array"] }, + { keyword: "nullable", schemaType: "boolean" }, + const_1.default, + enum_1.default + ]; + exports.default = validation; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/applicator/additionalItems.js +var require_additionalItems2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/applicator/additionalItems.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.validateAdditionalItems = void 0; + var codegen_1 = require_codegen2(); + var util_1 = require_util2(); + var error61 = { + message: ({ params: { len } }) => (0, codegen_1.str)`must NOT have more than ${len} items`, + params: ({ params: { len } }) => (0, codegen_1._)`{limit: ${len}}` + }; + var def = { + keyword: "additionalItems", + type: "array", + schemaType: ["boolean", "object"], + before: "uniqueItems", + error: error61, + code(cxt) { + const { parentSchema, it } = cxt; + const { items } = parentSchema; + if (!Array.isArray(items)) { + (0, util_1.checkStrictMode)(it, '"additionalItems" is ignored when "items" is not an array of schemas'); + return; + } + validateAdditionalItems(cxt, items); + } + }; + function validateAdditionalItems(cxt, items) { + const { gen, schema, data, keyword, it } = cxt; + it.items = true; + const len = gen.const("len", (0, codegen_1._)`${data}.length`); + if (schema === false) { + cxt.setParams({ len: items.length }); + cxt.pass((0, codegen_1._)`${len} <= ${items.length}`); + } else if (typeof schema == "object" && !(0, util_1.alwaysValidSchema)(it, schema)) { + const valid = gen.var("valid", (0, codegen_1._)`${len} <= ${items.length}`); + gen.if((0, codegen_1.not)(valid), () => validateItems(valid)); + cxt.ok(valid); + } + function validateItems(valid) { + gen.forRange("i", items.length, len, (i) => { + cxt.subschema({ keyword, dataProp: i, dataPropType: util_1.Type.Num }, valid); + if (!it.allErrors) + gen.if((0, codegen_1.not)(valid), () => gen.break()); + }); + } + } + exports.validateAdditionalItems = validateAdditionalItems; + exports.default = def; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/applicator/items.js +var require_items2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/applicator/items.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.validateTuple = void 0; + var codegen_1 = require_codegen2(); + var util_1 = require_util2(); + var code_1 = require_code4(); + var def = { + keyword: "items", + type: "array", + schemaType: ["object", "array", "boolean"], + before: "uniqueItems", + code(cxt) { + const { schema, it } = cxt; + if (Array.isArray(schema)) + return validateTuple(cxt, "additionalItems", schema); + it.items = true; + if ((0, util_1.alwaysValidSchema)(it, schema)) + return; + cxt.ok((0, code_1.validateArray)(cxt)); + } + }; + function validateTuple(cxt, extraItems, schArr = cxt.schema) { + const { gen, parentSchema, data, keyword, it } = cxt; + checkStrictTuple(parentSchema); + if (it.opts.unevaluated && schArr.length && it.items !== true) { + it.items = util_1.mergeEvaluated.items(gen, schArr.length, it.items); + } + const valid = gen.name("valid"); + const len = gen.const("len", (0, codegen_1._)`${data}.length`); + schArr.forEach((sch, i) => { + if ((0, util_1.alwaysValidSchema)(it, sch)) + return; + gen.if((0, codegen_1._)`${len} > ${i}`, () => cxt.subschema({ + keyword, + schemaProp: i, + dataProp: i + }, valid)); + cxt.ok(valid); + }); + function checkStrictTuple(sch) { + const { opts, errSchemaPath } = it; + const l3 = schArr.length; + const fullTuple = l3 === sch.minItems && (l3 === sch.maxItems || sch[extraItems] === false); + if (opts.strictTuples && !fullTuple) { + const msg = `"${keyword}" is ${l3}-tuple, but minItems or maxItems/${extraItems} are not specified or different at path "${errSchemaPath}"`; + (0, util_1.checkStrictMode)(it, msg, opts.strictTuples); + } + } + } + exports.validateTuple = validateTuple; + exports.default = def; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/applicator/prefixItems.js +var require_prefixItems2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/applicator/prefixItems.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var items_1 = require_items2(); + var def = { + keyword: "prefixItems", + type: "array", + schemaType: ["array"], + before: "uniqueItems", + code: (cxt) => (0, items_1.validateTuple)(cxt, "items") + }; + exports.default = def; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/applicator/items2020.js +var require_items20202 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/applicator/items2020.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var codegen_1 = require_codegen2(); + var util_1 = require_util2(); + var code_1 = require_code4(); + var additionalItems_1 = require_additionalItems2(); + var error61 = { + message: ({ params: { len } }) => (0, codegen_1.str)`must NOT have more than ${len} items`, + params: ({ params: { len } }) => (0, codegen_1._)`{limit: ${len}}` + }; + var def = { + keyword: "items", + type: "array", + schemaType: ["object", "boolean"], + before: "uniqueItems", + error: error61, + code(cxt) { + const { schema, parentSchema, it } = cxt; + const { prefixItems } = parentSchema; + it.items = true; + if ((0, util_1.alwaysValidSchema)(it, schema)) + return; + if (prefixItems) + (0, additionalItems_1.validateAdditionalItems)(cxt, prefixItems); + else + cxt.ok((0, code_1.validateArray)(cxt)); + } + }; + exports.default = def; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/applicator/contains.js +var require_contains2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/applicator/contains.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var codegen_1 = require_codegen2(); + var util_1 = require_util2(); + var error61 = { + message: ({ params: { min, max } }) => max === void 0 ? (0, codegen_1.str)`must contain at least ${min} valid item(s)` : (0, codegen_1.str)`must contain at least ${min} and no more than ${max} valid item(s)`, + params: ({ params: { min, max } }) => max === void 0 ? (0, codegen_1._)`{minContains: ${min}}` : (0, codegen_1._)`{minContains: ${min}, maxContains: ${max}}` + }; + var def = { + keyword: "contains", + type: "array", + schemaType: ["object", "boolean"], + before: "uniqueItems", + trackErrors: true, + error: error61, + code(cxt) { + const { gen, schema, parentSchema, data, it } = cxt; + let min; + let max; + const { minContains, maxContains } = parentSchema; + if (it.opts.next) { + min = minContains === void 0 ? 1 : minContains; + max = maxContains; + } else { + min = 1; + } + const len = gen.const("len", (0, codegen_1._)`${data}.length`); + cxt.setParams({ min, max }); + if (max === void 0 && min === 0) { + (0, util_1.checkStrictMode)(it, `"minContains" == 0 without "maxContains": "contains" keyword ignored`); + return; + } + if (max !== void 0 && min > max) { + (0, util_1.checkStrictMode)(it, `"minContains" > "maxContains" is always invalid`); + cxt.fail(); + return; + } + if ((0, util_1.alwaysValidSchema)(it, schema)) { + let cond = (0, codegen_1._)`${len} >= ${min}`; + if (max !== void 0) + cond = (0, codegen_1._)`${cond} && ${len} <= ${max}`; + cxt.pass(cond); + return; + } + it.items = true; + const valid = gen.name("valid"); + if (max === void 0 && min === 1) { + validateItems(valid, () => gen.if(valid, () => gen.break())); + } else if (min === 0) { + gen.let(valid, true); + if (max !== void 0) + gen.if((0, codegen_1._)`${data}.length > 0`, validateItemsWithCount); + } else { + gen.let(valid, false); + validateItemsWithCount(); + } + cxt.result(valid, () => cxt.reset()); + function validateItemsWithCount() { + const schValid = gen.name("_valid"); + const count = gen.let("count", 0); + validateItems(schValid, () => gen.if(schValid, () => checkLimits(count))); + } + function validateItems(_valid, block) { + gen.forRange("i", 0, len, (i) => { + cxt.subschema({ + keyword: "contains", + dataProp: i, + dataPropType: util_1.Type.Num, + compositeRule: true + }, _valid); + block(); + }); + } + function checkLimits(count) { + gen.code((0, codegen_1._)`${count}++`); + if (max === void 0) { + gen.if((0, codegen_1._)`${count} >= ${min}`, () => gen.assign(valid, true).break()); + } else { + gen.if((0, codegen_1._)`${count} > ${max}`, () => gen.assign(valid, false).break()); + if (min === 1) + gen.assign(valid, true); + else + gen.if((0, codegen_1._)`${count} >= ${min}`, () => gen.assign(valid, true)); + } + } + } + }; + exports.default = def; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/applicator/dependencies.js +var require_dependencies2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/applicator/dependencies.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.validateSchemaDeps = exports.validatePropertyDeps = exports.error = void 0; + var codegen_1 = require_codegen2(); + var util_1 = require_util2(); + var code_1 = require_code4(); + exports.error = { + message: ({ params: { property, depsCount, deps } }) => { + const property_ies = depsCount === 1 ? "property" : "properties"; + return (0, codegen_1.str)`must have ${property_ies} ${deps} when property ${property} is present`; + }, + params: ({ params: { property, depsCount, deps, missingProperty } }) => (0, codegen_1._)`{property: ${property}, + missingProperty: ${missingProperty}, + depsCount: ${depsCount}, + deps: ${deps}}` + // TODO change to reference + }; + var def = { + keyword: "dependencies", + type: "object", + schemaType: "object", + error: exports.error, + code(cxt) { + const [propDeps, schDeps] = splitDependencies(cxt); + validatePropertyDeps(cxt, propDeps); + validateSchemaDeps(cxt, schDeps); + } + }; + function splitDependencies({ schema }) { + const propertyDeps = {}; + const schemaDeps = {}; + for (const key in schema) { + if (key === "__proto__") + continue; + const deps = Array.isArray(schema[key]) ? propertyDeps : schemaDeps; + deps[key] = schema[key]; + } + return [propertyDeps, schemaDeps]; + } + function validatePropertyDeps(cxt, propertyDeps = cxt.schema) { + const { gen, data, it } = cxt; + if (Object.keys(propertyDeps).length === 0) + return; + const missing = gen.let("missing"); + for (const prop in propertyDeps) { + const deps = propertyDeps[prop]; + if (deps.length === 0) + continue; + const hasProperty = (0, code_1.propertyInData)(gen, data, prop, it.opts.ownProperties); + cxt.setParams({ + property: prop, + depsCount: deps.length, + deps: deps.join(", ") + }); + if (it.allErrors) { + gen.if(hasProperty, () => { + for (const depProp of deps) { + (0, code_1.checkReportMissingProp)(cxt, depProp); + } + }); + } else { + gen.if((0, codegen_1._)`${hasProperty} && (${(0, code_1.checkMissingProp)(cxt, deps, missing)})`); + (0, code_1.reportMissingProp)(cxt, missing); + gen.else(); + } + } + } + exports.validatePropertyDeps = validatePropertyDeps; + function validateSchemaDeps(cxt, schemaDeps = cxt.schema) { + const { gen, data, keyword, it } = cxt; + const valid = gen.name("valid"); + for (const prop in schemaDeps) { + if ((0, util_1.alwaysValidSchema)(it, schemaDeps[prop])) + continue; + gen.if( + (0, code_1.propertyInData)(gen, data, prop, it.opts.ownProperties), + () => { + const schCxt = cxt.subschema({ keyword, schemaProp: prop }, valid); + cxt.mergeValidEvaluated(schCxt, valid); + }, + () => gen.var(valid, true) + // TODO var + ); + cxt.ok(valid); + } + } + exports.validateSchemaDeps = validateSchemaDeps; + exports.default = def; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/applicator/propertyNames.js +var require_propertyNames2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/applicator/propertyNames.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var codegen_1 = require_codegen2(); + var util_1 = require_util2(); + var error61 = { + message: "property name must be valid", + params: ({ params }) => (0, codegen_1._)`{propertyName: ${params.propertyName}}` + }; + var def = { + keyword: "propertyNames", + type: "object", + schemaType: ["object", "boolean"], + error: error61, + code(cxt) { + const { gen, schema, data, it } = cxt; + if ((0, util_1.alwaysValidSchema)(it, schema)) + return; + const valid = gen.name("valid"); + gen.forIn("key", data, (key) => { + cxt.setParams({ propertyName: key }); + cxt.subschema({ + keyword: "propertyNames", + data: key, + dataTypes: ["string"], + propertyName: key, + compositeRule: true + }, valid); + gen.if((0, codegen_1.not)(valid), () => { + cxt.error(true); + if (!it.allErrors) + gen.break(); + }); + }); + cxt.ok(valid); + } + }; + exports.default = def; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/applicator/additionalProperties.js +var require_additionalProperties2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/applicator/additionalProperties.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var code_1 = require_code4(); + var codegen_1 = require_codegen2(); + var names_1 = require_names2(); + var util_1 = require_util2(); + var error61 = { + message: "must NOT have additional properties", + params: ({ params }) => (0, codegen_1._)`{additionalProperty: ${params.additionalProperty}}` + }; + var def = { + keyword: "additionalProperties", + type: ["object"], + schemaType: ["boolean", "object"], + allowUndefined: true, + trackErrors: true, + error: error61, + code(cxt) { + const { gen, schema, parentSchema, data, errsCount, it } = cxt; + if (!errsCount) + throw new Error("ajv implementation error"); + const { allErrors, opts } = it; + it.props = true; + if (opts.removeAdditional !== "all" && (0, util_1.alwaysValidSchema)(it, schema)) + return; + const props = (0, code_1.allSchemaProperties)(parentSchema.properties); + const patProps = (0, code_1.allSchemaProperties)(parentSchema.patternProperties); + checkAdditionalProperties(); + cxt.ok((0, codegen_1._)`${errsCount} === ${names_1.default.errors}`); + function checkAdditionalProperties() { + gen.forIn("key", data, (key) => { + if (!props.length && !patProps.length) + additionalPropertyCode(key); + else + gen.if(isAdditional(key), () => additionalPropertyCode(key)); + }); + } + function isAdditional(key) { + let definedProp; + if (props.length > 8) { + const propsSchema = (0, util_1.schemaRefOrVal)(it, parentSchema.properties, "properties"); + definedProp = (0, code_1.isOwnProperty)(gen, propsSchema, key); + } else if (props.length) { + definedProp = (0, codegen_1.or)(...props.map((p) => (0, codegen_1._)`${key} === ${p}`)); + } else { + definedProp = codegen_1.nil; + } + if (patProps.length) { + definedProp = (0, codegen_1.or)(definedProp, ...patProps.map((p) => (0, codegen_1._)`${(0, code_1.usePattern)(cxt, p)}.test(${key})`)); + } + return (0, codegen_1.not)(definedProp); + } + function deleteAdditional(key) { + gen.code((0, codegen_1._)`delete ${data}[${key}]`); + } + function additionalPropertyCode(key) { + if (opts.removeAdditional === "all" || opts.removeAdditional && schema === false) { + deleteAdditional(key); + return; + } + if (schema === false) { + cxt.setParams({ additionalProperty: key }); + cxt.error(); + if (!allErrors) + gen.break(); + return; + } + if (typeof schema == "object" && !(0, util_1.alwaysValidSchema)(it, schema)) { + const valid = gen.name("valid"); + if (opts.removeAdditional === "failing") { + applyAdditionalSchema(key, valid, false); + gen.if((0, codegen_1.not)(valid), () => { + cxt.reset(); + deleteAdditional(key); + }); + } else { + applyAdditionalSchema(key, valid); + if (!allErrors) + gen.if((0, codegen_1.not)(valid), () => gen.break()); + } + } + } + function applyAdditionalSchema(key, valid, errors) { + const subschema = { + keyword: "additionalProperties", + dataProp: key, + dataPropType: util_1.Type.Str + }; + if (errors === false) { + Object.assign(subschema, { + compositeRule: true, + createErrors: false, + allErrors: false + }); + } + cxt.subschema(subschema, valid); + } + } + }; + exports.default = def; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/applicator/properties.js +var require_properties2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/applicator/properties.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var validate_1 = require_validate2(); + var code_1 = require_code4(); + var util_1 = require_util2(); + var additionalProperties_1 = require_additionalProperties2(); + var def = { + keyword: "properties", + type: "object", + schemaType: "object", + code(cxt) { + const { gen, schema, parentSchema, data, it } = cxt; + if (it.opts.removeAdditional === "all" && parentSchema.additionalProperties === void 0) { + additionalProperties_1.default.code(new validate_1.KeywordCxt(it, additionalProperties_1.default, "additionalProperties")); + } + const allProps = (0, code_1.allSchemaProperties)(schema); + for (const prop of allProps) { + it.definedProperties.add(prop); + } + if (it.opts.unevaluated && allProps.length && it.props !== true) { + it.props = util_1.mergeEvaluated.props(gen, (0, util_1.toHash)(allProps), it.props); + } + const properties = allProps.filter((p) => !(0, util_1.alwaysValidSchema)(it, schema[p])); + if (properties.length === 0) + return; + const valid = gen.name("valid"); + for (const prop of properties) { + if (hasDefault(prop)) { + applyPropertySchema(prop); + } else { + gen.if((0, code_1.propertyInData)(gen, data, prop, it.opts.ownProperties)); + applyPropertySchema(prop); + if (!it.allErrors) + gen.else().var(valid, true); + gen.endIf(); + } + cxt.it.definedProperties.add(prop); + cxt.ok(valid); + } + function hasDefault(prop) { + return it.opts.useDefaults && !it.compositeRule && schema[prop].default !== void 0; + } + function applyPropertySchema(prop) { + cxt.subschema({ + keyword: "properties", + schemaProp: prop, + dataProp: prop + }, valid); + } + } + }; + exports.default = def; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/applicator/patternProperties.js +var require_patternProperties2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/applicator/patternProperties.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var code_1 = require_code4(); + var codegen_1 = require_codegen2(); + var util_1 = require_util2(); + var util_2 = require_util2(); + var def = { + keyword: "patternProperties", + type: "object", + schemaType: "object", + code(cxt) { + const { gen, schema, data, parentSchema, it } = cxt; + const { opts } = it; + const patterns = (0, code_1.allSchemaProperties)(schema); + const alwaysValidPatterns = patterns.filter((p) => (0, util_1.alwaysValidSchema)(it, schema[p])); + if (patterns.length === 0 || alwaysValidPatterns.length === patterns.length && (!it.opts.unevaluated || it.props === true)) { + return; + } + const checkProperties = opts.strictSchema && !opts.allowMatchingProperties && parentSchema.properties; + const valid = gen.name("valid"); + if (it.props !== true && !(it.props instanceof codegen_1.Name)) { + it.props = (0, util_2.evaluatedPropsToName)(gen, it.props); + } + const { props } = it; + validatePatternProperties(); + function validatePatternProperties() { + for (const pat of patterns) { + if (checkProperties) + checkMatchingProperties(pat); + if (it.allErrors) { + validateProperties(pat); + } else { + gen.var(valid, true); + validateProperties(pat); + gen.if(valid); + } + } + } + function checkMatchingProperties(pat) { + for (const prop in checkProperties) { + if (new RegExp(pat).test(prop)) { + (0, util_1.checkStrictMode)(it, `property ${prop} matches pattern ${pat} (use allowMatchingProperties)`); + } + } + } + function validateProperties(pat) { + gen.forIn("key", data, (key) => { + gen.if((0, codegen_1._)`${(0, code_1.usePattern)(cxt, pat)}.test(${key})`, () => { + const alwaysValid = alwaysValidPatterns.includes(pat); + if (!alwaysValid) { + cxt.subschema({ + keyword: "patternProperties", + schemaProp: pat, + dataProp: key, + dataPropType: util_2.Type.Str + }, valid); + } + if (it.opts.unevaluated && props !== true) { + gen.assign((0, codegen_1._)`${props}[${key}]`, true); + } else if (!alwaysValid && !it.allErrors) { + gen.if((0, codegen_1.not)(valid), () => gen.break()); + } + }); + }); + } + } + }; + exports.default = def; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/applicator/not.js +var require_not2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/applicator/not.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var util_1 = require_util2(); + var def = { + keyword: "not", + schemaType: ["object", "boolean"], + trackErrors: true, + code(cxt) { + const { gen, schema, it } = cxt; + if ((0, util_1.alwaysValidSchema)(it, schema)) { + cxt.fail(); + return; + } + const valid = gen.name("valid"); + cxt.subschema({ + keyword: "not", + compositeRule: true, + createErrors: false, + allErrors: false + }, valid); + cxt.failResult(valid, () => cxt.reset(), () => cxt.error()); + }, + error: { message: "must NOT be valid" } + }; + exports.default = def; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/applicator/anyOf.js +var require_anyOf2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/applicator/anyOf.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var code_1 = require_code4(); + var def = { + keyword: "anyOf", + schemaType: "array", + trackErrors: true, + code: code_1.validateUnion, + error: { message: "must match a schema in anyOf" } + }; + exports.default = def; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/applicator/oneOf.js +var require_oneOf2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/applicator/oneOf.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var codegen_1 = require_codegen2(); + var util_1 = require_util2(); + var error61 = { + message: "must match exactly one schema in oneOf", + params: ({ params }) => (0, codegen_1._)`{passingSchemas: ${params.passing}}` + }; + var def = { + keyword: "oneOf", + schemaType: "array", + trackErrors: true, + error: error61, + code(cxt) { + const { gen, schema, parentSchema, it } = cxt; + if (!Array.isArray(schema)) + throw new Error("ajv implementation error"); + if (it.opts.discriminator && parentSchema.discriminator) + return; + const schArr = schema; + const valid = gen.let("valid", false); + const passing = gen.let("passing", null); + const schValid = gen.name("_valid"); + cxt.setParams({ passing }); + gen.block(validateOneOf); + cxt.result(valid, () => cxt.reset(), () => cxt.error(true)); + function validateOneOf() { + schArr.forEach((sch, i) => { + let schCxt; + if ((0, util_1.alwaysValidSchema)(it, sch)) { + gen.var(schValid, true); + } else { + schCxt = cxt.subschema({ + keyword: "oneOf", + schemaProp: i, + compositeRule: true + }, schValid); + } + if (i > 0) { + gen.if((0, codegen_1._)`${schValid} && ${valid}`).assign(valid, false).assign(passing, (0, codegen_1._)`[${passing}, ${i}]`).else(); + } + gen.if(schValid, () => { + gen.assign(valid, true); + gen.assign(passing, i); + if (schCxt) + cxt.mergeEvaluated(schCxt, codegen_1.Name); + }); + }); + } + } + }; + exports.default = def; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/applicator/allOf.js +var require_allOf2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/applicator/allOf.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var util_1 = require_util2(); + var def = { + keyword: "allOf", + schemaType: "array", + code(cxt) { + const { gen, schema, it } = cxt; + if (!Array.isArray(schema)) + throw new Error("ajv implementation error"); + const valid = gen.name("valid"); + schema.forEach((sch, i) => { + if ((0, util_1.alwaysValidSchema)(it, sch)) + return; + const schCxt = cxt.subschema({ keyword: "allOf", schemaProp: i }, valid); + cxt.ok(valid); + cxt.mergeEvaluated(schCxt); + }); + } + }; + exports.default = def; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/applicator/if.js +var require_if2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/applicator/if.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var codegen_1 = require_codegen2(); + var util_1 = require_util2(); + var error61 = { + message: ({ params }) => (0, codegen_1.str)`must match "${params.ifClause}" schema`, + params: ({ params }) => (0, codegen_1._)`{failingKeyword: ${params.ifClause}}` + }; + var def = { + keyword: "if", + schemaType: ["object", "boolean"], + trackErrors: true, + error: error61, + code(cxt) { + const { gen, parentSchema, it } = cxt; + if (parentSchema.then === void 0 && parentSchema.else === void 0) { + (0, util_1.checkStrictMode)(it, '"if" without "then" and "else" is ignored'); + } + const hasThen = hasSchema(it, "then"); + const hasElse = hasSchema(it, "else"); + if (!hasThen && !hasElse) + return; + const valid = gen.let("valid", true); + const schValid = gen.name("_valid"); + validateIf(); + cxt.reset(); + if (hasThen && hasElse) { + const ifClause = gen.let("ifClause"); + cxt.setParams({ ifClause }); + gen.if(schValid, validateClause("then", ifClause), validateClause("else", ifClause)); + } else if (hasThen) { + gen.if(schValid, validateClause("then")); + } else { + gen.if((0, codegen_1.not)(schValid), validateClause("else")); + } + cxt.pass(valid, () => cxt.error(true)); + function validateIf() { + const schCxt = cxt.subschema({ + keyword: "if", + compositeRule: true, + createErrors: false, + allErrors: false + }, schValid); + cxt.mergeEvaluated(schCxt); + } + function validateClause(keyword, ifClause) { + return () => { + const schCxt = cxt.subschema({ keyword }, schValid); + gen.assign(valid, schValid); + cxt.mergeValidEvaluated(schCxt, valid); + if (ifClause) + gen.assign(ifClause, (0, codegen_1._)`${keyword}`); + else + cxt.setParams({ ifClause: keyword }); + }; + } + } + }; + function hasSchema(it, keyword) { + const schema = it.schema[keyword]; + return schema !== void 0 && !(0, util_1.alwaysValidSchema)(it, schema); + } + exports.default = def; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/applicator/thenElse.js +var require_thenElse2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/applicator/thenElse.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var util_1 = require_util2(); + var def = { + keyword: ["then", "else"], + schemaType: ["object", "boolean"], + code({ keyword, parentSchema, it }) { + if (parentSchema.if === void 0) + (0, util_1.checkStrictMode)(it, `"${keyword}" without "if" is ignored`); + } + }; + exports.default = def; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/applicator/index.js +var require_applicator2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/applicator/index.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var additionalItems_1 = require_additionalItems2(); + var prefixItems_1 = require_prefixItems2(); + var items_1 = require_items2(); + var items2020_1 = require_items20202(); + var contains_1 = require_contains2(); + var dependencies_1 = require_dependencies2(); + var propertyNames_1 = require_propertyNames2(); + var additionalProperties_1 = require_additionalProperties2(); + var properties_1 = require_properties2(); + var patternProperties_1 = require_patternProperties2(); + var not_1 = require_not2(); + var anyOf_1 = require_anyOf2(); + var oneOf_1 = require_oneOf2(); + var allOf_1 = require_allOf2(); + var if_1 = require_if2(); + var thenElse_1 = require_thenElse2(); + function getApplicator(draft2020 = false) { + const applicator = [ + // any + not_1.default, + anyOf_1.default, + oneOf_1.default, + allOf_1.default, + if_1.default, + thenElse_1.default, + // object + propertyNames_1.default, + additionalProperties_1.default, + dependencies_1.default, + properties_1.default, + patternProperties_1.default + ]; + if (draft2020) + applicator.push(prefixItems_1.default, items2020_1.default); + else + applicator.push(additionalItems_1.default, items_1.default); + applicator.push(contains_1.default); + return applicator; + } + exports.default = getApplicator; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/format/format.js +var require_format3 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/format/format.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var codegen_1 = require_codegen2(); + var error61 = { + message: ({ schemaCode }) => (0, codegen_1.str)`must match format "${schemaCode}"`, + params: ({ schemaCode }) => (0, codegen_1._)`{format: ${schemaCode}}` + }; + var def = { + keyword: "format", + type: ["number", "string"], + schemaType: "string", + $data: true, + error: error61, + code(cxt, ruleType) { + const { gen, data, $data, schema, schemaCode, it } = cxt; + const { opts, errSchemaPath, schemaEnv, self } = it; + if (!opts.validateFormats) + return; + if ($data) + validate$DataFormat(); + else + validateFormat(); + function validate$DataFormat() { + const fmts = gen.scopeValue("formats", { + ref: self.formats, + code: opts.code.formats + }); + const fDef = gen.const("fDef", (0, codegen_1._)`${fmts}[${schemaCode}]`); + const fType = gen.let("fType"); + const format = gen.let("format"); + gen.if((0, codegen_1._)`typeof ${fDef} == "object" && !(${fDef} instanceof RegExp)`, () => gen.assign(fType, (0, codegen_1._)`${fDef}.type || "string"`).assign(format, (0, codegen_1._)`${fDef}.validate`), () => gen.assign(fType, (0, codegen_1._)`"string"`).assign(format, fDef)); + cxt.fail$data((0, codegen_1.or)(unknownFmt(), invalidFmt())); + function unknownFmt() { + if (opts.strictSchema === false) + return codegen_1.nil; + return (0, codegen_1._)`${schemaCode} && !${format}`; + } + function invalidFmt() { + const callFormat = schemaEnv.$async ? (0, codegen_1._)`(${fDef}.async ? await ${format}(${data}) : ${format}(${data}))` : (0, codegen_1._)`${format}(${data})`; + const validData = (0, codegen_1._)`(typeof ${format} == "function" ? ${callFormat} : ${format}.test(${data}))`; + return (0, codegen_1._)`${format} && ${format} !== true && ${fType} === ${ruleType} && !${validData}`; + } + } + function validateFormat() { + const formatDef = self.formats[schema]; + if (!formatDef) { + unknownFormat(); + return; + } + if (formatDef === true) + return; + const [fmtType, format, fmtRef] = getFormat(formatDef); + if (fmtType === ruleType) + cxt.pass(validCondition()); + function unknownFormat() { + if (opts.strictSchema === false) { + self.logger.warn(unknownMsg()); + return; + } + throw new Error(unknownMsg()); + function unknownMsg() { + return `unknown format "${schema}" ignored in schema at path "${errSchemaPath}"`; + } + } + function getFormat(fmtDef) { + const code = fmtDef instanceof RegExp ? (0, codegen_1.regexpCode)(fmtDef) : opts.code.formats ? (0, codegen_1._)`${opts.code.formats}${(0, codegen_1.getProperty)(schema)}` : void 0; + const fmt = gen.scopeValue("formats", { key: schema, ref: fmtDef, code }); + if (typeof fmtDef == "object" && !(fmtDef instanceof RegExp)) { + return [fmtDef.type || "string", fmtDef.validate, (0, codegen_1._)`${fmt}.validate`]; + } + return ["string", fmtDef, fmt]; + } + function validCondition() { + if (typeof formatDef == "object" && !(formatDef instanceof RegExp) && formatDef.async) { + if (!schemaEnv.$async) + throw new Error("async format in sync schema"); + return (0, codegen_1._)`await ${fmtRef}(${data})`; + } + return typeof format == "function" ? (0, codegen_1._)`${fmtRef}(${data})` : (0, codegen_1._)`${fmtRef}.test(${data})`; + } + } + } + }; + exports.default = def; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/format/index.js +var require_format4 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/format/index.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var format_1 = require_format3(); + var format = [format_1.default]; + exports.default = format; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/metadata.js +var require_metadata2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/metadata.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.contentVocabulary = exports.metadataVocabulary = void 0; + exports.metadataVocabulary = [ + "title", + "description", + "default", + "deprecated", + "readOnly", + "writeOnly", + "examples" + ]; + exports.contentVocabulary = [ + "contentMediaType", + "contentEncoding", + "contentSchema" + ]; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/draft7.js +var require_draft72 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/draft7.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var core_1 = require_core4(); + var validation_1 = require_validation2(); + var applicator_1 = require_applicator2(); + var format_1 = require_format4(); + var metadata_1 = require_metadata2(); + var draft7Vocabularies = [ + core_1.default, + validation_1.default, + (0, applicator_1.default)(), + format_1.default, + metadata_1.metadataVocabulary, + metadata_1.contentVocabulary + ]; + exports.default = draft7Vocabularies; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/discriminator/types.js +var require_types2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/discriminator/types.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.DiscrError = void 0; + var DiscrError; + (function(DiscrError2) { + DiscrError2["Tag"] = "tag"; + DiscrError2["Mapping"] = "mapping"; + })(DiscrError || (exports.DiscrError = DiscrError = {})); + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/discriminator/index.js +var require_discriminator2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/discriminator/index.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var codegen_1 = require_codegen2(); + var types_1 = require_types2(); + var compile_1 = require_compile2(); + var ref_error_1 = require_ref_error2(); + var util_1 = require_util2(); + var error61 = { + message: ({ params: { discrError, tagName } }) => discrError === types_1.DiscrError.Tag ? `tag "${tagName}" must be string` : `value of tag "${tagName}" must be in oneOf`, + params: ({ params: { discrError, tag, tagName } }) => (0, codegen_1._)`{error: ${discrError}, tag: ${tagName}, tagValue: ${tag}}` + }; + var def = { + keyword: "discriminator", + type: "object", + schemaType: "object", + error: error61, + code(cxt) { + const { gen, data, schema, parentSchema, it } = cxt; + const { oneOf } = parentSchema; + if (!it.opts.discriminator) { + throw new Error("discriminator: requires discriminator option"); + } + const tagName = schema.propertyName; + if (typeof tagName != "string") + throw new Error("discriminator: requires propertyName"); + if (schema.mapping) + throw new Error("discriminator: mapping is not supported"); + if (!oneOf) + throw new Error("discriminator: requires oneOf keyword"); + const valid = gen.let("valid", false); + const tag = gen.const("tag", (0, codegen_1._)`${data}${(0, codegen_1.getProperty)(tagName)}`); + gen.if((0, codegen_1._)`typeof ${tag} == "string"`, () => validateMapping(), () => cxt.error(false, { discrError: types_1.DiscrError.Tag, tag, tagName })); + cxt.ok(valid); + function validateMapping() { + const mapping = getMapping(); + gen.if(false); + for (const tagValue in mapping) { + gen.elseIf((0, codegen_1._)`${tag} === ${tagValue}`); + gen.assign(valid, applyTagSchema(mapping[tagValue])); + } + gen.else(); + cxt.error(false, { discrError: types_1.DiscrError.Mapping, tag, tagName }); + gen.endIf(); + } + function applyTagSchema(schemaProp) { + const _valid = gen.name("valid"); + const schCxt = cxt.subschema({ keyword: "oneOf", schemaProp }, _valid); + cxt.mergeEvaluated(schCxt, codegen_1.Name); + return _valid; + } + function getMapping() { + var _a3; + const oneOfMapping = {}; + const topRequired = hasRequired(parentSchema); + let tagRequired = true; + for (let i = 0; i < oneOf.length; i++) { + let sch = oneOf[i]; + if ((sch === null || sch === void 0 ? void 0 : sch.$ref) && !(0, util_1.schemaHasRulesButRef)(sch, it.self.RULES)) { + const ref = sch.$ref; + sch = compile_1.resolveRef.call(it.self, it.schemaEnv.root, it.baseId, ref); + if (sch instanceof compile_1.SchemaEnv) + sch = sch.schema; + if (sch === void 0) + throw new ref_error_1.default(it.opts.uriResolver, it.baseId, ref); + } + const propSch = (_a3 = sch === null || sch === void 0 ? void 0 : sch.properties) === null || _a3 === void 0 ? void 0 : _a3[tagName]; + if (typeof propSch != "object") { + throw new Error(`discriminator: oneOf subschemas (or referenced schemas) must have "properties/${tagName}"`); + } + tagRequired = tagRequired && (topRequired || hasRequired(sch)); + addMappings(propSch, i); + } + if (!tagRequired) + throw new Error(`discriminator: "${tagName}" must be required`); + return oneOfMapping; + function hasRequired({ required: required2 }) { + return Array.isArray(required2) && required2.includes(tagName); + } + function addMappings(sch, i) { + if (sch.const) { + addMapping(sch.const, i); + } else if (sch.enum) { + for (const tagValue of sch.enum) { + addMapping(tagValue, i); + } + } else { + throw new Error(`discriminator: "properties/${tagName}" must have "const" or "enum"`); + } + } + function addMapping(tagValue, i) { + if (typeof tagValue != "string" || tagValue in oneOfMapping) { + throw new Error(`discriminator: "${tagName}" values must be unique strings`); + } + oneOfMapping[tagValue] = i; + } + } + } + }; + exports.default = def; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/refs/json-schema-draft-07.json +var require_json_schema_draft_072 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/refs/json-schema-draft-07.json"(exports, module) { + module.exports = { + $schema: "http://json-schema.org/draft-07/schema#", + $id: "http://json-schema.org/draft-07/schema#", + title: "Core schema meta-schema", + definitions: { + schemaArray: { + type: "array", + minItems: 1, + items: { $ref: "#" } + }, + nonNegativeInteger: { + type: "integer", + minimum: 0 + }, + nonNegativeIntegerDefault0: { + allOf: [{ $ref: "#/definitions/nonNegativeInteger" }, { default: 0 }] + }, + simpleTypes: { + enum: ["array", "boolean", "integer", "null", "number", "object", "string"] + }, + stringArray: { + type: "array", + items: { type: "string" }, + uniqueItems: true, + default: [] + } + }, + type: ["object", "boolean"], + properties: { + $id: { + type: "string", + format: "uri-reference" + }, + $schema: { + type: "string", + format: "uri" + }, + $ref: { + type: "string", + format: "uri-reference" + }, + $comment: { + type: "string" + }, + title: { + type: "string" + }, + description: { + type: "string" + }, + default: true, + readOnly: { + type: "boolean", + default: false + }, + examples: { + type: "array", + items: true + }, + multipleOf: { + type: "number", + exclusiveMinimum: 0 + }, + maximum: { + type: "number" + }, + exclusiveMaximum: { + type: "number" + }, + minimum: { + type: "number" + }, + exclusiveMinimum: { + type: "number" + }, + maxLength: { $ref: "#/definitions/nonNegativeInteger" }, + minLength: { $ref: "#/definitions/nonNegativeIntegerDefault0" }, + pattern: { + type: "string", + format: "regex" + }, + additionalItems: { $ref: "#" }, + items: { + anyOf: [{ $ref: "#" }, { $ref: "#/definitions/schemaArray" }], + default: true + }, + maxItems: { $ref: "#/definitions/nonNegativeInteger" }, + minItems: { $ref: "#/definitions/nonNegativeIntegerDefault0" }, + uniqueItems: { + type: "boolean", + default: false + }, + contains: { $ref: "#" }, + maxProperties: { $ref: "#/definitions/nonNegativeInteger" }, + minProperties: { $ref: "#/definitions/nonNegativeIntegerDefault0" }, + required: { $ref: "#/definitions/stringArray" }, + additionalProperties: { $ref: "#" }, + definitions: { + type: "object", + additionalProperties: { $ref: "#" }, + default: {} + }, + properties: { + type: "object", + additionalProperties: { $ref: "#" }, + default: {} + }, + patternProperties: { + type: "object", + additionalProperties: { $ref: "#" }, + propertyNames: { format: "regex" }, + default: {} + }, + dependencies: { + type: "object", + additionalProperties: { + anyOf: [{ $ref: "#" }, { $ref: "#/definitions/stringArray" }] + } + }, + propertyNames: { $ref: "#" }, + const: true, + enum: { + type: "array", + items: true, + minItems: 1, + uniqueItems: true + }, + type: { + anyOf: [ + { $ref: "#/definitions/simpleTypes" }, + { + type: "array", + items: { $ref: "#/definitions/simpleTypes" }, + minItems: 1, + uniqueItems: true + } + ] + }, + format: { type: "string" }, + contentMediaType: { type: "string" }, + contentEncoding: { type: "string" }, + if: { $ref: "#" }, + then: { $ref: "#" }, + else: { $ref: "#" }, + allOf: { $ref: "#/definitions/schemaArray" }, + anyOf: { $ref: "#/definitions/schemaArray" }, + oneOf: { $ref: "#/definitions/schemaArray" }, + not: { $ref: "#" } + }, + default: true + }; + } +}); + +// node_modules/ajv-formats/node_modules/ajv/dist/ajv.js +var require_ajv2 = __commonJS({ + "node_modules/ajv-formats/node_modules/ajv/dist/ajv.js"(exports, module) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.MissingRefError = exports.ValidationError = exports.CodeGen = exports.Name = exports.nil = exports.stringify = exports.str = exports._ = exports.KeywordCxt = exports.Ajv = void 0; + var core_1 = require_core3(); + var draft7_1 = require_draft72(); + var discriminator_1 = require_discriminator2(); + var draft7MetaSchema = require_json_schema_draft_072(); + var META_SUPPORT_DATA = ["/properties"]; + var META_SCHEMA_ID = "http://json-schema.org/draft-07/schema"; + var Ajv2 = class extends core_1.default { + _addVocabularies() { + super._addVocabularies(); + draft7_1.default.forEach((v2) => this.addVocabulary(v2)); + if (this.opts.discriminator) + this.addKeyword(discriminator_1.default); + } + _addDefaultMetaSchema() { + super._addDefaultMetaSchema(); + if (!this.opts.meta) + return; + const metaSchema = this.opts.$data ? this.$dataMetaSchema(draft7MetaSchema, META_SUPPORT_DATA) : draft7MetaSchema; + this.addMetaSchema(metaSchema, META_SCHEMA_ID, false); + this.refs["http://json-schema.org/schema"] = META_SCHEMA_ID; + } + defaultMeta() { + return this.opts.defaultMeta = super.defaultMeta() || (this.getSchema(META_SCHEMA_ID) ? META_SCHEMA_ID : void 0); + } + }; + exports.Ajv = Ajv2; + module.exports = exports = Ajv2; + module.exports.Ajv = Ajv2; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.default = Ajv2; + var validate_1 = require_validate2(); Object.defineProperty(exports, "KeywordCxt", { enumerable: true, get: function() { return validate_1.KeywordCxt; } }); - var codegen_1 = require_codegen(); + var codegen_1 = require_codegen2(); Object.defineProperty(exports, "_", { enumerable: true, get: function() { return codegen_1._; } }); @@ -6866,228 +12680,25 @@ var require_ajv = __commonJS({ Object.defineProperty(exports, "CodeGen", { enumerable: true, get: function() { return codegen_1.CodeGen; } }); - var validation_error_1 = require_validation_error(); + var validation_error_1 = require_validation_error2(); Object.defineProperty(exports, "ValidationError", { enumerable: true, get: function() { return validation_error_1.default; } }); - var ref_error_1 = require_ref_error(); + var ref_error_1 = require_ref_error2(); Object.defineProperty(exports, "MissingRefError", { enumerable: true, get: function() { return ref_error_1.default; } }); } }); -// node_modules/ajv-formats/dist/formats.js -var require_formats = __commonJS({ - "node_modules/ajv-formats/dist/formats.js"(exports) { - "use strict"; - Object.defineProperty(exports, "__esModule", { value: true }); - exports.formatNames = exports.fastFormats = exports.fullFormats = void 0; - function fmtDef(validate2, compare) { - return { validate: validate2, compare }; - } - exports.fullFormats = { - // date: http://tools.ietf.org/html/rfc3339#section-5.6 - date: fmtDef(date5, compareDate), - // date-time: http://tools.ietf.org/html/rfc3339#section-5.6 - time: fmtDef(getTime(true), compareTime), - "date-time": fmtDef(getDateTime(true), compareDateTime), - "iso-time": fmtDef(getTime(), compareIsoTime), - "iso-date-time": fmtDef(getDateTime(), compareIsoDateTime), - // duration: https://tools.ietf.org/html/rfc3339#appendix-A - duration: /^P(?!$)((\d+Y)?(\d+M)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+S)?)?|(\d+W)?)$/, - uri, - "uri-reference": /^(?:[a-z][a-z0-9+\-.]*:)?(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'"()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*)?(?:\?(?:[a-z0-9\-._~!$&'"()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'"()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i, - // uri-template: https://tools.ietf.org/html/rfc6570 - "uri-template": /^(?:(?:[^\x00-\x20"'<>%\\^`{|}]|%[0-9a-f]{2})|\{[+#./;?&=,!@|]?(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\*)?(?:,(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\*)?)*\})*$/i, - // For the source: https://gist.github.com/dperini/729294 - // For test cases: https://mathiasbynens.be/demo/url-regex - url: /^(?:https?|ftp):\/\/(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u{00a1}-\u{ffff}]+-)*[a-z0-9\u{00a1}-\u{ffff}]+)(?:\.(?:[a-z0-9\u{00a1}-\u{ffff}]+-)*[a-z0-9\u{00a1}-\u{ffff}]+)*(?:\.(?:[a-z\u{00a1}-\u{ffff}]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?$/iu, - email: /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i, - hostname: /^(?=.{1,253}\.?$)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[-0-9a-z]{0,61}[0-9a-z])?)*\.?$/i, - // optimized https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9780596802837/ch07s16.html - ipv4: /^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/, - ipv6: /^((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))$/i, - regex, - // uuid: http://tools.ietf.org/html/rfc4122 - uuid: /^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i, - // JSON-pointer: https://tools.ietf.org/html/rfc6901 - // uri fragment: https://tools.ietf.org/html/rfc3986#appendix-A - "json-pointer": /^(?:\/(?:[^~/]|~0|~1)*)*$/, - "json-pointer-uri-fragment": /^#(?:\/(?:[a-z0-9_\-.!$&'()*+,;:=@]|%[0-9a-f]{2}|~0|~1)*)*$/i, - // relative JSON-pointer: http://tools.ietf.org/html/draft-luff-relative-json-pointer-00 - "relative-json-pointer": /^(?:0|[1-9][0-9]*)(?:#|(?:\/(?:[^~/]|~0|~1)*)*)$/, - // the following formats are used by the openapi specification: https://spec.openapis.org/oas/v3.0.0#data-types - // byte: https://github.com/miguelmota/is-base64 - byte, - // signed 32 bit integer - int32: { type: "number", validate: validateInt32 }, - // signed 64 bit integer - int64: { type: "number", validate: validateInt64 }, - // C-type float - float: { type: "number", validate: validateNumber }, - // C-type double - double: { type: "number", validate: validateNumber }, - // hint to the UI to hide input strings - password: true, - // unchecked string payload - binary: true - }; - exports.fastFormats = { - ...exports.fullFormats, - date: fmtDef(/^\d\d\d\d-[0-1]\d-[0-3]\d$/, compareDate), - time: fmtDef(/^(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)$/i, compareTime), - "date-time": fmtDef(/^\d\d\d\d-[0-1]\d-[0-3]\dt(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)$/i, compareDateTime), - "iso-time": fmtDef(/^(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)?$/i, compareIsoTime), - "iso-date-time": fmtDef(/^\d\d\d\d-[0-1]\d-[0-3]\d[t\s](?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)?$/i, compareIsoDateTime), - // uri: https://github.com/mafintosh/is-my-json-valid/blob/master/formats.js - uri: /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/)?[^\s]*$/i, - "uri-reference": /^(?:(?:[a-z][a-z0-9+\-.]*:)?\/?\/)?(?:[^\\\s#][^\s#]*)?(?:#[^\\\s]*)?$/i, - // email (sources from jsen validator): - // http://stackoverflow.com/questions/201323/using-a-regular-expression-to-validate-an-email-address#answer-8829363 - // http://www.w3.org/TR/html5/forms.html#valid-e-mail-address (search for 'wilful violation') - email: /^[a-z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/i - }; - exports.formatNames = Object.keys(exports.fullFormats); - function isLeapYear(year) { - return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0); - } - var DATE = /^(\d\d\d\d)-(\d\d)-(\d\d)$/; - var DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; - function date5(str) { - const matches = DATE.exec(str); - if (!matches) - return false; - const year = +matches[1]; - const month = +matches[2]; - const day = +matches[3]; - return month >= 1 && month <= 12 && day >= 1 && day <= (month === 2 && isLeapYear(year) ? 29 : DAYS[month]); - } - function compareDate(d1, d2) { - if (!(d1 && d2)) - return void 0; - if (d1 > d2) - return 1; - if (d1 < d2) - return -1; - return 0; - } - var TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i; - function getTime(strictTimeZone) { - return function time3(str) { - const matches = TIME.exec(str); - if (!matches) - return false; - const hr = +matches[1]; - const min = +matches[2]; - const sec = +matches[3]; - const tz = matches[4]; - const tzSign = matches[5] === "-" ? -1 : 1; - const tzH = +(matches[6] || 0); - const tzM = +(matches[7] || 0); - if (tzH > 23 || tzM > 59 || strictTimeZone && !tz) - return false; - if (hr <= 23 && min <= 59 && sec < 60) - return true; - const utcMin = min - tzM * tzSign; - const utcHr = hr - tzH * tzSign - (utcMin < 0 ? 1 : 0); - return (utcHr === 23 || utcHr === -1) && (utcMin === 59 || utcMin === -1) && sec < 61; - }; - } - function compareTime(s1, s2) { - if (!(s1 && s2)) - return void 0; - const t1 = (/* @__PURE__ */ new Date("2020-01-01T" + s1)).valueOf(); - const t2 = (/* @__PURE__ */ new Date("2020-01-01T" + s2)).valueOf(); - if (!(t1 && t2)) - return void 0; - return t1 - t2; - } - function compareIsoTime(t1, t2) { - if (!(t1 && t2)) - return void 0; - const a1 = TIME.exec(t1); - const a2 = TIME.exec(t2); - if (!(a1 && a2)) - return void 0; - t1 = a1[1] + a1[2] + a1[3]; - t2 = a2[1] + a2[2] + a2[3]; - if (t1 > t2) - return 1; - if (t1 < t2) - return -1; - return 0; - } - var DATE_TIME_SEPARATOR = /t|\s/i; - function getDateTime(strictTimeZone) { - const time3 = getTime(strictTimeZone); - return function date_time(str) { - const dateTime = str.split(DATE_TIME_SEPARATOR); - return dateTime.length === 2 && date5(dateTime[0]) && time3(dateTime[1]); - }; - } - function compareDateTime(dt1, dt2) { - if (!(dt1 && dt2)) - return void 0; - const d1 = new Date(dt1).valueOf(); - const d2 = new Date(dt2).valueOf(); - if (!(d1 && d2)) - return void 0; - return d1 - d2; - } - function compareIsoDateTime(dt1, dt2) { - if (!(dt1 && dt2)) - return void 0; - const [d1, t1] = dt1.split(DATE_TIME_SEPARATOR); - const [d2, t2] = dt2.split(DATE_TIME_SEPARATOR); - const res = compareDate(d1, d2); - if (res === void 0) - return void 0; - return res || compareTime(t1, t2); - } - var NOT_URI_FRAGMENT = /\/|:/; - var URI = /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i; - function uri(str) { - return NOT_URI_FRAGMENT.test(str) && URI.test(str); - } - var BYTE = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/gm; - function byte(str) { - BYTE.lastIndex = 0; - return BYTE.test(str); - } - var MIN_INT32 = -(2 ** 31); - var MAX_INT32 = 2 ** 31 - 1; - function validateInt32(value) { - return Number.isInteger(value) && value <= MAX_INT32 && value >= MIN_INT32; - } - function validateInt64(value) { - return Number.isInteger(value); - } - function validateNumber() { - return true; - } - var Z_ANCHOR = /[^\\]\\Z/; - function regex(str) { - if (Z_ANCHOR.test(str)) - return false; - try { - new RegExp(str); - return true; - } catch (e) { - return false; - } - } - } -}); - // node_modules/ajv-formats/dist/limit.js var require_limit = __commonJS({ "node_modules/ajv-formats/dist/limit.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.formatLimitDefinition = void 0; - var ajv_1 = require_ajv(); - var codegen_1 = require_codegen(); + var ajv_1 = require_ajv2(); + var codegen_1 = require_codegen2(); var ops = codegen_1.operators; var KWDs = { formatMaximum: { okStr: "<=", ok: ops.LTE, fail: ops.GT }, @@ -7159,7 +12770,7 @@ var require_dist = __commonJS({ Object.defineProperty(exports, "__esModule", { value: true }); var formats_1 = require_formats(); var limit_1 = require_limit(); - var codegen_1 = require_codegen(); + var codegen_1 = require_codegen2(); var fullName = new codegen_1.Name("fullFormats"); var fastName = new codegen_1.Name("fastFormats"); var formatsPlugin = (ajv, opts = { keywords: true }) => { @@ -7181,12 +12792,12 @@ var require_dist = __commonJS({ throw new Error(`Unknown format "${name}"`); return f; }; - function addFormats(ajv, list, fs5, exportName) { + function addFormats(ajv, list, fs6, exportName) { var _a3; var _b; (_a3 = (_b = ajv.opts.code).formats) !== null && _a3 !== void 0 ? _a3 : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`; for (const f of list) - ajv.addFormat(f, fs5[f]); + ajv.addFormat(f, fs6[f]); } module.exports = exports = formatsPlugin; Object.defineProperty(exports, "__esModule", { value: true }); @@ -7637,9 +13248,9 @@ function floatSafeRemainder(val, step) { return ratio - roundedRatio; } var EVALUATING = /* @__PURE__ */ Symbol("evaluating"); -function defineLazy(object3, key, getter) { +function defineLazy(object4, key, getter) { let value = void 0; - Object.defineProperty(object3, key, { + Object.defineProperty(object4, key, { get() { if (value === EVALUATING) { return void 0; @@ -7651,7 +13262,7 @@ function defineLazy(object3, key, getter) { return value; }, set(v2) { - Object.defineProperty(object3, key, { + Object.defineProperty(object4, key, { value: v2 // configurable: true, }); @@ -7681,18 +13292,18 @@ function mergeDefs(...defs) { function cloneDef(schema) { return mergeDefs(schema._zod.def); } -function getElementAtPath(obj, path3) { - if (!path3) +function getElementAtPath(obj, path4) { + if (!path4) return obj; - return path3.reduce((acc, key) => acc?.[key], obj); + return path4.reduce((acc, key) => acc?.[key], obj); } function promiseAllObject(promisesObj) { - const keys = Object.keys(promisesObj); - const promises = keys.map((key) => promisesObj[key]); + const keys2 = Object.keys(promisesObj); + const promises = keys2.map((key) => promisesObj[key]); return Promise.all(promises).then((results) => { const resolvedObj = {}; - for (let i = 0; i < keys.length; i++) { - resolvedObj[keys[i]] = results[i]; + for (let i = 0; i < keys2.length; i++) { + resolvedObj[keys2[i]] = results[i]; } return resolvedObj; }); @@ -8096,11 +13707,11 @@ function explicitlyAborted(x2, startIndex = 0) { } return false; } -function prefixIssues(path3, issues) { +function prefixIssues(path4, issues) { return issues.map((iss) => { var _a3; (_a3 = iss).path ?? (_a3.path = []); - iss.path.unshift(path3); + iss.path.unshift(path4); return iss; }); } @@ -8533,16 +14144,16 @@ function flattenError(error61, mapper = (issue2) => issue2.message) { } function formatError(error61, mapper = (issue2) => issue2.message) { const fieldErrors = { _errors: [] }; - const processError = (error62, path3 = []) => { + const processError = (error62, path4 = []) => { for (const issue2 of error62.issues) { if (issue2.code === "invalid_union" && issue2.errors.length) { - issue2.errors.map((issues) => processError({ issues }, [...path3, ...issue2.path])); + issue2.errors.map((issues) => processError({ issues }, [...path4, ...issue2.path])); } else if (issue2.code === "invalid_key") { - processError({ issues: issue2.issues }, [...path3, ...issue2.path]); + processError({ issues: issue2.issues }, [...path4, ...issue2.path]); } else if (issue2.code === "invalid_element") { - processError({ issues: issue2.issues }, [...path3, ...issue2.path]); + processError({ issues: issue2.issues }, [...path4, ...issue2.path]); } else { - const fullpath = [...path3, ...issue2.path]; + const fullpath = [...path4, ...issue2.path]; if (fullpath.length === 0) { fieldErrors._errors.push(mapper(issue2)); } else { @@ -8581,17 +14192,17 @@ function formatError(error61, mapper = (issue2) => issue2.message) { } function treeifyError(error61, mapper = (issue2) => issue2.message) { const result = { errors: [] }; - const processError = (error62, path3 = []) => { + const processError = (error62, path4 = []) => { var _a3; for (const issue2 of error62.issues) { if (issue2.code === "invalid_union" && issue2.errors.length) { - issue2.errors.map((issues) => processError({ issues }, [...path3, ...issue2.path])); + issue2.errors.map((issues) => processError({ issues }, [...path4, ...issue2.path])); } else if (issue2.code === "invalid_key") { - processError({ issues: issue2.issues }, [...path3, ...issue2.path]); + processError({ issues: issue2.issues }, [...path4, ...issue2.path]); } else if (issue2.code === "invalid_element") { - processError({ issues: issue2.issues }, [...path3, ...issue2.path]); + processError({ issues: issue2.issues }, [...path4, ...issue2.path]); } else { - const fullpath = [...path3, ...issue2.path]; + const fullpath = [...path4, ...issue2.path]; if (fullpath.length === 0) { result.errors.push(mapper(issue2)); continue; @@ -8630,8 +14241,8 @@ function treeifyError(error61, mapper = (issue2) => issue2.message) { } function toDotPath(_path) { const segs = []; - const path3 = _path.map((seg) => typeof seg === "object" ? seg.key : seg); - for (const seg of path3) { + const path4 = _path.map((seg) => typeof seg === "object" ? seg.key : seg); + for (const seg of path4) { if (typeof seg === "number") segs.push(`[${seg}]`); else if (typeof seg === "symbol") @@ -10330,10 +15941,10 @@ function handlePropertyResult(result, final, key, input2, optin, optout) { } var NO_SYMBOL_KEYS = []; function normalizeDef(def) { - const keys = Object.keys(def.shape); + const keys2 = Object.keys(def.shape); const ownSymbols = Object.getOwnPropertySymbols(def.shape); const symbolKeys = ownSymbols.length ? ownSymbols : NO_SYMBOL_KEYS; - const allKeys = symbolKeys.length ? [...keys, ...symbolKeys] : keys; + const allKeys = symbolKeys.length ? [...keys2, ...symbolKeys] : keys2; for (const k2 of allKeys) { if (!def.shape?.[k2]?._zod?.traits?.has("$ZodType")) { throw new Error(`Invalid element at key "${String(k2)}": expected a Zod schema`); @@ -10345,8 +15956,8 @@ function normalizeDef(def) { allKeys, symbolKeys, // string-only: handleCatchall matches it against `for...in`, which never yields a symbol - keySet: new Set(keys), - numKeys: keys.length, + keySet: new Set(keys2), + numKeys: keys2.length, optionalKeys: new Set(okeys) }; } @@ -10864,19 +16475,19 @@ function handleIntersectionResults(result, left, right) { let unrecIssue; const keyIssues = /* @__PURE__ */ new Map(); const collect = (iss, side) => { - let keys; + let keys2; if (iss.code === "unrecognized_keys" && !iss.path?.length) { unrecIssue ?? (unrecIssue = iss); - keys = iss.keys; + keys2 = iss.keys; } else if (iss.code === "invalid_key" && iss.origin === "record" && iss.path?.length === 1) { const k2 = String(iss.path[0]); if (!keyIssues.has(k2)) keyIssues.set(k2, iss); - keys = [k2]; + keys2 = [k2]; } else { return false; } - for (const k2 of keys) { + for (const k2 of keys2) { if (!unrecKeys.has(k2)) unrecKeys.set(k2, {}); unrecKeys.get(k2)[side] = true; @@ -19439,8 +25050,8 @@ function registry() { var globalRegistry = globalThis.__zod_globalRegistry; // node_modules/zod/v4/core/compile.js -var INVALID = Symbol.for("zod.compile.invalid"); -var FALLBACK_FLAG = Symbol.for("zod.compile.fallback"); +var INVALID = /* @__PURE__ */ Symbol.for("zod.compile.invalid"); +var FALLBACK_FLAG = /* @__PURE__ */ Symbol.for("zod.compile.fallback"); var ZodCompileAsyncError = class extends Error { constructor(message = "z.compile does not support async refinements, transforms, or checks") { super(message); @@ -20160,13 +25771,13 @@ function generateObjectCheck(doc, ctx, schema, accessor, buildsValue = true) { const def = schema._zod.def; doc.write(`if (typeof ${accessor} !== "object" || ${accessor} === null || Array.isArray(${accessor})) return INVALID;`); const shape = def.shape; - const keys = Object.keys(shape); + const keys2 = Object.keys(shape); const symbolKeys = Object.getOwnPropertySymbols(shape); - const allKeys = symbolKeys.length ? [...keys, ...symbolKeys] : keys; + const allKeys = symbolKeys.length ? [...keys2, ...symbolKeys] : keys2; const keyExpr = (k2) => typeof k2 === "symbol" ? addConstant(ctx, k2) : esc(k2); const propKey = (k2) => typeof k2 === "symbol" ? `[${keyExpr(k2)}]` : esc(k2); const propShape = shape; - if (keys.includes("__proto__")) { + if (keys2.includes("__proto__")) { throw new ZodCompileUnsupportedError('object shape key "__proto__"'); } const propOutputs = /* @__PURE__ */ new Map(); @@ -20208,7 +25819,7 @@ function generateObjectCheck(doc, ctx, schema, accessor, buildsValue = true) { if (catchall) { const catchallType = catchall._zod.def.type; if (catchallType === "never") { - const condition = keys.map((k2) => `k !== ${esc(k2)}`).join(" && ") || "true"; + const condition = keys2.map((k2) => `k !== ${esc(k2)}`).join(" && ") || "true"; doc.write(`for (const k in ${accessor}) {`); doc.indented((d2) => { d2.write(`if (${condition}) return INVALID;`); @@ -20224,7 +25835,7 @@ function generateObjectCheck(doc, ctx, schema, accessor, buildsValue = true) { const hasConditionalKeys = allKeys.some((k2) => mayOutputUndefined(propShape[k2]) || dropsWhenAbsent(propShape[k2])); if (!buildsValue) { if (unknownKeysMode === "schema") { - const knownSet = keys.length > 0 ? addConstant(ctx, new Set(keys)) : null; + const knownSet = keys2.length > 0 ? addConstant(ctx, new Set(keys2)) : null; doc.write(`for (const k in ${accessor}) {`); doc.indented((d2) => { d2.write(`if (k === "__proto__") continue;`); @@ -20256,7 +25867,7 @@ function generateObjectCheck(doc, ctx, schema, accessor, buildsValue = true) { } } if (unknownKeysMode !== "none") { - const knownSet = keys.length > 0 ? addConstant(ctx, new Set(keys)) : null; + const knownSet = keys2.length > 0 ? addConstant(ctx, new Set(keys2)) : null; doc.write(`for (const k in ${accessor}) {`); doc.indented((d2) => { d2.write(`if (k === "__proto__") continue;`); @@ -22244,8 +27855,8 @@ function compactTypeUnion(schema) { if (!option || typeof option !== "object") return; compactTypeUnion(option); - const keys = Object.keys(option); - if (keys.length !== 1 || keys[0] !== "type") + const keys2 = Object.keys(option); + if (keys2.length !== 1 || keys2[0] !== "type") return; const type = option.type; for (const member of Array.isArray(type) ? type : [type]) { @@ -22279,8 +27890,8 @@ function foldObjects(members2) { } const properties = {}; const required2 = /* @__PURE__ */ new Set(); - for (const object3 of objects) { - for (const key in object3.properties) { + for (const object4 of objects) { + for (const key in object4.properties) { if (Object.prototype.hasOwnProperty.call(properties, key)) continue; const parts = []; @@ -22294,18 +27905,18 @@ function foldObjects(members2) { const merged = parts.length === 1 ? parts[0] : foldObjects(parts) ?? { allOf: parts }; assignProp(properties, key, merged); } - for (const key of object3.required ?? []) + for (const key of object4.required ?? []) required2.add(key); } const folded = { type: "object", properties }; if (required2.size) folded.required = [...required2]; - if (objects.every((object3) => object3.additionalProperties === false)) { + if (objects.every((object4) => object4.additionalProperties === false)) { folded.additionalProperties = false; } else { const constraints = []; - for (const object3 of objects) { - const constraint = undeclaredConstraint(object3); + for (const object4 of objects) { + const constraint = undeclaredConstraint(object4); if (constraint && !constraints.some((seen) => JSON.stringify(seen) === JSON.stringify(constraint))) constraints.push(constraint); } @@ -23069,7 +28680,7 @@ var nonoptionalProcessor = (schema, ctx, _json, params) => { const seen = ctx.seen.get(schema); seen.ref = def.innerType; }; -var UNREPRESENTABLE_DEFAULT = Symbol(); +var UNREPRESENTABLE_DEFAULT = /* @__PURE__ */ Symbol(); function serializeDefaultValue(value, schema, ctx, json2, params) { let unrepresentable = false; const serialized = JSON.stringify(value, (_2, val) => { @@ -23308,7 +28919,7 @@ var JSONSchemaGenerator = class { var json_schema_exports = {}; // node_modules/zod/v4/core/visit.js -var RESOLVING = Symbol("z.visit/resolving"); +var RESOLVING = /* @__PURE__ */ Symbol("z.visit/resolving"); function visit(schema, fnOrHandlers) { const fn = typeof fnOrHandlers === "function" ? fnOrHandlers : (node2, rewritten) => { const h = fnOrHandlers[node2._zod.def.type]; @@ -23337,10 +28948,10 @@ function visit(schema, fnOrHandlers) { switch (kind) { case "object": { const oldShape = def.shape; - const keys = Object.keys(oldShape); + const keys2 = Object.keys(oldShape); let changed = false; const newShape = {}; - for (const k2 of keys) { + for (const k2 of keys2) { const mapped = run(oldShape[k2]); if (mapped !== oldShape[k2]) changed = true; @@ -25062,11 +30673,11 @@ var ZodEnum = /* @__PURE__ */ $constructor("ZodEnum", (inst, def) => { inst._zod.processJSONSchema = (ctx, json2, params) => enumProcessor(inst, ctx, json2, params); inst.enum = def.entries; inst.options = Object.values(def.entries); - const keys = new Set(Object.keys(def.entries)); + const keys2 = new Set(Object.keys(def.entries)); inst.extract = (values, params) => { const newEntries = {}; for (const value of values) { - if (keys.has(value)) { + if (keys2.has(value)) { newEntries[value] = def.entries[value]; } else throw new Error(`Key ${value} not found in enum`); @@ -25081,7 +30692,7 @@ var ZodEnum = /* @__PURE__ */ $constructor("ZodEnum", (inst, def) => { inst.exclude = (values, params) => { const newEntries = { ...def.entries }; for (const value of values) { - if (keys.has(value)) { + if (keys2.has(value)) { delete newEntries[value]; } else throw new Error(`Key ${value} not found in enum`); @@ -25616,13 +31227,13 @@ function resolveRef(ref, ctx) { if (!ref.startsWith("#")) { throw new Error("External $ref is not supported, only local refs (#/...) are allowed"); } - const path3 = ref.slice(1).split("/").filter(Boolean); - if (path3.length === 0) { + const path4 = ref.slice(1).split("/").filter(Boolean); + if (path4.length === 0) { return ctx.rootSchema; } const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions"; - if (path3[0] === defsKey) { - const key = path3[1] === void 0 ? void 0 : decodeJSONPointerSegment(path3[1]); + if (path4[0] === defsKey) { + const key = path4[1] === void 0 ? void 0 : decodeJSONPointerSegment(path4[1]); if (!key || !ctx.defs[key]) { throw new Error(`Reference not found: ${ref}`); } @@ -27647,10 +33258,7 @@ function isTerminal(status) { return status === "completed" || status === "failed" || status === "cancelled"; } -// node_modules/zod-to-json-schema/dist/esm/Options.js -var ignoreOverride = Symbol("Let zodToJsonSchema decide on which parser to use"); - -// node_modules/zod-to-json-schema/dist/esm/parsers/string.js +// node_modules/@modelcontextprotocol/sdk/node_modules/zod-to-json-schema/dist/esm/parsers/string.js var ALPHA_NUMERIC = new Set("ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvxyz0123456789"); // node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-json-schema-compat.js @@ -29424,9 +35032,16 @@ var StdioServerTransport = class { // src/db.ts import Database from "better-sqlite3"; -import path2 from "path"; -import fs2 from "fs"; -import * as sqliteVec from "sqlite-vec"; + +// src/record-admission.ts +import fs2 from "node:fs"; +import path2 from "node:path"; +import crypto from "node:crypto"; +import os2 from "node:os"; +import { spawnSync } from "node:child_process"; + +// src/constants.ts +var SUMMARIZER_CONTEXT_MARKER = "Context: This summary will be shown in a list to help users and Claude choose which conversations are relevant"; // src/paths.ts import os from "os"; @@ -29464,6 +35079,430 @@ function getDbPath() { return path.join(getIndexDir(), "db.sqlite"); } +// src/record-admission.ts +var EXCLUSION_MARKERS = [ + "DO NOT INDEX THIS CHAT", + "Only use NO_INSIGHTS_FOUND", + SUMMARIZER_CONTEXT_MARKER +]; +var MARKER_SCAN_CHUNK_BYTES = 1 << 20; +function instructionText(record2) { + if (!object3(record2)) return ""; + if (record2.type === "event_msg" && object3(record2.payload)) { + return record2.payload.type === "user_message" && typeof record2.payload.message === "string" ? record2.payload.message : ""; + } + const message = record2.type === "response_item" ? record2.payload : record2.message; + if (!object3(message) || (message.role ?? record2.role) !== "user") return ""; + const content = record2.type === "opencode_message" ? record2.parts : message.content; + if (typeof content === "string") return content; + if (!Array.isArray(content)) return ""; + return content.filter((part) => object3(part) && ["text", "input_text"].includes(String(part.type)) && typeof part.text === "string").map((part) => part.text).join("\n"); +} +function descriptorHasExclusionMarker(fd, byteLimit = Infinity, digest) { + if (!fs2.fstatSync(fd).isFile()) throw new Error("Conversation input must be a regular file"); + const buffer = Buffer.allocUnsafe(MARKER_SCAN_CHUNK_BYTES); + const decoder = new TextDecoder("utf-8", { fatal: true }); + let pending = Buffer.alloc(0); + let position = 0; + const excluded = (bytes) => { + const line = decoder.decode(bytes); + let text; + try { + text = instructionText(JSON.parse(line)); + } catch { + text = line; + } + return EXCLUSION_MARKERS.some((marker) => text.includes(marker)); + }; + let count; + while (position < byteLimit && (count = fs2.readSync(fd, buffer, 0, Math.min(buffer.length, byteLimit - position), position)) > 0) { + position += count; + digest?.update(buffer.subarray(0, count)); + pending = Buffer.concat([pending, buffer.subarray(0, count)]); + let newline; + while ((newline = pending.indexOf(10)) !== -1) { + if (newline > 64 * 1024 * 1024) throw new Error("Conversation record exceeds admission byte limit"); + if (excluded(pending.subarray(0, newline))) return true; + pending = pending.subarray(newline + 1); + } + if (pending.length > 64 * 1024 * 1024) throw new Error("Conversation record exceeds admission byte limit"); + } + if (position < byteLimit && byteLimit !== Infinity) throw new Error("Conversation changed during record admission"); + return pending.length > 0 && excluded(pending); +} +function assertUnchangedInput(file2, initial) { + const current = fs2.lstatSync(file2); + if (!current.isFile() || initial.dev !== current.dev || initial.ino !== current.ino || initial.size !== current.size || initial.mtimeMs !== current.mtimeMs || initial.ctimeMs !== current.ctimeMs) { + throw new Error("Conversation changed during record admission"); + } +} +function assertUnchangedSearchPrefix(file2, initial, expectedDigest) { + const changed = () => new Error("Conversation changed during record admission"); + let current; + try { + current = fs2.lstatSync(file2); + } catch { + throw changed(); + } + if (!current.isFile() || initial.dev !== current.dev || initial.ino !== current.ino || current.size < initial.size) { + throw changed(); + } + if (current.size === initial.size && current.mtimeMs === initial.mtimeMs && current.ctimeMs === initial.ctimeMs) return; + let fd; + try { + fd = fs2.openSync(file2, fs2.constants.O_RDONLY | fs2.constants.O_NOFOLLOW); + const opened = fs2.fstatSync(fd); + if (!opened.isFile() || opened.dev !== initial.dev || opened.ino !== initial.ino || opened.size < initial.size) throw changed(); + const digest = crypto.createHash("sha256"); + const buffer = Buffer.allocUnsafe(MARKER_SCAN_CHUNK_BYTES); + let position = 0; + while (position < initial.size) { + const count = fs2.readSync(fd, buffer, 0, Math.min(buffer.length, initial.size - position), position); + if (count <= 0) throw changed(); + digest.update(buffer.subarray(0, count)); + position += count; + } + if (digest.digest("hex") !== expectedDigest) throw changed(); + current = fs2.lstatSync(file2); + if (!current.isFile() || current.dev !== initial.dev || current.ino !== initial.ino || current.size < initial.size) throw changed(); + } catch { + throw changed(); + } finally { + if (fd !== void 0) fs2.closeSync(fd); + } +} +function assertUnchangedPolicy(expected) { + if (JSON.stringify(readRecordExclusions()) !== expected) { + throw new Error("Record exclusion policy changed during record admission"); + } +} +var POLICY_ERROR = "Invalid or unreadable record exclusion policy"; +var UUID = /[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}/ig; +function object3(value) { + return value !== null && typeof value === "object" && !Array.isArray(value); +} +function keys(value, expected) { + return Object.keys(value).length === expected.length && expected.every((key) => key in value); +} +function transcriptName(value) { + return identity(value) && !/[\\/]/.test(value) && value.endsWith(".jsonl") && value !== ".jsonl"; +} +function identity(value) { + return typeof value === "string" && value.length > 0 && value.length <= 512 && !/[\x00-\x1f]/.test(value); +} +function screeningText(value, depth = 0) { + if (depth > 24) throw new Error("Record screening nesting limit exceeded"); + if (typeof value === "string") { + let decoded; + try { + decoded = JSON.parse(value); + } catch { + return value; + } + if (typeof decoded === "string" || object3(decoded) || Array.isArray(decoded)) { + return screeningText(decoded, depth + 1); + } + return value; + } + if (Array.isArray(value)) return value.map((item) => screeningText(item, depth + 1)).join("\n"); + if (object3(value)) { + return Object.entries(value).map(([key, item]) => `${key} = ${screeningText(item, depth + 1)}`).join("\n"); + } + return value == null ? "" : String(value); +} +function createRecordScreener(policy) { + const config2 = policy?.screening; + if (!config2) return { scan() { + }, close() { + } }; + const unavailable = () => new Error("Record screening unavailable"); + let initial; + let fd; + try { + if (fs2.realpathSync(config2.executable) !== config2.executable) throw unavailable(); + fd = fs2.openSync(config2.executable, fs2.constants.O_RDONLY | fs2.constants.O_NOFOLLOW); + initial = fs2.fstatSync(fd); + if (!initial.isFile() || initial.size < 1 || initial.size > 134217728 || (initial.mode & 18) !== 0 || typeof process.getuid === "function" && initial.uid !== 0 && initial.uid !== process.getuid()) { + throw unavailable(); + } + fs2.accessSync(config2.executable, fs2.constants.X_OK); + const digest = crypto.createHash("sha256").update(fs2.readFileSync(fd)).digest("hex"); + if (digest !== config2.sha256) throw unavailable(); + } catch { + throw unavailable(); + } finally { + if (fd !== void 0) fs2.closeSync(fd); + } + let directory; + try { + directory = fs2.mkdtempSync(path2.join(os2.tmpdir(), "episodic-screen-")); + fs2.chmodSync(directory, 448); + } catch { + throw unavailable(); + } + const unchanged = () => { + try { + const current = fs2.lstatSync(config2.executable); + if (!current.isFile() || current.dev !== initial.dev || current.ino !== initial.ino || current.size !== initial.size || current.mtimeMs !== initial.mtimeMs || current.ctimeMs !== initial.ctimeMs) throw unavailable(); + } catch { + throw unavailable(); + } + }; + return { + scan(value) { + const raw = typeof value === "string" ? value : JSON.stringify(value); + if (raw === void 0) return; + if (Buffer.byteLength(raw) > config2.max_record_bytes) throw new Error("Record screening byte limit exceeded"); + const input2 = screeningText(value); + if (Buffer.byteLength(input2) > config2.max_record_bytes) throw new Error("Record screening byte limit exceeded"); + if (!input2.trim()) return; + unchanged(); + const result = spawnSync(config2.executable, [ + "stdin", + "--redact", + "--no-banner", + "--log-level", + "error", + "--ignore-gitleaks-allow", + "--exit-code", + "10", + "--report-format", + "json", + "--report-path", + "-" + ], { + input: input2, + encoding: "utf8", + cwd: directory, + shell: false, + env: { HOME: directory, USERPROFILE: directory, PATH: path2.dirname(config2.executable) }, + timeout: config2.timeout_ms, + killSignal: "SIGKILL", + maxBuffer: 1048576, + windowsHide: true + }); + unchanged(); + if (result.error || result.signal || ![0, 10].includes(result.status ?? -1)) throw unavailable(); + let report; + try { + report = JSON.parse(result.stdout); + } catch { + throw unavailable(); + } + if (!Array.isArray(report) || result.status === 0 && report.length !== 0 || result.status === 10 && (report.length === 0 || !report.every((item) => object3(item) && typeof item.RuleID === "string"))) { + throw unavailable(); + } + if (result.status === 10) throw new Error("Record content rejected by screening"); + }, + close() { + fs2.rmSync(directory, { recursive: true, force: true }); + } + }; +} +function readRecordExclusions() { + const file2 = path2.join(getSuperpowersDir(), "record-exclusions.json"); + let fd; + try { + fd = fs2.openSync(file2, fs2.constants.O_RDONLY | fs2.constants.O_NOFOLLOW); + const info = fs2.fstatSync(fd); + if (!info.isFile() || info.size > 4 * 1024 * 1024) throw new Error(POLICY_ERROR); + const value = JSON.parse(fs2.readFileSync(fd, "utf8")); + if (!object3(value) || !keys(value, "screening" in value ? ["version", "exchanges", "tool_calls", "screening"] : ["version", "exchanges", "tool_calls"]) || value.version !== 1 || !Array.isArray(value.exchanges) || !Array.isArray(value.tool_calls)) throw new Error(POLICY_ERROR); + if ("screening" in value) { + const s = value.screening; + if (!object3(s) || !keys(s, ["engine", "executable", "sha256", "timeout_ms", "max_record_bytes"]) || s.engine !== "gitleaks" || typeof s.executable !== "string" || !path2.isAbsolute(s.executable) || /[\x00-\x1f]/.test(s.executable) || typeof s.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(s.sha256) || !Number.isSafeInteger(s.timeout_ms) || Number(s.timeout_ms) < 50 || Number(s.timeout_ms) > 3e4 || !Number.isSafeInteger(s.max_record_bytes) || Number(s.max_record_bytes) < 1 || Number(s.max_record_bytes) > 8388608) { + throw new Error(POLICY_ERROR); + } + } + for (const row of value.exchanges) { + if (!object3(row) || !keys(row, ["session_id", "transcript", "line_start", "line_end"]) || !identity(row.session_id) || !transcriptName(row.transcript) || !Number.isSafeInteger(row.line_start) || !Number.isSafeInteger(row.line_end) || row.line_start < 1 || row.line_end < row.line_start) throw new Error(POLICY_ERROR); + } + for (const row of value.tool_calls) { + if (!object3(row) || !keys(row, ["session_id", "transcript", "call_id"]) || !identity(row.session_id) || !transcriptName(row.transcript) || !identity(row.call_id)) throw new Error(POLICY_ERROR); + } + return value; + } catch (error61) { + if (fd === void 0 && error61.code === "ENOENT") return null; + throw new Error(POLICY_ERROR); + } finally { + if (fd !== void 0) fs2.closeSync(fd); + } +} +function excludedSession(policy, sessionId) { + return !!sessionId && !!policy && (policy.exchanges.some((x2) => x2.session_id === sessionId) || policy.tool_calls.some((x2) => x2.session_id === sessionId)); +} +function sessionFromPath(file2) { + return path2.basename(file2).match(UUID)?.at(-1); +} +function sessionFromRecord(value) { + if (value?.type === "session_meta") return value.payload?.id; + if (value?.type === "session") return value.id; + if (value?.type === "opencode_session") return value.session?.id; + return value?.sessionId ?? value?.sessionID ?? value?.message?.sessionID; +} +function blockedCall(policy, sessionId, transcript, callId) { + return typeof callId === "string" && policy.tool_calls.some((x2) => x2.session_id === sessionId && x2.transcript === transcript && x2.call_id === callId); +} +function filterRecord(line, number4, policy, transcript, sessionId) { + if (!policy || !sessionId) return line; + if (policy.exchanges.some((x2) => x2.session_id === sessionId && x2.transcript === transcript && number4 >= x2.line_start && number4 <= x2.line_end)) return ""; + let value; + try { + value = JSON.parse(line); + } catch { + return line; + } + if (blockedCall(policy, sessionId, transcript, value?.payload?.call_id) || blockedCall(policy, sessionId, transcript, value?.call_id)) return ""; + const content = value?.message?.content; + if (Array.isArray(content)) { + const kept = content.filter((block) => !blockedCall(policy, sessionId, transcript, block?.id ?? block?.tool_use_id)); + if (kept.length !== content.length) { + return JSON.stringify({ ...value, message: { ...value.message, content: kept } }); + } + } + if (Array.isArray(value?.parts)) { + const kept = value.parts.filter((part) => !blockedCall(policy, sessionId, transcript, part?.callID ?? part?.id)); + if (kept.length !== value.parts.length) return JSON.stringify({ ...value, parts: kept }); + } + return line; +} +async function* admittedRecords(file2, policy, startLine = 1, endLine = Infinity) { + const policySnapshot = JSON.stringify(policy); + assertUnchangedPolicy(policySnapshot); + const input2 = fs2.openSync(file2, fs2.constants.O_RDONLY | fs2.constants.O_NOFOLLOW); + const initial = fs2.fstatSync(input2); + let stream; + let screener; + const decoder = new TextDecoder("utf-8", { fatal: true }); + let pending = Buffer.alloc(0); + let sessionId = sessionFromPath(file2); + let primaryIdentitySeen = false; + let number4 = 0; + const admit = (bytes, terminated) => { + number4++; + const crlf = terminated && bytes.at(-1) === 13; + const line = decoder.decode(crlf ? bytes.subarray(0, -1) : bytes); + let recordSession; + try { + recordSession = sessionFromRecord(JSON.parse(line)); + } catch { + } + if (recordSession && !primaryIdentitySeen) { + if (sessionId && recordSession !== sessionId && (excludedSession(policy, sessionId) || excludedSession(policy, recordSession))) { + throw new Error("Record exclusion session identity mismatch"); + } + sessionId = recordSession; + primaryIdentitySeen = true; + } + const filtered = number4 >= startLine && number4 <= endLine ? filterRecord(line, number4, policy, path2.basename(file2), sessionId) : ""; + screener?.scan(filtered); + return { line: filtered, ending: terminated ? crlf ? "\r\n" : "\n" : "" }; + }; + try { + if (descriptorHasExclusionMarker(input2)) return; + assertUnchangedInput(file2, initial); + screener = createRecordScreener(policy); + stream = fs2.createReadStream(file2, { fd: input2, autoClose: true, start: 0 }); + for await (const chunk of stream) { + assertUnchangedInput(file2, initial); + assertUnchangedPolicy(policySnapshot); + pending = Buffer.concat([pending, chunk]); + let newline; + while ((newline = pending.indexOf(10)) !== -1) { + yield admit(pending.subarray(0, newline), true); + pending = pending.subarray(newline + 1); + } + if (pending.length > 64 * 1024 * 1024) throw new Error("Conversation record exceeds admission byte limit"); + } + if (pending.length) yield admit(pending, false); + assertUnchangedInput(file2, initial); + assertUnchangedPolicy(policySnapshot); + } finally { + if (stream) stream.destroy(); + else fs2.closeSync(input2); + screener?.close(); + } +} +async function readAdmittedConversation(file2, startLine = 1, endLine = Infinity) { + if (!Number.isSafeInteger(startLine) || startLine < 1 || endLine !== Infinity && (!Number.isSafeInteger(endLine) || endLine < startLine)) { + throw new Error("Invalid conversation line range"); + } + const lines = []; + for await (const record2 of admittedRecords(file2, readRecordExclusions(), startLine, endLine)) lines.push(record2.line); + return lines.join("\n"); +} +function admitExchange(exchange, policy = readRecordExclusions()) { + if (!policy) return exchange; + const sessionIds = [exchange.sessionId, sessionFromPath(exchange.archivePath)].filter((id) => !!id); + const transcript = path2.basename(exchange.archivePath); + if (policy.exchanges.some((x2) => sessionIds.includes(x2.session_id) && x2.transcript === transcript && exchange.lineStart <= x2.line_end && exchange.lineEnd >= x2.line_start)) return null; + const kept = exchange.toolCalls?.filter((call) => !sessionIds.some((id) => blockedCall(policy, id, transcript, call.id))); + const admitted = kept && kept.length !== exchange.toolCalls?.length ? { ...exchange, toolCalls: kept } : exchange; + const screener = createRecordScreener(policy); + try { + screener.scan(admitted.userMessage); + screener.scan(admitted.assistantMessage); + for (const call of admitted.toolCalls ?? []) screener.scan(call); + const { userMessage, assistantMessage, toolCalls, ...metadata } = admitted; + screener.scan(metadata); + return admitted; + } finally { + screener.close(); + } +} +function createSearchAdmission() { + const policy = readRecordExclusions(); + const policySnapshot = JSON.stringify(policy); + const files = /* @__PURE__ */ new Map(); + const checkFile = (file2) => { + assertUnchangedPolicy(policySnapshot); + const cached2 = files.get(file2); + if (cached2) return !cached2.excluded; + let fd; + try { + const pathInfo = fs2.lstatSync(file2); + if (!pathInfo.isFile()) throw new Error("Conversation changed during record admission"); + fd = fs2.openSync(file2, fs2.constants.O_RDONLY | fs2.constants.O_NOFOLLOW); + const initial = fs2.fstatSync(fd); + if (!initial.isFile() || initial.dev !== pathInfo.dev || initial.ino !== pathInfo.ino || !Number.isSafeInteger(initial.size)) throw new Error("Conversation changed during record admission"); + const hash2 = crypto.createHash("sha256"); + const excluded = descriptorHasExclusionMarker(fd, initial.size, hash2); + files.set(file2, { initial, excluded, digest: excluded ? void 0 : hash2.digest("hex") }); + return !excluded; + } catch { + throw new Error("Conversation changed during record admission"); + } finally { + if (fd !== void 0) fs2.closeSync(fd); + } + }; + return { + admit(exchange) { + if (!checkFile(exchange.archivePath)) return null; + const admitted = admitExchange(exchange, policy); + assertUnchangedPolicy(policySnapshot); + return admitted; + }, + summary(exchange, text) { + const ids = [exchange.sessionId, sessionFromPath(exchange.archivePath)]; + const transcript = path2.basename(exchange.archivePath); + if (policy && [...policy.exchanges, ...policy.tool_calls].some((row) => row.transcript === transcript && ids.includes(row.session_id))) return void 0; + return admitExchange({ ...exchange, userMessage: text, assistantMessage: "", toolCalls: [] }, policy) ? text : void 0; + }, + verify() { + assertUnchangedPolicy(policySnapshot); + for (const [file2, { initial, digest }] of files) { + if (digest !== void 0) assertUnchangedSearchPrefix(file2, initial, digest); + } + assertUnchangedPolicy(policySnapshot); + } + }; +} + +// src/db.ts +import path3 from "path"; +import fs3 from "fs"; +import * as sqliteVec from "sqlite-vec"; + // src/file-lock.ts import * as lockfile from "proper-lockfile"; var DEFAULT_STALE_MS = 10 * 60 * 1e3; @@ -29545,9 +35584,9 @@ function migrateToolCallsCascade(db) { } function initDatabase() { const dbPath = getDbPath(); - const dbDir = path2.dirname(dbPath); - if (!fs2.existsSync(dbDir)) { - fs2.mkdirSync(dbDir, { recursive: true }); + const dbDir = path3.dirname(dbPath); + if (!fs3.existsSync(dbDir)) { + fs3.mkdirSync(dbDir, { recursive: true }); } const db = new Database(dbPath); sqliteVec.load(db); @@ -29708,7 +35747,7 @@ function isErroredSentinel(content) { } // src/search.ts -import fs3 from "fs"; +import fs4 from "fs"; import readline from "readline"; var SIDECHAIN_DISTANCE_PENALTY = 0.05; function buildSearchFilters(options) { @@ -29826,84 +35865,88 @@ async function searchConversations(query, options = {}) { const sidechainClause = includeSidechains ? "" : "AND e.is_sidechain = 0"; if (after) validateISODate(after, "--after"); if (before) validateISODate(before, "--before"); + const admission = createSearchAdmission(); const db = initDatabase(); + const admitted = (row) => admission.admit(exchangeFromRow(row)) !== null; let results = []; const { sql: filterClause, params: filterParams } = buildSearchFilters(options); - if (mode === "vector" || mode === "both") { - await initEmbeddings(); - const queryEmbedding = await generateQueryEmbedding(query); - const k2 = limit * 3; - const stmt = db.prepare(` - SELECT - ${EXCHANGE_SELECT_COLUMNS}, - vec.distance - FROM vec_exchanges AS vec - JOIN exchanges AS e ON vec.id = e.id - WHERE vec.embedding MATCH ? - AND k = ? - ${sidechainClause} - ${filterClause} - ORDER BY (vec.distance + e.is_sidechain * ?) ASC - `); - results = stmt.all( - Buffer.from(new Float32Array(queryEmbedding).buffer), - k2, - ...filterParams, - SIDECHAIN_DISTANCE_PENALTY - ); - if (results.length > limit) { - results = results.slice(0, limit); - } - } - if (mode === "text" || mode === "both") { - const { sql: textMatchSql, params: textMatchParams } = buildTextMatchClause(query); - const textStmt = db.prepare(` - SELECT - ${EXCHANGE_SELECT_COLUMNS}, - 0 as distance - FROM exchanges AS e - WHERE ${textMatchSql} - ${sidechainClause} - ${filterClause} - ORDER BY e.is_sidechain ASC, e.timestamp DESC - LIMIT ? - `); - const textResults = textStmt.all(...textMatchParams, ...filterParams, limit); - if (mode === "both") { - const seenIds = new Set(results.map((r) => r.id)); - for (const textResult of textResults) { - if (!seenIds.has(textResult.id)) { - results.push(textResult); + try { + if (mode === "vector" || mode === "both") { + await initEmbeddings(); + const queryEmbedding = await generateQueryEmbedding(query); + const stmt = db.prepare(` + SELECT ${EXCHANGE_SELECT_COLUMNS}, + vec_distance_l2(vec.embedding, ?) AS retrieval_distance + FROM vec_exchanges AS vec + JOIN exchanges AS e ON vec.id = e.id + WHERE 1 = 1 ${sidechainClause} ${filterClause} + ORDER BY (retrieval_distance + e.is_sidechain * ?) ASC + `); + for (const row of stmt.iterate( + Buffer.from(new Float32Array(queryEmbedding).buffer), + ...filterParams, + SIDECHAIN_DISTANCE_PENALTY + )) { + if (admitted(row)) results.push(row); + if (results.length === limit) break; + } + } + if (mode === "text" || mode === "both") { + const { sql: textMatchSql, params: textMatchParams } = buildTextMatchClause(query); + const textStmt = db.prepare(` + SELECT + ${EXCHANGE_SELECT_COLUMNS}, + 0 as retrieval_distance + FROM exchanges AS e + WHERE ${textMatchSql} + ${sidechainClause} + ${filterClause} + ORDER BY e.is_sidechain ASC, e.timestamp DESC + `); + const textResults = []; + for (const row of textStmt.iterate(...textMatchParams, ...filterParams)) { + if (admitted(row)) textResults.push(row); + if (textResults.length === limit) break; + } + if (mode === "both") { + const seenIds = new Set(results.map((r) => r.id)); + for (const textResult of textResults) { + if (!seenIds.has(textResult.id)) { + results.push(textResult); + } } + } else { + results = textResults; } - } else { - results = textResults; } + } finally { + db.close(); } - db.close(); - return results.map((row) => { + const output2 = results.map((row) => { const exchange = exchangeFromRow(row); const summaryPath = row.archive_path.replace(".jsonl", "-summary.txt"); let summary; - if (fs3.existsSync(summaryPath)) { - const raw = fs3.readFileSync(summaryPath, "utf-8"); + if (fs4.existsSync(summaryPath)) { + const raw = fs4.readFileSync(summaryPath, "utf-8"); if (!isErroredSentinel(raw)) { - summary = raw.trim(); + summary = admission.summary(exchange, raw.trim()); } } const snippetText = exchange.userMessage.substring(0, 200).replace(/\s+/g, " ").trim(); const snippet = snippetText + (exchange.userMessage.length > 200 ? "..." : ""); return { exchange, - similarity: mode === "text" ? void 0 : l2DistanceToCosineSimilarity(row.distance), + similarity: mode === "text" ? void 0 : l2DistanceToCosineSimilarity(row.retrieval_distance), snippet, summary }; }); + admission.verify(); + return output2; } async function countLines(filePath) { try { - const fileStream = fs3.createReadStream(filePath); + const fileStream = fs4.createReadStream(filePath); const rl = readline.createInterface({ input: fileStream, crlfDelay: Infinity @@ -29919,7 +35962,7 @@ async function countLines(filePath) { } function getFileSizeInKB(filePath) { try { - const stats = fs3.statSync(filePath); + const stats = fs4.statSync(filePath); return Math.round(stats.size / 1024 * 10) / 10; } catch (error61) { return 0; @@ -30046,140 +36089,147 @@ function L() { return { async: false, breaks: false, extensions: null, gfm: true, hooks: null, pedantic: false, renderer: null, silent: false, tokenizer: null, walkTokens: null }; } var T = L(); -function G(u3) { - T = u3; +function G(l3) { + T = l3; } -var I = { exec: () => null }; -function d(u3, e = "") { - let t = typeof u3 == "string" ? u3 : u3.source, n = { replace: (r, i) => { +var E = { exec: () => null }; +function d(l3, e = "") { + let t = typeof l3 == "string" ? l3 : l3.source, n = { replace: (r, i) => { let s = typeof i == "string" ? i : i.source; return s = s.replace(m.caret, "$1"), t = t.replace(r, s), n; }, getRegex: () => new RegExp(t, e) }; return n; } -var m = { codeRemoveIndent: /^(?: {1,4}| {0,3}\t)/gm, outputLinkReplace: /\\([\[\]])/g, indentCodeCompensation: /^(\s+)(?:```)/, beginningSpace: /^\s+/, endingHash: /#$/, startingSpaceChar: /^ /, endingSpaceChar: / $/, nonSpaceChar: /[^ ]/, newLineCharGlobal: /\n/g, tabCharGlobal: /\t/g, multipleSpaceGlobal: /\s+/g, blankLine: /^[ \t]*$/, doubleBlankLine: /\n[ \t]*\n[ \t]*$/, blockquoteStart: /^ {0,3}>/, blockquoteSetextReplace: /\n {0,3}((?:=+|-+) *)(?=\n|$)/g, blockquoteSetextReplace2: /^ {0,3}>[ \t]?/gm, listReplaceTabs: /^\t+/, listReplaceNesting: /^ {1,4}(?=( {4})*[^ ])/g, listIsTask: /^\[[ xX]\] /, listReplaceTask: /^\[[ xX]\] +/, anyLine: /\n.*\n/, hrefBrackets: /^<(.*)>$/, tableDelimiter: /[:|]/, tableAlignChars: /^\||\| *$/g, tableRowBlankLine: /\n[ \t]*$/, tableAlignRight: /^ *-+: *$/, tableAlignCenter: /^ *:-+: *$/, tableAlignLeft: /^ *:-+ *$/, startATag: /^/i, startPreScriptTag: /^<(pre|code|kbd|script)(\s|>)/i, endPreScriptTag: /^<\/(pre|code|kbd|script)(\s|>)/i, startAngleBracket: /^$/, pedanticHrefTitle: /^([^'"]*[^\s])\s+(['"])(.*)\2/, unicodeAlphaNumeric: /[\p{L}\p{N}]/u, escapeTest: /[&<>"']/, escapeReplace: /[&<>"']/g, escapeTestNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/, escapeReplaceNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g, unescapeTest: /&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig, caret: /(^|[^\[])\^/g, percentDecode: /%25/g, findPipe: /\|/g, splitPipe: / \|/, slashPipe: /\\\|/g, carriageReturn: /\r\n|\r/g, spaceLine: /^ +$/gm, notSpaceStart: /^\S*/, endingNewline: /\n$/, listItemRegex: (u3) => new RegExp(`^( {0,3}${u3})((?:[ ][^\\n]*)?(?:\\n|$))`), nextBulletRegex: (u3) => new RegExp(`^ {0,${Math.min(3, u3 - 1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ ][^\\n]*)?(?:\\n|$))`), hrRegex: (u3) => new RegExp(`^ {0,${Math.min(3, u3 - 1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`), fencesBeginRegex: (u3) => new RegExp(`^ {0,${Math.min(3, u3 - 1)}}(?:\`\`\`|~~~)`), headingBeginRegex: (u3) => new RegExp(`^ {0,${Math.min(3, u3 - 1)}}#`), htmlBeginRegex: (u3) => new RegExp(`^ {0,${Math.min(3, u3 - 1)}}<(?:[a-z].*>|!--)`, "i") }; -var be = /^(?:[ \t]*(?:\n|$))+/; -var Re = /^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/; -var Te = /^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/; -var E = /^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/; -var Oe = /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/; +var be = (() => { + try { + return !!new RegExp("(?<=1)(?/, blockquoteSetextReplace: /\n {0,3}((?:=+|-+) *)(?=\n|$)/g, blockquoteSetextReplace2: /^ {0,3}>[ \t]?/gm, listReplaceTabs: /^\t+/, listReplaceNesting: /^ {1,4}(?=( {4})*[^ ])/g, listIsTask: /^\[[ xX]\] /, listReplaceTask: /^\[[ xX]\] +/, anyLine: /\n.*\n/, hrefBrackets: /^<(.*)>$/, tableDelimiter: /[:|]/, tableAlignChars: /^\||\| *$/g, tableRowBlankLine: /\n[ \t]*$/, tableAlignRight: /^ *-+: *$/, tableAlignCenter: /^ *:-+: *$/, tableAlignLeft: /^ *:-+ *$/, startATag: /^/i, startPreScriptTag: /^<(pre|code|kbd|script)(\s|>)/i, endPreScriptTag: /^<\/(pre|code|kbd|script)(\s|>)/i, startAngleBracket: /^$/, pedanticHrefTitle: /^([^'"]*[^\s])\s+(['"])(.*)\2/, unicodeAlphaNumeric: /[\p{L}\p{N}]/u, escapeTest: /[&<>"']/, escapeReplace: /[&<>"']/g, escapeTestNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/, escapeReplaceNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g, unescapeTest: /&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig, caret: /(^|[^\[])\^/g, percentDecode: /%25/g, findPipe: /\|/g, splitPipe: / \|/, slashPipe: /\\\|/g, carriageReturn: /\r\n|\r/g, spaceLine: /^ +$/gm, notSpaceStart: /^\S*/, endingNewline: /\n$/, listItemRegex: (l3) => new RegExp(`^( {0,3}${l3})((?:[ ][^\\n]*)?(?:\\n|$))`), nextBulletRegex: (l3) => new RegExp(`^ {0,${Math.min(3, l3 - 1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ ][^\\n]*)?(?:\\n|$))`), hrRegex: (l3) => new RegExp(`^ {0,${Math.min(3, l3 - 1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`), fencesBeginRegex: (l3) => new RegExp(`^ {0,${Math.min(3, l3 - 1)}}(?:\`\`\`|~~~)`), headingBeginRegex: (l3) => new RegExp(`^ {0,${Math.min(3, l3 - 1)}}#`), htmlBeginRegex: (l3) => new RegExp(`^ {0,${Math.min(3, l3 - 1)}}<(?:[a-z].*>|!--)`, "i") }; +var Re = /^(?:[ \t]*(?:\n|$))+/; +var Te = /^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/; +var Oe = /^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/; +var I = /^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/; +var we = /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/; var F = /(?:[*+-]|\d{1,9}[.)])/; var ie = /^(?!bull |blockCode|fences|blockquote|heading|html|table)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html|table))+?)\n {0,3}(=+|-+) *(?:\n+|$)/; var oe = d(ie).replace(/bull/g, F).replace(/blockCode/g, /(?: {4}| {0,3}\t)/).replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g, / {0,3}>/).replace(/heading/g, / {0,3}#{1,6}/).replace(/html/g, / {0,3}<[^\n>]+>\n/).replace(/\|table/g, "").getRegex(); -var we = d(ie).replace(/bull/g, F).replace(/blockCode/g, /(?: {4}| {0,3}\t)/).replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g, / {0,3}>/).replace(/heading/g, / {0,3}#{1,6}/).replace(/html/g, / {0,3}<[^\n>]+>\n/).replace(/table/g, / {0,3}\|?(?:[:\- ]*\|)+[\:\- ]*\n/).getRegex(); +var ye = d(ie).replace(/bull/g, F).replace(/blockCode/g, /(?: {4}| {0,3}\t)/).replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g, / {0,3}>/).replace(/heading/g, / {0,3}#{1,6}/).replace(/html/g, / {0,3}<[^\n>]+>\n/).replace(/table/g, / {0,3}\|?(?:[:\- ]*\|)+[\:\- ]*\n/).getRegex(); var j = /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/; -var ye = /^[^\n]+/; +var Pe = /^[^\n]+/; var Q = /(?!\s*\])(?:\\[\s\S]|[^\[\]\\])+/; -var Pe = d(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label", Q).replace("title", /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex(); -var Se = d(/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/).replace(/bull/g, F).getRegex(); +var Se = d(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label", Q).replace("title", /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex(); +var $e = d(/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/).replace(/bull/g, F).getRegex(); var v = "address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul"; var U = /|$))/; -var $e = d("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|\\n*|$)|\\n*|$)|)[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$))", "i").replace("comment", U).replace("tag", v).replace("attribute", / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(); -var ae = d(j).replace("hr", E).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("|table", "").replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)]) ").replace("html", ")|<(?:script|pre|style|textarea|!--)").replace("tag", v).getRegex(); -var _e = d(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph", ae).getRegex(); -var K = { blockquote: _e, code: Re, def: Pe, fences: Te, heading: Oe, hr: E, html: $e, lheading: oe, list: Se, newline: be, paragraph: ae, table: I, text: ye }; -var re = d("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr", E).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("blockquote", " {0,3}>").replace("code", "(?: {4}| {0,3} )[^\\n]").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)]) ").replace("html", ")|<(?:script|pre|style|textarea|!--)").replace("tag", v).getRegex(); -var Le = { ...K, lheading: we, table: re, paragraph: d(j).replace("hr", E).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("table", re).replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)]) ").replace("html", ")|<(?:script|pre|style|textarea|!--)").replace("tag", v).getRegex() }; -var Me = { ...K, html: d(`^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))`).replace("comment", U).replace(/tag/g, "(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(), def: /^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/, heading: /^(#{1,6})(.*)(?:\n+|$)/, fences: I, lheading: /^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/, paragraph: d(j).replace("hr", E).replace("heading", ` *#{1,6} *[^ +var _e = d("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|\\n*|$)|\\n*|$)|)[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$))", "i").replace("comment", U).replace("tag", v).replace("attribute", / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(); +var ae = d(j).replace("hr", I).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("|table", "").replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)]) ").replace("html", ")|<(?:script|pre|style|textarea|!--)").replace("tag", v).getRegex(); +var Le = d(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph", ae).getRegex(); +var K = { blockquote: Le, code: Te, def: Se, fences: Oe, heading: we, hr: I, html: _e, lheading: oe, list: $e, newline: Re, paragraph: ae, table: E, text: Pe }; +var re = d("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr", I).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("blockquote", " {0,3}>").replace("code", "(?: {4}| {0,3} )[^\\n]").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)]) ").replace("html", ")|<(?:script|pre|style|textarea|!--)").replace("tag", v).getRegex(); +var Me = { ...K, lheading: ye, table: re, paragraph: d(j).replace("hr", I).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("table", re).replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)]) ").replace("html", ")|<(?:script|pre|style|textarea|!--)").replace("tag", v).getRegex() }; +var ze = { ...K, html: d(`^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))`).replace("comment", U).replace(/tag/g, "(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(), def: /^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/, heading: /^(#{1,6})(.*)(?:\n+|$)/, fences: E, lheading: /^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/, paragraph: d(j).replace("hr", I).replace("heading", ` *#{1,6} *[^ ]`).replace("lheading", oe).replace("|table", "").replace("blockquote", " {0,3}>").replace("|fences", "").replace("|list", "").replace("|html", "").replace("|tag", "").getRegex() }; -var ze = /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/; -var Ae = /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/; +var Ae = /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/; +var Ee = /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/; var le = /^( {2,}|\\)\n(?!\s*$)/; var Ie = /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\]*?>/g; +var Be = /(?!~)[\s\p{P}\p{S}]/u; +var qe = /(?:[^\s\p{P}\p{S}]|~)/u; +var ve = d(/link|precode-code|html/, "g").replace("link", /\[(?:[^\[\]`]|(?`+)[^`]+\k(?!`))*?\]\((?:\\[\s\S]|[^\\\(\)]|\((?:\\[\s\S]|[^\\\(\)])*\))*\)/).replace("precode-", be ? "(?`+)[^`]+\k(?!`)/).replace("html", /<(?! )[^<>]*?>/).getRegex(); var ce = /^(?:\*+(?:((?!\*)punct)|[^\s*]))|^_+(?:((?!_)punct)|([^\s_]))/; -var ve = d(ce, "u").replace(/punct/g, D).getRegex(); -var De = d(ce, "u").replace(/punct/g, pe).getRegex(); +var De = d(ce, "u").replace(/punct/g, D).getRegex(); +var He = d(ce, "u").replace(/punct/g, pe).getRegex(); var he = "^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)punct(\\*+)(?=[\\s]|$)|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)|(?!\\*)punctSpace(\\*+)(?=notPunctSpace)|[\\s](\\*+)(?!\\*)(?=punct)|(?!\\*)punct(\\*+)(?!\\*)(?=punct)|notPunctSpace(\\*+)(?=notPunctSpace)"; -var He = d(he, "gu").replace(/notPunctSpace/g, ue).replace(/punctSpace/g, W).replace(/punct/g, D).getRegex(); -var Ze = d(he, "gu").replace(/notPunctSpace/g, Be).replace(/punctSpace/g, Ce).replace(/punct/g, pe).getRegex(); -var Ge = d("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)punct(_+)(?=[\\s]|$)|notPunctSpace(_+)(?!_)(?=punctSpace|$)|(?!_)punctSpace(_+)(?=notPunctSpace)|[\\s](_+)(?!_)(?=punct)|(?!_)punct(_+)(?!_)(?=punct)", "gu").replace(/notPunctSpace/g, ue).replace(/punctSpace/g, W).replace(/punct/g, D).getRegex(); -var Ne = d(/\\(punct)/, "gu").replace(/punct/g, D).getRegex(); -var Fe = d(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme", /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email", /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex(); -var je = d(U).replace("(?:-->|$)", "-->").getRegex(); -var Qe = d("^comment|^|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^|^").replace("comment", je).replace("attribute", /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex(); +var Ze = d(he, "gu").replace(/notPunctSpace/g, ue).replace(/punctSpace/g, W).replace(/punct/g, D).getRegex(); +var Ge = d(he, "gu").replace(/notPunctSpace/g, qe).replace(/punctSpace/g, Be).replace(/punct/g, pe).getRegex(); +var Ne = d("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)punct(_+)(?=[\\s]|$)|notPunctSpace(_+)(?!_)(?=punctSpace|$)|(?!_)punctSpace(_+)(?=notPunctSpace)|[\\s](_+)(?!_)(?=punct)|(?!_)punct(_+)(?!_)(?=punct)", "gu").replace(/notPunctSpace/g, ue).replace(/punctSpace/g, W).replace(/punct/g, D).getRegex(); +var Fe = d(/\\(punct)/, "gu").replace(/punct/g, D).getRegex(); +var je = d(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme", /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email", /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex(); +var Qe = d(U).replace("(?:-->|$)", "-->").getRegex(); +var Ue = d("^comment|^|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^|^").replace("comment", Qe).replace("attribute", /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex(); var q = /(?:\[(?:\\[\s\S]|[^\[\]\\])*\]|\\[\s\S]|`+[^`]*?`+(?!`)|[^\[\]\\`])*?/; -var Ue = d(/^!?\[(label)\]\(\s*(href)(?:(?:[ \t]*(?:\n[ \t]*)?)(title))?\s*\)/).replace("label", q).replace("href", /<(?:\\.|[^\n<>\\])+>|[^ \t\n\x00-\x1f]*/).replace("title", /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex(); +var Ke = d(/^!?\[(label)\]\(\s*(href)(?:(?:[ \t]*(?:\n[ \t]*)?)(title))?\s*\)/).replace("label", q).replace("href", /<(?:\\.|[^\n<>\\])+>|[^ \t\n\x00-\x1f]*/).replace("title", /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex(); var de = d(/^!?\[(label)\]\[(ref)\]/).replace("label", q).replace("ref", Q).getRegex(); var ke = d(/^!?\[(ref)\](?:\[\])?/).replace("ref", Q).getRegex(); -var Ke = d("reflink|nolink(?!\\()", "g").replace("reflink", de).replace("nolink", ke).getRegex(); +var We = d("reflink|nolink(?!\\()", "g").replace("reflink", de).replace("nolink", ke).getRegex(); var se = /[hH][tT][tT][pP][sS]?|[fF][tT][pP]/; -var X = { _backpedal: I, anyPunctuation: Ne, autolink: Fe, blockSkip: qe, br: le, code: Ae, del: I, emStrongLDelim: ve, emStrongRDelimAst: He, emStrongRDelimUnd: Ge, escape: ze, link: Ue, nolink: ke, punctuation: Ee, reflink: de, reflinkSearch: Ke, tag: Qe, text: Ie, url: I }; -var We = { ...X, link: d(/^!?\[(label)\]\((.*?)\)/).replace("label", q).getRegex(), reflink: d(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label", q).getRegex() }; -var N = { ...X, emStrongRDelimAst: Ze, emStrongLDelim: De, url: d(/^((?:protocol):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/).replace("protocol", se).replace("email", /[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/).getRegex(), _backpedal: /(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/, del: /^(~~?)(?=[^\s~])((?:\\[\s\S]|[^\\])*?(?:\\[\s\S]|[^\s~\\]))\1(?=[^~]|$)/, text: d(/^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\": ">", '"': """, "'": "'" }; -var ge = (u3) => Je[u3]; -function w(u3, e) { +var X = { _backpedal: E, anyPunctuation: Fe, autolink: je, blockSkip: ve, br: le, code: Ee, del: E, emStrongLDelim: De, emStrongRDelimAst: Ze, emStrongRDelimUnd: Ne, escape: Ae, link: Ke, nolink: ke, punctuation: Ce, reflink: de, reflinkSearch: We, tag: Ue, text: Ie, url: E }; +var Xe = { ...X, link: d(/^!?\[(label)\]\((.*?)\)/).replace("label", q).getRegex(), reflink: d(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label", q).getRegex() }; +var N = { ...X, emStrongRDelimAst: Ge, emStrongLDelim: He, url: d(/^((?:protocol):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/).replace("protocol", se).replace("email", /[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/).getRegex(), _backpedal: /(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/, del: /^(~~?)(?=[^\s~])((?:\\[\s\S]|[^\\])*?(?:\\[\s\S]|[^\s~\\]))\1(?=[^~]|$)/, text: d(/^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\": ">", '"': """, "'": "'" }; +var ge = (l3) => Ve[l3]; +function w(l3, e) { if (e) { - if (m.escapeTest.test(u3)) return u3.replace(m.escapeReplace, ge); - } else if (m.escapeTestNoEncode.test(u3)) return u3.replace(m.escapeReplaceNoEncode, ge); - return u3; + if (m.escapeTest.test(l3)) return l3.replace(m.escapeReplace, ge); + } else if (m.escapeTestNoEncode.test(l3)) return l3.replace(m.escapeReplaceNoEncode, ge); + return l3; } -function J(u3) { +function J(l3) { try { - u3 = encodeURI(u3).replace(m.percentDecode, "%"); + l3 = encodeURI(l3).replace(m.percentDecode, "%"); } catch { return null; } - return u3; + return l3; } -function V(u3, e) { - let t = u3.replace(m.findPipe, (i, s, o) => { - let a = false, l = s; - for (; --l >= 0 && o[l] === "\\"; ) a = !a; - return a ? "|" : " |"; +function V(l3, e) { + let t = l3.replace(m.findPipe, (i, s, a) => { + let o = false, p = s; + for (; --p >= 0 && a[p] === "\\"; ) o = !o; + return o ? "|" : " |"; }), n = t.split(m.splitPipe), r = 0; if (n[0].trim() || n.shift(), n.length > 0 && !n.at(-1)?.trim() && n.pop(), e) if (n.length > e) n.splice(e); else for (; n.length < e; ) n.push(""); for (; r < n.length; r++) n[r] = n[r].trim().replace(m.slashPipe, "|"); return n; } -function z2(u3, e, t) { - let n = u3.length; +function z2(l3, e, t) { + let n = l3.length; if (n === 0) return ""; let r = 0; for (; r < n; ) { - let i = u3.charAt(n - r - 1); + let i = l3.charAt(n - r - 1); if (i === e && !t) r++; else if (i !== e && t) r++; else break; } - return u3.slice(0, n - r); + return l3.slice(0, n - r); } -function fe(u3, e) { - if (u3.indexOf(e[1]) === -1) return -1; +function fe(l3, e) { + if (l3.indexOf(e[1]) === -1) return -1; let t = 0; - for (let n = 0; n < u3.length; n++) if (u3[n] === "\\") n++; - else if (u3[n] === e[0]) t++; - else if (u3[n] === e[1] && (t--, t < 0)) return n; + for (let n = 0; n < l3.length; n++) if (l3[n] === "\\") n++; + else if (l3[n] === e[0]) t++; + else if (l3[n] === e[1] && (t--, t < 0)) return n; return t > 0 ? -2 : -1; } -function me(u3, e, t, n, r) { - let i = e.href, s = e.title || null, o = u3[1].replace(r.other.outputLinkReplace, "$1"); +function me(l3, e, t, n, r) { + let i = e.href, s = e.title || null, a = l3[1].replace(r.other.outputLinkReplace, "$1"); n.state.inLink = true; - let a = { type: u3[0].charAt(0) === "!" ? "image" : "link", raw: t, href: i, title: s, text: o, tokens: n.inlineTokens(o) }; - return n.state.inLink = false, a; + let o = { type: l3[0].charAt(0) === "!" ? "image" : "link", raw: t, href: i, title: s, text: a, tokens: n.inlineTokens(a) }; + return n.state.inLink = false, o; } -function Ve(u3, e, t) { - let n = u3.match(t.other.indentCodeCompensation); +function Ye(l3, e, t) { + let n = l3.match(t.other.indentCodeCompensation); if (n === null) return e; let r = n[1]; return e.split(` `).map((i) => { let s = i.match(t.other.beginningSpace); if (s === null) return i; - let [o] = s; - return o.length >= r.length ? i.slice(r.length) : i; + let [a] = s; + return a.length >= r.length ? i.slice(r.length) : i; }).join(` `); } @@ -30205,7 +36255,7 @@ var y = class { fences(e) { let t = this.rules.block.fences.exec(e); if (t) { - let n = t[0], r = Ve(n, t[3] || "", this.rules); + let n = t[0], r = Ye(n, t[3] || "", this.rules); return { type: "code", raw: n, lang: t[2] ? t[2].trim().replace(this.rules.inline.anyPunctuation, "$1") : t[2], text: r }; } } @@ -30232,19 +36282,19 @@ var y = class { `).split(` `), r = "", i = "", s = []; for (; n.length > 0; ) { - let o = false, a = [], l; - for (l = 0; l < n.length; l++) if (this.rules.other.blockquoteStart.test(n[l])) a.push(n[l]), o = true; - else if (!o) a.push(n[l]); + let a = false, o = [], p; + for (p = 0; p < n.length; p++) if (this.rules.other.blockquoteStart.test(n[p])) o.push(n[p]), a = true; + else if (!a) o.push(n[p]); else break; - n = n.slice(l); - let c = a.join(` -`), p = c.replace(this.rules.other.blockquoteSetextReplace, ` + n = n.slice(p); + let u = o.join(` +`), c = u.replace(this.rules.other.blockquoteSetextReplace, ` $1`).replace(this.rules.other.blockquoteSetextReplace2, ""); r = r ? `${r} -${c}` : c, i = i ? `${i} -${p}` : p; +${u}` : u, i = i ? `${i} +${c}` : c; let g = this.lexer.state.top; - if (this.lexer.state.top = true, this.lexer.blockTokens(p, s, true), this.lexer.state.top = g, n.length === 0) break; + if (this.lexer.state.top = true, this.lexer.blockTokens(c, s, true), this.lexer.state.top = g, n.length === 0) break; let h = s.at(-1); if (h?.type === "code") break; if (h?.type === "blockquote") { @@ -30270,45 +36320,45 @@ ${p}` : p; if (t) { let n = t[1].trim(), r = n.length > 1, i = { type: "list", raw: "", ordered: r, start: r ? +n.slice(0, -1) : "", loose: false, items: [] }; n = r ? `\\d{1,9}\\${n.slice(-1)}` : `\\${n}`, this.options.pedantic && (n = r ? n : "[*+-]"); - let s = this.rules.other.listItemRegex(n), o = false; + let s = this.rules.other.listItemRegex(n), a = false; for (; e; ) { - let l = false, c = "", p = ""; + let p = false, u = "", c = ""; if (!(t = s.exec(e)) || this.rules.block.hr.test(e)) break; - c = t[0], e = e.substring(c.length); + u = t[0], e = e.substring(u.length); let g = t[2].split(` `, 1)[0].replace(this.rules.other.listReplaceTabs, (H) => " ".repeat(3 * H.length)), h = e.split(` `, 1)[0], R = !g.trim(), f = 0; - if (this.options.pedantic ? (f = 2, p = g.trimStart()) : R ? f = t[1].length + 1 : (f = t[2].search(this.rules.other.nonSpaceChar), f = f > 4 ? 1 : f, p = g.slice(f), f += t[1].length), R && this.rules.other.blankLine.test(h) && (c += h + ` -`, e = e.substring(h.length + 1), l = true), !l) { + if (this.options.pedantic ? (f = 2, c = g.trimStart()) : R ? f = t[1].length + 1 : (f = t[2].search(this.rules.other.nonSpaceChar), f = f > 4 ? 1 : f, c = g.slice(f), f += t[1].length), R && this.rules.other.blankLine.test(h) && (u += h + ` +`, e = e.substring(h.length + 1), p = true), !p) { let H = this.rules.other.nextBulletRegex(f), ee = this.rules.other.hrRegex(f), te = this.rules.other.fencesBeginRegex(f), ne = this.rules.other.headingBeginRegex(f), xe = this.rules.other.htmlBeginRegex(f); for (; e; ) { let Z = e.split(` `, 1)[0], A; if (h = Z, this.options.pedantic ? (h = h.replace(this.rules.other.listReplaceNesting, " "), A = h) : A = h.replace(this.rules.other.tabCharGlobal, " "), te.test(h) || ne.test(h) || xe.test(h) || H.test(h) || ee.test(h)) break; - if (A.search(this.rules.other.nonSpaceChar) >= f || !h.trim()) p += ` + if (A.search(this.rules.other.nonSpaceChar) >= f || !h.trim()) c += ` ` + A.slice(f); else { if (R || g.replace(this.rules.other.tabCharGlobal, " ").search(this.rules.other.nonSpaceChar) >= 4 || te.test(g) || ne.test(g) || ee.test(g)) break; - p += ` + c += ` ` + h; } - !R && !h.trim() && (R = true), c += Z + ` + !R && !h.trim() && (R = true), u += Z + ` `, e = e.substring(Z.length + 1), g = A.slice(f); } } - i.loose || (o ? i.loose = true : this.rules.other.doubleBlankLine.test(c) && (o = true)); + i.loose || (a ? i.loose = true : this.rules.other.doubleBlankLine.test(u) && (a = true)); let O = null, Y; - this.options.gfm && (O = this.rules.other.listIsTask.exec(p), O && (Y = O[0] !== "[ ] ", p = p.replace(this.rules.other.listReplaceTask, ""))), i.items.push({ type: "list_item", raw: c, task: !!O, checked: Y, loose: false, text: p, tokens: [] }), i.raw += c; + this.options.gfm && (O = this.rules.other.listIsTask.exec(c), O && (Y = O[0] !== "[ ] ", c = c.replace(this.rules.other.listReplaceTask, ""))), i.items.push({ type: "list_item", raw: u, task: !!O, checked: Y, loose: false, text: c, tokens: [] }), i.raw += u; } - let a = i.items.at(-1); - if (a) a.raw = a.raw.trimEnd(), a.text = a.text.trimEnd(); + let o = i.items.at(-1); + if (o) o.raw = o.raw.trimEnd(), o.text = o.text.trimEnd(); else return; i.raw = i.raw.trimEnd(); - for (let l = 0; l < i.items.length; l++) if (this.lexer.state.top = false, i.items[l].tokens = this.lexer.blockTokens(i.items[l].text, []), !i.loose) { - let c = i.items[l].tokens.filter((g) => g.type === "space"), p = c.length > 0 && c.some((g) => this.rules.other.anyLine.test(g.raw)); - i.loose = p; + for (let p = 0; p < i.items.length; p++) if (this.lexer.state.top = false, i.items[p].tokens = this.lexer.blockTokens(i.items[p].text, []), !i.loose) { + let u = i.items[p].tokens.filter((g) => g.type === "space"), c = u.length > 0 && u.some((g) => this.rules.other.anyLine.test(g.raw)); + i.loose = c; } - if (i.loose) for (let l = 0; l < i.items.length; l++) i.items[l].loose = true; + if (i.loose) for (let p = 0; p < i.items.length; p++) i.items[p].loose = true; return i; } } @@ -30329,9 +36379,9 @@ ${p}` : p; let n = V(t[1]), r = t[2].replace(this.rules.other.tableAlignChars, "").split("|"), i = t[3]?.trim() ? t[3].replace(this.rules.other.tableRowBlankLine, "").split(` `) : [], s = { type: "table", raw: t[0], header: [], align: [], rows: [] }; if (n.length === r.length) { - for (let o of r) this.rules.other.tableAlignRight.test(o) ? s.align.push("right") : this.rules.other.tableAlignCenter.test(o) ? s.align.push("center") : this.rules.other.tableAlignLeft.test(o) ? s.align.push("left") : s.align.push(null); - for (let o = 0; o < n.length; o++) s.header.push({ text: n[o], tokens: this.lexer.inline(n[o]), header: true, align: s.align[o] }); - for (let o of i) s.rows.push(V(o, s.header.length).map((a, l) => ({ text: a, tokens: this.lexer.inline(a), header: false, align: s.align[l] }))); + for (let a of r) this.rules.other.tableAlignRight.test(a) ? s.align.push("right") : this.rules.other.tableAlignCenter.test(a) ? s.align.push("center") : this.rules.other.tableAlignLeft.test(a) ? s.align.push("left") : s.align.push(null); + for (let a = 0; a < n.length; a++) s.header.push({ text: n[a], tokens: this.lexer.inline(n[a]), header: true, align: s.align[a] }); + for (let a of i) s.rows.push(V(a, s.header.length).map((o, p) => ({ text: o, tokens: this.lexer.inline(o), header: false, align: s.align[p] }))); return s; } } @@ -30371,8 +36421,8 @@ ${p}` : p; let s = fe(t[2], "()"); if (s === -2) return; if (s > -1) { - let a = (t[0].indexOf("!") === 0 ? 5 : 4) + t[1].length + s; - t[2] = t[2].substring(0, s), t[0] = t[0].substring(0, a).trim(), t[3] = ""; + let o = (t[0].indexOf("!") === 0 ? 5 : 4) + t[1].length + s; + t[2] = t[2].substring(0, s), t[0] = t[0].substring(0, o).trim(), t[3] = ""; } } let r = t[2], i = ""; @@ -30398,20 +36448,20 @@ ${p}` : p; let r = this.rules.inline.emStrongLDelim.exec(e); if (!r || r[3] && n.match(this.rules.other.unicodeAlphaNumeric)) return; if (!(r[1] || r[2] || "") || !n || this.rules.inline.punctuation.exec(n)) { - let s = [...r[0]].length - 1, o, a, l = s, c = 0, p = r[0][0] === "*" ? this.rules.inline.emStrongRDelimAst : this.rules.inline.emStrongRDelimUnd; - for (p.lastIndex = 0, t = t.slice(-1 * e.length + s); (r = p.exec(t)) != null; ) { - if (o = r[1] || r[2] || r[3] || r[4] || r[5] || r[6], !o) continue; - if (a = [...o].length, r[3] || r[4]) { - l += a; + let s = [...r[0]].length - 1, a, o, p = s, u = 0, c = r[0][0] === "*" ? this.rules.inline.emStrongRDelimAst : this.rules.inline.emStrongRDelimUnd; + for (c.lastIndex = 0, t = t.slice(-1 * e.length + s); (r = c.exec(t)) != null; ) { + if (a = r[1] || r[2] || r[3] || r[4] || r[5] || r[6], !a) continue; + if (o = [...a].length, r[3] || r[4]) { + p += o; continue; - } else if ((r[5] || r[6]) && s % 3 && !((s + a) % 3)) { - c += a; + } else if ((r[5] || r[6]) && s % 3 && !((s + o) % 3)) { + u += o; continue; } - if (l -= a, l > 0) continue; - a = Math.min(a, a + l + c); - let g = [...r[0]][0].length, h = e.slice(0, s + r.index + g + a); - if (Math.min(s, a) % 2) { + if (p -= o, p > 0) continue; + o = Math.min(o, o + p + u); + let g = [...r[0]][0].length, h = e.slice(0, s + r.index + g + o); + if (Math.min(s, o) % 2) { let f = h.slice(1, -1); return { type: "em", raw: h, text: f, tokens: this.lexer.inlineTokens(f) }; } @@ -30465,7 +36515,7 @@ ${p}` : p; } } }; -var x = class u { +var x = class l { tokens; options; state; @@ -30480,10 +36530,10 @@ var x = class u { return { block: C, inline: M }; } static lex(e, t) { - return new u(t).lex(e); + return new l(t).lex(e); } static lexInline(e, t) { - return new u(t).inlineTokens(e); + return new l(t).inlineTokens(e); } lex(e) { e = e.replace(m.carriageReturn, ` @@ -30557,9 +36607,9 @@ var x = class u { } let i = e; if (this.options.extensions?.startBlock) { - let s = 1 / 0, o = e.slice(1), a; - this.options.extensions.startBlock.forEach((l) => { - a = l.call({ lexer: this }, o), typeof a == "number" && a >= 0 && (s = Math.min(s, a)); + let s = 1 / 0, a = e.slice(1), o; + this.options.extensions.startBlock.forEach((p) => { + o = p.call({ lexer: this }, a), typeof o == "number" && o >= 0 && (s = Math.min(s, o)); }), s < 1 / 0 && s >= 0 && (i = e.substring(0, s + 1)); } if (this.state.top && (r = this.tokenizer.paragraph(i))) { @@ -30599,13 +36649,14 @@ var x = class u { if (o.length > 0) for (; (r = this.tokenizer.rules.inline.reflinkSearch.exec(n)) != null; ) o.includes(r[0].slice(r[0].lastIndexOf("[") + 1, -1)) && (n = n.slice(0, r.index) + "[" + "a".repeat(r[0].length - 2) + "]" + n.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex)); } for (; (r = this.tokenizer.rules.inline.anyPunctuation.exec(n)) != null; ) n = n.slice(0, r.index) + "++" + n.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex); - for (; (r = this.tokenizer.rules.inline.blockSkip.exec(n)) != null; ) n = n.slice(0, r.index) + "[" + "a".repeat(r[0].length - 2) + "]" + n.slice(this.tokenizer.rules.inline.blockSkip.lastIndex); + let i; + for (; (r = this.tokenizer.rules.inline.blockSkip.exec(n)) != null; ) i = r[2] ? r[2].length : 0, n = n.slice(0, r.index + i) + "[" + "a".repeat(r[0].length - i - 2) + "]" + n.slice(this.tokenizer.rules.inline.blockSkip.lastIndex); n = this.options.hooks?.emStrongMask?.call({ lexer: this }, n) ?? n; - let i = false, s = ""; + let s = false, a = ""; for (; e; ) { - i || (s = ""), i = false; + s || (a = ""), s = false; let o; - if (this.options.extensions?.inline?.some((l) => (o = l.call({ lexer: this }, e, t)) ? (e = e.substring(o.raw.length), t.push(o), true) : false)) continue; + if (this.options.extensions?.inline?.some((u) => (o = u.call({ lexer: this }, e, t)) ? (e = e.substring(o.raw.length), t.push(o), true) : false)) continue; if (o = this.tokenizer.escape(e)) { e = e.substring(o.raw.length), t.push(o); continue; @@ -30620,11 +36671,11 @@ var x = class u { } if (o = this.tokenizer.reflink(e, this.tokens.links)) { e = e.substring(o.raw.length); - let l = t.at(-1); - o.type === "text" && l?.type === "text" ? (l.raw += o.raw, l.text += o.text) : t.push(o); + let u = t.at(-1); + o.type === "text" && u?.type === "text" ? (u.raw += o.raw, u.text += o.text) : t.push(o); continue; } - if (o = this.tokenizer.emStrong(e, n, s)) { + if (o = this.tokenizer.emStrong(e, n, a)) { e = e.substring(o.raw.length), t.push(o); continue; } @@ -30648,25 +36699,25 @@ var x = class u { e = e.substring(o.raw.length), t.push(o); continue; } - let a = e; + let p = e; if (this.options.extensions?.startInline) { - let l = 1 / 0, c = e.slice(1), p; - this.options.extensions.startInline.forEach((g) => { - p = g.call({ lexer: this }, c), typeof p == "number" && p >= 0 && (l = Math.min(l, p)); - }), l < 1 / 0 && l >= 0 && (a = e.substring(0, l + 1)); - } - if (o = this.tokenizer.inlineText(a)) { - e = e.substring(o.raw.length), o.raw.slice(-1) !== "_" && (s = o.raw.slice(-1)), i = true; - let l = t.at(-1); - l?.type === "text" ? (l.raw += o.raw, l.text += o.text) : t.push(o); + let u = 1 / 0, c = e.slice(1), g; + this.options.extensions.startInline.forEach((h) => { + g = h.call({ lexer: this }, c), typeof g == "number" && g >= 0 && (u = Math.min(u, g)); + }), u < 1 / 0 && u >= 0 && (p = e.substring(0, u + 1)); + } + if (o = this.tokenizer.inlineText(p)) { + e = e.substring(o.raw.length), o.raw.slice(-1) !== "_" && (a = o.raw.slice(-1)), s = true; + let u = t.at(-1); + u?.type === "text" ? (u.raw += o.raw, u.text += o.text) : t.push(o); continue; } if (e) { - let l = "Infinite loop on byte: " + e.charCodeAt(0); + let u = "Infinite loop on byte: " + e.charCodeAt(0); if (this.options.silent) { - console.error(l); + console.error(u); break; - } else throw new Error(l); + } else throw new Error(u); } } return t; @@ -30709,9 +36760,9 @@ ${this.parser.parse(e)} } list(e) { let t = e.ordered, n = e.start, r = ""; - for (let o = 0; o < e.items.length; o++) { - let a = e.items[o]; - r += this.listitem(a); + for (let a = 0; a < e.items.length; a++) { + let o = e.items[a]; + r += this.listitem(o); } let i = t ? "ol" : "ul", s = t && n !== 1 ? ' start="' + n + '"' : ""; return "<" + i + s + `> @@ -30742,7 +36793,7 @@ ${this.parser.parse(e)} for (let i = 0; i < e.rows.length; i++) { let s = e.rows[i]; n = ""; - for (let o = 0; o < s.length; o++) n += this.tablecell(s[o]); + for (let a = 0; a < s.length; a++) n += this.tablecell(s[a]); r += this.tablerow({ text: n }); } return r && (r = `${r}`), ` @@ -30824,7 +36875,7 @@ var $ = class { return ""; } }; -var b = class u2 { +var b = class l2 { options; renderer; textRenderer; @@ -30832,19 +36883,19 @@ var b = class u2 { this.options = e || T, this.options.renderer = this.options.renderer || new P(), this.renderer = this.options.renderer, this.renderer.options = this.options, this.renderer.parser = this, this.textRenderer = new $(); } static parse(e, t) { - return new u2(t).parse(e); + return new l2(t).parse(e); } static parseInline(e, t) { - return new u2(t).parseInline(e); + return new l2(t).parseInline(e); } parse(e, t = true) { let n = ""; for (let r = 0; r < e.length; r++) { let i = e[r]; if (this.options.extensions?.renderers?.[i.type]) { - let o = i, a = this.options.extensions.renderers[o.type].call({ parser: this }, o); - if (a !== false || !["space", "hr", "heading", "code", "table", "blockquote", "list", "html", "def", "paragraph", "text"].includes(o.type)) { - n += a || ""; + let a = i, o = this.options.extensions.renderers[a.type].call({ parser: this }, a); + if (o !== false || !["space", "hr", "heading", "code", "table", "blockquote", "list", "html", "def", "paragraph", "text"].includes(a.type)) { + n += o || ""; continue; } } @@ -30891,16 +36942,16 @@ var b = class u2 { continue; } case "text": { - let o = s, a = this.renderer.text(o); - for (; r + 1 < e.length && e[r + 1].type === "text"; ) o = e[++r], a += ` -` + this.renderer.text(o); - t ? n += this.renderer.paragraph({ type: "paragraph", raw: a, text: a, tokens: [{ type: "text", raw: a, text: a, escaped: true }] }) : n += a; + let a = s, o = this.renderer.text(a); + for (; r + 1 < e.length && e[r + 1].type === "text"; ) a = e[++r], o += ` +` + this.renderer.text(a); + t ? n += this.renderer.paragraph({ type: "paragraph", raw: o, text: o, tokens: [{ type: "text", raw: o, text: o, escaped: true }] }) : n += o; continue; } default: { - let o = 'Token with "' + s.type + '" type was not found.'; - if (this.options.silent) return console.error(o), ""; - throw new Error(o); + let a = 'Token with "' + s.type + '" type was not found.'; + if (this.options.silent) return console.error(a), ""; + throw new Error(a); } } } @@ -30911,9 +36962,9 @@ var b = class u2 { for (let r = 0; r < e.length; r++) { let i = e[r]; if (this.options.extensions?.renderers?.[i.type]) { - let o = this.options.extensions.renderers[i.type].call({ parser: this }, i); - if (o !== false || !["escape", "html", "link", "image", "strong", "em", "codespan", "br", "del", "text"].includes(i.type)) { - n += o || ""; + let a = this.options.extensions.renderers[i.type].call({ parser: this }, i); + if (a !== false || !["escape", "html", "link", "image", "strong", "em", "codespan", "br", "del", "text"].includes(i.type)) { + n += a || ""; continue; } } @@ -30960,9 +37011,9 @@ var b = class u2 { break; } default: { - let o = 'Token with "' + s.type + '" type was not found.'; - if (this.options.silent) return console.error(o), ""; - throw new Error(o); + let a = 'Token with "' + s.type + '" type was not found.'; + if (this.options.silent) return console.error(a), ""; + throw new Error(a); } } } @@ -31016,7 +37067,7 @@ var B = class { case "table": { let i = r; for (let s of i.header) n = n.concat(this.walkTokens(s.tokens, t)); - for (let s of i.rows) for (let o of s) n = n.concat(this.walkTokens(o.tokens, t)); + for (let s of i.rows) for (let a of s) n = n.concat(this.walkTokens(a.tokens, t)); break; } case "list": { @@ -31027,8 +37078,8 @@ var B = class { default: { let i = r; this.defaults.extensions?.childTokens?.[i.type] ? this.defaults.extensions.childTokens[i.type].forEach((s) => { - let o = i[s].flat(1 / 0); - n = n.concat(this.walkTokens(o, t)); + let a = i[s].flat(1 / 0); + n = n.concat(this.walkTokens(a, t)); }) : i.tokens && (n = n.concat(this.walkTokens(i.tokens, t))); } } @@ -31042,9 +37093,9 @@ var B = class { if (!i.name) throw new Error("extension name required"); if ("renderer" in i) { let s = t.renderers[i.name]; - s ? t.renderers[i.name] = function(...o) { - let a = i.renderer.apply(this, o); - return a === false && (a = s.apply(this, o)), a; + s ? t.renderers[i.name] = function(...a) { + let o = i.renderer.apply(this, a); + return o === false && (o = s.apply(this, a)), o; } : t.renderers[i.name] = i.renderer; } if ("tokenizer" in i) { @@ -31058,10 +37109,10 @@ var B = class { for (let s in n.renderer) { if (!(s in i)) throw new Error(`renderer '${s}' does not exist`); if (["options", "parser"].includes(s)) continue; - let o = s, a = n.renderer[o], l = i[o]; - i[o] = (...c) => { - let p = a.apply(i, c); - return p === false && (p = l.apply(i, c)), p || ""; + let a = s, o = n.renderer[a], p = i[a]; + i[a] = (...u) => { + let c = o.apply(i, u); + return c === false && (c = p.apply(i, u)), c || ""; }; } r.renderer = i; @@ -31071,10 +37122,10 @@ var B = class { for (let s in n.tokenizer) { if (!(s in i)) throw new Error(`tokenizer '${s}' does not exist`); if (["options", "rules", "lexer"].includes(s)) continue; - let o = s, a = n.tokenizer[o], l = i[o]; - i[o] = (...c) => { - let p = a.apply(i, c); - return p === false && (p = l.apply(i, c)), p; + let a = s, o = n.tokenizer[a], p = i[a]; + i[a] = (...u) => { + let c = o.apply(i, u); + return c === false && (c = p.apply(i, u)), c; }; } r.tokenizer = i; @@ -31084,30 +37135,30 @@ var B = class { for (let s in n.hooks) { if (!(s in i)) throw new Error(`hook '${s}' does not exist`); if (["options", "block"].includes(s)) continue; - let o = s, a = n.hooks[o], l = i[o]; - S.passThroughHooks.has(s) ? i[o] = (c) => { + let a = s, o = n.hooks[a], p = i[a]; + S.passThroughHooks.has(s) ? i[a] = (u) => { if (this.defaults.async && S.passThroughHooksRespectAsync.has(s)) return (async () => { - let g = await a.call(i, c); - return l.call(i, g); + let g = await o.call(i, u); + return p.call(i, g); })(); - let p = a.call(i, c); - return l.call(i, p); - } : i[o] = (...c) => { + let c = o.call(i, u); + return p.call(i, c); + } : i[a] = (...u) => { if (this.defaults.async) return (async () => { - let g = await a.apply(i, c); - return g === false && (g = await l.apply(i, c)), g; + let g = await o.apply(i, u); + return g === false && (g = await p.apply(i, u)), g; })(); - let p = a.apply(i, c); - return p === false && (p = l.apply(i, c)), p; + let c = o.apply(i, u); + return c === false && (c = p.apply(i, u)), c; }; } r.hooks = i; } if (n.walkTokens) { let i = this.defaults.walkTokens, s = n.walkTokens; - r.walkTokens = function(o) { - let a = []; - return a.push(s.call(this, o)), i && (a = a.concat(i.call(this, o))), a; + r.walkTokens = function(a) { + let o = []; + return o.push(s.call(this, a)), i && (o = o.concat(i.call(this, a))), o; }; } this.defaults = { ...this.defaults, ...r }; @@ -31124,24 +37175,24 @@ var B = class { } parseMarkdown(e) { return (n, r) => { - let i = { ...r }, s = { ...this.defaults, ...i }, o = this.onError(!!s.silent, !!s.async); - if (this.defaults.async === true && i.async === false) return o(new Error("marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise.")); - if (typeof n > "u" || n === null) return o(new Error("marked(): input parameter is undefined or null")); - if (typeof n != "string") return o(new Error("marked(): input parameter is of type " + Object.prototype.toString.call(n) + ", string expected")); + let i = { ...r }, s = { ...this.defaults, ...i }, a = this.onError(!!s.silent, !!s.async); + if (this.defaults.async === true && i.async === false) return a(new Error("marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise.")); + if (typeof n > "u" || n === null) return a(new Error("marked(): input parameter is undefined or null")); + if (typeof n != "string") return a(new Error("marked(): input parameter is of type " + Object.prototype.toString.call(n) + ", string expected")); if (s.hooks && (s.hooks.options = s, s.hooks.block = e), s.async) return (async () => { - let a = s.hooks ? await s.hooks.preprocess(n) : n, c = await (s.hooks ? await s.hooks.provideLexer() : e ? x.lex : x.lexInline)(a, s), p = s.hooks ? await s.hooks.processAllTokens(c) : c; - s.walkTokens && await Promise.all(this.walkTokens(p, s.walkTokens)); - let h = await (s.hooks ? await s.hooks.provideParser() : e ? b.parse : b.parseInline)(p, s); + let o = s.hooks ? await s.hooks.preprocess(n) : n, u = await (s.hooks ? await s.hooks.provideLexer() : e ? x.lex : x.lexInline)(o, s), c = s.hooks ? await s.hooks.processAllTokens(u) : u; + s.walkTokens && await Promise.all(this.walkTokens(c, s.walkTokens)); + let h = await (s.hooks ? await s.hooks.provideParser() : e ? b.parse : b.parseInline)(c, s); return s.hooks ? await s.hooks.postprocess(h) : h; - })().catch(o); + })().catch(a); try { s.hooks && (n = s.hooks.preprocess(n)); - let l = (s.hooks ? s.hooks.provideLexer() : e ? x.lex : x.lexInline)(n, s); - s.hooks && (l = s.hooks.processAllTokens(l)), s.walkTokens && this.walkTokens(l, s.walkTokens); - let p = (s.hooks ? s.hooks.provideParser() : e ? b.parse : b.parseInline)(l, s); - return s.hooks && (p = s.hooks.postprocess(p)), p; - } catch (a) { - return o(a); + let p = (s.hooks ? s.hooks.provideLexer() : e ? x.lex : x.lexInline)(n, s); + s.hooks && (p = s.hooks.processAllTokens(p)), s.walkTokens && this.walkTokens(p, s.walkTokens); + let c = (s.hooks ? s.hooks.provideParser() : e ? b.parse : b.parseInline)(p, s); + return s.hooks && (c = s.hooks.postprocess(c)), c; + } catch (o) { + return a(o); } }; } @@ -31158,19 +37209,19 @@ Please report this to https://github.com/markedjs/marked.`, e) { } }; var _ = new B(); -function k(u3, e) { - return _.parse(u3, e); +function k(l3, e) { + return _.parse(l3, e); } -k.options = k.setOptions = function(u3) { - return _.setOptions(u3), k.defaults = _.defaults, G(k.defaults), k; +k.options = k.setOptions = function(l3) { + return _.setOptions(l3), k.defaults = _.defaults, G(k.defaults), k; }; k.getDefaults = L; k.defaults = T; -k.use = function(...u3) { - return _.use(...u3), k.defaults = _.defaults, G(k.defaults), k; +k.use = function(...l3) { + return _.use(...l3), k.defaults = _.defaults, G(k.defaults), k; }; -k.walkTokens = function(u3, e) { - return _.walkTokens(u3, e); +k.walkTokens = function(l3, e) { + return _.walkTokens(l3, e); }; k.parseInline = _.parseInline; k.Parser = b; @@ -31182,21 +37233,22 @@ k.lexer = x.lex; k.Tokenizer = y; k.Hooks = S; k.parse = k; -var Ht = k.options; -var Zt = k.setOptions; -var Gt = k.use; -var Nt = k.walkTokens; -var Ft = k.parseInline; -var Qt = b.parse; -var Ut = x.lex; +var Zt = k.options; +var Gt = k.setOptions; +var Nt = k.use; +var Ft = k.walkTokens; +var jt = k.parseInline; +var Ut = b.parse; +var Kt = x.lex; // src/show.ts function formatConversationAsMarkdown(jsonl, startLine, endLine) { - const allLines = jsonl.trim().split("\n").filter((line) => line.trim()); - const lines = startLine !== void 0 || endLine !== void 0 ? allLines.slice( + const allLines = jsonl.split("\n"); + const selectedLines = startLine !== void 0 || endLine !== void 0 ? allLines.slice( startLine !== void 0 ? startLine - 1 : 0, endLine !== void 0 ? endLine : void 0 ) : allLines; + const lines = selectedLines.filter((line) => line.trim()); if (isCodexRollout(lines)) { return formatCodexConversationAsMarkdown(lines); } @@ -31861,7 +37913,7 @@ function formatCursorConversationAsMarkdown(lines) { var VERSION = "1.6.0"; // src/mcp-server.ts -import fs4 from "fs"; +import fs5 from "fs"; var SearchModeEnum = external_exports.enum(["vector", "text", "both"]); var ResponseFormatEnum = external_exports.enum(["markdown", "json"]); var SearchInputSchema = external_exports.object({ @@ -32040,10 +38092,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { } if (name === "read") { const params = ShowConversationInputSchema.parse(args); - if (!fs4.existsSync(params.path)) { + if (!fs5.existsSync(params.path)) { throw new Error(`File not found: ${params.path}`); } - const jsonlContent = fs4.readFileSync(params.path, "utf-8"); + const jsonlContent = await readAdmittedConversation(params.path, params.startLine, params.endLine); const markdownContent = formatConversationAsMarkdown( jsonlContent, params.startLine, diff --git a/dist/parser.js b/dist/parser.js index dcc1c270..08b3befc 100644 --- a/dist/parser.js +++ b/dist/parser.js @@ -1,13 +1,9 @@ import fs from 'fs'; -import readline from 'readline'; import path from 'path'; import crypto from 'crypto'; +import { admittedConversationLines } from './record-admission.js'; async function detectConversationHarness(filePath) { - const fileStream = fs.createReadStream(filePath); - const rl = readline.createInterface({ - input: fileStream, - crlfDelay: Infinity - }); + const rl = admittedConversationLines(filePath); for await (const line of rl) { if (!line.trim()) continue; @@ -73,11 +69,7 @@ export async function parseConversation(filePath, projectName, archivePath) { } async function parseClaudeConversation(filePath, projectName, archivePath) { const exchanges = []; - const fileStream = fs.createReadStream(filePath); - const rl = readline.createInterface({ - input: fileStream, - crlfDelay: Infinity - }); + const rl = admittedConversationLines(filePath); let lineNumber = 0; let currentExchange = null; const finalizeExchange = () => { @@ -343,11 +335,7 @@ function extractOpencodeToolCalls(parts, fallbackTimestamp) { } async function parseOpencodeConversation(filePath, projectName, archivePath) { const exchanges = []; - const fileStream = fs.createReadStream(filePath); - const rl = readline.createInterface({ - input: fileStream, - crlfDelay: Infinity - }); + const rl = admittedConversationLines(filePath); let lineNumber = 0; let sessionId; let cwd; @@ -524,11 +512,7 @@ function extractOmpText(content) { */ async function parseOmpConversation(filePath, projectName, archivePath) { const exchanges = []; - const fileStream = fs.createReadStream(filePath); - const rl = readline.createInterface({ - input: fileStream, - crlfDelay: Infinity - }); + const rl = admittedConversationLines(filePath); let sessionId; let cwd; let headerTimestamp; @@ -632,11 +616,7 @@ async function parseOmpConversation(filePath, projectName, archivePath) { } async function parseCodexConversation(filePath, projectName, archivePath) { const exchanges = []; - const fileStream = fs.createReadStream(filePath); - const rl = readline.createInterface({ - input: fileStream, - crlfDelay: Infinity - }); + const rl = admittedConversationLines(filePath); let lineNumber = 0; let sessionId; let cwd; @@ -763,7 +743,8 @@ async function parseCodexConversation(filePath, projectName, archivePath) { const payload = parsed.payload; const timestamp = parsed.timestamp || new Date().toISOString(); if (parsed.type === 'session_meta' && payload) { - sessionId = payload.id || sessionId; + // Forked transcripts may retain ancestor headers after the primary one. + sessionId = sessionId || payload.id; cwd = payload.cwd || cwd; gitBranch = payload.git?.branch || gitBranch; agentVersion = payload.cli_version || agentVersion; @@ -884,11 +865,7 @@ function cursorProjectFromPath(filePath) { const UUID_PATTERN = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i; async function parseCursorConversation(filePath, projectName, archivePath) { const exchanges = []; - const fileStream = fs.createReadStream(filePath); - const rl = readline.createInterface({ - input: fileStream, - crlfDelay: Infinity - }); + const rl = admittedConversationLines(filePath); // Live Cursor transcripts carry no per-message timestamps; fall back to the // file mtime (preserved from the source by sync's copyIfNewer). Legacy // exports from import-cursor-history embed real per-message timestamps. diff --git a/dist/record-admission.d.ts b/dist/record-admission.d.ts new file mode 100644 index 00000000..a62ddb17 --- /dev/null +++ b/dist/record-admission.d.ts @@ -0,0 +1,43 @@ +import type { ConversationExchange } from './types.js'; +/** Scan decoded instruction records without retaining the whole transcript. */ +export declare function shouldSkipConversation(filePath: string): boolean; +interface ExchangeExclusion { + session_id: string; + transcript: string; + line_start: number; + line_end: number; +} +interface ToolExclusion { + session_id: string; + transcript: string; + call_id: string; +} +export interface RecordExclusions { + version: 1; + exchanges: ExchangeExclusion[]; + tool_calls: ToolExclusion[]; + screening?: { + engine: 'gitleaks'; + executable: string; + sha256: string; + timeout_ms: number; + max_record_bytes: number; + }; +} +export declare function readRecordExclusions(): RecordExclusions | null; +export declare function excludedSession(policy: RecordExclusions | null, sessionId?: string): boolean; +/** Read original inputs without mutation, preserving their physical line numbers. */ +export declare function admittedConversationLines(file: string, policy?: RecordExclusions | null): AsyncGenerator; +/** Read the admitted display input without changing original physical coordinates. */ +export declare function readAdmittedConversation(file: string, startLine?: number, endLine?: number): Promise; +/** Enforce the same rule for direct callers that bypass transcript parsing. */ +export declare function admitExchange(exchange: ConversationExchange, policy?: RecordExclusions | null): ConversationExchange | null; +/** Publish only admitted bytes. Never write an unfiltered temporary copy. */ +export declare function archiveAdmittedConversation(source: string, destination: string, policy?: RecordExclusions | null): Promise; +/** Request-local admission: retained search rows are not trusted merely because they were indexed. */ +export declare function createSearchAdmission(): { + admit(exchange: ConversationExchange): ConversationExchange | null; + summary(exchange: ConversationExchange, text: string): string | undefined; + verify(): void; +}; +export {}; diff --git a/dist/record-admission.js b/dist/record-admission.js new file mode 100644 index 00000000..396a9166 --- /dev/null +++ b/dist/record-admission.js @@ -0,0 +1,637 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import crypto from 'node:crypto'; +import os from 'node:os'; +import { spawnSync } from 'node:child_process'; +import { SUMMARIZER_CONTEXT_MARKER } from './constants.js'; +import { getSuperpowersDir } from './paths.js'; +const EXCLUSION_MARKERS = [ + 'DO NOT INDEX THIS CHAT', + 'Only use NO_INSIGHTS_FOUND', + SUMMARIZER_CONTEXT_MARKER, +]; +const MARKER_SCAN_CHUNK_BYTES = 1 << 20; // 1 MiB +/** Scan decoded instruction records without retaining the whole transcript. */ +export function shouldSkipConversation(filePath) { + let fd; + try { + fd = fs.openSync(filePath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW); + const initial = fs.fstatSync(fd); + const excluded = descriptorHasExclusionMarker(fd); + assertUnchangedInput(filePath, initial); + return excluded; + } + catch { + return true; + } + finally { + if (fd !== undefined) + fs.closeSync(fd); + } +} +function instructionText(record) { + if (!object(record)) + return ''; + // Codex records both the response item and its user-message event. + if (record.type === 'event_msg' && object(record.payload)) { + return record.payload.type === 'user_message' && typeof record.payload.message === 'string' + ? record.payload.message : ''; + } + const message = record.type === 'response_item' ? record.payload : record.message; + if (!object(message) || (message.role ?? record.role) !== 'user') + return ''; + const content = record.type === 'opencode_message' ? record.parts : message.content; + if (typeof content === 'string') + return content; + if (!Array.isArray(content)) + return ''; + // Claude tool results are wrapped in a user envelope, but are not instructions. + return content.filter(part => object(part) && ['text', 'input_text'].includes(String(part.type)) + && typeof part.text === 'string').map(part => part.text).join('\n'); +} +function descriptorHasExclusionMarker(fd, byteLimit = Infinity, digest) { + if (!fs.fstatSync(fd).isFile()) + throw new Error('Conversation input must be a regular file'); + const buffer = Buffer.allocUnsafe(MARKER_SCAN_CHUNK_BYTES); + const decoder = new TextDecoder('utf-8', { fatal: true }); + let pending = Buffer.alloc(0); + let position = 0; + const excluded = (bytes) => { + const line = decoder.decode(bytes); + let text; + try { + text = instructionText(JSON.parse(line)); + } + catch { + text = line; + } // Preserve conservative handling of legacy raw transcripts. + return EXCLUSION_MARKERS.some(marker => text.includes(marker)); + }; + let count; + while (position < byteLimit && (count = fs.readSync(fd, buffer, 0, Math.min(buffer.length, byteLimit - position), position)) > 0) { + position += count; + digest?.update(buffer.subarray(0, count)); + pending = Buffer.concat([pending, buffer.subarray(0, count)]); + let newline; + while ((newline = pending.indexOf(10)) !== -1) { + if (newline > 64 * 1024 * 1024) + throw new Error('Conversation record exceeds admission byte limit'); + if (excluded(pending.subarray(0, newline))) + return true; + pending = pending.subarray(newline + 1); + } + if (pending.length > 64 * 1024 * 1024) + throw new Error('Conversation record exceeds admission byte limit'); + } + if (position < byteLimit && byteLimit !== Infinity) + throw new Error('Conversation changed during record admission'); + return pending.length > 0 && excluded(pending); +} +function assertUnchangedInput(file, initial) { + const current = fs.lstatSync(file); + if (!current.isFile() || initial.dev !== current.dev || initial.ino !== current.ino || + initial.size !== current.size || initial.mtimeMs !== current.mtimeMs || initial.ctimeMs !== current.ctimeMs) { + throw new Error('Conversation changed during record admission'); + } +} +function assertUnchangedSearchPrefix(file, initial, expectedDigest) { + // A live transcript may grow after the search snapshot. Rehash the pinned + // prefix only when metadata changes, so appended bytes cannot hide a rewrite. + const changed = () => new Error('Conversation changed during record admission'); + let current; + try { + current = fs.lstatSync(file); + } + catch { + throw changed(); + } + if (!current.isFile() || initial.dev !== current.dev || initial.ino !== current.ino || current.size < initial.size) { + throw changed(); + } + if (current.size === initial.size && current.mtimeMs === initial.mtimeMs && current.ctimeMs === initial.ctimeMs) + return; + let fd; + try { + fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW); + const opened = fs.fstatSync(fd); + if (!opened.isFile() || opened.dev !== initial.dev || opened.ino !== initial.ino || opened.size < initial.size) + throw changed(); + const digest = crypto.createHash('sha256'); + const buffer = Buffer.allocUnsafe(MARKER_SCAN_CHUNK_BYTES); + let position = 0; + while (position < initial.size) { + const count = fs.readSync(fd, buffer, 0, Math.min(buffer.length, initial.size - position), position); + if (count <= 0) + throw changed(); + digest.update(buffer.subarray(0, count)); + position += count; + } + if (digest.digest('hex') !== expectedDigest) + throw changed(); + current = fs.lstatSync(file); + if (!current.isFile() || current.dev !== initial.dev || current.ino !== initial.ino || current.size < initial.size) + throw changed(); + } + catch { + throw changed(); + } + finally { + if (fd !== undefined) + fs.closeSync(fd); + } +} +function assertUnchangedPolicy(expected) { + if (JSON.stringify(readRecordExclusions()) !== expected) { + throw new Error('Record exclusion policy changed during record admission'); + } +} +function prepareArchiveParent(directory) { + try { + const info = fs.lstatSync(directory); + if (!info.isDirectory() || info.isSymbolicLink()) + throw new Error('Archive parent must be a non-symlink directory'); + } + catch (error) { + if (error.code !== 'ENOENT') + throw error; + const parent = path.dirname(directory); + if (parent === directory) + throw error; + prepareArchiveParent(parent); + fs.mkdirSync(directory, { mode: 0o700 }); + } + return fs.realpathSync(directory); +} +const POLICY_ERROR = 'Invalid or unreadable record exclusion policy'; +const UUID = /[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}/ig; +function object(value) { + return value !== null && typeof value === 'object' && !Array.isArray(value); +} +function keys(value, expected) { + return Object.keys(value).length === expected.length && expected.every(key => key in value); +} +function transcriptName(value) { + return identity(value) && !/[\\/]/.test(value) && value.endsWith('.jsonl') && value !== '.jsonl'; +} +function identity(value) { + return typeof value === 'string' && value.length > 0 && value.length <= 512 && !/[\x00-\x1f]/.test(value); +} +/** Keep scanner inputs local to one record, including JSON-escaped text. */ +function screeningText(value, depth = 0) { + if (depth > 24) + throw new Error('Record screening nesting limit exceeded'); + if (typeof value === 'string') { + let decoded; + try { + decoded = JSON.parse(value); + } + catch { + return value; + } + if (typeof decoded === 'string' || object(decoded) || Array.isArray(decoded)) { + return screeningText(decoded, depth + 1); + } + return value; + } + if (Array.isArray(value)) + return value.map(item => screeningText(item, depth + 1)).join('\n'); + if (object(value)) { + // Generic credential rules need an assignment delimiter, not just a nearby + // keyword. Decode nested text without destroying its key/value relation. + return Object.entries(value).map(([key, item]) => `${key} = ${screeningText(item, depth + 1)}`).join('\n'); + } + return value == null ? '' : String(value); +} +/** One pinned, short-lived local scanner context; no secret-bearing cache. */ +function createRecordScreener(policy) { + const config = policy?.screening; + if (!config) + return { scan() { }, close() { } }; + const unavailable = () => new Error('Record screening unavailable'); + let initial; + let fd; + try { + if (fs.realpathSync(config.executable) !== config.executable) + throw unavailable(); + fd = fs.openSync(config.executable, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW); + initial = fs.fstatSync(fd); + if (!initial.isFile() || initial.size < 1 || initial.size > 134_217_728 || + (initial.mode & 0o022) !== 0 || + (typeof process.getuid === 'function' && initial.uid !== 0 && initial.uid !== process.getuid())) { + throw unavailable(); + } + fs.accessSync(config.executable, fs.constants.X_OK); + const digest = crypto.createHash('sha256').update(fs.readFileSync(fd)).digest('hex'); + if (digest !== config.sha256) + throw unavailable(); + } + catch { + throw unavailable(); + } + finally { + if (fd !== undefined) + fs.closeSync(fd); + } + let directory; + try { + directory = fs.mkdtempSync(path.join(os.tmpdir(), 'episodic-screen-')); + fs.chmodSync(directory, 0o700); + } + catch { + throw unavailable(); + } + const unchanged = () => { + try { + const current = fs.lstatSync(config.executable); + if (!current.isFile() || current.dev !== initial.dev || current.ino !== initial.ino || + current.size !== initial.size || current.mtimeMs !== initial.mtimeMs || + current.ctimeMs !== initial.ctimeMs) + throw unavailable(); + } + catch { + throw unavailable(); + } + }; + return { + scan(value) { + const raw = typeof value === 'string' ? value : JSON.stringify(value); + if (raw === undefined) + return; + if (Buffer.byteLength(raw) > config.max_record_bytes) + throw new Error('Record screening byte limit exceeded'); + const input = screeningText(value); + if (Buffer.byteLength(input) > config.max_record_bytes) + throw new Error('Record screening byte limit exceeded'); + if (!input.trim()) + return; + unchanged(); + const result = spawnSync(config.executable, [ + 'stdin', '--redact', '--no-banner', '--log-level', 'error', + '--ignore-gitleaks-allow', '--exit-code', '10', '--report-format', 'json', '--report-path', '-', + ], { + input, encoding: 'utf8', cwd: directory, shell: false, + env: { HOME: directory, USERPROFILE: directory, PATH: path.dirname(config.executable) }, + timeout: config.timeout_ms, killSignal: 'SIGKILL', maxBuffer: 1_048_576, + windowsHide: true, + }); + unchanged(); + // Exit zero alone is not a clean scan. Validate the full report and never + // forward stdout/stderr, findings, fingerprints or input into errors. + if (result.error || result.signal || ![0, 10].includes(result.status ?? -1)) + throw unavailable(); + let report; + try { + report = JSON.parse(result.stdout); + } + catch { + throw unavailable(); + } + if (!Array.isArray(report) || (result.status === 0 && report.length !== 0) || + (result.status === 10 && (report.length === 0 || !report.every(item => object(item) && typeof item.RuleID === 'string')))) { + throw unavailable(); + } + if (result.status === 10) + throw new Error('Record content rejected by screening'); + }, + close() { fs.rmSync(directory, { recursive: true, force: true }); }, + }; +} +export function readRecordExclusions() { + const file = path.join(getSuperpowersDir(), 'record-exclusions.json'); + let fd; + try { + fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW); + const info = fs.fstatSync(fd); + if (!info.isFile() || info.size > 4 * 1024 * 1024) + throw new Error(POLICY_ERROR); + const value = JSON.parse(fs.readFileSync(fd, 'utf8')); + if (!object(value) || !keys(value, 'screening' in value + ? ['version', 'exchanges', 'tool_calls', 'screening'] : ['version', 'exchanges', 'tool_calls']) || value.version !== 1 || + !Array.isArray(value.exchanges) || !Array.isArray(value.tool_calls)) + throw new Error(POLICY_ERROR); + if ('screening' in value) { + const s = value.screening; + if (!object(s) || !keys(s, ['engine', 'executable', 'sha256', 'timeout_ms', 'max_record_bytes']) || + s.engine !== 'gitleaks' || typeof s.executable !== 'string' || !path.isAbsolute(s.executable) || + /[\x00-\x1f]/.test(s.executable) || typeof s.sha256 !== 'string' || !/^[a-f0-9]{64}$/.test(s.sha256) || + !Number.isSafeInteger(s.timeout_ms) || Number(s.timeout_ms) < 50 || Number(s.timeout_ms) > 30_000 || + !Number.isSafeInteger(s.max_record_bytes) || Number(s.max_record_bytes) < 1 || Number(s.max_record_bytes) > 8_388_608) { + throw new Error(POLICY_ERROR); + } + } + for (const row of value.exchanges) { + if (!object(row) || !keys(row, ['session_id', 'transcript', 'line_start', 'line_end']) || !identity(row.session_id) || !transcriptName(row.transcript) || + !Number.isSafeInteger(row.line_start) || !Number.isSafeInteger(row.line_end) || + row.line_start < 1 || row.line_end < row.line_start) + throw new Error(POLICY_ERROR); + } + for (const row of value.tool_calls) { + if (!object(row) || !keys(row, ['session_id', 'transcript', 'call_id']) || !identity(row.session_id) || !transcriptName(row.transcript) || !identity(row.call_id)) + throw new Error(POLICY_ERROR); + } + return value; + } + catch (error) { + if (fd === undefined && error.code === 'ENOENT') + return null; + throw new Error(POLICY_ERROR); + } + finally { + if (fd !== undefined) + fs.closeSync(fd); + } +} +export function excludedSession(policy, sessionId) { + return !!sessionId && !!policy && (policy.exchanges.some(x => x.session_id === sessionId) || + policy.tool_calls.some(x => x.session_id === sessionId)); +} +function sessionFromPath(file) { + return path.basename(file).match(UUID)?.at(-1); +} +function sessionFromRecord(value) { + if (value?.type === 'session_meta') + return value.payload?.id; + if (value?.type === 'session') + return value.id; + if (value?.type === 'opencode_session') + return value.session?.id; + return value?.sessionId ?? value?.sessionID ?? value?.message?.sessionID; +} +function blockedCall(policy, sessionId, transcript, callId) { + return typeof callId === 'string' && policy.tool_calls.some(x => x.session_id === sessionId && x.transcript === transcript && x.call_id === callId); +} +function filterRecord(line, number, policy, transcript, sessionId) { + if (!policy || !sessionId) + return line; + if (policy.exchanges.some(x => x.session_id === sessionId && x.transcript === transcript && number >= x.line_start && number <= x.line_end)) + return ''; + let value; + try { + value = JSON.parse(line); + } + catch { + return line; + } + if (blockedCall(policy, sessionId, transcript, value?.payload?.call_id) || blockedCall(policy, sessionId, transcript, value?.call_id)) + return ''; + // Claude and Cursor may combine safe text and tool payloads in one physical record. + // Preserve the record and safe blocks rather than excluding the entire exchange. + const content = value?.message?.content; + if (Array.isArray(content)) { + const kept = content.filter(block => !blockedCall(policy, sessionId, transcript, block?.id ?? block?.tool_use_id)); + if (kept.length !== content.length) { + return JSON.stringify({ ...value, message: { ...value.message, content: kept } }); + } + } + if (Array.isArray(value?.parts)) { + const kept = value.parts.filter((part) => !blockedCall(policy, sessionId, transcript, part?.callID ?? part?.id)); + if (kept.length !== value.parts.length) + return JSON.stringify({ ...value, parts: kept }); + } + return line; +} +/** Keep byte delimiters and physical line coordinates stable in derived copies. */ +async function* admittedRecords(file, policy, startLine = 1, endLine = Infinity) { + const policySnapshot = JSON.stringify(policy); + assertUnchangedPolicy(policySnapshot); + const input = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW); + const initial = fs.fstatSync(input); + let stream; + let screener; + const decoder = new TextDecoder('utf-8', { fatal: true }); + let pending = Buffer.alloc(0); + let sessionId = sessionFromPath(file); + let primaryIdentitySeen = false; + let number = 0; + const admit = (bytes, terminated) => { + number++; + const crlf = terminated && bytes.at(-1) === 13; + const line = decoder.decode(crlf ? bytes.subarray(0, -1) : bytes); + let recordSession; + try { + recordSession = sessionFromRecord(JSON.parse(line)); + } + catch { /* Parser owns malformed records. */ } + if (recordSession && !primaryIdentitySeen) { + if (sessionId && recordSession !== sessionId && (excludedSession(policy, sessionId) || excludedSession(policy, recordSession))) { + throw new Error('Record exclusion session identity mismatch'); + } + sessionId = recordSession; + primaryIdentitySeen = true; + } + const filtered = number >= startLine && number <= endLine + ? filterRecord(line, number, policy, path.basename(file), sessionId) : ''; + screener?.scan(filtered); + return { line: filtered, ending: terminated ? (crlf ? '\r\n' : '\n') : '' }; + }; + try { + // The marker scan and record stream share one descriptor and identity. + // A path replacement cannot substitute a different unscanned transcript. + if (descriptorHasExclusionMarker(input)) + return; + assertUnchangedInput(file, initial); + screener = createRecordScreener(policy); + stream = fs.createReadStream(file, { fd: input, autoClose: true, start: 0 }); + for await (const chunk of stream) { + assertUnchangedInput(file, initial); + assertUnchangedPolicy(policySnapshot); + pending = Buffer.concat([pending, chunk]); + let newline; + while ((newline = pending.indexOf(10)) !== -1) { + yield admit(pending.subarray(0, newline), true); + pending = pending.subarray(newline + 1); + } + if (pending.length > 64 * 1024 * 1024) + throw new Error('Conversation record exceeds admission byte limit'); + } + if (pending.length) + yield admit(pending, false); + assertUnchangedInput(file, initial); + assertUnchangedPolicy(policySnapshot); + } + finally { + if (stream) + stream.destroy(); + else + fs.closeSync(input); + screener?.close(); + } +} +/** Read original inputs without mutation, preserving their physical line numbers. */ +export async function* admittedConversationLines(file, policy = readRecordExclusions()) { + if (!fs.lstatSync(file).isFile()) + throw new Error('Conversation input must be a regular non-symlink file'); + if (shouldSkipConversation(file)) + return; + for await (const record of admittedRecords(file, policy)) + yield record.line; +} +/** Read the admitted display input without changing original physical coordinates. */ +export async function readAdmittedConversation(file, startLine = 1, endLine = Infinity) { + if (!Number.isSafeInteger(startLine) || startLine < 1 || + (endLine !== Infinity && (!Number.isSafeInteger(endLine) || endLine < startLine))) { + throw new Error('Invalid conversation line range'); + } + const lines = []; + for await (const record of admittedRecords(file, readRecordExclusions(), startLine, endLine)) + lines.push(record.line); + return lines.join('\n'); +} +/** Enforce the same rule for direct callers that bypass transcript parsing. */ +export function admitExchange(exchange, policy = readRecordExclusions()) { + if (!policy) + return exchange; + // Historical Codex rows may carry a fork ancestor's ID. Neither that metadata + // nor the filename may override a denial bound to the other known identity. + const sessionIds = [exchange.sessionId, sessionFromPath(exchange.archivePath)].filter((id) => !!id); + const transcript = path.basename(exchange.archivePath); + if (policy.exchanges.some(x => sessionIds.includes(x.session_id) && x.transcript === transcript && exchange.lineStart <= x.line_end && exchange.lineEnd >= x.line_start)) + return null; + const kept = exchange.toolCalls?.filter(call => !sessionIds.some(id => blockedCall(policy, id, transcript, call.id))); + const admitted = kept && kept.length !== exchange.toolCalls?.length ? { ...exchange, toolCalls: kept } : exchange; + const screener = createRecordScreener(policy); + try { + // These are separate original records, not one keyword-prefilter fragment. + screener.scan(admitted.userMessage); + screener.scan(admitted.assistantMessage); + for (const call of admitted.toolCalls ?? []) + screener.scan(call); + const { userMessage, assistantMessage, toolCalls, ...metadata } = admitted; + screener.scan(metadata); + return admitted; + } + finally { + screener.close(); + } +} +/** Publish only admitted bytes. Never write an unfiltered temporary copy. */ +export async function archiveAdmittedConversation(source, destination, policy = readRecordExclusions()) { + const policySnapshot = JSON.stringify(policy); + assertUnchangedPolicy(policySnapshot); + if (shouldSkipConversation(source)) + return false; + if (path.resolve(source) === path.resolve(destination)) + throw new Error('Source and derived archive must be distinct'); + const original = fs.lstatSync(source); + if (!original.isFile()) + throw new Error('Conversation source must be a regular non-symlink file'); + let target; + if (fs.existsSync(destination)) { + target = fs.lstatSync(destination); + if (!target.isFile() || target.dev === original.dev && target.ino === original.ino) { + throw new Error('Source and derived archive must be distinct regular files'); + } + } + if (!policy && target && target.mtimeMs >= original.mtimeMs && target.size === original.size) + return false; + const initial = fs.statSync(source); + const parent = prepareArchiveParent(path.dirname(path.resolve(destination))); + const parentIdentity = fs.statSync(parent); + destination = path.join(parent, path.basename(destination)); + const temp = destination + '.tmp.' + process.pid + '.' + crypto.randomUUID(); + const fd = fs.openSync(temp, 'wx', 0o600); + const hash = crypto.createHash('sha256'); + try { + for await (const record of admittedRecords(source, policy)) { + const bytes = Buffer.from(record.line + record.ending); + hash.update(bytes); + fs.writeFileSync(fd, bytes); + } + fs.fsyncSync(fd); + } + catch (error) { + fs.closeSync(fd); + fs.unlinkSync(temp); + throw error; + } + fs.closeSync(fd); + try { + const final = fs.statSync(source); + if (initial.dev !== final.dev || initial.ino !== final.ino || initial.size !== final.size || initial.mtimeMs !== final.mtimeMs) { + throw new Error('Conversation changed during record admission'); + } + if (fs.existsSync(destination)) { + const existing = crypto.createHash('sha256'); + for await (const chunk of fs.createReadStream(destination)) + existing.update(chunk); + if (existing.digest('hex') === hash.digest('hex')) + return false; + } + assertUnchangedInput(source, original); + assertUnchangedPolicy(policySnapshot); + const currentParent = fs.lstatSync(parent); + if (!currentParent.isDirectory() || currentParent.dev !== parentIdentity.dev || currentParent.ino !== parentIdentity.ino) { + throw new Error('Archive parent changed during record admission'); + } + fs.renameSync(temp, destination); + fs.utimesSync(destination, initial.atimeMs / 1000, Math.ceil(initial.mtimeMs) / 1000); + return true; + } + finally { + if (fs.existsSync(temp)) + fs.unlinkSync(temp); + } +} +/** Request-local admission: retained search rows are not trusted merely because they were indexed. */ +export function createSearchAdmission() { + const policy = readRecordExclusions(); + const policySnapshot = JSON.stringify(policy); + const files = new Map(); + const checkFile = (file) => { + assertUnchangedPolicy(policySnapshot); + const cached = files.get(file); + if (cached) + return !cached.excluded; + let fd; + try { + const pathInfo = fs.lstatSync(file); + if (!pathInfo.isFile()) + throw new Error('Conversation changed during record admission'); + fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW); + const initial = fs.fstatSync(fd); + if (!initial.isFile() || initial.dev !== pathInfo.dev || initial.ino !== pathInfo.ino || + !Number.isSafeInteger(initial.size)) + throw new Error('Conversation changed during record admission'); + const hash = crypto.createHash('sha256'); + // The observed size is this request's snapshot boundary. Records appended + // later are admitted by the next request, including conversation opt-outs. + const excluded = descriptorHasExclusionMarker(fd, initial.size, hash); + files.set(file, { initial, excluded, digest: excluded ? undefined : hash.digest('hex') }); + return !excluded; + } + catch { + throw new Error('Conversation changed during record admission'); + } + finally { + if (fd !== undefined) + fs.closeSync(fd); + } + }; + return { + admit(exchange) { + if (!checkFile(exchange.archivePath)) + return null; + const admitted = admitExchange(exchange, policy); + assertUnchangedPolicy(policySnapshot); + return admitted; + }, + summary(exchange, text) { + // An old summary has no record-level provenance. A filtered source may have + // contributed any of its text, so do not expose that derived summary. + const ids = [exchange.sessionId, sessionFromPath(exchange.archivePath)]; + const transcript = path.basename(exchange.archivePath); + if (policy && [...policy.exchanges, ...policy.tool_calls].some(row => row.transcript === transcript && ids.includes(row.session_id))) + return undefined; + return admitExchange({ ...exchange, userMessage: text, assistantMessage: '', toolCalls: [] }, policy) + ? text : undefined; + }, + verify() { + assertUnchangedPolicy(policySnapshot); + for (const [file, { initial, digest }] of files) { + if (digest !== undefined) + assertUnchangedSearchPrefix(file, initial, digest); + } + assertUnchangedPolicy(policySnapshot); + }, + }; +} diff --git a/dist/search.js b/dist/search.js index 281b4f0f..ae5497de 100644 --- a/dist/search.js +++ b/dist/search.js @@ -1,4 +1,5 @@ import { initDatabase } from './db.js'; +import { createSearchAdmission } from './record-admission.js'; import { initEmbeddings, generateQueryEmbedding } from './embeddings.js'; import { isErroredSentinel } from './summary-sentinel.js'; import fs from 'fs'; @@ -160,67 +161,74 @@ export async function searchConversations(query, options = {}) { validateISODate(after, '--after'); if (before) validateISODate(before, '--before'); + const admission = createSearchAdmission(); const db = initDatabase(); + const admitted = (row) => admission.admit(exchangeFromRow(row)) !== null; let results = []; const { sql: filterClause, params: filterParams } = buildSearchFilters(options); - if (mode === 'vector' || mode === 'both') { - // Vector similarity search. - // vec0 applies KNN before the WHERE clause and before our sidechain - // de-rank, so we over-fetch candidates and trim after the final ordering. - await initEmbeddings(); - const queryEmbedding = await generateQueryEmbedding(query); - const k = limit * 3; - const stmt = db.prepare(` - SELECT - ${EXCHANGE_SELECT_COLUMNS}, - vec.distance - FROM vec_exchanges AS vec - JOIN exchanges AS e ON vec.id = e.id - WHERE vec.embedding MATCH ? - AND k = ? - ${sidechainClause} - ${filterClause} - ORDER BY (vec.distance + e.is_sidechain * ?) ASC - `); - results = stmt.all(Buffer.from(new Float32Array(queryEmbedding).buffer), k, ...filterParams, SIDECHAIN_DISTANCE_PENALTY); - if (results.length > limit) { - results = results.slice(0, limit); + try { + if (mode === 'vector' || mode === 'both') { + // Order the filtered candidates before admission. The vec0 KNN API caps k; + // increasing k cannot find safe results beyond a large excluded prefix. + await initEmbeddings(); + const queryEmbedding = await generateQueryEmbedding(query); + const stmt = db.prepare(` + SELECT ${EXCHANGE_SELECT_COLUMNS}, + vec_distance_l2(vec.embedding, ?) AS retrieval_distance + FROM vec_exchanges AS vec + JOIN exchanges AS e ON vec.id = e.id + WHERE 1 = 1 ${sidechainClause} ${filterClause} + ORDER BY (retrieval_distance + e.is_sidechain * ?) ASC + `); + for (const row of stmt.iterate(Buffer.from(new Float32Array(queryEmbedding).buffer), ...filterParams, SIDECHAIN_DISTANCE_PENALTY)) { + if (admitted(row)) + results.push(row); + if (results.length === limit) + break; + } } - } - if (mode === 'text' || mode === 'both') { - // Text search: AND of per-token LIKE patterns so multi-word queries match - // when every term appears somewhere in the exchange (any order, either field). - // See #127 — whole-query contiguous substring matching returned empty for - // typical multi-word searches that are not verbatim phrases. Sidechain rows - // are de-ranked (ordered after main-thread rows), not excluded (#128). - const { sql: textMatchSql, params: textMatchParams } = buildTextMatchClause(query); - const textStmt = db.prepare(` - SELECT - ${EXCHANGE_SELECT_COLUMNS}, - 0 as distance - FROM exchanges AS e - WHERE ${textMatchSql} - ${sidechainClause} - ${filterClause} - ORDER BY e.is_sidechain ASC, e.timestamp DESC - LIMIT ? - `); - const textResults = textStmt.all(...textMatchParams, ...filterParams, limit); - if (mode === 'both') { - // Merge and deduplicate by ID - const seenIds = new Set(results.map(r => r.id)); - for (const textResult of textResults) { - if (!seenIds.has(textResult.id)) { - results.push(textResult); + if (mode === 'text' || mode === 'both') { + // Text search: AND of per-token LIKE patterns so multi-word queries match + // when every term appears somewhere in the exchange (any order, either field). + // See #127 — whole-query contiguous substring matching returned empty for + // typical multi-word searches that are not verbatim phrases. Sidechain rows + // are de-ranked (ordered after main-thread rows), not excluded (#128). + const { sql: textMatchSql, params: textMatchParams } = buildTextMatchClause(query); + const textStmt = db.prepare(` + SELECT + ${EXCHANGE_SELECT_COLUMNS}, + 0 as retrieval_distance + FROM exchanges AS e + WHERE ${textMatchSql} + ${sidechainClause} + ${filterClause} + ORDER BY e.is_sidechain ASC, e.timestamp DESC + `); + const textResults = []; + for (const row of textStmt.iterate(...textMatchParams, ...filterParams)) { + if (admitted(row)) + textResults.push(row); + if (textResults.length === limit) + break; + } + if (mode === 'both') { + // Merge and deduplicate by ID + const seenIds = new Set(results.map(r => r.id)); + for (const textResult of textResults) { + if (!seenIds.has(textResult.id)) { + results.push(textResult); + } } } - } - else { - results = textResults; + else { + results = textResults; + } } } - db.close(); - return results.map((row) => { + finally { + db.close(); + } + const output = results.map((row) => { const exchange = exchangeFromRow(row); // Try to load summary if available. Skip error sentinels (#96) so failed // summarizations don't surface as the conversation's summary in results. @@ -229,7 +237,7 @@ export async function searchConversations(query, options = {}) { if (fs.existsSync(summaryPath)) { const raw = fs.readFileSync(summaryPath, 'utf-8'); if (!isErroredSentinel(raw)) { - summary = raw.trim(); + summary = admission.summary(exchange, raw.trim()); } } // Create snippet (first 200 chars, collapse newlines) @@ -237,11 +245,13 @@ export async function searchConversations(query, options = {}) { const snippet = snippetText + (exchange.userMessage.length > 200 ? '...' : ''); return { exchange, - similarity: mode === 'text' ? undefined : l2DistanceToCosineSimilarity(row.distance), + similarity: mode === 'text' ? undefined : l2DistanceToCosineSimilarity(row.retrieval_distance), snippet, summary }; }); + admission.verify(); + return output; } // Helper function to count lines in a file efficiently async function countLines(filePath) { diff --git a/dist/show-cli.js b/dist/show-cli.js index 53ca9739..c5693d30 100644 --- a/dist/show-cli.js +++ b/dist/show-cli.js @@ -1,4 +1,4 @@ -import { readFileSync } from 'fs'; +import { readAdmittedConversation } from './record-admission.js'; import { formatConversationAsMarkdown, formatConversationAsHTML } from './show.js'; const args = process.argv.slice(2); // Parse arguments @@ -42,7 +42,7 @@ if (!filePath) { process.exit(1); } try { - const jsonl = readFileSync(filePath, 'utf-8'); + const jsonl = await readAdmittedConversation(filePath); if (format === 'html') { console.log(formatConversationAsHTML(jsonl)); } diff --git a/dist/show.js b/dist/show.js index 2f297196..9d1d01f9 100644 --- a/dist/show.js +++ b/dist/show.js @@ -1,10 +1,11 @@ import { marked } from 'marked'; export function formatConversationAsMarkdown(jsonl, startLine, endLine) { - const allLines = jsonl.trim().split('\n').filter(line => line.trim()); + const allLines = jsonl.split('\n'); // Apply line range if specified (1-indexed, inclusive) - const lines = startLine !== undefined || endLine !== undefined + const selectedLines = startLine !== undefined || endLine !== undefined ? allLines.slice(startLine !== undefined ? startLine - 1 : 0, endLine !== undefined ? endLine : undefined) : allLines; + const lines = selectedLines.filter(line => line.trim()); if (isCodexRollout(lines)) { return formatCodexConversationAsMarkdown(lines); } diff --git a/dist/summarizer.d.ts b/dist/summarizer.d.ts index 4c97be52..039d6252 100644 --- a/dist/summarizer.d.ts +++ b/dist/summarizer.d.ts @@ -57,10 +57,11 @@ export interface CodexSummarizerCommand { command: string; args: string[]; prompt: string; - sessionId: string; + sessionId?: string; model?: string; versionArgs?: string[]; skipVersionCheck?: boolean; + isolated?: boolean; } /** * Get API environment overrides for summarization calls. @@ -150,10 +151,11 @@ export declare function buildSummarizerQueryOptions(args: { }): Record; export declare function buildCodexSummaryPrompt(): string; export declare function buildCodexSummarizerCommand(args: { - sessionId: string; + sessionId?: string; prompt: string; model?: string; codexBin?: string; + isolated?: boolean; }): CodexSummarizerCommand; export declare function runCodexCommand(command: CodexSummarizerCommand): Promise; /** diff --git a/dist/summarizer.js b/dist/summarizer.js index 60719809..2ffac728 100644 --- a/dist/summarizer.js +++ b/dist/summarizer.js @@ -1,4 +1,7 @@ import fs from 'fs'; +import os from 'node:os'; +import path from 'node:path'; +import { admitExchange, readRecordExclusions } from './record-admission.js'; import { query } from '@anthropic-ai/claude-agent-sdk'; import { SUMMARIZER_CONTEXT_MARKER } from './constants.js'; import { VERSION } from './version.js'; @@ -362,12 +365,27 @@ Bad: } export function buildCodexSummarizerCommand(args) { const command = args.codexBin || process.env.EPISODIC_MEMORY_CODEX_BIN || 'codex'; + if (args.isolated && args.sessionId) + throw new Error('An isolated summary cannot resume a source session'); + const restrictions = args.isolated ? [ + ...['shell_tool', 'unified_exec', 'shell_snapshot', 'apps', 'plugins', 'remote_plugin', + 'hooks', 'multi_agent', 'multi_agent_v2', 'memories', 'goals', 'computer_use', + 'browser_use', 'browser_use_external', 'image_generation', 'workspace_dependencies', + 'skill_search', 'skill_mcp_dependency_install', 'tool_suggest', 'code_mode_host', + 'sleep_tool', 'view_image', 'default_mode_request_user_input'] + .map(feature => `features.${feature}=false`), + 'features.skip_host_skill_discovery=true', 'skills.bundled.enabled=false', + 'skills.include_instructions=false', 'project_doc_max_bytes=0', + 'tools.experimental_request_user_input.enabled=false', 'web_search="disabled"', + 'history.persistence="none"', + ].flatMap(value => ['-c', value]) : []; return { command, - args: ['app-server'], + args: ['app-server', ...restrictions], prompt: args.prompt, sessionId: args.sessionId, model: args.model, + ...(args.isolated ? { isolated: true } : {}), }; } async function callClaude(prompt, sessionId, useFallback = false, cwd) { @@ -421,6 +439,9 @@ async function assertSupportedCodexVersion(command) { } const output = await readCommandOutput(command.command, command.versionArgs || ['--version']); const version = parseCodexCliVersion(output); + if (command.isolated && (!version || !versionMeetsMinimum(version, '0.154.0'))) { + throw new Error('Isolated record-policy summarization requires codex-cli >= 0.154.0'); + } if (!version || !versionMeetsMinimum(version)) { throw new Error(codexVersionRequirementMessage(output)); } @@ -440,10 +461,16 @@ function requireTurnId(result, method) { return turnId; } export async function runCodexCommand(command) { + if (command.isolated && command.sessionId) + throw new Error('An isolated summary cannot resume a source session'); await assertSupportedCodexVersion(command); + const summaryCwd = command.isolated + ? fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'episodic-memory-summary-'))) + : undefined; return new Promise((resolve, reject) => { const child = spawn(command.command, command.args, { env: getApiEnv(), + ...(summaryCwd ? { cwd: summaryCwd } : {}), stdio: ['pipe', 'pipe', 'pipe'] }); let stderr = ''; @@ -451,6 +478,7 @@ export async function runCodexCommand(command) { let nextRequestId = 1; let targetTurnId; let finished = false; + let terminalOutcome; let timeout; const pending = new Map(); const lines = createInterface({ input: child.stdout }); @@ -471,6 +499,12 @@ export async function runCodexCommand(command) { return; finished = true; cleanup(); + if (command.isolated) { + // Wait for process termination before removing its owned directory. + // Provider payloads may repeat input; never persist them in error sidecars. + terminalOutcome = { error: error ? new Error('Isolated Codex summary failed; provider details suppressed') : undefined, result }; + return; + } if (error) { reject(error); } @@ -544,6 +578,23 @@ export async function runCodexCommand(command) { finish(new Error(detail)); } }); + child.on('close', () => { + if (!command.isolated) + return; + try { + if (summaryCwd) + fs.rmSync(summaryCwd, { recursive: true, force: true }); + if (terminalOutcome?.error) + reject(terminalOutcome.error); + else if (terminalOutcome) + resolve(terminalOutcome.result); + else + reject(new Error('Isolated Codex summary ended without a terminal result')); + } + catch { + reject(new Error('Isolated Codex summary cleanup failed')); + } + }); (async () => { try { await send('initialize', { @@ -557,15 +608,45 @@ export async function runCodexCommand(command) { }, }); notify('initialized'); - const fork = await send('thread/fork', { - threadId: command.sessionId, + let isolation = {}; + if (command.isolated) { + const configured = await send('config/read', { includeLayers: false }); + const servers = configured?.config?.mcp_servers; + if (!configured?.config || servers !== undefined && (!servers || typeof servers !== 'object' || Array.isArray(servers))) { + throw new Error('Cannot establish configured MCP boundary'); + } + const listed = await send('skills/list', { cwds: [summaryCwd], forceReload: true }); + if (!Array.isArray(listed?.data) || listed.data.some((entry) => !Array.isArray(entry.skills) || !Array.isArray(entry.errors) || entry.errors.length)) { + throw new Error('Cannot establish configured skill boundary'); + } + const skills = listed.data.flatMap((entry) => entry.skills).map((skill) => { + if (typeof skill.path !== 'string' || !path.isAbsolute(skill.path)) + throw new Error('Invalid configured skill identity'); + return { path: skill.path, enabled: false }; + }); + isolation = { + cwd: summaryCwd, + dynamicTools: [], environments: [], selectedCapabilityRoots: [], + allowProviderModelFallback: false, + baseInstructions: 'Summarize only the admitted transcript. Do not use tools or recover external context.', + developerInstructions: 'Output only a concise factual summary inside .', + config: { + mcp_servers: Object.fromEntries(Object.keys(servers ?? {}).map(name => [name, { enabled: false }])), + 'skills.config': skills, + }, + }; + } + const method = command.sessionId ? 'thread/fork' : 'thread/start'; + const fork = await send(method, { + ...(command.sessionId ? { threadId: command.sessionId } : {}), ephemeral: true, excludeTurns: true, sandbox: 'read-only', approvalPolicy: 'never', ...(command.model ? { model: command.model } : {}), + ...isolation, }); - const forkThreadId = requireThreadId(fork, 'thread/fork'); + const forkThreadId = requireThreadId(fork, method); const turn = await send('turn/start', { threadId: forkThreadId, input: [{ @@ -582,8 +663,8 @@ export async function runCodexCommand(command) { })(); }); } -async function callCodex(prompt, sessionId, model) { - const command = buildCodexSummarizerCommand({ sessionId, prompt, model }); +async function callCodex(prompt, sessionId, model, isolated = false) { + const command = buildCodexSummarizerCommand({ sessionId, prompt, model, isolated }); return runCodexCommand(command); } function chunkExchanges(exchanges, chunkSize) { @@ -597,7 +678,7 @@ function getCodexSessionId(exchanges, sessionId) { if (!exchanges.some(exchange => exchange.harness === 'codex')) { return undefined; } - return sessionId || exchanges.find(exchange => exchange.sessionId)?.sessionId; + return sessionId || exchanges.find(exchange => exchange.harness === 'codex' && exchange.sessionId)?.sessionId; } /** * Resolve the model to pass into Codex `thread/fork` for summarization. @@ -618,6 +699,10 @@ export function getCodexModel(_exchanges) { return process.env.EPISODIC_MEMORY_CODEX_MODEL || undefined; } export async function summarizeConversation(exchanges, sessionId) { + const recordPolicy = readRecordExclusions(); + exchanges = exchanges.map(exchange => admitExchange(exchange, recordPolicy)).filter((exchange) => exchange !== null); + if (recordPolicy) + sessionId = undefined; // Handle trivial conversations if (exchanges.length === 0) { return 'Trivial conversation with no substantive content.'; @@ -629,14 +714,15 @@ export async function summarizeConversation(exchanges, sessionId) { } } const codexSessionId = getCodexSessionId(exchanges, sessionId); - if (codexSessionId) { - try { - const result = await callCodex(buildCodexSummaryPrompt(), codexSessionId, getCodexModel(exchanges)); - return extractSummary(result); - } - catch (error) { - console.log(` Codex summarizer unavailable, falling back to transcript text: ${error instanceof Error ? error.message : String(error)}`); - } + if (exchanges.some(exchange => exchange.harness === 'codex')) { + // A sessionless or policy-filtered transcript has no source context to fork. + // Summarize its admitted text in a fresh, isolated Codex thread instead. + const isolated = recordPolicy !== null || !codexSessionId; + const prompt = isolated + ? `${SUMMARIZER_CONTEXT_MARKER}. Summarize only the following admitted transcript in 2-4 factual sentences inside . Do not inspect files, use tools or recover any original session.\n\n${formatConversationText(exchanges)}` + : buildCodexSummaryPrompt(); + const result = await callCodex(prompt, isolated ? undefined : codexSessionId, getCodexModel(exchanges), isolated); + return extractSummary(result); } // Cost safety (#104): everything below this point calls the metered Claude API // path. If a stray global ANTHROPIC_API_KEY would be billed and the user hasn't diff --git a/dist/sync-cli.js b/dist/sync-cli.js index 26172c70..c4709fb9 100644 --- a/dist/sync-cli.js +++ b/dist/sync-cli.js @@ -203,7 +203,13 @@ async function syncAll() { totals.summarized += result.summarized; totals.errors.push(...result.errors); } - console.log(`\nāœ… Sync complete!`); + // Admission failure is a partial run, not successful synchronization. + // Finish migration before emitting a success claim as well. + if (totals.errors.length === 0) + await runEmbeddingMigrationPhase(); + else + process.exitCode = 1; + console.log(totals.errors.length ? '\nSync finished with errors.' : '\nāœ… Sync complete!'); console.log(` Copied: ${totals.copied}`); console.log(` Skipped: ${totals.skipped}`); console.log(` Indexed: ${totals.indexed}`); @@ -223,10 +229,6 @@ async function syncAll() { console.log(` Check your API configuration (EPISODIC_MEMORY_API_BASE_URL / ANTHROPIC_API_KEY).`); } } - // After regular sync, do a batch of embedding migration if any rows are - // still on the old encoder. Lock-protected; if another process is already - // migrating, this is a no-op. - await runEmbeddingMigrationPhase(); } const MIGRATION_BATCH_SIZE = parseInt(process.env.EPISODIC_MEMORY_MIGRATION_BATCH || '500', 10); async function runEmbeddingMigrationPhase() { @@ -246,6 +248,7 @@ async function runEmbeddingMigrationPhase() { } catch (err) { console.error('episodic-memory: migration phase error:', err instanceof Error ? err.message : err); + throw err; } finally { db.close(); diff --git a/dist/sync.d.ts b/dist/sync.d.ts index 658e98ad..529aa0f0 100644 --- a/dist/sync.d.ts +++ b/dist/sync.d.ts @@ -1,13 +1,4 @@ -/** - * Stream and scan for any exclusion marker, carrying an overlap between - * chunks so a marker split across a boundary is still found. A single - * fs.readFileSync(path, 'utf-8') throws ERR_STRING_TOO_LONG above Node's - * ~512 MB max string length; the old catch returned false (fail OPEN), - * silently indexing a file whose DO NOT INDEX marker we never read (#152). - * Streaming confirms cleanliness at any size, and a real read error now - * fails CLOSED (skip) rather than open. - */ -export declare function shouldSkipConversation(filePath: string): boolean; +export { shouldSkipConversation } from './record-admission.js'; export interface SyncResult { copied: number; skipped: number; diff --git a/dist/sync.js b/dist/sync.js index 0629761d..e3bfea2b 100644 --- a/dist/sync.js +++ b/dist/sync.js @@ -1,56 +1,11 @@ import fs from 'fs'; +import { archiveAdmittedConversation, readRecordExclusions } from './record-admission.js'; import path from 'path'; -import { StringDecoder } from 'string_decoder'; -import { SUMMARIZER_CONTEXT_MARKER } from './constants.js'; import { getExcludedProjects, findJsonlFiles, statIfExists } from './paths.js'; import { formatErrorSentinel, shouldQueueForSummary } from './summary-sentinel.js'; import { getMaxMessageBytes, isOversizeExchange } from './message-size.js'; -const EXCLUSION_MARKERS = [ - 'DO NOT INDEX THIS CHAT', - 'Only use NO_INSIGHTS_FOUND', - SUMMARIZER_CONTEXT_MARKER, -]; -const MARKER_SCAN_CHUNK_BYTES = 1 << 20; // 1 MiB -/** - * Stream and scan for any exclusion marker, carrying an overlap between - * chunks so a marker split across a boundary is still found. A single - * fs.readFileSync(path, 'utf-8') throws ERR_STRING_TOO_LONG above Node's - * ~512 MB max string length; the old catch returned false (fail OPEN), - * silently indexing a file whose DO NOT INDEX marker we never read (#152). - * Streaming confirms cleanliness at any size, and a real read error now - * fails CLOSED (skip) rather than open. - */ -export function shouldSkipConversation(filePath) { - const maxMarkerLen = Math.max(...EXCLUSION_MARKERS.map(m => m.length)); - let fd; - try { - fd = fs.openSync(filePath, 'r'); - const buf = Buffer.allocUnsafe(MARKER_SCAN_CHUNK_BYTES); - const decoder = new StringDecoder('utf8'); - let carry = ''; - let bytesRead; - while ((bytesRead = fs.readSync(fd, buf, 0, buf.length, null)) > 0) { - const window = carry + decoder.write(buf.subarray(0, bytesRead)); - if (EXCLUSION_MARKERS.some(marker => window.includes(marker))) { - return true; - } - carry = window.slice(Math.max(0, window.length - (maxMarkerLen - 1))); - } - const tail = carry + decoder.end(); - return EXCLUSION_MARKERS.some(marker => tail.includes(marker)); - } - catch { - return true; // fail closed (#152) - } - finally { - if (fd !== undefined) { - try { - fs.closeSync(fd); - } - catch { } - } - } -} +export { shouldSkipConversation } from './record-admission.js'; +import { shouldSkipConversation } from './record-admission.js'; /** * True when a transcript contains at least one message line in any supported * harness format. Summarizer-spawned Agent SDK sessions materialize as @@ -122,33 +77,6 @@ function hasConversationContent(filePath) { export function buildSyncOptionsFromEnv(env) { return { skipSummaries: env.EPISODIC_MEMORY_SKIP_SUMMARIES === '1' }; } -function copyIfNewer(src, dest) { - // Ensure destination directory exists - const destDir = path.dirname(dest); - if (!fs.existsSync(destDir)) { - fs.mkdirSync(destDir, { recursive: true }); - } - // Check if destination exists and is up-to-date - if (fs.existsSync(dest)) { - const srcStat = fs.statSync(src); - const destStat = fs.statSync(dest); - if (destStat.mtimeMs >= srcStat.mtimeMs) { - return false; // Dest is current, skip - } - } - // Atomic copy: temp file + rename - const tempDest = dest + '.tmp.' + process.pid; - fs.copyFileSync(src, tempDest); - fs.renameSync(tempDest, dest); // Atomic on same filesystem - // Preserve source mtime: harnesses without per-message timestamps (Cursor - // agent transcripts) fall back to file mtime. Round up to the next whole - // millisecond — utimes can't always represent the source's sub-millisecond - // precision, and a dest mtime even fractionally older would defeat the - // skip-if-current check above on every subsequent sync. - const srcStat = fs.statSync(src); - fs.utimesSync(dest, srcStat.atimeMs / 1000, Math.ceil(srcStat.mtimeMs) / 1000); - return true; -} export function extractSessionIdFromPath(filePath) { // Extract session ID from Claude filename or Codex rollout filename. const basename = path.basename(filePath, '.jsonl'); @@ -160,6 +88,7 @@ export function extractSessionIdFromPath(filePath) { return null; } export async function syncConversations(sourceDir, destDir, options = {}) { + const exclusions = readRecordExclusions(); const result = { copied: 0, skipped: 0, @@ -198,7 +127,7 @@ export async function syncConversations(sourceDir, destDir, options = {}) { result.skipped++; continue; } - const wasCopied = copyIfNewer(srcFile, destFile); + const wasCopied = await archiveAdmittedConversation(srcFile, destFile, exclusions); if (wasCopied) { result.copied++; filesToIndex.push(destFile); @@ -212,10 +141,10 @@ export async function syncConversations(sourceDir, destDir, options = {}) { if (!options.skipSummaries) { const summaryPath = destFile.replace('.jsonl', '-summary.txt'); if (shouldQueueForSummary(summaryPath) && !shouldSkipConversation(destFile)) { - // sessionId enables Claude session-resume summarization; when the - // filename has no UUID to extract (e.g. subagent transcripts named - // agent-.jsonl), queue anyway — summarizeConversation falls - // back to summarizing from the transcript text. + // sessionId enables source-session summarization. When the filename + // has no UUID (e.g. agent-.jsonl), queue anyway: Codex uses an + // isolated transcript-only thread; other harnesses use their own + // transcript-text route without resuming a source session. const sessionId = extractSessionIdFromPath(destFile) ?? undefined; filesToSummarize.push({ path: destFile, sessionId }); } diff --git a/dist/verify.d.ts b/dist/verify.d.ts index 9558c879..6e543c60 100644 --- a/dist/verify.d.ts +++ b/dist/verify.d.ts @@ -12,6 +12,12 @@ export interface VerificationResult { fileTime: number; dbTime: number; }>; + screeningRejected: Array<{ + path: string; + }>; + screeningUnavailable: Array<{ + path: string; + }>; corrupted: Array<{ path: string; error: string; diff --git a/dist/verify.js b/dist/verify.js index dd4535d2..5d7e8396 100644 --- a/dist/verify.js +++ b/dist/verify.js @@ -4,11 +4,15 @@ import { parseConversation } from './parser.js'; import { initDatabase, getAllExchanges, getFileLastIndexed } from './db.js'; import { getArchiveDir, getExcludedProjects, findJsonlFiles, statIfExists } from './paths.js'; import { isErroredSentinel } from './summary-sentinel.js'; +import { readRecordExclusions } from './record-admission.js'; export async function verifyIndex() { + readRecordExclusions(); const result = { missing: [], orphaned: [], outdated: [], + screeningRejected: [], + screeningUnavailable: [], corrupted: [] }; const archiveDir = getArchiveDir(); @@ -47,11 +51,9 @@ export async function verifyIndex() { // re-attempts it rather than reporting the conversation as healthy. if (!fs.existsSync(summaryPath)) { result.missing.push({ path: conversationPath, reason: 'No summary file' }); - continue; } - if (isErroredSentinel(fs.readFileSync(summaryPath, 'utf-8'))) { + else if (isErroredSentinel(fs.readFileSync(summaryPath, 'utf-8'))) { result.missing.push({ path: conversationPath, reason: 'Previous summarization failed (error sentinel)' }); - continue; } // Check if file is outdated (modified after last_indexed) const lastIndexed = getFileLastIndexed(db, conversationPath); @@ -70,10 +72,18 @@ export async function verifyIndex() { await parseConversation(conversationPath, project, conversationPath); } catch (error) { - result.corrupted.push({ - path: conversationPath, - error: error instanceof Error ? error.message : String(error) - }); + const message = error instanceof Error ? error.message : String(error); + if (message === 'Record content rejected by screening' || + message === 'Record screening byte limit exceeded' || + message === 'Record screening nesting limit exceeded') { + result.screeningRejected.push({ path: conversationPath }); + } + else if (message === 'Record screening unavailable') { + result.screeningUnavailable.push({ path: conversationPath }); + } + else { + result.corrupted.push({ path: conversationPath, error: message }); + } } } } @@ -92,6 +102,10 @@ export async function verifyIndex() { return result; } export async function repairIndex(issues) { + if (issues.screeningRejected.length + issues.screeningUnavailable.length > 0) { + throw new Error('Resolve record screening failures before repair'); + } + readRecordExclusions(); console.log('Repairing index...'); // To avoid circular dependencies, we import the indexer functions dynamically const { initDatabase, insertExchange, deleteExchange } = await import('./db.js'); @@ -138,6 +152,8 @@ export async function repairIndex(issues) { } catch (error) { console.error(`Failed to re-index ${conversationPath}:`, error); + db.close(); + throw error; } } db.close(); diff --git a/docs/CODEX.md b/docs/CODEX.md index c3d83f97..b0a2d513 100644 --- a/docs/CODEX.md +++ b/docs/CODEX.md @@ -90,6 +90,6 @@ The test creates an isolated temporary `CODEX_HOME`, copies your existing Codex ## Summaries -Codex summaries use `codex app-server`, `thread/fork`, and `ephemeral: true`. This matters: `codex exec --ephemeral resume ` was tested and still appended to the resumed rollout, so it is not the quality bar for summarization. +Codex summaries with a session ID use `codex app-server`, `thread/fork`, and `ephemeral: true`. This matters: `codex exec --ephemeral resume ` was tested and still appended to the resumed rollout, so it is not the quality bar for summarization. Without a session ID, an isolated ephemeral `thread/start` receives only the admitted transcript text and requires Codex `0.154.0` or newer. -If the Codex app-server summarizer is unavailable or below the support floor, Episodic Memory logs the reason and falls back to transcript-text summarization instead of silently skipping the conversation. +If the Codex app-server summarizer is unavailable or below the applicable support floor, Episodic Memory records a retryable summary failure. It does not send Codex transcripts to Claude. diff --git a/src/db.ts b/src/db.ts index 8c7a73ad..1a76cc8c 100644 --- a/src/db.ts +++ b/src/db.ts @@ -1,4 +1,5 @@ import Database from 'better-sqlite3'; +import { admitExchange } from './record-admission.js'; import { ConversationExchange } from './types.js'; import path from 'path'; import fs from 'fs'; @@ -216,6 +217,9 @@ export function insertExchange( embedding: number[], toolNames?: string[] ): void { + const admitted = admitExchange(exchange); + if (!admitted) return; + exchange = admitted; const now = Date.now(); const stmt = db.prepare(` diff --git a/src/index-cli.ts b/src/index-cli.ts index 272ecdba..7b724acf 100644 --- a/src/index-cli.ts +++ b/src/index-cli.ts @@ -73,6 +73,8 @@ async function main() { console.log(`Missing summaries: ${issues.missing.length}`); console.log(`Orphaned entries: ${issues.orphaned.length}`); console.log(`Outdated files: ${issues.outdated.length}`); + console.log(`Screening-rejected archives: ${issues.screeningRejected.length}`); + console.log(`Screening-unavailable archives: ${issues.screeningUnavailable.length}`); console.log(`Corrupted files: ${issues.corrupted.length}`); if (issues.missing.length > 0) { @@ -80,8 +82,9 @@ async function main() { issues.missing.forEach(m => console.log(` ${m.path}`)); } - if (issues.missing.length + issues.orphaned.length + issues.outdated.length + issues.corrupted.length > 0) { - console.log('\nRun with --repair to fix these issues.'); + if (issues.missing.length + issues.orphaned.length + issues.outdated.length + + issues.screeningRejected.length + issues.screeningUnavailable.length + issues.corrupted.length > 0) { + console.log('\nReview the reported categories before repair; --repair may regenerate embeddings and AI summaries.'); process.exit(1); } else { console.log('\nāœ… Index is healthy!'); @@ -92,6 +95,11 @@ async function main() { console.log('Verifying conversation index...'); const repairIssues = await verifyIndex(); + if (repairIssues.screeningRejected.length + repairIssues.screeningUnavailable.length > 0) { + console.error('Record screening failures require review; repair was not started.'); + process.exitCode = 1; + break; + } if (repairIssues.missing.length + repairIssues.orphaned.length + repairIssues.outdated.length > 0) { await repairIndex(repairIssues); } else { diff --git a/src/indexer.ts b/src/indexer.ts index 173ef679..f6e67ae2 100644 --- a/src/indexer.ts +++ b/src/indexer.ts @@ -1,4 +1,5 @@ import fs from 'fs'; +import { archiveAdmittedConversation, readRecordExclusions } from './record-admission.js'; import path from 'path'; import os from 'os'; import { initDatabase, insertExchange } from './db.js'; @@ -44,6 +45,7 @@ export async function indexConversations( concurrency: number = 1, noSummaries: boolean = false ): Promise { + readRecordExclusions(); console.log('Initializing database...'); const db = initDatabase(); @@ -114,15 +116,12 @@ export async function indexConversations( let exchanges; try { // Copy to archive (ensure parent dirs exist for subagent files) - if (!fs.existsSync(archivePath)) { - fs.mkdirSync(path.dirname(archivePath), { recursive: true }); - fs.copyFileSync(sourcePath, archivePath); - console.log(` Archived: ${file}`); - } + if (await archiveAdmittedConversation(sourcePath, archivePath)) console.log(` Archived: ${file}`); // Parse conversation exchanges = await parseConversation(sourcePath, project, archivePath); } catch (error) { + if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { db.close(); throw error; } console.log(` Skipped ${file} (read failed: ${error instanceof Error ? error.message : error})`); continue; } @@ -210,6 +209,7 @@ export async function indexConversations( } export async function indexSession(sessionId: string, concurrency: number = 1, noSummaries: boolean = false): Promise { + readRecordExclusions(); console.log(`Indexing session: ${sessionId}`); // Find the conversation file for this session @@ -246,14 +246,12 @@ export async function indexSession(sessionId: string, concurrency: number = 1, n // Archive + parse — source may vanish mid-run (Claude Code cleanup). let exchanges; try { - if (!fs.existsSync(archivePath)) { - fs.mkdirSync(path.dirname(archivePath), { recursive: true }); - fs.copyFileSync(sourcePath, archivePath); - } + await archiveAdmittedConversation(sourcePath, archivePath); exchanges = await parseConversation(sourcePath, project, archivePath); } catch (error) { - console.log(`Skipped ${file} (read failed: ${error instanceof Error ? error.message : error})`); db.close(); + if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error; + console.log(`Skipped ${file} (read failed: ${error instanceof Error ? error.message : error})`); break; } @@ -313,6 +311,7 @@ export async function indexSession(sessionId: string, concurrency: number = 1, n } export async function indexUnprocessed(concurrency: number = 1, noSummaries: boolean = false): Promise { + readRecordExclusions(); console.log('Finding unprocessed conversations...'); if (concurrency > 1) console.log(`Concurrency: ${concurrency}`); if (noSummaries) console.log('āš ļø Running in no-summaries mode (skipping AI summaries)'); @@ -366,9 +365,7 @@ export async function indexUnprocessed(concurrency: number = 1, noSummaries: boo fs.mkdirSync(path.dirname(archivePath), { recursive: true }); // Refresh the archive when the source may have grown beyond what we've seen. - if (!fs.existsSync(archivePath) || maxIndexedLine > 0) { - fs.copyFileSync(sourcePath, archivePath); - } + await archiveAdmittedConversation(sourcePath, archivePath); // Parse and filter to exchanges past the high-water mark const exchanges = await parseConversation(sourcePath, project, archivePath); @@ -379,6 +376,7 @@ export async function indexUnprocessed(concurrency: number = 1, noSummaries: boo unprocessed.push({ project, file, sourcePath, archivePath, summaryPath, exchanges: newExchanges }); } catch (error) { + if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { db.close(); throw error; } console.log(` Skipped ${file} (read failed: ${error instanceof Error ? error.message : error})`); continue; } diff --git a/src/mcp-server.ts b/src/mcp-server.ts index c1cbd0e3..1dce206c 100644 --- a/src/mcp-server.ts +++ b/src/mcp-server.ts @@ -23,6 +23,7 @@ import { import { formatConversationAsMarkdown } from './show.js'; import { VERSION } from './version.js'; import fs from 'fs'; +import { readAdmittedConversation } from './record-admission.js'; // Zod Schemas for Input Validation @@ -291,7 +292,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { } // Read and format conversation with optional line range - const jsonlContent = fs.readFileSync(params.path, 'utf-8'); + const jsonlContent = await readAdmittedConversation(params.path, params.startLine, params.endLine); const markdownContent = formatConversationAsMarkdown( jsonlContent, params.startLine, diff --git a/src/parser.ts b/src/parser.ts index a416a113..9cef9f17 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -1,8 +1,8 @@ import fs from 'fs'; -import readline from 'readline'; import path from 'path'; import { ConversationExchange, ConversationHarness, ToolCall } from './types.js'; import crypto from 'crypto'; +import { admittedConversationLines } from './record-admission.js'; interface JSONLMessage { type: string; @@ -91,11 +91,7 @@ interface ExchangeBuilder { } async function detectConversationHarness(filePath: string): Promise { - const fileStream = fs.createReadStream(filePath); - const rl = readline.createInterface({ - input: fileStream, - crlfDelay: Infinity - }); + const rl = admittedConversationLines(filePath); for await (const line of rl) { if (!line.trim()) continue; @@ -175,11 +171,7 @@ async function parseClaudeConversation( archivePath: string ): Promise { const exchanges: ConversationExchange[] = []; - const fileStream = fs.createReadStream(filePath); - const rl = readline.createInterface({ - input: fileStream, - crlfDelay: Infinity - }); + const rl = admittedConversationLines(filePath); let lineNumber = 0; let currentExchange: ExchangeBuilder | null = null; @@ -479,11 +471,7 @@ async function parseOpencodeConversation( archivePath: string ): Promise { const exchanges: ConversationExchange[] = []; - const fileStream = fs.createReadStream(filePath); - const rl = readline.createInterface({ - input: fileStream, - crlfDelay: Infinity - }); + const rl = admittedConversationLines(filePath); let lineNumber = 0; let sessionId: string | undefined; @@ -687,11 +675,7 @@ async function parseOmpConversation( archivePath: string ): Promise { const exchanges: ConversationExchange[] = []; - const fileStream = fs.createReadStream(filePath); - const rl = readline.createInterface({ - input: fileStream, - crlfDelay: Infinity - }); + const rl = admittedConversationLines(filePath); let sessionId: string | undefined; let cwd: string | undefined; @@ -811,11 +795,7 @@ async function parseCodexConversation( archivePath: string ): Promise { const exchanges: ConversationExchange[] = []; - const fileStream = fs.createReadStream(filePath); - const rl = readline.createInterface({ - input: fileStream, - crlfDelay: Infinity - }); + const rl = admittedConversationLines(filePath); let lineNumber = 0; let sessionId: string | undefined; @@ -955,7 +935,8 @@ async function parseCodexConversation( const timestamp = parsed.timestamp || new Date().toISOString(); if (parsed.type === 'session_meta' && payload) { - sessionId = payload.id || sessionId; + // Forked transcripts may retain ancestor headers after the primary one. + sessionId = sessionId || payload.id; cwd = payload.cwd || cwd; gitBranch = payload.git?.branch || gitBranch; agentVersion = payload.cli_version || agentVersion; @@ -1092,11 +1073,7 @@ async function parseCursorConversation( archivePath: string ): Promise { const exchanges: ConversationExchange[] = []; - const fileStream = fs.createReadStream(filePath); - const rl = readline.createInterface({ - input: fileStream, - crlfDelay: Infinity - }); + const rl = admittedConversationLines(filePath); // Live Cursor transcripts carry no per-message timestamps; fall back to the // file mtime (preserved from the source by sync's copyIfNewer). Legacy diff --git a/src/record-admission.ts b/src/record-admission.ts new file mode 100644 index 00000000..3254fd10 --- /dev/null +++ b/src/record-admission.ts @@ -0,0 +1,562 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import crypto from 'node:crypto'; +import os from 'node:os'; +import { spawnSync } from 'node:child_process'; +import { SUMMARIZER_CONTEXT_MARKER } from './constants.js'; +import type { ConversationExchange } from './types.js'; +import { getSuperpowersDir } from './paths.js'; + +const EXCLUSION_MARKERS = [ + 'DO NOT INDEX THIS CHAT', + 'Only use NO_INSIGHTS_FOUND', + SUMMARIZER_CONTEXT_MARKER, +]; + +const MARKER_SCAN_CHUNK_BYTES = 1 << 20; // 1 MiB + +/** Scan decoded instruction records without retaining the whole transcript. */ +export function shouldSkipConversation(filePath: string): boolean { + let fd: number | undefined; + try { + fd = fs.openSync(filePath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW); + const initial = fs.fstatSync(fd); + const excluded = descriptorHasExclusionMarker(fd); + assertUnchangedInput(filePath, initial); + return excluded; + } catch { + return true; + } finally { + if (fd !== undefined) fs.closeSync(fd); + } +} + +function instructionText(record: unknown): string { + if (!object(record)) return ''; + // Codex records both the response item and its user-message event. + if (record.type === 'event_msg' && object(record.payload)) { + return record.payload.type === 'user_message' && typeof record.payload.message === 'string' + ? record.payload.message : ''; + } + const message = record.type === 'response_item' ? record.payload : record.message; + if (!object(message) || (message.role ?? record.role) !== 'user') return ''; + const content = record.type === 'opencode_message' ? record.parts : message.content; + if (typeof content === 'string') return content; + if (!Array.isArray(content)) return ''; + // Claude tool results are wrapped in a user envelope, but are not instructions. + return content.filter(part => object(part) && ['text', 'input_text'].includes(String(part.type)) + && typeof part.text === 'string').map(part => part.text).join('\n'); +} + +function descriptorHasExclusionMarker(fd: number, byteLimit = Infinity, digest?: crypto.Hash): boolean { + if (!fs.fstatSync(fd).isFile()) throw new Error('Conversation input must be a regular file'); + const buffer = Buffer.allocUnsafe(MARKER_SCAN_CHUNK_BYTES); + const decoder = new TextDecoder('utf-8', { fatal: true }); + let pending = Buffer.alloc(0); + let position = 0; + const excluded = (bytes: Buffer) => { + const line = decoder.decode(bytes); + let text: string; + try { text = instructionText(JSON.parse(line)); } + catch { text = line; } // Preserve conservative handling of legacy raw transcripts. + return EXCLUSION_MARKERS.some(marker => text.includes(marker)); + }; + let count: number; + while (position < byteLimit && (count = fs.readSync(fd, buffer, 0, Math.min(buffer.length, byteLimit - position), position)) > 0) { + position += count; + digest?.update(buffer.subarray(0, count)); + pending = Buffer.concat([pending, buffer.subarray(0, count)]); + let newline: number; + while ((newline = pending.indexOf(10)) !== -1) { + if (newline > 64 * 1024 * 1024) throw new Error('Conversation record exceeds admission byte limit'); + if (excluded(pending.subarray(0, newline))) return true; + pending = pending.subarray(newline + 1); + } + if (pending.length > 64 * 1024 * 1024) throw new Error('Conversation record exceeds admission byte limit'); + } + if (position < byteLimit && byteLimit !== Infinity) throw new Error('Conversation changed during record admission'); + return pending.length > 0 && excluded(pending); +} + +function assertUnchangedInput(file: string, initial: fs.Stats): void { + const current = fs.lstatSync(file); + if (!current.isFile() || initial.dev !== current.dev || initial.ino !== current.ino || + initial.size !== current.size || initial.mtimeMs !== current.mtimeMs || initial.ctimeMs !== current.ctimeMs) { + throw new Error('Conversation changed during record admission'); + } +} + +function assertUnchangedSearchPrefix(file: string, initial: fs.Stats, expectedDigest: string): void { + // A live transcript may grow after the search snapshot. Rehash the pinned + // prefix only when metadata changes, so appended bytes cannot hide a rewrite. + const changed = () => new Error('Conversation changed during record admission'); + let current: fs.Stats; + try { current = fs.lstatSync(file); } catch { throw changed(); } + if (!current.isFile() || initial.dev !== current.dev || initial.ino !== current.ino || current.size < initial.size) { + throw changed(); + } + if (current.size === initial.size && current.mtimeMs === initial.mtimeMs && current.ctimeMs === initial.ctimeMs) return; + + let fd: number | undefined; + try { + fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW); + const opened = fs.fstatSync(fd); + if (!opened.isFile() || opened.dev !== initial.dev || opened.ino !== initial.ino || opened.size < initial.size) throw changed(); + const digest = crypto.createHash('sha256'); + const buffer = Buffer.allocUnsafe(MARKER_SCAN_CHUNK_BYTES); + let position = 0; + while (position < initial.size) { + const count = fs.readSync(fd, buffer, 0, Math.min(buffer.length, initial.size - position), position); + if (count <= 0) throw changed(); + digest.update(buffer.subarray(0, count)); + position += count; + } + if (digest.digest('hex') !== expectedDigest) throw changed(); + current = fs.lstatSync(file); + if (!current.isFile() || current.dev !== initial.dev || current.ino !== initial.ino || current.size < initial.size) throw changed(); + } catch { throw changed(); } + finally { if (fd !== undefined) fs.closeSync(fd); } +} + +function assertUnchangedPolicy(expected: string): void { + if (JSON.stringify(readRecordExclusions()) !== expected) { + throw new Error('Record exclusion policy changed during record admission'); + } +} + +function prepareArchiveParent(directory: string): string { + try { + const info = fs.lstatSync(directory); + if (!info.isDirectory() || info.isSymbolicLink()) throw new Error('Archive parent must be a non-symlink directory'); + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error; + const parent = path.dirname(directory); + if (parent === directory) throw error; + prepareArchiveParent(parent); + fs.mkdirSync(directory, { mode: 0o700 }); + } + return fs.realpathSync(directory); +} + +interface ExchangeExclusion { + session_id: string; + transcript: string; + line_start: number; + line_end: number; +} + +interface ToolExclusion { + session_id: string; + transcript: string; + call_id: string; +} + +export interface RecordExclusions { + version: 1; + exchanges: ExchangeExclusion[]; + tool_calls: ToolExclusion[]; + screening?: { + engine: 'gitleaks'; + executable: string; + sha256: string; + timeout_ms: number; + max_record_bytes: number; + }; +} + +const POLICY_ERROR = 'Invalid or unreadable record exclusion policy'; +const UUID = /[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}/ig; + +function object(value: unknown): value is Record { + return value !== null && typeof value === 'object' && !Array.isArray(value); +} + +function keys(value: Record, expected: string[]): boolean { + return Object.keys(value).length === expected.length && expected.every(key => key in value); +} + +function transcriptName(value: unknown): value is string { + return identity(value) && !/[\\/]/.test(value) && value.endsWith('.jsonl') && value !== '.jsonl'; +} + +function identity(value: unknown): value is string { + return typeof value === 'string' && value.length > 0 && value.length <= 512 && !/[\x00-\x1f]/.test(value); +} + +/** Keep scanner inputs local to one record, including JSON-escaped text. */ +function screeningText(value: unknown, depth = 0): string { + if (depth > 24) throw new Error('Record screening nesting limit exceeded'); + if (typeof value === 'string') { + let decoded: unknown; + try { decoded = JSON.parse(value); } catch { return value; } + if (typeof decoded === 'string' || object(decoded) || Array.isArray(decoded)) { + return screeningText(decoded, depth + 1); + } + return value; + } + if (Array.isArray(value)) return value.map(item => screeningText(item, depth + 1)).join('\n'); + if (object(value)) { + // Generic credential rules need an assignment delimiter, not just a nearby + // keyword. Decode nested text without destroying its key/value relation. + return Object.entries(value).map(([key, item]) => `${key} = ${screeningText(item, depth + 1)}`).join('\n'); + } + return value == null ? '' : String(value); +} + +/** One pinned, short-lived local scanner context; no secret-bearing cache. */ +function createRecordScreener(policy: RecordExclusions | null): { scan(value: unknown): void; close(): void } { + const config = policy?.screening; + if (!config) return { scan() {}, close() {} }; + const unavailable = () => new Error('Record screening unavailable'); + let initial: fs.Stats; + let fd: number | undefined; + try { + if (fs.realpathSync(config.executable) !== config.executable) throw unavailable(); + fd = fs.openSync(config.executable, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW); + initial = fs.fstatSync(fd); + if (!initial.isFile() || initial.size < 1 || initial.size > 134_217_728 || + (initial.mode & 0o022) !== 0 || + (typeof process.getuid === 'function' && initial.uid !== 0 && initial.uid !== process.getuid())) { + throw unavailable(); + } + fs.accessSync(config.executable, fs.constants.X_OK); + const digest = crypto.createHash('sha256').update(fs.readFileSync(fd)).digest('hex'); + if (digest !== config.sha256) throw unavailable(); + } catch { throw unavailable(); } + finally { if (fd !== undefined) fs.closeSync(fd); } + let directory: string; + try { + directory = fs.mkdtempSync(path.join(os.tmpdir(), 'episodic-screen-')); + fs.chmodSync(directory, 0o700); + } catch { throw unavailable(); } + const unchanged = () => { + try { + const current = fs.lstatSync(config.executable); + if (!current.isFile() || current.dev !== initial.dev || current.ino !== initial.ino || + current.size !== initial.size || current.mtimeMs !== initial.mtimeMs || + current.ctimeMs !== initial.ctimeMs) throw unavailable(); + } catch { throw unavailable(); } + }; + return { + scan(value: unknown) { + const raw = typeof value === 'string' ? value : JSON.stringify(value); + if (raw === undefined) return; + if (Buffer.byteLength(raw) > config.max_record_bytes) throw new Error('Record screening byte limit exceeded'); + const input = screeningText(value); + if (Buffer.byteLength(input) > config.max_record_bytes) throw new Error('Record screening byte limit exceeded'); + if (!input.trim()) return; + unchanged(); + const result = spawnSync(config.executable, [ + 'stdin', '--redact', '--no-banner', '--log-level', 'error', + '--ignore-gitleaks-allow', '--exit-code', '10', '--report-format', 'json', '--report-path', '-', + ], { + input, encoding: 'utf8', cwd: directory, shell: false, + env: { HOME: directory, USERPROFILE: directory, PATH: path.dirname(config.executable) }, + timeout: config.timeout_ms, killSignal: 'SIGKILL', maxBuffer: 1_048_576, + windowsHide: true, + }); + unchanged(); + // Exit zero alone is not a clean scan. Validate the full report and never + // forward stdout/stderr, findings, fingerprints or input into errors. + if (result.error || result.signal || ![0, 10].includes(result.status ?? -1)) throw unavailable(); + let report: unknown; + try { report = JSON.parse(result.stdout); } catch { throw unavailable(); } + if (!Array.isArray(report) || (result.status === 0 && report.length !== 0) || + (result.status === 10 && (report.length === 0 || !report.every(item => object(item) && typeof item.RuleID === 'string')))) { + throw unavailable(); + } + if (result.status === 10) throw new Error('Record content rejected by screening'); + }, + close() { fs.rmSync(directory, { recursive: true, force: true }); }, + }; +} + +export function readRecordExclusions(): RecordExclusions | null { + const file = path.join(getSuperpowersDir(), 'record-exclusions.json'); + let fd: number | undefined; + try { + fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW); + const info = fs.fstatSync(fd); + if (!info.isFile() || info.size > 4 * 1024 * 1024) throw new Error(POLICY_ERROR); + const value: unknown = JSON.parse(fs.readFileSync(fd, 'utf8')); + if (!object(value) || !keys(value, 'screening' in value + ? ['version', 'exchanges', 'tool_calls', 'screening'] : ['version', 'exchanges', 'tool_calls']) || value.version !== 1 || + !Array.isArray(value.exchanges) || !Array.isArray(value.tool_calls)) throw new Error(POLICY_ERROR); + if ('screening' in value) { + const s = value.screening; + if (!object(s) || !keys(s, ['engine', 'executable', 'sha256', 'timeout_ms', 'max_record_bytes']) || + s.engine !== 'gitleaks' || typeof s.executable !== 'string' || !path.isAbsolute(s.executable) || + /[\x00-\x1f]/.test(s.executable) || typeof s.sha256 !== 'string' || !/^[a-f0-9]{64}$/.test(s.sha256) || + !Number.isSafeInteger(s.timeout_ms) || Number(s.timeout_ms) < 50 || Number(s.timeout_ms) > 30_000 || + !Number.isSafeInteger(s.max_record_bytes) || Number(s.max_record_bytes) < 1 || Number(s.max_record_bytes) > 8_388_608) { + throw new Error(POLICY_ERROR); + } + } + for (const row of value.exchanges) { + if (!object(row) || !keys(row, ['session_id', 'transcript', 'line_start', 'line_end']) || !identity(row.session_id) || !transcriptName(row.transcript) || + !Number.isSafeInteger(row.line_start) || !Number.isSafeInteger(row.line_end) || + (row.line_start as number) < 1 || (row.line_end as number) < (row.line_start as number)) throw new Error(POLICY_ERROR); + } + for (const row of value.tool_calls) { + if (!object(row) || !keys(row, ['session_id', 'transcript', 'call_id']) || !identity(row.session_id) || !transcriptName(row.transcript) || !identity(row.call_id)) throw new Error(POLICY_ERROR); + } + return value as unknown as RecordExclusions; + } catch (error) { + if (fd === undefined && (error as NodeJS.ErrnoException).code === 'ENOENT') return null; + throw new Error(POLICY_ERROR); + } finally { + if (fd !== undefined) fs.closeSync(fd); + } +} + +export function excludedSession(policy: RecordExclusions | null, sessionId?: string): boolean { + return !!sessionId && !!policy && (policy.exchanges.some(x => x.session_id === sessionId) || + policy.tool_calls.some(x => x.session_id === sessionId)); +} + +function sessionFromPath(file: string): string | undefined { + return path.basename(file).match(UUID)?.at(-1); +} + +function sessionFromRecord(value: any): string | undefined { + if (value?.type === 'session_meta') return value.payload?.id; + if (value?.type === 'session') return value.id; + if (value?.type === 'opencode_session') return value.session?.id; + return value?.sessionId ?? value?.sessionID ?? value?.message?.sessionID; +} + +function blockedCall(policy: RecordExclusions, sessionId: string, transcript: string, callId: unknown): boolean { + return typeof callId === 'string' && policy.tool_calls.some(x => x.session_id === sessionId && x.transcript === transcript && x.call_id === callId); +} + +function filterRecord(line: string, number: number, policy: RecordExclusions | null, transcript: string, sessionId?: string): string { + if (!policy || !sessionId) return line; + if (policy.exchanges.some(x => x.session_id === sessionId && x.transcript === transcript && number >= x.line_start && number <= x.line_end)) return ''; + let value: any; + try { value = JSON.parse(line); } catch { return line; } + if (blockedCall(policy, sessionId, transcript, value?.payload?.call_id) || blockedCall(policy, sessionId, transcript, value?.call_id)) return ''; + // Claude and Cursor may combine safe text and tool payloads in one physical record. + // Preserve the record and safe blocks rather than excluding the entire exchange. + const content = value?.message?.content; + if (Array.isArray(content)) { + const kept = content.filter(block => !blockedCall(policy, sessionId, transcript, block?.id ?? block?.tool_use_id)); + if (kept.length !== content.length) { + return JSON.stringify({ ...value, message: { ...value.message, content: kept } }); + } + } + if (Array.isArray(value?.parts)) { + const kept = value.parts.filter((part: any) => !blockedCall(policy, sessionId, transcript, part?.callID ?? part?.id)); + if (kept.length !== value.parts.length) return JSON.stringify({ ...value, parts: kept }); + } + return line; +} + +/** Keep byte delimiters and physical line coordinates stable in derived copies. */ +async function* admittedRecords(file: string, policy: RecordExclusions | null, startLine = 1, endLine = Infinity): AsyncGenerator<{ line: string; ending: string }> { + const policySnapshot = JSON.stringify(policy); + assertUnchangedPolicy(policySnapshot); + const input = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW); + const initial = fs.fstatSync(input); + let stream: fs.ReadStream | undefined; + let screener: ReturnType | undefined; + const decoder = new TextDecoder('utf-8', { fatal: true }); + let pending = Buffer.alloc(0); + let sessionId = sessionFromPath(file); + let primaryIdentitySeen = false; + let number = 0; + const admit = (bytes: Buffer, terminated: boolean) => { + number++; + const crlf = terminated && bytes.at(-1) === 13; + const line = decoder.decode(crlf ? bytes.subarray(0, -1) : bytes); + let recordSession: string | undefined; + try { recordSession = sessionFromRecord(JSON.parse(line)); } catch { /* Parser owns malformed records. */ } + if (recordSession && !primaryIdentitySeen) { + if (sessionId && recordSession !== sessionId && (excludedSession(policy, sessionId) || excludedSession(policy, recordSession))) { + throw new Error('Record exclusion session identity mismatch'); + } + sessionId = recordSession; + primaryIdentitySeen = true; + } + const filtered = number >= startLine && number <= endLine + ? filterRecord(line, number, policy, path.basename(file), sessionId) : ''; + screener?.scan(filtered); + return { line: filtered, ending: terminated ? (crlf ? '\r\n' : '\n') : '' }; + }; + try { + // The marker scan and record stream share one descriptor and identity. + // A path replacement cannot substitute a different unscanned transcript. + if (descriptorHasExclusionMarker(input)) return; + assertUnchangedInput(file, initial); + screener = createRecordScreener(policy); + stream = fs.createReadStream(file, { fd: input, autoClose: true, start: 0 }); + for await (const chunk of stream) { + assertUnchangedInput(file, initial); + assertUnchangedPolicy(policySnapshot); + pending = Buffer.concat([pending, chunk]); + let newline: number; + while ((newline = pending.indexOf(10)) !== -1) { + yield admit(pending.subarray(0, newline), true); + pending = pending.subarray(newline + 1); + } + if (pending.length > 64 * 1024 * 1024) throw new Error('Conversation record exceeds admission byte limit'); + } + if (pending.length) yield admit(pending, false); + assertUnchangedInput(file, initial); + assertUnchangedPolicy(policySnapshot); + } finally { + if (stream) stream.destroy(); + else fs.closeSync(input); + screener?.close(); + } +} + +/** Read original inputs without mutation, preserving their physical line numbers. */ +export async function* admittedConversationLines(file: string, policy = readRecordExclusions()): AsyncGenerator { + if (!fs.lstatSync(file).isFile()) throw new Error('Conversation input must be a regular non-symlink file'); + if (shouldSkipConversation(file)) return; + for await (const record of admittedRecords(file, policy)) yield record.line; +} + +/** Read the admitted display input without changing original physical coordinates. */ +export async function readAdmittedConversation(file: string, startLine = 1, endLine = Infinity): Promise { + if (!Number.isSafeInteger(startLine) || startLine < 1 || + (endLine !== Infinity && (!Number.isSafeInteger(endLine) || endLine < startLine))) { + throw new Error('Invalid conversation line range'); + } + const lines: string[] = []; + for await (const record of admittedRecords(file, readRecordExclusions(), startLine, endLine)) lines.push(record.line); + return lines.join('\n'); +} + +/** Enforce the same rule for direct callers that bypass transcript parsing. */ +export function admitExchange(exchange: ConversationExchange, policy = readRecordExclusions()): ConversationExchange | null { + if (!policy) return exchange; + // Historical Codex rows may carry a fork ancestor's ID. Neither that metadata + // nor the filename may override a denial bound to the other known identity. + const sessionIds = [exchange.sessionId, sessionFromPath(exchange.archivePath)].filter((id): id is string => !!id); + const transcript = path.basename(exchange.archivePath); + if (policy.exchanges.some(x => sessionIds.includes(x.session_id) && x.transcript === transcript && exchange.lineStart <= x.line_end && exchange.lineEnd >= x.line_start)) return null; + const kept = exchange.toolCalls?.filter(call => !sessionIds.some(id => blockedCall(policy, id, transcript, call.id))); + const admitted = kept && kept.length !== exchange.toolCalls?.length ? { ...exchange, toolCalls: kept } : exchange; + const screener = createRecordScreener(policy); + try { + // These are separate original records, not one keyword-prefilter fragment. + screener.scan(admitted.userMessage); + screener.scan(admitted.assistantMessage); + for (const call of admitted.toolCalls ?? []) screener.scan(call); + const { userMessage, assistantMessage, toolCalls, ...metadata } = admitted; + screener.scan(metadata); + return admitted; + } finally { screener.close(); } +} + +/** Publish only admitted bytes. Never write an unfiltered temporary copy. */ +export async function archiveAdmittedConversation(source: string, destination: string, policy = readRecordExclusions()): Promise { + const policySnapshot = JSON.stringify(policy); + assertUnchangedPolicy(policySnapshot); + if (shouldSkipConversation(source)) return false; + if (path.resolve(source) === path.resolve(destination)) throw new Error('Source and derived archive must be distinct'); + const original = fs.lstatSync(source); + if (!original.isFile()) throw new Error('Conversation source must be a regular non-symlink file'); + let target: fs.Stats | undefined; + if (fs.existsSync(destination)) { + target = fs.lstatSync(destination); + if (!target.isFile() || target.dev === original.dev && target.ino === original.ino) { + throw new Error('Source and derived archive must be distinct regular files'); + } + } + if (!policy && target && target.mtimeMs >= original.mtimeMs && target.size === original.size) return false; + const initial = fs.statSync(source); + const parent = prepareArchiveParent(path.dirname(path.resolve(destination))); + const parentIdentity = fs.statSync(parent); + destination = path.join(parent, path.basename(destination)); + const temp = destination + '.tmp.' + process.pid + '.' + crypto.randomUUID(); + const fd = fs.openSync(temp, 'wx', 0o600); + const hash = crypto.createHash('sha256'); + try { + for await (const record of admittedRecords(source, policy)) { + const bytes = Buffer.from(record.line + record.ending); + hash.update(bytes); + fs.writeFileSync(fd, bytes); + } + fs.fsyncSync(fd); + } catch (error) { + fs.closeSync(fd); + fs.unlinkSync(temp); + throw error; + } + fs.closeSync(fd); + try { + const final = fs.statSync(source); + if (initial.dev !== final.dev || initial.ino !== final.ino || initial.size !== final.size || initial.mtimeMs !== final.mtimeMs) { + throw new Error('Conversation changed during record admission'); + } + if (fs.existsSync(destination)) { + const existing = crypto.createHash('sha256'); + for await (const chunk of fs.createReadStream(destination)) existing.update(chunk); + if (existing.digest('hex') === hash.digest('hex')) return false; + } + assertUnchangedInput(source, original); + assertUnchangedPolicy(policySnapshot); + const currentParent = fs.lstatSync(parent); + if (!currentParent.isDirectory() || currentParent.dev !== parentIdentity.dev || currentParent.ino !== parentIdentity.ino) { + throw new Error('Archive parent changed during record admission'); + } + fs.renameSync(temp, destination); + fs.utimesSync(destination, initial.atimeMs / 1000, Math.ceil(initial.mtimeMs) / 1000); + return true; + } finally { if (fs.existsSync(temp)) fs.unlinkSync(temp); } +} + +/** Request-local admission: retained search rows are not trusted merely because they were indexed. */ +export function createSearchAdmission() { + const policy = readRecordExclusions(); + const policySnapshot = JSON.stringify(policy); + const files = new Map(); + const checkFile = (file: string) => { + assertUnchangedPolicy(policySnapshot); + const cached = files.get(file); + if (cached) return !cached.excluded; + let fd: number | undefined; + try { + const pathInfo = fs.lstatSync(file); + if (!pathInfo.isFile()) throw new Error('Conversation changed during record admission'); + fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW); + const initial = fs.fstatSync(fd); + if (!initial.isFile() || initial.dev !== pathInfo.dev || initial.ino !== pathInfo.ino || + !Number.isSafeInteger(initial.size)) throw new Error('Conversation changed during record admission'); + const hash = crypto.createHash('sha256'); + // The observed size is this request's snapshot boundary. Records appended + // later are admitted by the next request, including conversation opt-outs. + const excluded = descriptorHasExclusionMarker(fd, initial.size, hash); + files.set(file, { initial, excluded, digest: excluded ? undefined : hash.digest('hex') }); + return !excluded; + } catch { throw new Error('Conversation changed during record admission'); } + finally { if (fd !== undefined) fs.closeSync(fd); } + }; + return { + admit(exchange: ConversationExchange): ConversationExchange | null { + if (!checkFile(exchange.archivePath)) return null; + const admitted = admitExchange(exchange, policy); + assertUnchangedPolicy(policySnapshot); + return admitted; + }, + summary(exchange: ConversationExchange, text: string): string | undefined { + // An old summary has no record-level provenance. A filtered source may have + // contributed any of its text, so do not expose that derived summary. + const ids = [exchange.sessionId, sessionFromPath(exchange.archivePath)]; + const transcript = path.basename(exchange.archivePath); + if (policy && [...policy.exchanges, ...policy.tool_calls].some(row => + row.transcript === transcript && ids.includes(row.session_id))) return undefined; + return admitExchange({ ...exchange, userMessage: text, assistantMessage: '', toolCalls: [] }, policy) + ? text : undefined; + }, + verify() { + assertUnchangedPolicy(policySnapshot); + for (const [file, { initial, digest }] of files) { + if (digest !== undefined) assertUnchangedSearchPrefix(file, initial, digest); + } + assertUnchangedPolicy(policySnapshot); + }, + }; +} diff --git a/src/search.ts b/src/search.ts index 786d5119..b6c5e3fb 100644 --- a/src/search.ts +++ b/src/search.ts @@ -1,5 +1,6 @@ import Database from 'better-sqlite3'; import { initDatabase } from './db.js'; +import { createSearchAdmission } from './record-admission.js'; import { initEmbeddings, generateQueryEmbedding } from './embeddings.js'; import { SearchResult, ConversationExchange, MultiConceptResult } from './types.js'; import { isErroredSentinel } from './summary-sentinel.js'; @@ -188,81 +189,77 @@ export async function searchConversations( if (after) validateISODate(after, '--after'); if (before) validateISODate(before, '--before'); + const admission = createSearchAdmission(); const db = initDatabase(); + const admitted = (row: any) => admission.admit(exchangeFromRow(row)) !== null; let results: any[] = []; const { sql: filterClause, params: filterParams } = buildSearchFilters(options); - if (mode === 'vector' || mode === 'both') { - // Vector similarity search. - // vec0 applies KNN before the WHERE clause and before our sidechain - // de-rank, so we over-fetch candidates and trim after the final ordering. - await initEmbeddings(); - const queryEmbedding = await generateQueryEmbedding(query); - const k = limit * 3; - - const stmt = db.prepare(` - SELECT - ${EXCHANGE_SELECT_COLUMNS}, - vec.distance - FROM vec_exchanges AS vec - JOIN exchanges AS e ON vec.id = e.id - WHERE vec.embedding MATCH ? - AND k = ? - ${sidechainClause} - ${filterClause} - ORDER BY (vec.distance + e.is_sidechain * ?) ASC - `); - - results = stmt.all( - Buffer.from(new Float32Array(queryEmbedding).buffer), - k, - ...filterParams, - SIDECHAIN_DISTANCE_PENALTY - ); - if (results.length > limit) { - results = results.slice(0, limit); + try { + if (mode === 'vector' || mode === 'both') { + // Order the filtered candidates before admission. The vec0 KNN API caps k; + // increasing k cannot find safe results beyond a large excluded prefix. + await initEmbeddings(); + const queryEmbedding = await generateQueryEmbedding(query); + const stmt = db.prepare(` + SELECT ${EXCHANGE_SELECT_COLUMNS}, + vec_distance_l2(vec.embedding, ?) AS retrieval_distance + FROM vec_exchanges AS vec + JOIN exchanges AS e ON vec.id = e.id + WHERE 1 = 1 ${sidechainClause} ${filterClause} + ORDER BY (retrieval_distance + e.is_sidechain * ?) ASC + `); + for (const row of stmt.iterate( + Buffer.from(new Float32Array(queryEmbedding).buffer), + ...filterParams, SIDECHAIN_DISTANCE_PENALTY + )) { + if (admitted(row)) results.push(row); + if (results.length === limit) break; + } } - } - if (mode === 'text' || mode === 'both') { - // Text search: AND of per-token LIKE patterns so multi-word queries match - // when every term appears somewhere in the exchange (any order, either field). - // See #127 — whole-query contiguous substring matching returned empty for - // typical multi-word searches that are not verbatim phrases. Sidechain rows - // are de-ranked (ordered after main-thread rows), not excluded (#128). - const { sql: textMatchSql, params: textMatchParams } = buildTextMatchClause(query); - const textStmt = db.prepare(` - SELECT - ${EXCHANGE_SELECT_COLUMNS}, - 0 as distance - FROM exchanges AS e - WHERE ${textMatchSql} - ${sidechainClause} - ${filterClause} - ORDER BY e.is_sidechain ASC, e.timestamp DESC - LIMIT ? - `); - - const textResults = textStmt.all(...textMatchParams, ...filterParams, limit); - - if (mode === 'both') { - // Merge and deduplicate by ID - const seenIds = new Set(results.map(r => r.id)); - for (const textResult of textResults) { - if (!seenIds.has((textResult as any).id)) { - results.push(textResult); + if (mode === 'text' || mode === 'both') { + // Text search: AND of per-token LIKE patterns so multi-word queries match + // when every term appears somewhere in the exchange (any order, either field). + // See #127 — whole-query contiguous substring matching returned empty for + // typical multi-word searches that are not verbatim phrases. Sidechain rows + // are de-ranked (ordered after main-thread rows), not excluded (#128). + const { sql: textMatchSql, params: textMatchParams } = buildTextMatchClause(query); + const textStmt = db.prepare(` + SELECT + ${EXCHANGE_SELECT_COLUMNS}, + 0 as retrieval_distance + FROM exchanges AS e + WHERE ${textMatchSql} + ${sidechainClause} + ${filterClause} + ORDER BY e.is_sidechain ASC, e.timestamp DESC + `); + + const textResults: any[] = []; + for (const row of textStmt.iterate(...textMatchParams, ...filterParams)) { + if (admitted(row)) textResults.push(row); + if (textResults.length === limit) break; + } + + if (mode === 'both') { + // Merge and deduplicate by ID + const seenIds = new Set(results.map(r => r.id)); + for (const textResult of textResults) { + if (!seenIds.has((textResult as any).id)) { + results.push(textResult); + } } + } else { + results = textResults; } - } else { - results = textResults; } - } - db.close(); + } finally { db.close(); } - return results.map((row: any) => { + const output = results.map((row: any) => { const exchange = exchangeFromRow(row); // Try to load summary if available. Skip error sentinels (#96) so failed @@ -272,7 +269,7 @@ export async function searchConversations( if (fs.existsSync(summaryPath)) { const raw = fs.readFileSync(summaryPath, 'utf-8'); if (!isErroredSentinel(raw)) { - summary = raw.trim(); + summary = admission.summary(exchange, raw.trim()); } } @@ -282,11 +279,13 @@ export async function searchConversations( return { exchange, - similarity: mode === 'text' ? undefined : l2DistanceToCosineSimilarity(row.distance), + similarity: mode === 'text' ? undefined : l2DistanceToCosineSimilarity(row.retrieval_distance), snippet, summary } as SearchResult & { summary?: string }; }); + admission.verify(); + return output; } // Helper function to count lines in a file efficiently diff --git a/src/show-cli.ts b/src/show-cli.ts index 397267e0..689e1aff 100644 --- a/src/show-cli.ts +++ b/src/show-cli.ts @@ -1,4 +1,4 @@ -import { readFileSync } from 'fs'; +import { readAdmittedConversation } from './record-admission.js'; import { formatConversationAsMarkdown, formatConversationAsHTML } from './show.js'; const args = process.argv.slice(2); @@ -46,7 +46,7 @@ if (!filePath) { } try { - const jsonl = readFileSync(filePath, 'utf-8'); + const jsonl = await readAdmittedConversation(filePath); if (format === 'html') { console.log(formatConversationAsHTML(jsonl)); diff --git a/src/show.ts b/src/show.ts index 06a34b45..acab547c 100644 --- a/src/show.ts +++ b/src/show.ts @@ -24,15 +24,16 @@ interface ConversationMessage { } export function formatConversationAsMarkdown(jsonl: string, startLine?: number, endLine?: number): string { - const allLines = jsonl.trim().split('\n').filter(line => line.trim()); + const allLines = jsonl.split('\n'); // Apply line range if specified (1-indexed, inclusive) - const lines = startLine !== undefined || endLine !== undefined + const selectedLines = startLine !== undefined || endLine !== undefined ? allLines.slice( startLine !== undefined ? startLine - 1 : 0, endLine !== undefined ? endLine : undefined ) : allLines; + const lines = selectedLines.filter(line => line.trim()); if (isCodexRollout(lines)) { return formatCodexConversationAsMarkdown(lines); diff --git a/src/summarizer.ts b/src/summarizer.ts index d7e3cc04..a0d308c9 100644 --- a/src/summarizer.ts +++ b/src/summarizer.ts @@ -1,4 +1,7 @@ import fs from 'fs'; +import os from 'node:os'; +import path from 'node:path'; +import { admitExchange, readRecordExclusions } from './record-admission.js'; import { ConversationExchange } from './types.js'; import { query } from '@anthropic-ai/claude-agent-sdk'; import { SUMMARIZER_CONTEXT_MARKER } from './constants.js'; @@ -121,10 +124,11 @@ export interface CodexSummarizerCommand { command: string; args: string[]; prompt: string; - sessionId: string; + sessionId?: string; model?: string; versionArgs?: string[]; skipVersionCheck?: boolean; + isolated?: boolean; } /** @@ -404,19 +408,34 @@ Bad: } export function buildCodexSummarizerCommand(args: { - sessionId: string; + sessionId?: string; prompt: string; model?: string; codexBin?: string; + isolated?: boolean; }): CodexSummarizerCommand { const command = args.codexBin || process.env.EPISODIC_MEMORY_CODEX_BIN || 'codex'; + if (args.isolated && args.sessionId) throw new Error('An isolated summary cannot resume a source session'); + const restrictions = args.isolated ? [ + ...['shell_tool', 'unified_exec', 'shell_snapshot', 'apps', 'plugins', 'remote_plugin', + 'hooks', 'multi_agent', 'multi_agent_v2', 'memories', 'goals', 'computer_use', + 'browser_use', 'browser_use_external', 'image_generation', 'workspace_dependencies', + 'skill_search', 'skill_mcp_dependency_install', 'tool_suggest', 'code_mode_host', + 'sleep_tool', 'view_image', 'default_mode_request_user_input'] + .map(feature => `features.${feature}=false`), + 'features.skip_host_skill_discovery=true', 'skills.bundled.enabled=false', + 'skills.include_instructions=false', 'project_doc_max_bytes=0', + 'tools.experimental_request_user_input.enabled=false', 'web_search="disabled"', + 'history.persistence="none"', + ].flatMap(value => ['-c', value]) : []; return { command, - args: ['app-server'], + args: ['app-server', ...restrictions], prompt: args.prompt, sessionId: args.sessionId, model: args.model, + ...(args.isolated ? { isolated: true } : {}), }; } @@ -484,6 +503,9 @@ async function assertSupportedCodexVersion(command: CodexSummarizerCommand): Pro const output = await readCommandOutput(command.command, command.versionArgs || ['--version']); const version = parseCodexCliVersion(output); + if (command.isolated && (!version || !versionMeetsMinimum(version, '0.154.0'))) { + throw new Error('Isolated record-policy summarization requires codex-cli >= 0.154.0'); + } if (!version || !versionMeetsMinimum(version)) { throw new Error(codexVersionRequirementMessage(output)); } @@ -506,11 +528,16 @@ function requireTurnId(result: any, method: string): string { } export async function runCodexCommand(command: CodexSummarizerCommand): Promise { + if (command.isolated && command.sessionId) throw new Error('An isolated summary cannot resume a source session'); await assertSupportedCodexVersion(command); + const summaryCwd = command.isolated + ? fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'episodic-memory-summary-'))) + : undefined; return new Promise((resolve, reject) => { const child = spawn(command.command, command.args, { env: getApiEnv(), + ...(summaryCwd ? { cwd: summaryCwd } : {}), stdio: ['pipe', 'pipe', 'pipe'] }); @@ -519,6 +546,7 @@ export async function runCodexCommand(command: CodexSummarizerCommand): Promise< let nextRequestId = 1; let targetTurnId: string | undefined; let finished = false; + let terminalOutcome: { error?: Error; result: string } | undefined; let timeout: NodeJS.Timeout | undefined; const pending = new Map(); const lines = createInterface({ input: child.stdout }); @@ -541,6 +569,12 @@ export async function runCodexCommand(command: CodexSummarizerCommand): Promise< if (finished) return; finished = true; cleanup(); + if (command.isolated) { + // Wait for process termination before removing its owned directory. + // Provider payloads may repeat input; never persist them in error sidecars. + terminalOutcome = { error: error ? new Error('Isolated Codex summary failed; provider details suppressed') : undefined, result }; + return; + } if (error) { reject(error); } else { @@ -622,6 +656,15 @@ export async function runCodexCommand(command: CodexSummarizerCommand): Promise< finish(new Error(detail)); } }); + child.on('close', () => { + if (!command.isolated) return; + try { + if (summaryCwd) fs.rmSync(summaryCwd, { recursive: true, force: true }); + if (terminalOutcome?.error) reject(terminalOutcome.error); + else if (terminalOutcome) resolve(terminalOutcome.result); + else reject(new Error('Isolated Codex summary ended without a terminal result')); + } catch { reject(new Error('Isolated Codex summary cleanup failed')); } + }); (async () => { try { @@ -637,15 +680,44 @@ export async function runCodexCommand(command: CodexSummarizerCommand): Promise< }); notify('initialized'); - const fork = await send('thread/fork', { - threadId: command.sessionId, + let isolation: Record = {}; + if (command.isolated) { + const configured = await send('config/read', { includeLayers: false }); + const servers = configured?.config?.mcp_servers; + if (!configured?.config || servers !== undefined && (!servers || typeof servers !== 'object' || Array.isArray(servers))) { + throw new Error('Cannot establish configured MCP boundary'); + } + const listed = await send('skills/list', { cwds: [summaryCwd], forceReload: true }); + if (!Array.isArray(listed?.data) || listed.data.some((entry: any) => !Array.isArray(entry.skills) || !Array.isArray(entry.errors) || entry.errors.length)) { + throw new Error('Cannot establish configured skill boundary'); + } + const skills = listed.data.flatMap((entry: any) => entry.skills).map((skill: any) => { + if (typeof skill.path !== 'string' || !path.isAbsolute(skill.path)) throw new Error('Invalid configured skill identity'); + return { path: skill.path, enabled: false }; + }); + isolation = { + cwd: summaryCwd, + dynamicTools: [], environments: [], selectedCapabilityRoots: [], + allowProviderModelFallback: false, + baseInstructions: 'Summarize only the admitted transcript. Do not use tools or recover external context.', + developerInstructions: 'Output only a concise factual summary inside .', + config: { + mcp_servers: Object.fromEntries(Object.keys(servers ?? {}).map(name => [name, { enabled: false }])), + 'skills.config': skills, + }, + }; + } + const method = command.sessionId ? 'thread/fork' : 'thread/start'; + const fork = await send(method, { + ...(command.sessionId ? { threadId: command.sessionId } : {}), ephemeral: true, excludeTurns: true, sandbox: 'read-only', approvalPolicy: 'never', ...(command.model ? { model: command.model } : {}), + ...isolation, }); - const forkThreadId = requireThreadId(fork, 'thread/fork'); + const forkThreadId = requireThreadId(fork, method); const turn = await send('turn/start', { threadId: forkThreadId, @@ -663,8 +735,8 @@ export async function runCodexCommand(command: CodexSummarizerCommand): Promise< }); } -async function callCodex(prompt: string, sessionId: string, model?: string): Promise { - const command = buildCodexSummarizerCommand({ sessionId, prompt, model }); +async function callCodex(prompt: string, sessionId?: string, model?: string, isolated = false): Promise { + const command = buildCodexSummarizerCommand({ sessionId, prompt, model, isolated }); return runCodexCommand(command); } @@ -680,7 +752,7 @@ function getCodexSessionId(exchanges: ConversationExchange[], sessionId?: string if (!exchanges.some(exchange => exchange.harness === 'codex')) { return undefined; } - return sessionId || exchanges.find(exchange => exchange.sessionId)?.sessionId; + return sessionId || exchanges.find(exchange => exchange.harness === 'codex' && exchange.sessionId)?.sessionId; } /** @@ -703,6 +775,9 @@ export function getCodexModel(_exchanges: ConversationExchange[]): string | unde } export async function summarizeConversation(exchanges: ConversationExchange[], sessionId?: string): Promise { + const recordPolicy = readRecordExclusions(); + exchanges = exchanges.map(exchange => admitExchange(exchange, recordPolicy)).filter((exchange): exchange is ConversationExchange => exchange !== null); + if (recordPolicy) sessionId = undefined; // Handle trivial conversations if (exchanges.length === 0) { return 'Trivial conversation with no substantive content.'; @@ -716,13 +791,15 @@ export async function summarizeConversation(exchanges: ConversationExchange[], s } const codexSessionId = getCodexSessionId(exchanges, sessionId); - if (codexSessionId) { - try { - const result = await callCodex(buildCodexSummaryPrompt(), codexSessionId, getCodexModel(exchanges)); - return extractSummary(result); - } catch (error) { - console.log(` Codex summarizer unavailable, falling back to transcript text: ${error instanceof Error ? error.message : String(error)}`); - } + if (exchanges.some(exchange => exchange.harness === 'codex')) { + // A sessionless or policy-filtered transcript has no source context to fork. + // Summarize its admitted text in a fresh, isolated Codex thread instead. + const isolated = recordPolicy !== null || !codexSessionId; + const prompt = isolated + ? `${SUMMARIZER_CONTEXT_MARKER}. Summarize only the following admitted transcript in 2-4 factual sentences inside . Do not inspect files, use tools or recover any original session.\n\n${formatConversationText(exchanges)}` + : buildCodexSummaryPrompt(); + const result = await callCodex(prompt, isolated ? undefined : codexSessionId, getCodexModel(exchanges), isolated); + return extractSummary(result); } // Cost safety (#104): everything below this point calls the metered Claude API diff --git a/src/sync-cli.ts b/src/sync-cli.ts index 297b20f8..d9b1476b 100644 --- a/src/sync-cli.ts +++ b/src/sync-cli.ts @@ -224,7 +224,12 @@ async function syncAll() { totals.errors.push(...result.errors); } - console.log(`\nāœ… Sync complete!`); + // Admission failure is a partial run, not successful synchronization. + // Finish migration before emitting a success claim as well. + if (totals.errors.length === 0) await runEmbeddingMigrationPhase(); + else process.exitCode = 1; + + console.log(totals.errors.length ? '\nSync finished with errors.' : '\nāœ… Sync complete!'); console.log(` Copied: ${totals.copied}`); console.log(` Skipped: ${totals.skipped}`); console.log(` Indexed: ${totals.indexed}`); @@ -246,10 +251,6 @@ async function syncAll() { } } - // After regular sync, do a batch of embedding migration if any rows are - // still on the old encoder. Lock-protected; if another process is already - // migrating, this is a no-op. - await runEmbeddingMigrationPhase(); } const MIGRATION_BATCH_SIZE = parseInt(process.env.EPISODIC_MEMORY_MIGRATION_BATCH || '500', 10); @@ -270,6 +271,7 @@ async function runEmbeddingMigrationPhase(): Promise { } } catch (err) { console.error('episodic-memory: migration phase error:', err instanceof Error ? err.message : err); + throw err; } finally { db.close(); } diff --git a/src/sync.ts b/src/sync.ts index d9ee6810..40efdfad 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -1,52 +1,12 @@ import fs from 'fs'; +import { archiveAdmittedConversation, readRecordExclusions } from './record-admission.js'; import path from 'path'; -import { StringDecoder } from 'string_decoder'; -import { SUMMARIZER_CONTEXT_MARKER } from './constants.js'; import { getExcludedProjects, findJsonlFiles, statIfExists } from './paths.js'; import { formatErrorSentinel, shouldQueueForSummary } from './summary-sentinel.js'; import { getMaxMessageBytes, isOversizeExchange } from './message-size.js'; -const EXCLUSION_MARKERS = [ - 'DO NOT INDEX THIS CHAT', - 'Only use NO_INSIGHTS_FOUND', - SUMMARIZER_CONTEXT_MARKER, -]; - -const MARKER_SCAN_CHUNK_BYTES = 1 << 20; // 1 MiB - -/** - * Stream and scan for any exclusion marker, carrying an overlap between - * chunks so a marker split across a boundary is still found. A single - * fs.readFileSync(path, 'utf-8') throws ERR_STRING_TOO_LONG above Node's - * ~512 MB max string length; the old catch returned false (fail OPEN), - * silently indexing a file whose DO NOT INDEX marker we never read (#152). - * Streaming confirms cleanliness at any size, and a real read error now - * fails CLOSED (skip) rather than open. - */ -export function shouldSkipConversation(filePath: string): boolean { - const maxMarkerLen = Math.max(...EXCLUSION_MARKERS.map(m => m.length)); - let fd: number | undefined; - try { - fd = fs.openSync(filePath, 'r'); - const buf = Buffer.allocUnsafe(MARKER_SCAN_CHUNK_BYTES); - const decoder = new StringDecoder('utf8'); - let carry = ''; - let bytesRead: number; - while ((bytesRead = fs.readSync(fd, buf, 0, buf.length, null)) > 0) { - const window = carry + decoder.write(buf.subarray(0, bytesRead)); - if (EXCLUSION_MARKERS.some(marker => window.includes(marker))) { - return true; - } - carry = window.slice(Math.max(0, window.length - (maxMarkerLen - 1))); - } - const tail = carry + decoder.end(); - return EXCLUSION_MARKERS.some(marker => tail.includes(marker)); - } catch { - return true; // fail closed (#152) - } finally { - if (fd !== undefined) { try { fs.closeSync(fd); } catch {} } - } -} +export { shouldSkipConversation } from './record-admission.js'; +import { shouldSkipConversation } from './record-admission.js'; /** * True when a transcript contains at least one message line in any supported @@ -132,37 +92,6 @@ export function buildSyncOptionsFromEnv(env: NodeJS.ProcessEnv): SyncOptions { return { skipSummaries: env.EPISODIC_MEMORY_SKIP_SUMMARIES === '1' }; } -function copyIfNewer(src: string, dest: string): boolean { - // Ensure destination directory exists - const destDir = path.dirname(dest); - if (!fs.existsSync(destDir)) { - fs.mkdirSync(destDir, { recursive: true }); - } - - // Check if destination exists and is up-to-date - if (fs.existsSync(dest)) { - const srcStat = fs.statSync(src); - const destStat = fs.statSync(dest); - if (destStat.mtimeMs >= srcStat.mtimeMs) { - return false; // Dest is current, skip - } - } - - // Atomic copy: temp file + rename - const tempDest = dest + '.tmp.' + process.pid; - fs.copyFileSync(src, tempDest); - fs.renameSync(tempDest, dest); // Atomic on same filesystem - - // Preserve source mtime: harnesses without per-message timestamps (Cursor - // agent transcripts) fall back to file mtime. Round up to the next whole - // millisecond — utimes can't always represent the source's sub-millisecond - // precision, and a dest mtime even fractionally older would defeat the - // skip-if-current check above on every subsequent sync. - const srcStat = fs.statSync(src); - fs.utimesSync(dest, srcStat.atimeMs / 1000, Math.ceil(srcStat.mtimeMs) / 1000); - return true; -} - export function extractSessionIdFromPath(filePath: string): string | null { // Extract session ID from Claude filename or Codex rollout filename. const basename = path.basename(filePath, '.jsonl'); @@ -179,6 +108,7 @@ export async function syncConversations( destDir: string, options: SyncOptions = {} ): Promise { + const exclusions = readRecordExclusions(); const result: SyncResult = { copied: 0, skipped: 0, @@ -226,7 +156,7 @@ export async function syncConversations( continue; } - const wasCopied = copyIfNewer(srcFile, destFile); + const wasCopied = await archiveAdmittedConversation(srcFile, destFile, exclusions); if (wasCopied) { result.copied++; filesToIndex.push(destFile); @@ -240,10 +170,10 @@ export async function syncConversations( if (!options.skipSummaries) { const summaryPath = destFile.replace('.jsonl', '-summary.txt'); if (shouldQueueForSummary(summaryPath) && !shouldSkipConversation(destFile)) { - // sessionId enables Claude session-resume summarization; when the - // filename has no UUID to extract (e.g. subagent transcripts named - // agent-.jsonl), queue anyway — summarizeConversation falls - // back to summarizing from the transcript text. + // sessionId enables source-session summarization. When the filename + // has no UUID (e.g. agent-.jsonl), queue anyway: Codex uses an + // isolated transcript-only thread; other harnesses use their own + // transcript-text route without resuming a source session. const sessionId = extractSessionIdFromPath(destFile) ?? undefined; filesToSummarize.push({ path: destFile, sessionId }); } diff --git a/src/verify.ts b/src/verify.ts index 0dd2c5db..0cb61a56 100644 --- a/src/verify.ts +++ b/src/verify.ts @@ -4,19 +4,25 @@ import { parseConversation } from './parser.js'; import { initDatabase, getAllExchanges, getFileLastIndexed } from './db.js'; import { getArchiveDir, getExcludedProjects, findJsonlFiles, statIfExists } from './paths.js'; import { isErroredSentinel } from './summary-sentinel.js'; +import { readRecordExclusions } from './record-admission.js'; export interface VerificationResult { missing: Array<{ path: string; reason: string }>; orphaned: Array<{ uuid: string; path: string }>; outdated: Array<{ path: string; fileTime: number; dbTime: number }>; + screeningRejected: Array<{ path: string }>; + screeningUnavailable: Array<{ path: string }>; corrupted: Array<{ path: string; error: string }>; } export async function verifyIndex(): Promise { + readRecordExclusions(); const result: VerificationResult = { missing: [], orphaned: [], outdated: [], + screeningRejected: [], + screeningUnavailable: [], corrupted: [] }; @@ -68,11 +74,8 @@ export async function verifyIndex(): Promise { // re-attempts it rather than reporting the conversation as healthy. if (!fs.existsSync(summaryPath)) { result.missing.push({ path: conversationPath, reason: 'No summary file' }); - continue; - } - if (isErroredSentinel(fs.readFileSync(summaryPath, 'utf-8'))) { + } else if (isErroredSentinel(fs.readFileSync(summaryPath, 'utf-8'))) { result.missing.push({ path: conversationPath, reason: 'Previous summarization failed (error sentinel)' }); - continue; } // Check if file is outdated (modified after last_indexed) @@ -92,10 +95,16 @@ export async function verifyIndex(): Promise { try { await parseConversation(conversationPath, project, conversationPath); } catch (error) { - result.corrupted.push({ - path: conversationPath, - error: error instanceof Error ? error.message : String(error) - }); + const message = error instanceof Error ? error.message : String(error); + if (message === 'Record content rejected by screening' || + message === 'Record screening byte limit exceeded' || + message === 'Record screening nesting limit exceeded') { + result.screeningRejected.push({ path: conversationPath }); + } else if (message === 'Record screening unavailable') { + result.screeningUnavailable.push({ path: conversationPath }); + } else { + result.corrupted.push({ path: conversationPath, error: message }); + } } } } @@ -119,6 +128,10 @@ export async function verifyIndex(): Promise { } export async function repairIndex(issues: VerificationResult): Promise { + if (issues.screeningRejected.length + issues.screeningUnavailable.length > 0) { + throw new Error('Resolve record screening failures before repair'); + } + readRecordExclusions(); console.log('Repairing index...'); // To avoid circular dependencies, we import the indexer functions dynamically @@ -178,6 +191,8 @@ export async function repairIndex(issues: VerificationResult): Promise { console.log(` Indexed ${exchanges.length} exchanges`); } catch (error) { console.error(`Failed to re-index ${conversationPath}:`, error); + db.close(); + throw error; } } diff --git a/test/codex-summary-routing.test.ts b/test/codex-summary-routing.test.ts new file mode 100644 index 00000000..8daea696 --- /dev/null +++ b/test/codex-summary-routing.test.ts @@ -0,0 +1,132 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import type { ConversationExchange } from '../src/types.js'; + +vi.mock('@anthropic-ai/claude-agent-sdk', () => ({ query: vi.fn() })); + +import { query } from '@anthropic-ai/claude-agent-sdk'; +import { summarizeConversation } from '../src/summarizer.js'; + +let testDir: string; +let previousEnv: NodeJS.ProcessEnv; + +function exchange(sessionId?: string): ConversationExchange { + return { + id: 'codex-summary-fixture', + project: 'fixture', + timestamp: '2026-01-01T00:00:00Z', + userMessage: 'Explain the design and acceptance boundary for this synthetic Codex conversation.', + assistantMessage: 'The design keeps provider ownership and validates the route with an isolated local app-server fixture.', + archivePath: path.join(testDir, 'synthetic.jsonl'), + lineStart: 1, + lineEnd: 2, + harness: 'codex', + ...(sessionId ? { sessionId } : {}), + }; +} + +function installFakeCodex(failMethod?: 'thread/start' | 'thread/fork'): void { + const executable = path.join(testDir, 'fake-codex'); + fs.writeFileSync(executable, `#!/usr/bin/env node +const readline = require('node:readline'); +if (process.argv.includes('--version')) { + console.log('codex-cli 0.154.0'); + process.exit(0); +} +if (!process.argv.includes('app-server')) process.exit(2); +const emit = value => process.stdout.write(JSON.stringify(value) + '\\n'); +readline.createInterface({ input: process.stdin }).on('line', line => { + const message = JSON.parse(line); + if (message.method === 'initialize') emit({ id: message.id, result: {} }); + if (message.method === 'config/read') emit({ id: message.id, result: { config: { mcp_servers: {} } } }); + if (message.method === 'skills/list') emit({ id: message.id, result: { data: [{ skills: [], errors: [] }] } }); + if (message.method === 'thread/start') { + if (${failMethod === 'thread/start'}) process.exit(7); + const params = message.params; + if (!params.ephemeral || params.threadId || params.sandbox !== 'read-only' || + params.allowProviderModelFallback !== false || !params.cwd) process.exit(8); + emit({ id: message.id, result: { thread: { id: 'ephemeral-summary' } } }); + } + if (message.method === 'thread/fork') { + if (${failMethod === 'thread/fork'}) process.exit(7); + if (message.params.threadId !== 'source-session' || !message.params.ephemeral) process.exit(9); + emit({ id: message.id, result: { thread: { id: 'forked-summary' } } }); + } + if (message.method === 'turn/start') { + const prompt = message.params.input[0].text; + if (message.params.threadId === 'ephemeral-summary' && + (!prompt.includes('synthetic Codex conversation') || !prompt.includes('isolated local app-server fixture'))) process.exit(10); + emit({ id: message.id, result: { turn: { id: 'summary-turn' } } }); + emit({ method: 'item/completed', params: { item: { type: 'agentMessage', text: 'Codex kept provider ownership.' } } }); + emit({ method: 'turn/completed', params: { turn: { id: 'summary-turn', status: 'completed' } } }); + } +}); +`); + fs.chmodSync(executable, 0o700); + process.env.EPISODIC_MEMORY_CODEX_BIN = executable; +} + +beforeEach(() => { + previousEnv = { ...process.env }; + testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codex-summary-routing-')); + process.env.EPISODIC_MEMORY_CONFIG_DIR = path.join(testDir, 'config'); + vi.mocked(query).mockReset(); + vi.mocked(query).mockReturnValue({ + async *[Symbol.asyncIterator]() { + yield { type: 'result', is_error: false, result: 'Wrong provider.' }; + }, + } as any); +}); + +afterEach(() => { + process.env = previousEnv; + fs.rmSync(testDir, { recursive: true, force: true }); +}); + +describe('Codex summary provider ownership', () => { + it('summarizes a sessionless Codex transcript in an isolated ephemeral Codex thread', async () => { + installFakeCodex(); + + expect(await summarizeConversation([exchange()])).toBe('Codex kept provider ownership.'); + expect(query).not.toHaveBeenCalled(); + }); + + it('does not borrow another harness session ID for a sessionless Codex exchange', async () => { + installFakeCodex(); + const otherHarness: ConversationExchange = { + ...exchange('other-provider-session'), + id: 'other-provider-exchange', + harness: 'claude', + }; + + expect(await summarizeConversation([otherHarness, exchange()])) + .toBe('Codex kept provider ownership.'); + expect(query).not.toHaveBeenCalled(); + }); + + it('does not fall back to Claude when a sessionless Codex summary fails', async () => { + installFakeCodex('thread/start'); + + await expect(summarizeConversation([exchange()])) + .rejects.toThrow('Isolated Codex summary failed; provider details suppressed'); + expect(query).not.toHaveBeenCalled(); + }); + + it('keeps the ephemeral fork for a Codex transcript with a session ID', async () => { + installFakeCodex(); + + expect(await summarizeConversation([exchange('source-session')])) + .toBe('Codex kept provider ownership.'); + expect(query).not.toHaveBeenCalled(); + }); + + it('does not fall back to Claude when a Codex fork fails', async () => { + installFakeCodex('thread/fork'); + + await expect(summarizeConversation([exchange('source-session')])) + .rejects.toThrow(); + expect(query).not.toHaveBeenCalled(); + }); +}); diff --git a/test/mcp-server.test.ts b/test/mcp-server.test.ts index 47382c96..c70b565c 100644 --- a/test/mcp-server.test.ts +++ b/test/mcp-server.test.ts @@ -1,7 +1,7 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest'; import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; -import { existsSync, mkdirSync, mkdtempSync, rmSync } from 'node:fs'; +import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync, readFileSync, unlinkSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -87,6 +87,47 @@ describe('MCP search tool', () => { } }); + it('enforces changed exclusions through the packaged MCP search entrypoint', async () => { + const { initDatabase, insertExchange } = await import('../src/db.js'); + const priorDb = process.env.EPISODIC_MEMORY_DB_PATH; + const priorTestDb = process.env.TEST_DB_PATH; + const priorConfig = process.env.EPISODIC_MEMORY_CONFIG_DIR; + const session = '00000000-1111-4222-8333-444444444444'; + const file = join(testDir!, `packaged-${session}.jsonl`); + const policy = join(testDir!, 'config', 'record-exclusions.json'); + mkdirSync(join(testDir!, 'config'), { recursive: true }); + const original = [ + { type: 'session_meta', payload: { id: session } }, + { type: 'response_item', payload: { type: 'message', role: 'user', content: [{ type: 'input_text', text: 'packaged-admission-probe' }] } }, + { type: 'response_item', payload: { type: 'message', role: 'assistant', content: [{ type: 'output_text', text: 'safe answer' }] } }, + ].map(row => JSON.stringify(row)).join('\n'); + writeFileSync(file, original); + try { + delete process.env.EPISODIC_MEMORY_DB_PATH; + process.env.TEST_DB_PATH = testDbPath; + process.env.EPISODIC_MEMORY_CONFIG_DIR = join(testDir!, 'config'); + const db = initDatabase(); + insertExchange(db, { id: 'packaged-admission', project: 'fixture', timestamp: '2026-01-01T00:00:00Z', + userMessage: 'packaged-admission-probe', assistantMessage: 'safe answer', archivePath: file, + lineStart: 2, lineEnd: 3, sessionId: session }, new Array(384).fill(0.1)); + db.close(); + const search = () => client.callTool({ name: 'search', arguments: { + query: 'packaged-admission-probe', mode: 'text', response_format: 'json', limit: 1, + } }); + expect(JSON.parse(getTextContent((await search()).content as ToolContent[])).count).toBe(1); + writeFileSync(policy, JSON.stringify({ version: 1, exchanges: [{ session_id: session, + transcript: `packaged-${session}.jsonl`, line_start: 2, line_end: 3 }], tool_calls: [] })); + const denied = await search(); expect(denied.isError).toBeFalsy(); + expect(JSON.parse(getTextContent(denied.content as ToolContent[])).count).toBe(0); + expect(readFileSync(file, 'utf8')).toBe(original); + } finally { + if (priorDb === undefined) delete process.env.EPISODIC_MEMORY_DB_PATH; else process.env.EPISODIC_MEMORY_DB_PATH = priorDb; + if (priorTestDb === undefined) delete process.env.TEST_DB_PATH; else process.env.TEST_DB_PATH = priorTestDb; + if (priorConfig === undefined) delete process.env.EPISODIC_MEMORY_CONFIG_DIR; else process.env.EPISODIC_MEMORY_CONFIG_DIR = priorConfig; + if (existsSync(policy)) unlinkSync(policy); + } + }); + it('advertises search and read tools with single and multi-concept queries', async () => { const tools = await client.listTools(); const searchTool = tools.tools.find((tool) => tool.name === 'search'); @@ -121,4 +162,34 @@ describe('MCP search tool', () => { }); expect(existsSync(testDbPath)).toBe(true); }); + + it('applies live record exclusions to direct reads and keeps physical range coordinates', async () => { + const session = '00000000-1111-4222-8333-444444444444'; + const file = join(testDir!, `rollout-${session}.jsonl`); + const config = join(testDir!, 'config', 'record-exclusions.json'); + mkdirSync(join(testDir!, 'config'), { recursive: true }); + const rows = [ + { type: 'session_meta', payload: { id: session } }, + ...['safe first', 'EXCLUDED_PUBLIC_FIXTURE', 'safe last'].map(text => ({ type: 'response_item', + payload: { type: 'message', role: 'user', content: [{ type: 'text', text }] } })), + ]; + const original = rows.map(row => JSON.stringify(row)).join('\n') + '\n'; + writeFileSync(file, original); + writeFileSync(config, JSON.stringify({ version: 1, + exchanges: [{ session_id: session, transcript: `rollout-${session}.jsonl`, line_start: 3, line_end: 3 }], tool_calls: [] })); + try { + const result = await client.callTool({ name: 'read', arguments: { path: file } }); + expect(result.isError).toBeFalsy(); + const text = getTextContent(result.content as ToolContent[]); + expect(text).not.toContain('EXCLUDED_PUBLIC_FIXTURE'); + expect(text).toContain('safe first'); + expect(text).toContain('safe last'); + const ranged = await client.callTool({ name: 'read', arguments: { path: file, startLine: 4, endLine: 4 } }); + expect(ranged.isError).toBeFalsy(); + const rangedText = getTextContent(ranged.content as ToolContent[]); + expect(rangedText).toContain('safe last'); + expect(rangedText).not.toContain('safe first'); + expect(readFileSync(file, 'utf8')).toBe(original); + } finally { unlinkSync(config); unlinkSync(file); } + }); }); diff --git a/test/record-admission.test.ts b/test/record-admission.test.ts new file mode 100644 index 00000000..b7c40e78 --- /dev/null +++ b/test/record-admission.test.ts @@ -0,0 +1,449 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { syncConversations } from '../src/sync.js'; +import { parseConversation } from '../src/parser.js'; +import { initDatabase, insertExchange } from '../src/db.js'; +import { indexConversations, indexSession, indexUnprocessed } from '../src/indexer.js'; +import { summarizeConversation, runCodexCommand } from '../src/summarizer.js'; +import { query } from '@anthropic-ai/claude-agent-sdk'; +import { admittedConversationLines, archiveAdmittedConversation } from '../src/record-admission.js'; + +vi.mock('../src/embeddings.js', () => ({ + initEmbeddings: vi.fn(async () => {}), + generateExchangeEmbedding: vi.fn(async () => new Array(384).fill(0)), +})); +vi.mock('@anthropic-ai/claude-agent-sdk', () => ({ query: vi.fn() })); +import type { ConversationExchange } from '../src/types.js'; + +const SESSION = '00000000-1111-4222-8333-444444444444'; +const PRIVATE = 'public-fixture-excluded-content'; +let root: string; +let source: string; +let archive: string; +let config: string; +let sourceFile: string; +let archiveFile: string; +let previous: NodeJS.ProcessEnv; + +function message(role: string, text: string): object { + return { type: 'response_item', timestamp: '2026-01-01T00:00:00Z', + payload: { type: 'message', role, content: [{ type: 'text', text }] } }; +} + +function transcript(): string { + return [ + { type: 'session_meta', payload: { id: SESSION, cwd: '/synthetic/project' } }, + message('user', 'safe question one'), + message('assistant', 'safe answer one'), + message('user', PRIVATE), + message('assistant', PRIVATE), + message('user', 'safe question two'), + { type: 'response_item', payload: { type: 'function_call', call_id: 'call-denied', name: 'fixture', arguments: JSON.stringify({ value: PRIVATE }) } }, + { type: 'response_item', payload: { type: 'function_call_output', call_id: 'call-denied', output: PRIVATE } }, + message('assistant', 'safe answer two'), + ].map(value => JSON.stringify(value)).join('\n') + '\n'; +} + +function policy(value: object = { + version: 1, + exchanges: [{ session_id: SESSION, transcript: `rollout-${SESSION}.jsonl`, line_start: 4, line_end: 5 }], + tool_calls: [{ session_id: SESSION, transcript: `rollout-${SESSION}.jsonl`, call_id: 'call-denied' }], +}): void { + fs.mkdirSync(config, { recursive: true }); + fs.writeFileSync(path.join(config, 'record-exclusions.json'), JSON.stringify(value)); +} + +beforeEach(() => { + previous = { ...process.env }; + root = fs.mkdtempSync(path.join(os.tmpdir(), 'record-admission-')); + source = path.join(root, 'source'); + archive = path.join(root, 'archive'); + config = path.join(root, 'config'); + process.env.EPISODIC_MEMORY_CONFIG_DIR = config; + process.env.EPISODIC_MEMORY_DB_PATH = path.join(root, 'index.sqlite'); + delete process.env.CONVERSATION_SEARCH_EXCLUDE_PROJECTS; + process.env.TEST_PROJECTS_DIR = source; + process.env.TEST_ARCHIVE_DIR = archive; + fs.mkdirSync(path.join(source, '2026'), { recursive: true }); + sourceFile = path.join(source, '2026', `rollout-${SESSION}.jsonl`); + archiveFile = path.join(archive, '2026', `rollout-${SESSION}.jsonl`); + fs.writeFileSync(sourceFile, transcript()); +}); + +afterEach(() => { + process.env = previous; + fs.rmSync(root, { recursive: true, force: true }); + expect(fs.existsSync(root)).toBe(false); + vi.restoreAllMocks(); +}); + +describe('persistent record exclusion at native ingestion boundaries', () => { + it('does not apply one transcript range to a sibling that shares its session', async () => { + policy({ version: 1, exchanges: [{ session_id: SESSION, transcript: `rollout-${SESSION}.jsonl`, line_start: 4, line_end: 5 }], tool_calls: [] }); + const sibling = path.join(source, '2026', 'agent-sibling.jsonl'); + fs.writeFileSync(sibling, transcript()); + const result = await syncConversations(source, archive, { skipIndex: true, skipSummaries: true }); + expect(result.errors).toEqual([]); + expect(fs.readFileSync(path.join(archive, '2026', 'agent-sibling.jsonl'), 'utf8')).toBe(transcript()); + const admitted = await parseConversation(archiveFile, '2026', archiveFile); + expect(admitted.map(x => x.userMessage)).toEqual(['safe question one', 'safe question two']); + }); + + it('keeps the first transcript identity when fork history contains ancestor headers', async () => { + policy({ version: 1, exchanges: [{ session_id: SESSION, transcript: `rollout-${SESSION}.jsonl`, line_start: 5, line_end: 6 }], tool_calls: [] }); + const rows = transcript().trimEnd().split('\n'); + rows.splice(1, 0, JSON.stringify({ type: 'session_meta', payload: { id: '00000000-aaaa-4bbb-8ccc-dddddddddddd' } })); + fs.writeFileSync(sourceFile, rows.join('\n') + '\n'); + const result = await syncConversations(source, archive, { skipIndex: true, skipSummaries: true }); + expect(result.errors).toEqual([]); + const exchanges = await parseConversation(archiveFile, '2026', archiveFile); + expect(exchanges.map(x => x.userMessage)).toEqual(['safe question one', 'safe question two']); + expect(exchanges.every(x => x.sessionId === SESSION)).toBe(true); + }); + + it('retains safe Claude text in a block shared with an excluded native tool call', async () => { + policy({ version: 1, exchanges: [], tool_calls: [{ session_id: SESSION, transcript: `rollout-${SESSION}.jsonl`, call_id: 'toolu-denied' }] }); + const lines = [ + { type: 'user', sessionId: SESSION, message: { role: 'user', content: 'safe Claude question' } }, + { type: 'assistant', sessionId: SESSION, message: { role: 'assistant', content: [ + { type: 'text', text: 'safe Claude reply' }, + { type: 'tool_use', id: 'toolu-denied', name: 'fixture', input: { value: PRIVATE } }, + { type: 'tool_use', id: 'toolu-allowed', name: 'fixture', input: { value: 'safe input' } }, + ] } }, + ].map(JSON.stringify).join('\n') + '\n'; + fs.writeFileSync(sourceFile, lines); + await syncConversations(source, archive, { skipIndex: true, skipSummaries: true }); + const copied = fs.readFileSync(archiveFile, 'utf8'); + expect(copied).not.toContain(PRIVATE); + expect(copied).toContain('safe Claude reply'); + expect(copied).toContain('toolu-allowed'); + const exchanges = await parseConversation(sourceFile, 'fixture', archiveFile); + expect(exchanges).toHaveLength(1); + expect(exchanges[0].toolCalls).toHaveLength(1); + expect(exchanges[0].assistantMessage).toBe('safe Claude reply'); + expect(fs.readFileSync(sourceFile, 'utf8')).toBe(lines); + }); + + it('filters only denied records before copying and preserves physical line coordinates', async () => { + policy(); + const before = fs.readFileSync(sourceFile); + const result = await syncConversations(source, archive, { skipIndex: true, skipSummaries: true }); + expect(result.errors).toEqual([]); + expect(fs.readFileSync(sourceFile)).toEqual(before); + const copied = fs.readFileSync(archiveFile, 'utf8'); + expect(copied).not.toContain(PRIVATE); + expect(copied.split('\n')).toHaveLength(transcript().split('\n').length); + const exchanges = await parseConversation(archiveFile, '2026', archiveFile); + expect(exchanges.map(x => x.userMessage)).toEqual(['safe question one', 'safe question two']); + expect(exchanges.map(x => x.lineStart)).toEqual([2, 6]); + expect(exchanges.flatMap(x => x.toolCalls ?? [])).toEqual([]); + }); + + it('applies exclusions when directly parsing original input without rewriting it', async () => { + policy(); + const before = fs.readFileSync(sourceFile); + const exchanges = await parseConversation(sourceFile, '2026', archiveFile); + expect(exchanges.map(x => x.userMessage)).toEqual(['safe question one', 'safe question two']); + expect(JSON.stringify(exchanges)).not.toContain(PRIVATE); + expect(fs.readFileSync(sourceFile)).toEqual(before); + }); + + it('reapplies a changed policy even when source mtime did not advance', async () => { + await syncConversations(source, archive, { skipIndex: true, skipSummaries: true }); + expect(fs.readFileSync(archiveFile, 'utf8')).toContain(PRIVATE); + policy(); + await syncConversations(source, archive, { skipIndex: true, skipSummaries: true }); + expect(fs.readFileSync(archiveFile, 'utf8')).not.toContain(PRIVATE); + }); + + it('fails closed on malformed policy before any destination write', async () => { + policy({ version: 1, exchanges: [{ session_id: SESSION, transcript: `rollout-${SESSION}.jsonl`, line_start: '4', line_end: 5 }], tool_calls: [] }); + await expect(syncConversations(source, archive, { skipIndex: true, skipSummaries: true })).rejects.toThrow(/record.*exclusion/i); + expect(fs.existsSync(archiveFile)).toBe(false); + }); + + it('honors whole-conversation opt-out before any archive copy or parsing', async () => { + const marker = 'DO NOT INDEX THIS CHAT'; + fs.writeFileSync(sourceFile, transcript().replace('safe question one', marker)); + const original = fs.readFileSync(sourceFile); + await syncConversations(source, archive, { skipIndex: true, skipSummaries: true }); + expect(fs.existsSync(archiveFile)).toBe(false); + expect(await parseConversation(sourceFile, '2026', archiveFile)).toEqual([]); + expect(fs.readFileSync(sourceFile)).toEqual(original); + }); + + it('rejects policy identity mismatch rather than silently selecting a different session', async () => { + policy(); + fs.writeFileSync(sourceFile, transcript().replace(`"id":"${SESSION}"`, '"id":"00000000-aaaa-4bbb-8ccc-dddddddddddd"')); + const result = await syncConversations(source, archive, { skipIndex: true, skipSummaries: true }); + expect(result.errors).toHaveLength(1); + expect(fs.existsSync(archiveFile)).toBe(false); + }); + + it.each(['all', 'session', 'incremental'] as const)('does not report %s indexing success after an admission rejection', async mode => { + policy(); + fs.writeFileSync(sourceFile, transcript().replace(`"id":"${SESSION}"`, '"id":"00000000-aaaa-4bbb-8ccc-dddddddddddd"')); + const run = mode === 'all' ? indexConversations(undefined, undefined, 1, true) + : mode === 'session' ? indexSession(SESSION, 1, true) : indexUnprocessed(1, true); + await expect(run).rejects.toThrow(/session identity mismatch/); + expect(fs.existsSync(archiveFile)).toBe(false); + }); + + it('fails closed when the policy itself is a symlink', async () => { + policy(); + const file = path.join(config, 'record-exclusions.json'); + fs.renameSync(file, path.join(root, 'policy.json')); + fs.symlinkSync(path.join(root, 'policy.json'), file); + await expect(syncConversations(source, archive, { skipIndex: true, skipSummaries: true })).rejects.toThrow(/record.*exclusion/i); + expect(fs.existsSync(archiveFile)).toBe(false); + }); + + it('rejects original/archive hardlink aliasing without modifying either input', async () => { + policy(); + fs.mkdirSync(path.dirname(archiveFile), { recursive: true }); + fs.linkSync(sourceFile, archiveFile); + const before = fs.readFileSync(sourceFile); + const result = await syncConversations(source, archive, { skipIndex: true, skipSummaries: true }); + expect(result.errors).toHaveLength(1); + expect(fs.readFileSync(sourceFile)).toEqual(before); + expect(fs.statSync(sourceFile).ino).toBe(fs.statSync(archiveFile).ino); + }); + + it('rejects a symlinked destination parent without creating an external archive', async () => { + policy(); + const outside = path.join(root, 'outside'); + fs.mkdirSync(outside); + fs.mkdirSync(archive); + fs.symlinkSync(outside, path.dirname(archiveFile)); + await expect(archiveAdmittedConversation(sourceFile, archiveFile)).rejects.toThrow(/archive parent.*symlink/i); + expect(fs.readdirSync(outside)).toEqual([]); + }); + + it('preserves nonmatching final newline bytes with an active empty policy', async () => { + policy({ version: 1, exchanges: [], tool_calls: [] }); + const bytes = transcript().replace(/\n$/, ''); + fs.writeFileSync(sourceFile, bytes); + await syncConversations(source, archive, { skipIndex: true, skipSummaries: true }); + expect(fs.readFileSync(archiveFile, 'utf8')).toBe(bytes); + }); + + it('refreshes an archive when the source grows but its mtime ties the archive', async () => { + expect(await archiveAdmittedConversation(sourceFile, archiveFile)).toBe(true); + const archiveTime = fs.statSync(archiveFile).mtime; + fs.appendFileSync(sourceFile, JSON.stringify(message('user', 'An appended safe exchange')) + '\n'); + fs.utimesSync(sourceFile, archiveTime, archiveTime); + + expect(await archiveAdmittedConversation(sourceFile, archiveFile)).toBe(true); + expect(fs.readFileSync(archiveFile)).toEqual(fs.readFileSync(sourceFile)); + }); + + it('rejects source mutation during streaming without publishing a derived file', async () => { + policy(); + const original = fs.createReadStream; + let changed = false; + vi.spyOn(fs, 'createReadStream').mockImplementation(((file: any, options: any) => { + if (file === sourceFile && !changed) { + changed = true; + fs.appendFileSync(sourceFile, JSON.stringify(message('user', 'later public content')) + '\n'); + } + return original(file, options); + }) as typeof fs.createReadStream); + await expect(archiveAdmittedConversation(sourceFile, archiveFile)).rejects.toThrow(/changed during record admission/); + expect(fs.existsSync(archiveFile)).toBe(false); + expect(fs.readdirSync(path.dirname(archiveFile))).toEqual([]); + }); + + it('does not copy unscanned bytes when an opt-out is appended without a record policy', async () => { + const original = fs.createReadStream; + const copy = fs.copyFileSync; + let changed = false; + const change = () => { + if (!changed) { + changed = true; + fs.appendFileSync(sourceFile, JSON.stringify(message('user', 'DO NOT INDEX THIS CHAT')) + '\n'); + } + }; + vi.spyOn(fs, 'createReadStream').mockImplementation(((file: any, options: any) => { + if (file === sourceFile) change(); + return original(file, options); + }) as typeof fs.createReadStream); + vi.spyOn(fs, 'copyFileSync').mockImplementation(((src: any, dest: any, flags: any) => { + if (src === sourceFile) change(); + return copy(src, dest, flags); + }) as typeof fs.copyFileSync); + await expect(archiveAdmittedConversation(sourceFile, archiveFile)).rejects.toThrow(/changed during record admission/); + expect(fs.existsSync(archiveFile)).toBe(false); + }); + + it('rejects a policy change during a derived copy rather than publishing stale admission', async () => { + policy({ version: 1, exchanges: [], tool_calls: [] }); + const original = fs.createReadStream; + let changed = false; + vi.spyOn(fs, 'createReadStream').mockImplementation(((file: any, options: any) => { + if (file === sourceFile && !changed) { changed = true; policy(); } + return original(file, options); + }) as typeof fs.createReadStream); + await expect(archiveAdmittedConversation(sourceFile, archiveFile)).rejects.toThrow(/policy changed during record admission/); + expect(fs.existsSync(archiveFile)).toBe(false); + expect(fs.readdirSync(path.dirname(archiveFile))).toEqual([]); + }); + + it('rejects a parser input replaced between marker scan and stream consumption', async () => { + policy(); + const original = fs.createReadStream; + let changed = false; + vi.spyOn(fs, 'createReadStream').mockImplementation(((file: any, options: any) => { + if (file === sourceFile && !changed) { + changed = true; + fs.renameSync(sourceFile, sourceFile + '.previous'); + fs.writeFileSync(sourceFile, transcript()); + } + return original(file, options); + }) as typeof fs.createReadStream); + await expect((async () => { + for await (const _line of admittedConversationLines(sourceFile)) { /* Consume to the verification boundary. */ } + })()).rejects.toThrow(/changed during record admission/); + }); + + it('does not let project-exclusion environment override record policy', async () => { + policy(); + process.env.CONVERSATION_SEARCH_EXCLUDE_PROJECTS = 'unrelated-project'; + await syncConversations(source, archive, { skipIndex: true, skipSummaries: true }); + expect(fs.readFileSync(archiveFile, 'utf8')).not.toContain(PRIVATE); + }); + + it.each(['all', 'session', 'incremental'] as const)('filters before the %s index archive copy', async mode => { + policy(); + if (mode === 'all') await indexConversations(undefined, undefined, 1, true); + if (mode === 'session') await indexSession(SESSION, 1, true); + if (mode === 'incremental') await indexUnprocessed(1, true); + expect(fs.readFileSync(archiveFile, 'utf8')).not.toContain(PRIVATE); + const db = initDatabase(); + try { + expect(db.prepare('SELECT count(*) AS n FROM exchanges').get()).toEqual({ n: 2 }); + expect(db.prepare('SELECT count(*) AS n FROM tool_calls').get()).toEqual({ n: 0 }); + } finally { db.close(); } + }); + + it('starts an ephemeral Codex text-only context rather than forking hidden history', async () => { + const fake = ` + const readline = require('readline'); + const lines = readline.createInterface({ input: process.stdin }); + const emit = value => process.stdout.write(JSON.stringify(value) + '\\n'); + lines.on('line', line => { + const m = JSON.parse(line); + if (m.method === 'initialize') emit({ id: m.id, result: {} }); + if (m.method === 'thread/fork') process.exit(9); + if (m.method === 'thread/start') { + if (!m.params.ephemeral || m.params.threadId) process.exit(10); + emit({ id: m.id, result: { thread: { id: 'synthetic-ephemeral' } } }); + } + if (m.method === 'turn/start') { + emit({ id: m.id, result: { turn: { id: 'synthetic-turn' } } }); + emit({ method: 'item/completed', params: { item: { type: 'agentMessage', text: 'Safe text only.' } } }); + emit({ method: 'turn/completed', params: { turn: { id: 'synthetic-turn', status: 'completed' } } }); + } + }); + `; + expect(await runCodexCommand({ command: process.execPath, args: ['-e', fake], + prompt: 'Only synthetic admitted content.', skipVersionCheck: true })).toContain('Safe text only.'); + }); + + it('does not resume a source session when a native record policy is active', async () => { + policy(); + vi.mocked(query).mockReturnValue({ + async *[Symbol.asyncIterator]() { + yield { type: 'result', is_error: false, result: 'Safe summary.' }; + }, + } as any); + const exchange: ConversationExchange = { id: 'safe-summary', project: 'fixture', + timestamp: '2026-01-01T00:00:00Z', userMessage: 'Explain the selected project architecture and its acceptance boundaries.', + assistantMessage: 'This is an ordinary safe answer that must remain available for summarization without any original hidden session context.', + archivePath: archiveFile, lineStart: 2, lineEnd: 3, sessionId: SESSION, harness: 'claude' }; + expect(await summarizeConversation([exchange], SESSION)).toBe('Safe summary.'); + const request = vi.mocked(query).mock.calls.at(-1)![0]; + expect(request.options.resume).toBeUndefined(); + expect(request.prompt).toContain(exchange.userMessage); + expect(request.prompt).not.toContain(PRIVATE); + }); + + it('does not route a policy-admitted Codex transcript without a session ID to Claude', async () => { + policy(); + process.env.EPISODIC_MEMORY_CODEX_BIN = path.join(root, 'missing-codex'); + vi.mocked(query).mockReset(); + vi.mocked(query).mockReturnValue({ + async *[Symbol.asyncIterator]() { + yield { type: 'result', is_error: false, result: 'Wrong provider.' }; + }, + } as any); + const exchange: ConversationExchange = { id: 'codex-no-id', project: 'fixture', + timestamp: '2026-01-01T00:00:00Z', userMessage: 'Explain the selected project architecture and its acceptance boundaries.', + assistantMessage: 'This admitted answer is long enough to require a summary but must never reach a different provider.', + archivePath: archiveFile, lineStart: 2, lineEnd: 3, harness: 'codex' }; + + await expect(summarizeConversation([exchange])).rejects.toThrow(); + expect(query).not.toHaveBeenCalled(); + }); + + it('refuses direct database reinsertion after native deletion across reopen', () => { + policy(); + const exchange: ConversationExchange = { id: 'denied-body', project: 'fixture', + timestamp: '2026-01-01T00:00:00Z', userMessage: PRIVATE, assistantMessage: PRIVATE, + archivePath: archiveFile, lineStart: 4, lineEnd: 5, sessionId: SESSION, harness: 'codex' }; + for (let n = 0; n < 2; n++) { + const db = initDatabase(); + try { + insertExchange(db, exchange, new Array(384).fill(0)); + expect(db.prepare('SELECT count(*) AS n FROM exchanges').get()).toEqual({ n: 0 }); + expect(db.prepare('SELECT count(*) AS n FROM vec_exchanges').get()).toEqual({ n: 0 }); + } finally { db.close(); } + } + }); + + it('does not trust an inherited ancestor ID over the transcript identity at direct insertion', () => { + policy(); + const db = initDatabase(); + try { + const exchange: ConversationExchange = { id: 'ancestor-metadata', project: 'fixture', + timestamp: '2026-01-01T00:00:00Z', userMessage: PRIVATE, assistantMessage: PRIVATE, + archivePath: archiveFile, lineStart: 4, lineEnd: 5, + sessionId: '00000000-aaaa-4bbb-8ccc-dddddddddddd', harness: 'codex' }; + insertExchange(db, exchange, new Array(384).fill(0)); + expect(db.prepare('SELECT count(*) AS n FROM exchanges').get()).toEqual({ n: 0 }); + expect(db.prepare('SELECT count(*) AS n FROM vec_exchanges').get()).toEqual({ n: 0 }); + } finally { db.close(); } + }); + + it('filters denied native tool IDs even when direct callers retain ancestor metadata', () => { + policy(); + const db = initDatabase(); + try { + const exchange: ConversationExchange = { id: 'ancestor-safe-body', project: 'fixture', + timestamp: '2026-01-01T00:00:00Z', userMessage: 'safe question', assistantMessage: 'safe answer', + archivePath: archiveFile, lineStart: 6, lineEnd: 9, + sessionId: '00000000-aaaa-4bbb-8ccc-dddddddddddd', harness: 'codex', + toolCalls: [{ id: 'call-denied', exchangeId: 'ancestor-safe-body', toolName: 'fixture', + toolInput: { value: PRIVATE }, toolResult: PRIVATE, isError: false, timestamp: '2026-01-01T00:00:00Z' }] }; + insertExchange(db, exchange, new Array(384).fill(0)); + expect(db.prepare('SELECT count(*) AS n FROM exchanges').get()).toEqual({ n: 1 }); + expect(db.prepare('SELECT count(*) AS n FROM tool_calls').get()).toEqual({ n: 0 }); + } finally { db.close(); } + }); + + it('retains a safe body but removes denied tool calls at database insertion', () => { + policy(); + const db = initDatabase(); + try { + const exchange: ConversationExchange = { id: 'safe-body', project: 'fixture', + timestamp: '2026-01-01T00:00:00Z', userMessage: 'safe question', assistantMessage: 'safe answer', + archivePath: archiveFile, lineStart: 6, lineEnd: 9, sessionId: SESSION, harness: 'codex', + toolCalls: [{ id: 'call-denied', exchangeId: 'safe-body', toolName: 'fixture', toolInput: { value: PRIVATE }, toolResult: PRIVATE, isError: false, timestamp: '2026-01-01T00:00:00Z' }] }; + insertExchange(db, exchange, new Array(384).fill(0)); + expect(db.prepare('SELECT count(*) AS n FROM exchanges').get()).toEqual({ n: 1 }); + expect(db.prepare('SELECT count(*) AS n FROM tool_calls').get()).toEqual({ n: 0 }); + expect(exchange.toolCalls).toHaveLength(1); + } finally { db.close(); } + }); +}); diff --git a/test/record-screening.test.ts b/test/record-screening.test.ts new file mode 100644 index 00000000..0640d95f --- /dev/null +++ b/test/record-screening.test.ts @@ -0,0 +1,311 @@ +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import crypto from 'node:crypto'; +import { spawnSync } from 'node:child_process'; +import { + admitExchange, + archiveAdmittedConversation, + readAdmittedConversation, + readRecordExclusions, +} from '../src/record-admission.js'; +import { initDatabase, insertExchange } from '../src/db.js'; +import { searchConversations } from '../src/search.js'; +import { verifyIndex } from '../src/verify.js'; +import type { ConversationExchange } from '../src/types.js'; + +const session = '00000000-1111-4222-8333-444444444444'; +// Deliberately public nonfunctional fixture, never a production credential. +const token = 'ghp_' + 'A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8'; +const scannerName = process.platform === 'win32' ? 'gitleaks.exe' : 'gitleaks'; +const realScannerPath = process.env.TEST_GITLEAKS_PATH || (process.env.PATH || '').split(path.delimiter) + .map(directory => path.join(directory, scannerName)).find(file => fs.existsSync(file)); +let scanner: string; +let digest: string; +let root: string; +let prior: NodeJS.ProcessEnv; +let source: string; +let destination: string; + +function policy(overrides: Record = {}): void { + fs.writeFileSync(path.join(root, 'record-exclusions.json'), JSON.stringify({ + version: 1, + exchanges: [], + tool_calls: [], + screening: { + engine: 'gitleaks', executable: scanner, sha256: digest, + timeout_ms: 10_000, max_record_bytes: 1_048_576, ...overrides, + }, + }), { mode: 0o600 }); +} + +function exchange(text = 'ordinary safe explanation'): ConversationExchange { + return { + id: 'fixture-exchange', project: 'fixture', timestamp: '2026-01-01T00:00:00Z', + userMessage: text, assistantMessage: 'safe answer', archivePath: source, + lineStart: 2, lineEnd: 3, sessionId: session, harness: 'codex', + }; +} + +function transcript(text: string): string { + return [ + { type: 'session_meta', payload: { id: session, cwd: '/synthetic' } }, + { type: 'response_item', payload: { type: 'message', role: 'user', + content: [{ type: 'input_text', text }] } }, + { type: 'response_item', payload: { type: 'message', role: 'assistant', + content: [{ type: 'output_text', text: 'safe answer' }] } }, + ].map(JSON.stringify).join('\n') + '\n'; +} + +function archivedTranscript(text: string, withSummary = false): string { + const archiveDir = path.join(root, 'archive'); + const projectDir = path.join(archiveDir, 'project'); + fs.mkdirSync(projectDir, { recursive: true }); + process.env.TEST_ARCHIVE_DIR = archiveDir; + const file = path.join(projectDir, path.basename(source)); + fs.writeFileSync(file, transcript(text)); + if (withSummary) fs.writeFileSync(file.replace('.jsonl', '-summary.txt'), 'Existing summary'); + return file; +} + +// Contract probes are executable and digest-pinned without requiring Gitleaks +// on every CI host. A separate optional integration test exercises Gitleaks. +function scannerProbe(body: string): string { + const file = path.join(root, 'scanner-probe'); + fs.writeFileSync(file, `#!${process.execPath}\n${body}\n`, { mode: 0o700 }); + policy({ executable: fs.realpathSync(file), + sha256: crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex') }); + return file; +} + +beforeEach(() => { + prior = { ...process.env }; + root = fs.mkdtempSync(path.join(os.tmpdir(), 'record-screening-test-')); + process.env.EPISODIC_MEMORY_CONFIG_DIR = root; + process.env.EPISODIC_MEMORY_DB_PATH = path.join(root, 'test.sqlite'); + scanner = path.join(root, 'scanner-fixture'); + fs.writeFileSync(scanner, `#!${process.execPath} +const fs = require('node:fs'); +const input = fs.readFileSync(0, 'utf8'); +const found = input.includes('ghp_') || /api_key\\s*=\\s*\\S{20}/i.test(input) || + (input.includes('Sourcegraph') && /[a-f0-9]{40}/i.test(input)); +process.stdout.write(found ? '[{"RuleID":"fixture"}]' : '[]'); +process.exit(found ? 10 : 0); +`, { mode: 0o700 }); + scanner = fs.realpathSync(scanner); + digest = crypto.createHash('sha256').update(fs.readFileSync(scanner)).digest('hex'); + source = path.join(root, `rollout-${session}.jsonl`); + destination = path.join(root, 'archive', path.basename(source)); + fs.writeFileSync(source, transcript('safe question')); + policy(); +}); + +afterEach(() => { + process.env = prior; + fs.rmSync(root, { recursive: true, force: true }); + expect(fs.existsSync(root)).toBe(false); +}); + +describe('pinned record-local screening at native admission', () => { + it('fails verification on an invalid record policy even with no archived files', async () => { + fs.writeFileSync(path.join(root, 'record-exclusions.json'), '{invalid'); + process.env.TEST_ARCHIVE_DIR = path.join(root, 'empty-archive'); + await expect(verifyIndex()).rejects.toThrow(/record exclusion policy/i); + }); + + it.each(['rejected', 'unavailable'] as const)('classifies %s screening independently from corruption and missing summary', async outcome => { + const file = archivedTranscript(token); + if (outcome === 'unavailable') policy({ sha256: '0'.repeat(64) }); + + const result = await verifyIndex(); + expect(result.missing.map(item => item.path)).toContain(file); + expect(result.corrupted).toEqual([]); + expect(result.screeningRejected.map(item => item.path)).toEqual(outcome === 'rejected' ? [file] : []); + expect(result.screeningUnavailable.map(item => item.path)).toEqual(outcome === 'unavailable' ? [file] : []); + }); + + it('does not report a green repair when configured screening rejects an archive', () => { + archivedTranscript(token, true); + const cli = path.join(import.meta.dirname, '..', 'dist', 'index-cli.js'); + const result = spawnSync(process.execPath, [cli, 'repair'], { + env: process.env, encoding: 'utf8', timeout: 30_000, + }); + expect(result.status).toBe(1); + expect(result.stdout).not.toContain('No issues to repair!'); + expect(result.stdout).not.toContain('Repair complete.'); + }); + + it.skipIf(!realScannerPath)('rejects a synthetic token with the actual Gitleaks executable', () => { + const executable = fs.realpathSync(realScannerPath!); + policy({ executable, sha256: crypto.createHash('sha256').update(fs.readFileSync(executable)).digest('hex') }); + expect(() => admitExchange(exchange(token))).toThrow(/content rejected by screening/i); + }); + + it('screens retained search content and summaries against the current pinned scanner', async () => { + const db = initDatabase(); + insertExchange(db, exchange(), new Array(384).fill(0)); + db.prepare('UPDATE exchanges SET assistant_message = ?').run(token); + db.close(); + await expect(searchConversations('ordinary', { mode: 'text' })).rejects.toThrow(/content rejected by screening/i); + const repaired = initDatabase(); + repaired.prepare('UPDATE exchanges SET assistant_message = ?').run('safe answer'); + repaired.close(); + fs.writeFileSync(source.replace('.jsonl', '-summary.txt'), token); + await expect(searchConversations('ordinary', { mode: 'text' })).rejects.toThrow(/content rejected by screening/i); + fs.unlinkSync(source.replace('.jsonl', '-summary.txt')); + policy({ sha256: '0'.repeat(64) }); + await expect(searchConversations('ordinary', { mode: 'text' })).rejects.toThrow(/screening unavailable/i); + }); + + it('accepts an explicitly configured scanner without changing exclusion identity', () => { + expect(readRecordExclusions()).toMatchObject({ version: 1, screening: { engine: 'gitleaks' } }); + expect(admitExchange(exchange())).toEqual(exchange()); + }); + + it('rejects a new synthetic token at direct database ingress without inserting rows', () => { + const db = initDatabase(); + try { + expect(() => insertExchange(db, exchange(token), new Array(384).fill(0))) + .toThrow(/content rejected by screening/i); + expect(db.prepare('SELECT count(*) AS n FROM exchanges').get()).toEqual({ n: 0 }); + expect(db.prepare('SELECT count(*) AS n FROM vec_exchanges').get()).toEqual({ n: 0 }); + } finally { db.close(); } + }); + + it('preserves original and previous archive when new content fails screening', async () => { + const original = transcript(token); + fs.writeFileSync(source, original); + fs.mkdirSync(path.dirname(destination)); + fs.writeFileSync(destination, 'previous admitted bytes\n'); + await expect(archiveAdmittedConversation(source, destination)).rejects.toThrow(/content rejected by screening/i); + expect(fs.readFileSync(source, 'utf8')).toBe(original); + expect(fs.readFileSync(destination, 'utf8')).toBe('previous admitted bytes\n'); + expect(fs.readdirSync(path.dirname(destination))).toEqual([path.basename(destination)]); + }); + + it('does not expose newly detected content through native read', async () => { + fs.writeFileSync(source, transcript(token)); + await expect(readAdmittedConversation(source)).rejects.toThrow(/content rejected by screening/i); + }); + + it('screens decoded JSON strings rather than trusting escaped token spelling', async () => { + fs.writeFileSync(source, transcript(token).replace('ghp_', '\\u0067hp_')); + await expect(archiveAdmittedConversation(source, destination)).rejects.toThrow(/content rejected by screening/i); + expect(fs.existsSync(destination)).toBe(false); + }); + + it('keeps separate user and assistant records from contaminating keyword screening', () => { + const x = exchange('Sourcegraph'); + x.assistantMessage = ['01234567', '89abcdef', '01234567', '89abcdef', '01234567'].join(''); + expect(admitExchange(x)).toEqual(x); + }); + + it('rejects newly detected tool payloads without exposing their values in errors', () => { + const x = exchange(); + x.toolCalls = [{ id: 'tool', exchangeId: x.id, toolName: 'fixture', + toolInput: { value: token }, toolResult: 'safe result', timestamp: x.timestamp, isError: false }]; + let message = ''; + try { admitExchange(x); } catch (error) { message = String(error); } + expect(message).toMatch(/content rejected by screening/i); + expect(message).not.toContain(token); + }); + + it('applies exact known exclusions before scanning so safe neighboring records survive', async () => { + const file = path.join(root, 'record-exclusions.json'); + const configured = JSON.parse(fs.readFileSync(file, 'utf8')); + configured.exchanges = [{ session_id: session, transcript: path.basename(source), line_start: 2, line_end: 2 }]; + fs.writeFileSync(file, JSON.stringify(configured)); + fs.writeFileSync(source, transcript(token)); + expect(await archiveAdmittedConversation(source, destination)).toBe(true); + const actual = fs.readFileSync(destination, 'utf8'); + expect(actual).not.toContain(token); + expect(actual.split('\n')[1]).toBe(''); + expect(actual).toContain('safe answer'); + }); + + it('fails closed when the scanner is unavailable or has the wrong digest', () => { + policy({ executable: path.join(root, 'missing') }); + expect(() => admitExchange(exchange())).toThrow(/screening unavailable/i); + policy({ sha256: '0'.repeat(64) }); + expect(() => admitExchange(exchange())).toThrow(/screening unavailable/i); + }); + + it('rejects oversized input before execution and never logs it', () => { + policy({ max_record_bytes: 32 }); + expect(() => admitExchange(exchange('x'.repeat(33)))).toThrow(/screening byte limit/i); + }); + + it('cannot disable configured screening through ambient scanner config or allow comments', () => { + process.env.GITLEAKS_CONFIG = '/nonexistent/ambient.toml'; + expect(() => admitExchange(exchange(token + ' # gitleaks:allow'))) + .toThrow(/content rejected by screening/i); + }); + + it('retains byte-identical safe archives and idempotent retry behavior', async () => { + const raw = fs.readFileSync(source); + expect(await archiveAdmittedConversation(source, destination)).toBe(true); + expect(fs.readFileSync(destination)).toEqual(raw); + expect(await archiveAdmittedConversation(source, destination)).toBe(false); + expect(fs.readFileSync(source)).toEqual(raw); + }); + + it('retains key/value assignment syntax required by generic credential rules', () => { + const text = JSON.stringify({ api_key: ['rP8v2Ls7', 'Qm4Zx9Nc', '6Tb3Wy5H', 'd1Kf0Aj8'].join('') }); + expect(() => admitExchange(exchange(text))).toThrow(/content rejected by screening/i); + }); + + it('does not bypass screening when session identity is absent', () => { + const x = exchange(token); + delete x.sessionId; + x.archivePath = path.join(root, 'anonymous.jsonl'); + expect(() => admitExchange(x)).toThrow(/content rejected by screening/i); + }); + + it.each([ + ['empty report', '', 0], + ['malformed report', '{', 0], + ['non-array report', '{}', 0], + ['contradictory clean exit', '[{"RuleID":"fixture"}]', 0], + ['empty detection', '[]', 10], + ['invalid detection', '[{}]', 10], + ['scanner failure', '[]', 2], + ])('rejects %s without exposing subprocess output', (_name, stdout, status) => { + scannerProbe(`process.stderr.write('private-probe-diagnostic'); + process.stdout.write(${JSON.stringify(stdout)}); process.exit(${status});`); + let message = ''; + try { admitExchange(exchange()); } catch (error) { message = String(error); } + expect(message).toMatch(/screening unavailable/i); + expect(message).not.toContain('private-probe-diagnostic'); + }); + + it('terminates a hung scanner within its bounded timeout', () => { + scannerProbe('setInterval(() => {}, 1000);'); + const file = path.join(root, 'record-exclusions.json'); + const configured = JSON.parse(fs.readFileSync(file, 'utf8')); + configured.screening.timeout_ms = 100; + fs.writeFileSync(file, JSON.stringify(configured)); + const start = performance.now(); + expect(() => admitExchange(exchange())).toThrow(/screening unavailable/i); + expect(performance.now() - start).toBeLessThan(3000); + }); + + it('rejects non-executable and symlink scanner identities', () => { + const file = scannerProbe("process.stdout.write('[]');"); + fs.chmodSync(file, 0o600); + expect(() => admitExchange(exchange())).toThrow(/screening unavailable/i); + fs.chmodSync(file, 0o700); + const alias = path.join(root, 'scanner-link'); + fs.symlinkSync(file, alias); + const configured = JSON.parse(fs.readFileSync(path.join(root, 'record-exclusions.json'), 'utf8')); + policy({ ...configured.screening, executable: alias }); + expect(() => admitExchange(exchange())).toThrow(/screening unavailable/i); + }); + + it('rejects scanner mutation during execution and cleans private work directories', () => { + const before = fs.readdirSync(os.tmpdir()).filter(name => name.startsWith('episodic-screen-')).sort(); + scannerProbe("require('node:fs').appendFileSync(__filename, '\\n// changed'); process.stdout.write('[]');"); + expect(() => admitExchange(exchange())).toThrow(/screening unavailable/i); + expect(fs.readdirSync(os.tmpdir()).filter(name => name.startsWith('episodic-screen-')).sort()).toEqual(before); + }); +}); diff --git a/test/search-admission.test.ts b/test/search-admission.test.ts new file mode 100644 index 00000000..f339c533 --- /dev/null +++ b/test/search-admission.test.ts @@ -0,0 +1,210 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { initDatabase, insertExchange } from '../src/db.js'; +import { searchConversations } from '../src/search.js'; +import { readAdmittedConversation, shouldSkipConversation } from '../src/record-admission.js'; + +vi.mock('../src/embeddings.js', () => ({ + initEmbeddings: vi.fn(async () => {}), + generateQueryEmbedding: vi.fn(async () => new Array(384).fill(0.1)), +})); +const marker = 'DO NOT INDEX THIS CHAT'; +const session = '00000000-1111-4222-8333-444444444444'; +const message = (role: string, text: string) => ({ type: 'response_item', payload: { + type: 'message', role, content: [{ type: 'input_text', text }], +} }); +let root: string; +let previous: NodeJS.ProcessEnv; +beforeEach(() => { + previous = { ...process.env }; + root = fs.mkdtempSync(path.join(os.tmpdir(), 'em-search-admission-')); + process.env.EPISODIC_MEMORY_CONFIG_DIR = root; + process.env.TEST_DB_PATH = path.join(root, 'test.db'); + delete process.env.EPISODIC_MEMORY_DB_PATH; +}); +afterEach(() => { vi.restoreAllMocks(); process.env = previous; fs.rmSync(root, { recursive: true, force: true }); }); +function seed(id: string, timestamp = '2026-01-01T00:00:00Z') { + const file = path.join(root, `${id}-${session}.jsonl`); + fs.writeFileSync(file, [ + { type: 'session_meta', payload: { id: session } }, + message('user', `needle ${id}`), message('assistant', 'safe answer'), + ].map(x => JSON.stringify(x)).join('\n') + '\n'); + const db = initDatabase(); + insertExchange(db, { id, project: 'fixture', timestamp, archivePath: file, + lineStart: 2, lineEnd: 3, userMessage: `needle ${id}`, assistantMessage: 'safe answer', sessionId: session, + }, new Array(384).fill(0.1)); + db.close(); return file; +} +function exclude(file: string, start = 2, end = 3) { + fs.writeFileSync(path.join(root, 'record-exclusions.json'), JSON.stringify({ version: 1, + exchanges: [{ session_id: session, transcript: path.basename(file), line_start: start, line_end: end }], tool_calls: [], + })); +} + +describe('decoded user instruction markers', () => { + it('retains physical line coordinates while loading only a requested range', async () => { + const file = path.join(root, 'range.jsonl'); + fs.writeFileSync(file, [message('user', 'outside'), message('assistant', 'selected \u2028 text'), message('user', 'outside after')].map(x => JSON.stringify(x)).join('\r\n')); + const result = await readAdmittedConversation(file, 2, 2); + expect(result.split('\n')).toHaveLength(3); + expect(result.split('\n')[0]).toBe(''); + expect(result.split('\n')[1]).toContain('selected'); + expect(result).not.toContain('outside'); + fs.appendFileSync(file, '\n' + JSON.stringify(message('user', marker))); + expect(await readAdmittedConversation(file, 2, 2)).toBe(''); + }); + + it.each([ + ['assistant quote', message('assistant', marker)], + ['tool output', { type: 'response_item', payload: { type: 'function_call_output', output: marker } }], + ['Claude tool result', { type: 'user', message: { role: 'user', content: [{ type: 'tool_result', content: marker }] } }], + ])('does not opt out for %s', async (_, record) => { + const file = path.join(root, 'quoted.jsonl'); + const bytes = JSON.stringify(record) + '\n'; fs.writeFileSync(file, bytes); + expect(shouldSkipConversation(file)).toBe(false); + expect(await readAdmittedConversation(file)).toContain(marker); + expect(fs.readFileSync(file, 'utf8')).toBe(bytes); + }); + it.each([ + ['Codex response', message('user', marker)], + ['Codex event', { type: 'event_msg', payload: { type: 'user_message', message: marker } }], + ['Claude', { type: 'user', message: { role: 'user', content: marker } }], + ['Cursor', { role: 'user', message: { content: marker } }], + ['OMP', { type: 'message', message: { role: 'user', content: [{ type: 'text', text: marker }] } }], + ['opencode', { type: 'opencode_message', message: { role: 'user' }, parts: [{ type: 'text', text: marker }] }], + ])('honors JSON-escaped opt out in %s', async (_, record) => { + const file = path.join(root, 'escaped.jsonl'); + fs.writeFileSync(file, JSON.stringify(record).replaceAll('<', '\\u003c')); + expect(shouldSkipConversation(file)).toBe(true); + expect(await readAdmittedConversation(file)).toBe(''); + }); +}); + +describe('retained search admission', () => { + it('orders admitted vectors by computed distance before applying the result limit', async () => { + seed('far'); seed('near'); seed('middle'); + const db = initDatabase(); + const update = db.prepare('UPDATE vec_exchanges SET embedding = ? WHERE id = ?'); + for (const [id, coordinate] of [['far', 0.8], ['near', 0.1], ['middle', 0.3]] as const) { + update.run(Buffer.from(new Float32Array(new Array(384).fill(coordinate)).buffer), id); + } + db.close(); + + const results = await searchConversations('needle', { mode: 'vector', limit: 2 }); + expect(results.map(result => result.exchange.id)).toEqual(['near', 'middle']); + expect(results[0].similarity).toBeGreaterThan(results[1].similarity!); + }); + + it('applies the sidechain penalty to computed distance rather than storage order', async () => { + seed('sidechain'); seed('primary'); + const db = initDatabase(); + db.prepare('UPDATE exchanges SET is_sidechain = 1 WHERE id = ?').run('sidechain'); + db.close(); + + const results = await searchConversations('needle', { mode: 'vector', limit: 2 }); + expect(results.map(result => result.exchange.id)).toEqual(['primary', 'sidechain']); + expect(results[0].similarity).toBeCloseTo(results[1].similarity!); + }); + + it('finds an admitted vector beyond the native KNN candidate ceiling', async () => { + const file = seed('denied'); seed('safe'); + const db = initDatabase(); + db.transaction(() => { + for (let i = 0; i < 4096; i++) { + insertExchange(db, { id: `denied-${i}`, project: 'fixture', timestamp: '2026-01-01T00:00:00Z', + archivePath: file, lineStart: 2, lineEnd: 3, userMessage: 'needle denied', assistantMessage: 'safe', sessionId: session, + }, new Array(384).fill(0.1)); + } + db.prepare('UPDATE vec_exchanges SET embedding = ? WHERE id = ?') + .run(Buffer.from(new Float32Array(new Array(384).fill(0.2)).buffer), 'safe'); + })(); + db.close(); exclude(file); + expect((await searchConversations('needle', { mode: 'vector', limit: 1 })).map(x => x.exchange.id)).toEqual(['safe']); + }); + + it('never follows a substituted transcript symlink', async () => { + const file = seed('safe'); + fs.renameSync(file, file + '.original'); + fs.symlinkSync(file + '.original', file); + await expect(searchConversations('needle', { mode: 'text' })).rejects.toThrow(/changed during record admission/); + }); + it('keeps an indexed exchange searchable while its transcript grows during admission', async () => { + const file = seed('safe'); + const original = fs.readSync; + let appended = false; + vi.spyOn(fs, 'readSync').mockImplementation(((...args: Parameters) => { + const count = original(...args); + if (!appended && count > 0) { + appended = true; + fs.appendFileSync(file, JSON.stringify({ type: 'event_msg', payload: { type: 'agent_message', message: 'later' } }) + '\n'); + } + return count; + }) as typeof fs.readSync); + + const results = await searchConversations('needle', { mode: 'text', before: '2026-09-14' }); + expect(appended).toBe(true); + expect(results.map(result => result.exchange.id)).toEqual(['safe']); + }); + it('rejects an indexed-range rewrite even when the transcript also grows', async () => { + const file = seed('safe'); + const summary = file.replace('.jsonl', '-summary.txt'); + fs.writeFileSync(summary, 'summary'); + const offset = fs.readFileSync(file).indexOf('needle safe'); + expect(offset).toBeGreaterThanOrEqual(0); + const original = fs.readFileSync; + let changed = false; + vi.spyOn(fs, 'readFileSync').mockImplementation(((p: any, ...args: any[]) => { + if (p === summary && !changed) { + changed = true; + fs.appendFileSync(file, JSON.stringify({ type: 'event_msg', payload: { type: 'agent_message', message: 'later' } }) + '\n'); + const fd = fs.openSync(file, 'r+'); + try { fs.writeSync(fd, Buffer.from('secret safe'), 0, 11, offset); } + finally { fs.closeSync(fd); } + } + return (original as any)(p, ...args); + }) as typeof fs.readFileSync); + + await expect(searchConversations('needle', { mode: 'text' })).rejects.toThrow(/changed during record admission/); + expect(changed).toBe(true); + }); + it('rejects policy changes while building results', async () => { + const file = seed('safe'); + const summary = file.replace('.jsonl', '-summary.txt'); fs.writeFileSync(summary, 'summary'); + const original = fs.readFileSync; + vi.spyOn(fs, 'readFileSync').mockImplementation(((p: any, ...args: any[]) => { + if (p === summary) exclude(file); + return (original as any)(p, ...args); + }) as typeof fs.readFileSync); + await expect(searchConversations('needle', { mode: 'text' })).rejects.toThrow(/policy changed/); + }); + + it.each(['text', 'vector', 'both'] as const)('applies current exclusions before limit in %s mode', async mode => { + seed('safe'); const denied = seed('denied', '2026-02-01T00:00:00Z'); + exclude(denied); + const results = await searchConversations('needle', { mode, limit: 1 }); + expect(results.map(x => x.exchange.id)).toEqual(['safe']); + expect(await readAdmittedConversation(denied)).not.toContain('needle denied'); + }); + it('rechecks opt-out appended after indexing, but not quoted tool output', async () => { + const file = seed('later'); + fs.appendFileSync(file, JSON.stringify({ type: 'response_item', payload: { type: 'function_call_output', output: marker } }) + '\n'); + expect(await searchConversations('needle', { mode: 'text' })).toHaveLength(1); + fs.appendFileSync(file, JSON.stringify(message('user', marker)).replaceAll('<', '\\u003c') + '\n'); + expect(await searchConversations('needle', { mode: 'text' })).toEqual([]); + }); + it('omits a stale whole-conversation summary when any source record is excluded', async () => { + const file = seed('safe'); + fs.appendFileSync(file, JSON.stringify(message('user', 'excluded summary phrase')) + '\n'); + fs.writeFileSync(file.replace('.jsonl', '-summary.txt'), 'excluded summary phrase'); + exclude(file, 4, 4); + const results = await searchConversations('needle', { mode: 'text' }); + expect(results).toHaveLength(1); + expect(JSON.stringify(results)).not.toContain('excluded summary phrase'); + }); + it('fails closed on invalid exclusion policy', async () => { + seed('safe'); fs.writeFileSync(path.join(root, 'record-exclusions.json'), '{invalid'); + await expect(searchConversations('needle', { mode: 'text' })).rejects.toThrow(/exclusion policy/i); + }); +}); diff --git a/test/setup-isolated-config.ts b/test/setup-isolated-config.ts index 71b3e795..2b298c07 100644 --- a/test/setup-isolated-config.ts +++ b/test/setup-isolated-config.ts @@ -13,6 +13,7 @@ const isolatedRoot = mkdtempSync(join(tmpdir(), 'episodic-memory-vitest-')); process.env.EPISODIC_MEMORY_CONFIG_DIR = join(isolatedRoot, 'superpowers'); process.env.CLAUDE_CONFIG_DIR = join(isolatedRoot, 'claude'); process.env.CODEX_HOME = join(isolatedRoot, 'codex'); +process.env.OMP_HOME = join(isolatedRoot, 'omp'); // Clear any ambient summarizer-billing signals so tests get deterministic // cost-guard (#104) and timeout (#160) behavior regardless of the developer's diff --git a/test/summarizer-options.test.ts b/test/summarizer-options.test.ts index 1267b880..b7260a0e 100644 --- a/test/summarizer-options.test.ts +++ b/test/summarizer-options.test.ts @@ -110,6 +110,22 @@ describe('isResumeFailure', () => { }); describe('buildCodexSummarizerCommand', () => { + it('disables host capabilities before an isolated summary server can load them', () => { + const command = buildCodexSummarizerCommand({ prompt: 'Admitted text.', isolated: true }); + expect(command.isolated).toBe(true); + for (const option of ['features.shell_tool=false', 'features.plugins=false', 'features.hooks=false', + 'features.apps=false', 'features.code_mode_host=false', 'features.skip_host_skill_discovery=true', + 'skills.bundled.enabled=false', 'skills.include_instructions=false', 'project_doc_max_bytes=0', + 'tools.experimental_request_user_input.enabled=false', 'web_search="disabled"']) { + expect(command.args).toContain(option); + } + }); + + it('refuses to combine isolated admitted text with an original session identity', () => { + expect(() => buildCodexSummarizerCommand({ prompt: 'Admitted text.', isolated: true, sessionId: 'original' })) + .toThrow(/isolated.*session/i); + }); + it('starts the Codex app-server so the summarizer can fork ephemerally', () => { const command = buildCodexSummarizerCommand({ sessionId: '019e4c75-d5bf-7c71-9df7-77f5fb86b711', @@ -129,6 +145,55 @@ describe('buildCodexSummarizerCommand', () => { }); describe('runCodexCommand', () => { + it('disables inherited MCP and skill entries per subject and removes its temporary cwd', async () => { + const fake = ` + const fs = require('fs'); + const readline = require('readline'); + const emit = value => console.log(JSON.stringify(value)); + readline.createInterface({ input: process.stdin }).on('line', line => { + const m = JSON.parse(line); + if (m.method === 'initialize') emit({ id: m.id, result: {} }); + if (m.method === 'config/read') emit({ id: m.id, result: { config: { mcp_servers: { inherited: { command: 'false', enabled: true } } } } }); + if (m.method === 'skills/list') emit({ id: m.id, result: { data: [{ skills: [{ path: '/public-fixture/SKILL.md', enabled: true }], errors: [] }] } }); + if (m.method === 'thread/fork') process.exit(8); + if (m.method === 'thread/start') { + const p = m.params; + if (p.config?.mcp_servers?.inherited?.enabled !== false) process.exit(9); + if (p.config?.['skills.config']?.[0]?.enabled !== false) process.exit(10); + if ([p.dynamicTools, p.environments, p.selectedCapabilityRoots].some(x => !Array.isArray(x) || x.length)) process.exit(11); + if (p.cwd !== process.cwd() || !fs.existsSync(p.cwd)) process.exit(12); + if (!p.ephemeral || p.allowProviderModelFallback !== false) process.exit(13); + emit({ id: m.id, result: { thread: { id: 'isolated' } } }); + } + if (m.method === 'turn/start') { + emit({ id: m.id, result: { turn: { id: 'turn' } } }); + emit({ method: 'item/completed', params: { item: { type: 'agentMessage', text: process.cwd() } } }); + emit({ method: 'turn/completed', params: { turn: { id: 'turn', status: 'completed' } } }); + } + }); + `; + const result = await runCodexCommand({ command: process.execPath, args: ['-e', fake], + prompt: 'Admitted public fixture.', isolated: true, skipVersionCheck: true }); + expect(result).toMatch(/episodic-memory-summary-/); + const { existsSync } = await import('fs'); + expect(existsSync(result)).toBe(false); + }); + + it('does not echo a provider error payload from an isolated summary', async () => { + const fake = ` + const readline = require('readline'); + readline.createInterface({ input: process.stdin }).on('line', line => { + const m = JSON.parse(line); + console.log(JSON.stringify(m.method === 'initialize' + ? { id: m.id, result: {} } + : { id: m.id, error: { message: 'EXCLUDED_PUBLIC_FIXTURE' } })); + }); + `; + await expect(runCodexCommand({ command: process.execPath, args: ['-e', fake], + prompt: 'Admitted text.', isolated: true, skipVersionCheck: true })) + .rejects.toThrow(/^Isolated Codex summary failed; provider details suppressed$/); + }); + it('forks the session ephemerally and returns the completed agent message', async () => { const fakeAppServer = ` const readline = require('readline'); diff --git a/test/sync-cli-single-instance.test.ts b/test/sync-cli-single-instance.test.ts index b1505559..c43e8421 100644 --- a/test/sync-cli-single-instance.test.ts +++ b/test/sync-cli-single-instance.test.ts @@ -103,6 +103,24 @@ describe('sync-cli single-instance lock (#97)', () => { expect(result.stdout).toMatch(/Sync complete/); }); + it('exits nonzero without a success claim when record admission rejects a source', () => { + const session = '00000000-0000-0000-0000-000000000002'; + const transcript = `rollout-${session}.jsonl`; + writeFileSync(join(testDir, 'projects', 'project-a', transcript), [ + { type: 'session_meta', payload: { id: '00000000-aaaa-4bbb-8ccc-dddddddddddd' } }, + { type: 'response_item', payload: { type: 'message', role: 'user', content: [{ type: 'text', text: 'public fixture' }] } }, + ].map(row => JSON.stringify(row)).join('\n') + '\n'); + writeFileSync(join(testDir, 'config', 'record-exclusions.json'), JSON.stringify({ + version: 1, exchanges: [{ session_id: session, transcript, line_start: 2, line_end: 2 }], tool_calls: [], + })); + const result = runWith({ ...envOverrides, EPISODIC_MEMORY_SKIP_SUMMARIES: '1' }); + expect(result.stdout + result.stderr).toMatch(/session identity mismatch/); + expect(result.status).toBe(1); + expect(result.stdout).not.toMatch(/Sync complete/); + expect(existsSync(join(testDir, 'archive', 'project-a', transcript))).toBe(false); + expect(existsSync(join(testDir, 'config', 'logs', 'episodic-memory-sync.lock'))).toBe(false); + }); + it('releases the lock on normal exit — a subsequent run is not skipped', () => { const first = runWith(envOverrides); expect(first.status).toBe(0); diff --git a/test/sync.test.ts b/test/sync.test.ts index f35f9a00..551fd7b9 100644 --- a/test/sync.test.ts +++ b/test/sync.test.ts @@ -240,10 +240,12 @@ describe('sync command', () => { // Sync with indexing enabled const result = await syncConversations(sourceDir, destDir); - // Both files should be copied - expect(result.copied).toBe(2); + // Whole-conversation opt-out now rejects copying as well as indexing. + expect(result.copied).toBe(1); + expect(existsSync(join(destDir, 'project-a', 'marked.jsonl'))).toBe(false); + expect(existsSync(join(destDir, 'project-a', 'normal.jsonl'))).toBe(true); - // But only normal conversation should be indexed + // Only the normal conversation should be indexed expect(result.indexed).toBe(1); // Verify in database diff --git a/test/verify.test.ts b/test/verify.test.ts index ae70da81..fa47e457 100644 --- a/test/verify.test.ts +++ b/test/verify.test.ts @@ -281,6 +281,13 @@ describe('repairIndex', () => { dbAfter.close(); }); + it('rejects a failed repair instead of reporting completion', async () => { + const input = path.join(archiveDir, 'not-a-transcript'); + fs.mkdirSync(input, { recursive: true }); + await expect(repairIndex({ missing: [{ path: input, reason: 'synthetic invalid source' }], + outdated: [], orphaned: [], screeningRejected: [], screeningUnavailable: [], corrupted: [] })).rejects.toThrow(/regular non-symlink file/); + }); + it('re-indexes outdated files during repair', { timeout: 30000 }, async () => { // Create conversation file with summary const projectArchive = path.join(archiveDir, 'test-project');