AI Agent Memory: Handling Duplicates, Outdated Decisions, and Project Context
Handle duplicate memories, outdated decisions and project context across AI agents, with a compiled, linted and integration-tested TypeScript example.
To make AI agent memory useful across sessions and tools, compare information before saving it, give each record an explicit scope, preserve changes and sources, and retrieve a small set of relevant current records. Keep uncertain statements out of ordinary retrieval until someone has reviewed them.
If you use several agents to build a product, run a small business, or work with a small team, these details become practical very quickly. The next agent needs enough context to continue the work. It also needs a way to recognize that yesterday's decision may have changed.
We recently revisited these problems in our own development work. One discovery was particularly entertaining: give an AI a memory, and it develops some remarkably human habits. It repeats itself, clings to outdated opinions, and says the right thing in entirely the wrong context.
We used to worry about it forgetting. Now it remembers, and we get to deal with the consequences.
Below are five problems worth checking in your own workflow, followed by a runnable TypeScript example. All names, records, and conversations in the example are fictional. The implementation is a standalone teaching exercise.
1. Duplicate memories turn repetition into apparent agreement
Imagine telling one agent, "Use short paragraphs." Later, you tell another, "Keep paragraphs brief."
A system that saves both statements independently has accumulated two records for one preference. Retrieve both, and the repetition can look like extra support for the same conclusion.
Rather like a meeting: repeat an opinion five times, and it begins to acquire the dignity of consensus.
The useful question at write time is what relationship the incoming statement has to the existing record:
- Add: a genuinely new topic in this scope.
- Confirm: another observation of the same meaning.
- Supplement: additional information that belongs in the current record.
- Replace: a new conclusion that supersedes the current one.
- Review: an unresolved statement that should wait for a decision.
In the example, confirmation keeps one current record and attaches the new wording and source. Repeated submission of the exact same observation does not increase its observation count.
That count describes recorded observations. Two agents repeating the same user's statement do not become two independent witnesses. We never use the count as a truth score.
The distinction between confirmation and change requires interpretation. A person or the host agent's model supplies that judgment. The example does not infer semantic equivalence with a string comparison, and it does not call a model API.
2. Outdated decisions keep speaking for your former self
Yesterday, you wanted concise notes. Today, you decide that a particular project needs more detailed explanations. An agent retrieves the older preference and follows it with admirable loyalty.
A slightly exhausting form of loyalty.
Changing a decision should create a clear current version while preserving what came before. In our example, supplementation and replacement both create a new record, mark the previous record as superseded, and link the two. Supplementation supplies the complete revised text; replacement uses the incoming text.
Ordinary retrieval returns active records. Explicit history inspection can still show the previous versions. An unresolved suggestion is stored as pending, leaving the existing decision active.
This makes a useful distinction visible: "someone mentioned this" and "this is the current working conclusion" have different consequences.
The example keeps logical version order, rather than dates when a statement became true. Applications that need schedules, expiration, or retroactive corrections will also need explicit time semantics.
3. Project rules escape their original context
A preference for one project quietly becomes a rule for every project. A temporary instruction gradually becomes "the user always wants this."
The memory system develops a certain managerial ambition: hear something once, turn it into permanent policy.
Give records an explicit scope. The example uses three:
- Personal: a preference belonging to one user across projects.
- Project: an agreement for one user's particular project.
- Task: a temporary constraint within that project and task.
Comparison before writing uses the exact scope and topic. Identical text in two projects can therefore remain two independent records. Similarity is not permission to merge across boundaries.
Retrieval includes the user's personal records, records for the selected project, and records for the selected task when one is provided. Other users and other projects are excluded. More specific records appear first.
Ordering alone does not settle a conflict between a personal preference and a project exception. Both can be returned; the reader must interpret the scope. A larger application can define an explicit precedence policy.
One important boundary: this example assumes a trusted caller provides the user and project identifiers. In a shared service, derive identity from the authenticated session and check project access on the server. A filter containing a user ID is not an authorization system.
4. Agents share conclusions while losing their origins
An agent picks up a sentence from shared memory. Which conversation produced it? Was it a direct user request, an inference, or a tentative suggestion? Has it changed since then?
Without that context, a shared notebook can grow increasingly authoritative while nobody quite remembers who wrote it.
The example records an agent label, a source reference, and the observed wording. Revised records carry their earlier observations forward, while the original versions remain in history. Those observations describe lineage; they do not prove that every earlier statement supports the latest wording.
Another agent can use those references to ask a better question or inspect the original material, if it has access. A source reference itself does not grant access or verify the source.
For a small personal workflow, a project note containing the current decision, the previous decision, and a source link may already be enough. A separate memory service becomes more useful when several tools need consistent access and updates. It also brings maintenance work, so start with the simplest arrangement that addresses the failure you actually have.
5. Retrieval fills the conversation with everything except room to think
Putting every possibly relevant memory into the prompt looks conscientious. Useful decisions and ancient trivia then arrive together, leaving the agent to sort through the pile again.
The example makes retrieval deliberately explicit: request particular topics within a user, project, and optional task; return active records; apply both a record limit and a character budget.
The topic keys are supplied by the caller. There is no semantic search here. A larger collection may need a search index or a model to identify relevant topics, followed by the same scope and status checks.
The character budget counts the serialized JSON array, including metadata and observations. Oversized records are skipped so a smaller relevant record can still fit. An empty result can mean that nothing fits; it is not proof that no memory exists. Character counts are a convenient demonstration budget. A deployed model integration should measure its actual token budget and leave room for the task and response.
LangChain's memory overview also distinguishes conversation-scoped state from memory shared across sessions, and describes namespaces for organizing long-term records. Here, we concentrate on the behavior of a small explicit record store.
A minimal TypeScript implementation you can run
The example directory contains:
example/
package.json
package-lock.json
tsconfig.json
src/memory.ts
src/walkthrough.ts
src/protocol.ts
src/session.ts
eslint.config.mjs
tools/verify.mjs
demo.mjs
test/memory.test.mjs
test/integration.test.mjs
MemoryStore exposes four operations:
| Operation | Purpose |
|---|---|
prepare(candidate) |
Return the current related records for the exact scope and topic. |
commit(prepared, decision) |
Validate and apply a supplied interpretation of the candidate. |
history(scope, topic) |
Inspect all versions and pending records for that exact topic. |
retrieve(request) |
Select visible active records within the requested topics and budgets. |
The write decision is a TypeScript discriminated union, so each action carries the fields it needs:
export type Decision =
| { kind: "add" }
| { kind: "confirm"; targetId: string }
| { kind: "supplement"; targetId: string; mergedText: string }
| { kind: "replace"; targetId: string }
| { kind: "review" };
The TypeScript narrowing documentation explains how a common literal field lets the compiler distinguish the cases. Runtime checks in the store also reject missing active targets, incompatible scopes, empty text, and invalid budgets. The local session adapter adds Zod schemas to validate JSON requests before they reach the store.
Here is the complete src/walkthrough.ts. It uses one fictional writing project to exercise all five write decisions:
import { MemoryStore, type Candidate } from "./memory.js";
export function walkthrough() {
const store = new MemoryStore();
const draft = (text: string, ref: string): Candidate => ({
scope: { kind: "project", userId: "reader", projectId: "field-notes" },
topic: "writing.style",
text,
source: { agent: "editor", ref },
});
const first = store.commit(
store.prepare(draft("Use short paragraphs.", "conversation-1")),
{ kind: "add" },
);
// These judgments are supplied by a person or the host agent's model.
// The store does not infer that two sentences mean the same thing.
store.commit(
store.prepare(draft("Keep paragraphs brief.", "conversation-2")),
{ kind: "confirm", targetId: first.id },
);
const fuller = store.commit(
store.prepare(draft("Explain unfamiliar terms.", "conversation-3")),
{
kind: "supplement", targetId: first.id,
mergedText: "Use short paragraphs. Explain unfamiliar terms.",
},
);
store.commit(
store.prepare(draft("Use longer paragraphs. Explain unfamiliar terms.", "conversation-4")),
{ kind: "replace", targetId: fuller.id },
);
store.commit(
store.prepare(draft("Maybe use bullet points throughout?", "conversation-5")),
{ kind: "review" },
);
const current = store.retrieve({
userId: "reader", projectId: "field-notes",
topics: ["writing.style"], limit: 3, maxChars: 2000,
});
const history = store.history(first.scope, "writing.style");
return { current, history };
}
The complete source, lockfile, and both test suites appear in the appendix at the end of this article. The tests exercise the compiled session process through serialized requests and replies.
From the example directory, with Node.js 22.13 or newer and npm installed:
npm ci
npm run verify
The verification command compiles the code, runs lint, executes both test groups, and runs the demo. It writes the actual commands, versions, source hashes, exit codes, and tool output into evidence/. You can also run npm run demo on its own. The demo prints:
Current: [ 'Use longer paragraphs. Explain unfamiliar terms.' ]
Current observations: 4
History: [ 'm1: superseded', 'm2: superseded', 'm3: active', 'm4: pending' ]
The two initial phrasings produced one record. The supplementation and replacement preserved older versions. The uncertain suggestion stayed out of the current result.
What happens if another agent writes during comparison?
A model may take time to compare a candidate with existing records. Something else can change in the meantime.
Each preparation captures a store revision. Any successful write, including confirmation, changes that revision. A commit based on an older revision is rejected, requiring a fresh preparation and another comparison. Preparations are single-use and belong to the store that created them.
This is intentionally coarse: even a write to an unrelated topic invalidates the preparation. It is a small, conservative way to expose stale comparisons in one process. A database-backed service needs durable version checks and a transaction around validation and writing. Separate instances of this in-memory class do not coordinate.
What the process integration test actually does
The test starts the compiled dist/session.js as a real Node process. Two logical agent clients send JSON requests over its stdin and read JSON replies from stdout. The store, request validation, transport, and compiled modules all run together; none of those components is replaced with a mock.
One scenario makes the interleaving explicit:
- Agent A prepares a replacement while the current record has one observation.
- Agent B prepares and commits a confirmation of that record.
- Agent A tries to commit its earlier comparison.
- The service rejects that stale commit. Agent A prepares again, sees both observations, and then applies the replacement.
The service returned this error fragment during the test:
{"code":"conflict","message":"Stale preparation: prepare again"}
The actual response is captured in the integration test output. The test then checks that the new current record retains all three observations and that the original version is still in history. It also verifies that replaying a consumed preparation token is rejected.
That is the practical problem behind the version check: while one agent is thinking, another agent can change what it was thinking about. Putting them in the same conversation would at least let them interrupt each other. A shared store needs a more explicit arrangement.
Other integration scenarios check scope filtering, malformed JSON, invalid request shapes, rejected cross-scope updates, recovery after errors, serialized snapshots, and retrieval budgets. These are two clients sharing one local service process; they do not simulate a distributed database or evaluate a model's judgment.
What was actually verified?
The final local run passed TypeScript compilation, ESLint with zero errors and zero warnings, 25 behavioral tests, and 6 process integration tests. The verification summary below records the environment and results. Running the included verifier creates example/evidence/, with a machine-readable summary and captured tool output.
The compiler uses strict checking and refuses to emit on an error. Lint includes type-aware TypeScript rules and checks the source, test files, verification runner, and configuration. Tests use Node's built-in test runner against emitted JavaScript. A lint check or build failure stops the verification command before it can report a successful test run.
These tests verify the store's handling of supplied decisions. They do not measure a model's accuracy at deciding whether two statements agree. The walkthrough uses fixed, reviewable judgments and needs no API key or network access at runtime.
What would you add for a shared service?
The example's data disappears when the process exits. Topic names are exact caller-supplied keys, with one active record per exact scope and topic. There is no account system, automatic classification, semantic search, or review interface; pending records are inspectable through history but have no resolution workflow here. The JSON adapter validates request shapes and manages preparation tokens; it does not authenticate local callers.
For a shared service, the next pieces include persistent storage, authenticated authorization, transactional writes, safe retries, deletion and retention rules, and a way for people to resolve disagreements. External source material must remain data when it enters a prompt; retrieving a sentence does not give it authority to override the current task.
You can evaluate those additions against your own workflow. Begin with an old decision, a revised decision, two projects, and two agents. Check what the second agent actually receives. That small exercise is often more informative than adding another page to an architecture diagram.
Let the user change their mind
The purpose of shared agent memory is to help work continue with less reconstruction. That requires remembering the boundaries around a statement: where it applies, how it changed, and where it came from.
After working through these cases, I find myself pausing whenever I hear, "The better AI knows you, the better."
A system that remembers what I said yesterday is useful.
Ideally, it should also let me say today: I've changed my mind.
Complete runnable source
Save each block below using its heading as the relative file path. Keep article.en.md
beside the example directory: it is a small test fixture containing the walkthrough
and output printed above. It is included here so the consistency tests need no external
download or copy of the full article. The implementation and all 31 tests are included.
The lockfile is long, but it pins the dependency tree used by npm ci.
agent-memory/
article.en.md
example/
package.json
package-lock.json
tsconfig.json
eslint.config.mjs
.gitignore
README.md
src/memory.ts
src/walkthrough.ts
src/protocol.ts
src/session.ts
demo.mjs
test/memory.test.mjs
test/integration.test.mjs
tools/verify.mjs
After saving the files:
cd agent-memory/example
npm ci
npm run verify
article.en.md
# Article excerpts used by the consistency tests
These are the TypeScript blocks and demo output printed in the article above.
```ts
export type Decision =
| { kind: "add" }
| { kind: "confirm"; targetId: string }
| { kind: "supplement"; targetId: string; mergedText: string }
| { kind: "replace"; targetId: string }
| { kind: "review" };
```
```ts
import { MemoryStore, type Candidate } from "./memory.js";
export function walkthrough() {
const store = new MemoryStore();
const draft = (text: string, ref: string): Candidate => ({
scope: { kind: "project", userId: "reader", projectId: "field-notes" },
topic: "writing.style",
text,
source: { agent: "editor", ref },
});
const first = store.commit(
store.prepare(draft("Use short paragraphs.", "conversation-1")),
{ kind: "add" },
);
// These judgments are supplied by a person or the host agent's model.
// The store does not infer that two sentences mean the same thing.
store.commit(
store.prepare(draft("Keep paragraphs brief.", "conversation-2")),
{ kind: "confirm", targetId: first.id },
);
const fuller = store.commit(
store.prepare(draft("Explain unfamiliar terms.", "conversation-3")),
{
kind: "supplement", targetId: first.id,
mergedText: "Use short paragraphs. Explain unfamiliar terms.",
},
);
store.commit(
store.prepare(draft("Use longer paragraphs. Explain unfamiliar terms.", "conversation-4")),
{ kind: "replace", targetId: fuller.id },
);
store.commit(
store.prepare(draft("Maybe use bullet points throughout?", "conversation-5")),
{ kind: "review" },
);
const current = store.retrieve({
userId: "reader", projectId: "field-notes",
topics: ["writing.style"], limit: 3, maxChars: 2000,
});
const history = store.history(first.scope, "writing.style");
return { current, history };
}
```
```text
Current: [ 'Use longer paragraphs. Explain unfamiliar terms.' ]
Current observations: 4
History: [ 'm1: superseded', 'm2: superseded', 'm3: active', 'm4: pending' ]
```
example/package.json
{
"name": "agent-memory-example",
"version": "0.0.0",
"private": true,
"type": "module",
"engines": { "node": ">=22.13.0" },
"scripts": {
"build": "tsc -p tsconfig.json",
"lint": "eslint . --max-warnings 0",
"test": "npm run build && node --test test/*.test.mjs",
"test:unit": "npm run build && node --test test/memory.test.mjs",
"test:integration": "npm run build && node --test test/integration.test.mjs",
"verify": "node tools/verify.mjs",
"demo": "npm run build && node demo.mjs",
"session": "npm run build && node dist/session.js"
},
"dependencies": { "zod": "4.6.7" },
"devDependencies": {
"@eslint/js": "10.0.1",
"@types/node": "24.10.1",
"eslint": "10.13.0",
"typescript": "6.0.3",
"typescript-eslint": "8.71.2"
}
}
example/package-lock.json
{
"name": "agent-memory-example",
"version": "0.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "agent-memory-example",
"version": "0.0.0",
"dependencies": {
"zod": "4.6.7"
},
"devDependencies": {
"@eslint/js": "10.0.1",
"@types/node": "24.10.1",
"eslint": "10.13.0",
"typescript": "6.0.3",
"typescript-eslint": "8.71.2"
},
"engines": {
"node": ">=22.13.0"
}
},
"node_modules/@cacheable/memory": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.2.0.tgz",
"integrity": "sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@cacheable/utils": "^2.5.0",
"@keyv/bigmap": "^1.3.1",
"hookified": "^1.15.1",
"keyv": "^5.6.0"
}
},
"node_modules/@cacheable/utils": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.5.0.tgz",
"integrity": "sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"hashery": "^1.5.1",
"keyv": "^5.6.0"
}
},
"node_modules/@eslint-community/eslint-utils": {
"version": "4.10.1",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz",
"integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==",
"dev": true,
"license": "MIT",
"dependencies": {
"eslint-visitor-keys": "^3.4.3"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
},
"peerDependencies": {
"eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
}
},
"node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/@eslint-community/regexpp": {
"version": "4.12.2",
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
"integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
}
},
"node_modules/@eslint/config-array": {
"version": "0.23.5",
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz",
"integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@eslint/object-schema": "^3.0.5",
"debug": "^4.3.1",
"minimatch": "^10.2.4"
},
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=24"
}
},
"node_modules/@eslint/config-helpers": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz",
"integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@eslint/core": "^1.2.1"
},
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=24"
}
},
"node_modules/@eslint/core": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz",
"integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@types/json-schema": "^7.0.15"
},
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=24"
}
},
"node_modules/@eslint/js": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz",
"integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=24"
},
"funding": {
"url": "https://eslint.org/donate"
},
"peerDependencies": {
"eslint": "^10.0.0"
},
"peerDependenciesMeta": {
"eslint": {
"optional": true
}
}
},
"node_modules/@eslint/object-schema": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz",
"integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=24"
}
},
"node_modules/@eslint/plugin-kit": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.3.tgz",
"integrity": "sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@eslint/core": "^1.2.1",
"levn": "^0.4.1"
},
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=24"
}
},
"node_modules/@humanfs/core": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
"integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@humanfs/types": "^0.15.0"
},
"engines": {
"node": ">=18.18.0"
}
},
"node_modules/@humanfs/node": {
"version": "0.16.8",
"resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz",
"integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@humanfs/core": "^0.19.2",
"@humanfs/types": "^0.15.0",
"@humanwhocodes/retry": "^0.4.0"
},
"engines": {
"node": ">=18.18.0"
}
},
"node_modules/@humanfs/types": {
"version": "0.15.0",
"resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz",
"integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=18.18.0"
}
},
"node_modules/@humanwhocodes/module-importer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
"integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=12.22"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/nzakas"
}
},
"node_modules/@humanwhocodes/retry": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
"integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=18.18"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/nzakas"
}
},
"node_modules/@keyv/bigmap": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz",
"integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"hashery": "^1.4.0",
"hookified": "^1.15.0"
},
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"keyv": "^5.6.0"
}
},
"node_modules/@keyv/serialize": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz",
"integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/esrecurse": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz",
"integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/estree": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
"integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": {
"version": "24.10.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
"integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~7.16.0"
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.71.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.71.2.tgz",
"integrity": "sha512-K72/pvT7qpm8q3k8G5u93NCSN1RcLxX5h7Gw0bQpbMXviBHeY6Nx1WEwmk9wP+eKTSQ8ecxvcgt/ZX/rB938xQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.12.2",
"@typescript-eslint/scope-manager": "8.71.2",
"@typescript-eslint/type-utils": "8.71.2",
"@typescript-eslint/utils": "8.71.2",
"@typescript-eslint/visitor-keys": "8.71.2",
"ignore": "^7.0.5",
"natural-compare": "^1.4.0",
"ts-api-utils": "^2.5.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"@typescript-eslint/parser": "^8.71.2",
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
"version": "7.0.12",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.12.tgz",
"integrity": "sha512-/8UvqAPU9DGTI9k4mxtf49U37Isfwr8Uts96+SBHIkFxPJnHS0Ew4f00sM4Scd8V8EjM0jUNtVDdv1kPdt35lg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"node_modules/@typescript-eslint/parser": {
"version": "8.71.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.71.2.tgz",
"integrity": "sha512-aTtmZjp/x3gsGHkDs04/1I8kNogBJkpQaeGaN0PfiG43NzDEmW2n+OP5im9wfsvyX0yP1UZWBrAGtDBfH9jEVw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.71.2",
"@typescript-eslint/types": "8.71.2",
"@typescript-eslint/typescript-estree": "8.71.2",
"@typescript-eslint/visitor-keys": "8.71.2",
"debug": "^4.4.3"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/project-service": {
"version": "8.71.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.71.2.tgz",
"integrity": "sha512-Pd68GsLVh5ZXlcEccFPdhSOvtHZ/2WU0XsA8zmXOS3F158lVlcTLhfQ6qvtL1RzbMie0Kp8xtbVwAg4nejKPHw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.71.2",
"@typescript-eslint/types": "^8.71.2",
"debug": "^4.4.3"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.71.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.71.2.tgz",
"integrity": "sha512-YF4wgg5iUX5Obywoac27B56FhozMNFhdJSbh11H5GBAwmBtfyRhmgTQqW9tRNNmHDvsZ2CX6UGzoz+YfYn2Leg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.71.2",
"@typescript-eslint/visitor-keys": "8.71.2"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.71.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.71.2.tgz",
"integrity": "sha512-jz49TAWokm/7yWD9/o837nYax9DyALxIPOImdImvVzo6canBCZYzin8zJyqNR5k1j9K1DE0bVvr0XYB+99uAdg==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.71.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.71.2.tgz",
"integrity": "sha512-cC8UaalCViJkb3rH/xLIvidEwoFRtMZMUqDjjJMGEKaTFwYRHYBcoczlEGHuvkHEO8svRDrgZ0yQgOArFmwang==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.71.2",
"@typescript-eslint/typescript-estree": "8.71.2",
"@typescript-eslint/utils": "8.71.2",
"debug": "^4.4.3",
"ts-api-utils": "^2.5.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/types": {
"version": "8.71.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.71.2.tgz",
"integrity": "sha512-y5Ukl3oVp7e/VGxqIoEXoeb/Roj71PAJ4WG/N/RGj+eeDvKRLM15kGr9MijLbN0i03S52zxu31Z98E5gWChJKw==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.71.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.71.2.tgz",
"integrity": "sha512-DgNOEsl4pdYzwSL+N/Cl33tX1Hlw3FvId73LnKXfq7jnrF2viOEn7X+P7RBkkSWCVm92gnWd28GIC5vJuLREeA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.71.2",
"@typescript-eslint/tsconfig-utils": "8.71.2",
"@typescript-eslint/types": "8.71.2",
"@typescript-eslint/visitor-keys": "8.71.2",
"debug": "^4.4.3",
"minimatch": "^10.2.2",
"semver": "^7.7.3",
"tinyglobby": "^0.2.15",
"ts-api-utils": "^2.5.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/utils": {
"version": "8.71.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.71.2.tgz",
"integrity": "sha512-RyKa1G1M9rKHDhrk+HJtiF1MvLD5Ux2JW3TSkzw+02pRALdpJb+QzG1Y5em+LvCgzGwgg8H7mBRFKljNz7ImPw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.9.1",
"@typescript-eslint/scope-manager": "8.71.2",
"@typescript-eslint/types": "8.71.2",
"@typescript-eslint/typescript-estree": "8.71.2"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.71.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.71.2.tgz",
"integrity": "sha512-zozlN0UA32bSG2zkKAgQyRYSbEISVoyU6vvkKYo2aSny74D8Jh4e+POkJ6Lk0dlezZi5x7TtD+88Xb06IB8nhg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.71.2",
"eslint-visitor-keys": "^5.0.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/acorn": {
"version": "8.19.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.19.0.tgz",
"integrity": "sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/acorn-jsx": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
"node_modules/ajv": {
"version": "6.15.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
"integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/brace-expansion": {
"version": "5.0.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz",
"integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
"node": "20 || >=22"
}
},
"node_modules/cacheable": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.5.0.tgz",
"integrity": "sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@cacheable/memory": "^2.2.0",
"@cacheable/utils": "^2.5.0",
"hookified": "^1.15.0",
"keyv": "^5.6.0",
"qified": "^0.10.1"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
"dev": true,
"license": "MIT"
},
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/eslint": {
"version": "10.13.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-10.13.0.tgz",
"integrity": "sha512-UohDjD4zSzEwhf58M3O7vc/ycc8O1I7dNlhtD0WxE8Px8EHNJKtUwzBqrzeGhisRuv2ag4sAYswszCJRgKXYUg==",
"dev": true,
"license": "MIT",
"peer": true,
"workspaces": [
"packages/*"
],
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.2",
"@eslint/config-array": "^0.23.5",
"@eslint/config-helpers": "^0.7.0",
"@eslint/core": "^1.2.1",
"@eslint/plugin-kit": "^0.7.3",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.2",
"@types/estree": "^1.0.6",
"ajv": "^6.14.0",
"cross-spawn": "^7.0.6",
"debug": "^4.3.2",
"escape-string-regexp": "^4.0.0",
"eslint-scope": "^9.1.2",
"eslint-visitor-keys": "^5.0.1",
"espree": "^11.2.0",
"esquery": "^1.7.0",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
"file-entry-cache": "11.1.5 || >11.1.6 <12",
"find-up": "^5.0.0",
"glob-parent": "^6.0.2",
"ignore": "^5.2.0",
"imurmurhash": "^0.1.4",
"is-glob": "^4.0.0",
"json-stable-stringify-without-jsonify": "^1.0.1",
"minimatch": "^10.2.5",
"natural-compare": "^1.4.0",
"optionator": "^0.9.3"
},
"bin": {
"eslint": "bin/eslint.js"
},
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=24"
},
"funding": {
"url": "https://eslint.org/donate"
},
"peerDependencies": {
"jiti": "*"
},
"peerDependenciesMeta": {
"jiti": {
"optional": true
}
}
},
"node_modules/eslint-scope": {
"version": "9.1.2",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz",
"integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"@types/esrecurse": "^4.3.1",
"@types/estree": "^1.0.8",
"esrecurse": "^4.3.0",
"estraverse": "^5.2.0"
},
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=24"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint-visitor-keys": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
"integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=24"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/espree": {
"version": "11.2.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz",
"integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"acorn": "^8.16.0",
"acorn-jsx": "^5.3.2",
"eslint-visitor-keys": "^5.0.1"
},
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=24"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/esquery": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
"integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"estraverse": "^5.1.0"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/esrecurse": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"estraverse": "^5.2.0"
},
"engines": {
"node": ">=4.0"
}
},
"node_modules/estraverse": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=4.0"
}
},
"node_modules/esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true,
"license": "MIT"
},
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
"dev": true,
"license": "MIT"
},
"node_modules/fast-levenshtein": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true,
"license": "MIT"
},
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": {
"picomatch": "^3 || ^4"
},
"peerDependenciesMeta": {
"picomatch": {
"optional": true
}
}
},
"node_modules/file-entry-cache": {
"version": "11.1.5",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.5.tgz",
"integrity": "sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"flat-cache": "^6.1.23"
}
},
"node_modules/find-up": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
"integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
"dev": true,
"license": "MIT",
"dependencies": {
"locate-path": "^6.0.0",
"path-exists": "^4.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/flat-cache": {
"version": "6.1.23",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.23.tgz",
"integrity": "sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"cacheable": "^2.5.0",
"flatted": "^3.4.2",
"hookified": "^1.15.0"
}
},
"node_modules/flatted": {
"version": "3.4.4",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz",
"integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==",
"dev": true,
"license": "ISC"
},
"node_modules/glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
"dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.3"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/hashery": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.1.tgz",
"integrity": "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"hookified": "^1.15.0"
},
"engines": {
"node": ">=20"
}
},
"node_modules/hookified": {
"version": "1.15.1",
"resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz",
"integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==",
"dev": true,
"license": "MIT"
},
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
"integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"node_modules/imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
"integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.8.19"
}
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-extglob": "^2.1.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true,
"license": "ISC"
},
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true,
"license": "MIT"
},
"node_modules/json-stable-stringify-without-jsonify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
"dev": true,
"license": "MIT"
},
"node_modules/keyv": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz",
"integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@keyv/serialize": "^1.1.1"
}
},
"node_modules/levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
"integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"prelude-ls": "^1.2.1",
"type-check": "~0.4.0"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
"integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
"dev": true,
"license": "MIT",
"dependencies": {
"p-locate": "^5.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/minimatch": {
"version": "10.2.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz",
"integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"brace-expansion": "^5.0.8"
},
"engines": {
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT"
},
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"dev": true,
"license": "MIT"
},
"node_modules/optionator": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
"integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
"dev": true,
"license": "MIT",
"dependencies": {
"deep-is": "^0.1.3",
"fast-levenshtein": "^2.0.6",
"levn": "^0.4.1",
"prelude-ls": "^1.2.1",
"type-check": "^0.4.0",
"word-wrap": "^1.2.5"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"yocto-queue": "^0.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/p-locate": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
"dev": true,
"license": "MIT",
"dependencies": {
"p-limit": "^3.0.2"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/picomatch": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
"integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/qified": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/qified/-/qified-0.10.1.tgz",
"integrity": "sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==",
"dev": true,
"license": "MIT",
"dependencies": {
"hookified": "^2.1.1"
},
"engines": {
"node": ">=20"
}
},
"node_modules/qified/node_modules/hookified": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/hookified/-/hookified-2.2.0.tgz",
"integrity": "sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==",
"dev": true,
"license": "MIT"
},
"node_modules/semver": {
"version": "7.8.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/tinyglobby": {
"version": "0.2.17",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
"integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
"dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
"picomatch": "^4.0.4"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/ts-api-utils": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
"integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18.12"
},
"peerDependencies": {
"typescript": ">=4.8.4"
}
},
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
"integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
"dev": true,
"license": "MIT",
"dependencies": {
"prelude-ls": "^1.2.1"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/typescript": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/typescript-eslint": {
"version": "8.71.2",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.71.2.tgz",
"integrity": "sha512-Rz4effbMy2g9wnvOJZG6SbVavX313Lap1vNEbk1RUochzickvzP8P4HB+bkNg5e50eVz1hQGjh9K1omI1xB4Bw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/eslint-plugin": "8.71.2",
"@typescript-eslint/parser": "8.71.2",
"@typescript-eslint/typescript-estree": "8.71.2",
"@typescript-eslint/utils": "8.71.2"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/undici-types": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
"dev": true,
"license": "MIT"
},
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"punycode": "^2.1.0"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/word-wrap": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/zod": {
"version": "4.6.7",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.6.7.tgz",
"integrity": "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
}
}
}
example/tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"lib": ["ES2022"],
"types": ["node"],
"strict": true,
"noEmitOnError": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"declaration": true,
"rootDir": "src",
"outDir": "dist"
},
"include": ["src/**/*.ts"]
}
example/eslint.config.mjs
import js from "@eslint/js";
import tseslint from "typescript-eslint";
export default tseslint.config(
{ ignores: ["dist/**", "node_modules/**", "evidence/**"] },
{
files: ["**/*.mjs"],
extends: [js.configs.recommended],
languageOptions: {
globals: { console: "readonly", process: "readonly", URL: "readonly", setTimeout: "readonly", clearTimeout: "readonly" },
},
},
{
files: ["src/**/*.ts"],
extends: [js.configs.recommended, ...tseslint.configs.recommendedTypeChecked],
languageOptions: {
parserOptions: { projectService: true, tsconfigRootDir: import.meta.dirname },
},
rules: { "@typescript-eslint/switch-exhaustiveness-check": "error" },
},
);
example/.gitignore
node_modules/
dist/
example/src/memory.ts
export type Scope =
| { kind: "personal"; userId: string }
| { kind: "project"; userId: string; projectId: string }
| { kind: "task"; userId: string; projectId: string; taskId: string };
export type Source = { agent: string; ref: string };
export type Candidate = { scope: Scope; topic: string; text: string; source: Source };
export type Observation = { text: string; source: Source };
export type Memory = {
id: string;
scope: Scope;
topic: string;
text: string;
status: "active" | "superseded" | "pending";
observations: Observation[];
supersedes?: string;
};
export type Decision =
| { kind: "add" }
| { kind: "confirm"; targetId: string }
| { kind: "supplement"; targetId: string; mergedText: string }
| { kind: "replace"; targetId: string }
| { kind: "review" };
export type Prepared = { revision: number; candidate: Candidate; related: Memory[] };
export type ReadRequest = {
userId: string;
projectId: string;
taskId?: string;
topics: readonly string[];
limit?: number;
maxChars?: number;
};
const copyCandidate = (c: Candidate): Candidate => ({
...c, scope: { ...c.scope }, source: { ...c.source },
});
const copyMemory = (m: Memory): Memory => ({
...m, scope: { ...m.scope },
observations: m.observations.map(o => ({ text: o.text, source: { ...o.source } })),
});
const scopeKey = (s: Scope): string => JSON.stringify(
s.kind === "personal" ? [s.kind, s.userId]
: s.kind === "project" ? [s.kind, s.userId, s.projectId]
: [s.kind, s.userId, s.projectId, s.taskId],
);
function nonempty(value: string): void {
if (typeof value !== "string" || !value.trim()) throw new Error("Expected nonempty text");
}
function validate(c: Candidate): void {
[c.scope.userId, c.topic, c.text, c.source.agent, c.source.ref].forEach(nonempty);
if (c.scope.kind !== "personal") nonempty(c.scope.projectId);
if (c.scope.kind === "task") nonempty(c.scope.taskId);
if (!["personal", "project", "task"].includes(c.scope.kind)) throw new Error("Invalid scope");
}
/** One trusted process; callers provide scope and semantic decisions. */
export class MemoryStore {
private revision = 0;
private records: Memory[] = [];
private preparations = new WeakMap<Prepared, { revision: number; candidate: Candidate }>();
prepare(input: Candidate): Prepared {
validate(input);
const candidate = copyCandidate(input);
const prepared: Prepared = {
revision: this.revision,
candidate: copyCandidate(candidate),
related: this.history(candidate.scope, candidate.topic)
.filter(m => m.status !== "superseded"),
};
// Keep the authoritative snapshot private; returned objects are editable copies.
this.preparations.set(prepared, { revision: this.revision, candidate });
return prepared;
}
commit(prepared: Prepared, decision: Decision): Memory {
const snapshot = this.preparations.get(prepared);
if (!snapshot) throw new Error("Unknown or consumed preparation");
if (snapshot.revision !== this.revision) throw new Error("Stale preparation: prepare again");
const c = snapshot.candidate;
const related = this.records.filter(m => scopeKey(m.scope) === scopeKey(c.scope)
&& m.topic === c.topic && m.status === "active");
let target: Memory | undefined;
if ("targetId" in decision) {
target = related.find(m => m.id === decision.targetId);
if (!target) throw new Error("Target must be active in the same scope and topic");
}
if (decision.kind === "add" && related.length) {
throw new Error("Topic already has an active memory: compare before adding");
}
if (decision.kind === "supplement") nonempty(decision.mergedText);
if (!["add", "confirm", "supplement", "replace", "review"].includes(decision.kind)) {
throw new Error("Invalid decision");
}
const observation: Observation = { text: c.text, source: { ...c.source } };
let result: Memory;
if (decision.kind === "confirm" && target) {
// This records another observation, not another independent witness.
if (!target.observations.some(o => o.text === c.text
&& o.source.agent === c.source.agent && o.source.ref === c.source.ref)) {
target.observations.push(observation);
}
result = target;
} else {
result = {
id: `m${this.records.length + 1}`, scope: { ...c.scope }, topic: c.topic,
text: decision.kind === "supplement" ? decision.mergedText : c.text,
status: decision.kind === "review" ? "pending" : "active",
observations: target
? [...target.observations.map(o => ({ text: o.text, source: { ...o.source } })), observation]
: [observation],
};
if (target) {
result.supersedes = target.id;
target.status = "superseded";
}
this.records.push(result);
}
this.revision += 1;
this.preparations.delete(prepared);
return copyMemory(result);
}
history(scope: Scope, topic: string): Memory[] {
return this.records.filter(m => scopeKey(m.scope) === scopeKey(scope) && m.topic === topic)
.map(copyMemory);
}
retrieve(request: ReadRequest): Memory[] {
const { userId, projectId, taskId, topics, limit = 5, maxChars = 4000 } = request;
nonempty(userId);
nonempty(projectId);
if (!Number.isInteger(limit) || limit < 0 || !Number.isInteger(maxChars) || maxChars < 2) {
throw new Error("Invalid retrieval budget");
}
const visible = this.records.filter(m => m.status === "active"
&& m.scope.userId === userId && topics.includes(m.topic)
&& (m.scope.kind === "personal" || (m.scope.projectId === projectId
&& (m.scope.kind === "project" || m.scope.taskId === taskId))));
const specificity = (s: Scope) => s.kind === "task" ? 3 : s.kind === "project" ? 2 : 1;
visible.sort((a, b) => specificity(b.scope) - specificity(a.scope));
const selected: Memory[] = [];
let chars = 2; // JSON array brackets; budget includes metadata and observations.
for (const m of visible) {
if (selected.length === limit) break;
const cost = JSON.stringify(m).length + (selected.length ? 1 : 0);
if (chars + cost > maxChars) continue;
selected.push(copyMemory(m));
chars += cost;
}
return selected;
}
}
example/src/walkthrough.ts
import { MemoryStore, type Candidate } from "./memory.js";
export function walkthrough() {
const store = new MemoryStore();
const draft = (text: string, ref: string): Candidate => ({
scope: { kind: "project", userId: "reader", projectId: "field-notes" },
topic: "writing.style",
text,
source: { agent: "editor", ref },
});
const first = store.commit(
store.prepare(draft("Use short paragraphs.", "conversation-1")),
{ kind: "add" },
);
// These judgments are supplied by a person or the host agent's model.
// The store does not infer that two sentences mean the same thing.
store.commit(
store.prepare(draft("Keep paragraphs brief.", "conversation-2")),
{ kind: "confirm", targetId: first.id },
);
const fuller = store.commit(
store.prepare(draft("Explain unfamiliar terms.", "conversation-3")),
{
kind: "supplement", targetId: first.id,
mergedText: "Use short paragraphs. Explain unfamiliar terms.",
},
);
store.commit(
store.prepare(draft("Use longer paragraphs. Explain unfamiliar terms.", "conversation-4")),
{ kind: "replace", targetId: fuller.id },
);
store.commit(
store.prepare(draft("Maybe use bullet points throughout?", "conversation-5")),
{ kind: "review" },
);
const current = store.retrieve({
userId: "reader", projectId: "field-notes",
topics: ["writing.style"], limit: 3, maxChars: 2000,
});
const history = store.history(first.scope, "writing.style");
return { current, history };
}
example/src/protocol.ts
import { z } from "zod";
import { MemoryStore, type Prepared } from "./memory.js";
const text = z.string().refine(value => value.trim().length > 0, "Expected nonempty text");
const scope = z.discriminatedUnion("kind", [
z.strictObject({ kind: z.literal("personal"), userId: text }),
z.strictObject({ kind: z.literal("project"), userId: text, projectId: text }),
z.strictObject({ kind: z.literal("task"), userId: text, projectId: text, taskId: text }),
]);
const candidate = z.strictObject({
scope, topic: text, text, source: z.strictObject({ agent: text, ref: text }),
});
const decision = z.discriminatedUnion("kind", [
z.strictObject({ kind: z.literal("add") }),
z.strictObject({ kind: z.literal("confirm"), targetId: text }),
z.strictObject({ kind: z.literal("supplement"), targetId: text, mergedText: text }),
z.strictObject({ kind: z.literal("replace"), targetId: text }),
z.strictObject({ kind: z.literal("review") }),
]);
const requestSchema = z.discriminatedUnion("op", [
z.strictObject({ id: text, op: z.literal("prepare"), candidate }),
z.strictObject({ id: text, op: z.literal("commit"), token: text, decision }),
z.strictObject({ id: text, op: z.literal("history"), scope, topic: text }),
z.strictObject({
id: text, op: z.literal("retrieve"),
userId: text, projectId: text, taskId: text.optional(), topics: z.array(text),
limit: z.number().int().min(0).optional(), maxChars: z.number().int().min(2).optional(),
}),
]);
type Request = z.infer<typeof requestSchema>;
type Reply =
| { id: string; ok: true; result: unknown }
| { id: string | null; ok: false; error: { code: string; message: string } };
function requestId(value: unknown): string | null {
return typeof value === "object" && value !== null && "id" in value && typeof value.id === "string"
? value.id : null;
}
/** A trusted local JSON-lines session. Tokens stay within this process. */
export class MemorySession {
private readonly store = new MemoryStore();
private readonly preparations = new Map<string, Prepared>();
private nextToken = 0;
handleLine(line: string): Reply {
let input: unknown;
try {
input = JSON.parse(line) as unknown;
} catch {
return { id: null, ok: false, error: { code: "invalid_json", message: "Expected JSON" } };
}
const parsed = requestSchema.safeParse(input);
if (!parsed.success) {
return { id: requestId(input), ok: false, error: { code: "invalid_request", message: "Request schema rejected input" } };
}
try {
return { id: parsed.data.id, ok: true, result: this.execute(parsed.data) };
} catch (error) {
const message = error instanceof Error ? error.message : "Request failed";
return {
id: parsed.data.id, ok: false,
error: { code: message.startsWith("Stale preparation") ? "conflict" : "rejected", message },
};
}
}
private execute(request: Request): unknown {
switch (request.op) {
case "prepare": {
const prepared = this.store.prepare(request.candidate);
const token = `p${++this.nextToken}`;
this.preparations.set(token, prepared);
return { token, ...prepared };
}
case "commit": {
const prepared = this.preparations.get(request.token);
if (!prepared) throw new Error("Unknown or consumed token");
try {
return this.store.commit(prepared, request.decision);
} finally {
// A failed commit also requires a fresh comparison at this boundary.
this.preparations.delete(request.token);
}
}
case "history":
return this.store.history(request.scope, request.topic);
case "retrieve":
return this.store.retrieve({
userId: request.userId, projectId: request.projectId, topics: request.topics,
...(request.taskId === undefined ? {} : { taskId: request.taskId }),
limit: request.limit ?? 5, maxChars: request.maxChars ?? 4000,
});
}
}
}
example/src/session.ts
import { createInterface } from "node:readline";
import { stdin, stdout } from "node:process";
import { MemorySession } from "./protocol.js";
const session = new MemorySession();
const input = createInterface({ input: stdin, crlfDelay: Infinity });
for await (const line of input) {
stdout.write(JSON.stringify(session.handleLine(line)) + "\n");
}
example/demo.mjs
import { walkthrough } from "./dist/walkthrough.js";
const { current, history } = walkthrough();
console.log("Current:", current.map(m => m.text));
console.log("Current observations:", current[0]?.observations.length);
console.log("History:", history.map(m => `${m.id}: ${m.status}`));
example/test/memory.test.mjs
import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { MemoryStore } from "../dist/memory.js";
import { walkthrough } from "../dist/walkthrough.js";
const project = { kind: "project", userId: "reader", projectId: "field-notes" };
const candidate = (text = "Use short paragraphs.", scope = project, topic = "writing.style", ref = "c1") => ({
scope: { ...scope }, topic, text, source: { agent: "editor", ref },
});
const add = (store, c = candidate()) => store.commit(store.prepare(c), { kind: "add" });
const read = (store, extra = {}) => store.retrieve({
userId: "reader", projectId: "field-notes", topics: ["writing.style"], ...extra,
});
test("a supplied semantic confirmation keeps one record and preserves both wordings", () => {
const store = new MemoryStore();
const first = add(store);
const confirmed = store.commit(store.prepare(candidate("Keep paragraphs brief.", project, "writing.style", "c2")),
{ kind: "confirm", targetId: first.id });
assert.equal(confirmed.id, first.id);
assert.equal(read(store).length, 1);
assert.deepEqual(confirmed.observations.map(o => o.text), ["Use short paragraphs.", "Keep paragraphs brief."]);
});
test("confirmation of the same observation does not inflate the observation count", () => {
const store = new MemoryStore();
const first = add(store);
store.commit(store.prepare(candidate()), { kind: "confirm", targetId: first.id });
assert.equal(read(store)[0].observations.length, 1);
});
test("an existing active topic cannot silently receive a second active add", () => {
const store = new MemoryStore();
add(store);
assert.throws(() => add(store, candidate("Use long paragraphs.")), /compare before adding/);
assert.equal(read(store)[0].text, "Use short paragraphs.");
});
test("supplement creates a complete revised record and keeps the original", () => {
const store = new MemoryStore();
const first = add(store);
const revised = store.commit(store.prepare(candidate("Explain unfamiliar terms.", project, "writing.style", "c2")),
{ kind: "supplement", targetId: first.id, mergedText: "Use short paragraphs. Explain unfamiliar terms." });
assert.equal(revised.supersedes, first.id);
assert.equal(revised.observations.length, 2);
assert.deepEqual(store.history(project, "writing.style").map(m => [m.text, m.status]), [
["Use short paragraphs.", "superseded"],
["Use short paragraphs. Explain unfamiliar terms.", "active"],
]);
});
test("replacement preserves the old text and retrieves only the current version", () => {
const store = new MemoryStore();
const old = add(store);
const next = store.commit(store.prepare(candidate("Use long paragraphs.")), { kind: "replace", targetId: old.id });
assert.deepEqual(read(store).map(m => m.id), [next.id]);
assert.equal(store.history(project, "writing.style")[0].text, old.text);
});
test("uncertain information is retained as pending and never enters ordinary retrieval", () => {
const store = new MemoryStore();
const old = add(store);
store.commit(store.prepare(candidate("Maybe bullets?")), { kind: "review" });
assert.deepEqual(read(store).map(m => m.id), [old.id]);
assert.equal(store.history(project, "writing.style")[1].status, "pending");
});
test("two projects can keep identical text as independent memories", () => {
const store = new MemoryStore();
add(store);
add(store, candidate("Use short paragraphs.", { ...project, projectId: "other" }));
assert.equal(read(store).length, 1);
assert.equal(read(store, { projectId: "other" }).length, 1);
assert.notEqual(read(store)[0].id, read(store, { projectId: "other" })[0].id);
});
test("another user's records do not appear in retrieval or preparation", () => {
const store = new MemoryStore();
add(store, candidate("Other user's style.", { ...project, userId: "other" }));
assert.deepEqual(read(store), []);
assert.deepEqual(store.prepare(candidate()).related, []);
});
test("personal memory is visible across that user's projects", () => {
const store = new MemoryStore();
add(store, candidate("Explain unfamiliar terms.", { kind: "personal", userId: "reader" }));
assert.equal(read(store, { projectId: "another" }).length, 1);
assert.equal(read(store, { userId: "someone-else" }).length, 0);
});
test("task memory requires the same project and task and sorts before broader memory", () => {
const store = new MemoryStore();
add(store, candidate("Personal.", { kind: "personal", userId: "reader" }));
add(store, candidate("Project."));
add(store, candidate("Task.", { ...project, kind: "task", taskId: "draft-1" }));
assert.deepEqual(read(store, { taskId: "draft-1" }).map(m => m.text), ["Task.", "Project.", "Personal."]);
assert.deepEqual(read(store, { taskId: "draft-2" }).map(m => m.text), ["Project.", "Personal."]);
assert.deepEqual(read(store, { taskId: "draft-1", projectId: "other" }).map(m => m.text), ["Personal."]);
});
test("only requested topics are retrieved", () => {
const store = new MemoryStore();
add(store);
add(store, candidate("Morning work.", project, "working.hours"));
assert.equal(read(store).length, 1);
assert.deepEqual(read(store, { topics: [] }), []);
});
test("a target from another scope or topic is rejected without modifying history", () => {
const store = new MemoryStore();
const first = add(store);
for (const c of [candidate("Other.", { ...project, projectId: "other" }), candidate("Other.", project, "other")]) {
assert.throws(() => store.commit(store.prepare(c), { kind: "replace", targetId: first.id }), /same scope and topic/);
}
assert.equal(read(store)[0].status, "active");
});
test("a superseded or pending record cannot be used as an active target", () => {
const store = new MemoryStore();
const first = add(store);
store.commit(store.prepare(candidate("New.")), { kind: "replace", targetId: first.id });
const pending = store.commit(store.prepare(candidate("Maybe.")), { kind: "review" });
for (const targetId of [first.id, pending.id]) {
assert.throws(() => store.commit(store.prepare(candidate()), { kind: "confirm", targetId }), /Target must be active/);
}
});
test("an intervening write makes a prepared comparison stale", () => {
const store = new MemoryStore();
const prepared = store.prepare(candidate());
add(store, candidate("Separate topic.", project, "other"));
assert.throws(() => store.commit(prepared, { kind: "add" }), /Stale preparation/);
assert.equal(read(store).length, 0);
});
test("confirmation also invalidates older preparations", () => {
const store = new MemoryStore();
const first = add(store);
const before = store.prepare(candidate("Updated."));
store.commit(store.prepare(candidate("Brief paragraphs.", project, "writing.style", "c2")),
{ kind: "confirm", targetId: first.id });
assert.throws(() => store.commit(before, { kind: "replace", targetId: first.id }), /Stale preparation/);
});
test("a preparation is single use and belongs to its originating store", () => {
const store = new MemoryStore();
const other = new MemoryStore();
const prepared = store.prepare(candidate());
assert.throws(() => other.commit(prepared, { kind: "add" }), /Unknown or consumed/);
store.commit(prepared, { kind: "add" });
assert.throws(() => store.commit(prepared, { kind: "add" }), /Unknown or consumed/);
});
test("editing input or the public preparation cannot change the private snapshot", () => {
const store = new MemoryStore();
const input = candidate();
const prepared = store.prepare(input);
input.scope.projectId = "mutated-input";
prepared.candidate.text = "mutated-view";
prepared.candidate.source.ref = "mutated-source";
prepared.revision = 900;
const result = store.commit(prepared, { kind: "add" });
assert.equal(result.scope.projectId, "field-notes");
assert.equal(result.text, "Use short paragraphs.");
assert.equal(result.observations[0].source.ref, "c1");
});
test("read, commit and history results do not expose mutable internal references", () => {
const store = new MemoryStore();
const result = add(store);
for (const copy of [result, read(store)[0], store.history(project, "writing.style")[0]]) {
copy.text = "mutated";
copy.scope.projectId = "mutated";
copy.observations[0].source.ref = "mutated";
}
assert.equal(read(store)[0].text, "Use short paragraphs.");
assert.equal(read(store)[0].observations[0].source.ref, "c1");
});
test("scope components containing separators do not collide", () => {
const store = new MemoryStore();
add(store, candidate("One.", { kind: "project", userId: "a/b", projectId: "c" }));
add(store, candidate("Two.", { kind: "project", userId: "a", projectId: "b/c" }));
assert.equal(read(store, { userId: "a/b", projectId: "c" })[0].text, "One.");
assert.equal(read(store, { userId: "a", projectId: "b/c" })[0].text, "Two.");
});
test("retrieval respects the count limit, including zero", () => {
const store = new MemoryStore();
add(store);
add(store, candidate("Other.", project, "other"));
assert.equal(read(store, { topics: ["writing.style", "other"], limit: 1 }).length, 1);
assert.deepEqual(read(store, { limit: 0 }), []);
});
test("character budget covers the serialized records, metadata and array punctuation", () => {
const store = new MemoryStore();
add(store);
const exact = JSON.stringify(read(store)).length;
assert.equal(read(store, { maxChars: exact }).length, 1);
assert.deepEqual(read(store, { maxChars: exact - 1 }), []);
assert.deepEqual(read(store, { maxChars: 2 }), []);
});
test("an oversized record is skipped so a smaller relevant record can fit", () => {
const store = new MemoryStore();
add(store, candidate("x".repeat(2000)));
add(store, candidate("Small.", project, "other"));
const result = read(store, { topics: ["writing.style", "other"], maxChars: 500 });
assert.deepEqual(result.map(m => m.text), ["Small."]);
assert.ok(JSON.stringify(result).length <= 500);
});
test("invalid text and retrieval budgets are rejected", () => {
const store = new MemoryStore();
assert.throws(() => store.prepare(candidate(" ")), /nonempty/);
assert.throws(() => read(store, { limit: -1 }), /Invalid retrieval budget/);
assert.throws(() => read(store, { maxChars: 1 }), /Invalid retrieval budget/);
assert.throws(() => read(store, { limit: 1.5 }), /Invalid retrieval budget/);
const first = add(store);
assert.throws(() => store.commit(store.prepare(candidate()),
{ kind: "supplement", targetId: first.id, mergedText: " " }), /nonempty/);
assert.equal(read(store)[0].text, first.text);
});
test("the complete walkthrough produces one current version, history and a pending item", () => {
const { current, history } = walkthrough();
assert.deepEqual(current.map(m => m.text), ["Use longer paragraphs. Explain unfamiliar terms."]);
assert.equal(current[0].observations.length, 4);
assert.deepEqual(history.map(m => `${m.id}: ${m.status}`), [
"m1: superseded", "m2: superseded", "m3: active", "m4: pending",
]);
});
test("the article's complete TypeScript walkthrough matches the compiled source", () => {
const article = readFileSync(new URL("../../article.en.md", import.meta.url), "utf8");
const source = readFileSync(new URL("../src/walkthrough.ts", import.meta.url), "utf8");
assert.ok(article.includes("```ts\n" + source.trim() + "\n```"));
const implementation = readFileSync(new URL("../src/memory.ts", import.meta.url), "utf8");
for (const block of article.matchAll(/```ts\n([\s\S]*?)\n```/g)) {
assert.ok(source.includes(block[1]) || implementation.includes(block[1]));
}
});
example/test/integration.test.mjs
import test from "node:test";
import assert from "node:assert/strict";
import { spawn, execFileSync } from "node:child_process";
import { createInterface } from "node:readline";
import { fileURLToPath } from "node:url";
import { readFileSync } from "node:fs";
const scope = { kind: "project", userId: "reader", projectId: "field-notes" };
const draft = (text, agent, ref, selectedScope = scope, topic = "writing.style") => ({
scope: selectedScope, topic, text, source: { agent, ref },
});
const query = extra => ({
op: "retrieve", userId: "reader", projectId: "field-notes", topics: ["writing.style"], ...extra,
});
function success(reply) {
assert.equal(reply.ok, true, JSON.stringify(reply));
return reply.result;
}
function startSession(t) {
const child = spawn(process.execPath, [fileURLToPath(new URL("../dist/session.js", import.meta.url))], {
stdio: ["pipe", "pipe", "pipe"],
});
let stderr = "";
let nextId = 0;
const pending = new Map();
const lines = createInterface({ input: child.stdout, crlfDelay: Infinity });
const exited = new Promise((resolve, reject) => {
child.once("exit", (code, signal) => resolve({ code, signal }));
child.once("error", reject);
});
// Install the rejection handler before sending any data.
void exited.catch(error => {
for (const waiter of pending.values()) { clearTimeout(waiter.timer); waiter.reject(error); }
pending.clear();
});
child.stderr.on("data", chunk => { stderr += chunk.toString(); });
child.once("exit", (code, signal) => {
for (const waiter of pending.values()) {
clearTimeout(waiter.timer);
waiter.reject(new Error(`Session exited: ${code}/${signal}; ${stderr}`));
}
pending.clear();
});
lines.on("line", line => {
const reply = JSON.parse(line);
const waiter = pending.get(reply.id);
assert.ok(waiter, `Unexpected response: ${line}`);
pending.delete(reply.id);
clearTimeout(waiter.timer);
waiter.resolve(reply);
});
t.after(async () => {
child.stdin.end();
const timer = setTimeout(() => child.kill(), 5000);
try {
const { code, signal } = await exited;
assert.equal(code, 0, `Session failed: ${signal}; ${stderr}`);
assert.equal(stderr, "");
} finally {
clearTimeout(timer);
lines.close();
}
});
function raw(line, id = null) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
pending.delete(id);
reject(new Error(`Response timeout: ${id}`));
}, 10000);
pending.set(id, { resolve, reject, timer });
child.stdin.write(line + "\n", error => {
if (error) {
clearTimeout(timer);
pending.delete(id);
reject(error);
}
});
});
}
return {
raw,
client(agent) {
return async payload => {
const id = `${agent}-${++nextId}`;
return raw(JSON.stringify({ id, ...payload }), id);
};
},
};
}
async function write(client, candidate, decision) {
const prepared = success(await client({ op: "prepare", candidate }));
return success(await client({ op: "commit", token: prepared.token, decision }));
}
test("integration: two agent clients complete the five write decisions through a compiled process", async t => {
const session = startSession(t);
const a = session.client("agent-a");
const b = session.client("agent-b");
const first = await write(a, draft("Use short paragraphs.", "agent-a", "chat-1"), { kind: "add" });
assert.deepEqual(success(await b(query())).map(m => m.id), [first.id]);
await write(b, draft("Keep paragraphs brief.", "agent-b", "chat-2"), { kind: "confirm", targetId: first.id });
const fuller = await write(a, draft("Explain unfamiliar terms.", "agent-a", "chat-3"), {
kind: "supplement", targetId: first.id, mergedText: "Use short paragraphs. Explain unfamiliar terms.",
});
const current = await write(b, draft("Use longer paragraphs. Explain unfamiliar terms.", "agent-b", "chat-4"),
{ kind: "replace", targetId: fuller.id });
await write(a, draft("Maybe bullet points?", "agent-a", "chat-5"), { kind: "review" });
const visible = success(await a(query()));
assert.deepEqual(visible.map(m => m.text), ["Use longer paragraphs. Explain unfamiliar terms."]);
assert.deepEqual(visible[0].observations.map(o => o.source.agent), ["agent-a", "agent-b", "agent-a", "agent-b"]);
const history = success(await b({ op: "history", scope, topic: "writing.style" }));
assert.deepEqual(history.map(m => m.status), ["superseded", "superseded", "active", "pending"]);
assert.equal(current.supersedes, fuller.id);
assert.equal(fuller.supersedes, first.id);
t.diagnostic(JSON.stringify({ current: visible.map(m => m.text), history: history.map(m => m.status) }));
});
test("integration: serialized requests preserve personal, project, task and user boundaries", async t => {
const session = startSession(t);
const client = session.client("agent-a");
const fixtures = [
["Personal.", { kind: "personal", userId: "reader" }],
["Project.", scope],
["Task.", { ...scope, kind: "task", taskId: "draft-1" }],
["Other project.", { ...scope, projectId: "other" }],
["Other user.", { ...scope, userId: "other" }],
];
for (const [text, selectedScope] of fixtures) {
await write(client, draft(text, "agent-a", text, selectedScope), { kind: "add" });
}
assert.deepEqual(success(await client(query({ taskId: "draft-1" }))).map(m => m.text), ["Task.", "Project.", "Personal."]);
assert.deepEqual(success(await client(query({ taskId: "draft-2" }))).map(m => m.text), ["Project.", "Personal."]);
assert.deepEqual(success(await client(query({ userId: "other" }))).map(m => m.text), ["Other user."]);
assert.deepEqual(success(await client(query({ projectId: "other" }))).map(m => m.text), ["Other project.", "Personal."]);
});
test("integration: interleaved clients receive a conflict, recompare, then update without losing history", async t => {
const session = startSession(t);
const a = session.client("agent-a");
const b = session.client("agent-b");
const first = await write(a, draft("Use short paragraphs.", "agent-a", "chat-1"), { kind: "add" });
const [pa, pb] = await Promise.all([
a({ op: "prepare", candidate: draft("Use long paragraphs.", "agent-a", "chat-2") }),
b({ op: "prepare", candidate: draft("Keep paragraphs brief.", "agent-b", "chat-3") }),
]);
success(await b({ op: "commit", token: success(pb).token, decision: { kind: "confirm", targetId: first.id } }));
const conflict = await a({ op: "commit", token: success(pa).token, decision: { kind: "replace", targetId: first.id } });
assert.equal(conflict.ok, false);
assert.equal(conflict.error.code, "conflict");
t.diagnostic(JSON.stringify({ observedConflict: conflict }));
assert.equal(success(await b(query()))[0].observations.length, 2);
const fresh = success(await a({ op: "prepare", candidate: draft("Use long paragraphs.", "agent-a", "chat-2") }));
assert.equal(fresh.related[0].observations.length, 2);
success(await a({ op: "commit", token: fresh.token, decision: { kind: "replace", targetId: first.id } }));
const replay = await a({ op: "commit", token: fresh.token, decision: { kind: "replace", targetId: first.id } });
assert.equal(replay.ok, false);
assert.match(replay.error.message, /consumed token/);
const history = success(await b({ op: "history", scope, topic: "writing.style" }));
assert.deepEqual(history.map(m => m.status), ["superseded", "active"]);
assert.equal(history[1].observations.length, 3);
});
test("integration: malformed input and rejected writes leave the process usable and current memory intact", async t => {
const session = startSession(t);
const client = session.client("agent-a");
assert.equal((await session.raw("not-json")).error.code, "invalid_json");
const first = await write(client, draft("Use short paragraphs.", "agent-a", "chat-1"), { kind: "add" });
for (const request of [
{ op: "prepare", candidate: draft(" ", "agent-a", "bad") },
{ op: "prepare", candidate: { ...draft("Bad.", "agent-a", "bad"), scope: { kind: "task", userId: "reader", projectId: "field-notes" } } },
{ op: "commit", token: "p0", decision: { kind: "replace" } },
query({ limit: -1 }), query({ maxChars: 1 }),
query({ extraField: "rejected" }),
]) {
const reply = await client(request);
assert.equal(reply.ok, false);
assert.equal(reply.error.code, "invalid_request");
}
const wrong = success(await client({ op: "prepare", candidate: draft("Other.", "agent-a", "bad", { ...scope, projectId: "other" }) }));
const rejected = await client({ op: "commit", token: wrong.token, decision: { kind: "replace", targetId: first.id } });
assert.equal(rejected.ok, false);
assert.equal(rejected.error.code, "rejected");
assert.equal(success(await client(query()))[0].text, "Use short paragraphs.");
assert.equal(success(await client({ op: "history", scope, topic: "writing.style" })).length, 1);
});
test("integration: serialization detaches snapshots and the JSON budget includes wire record data", async t => {
const session = startSession(t);
const client = session.client("agent-a");
const prepared = success(await client({ op: "prepare", candidate: draft("Use short paragraphs.", "agent-a", "chat-1") }));
prepared.candidate.text = "Edited response copy.";
prepared.candidate.source.ref = "Edited response source.";
success(await client({ op: "commit", token: prepared.token, decision: { kind: "add" } }));
const visible = success(await client(query()));
assert.equal(visible[0].text, "Use short paragraphs.");
assert.equal(visible[0].observations[0].source.ref, "chat-1");
const budget = JSON.stringify(visible).length;
assert.equal(success(await client(query({ maxChars: budget }))).length, 1);
assert.deepEqual(success(await client(query({ maxChars: budget - 1 }))), []);
assert.deepEqual(success(await client(query({ limit: 0 }))), []);
});
test("integration: the actual demo subprocess output matches the article's printed result", () => {
const stdout = execFileSync(process.execPath, [fileURLToPath(new URL("../demo.mjs", import.meta.url))], {
encoding: "utf8", timeout: 10000,
}).trim().replaceAll("\r\n", "\n");
const article = readFileSync(new URL("../../article.en.md", import.meta.url), "utf8");
assert.ok(article.includes("```text\n" + stdout + "\n```"));
});
example/tools/verify.mjs
import { spawnSync } from "node:child_process";
import { createHash } from "node:crypto";
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { relative, resolve } from "node:path";
const root = fileURLToPath(new URL("../", import.meta.url));
const evidence = resolve(root, "evidence");
mkdirSync(evidence, { recursive: true });
const version = name => JSON.parse(readFileSync(resolve(root, "node_modules", name, "package.json"), "utf8")).version;
const inputs = [
"package.json", "package-lock.json", "tsconfig.json", "eslint.config.mjs",
"src/memory.ts", "src/walkthrough.ts", "src/protocol.ts", "src/session.ts",
"test/memory.test.mjs", "test/integration.test.mjs", "demo.mjs", "tools/verify.mjs",
"../article.en.md",
];
const report = {
status: "running",
environment: {
node: process.version, platform: process.platform, architecture: process.arch,
npm: process.env.npm_config_user_agent?.match(/npm\/([^ ]+)/)?.[1] ?? "unknown",
typescript: version("typescript"), eslint: version("eslint"),
typescriptEslint: version("typescript-eslint"), zod: version("zod"),
},
inputs: Object.fromEntries(inputs.map(path => [path,
createHash("sha256").update(readFileSync(resolve(root, path))).digest("hex"),
])),
steps: [],
};
const steps = [
["build", ["node_modules/typescript/bin/tsc", "-p", "tsconfig.json"]],
["lint", ["node_modules/eslint/bin/eslint.js", ".", "--max-warnings", "0", "--format", "json"]],
["unit", ["--test", "--test-reporter=tap", "test/memory.test.mjs"]],
["integration", ["--test", "--test-reporter=tap", "test/integration.test.mjs"]],
["demo", ["demo.mjs"]],
];
for (const [name, args] of steps) {
const result = spawnSync(process.execPath, args, { cwd: root, encoding: "utf8", timeout: 120000 });
const exitCode = result.status ?? 1;
const step = { name, command: ["node", ...args], exitCode };
let stdout = result.stdout ?? "";
if (name === "lint" && exitCode === 0) {
const files = JSON.parse(result.stdout);
// Preserve diagnostics while keeping machine-specific paths out of shared evidence.
stdout = JSON.stringify(files.map(file => ({
...file, filePath: relative(root, file.filePath).replaceAll("\\", "/"),
})), null, 2) + "\n";
step.files = files.length;
step.errors = files.reduce((sum, file) => sum + file.errorCount, 0);
step.warnings = files.reduce((sum, file) => sum + file.warningCount, 0);
}
writeFileSync(resolve(evidence, `${name}.stdout.txt`), stdout);
writeFileSync(resolve(evidence, `${name}.stderr.txt`), result.stderr ?? "");
if ((name === "unit" || name === "integration") && exitCode === 0) {
step.tests = Number(result.stdout.match(/^# tests (\d+)$/m)?.[1]);
step.passed = Number(result.stdout.match(/^# pass (\d+)$/m)?.[1]);
step.failed = Number(result.stdout.match(/^# fail (\d+)$/m)?.[1]);
// Never turn an absent reporter summary into a claimed pass.
if (!Number.isInteger(step.tests) || step.tests === 0 || step.passed !== step.tests || step.failed !== 0) {
step.exitCode = 1;
}
}
report.steps.push(step);
console.log(`${step.exitCode === 0 ? "PASS" : "FAIL"} ${name}`);
if (step.exitCode !== 0) {
if (result.stderr) process.stderr.write(result.stderr);
if (result.stdout) process.stderr.write(result.stdout);
if (result.error) process.stderr.write(result.error.message + "\n");
report.status = "failed";
break;
}
}
if (report.status === "running") report.status = "passed";
writeFileSync(resolve(evidence, "verification.json"), JSON.stringify(report, null, 2) + "\n");
if (report.status !== "passed") process.exitCode = 1;
example/README.md
# Agent memory TypeScript example
All data is fictional. Use Node.js 22.13+ and npm. The published article contains
every file, including the lockfile and tests. Preserve the directory structure:
article.en.md is a small excerpt fixture one directory above example/.
From example/, run npm ci, then npm run verify. This compiles the source, runs
type-aware lint, 25 behavioral tests and 6 actual subprocess integration tests,
then executes the demo. Generated logs and source hashes appear in evidence/.
No API key, database or runtime network access is needed. The local service is
in-memory, trusts its callers and loses its data when the process exits.
Verification of the appendix
For this publication, I reconstructed all 15 files from the Markdown blocks above
in an empty directory, ran npm ci, and then ran npm run verify.
The actual local environment was Windows x64, Node.js 24.10.0, npm 11.6.1,
TypeScript 6.0.3, ESLint 10.13.0, typescript-eslint 8.71.2, and Zod 4.6.7.
| Check | Observed result |
|---|---|
| Strict TypeScript compilation | Passed, exit 0 |
| Type-aware ESLint | 9 files; 0 errors, 0 warnings |
| Behavioral tests | 25 passed; 0 failed, 0 skipped |
| Real process integration tests | 6 passed; 0 failed, 0 skipped |
| Demo | Passed; output matches the printed excerpts |
The verification was run on the specific environment above. Node.js 22.13+
is the declared supported range; this is not a claim that every runtime version
or operating system was tested. Your own run writes the commands, dependency
versions, source hashes, exit codes, and captured tool output into example/evidence/.