diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000000..e58667a53a --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,133 @@ +import stylistic from '@stylistic/eslint-plugin' +import tseslint from 'typescript-eslint' + +/** + * ESLint flat config. Two layers: + * + * 1. typescript-eslint strict-type-checked — correctness rules that need the + * type checker. The headline rules for this codebase: no-floating-promises + * and no-misused-promises (an un-awaited promise in the agent loop is our + * primary bug class), switch-exhaustiveness-check (we switch over + * merge-extensible unions everywhere). + * 2. @stylistic — formatting (2-space, no semicolons, single quotes, trailing + * commas), so style is enforced rather than drifting between agents. + * + * vendor/ is linted lightly (style only stays OFF — vendored code keeps + * upstream style; only a few safety rules apply there) and examples/tests are + * linted with relaxed unsafe-* rules where mocks intentionally bend types. + */ +export default tseslint.config( + { + ignores: [ + '**/lib/**', + '**/node_modules/**', + 'vendor/**', // vendored source keeps upstream style and idioms + '**/*.js', + '**/*.mjs', + ], + }, + + // --- our packages: full strictness ------------------------------------- + { + files: ['packages/*/src/**/*.ts', 'examples/**/*.ts', 'scripts/**/*.ts'], + extends: [ + ...tseslint.configs.strictTypeChecked, + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.typecheck.json'], + tsconfigRootDir: import.meta.dirname, + }, + }, + rules: { + // The bug class this repo cares most about: lost promises in the loop. + '@typescript-eslint/no-floating-promises': 'error', + '@typescript-eslint/no-misused-promises': 'error', + '@typescript-eslint/require-await': 'error', + '@typescript-eslint/switch-exhaustiveness-check': ['error', { + considerDefaultExhaustiveForUnions: true, + }], + '@typescript-eslint/no-unnecessary-condition': ['error', { + allowConstantLoopConditions: true, + }], + // `any` requires a justification comment — enforced as: no bare casts. + '@typescript-eslint/no-explicit-any': 'error', + // Style points where the codebase intentionally diverges from preset: + '@typescript-eslint/no-namespace': 'off', // Cordis Config-namespace idiom + '@typescript-eslint/no-empty-object-type': 'off', // merge-extensible maps + '@typescript-eslint/no-invalid-void-type': 'off', // event signatures + '@typescript-eslint/restrict-template-expressions': ['error', { + allowNumber: true, + allowBoolean: true, + }], + // `void foo()` in arrow listeners is our idiom for intentional fire-and-forget + 'no-void': 'off', + '@typescript-eslint/no-unused-vars': ['error', { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + caughtErrorsIgnorePattern: '^_', + }], + }, + }, + + // --- examples: demo code conforms to async interfaces without awaiting --- + { + files: ['examples/**/*.ts'], + rules: { + '@typescript-eslint/require-await': 'off', + }, + }, + + // --- tests: same rules, minus the friction that fights test ergonomics -- + { + files: ['packages/*/tests/**/*.ts'], + extends: [ + ...tseslint.configs.strictTypeChecked, + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.typecheck.json'], + tsconfigRootDir: import.meta.dirname, + }, + }, + rules: { + '@typescript-eslint/no-floating-promises': 'error', + '@typescript-eslint/no-misused-promises': 'error', + '@typescript-eslint/no-explicit-any': 'error', + '@typescript-eslint/no-non-null-assertion': 'off', // assertions follow expect()s + '@typescript-eslint/no-unnecessary-condition': 'off', + '@typescript-eslint/require-await': 'off', // mock execute() signatures + '@typescript-eslint/no-empty-function': 'off', // stub agents + '@typescript-eslint/only-throw-error': 'off', // testing non-Error throws + '@typescript-eslint/no-namespace': 'off', + '@typescript-eslint/no-empty-object-type': 'off', + '@typescript-eslint/restrict-template-expressions': 'off', + '@typescript-eslint/no-unused-vars': ['error', { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + caughtErrorsIgnorePattern: '^_', + }], + }, + }, + + // --- formatting (everything we own) ------------------------------------- + { + files: ['packages/**/*.ts', 'examples/**/*.ts', 'scripts/**/*.ts', 'eslint.config.mjs'], + plugins: { '@stylistic': stylistic }, + rules: { + '@stylistic/indent': ['error', 2], + '@stylistic/semi': ['error', 'never'], + '@stylistic/quotes': ['error', 'single', { avoidEscape: true }], + '@stylistic/comma-dangle': ['error', 'always-multiline'], + '@stylistic/eol-last': ['error', 'always'], + '@stylistic/no-trailing-spaces': 'error', + '@stylistic/object-curly-spacing': ['error', 'always'], + '@stylistic/arrow-parens': ['error', 'as-needed', { requireForBlockBody: true }], + '@stylistic/member-delimiter-style': ['error', { + multiline: { delimiter: 'none' }, + singleline: { delimiter: 'semi', requireLast: false }, + }], + '@stylistic/max-len': ['error', { code: 140, ignoreUrls: true, ignoreStrings: true, ignoreTemplateLiterals: true }], + }, + }, +) diff --git a/examples/echo-agent/src/stdio-chat.ts b/examples/echo-agent/src/stdio-chat.ts index 79c0498518..4160d43c80 100644 --- a/examples/echo-agent/src/stdio-chat.ts +++ b/examples/echo-agent/src/stdio-chat.ts @@ -55,6 +55,6 @@ export function apply(ctx: Context) { setTimeout(() => process.exit(0), 200) }) process.stdout.write('echo-agent ready. Type a message ("echo " triggers the tool).\n> ') - return () => reader.close() + return () => { reader.close() } }, 'stdio-chat') } diff --git a/package.json b/package.json index 1b3ba475b1..0898fb6418 100644 --- a/package.json +++ b/package.json @@ -14,14 +14,19 @@ "scripts": { "build": "tsc -b tsconfig.build.json && tsx scripts/build.ts", "typecheck": "tsc -b tsconfig.build.json && tsc -p tsconfig.typecheck.json", + "lint": "eslint .", + "lint:fix": "eslint . --fix", "test": "vitest run", "demo": "node --expose-internals --import tsx examples/echo-agent/start.ts" }, "devDependencies": { + "@stylistic/eslint-plugin": "^5.10.0", "@types/node": "^25.3.5", "dumble": "^0.2.3", + "eslint": "^10.4.1", "tsx": "^4.22.4", "typescript": "^6.0.3", + "typescript-eslint": "^8.61.0", "vite-tsconfig-paths": "^6.1.1", "vitest": "^4.1.8" } diff --git a/packages/agent-loop/src/agent.ts b/packages/agent-loop/src/agent.ts index 3edabb0405..3c9a2ad914 100644 --- a/packages/agent-loop/src/agent.ts +++ b/packages/agent-loop/src/agent.ts @@ -65,7 +65,7 @@ export class LoopAgent implements Agent { steer(content: ContentBlock[], options?: SendOptions): void { if (this._status === 'disposed') throw new Error(`agent "${this.id}" is disposed`) - if (this._status !== 'running') return this.send(content, options) + if (this._status !== 'running') { this.send(content, options); return } const source = this.resolveSource(options) this.inbox.steer({ content, source }) this.ctx.emit('agent/queued', this, content, { source, steering: true }) @@ -88,7 +88,7 @@ export class LoopAgent implements Agent { */ start(): () => void { this.done = runLoop(this.ctx, this, { - setStatus: status => this.setStatus(status), + setStatus: (status) => { this.setStatus(status) }, setAbort: controller => void (this.currentAbort = controller), disposed: this.disposed, isDisposed: () => this._status === 'disposed', diff --git a/packages/agent-loop/src/loop.ts b/packages/agent-loop/src/loop.ts index ffe17965fe..9ae6270d86 100644 --- a/packages/agent-loop/src/loop.ts +++ b/packages/agent-loop/src/loop.ts @@ -113,7 +113,9 @@ async function runTurn(ctx: Context, agent: LoopAgent, handle: LoopHandle, turn: // Drain queued messages into the session — they trigger this turn. const queued = agent.inbox.drainQueued() - const trigger: TurnTrigger = { kind: 'message', source: queued[0]!.source } + const first = queued[0] + if (!first) throw new Error('runTurn invariant violated: no queued message at turn start') + const trigger: TurnTrigger = { kind: 'message', source: first.source } for (const message of queued) { session.append('user/message', { content: message.content, source: message.source }) } @@ -177,7 +179,7 @@ async function runTurn(ctx: Context, agent: LoopAgent, handle: LoopHandle, turn: try { shouldContinue = await ctx.waterfall( 'agent/turn-continuation', agent, turn, defaultDecision, - async () => defaultDecision, + () => Promise.resolve(defaultDecision), ) } catch (error: unknown) { // A broken continuation plugin ends the turn, not the loop. @@ -246,7 +248,7 @@ async function runStep( ...assembly.tools.length > 0 ? { tools: assembly.tools } : {}, signal, } - request = await ctx.waterfall('agent/request', agent, turn, step, request, async () => request) + request = await ctx.waterfall('agent/request', agent, turn, step, request, () => Promise.resolve(request)) if (!request.model) { throw new Error(`agent "${agent.id}" has no model: set AgentOptions.model or supply one via the agent/request waterfall`) } @@ -264,7 +266,7 @@ async function runStep( // source of truth for derived history and replay) records the message that // tool dispatch actually uses. let message: Message = assembler.message() - message = await ctx.waterfall('agent/step-result', agent, turn, step, message, async () => message) + message = await ctx.waterfall('agent/step-result', agent, turn, step, message, () => Promise.resolve(message)) session.append('assistant/message', { turn, step, content: message.content }) if (assembler.usage) { @@ -297,6 +299,9 @@ async function runStep( content: result.content, isError: result.isError, }) + // signal CAN flip during the await above (abort() inside a tool); + // the analyzer can't see through the await boundary. + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (signal.aborted) throw new Error(String(signal.reason ?? 'aborted')) } @@ -305,9 +310,6 @@ async function runStep( /** The last turn number in a (possibly seeded) session log, or 0. */ function lastTurnNumber(session: Session): number { - for (let index = session.events.length - 1; index >= 0; index--) { - const event = session.events[index]! - if (event.type === 'turn/start') return event.data.turn - } - return 0 + const lastStart = session.events.findLast(event => event.type === 'turn/start') + return lastStart?.data.turn ?? 0 } diff --git a/packages/agent-loop/tests/loop.spec.ts b/packages/agent-loop/tests/loop.spec.ts index 0638e30b9f..6f50872079 100644 --- a/packages/agent-loop/tests/loop.spec.ts +++ b/packages/agent-loop/tests/loop.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest' import { Context } from 'cordis' -import LlmService, { StreamChunk, ToolResultBlock } from '@deepseek-ai/dsh-llm' -import SessionStore, { SessionEventType, TurnEndReason } from '@deepseek-ai/dsh-session' +import LlmService, { StreamChunk } from '@deepseek-ai/dsh-llm' +import SessionStore, { TurnEndReason } from '@deepseek-ai/dsh-session' import SystemPrompt from '@deepseek-ai/dsh-system-prompt' import ToolRegistry, { defineTool } from '@deepseek-ai/dsh-tools' import AgentRegistry from '@deepseek-ai/dsh-agent' @@ -99,7 +99,7 @@ describe('agent loop', () => { expect(toolResultMessage).toBeDefined() const block = toolResultMessage!.content.find(b => b.type === 'tool-result')! expect(block).toMatchObject({ toolCallId: 'c1', isError: false }) - expect((block as ToolResultBlock).content).toEqual([{ type: 'text', text: 'echo: ping' }]) + expect((block).content).toEqual([{ type: 'text', text: 'echo: ping' }]) // session log records call + result const types = agent.session.events.map(e => e.type) @@ -380,7 +380,7 @@ describe('agent loop', () => { expect(agent.status).toBe('disposed') expect(ctx.agents.get('scoped')).toBeUndefined() - expect(() => send(agent, 'too late')).toThrow('disposed') + expect(() => { send(agent, 'too late') }).toThrow('disposed') }) it('replays a session log into an identical derived history', async () => { @@ -405,6 +405,6 @@ describe('agent loop', () => { expect(replayed.deriveMessages()).toEqual(agent.session.deriveMessages()) // event-by-event identity of types expect(replayed.events.map(e => e.type)).toEqual( - agent.session.events.map(e => e.type as SessionEventType)) + agent.session.events.map(e => e.type)) }) }) diff --git a/packages/agent-loop/tests/mock-adapter.ts b/packages/agent-loop/tests/mock-adapter.ts index 36591d9c00..416f3b444d 100644 --- a/packages/agent-loop/tests/mock-adapter.ts +++ b/packages/agent-loop/tests/mock-adapter.ts @@ -5,7 +5,7 @@ import { LlmAdapter } from '@deepseek-ai/dsh-llm' export function textResponse(text: string): StreamChunk[] { return [ { type: 'block-start', index: 0, blockType: 'text' }, - ...[...text].map((char): StreamChunk => ({ type: 'text-delta', index: 0, text: char })), + ...Array.from(text, (char): StreamChunk => ({ type: 'text-delta', index: 0, text: char })), { type: 'block-end', index: 0, block: { type: 'text', text } }, { type: 'usage', usage: { inputTokens: 10, outputTokens: text.length } }, { type: 'finish', reason: { kind: 'stop' } }, @@ -60,8 +60,8 @@ export class MockAdapter extends LlmAdapter { yield { type: 'block-start', index: 0, blockType: 'text' } yield { type: 'text-delta', index: 0, text: 'partial' } await new Promise((_resolve, reject) => { - if (options.signal?.aborted) return reject(new Error('aborted')) - options.signal?.addEventListener('abort', () => reject(new Error('aborted')), { once: true }) + if (options.signal?.aborted) { reject(new Error('aborted')); return } + options.signal?.addEventListener('abort', () => { reject(new Error('aborted')) }, { once: true }) }) return } diff --git a/packages/agent/src/index.ts b/packages/agent/src/index.ts index 95aca7311d..a4d20b93a4 100644 --- a/packages/agent/src/index.ts +++ b/packages/agent/src/index.ts @@ -35,7 +35,7 @@ export class AgentRegistry extends Service { * when the calling fiber is disposed. Returns the disposer. */ register(agent: Agent): () => void { - return this.ctx.effect(() => { + const dispose = this.ctx.effect(() => { if (this.store.has(agent.id)) { throw new Error(`agent "${agent.id}" is already registered`) } @@ -46,6 +46,9 @@ export class AgentRegistry extends Service { this.ctx.emit('agent/disposed', agent) } }, 'agents.register()') + // ctx.effect's disposer returns Promise; our disposer API is + // synchronous fire-and-forget — discard the (always-resolved) promise. + return () => void dispose() } get(id: string): Agent | undefined { diff --git a/packages/llm/src/assembler.ts b/packages/llm/src/assembler.ts index 636c90e16c..91d1606421 100644 --- a/packages/llm/src/assembler.ts +++ b/packages/llm/src/assembler.ts @@ -109,9 +109,16 @@ export class BlockAssembler { } } + /** Invariant accessor: every index in `order` has a partial. */ + private mustGet(index: number): PartialBlock { + const partial = this.partials.get(index) + if (!partial) throw new Error(`BlockAssembler invariant violated: no partial for index ${index}`) + return partial + } + /** Assemble all blocks seen so far, in stream order. */ blocks(): ContentBlock[] { - return this.order.map(index => this.assemble(this.partials.get(index)!, index)) + return this.order.map(index => this.assemble(this.mustGet(index), index)) } /** @@ -123,8 +130,9 @@ export class BlockAssembler { flushReady(): ContentBlock[] { const ready: ContentBlock[] = [] while (this.flushed < this.order.length) { - const index = this.order[this.flushed]! - const partial = this.partials.get(index)! + const index = this.order[this.flushed] + if (index === undefined) break + const partial = this.mustGet(index) if (!partial.block) break ready.push(partial.block) this.flushed += 1 @@ -141,8 +149,9 @@ export class BlockAssembler { flushRemaining(): ContentBlock[] { const remaining: ContentBlock[] = [] while (this.flushed < this.order.length) { - const index = this.order[this.flushed]! - remaining.push(this.assemble(this.partials.get(index)!, index)) + const index = this.order[this.flushed] + if (index === undefined) break + remaining.push(this.assemble(this.mustGet(index), index)) this.flushed += 1 } return remaining diff --git a/packages/llm/src/index.ts b/packages/llm/src/index.ts index 578d08ac7b..9a1bad3f29 100644 --- a/packages/llm/src/index.ts +++ b/packages/llm/src/index.ts @@ -69,7 +69,7 @@ export class LlmService extends Service { * fiber. */ registerAdapter(models: string[], adapter: LlmAdapter): () => void { - return this.ctx.effect(() => { + const dispose = this.ctx.effect(() => { for (const model of models) { if (this.adapters.has(model)) { throw new LlmError(`an adapter for model "${model}" is already registered`, 'DUPLICATE_ADAPTER') @@ -82,6 +82,9 @@ export class LlmService extends Service { this.ctx.emit('llm/adapter-change') } }, 'llm.registerAdapter()') + // ctx.effect's disposer returns Promise; our disposer API is + // synchronous fire-and-forget — discard the (always-resolved) promise. + return () => void dispose() } /** Model names with a registered adapter. */ diff --git a/packages/session/src/index.ts b/packages/session/src/index.ts index 9a16d25217..b19fc47336 100644 --- a/packages/session/src/index.ts +++ b/packages/session/src/index.ts @@ -97,6 +97,10 @@ export class Session { deriveMessages(): Message[] { const messages: Message[] = [] for (const event of this.log) { + // Intentionally non-exhaustive: only message-producing events derive + // history; turn/step boundaries, chunks, usage, and errors are + // trace/replay data. + // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check switch (event.type) { case 'user/message': { messages.push({ role: 'user', content: event.data.content }) @@ -155,7 +159,7 @@ export class SessionStore extends Service { if (this.store.has(id)) throw new Error(`session "${id}" already exists`) const session = new Session(id, seed) this.ctx.effect(() => { - session.onAppend = (event) => this.ctx.emit('session/event', session, event) + session.onAppend = (event) => { this.ctx.emit('session/event', session, event) } this.store.set(id, session) this.ctx.emit('session/created', session) return () => { diff --git a/packages/system-prompt/src/index.ts b/packages/system-prompt/src/index.ts index 212020b48c..a4d62156f8 100644 --- a/packages/system-prompt/src/index.ts +++ b/packages/system-prompt/src/index.ts @@ -73,7 +73,7 @@ export class SystemPrompt extends Service { * fiber is disposed. Emits `system-prompt/change` on register/unregister. */ section(section: PromptSection): () => void { - return this.ctx.effect(() => { + const dispose = this.ctx.effect(() => { this.sections.push(section) this.ctx.emit('system-prompt/change') return () => { @@ -82,6 +82,9 @@ export class SystemPrompt extends Service { this.ctx.emit('system-prompt/change') } }, 'systemPrompt.section()') + // ctx.effect's disposer returns Promise; our disposer API is + // synchronous fire-and-forget — discard the (always-resolved) promise. + return () => void dispose() } /** @@ -90,7 +93,7 @@ export class SystemPrompt extends Service { * removed when the calling fiber is disposed. Emits `system-prompt/change`. */ tools(provider: () => ToolSchema[]): () => void { - return this.ctx.effect(() => { + const dispose = this.ctx.effect(() => { this.toolProviders.push(provider) this.ctx.emit('system-prompt/change') return () => { @@ -99,6 +102,9 @@ export class SystemPrompt extends Service { this.ctx.emit('system-prompt/change') } }, 'systemPrompt.tools()') + // ctx.effect's disposer returns Promise; our disposer API is + // synchronous fire-and-forget — discard the (always-resolved) promise. + return () => void dispose() } /** @@ -113,7 +119,7 @@ export class SystemPrompt extends Service { sections: [...this.sections].sort((a, b) => a.order - b.order), tools: this.toolProviders.flatMap(provider => provider()), } - return this.ctx.waterfall(this, 'system-prompt/assemble', assembly, async () => assembly) + return this.ctx.waterfall(this, 'system-prompt/assemble', assembly, () => Promise.resolve(assembly)) } } diff --git a/packages/system-prompt/tests/system-prompt.spec.ts b/packages/system-prompt/tests/system-prompt.spec.ts index 3e4b1a5f77..7d733ae357 100644 --- a/packages/system-prompt/tests/system-prompt.spec.ts +++ b/packages/system-prompt/tests/system-prompt.spec.ts @@ -8,7 +8,7 @@ describe('SystemPrompt', () => { await ctx.plugin(SystemPrompt) ctx.systemPrompt.section({ name: 'persona', order: 0, text: 'You are DeepSeek Code.' }) - ctx.systemPrompt.section({ name: 'cwd', order: 20, text: () => `cwd: /tmp` }) + ctx.systemPrompt.section({ name: 'cwd', order: 20, text: () => 'cwd: /tmp' }) ctx.systemPrompt.section({ name: 'rules', order: 10, text: 'Be precise.' }) ctx.systemPrompt.tools(() => [{ name: 'echo', description: 'echo back', parameters: {} }]) diff --git a/packages/tools/src/index.ts b/packages/tools/src/index.ts index 057f0397f7..05b84dda3d 100644 --- a/packages/tools/src/index.ts +++ b/packages/tools/src/index.ts @@ -107,7 +107,7 @@ export class ToolRegistry extends Service { * with the calling fiber. Emits `tools/change` on register/unregister. */ register(definition: ToolDefinition): () => void { - return this.ctx.effect(() => { + const dispose = this.ctx.effect(() => { if (this.store.has(definition.name)) { throw new Error(`tool "${definition.name}" is already registered`) } @@ -118,6 +118,9 @@ export class ToolRegistry extends Service { this.ctx.emit('tools/change') } }, 'tools.register()') + // ctx.effect's disposer returns Promise; our disposer API is + // synchronous fire-and-forget — discard the (always-resolved) promise. + return () => void dispose() } get(name: string): ToolDefinition | undefined { @@ -130,6 +133,8 @@ export class ToolRegistry extends Service { * assembly. */ schemas(): ToolSchema[] { + // Rest-destructure to drop `execute`; the unused binding is the idiom. + // eslint-disable-next-line @typescript-eslint/unbound-method, @typescript-eslint/no-unused-vars return [...this.store.values()].map(({ execute, ...schema }) => schema) } diff --git a/packages/tools/src/schema.ts b/packages/tools/src/schema.ts index c6190b423e..1ebf3d9b5c 100644 --- a/packages/tools/src/schema.ts +++ b/packages/tools/src/schema.ts @@ -60,11 +60,11 @@ export type SchemaSpec = Record /** Map a {@link SchemaType} to its TS primitive type. */ type TypeOf = T extends 'string' ? string : - T extends 'number' ? number : - T extends 'boolean' ? boolean : - T extends 'object' ? Record : - T extends 'array' ? unknown[] : - never + T extends 'number' ? number : + T extends 'boolean' ? boolean : + T extends 'object' ? Record : + T extends 'array' ? unknown[] : + never /** Flatten an intersection into one object type for readable hovers. */ type Simplify = { [K in keyof T]: T[K] } & {} @@ -82,8 +82,8 @@ type RequiredKeys = */ type InferPropValue

= P extends { type: 'object'; properties: infer Sub extends SchemaSpec } ? InferArgs : - P extends { type: 'array'; items: infer Item extends SchemaProp } ? InferPropValue[] : - TypeOf + P extends { type: 'array'; items: infer Item extends SchemaProp } ? InferPropValue[] : + TypeOf /** * Infer the TS argument type for a complete {@link SchemaSpec}. @@ -117,7 +117,7 @@ function propToJsonSchema(prop: SchemaProp): { schema: Record; if (prop.enum) result.enum = prop.enum if (prop.default !== undefined) result.default = prop.default - let required = prop.required === true + const required = prop.required === true if (prop.type === 'object' && prop.properties) { const nested = schemaSpecToJsonSchema(prop.properties) @@ -224,6 +224,9 @@ export function defineTool(options: DefineToolOptions): description: options.description, parameters: schemaSpecToJsonSchema(options.parameters) as unknown as Record, ...options.strict !== undefined ? { strict: options.strict } : {}, - execute: options.execute as ToolDefinition['execute'], + // Object-literal execute methods don't use `this`; passing the reference + // through is safe. + // eslint-disable-next-line @typescript-eslint/unbound-method + execute: options.execute, } } diff --git a/packages/tools/tests/tools.spec.ts b/packages/tools/tests/tools.spec.ts index 3e308daaaf..4c3d045e56 100644 --- a/packages/tools/tests/tools.spec.ts +++ b/packages/tools/tests/tools.spec.ts @@ -18,7 +18,7 @@ const echoTool = defineTool({ description: 'echo arguments back', parameters: { text: { type: 'string' } }, async execute(args) { - return [{ type: 'text' as const, text: String(args.text ?? '') }] + return [{ type: 'text' as const, text: args.text ?? '' }] }, }) @@ -372,7 +372,7 @@ describe('schema DSL regressions (Codex review round 2)', () => { ...echoTool, name: 'object-thrower', async execute() { - // eslint-disable-next-line no-throw-literal — testing non-Error throws + // testing non-Error throws on purpose throw { message: 'denied by object' } }, }) diff --git a/yarn.lock b/yarn.lock index 38b53f594a..55025cd126 100644 --- a/yarn.lock +++ b/yarn.lock @@ -142,10 +142,13 @@ __metadata: version: 0.0.0-use.local resolution: "@deepseek-ai/dsh-root@workspace:." dependencies: + "@stylistic/eslint-plugin": "npm:^5.10.0" "@types/node": "npm:^25.3.5" dumble: "npm:^0.2.3" + eslint: "npm:^10.4.1" tsx: "npm:^4.22.4" typescript: "npm:^6.0.3" + typescript-eslint: "npm:^8.61.0" vite-tsconfig-paths: "npm:^6.1.1" vitest: "npm:^4.1.8" languageName: unknown @@ -401,6 +404,111 @@ __metadata: languageName: node linkType: hard +"@eslint-community/eslint-utils@npm:^4.8.0, @eslint-community/eslint-utils@npm:^4.9.1": + version: 4.9.1 + resolution: "@eslint-community/eslint-utils@npm:4.9.1" + dependencies: + eslint-visitor-keys: "npm:^3.4.3" + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + checksum: 10c0/dc4ab5e3e364ef27e33666b11f4b86e1a6c1d7cbf16f0c6ff87b1619b3562335e9201a3d6ce806221887ff780ec9d828962a290bb910759fd40a674686503f02 + languageName: node + linkType: hard + +"@eslint-community/regexpp@npm:^4.12.2": + version: 4.12.2 + resolution: "@eslint-community/regexpp@npm:4.12.2" + checksum: 10c0/fddcbc66851b308478d04e302a4d771d6917a0b3740dc351513c0da9ca2eab8a1adf99f5e0aa7ab8b13fa0df005c81adeee7e63a92f3effd7d367a163b721c2d + languageName: node + linkType: hard + +"@eslint/config-array@npm:^0.23.5": + version: 0.23.5 + resolution: "@eslint/config-array@npm:0.23.5" + dependencies: + "@eslint/object-schema": "npm:^3.0.5" + debug: "npm:^4.3.1" + minimatch: "npm:^10.2.4" + checksum: 10c0/b24833c4c76e78ee075d306cd3f095db46b2db0f90cc13a6ee6e4275f9889731c05bf5403ab5fefb79c756e07ac9184ed0e04570341382f9eccbccc80e6d1a0c + languageName: node + linkType: hard + +"@eslint/config-helpers@npm:^0.6.0": + version: 0.6.0 + resolution: "@eslint/config-helpers@npm:0.6.0" + dependencies: + "@eslint/core": "npm:^1.2.1" + checksum: 10c0/f9af20e8b60b0ba27edb74b8eb40c0c5d51a9bf9baf9e053bb57833a87cb0a1c49b4dfaad88fc24d49c907ad1324c8a0b668684fa9c321351dac4bc9155ec10a + languageName: node + linkType: hard + +"@eslint/core@npm:^1.2.1": + version: 1.2.1 + resolution: "@eslint/core@npm:1.2.1" + dependencies: + "@types/json-schema": "npm:^7.0.15" + checksum: 10c0/10979b40588ecfef771fcb5013a542a35fb30692cc95a65f3481b0b36fbd89f5679efeb30d57f4eed35203d859aabace2a620177d6c536f71b299a1af2f3398f + languageName: node + linkType: hard + +"@eslint/object-schema@npm:^3.0.5": + version: 3.0.5 + resolution: "@eslint/object-schema@npm:3.0.5" + checksum: 10c0/1db337431f520b99e9edda64ef5fafd7ec6a029843eeb608753025125b6649d861d843cffafafd3c4e37926d7d5f9ec0c6a8e3665c13c3da2144e8132892e92e + languageName: node + linkType: hard + +"@eslint/plugin-kit@npm:^0.7.2": + version: 0.7.2 + resolution: "@eslint/plugin-kit@npm:0.7.2" + dependencies: + "@eslint/core": "npm:^1.2.1" + levn: "npm:^0.4.1" + checksum: 10c0/aafba08077bcd6d7dde6c2e21db18086046a88f914f29971a84cac9ad2d48952ded1b293e665e523805297eff756522dafa16f0062195e2c7143dcd1d47d11ed + languageName: node + linkType: hard + +"@humanfs/core@npm:^0.19.2": + version: 0.19.2 + resolution: "@humanfs/core@npm:0.19.2" + dependencies: + "@humanfs/types": "npm:^0.15.0" + checksum: 10c0/d0a1d52d7b30c27d49475a53072d1510b81c5803e44b342fb8faf3887f1aa27593a1e6dc76a45268e7892d3f4e198146659281f6b6d55eacf3fd5a38bac30c5c + languageName: node + linkType: hard + +"@humanfs/node@npm:^0.16.6": + version: 0.16.8 + resolution: "@humanfs/node@npm:0.16.8" + dependencies: + "@humanfs/core": "npm:^0.19.2" + "@humanfs/types": "npm:^0.15.0" + "@humanwhocodes/retry": "npm:^0.4.0" + checksum: 10c0/56140579db811af4e160b195d45d0f29acf644d192c93fe24c9e594ebf06f19dfc157494a07c84540b8a071c0e4b37209c2362765d31734f4d0be869c2422e25 + languageName: node + linkType: hard + +"@humanfs/types@npm:^0.15.0": + version: 0.15.0 + resolution: "@humanfs/types@npm:0.15.0" + checksum: 10c0/fc26b9a024b0e55f7eaf64036df94345bf5d36d6a41ef80ef38e78f1f7430ce26cf435af736adae58913baae18eac3f38c18739054a3d379102015978eae862e + languageName: node + linkType: hard + +"@humanwhocodes/module-importer@npm:^1.0.1": + version: 1.0.1 + resolution: "@humanwhocodes/module-importer@npm:1.0.1" + checksum: 10c0/909b69c3b86d482c26b3359db16e46a32e0fb30bd306a3c176b8313b9e7313dba0f37f519de6aa8b0a1921349e505f259d19475e123182416a506d7f87e7f529 + languageName: node + linkType: hard + +"@humanwhocodes/retry@npm:^0.4.0, @humanwhocodes/retry@npm:^0.4.2": + version: 0.4.3 + resolution: "@humanwhocodes/retry@npm:0.4.3" + checksum: 10c0/3775bb30087d4440b3f7406d5a057777d90e4b9f435af488a4923ef249e93615fb78565a85f173a186a076c7706a81d0d57d563a2624e4de2c5c9c66c486ce42 + languageName: node + linkType: hard + "@isaacs/fs-minipass@npm:^4.0.0": version: 4.0.1 resolution: "@isaacs/fs-minipass@npm:4.0.1" @@ -593,6 +701,22 @@ __metadata: languageName: node linkType: hard +"@stylistic/eslint-plugin@npm:^5.10.0": + version: 5.10.0 + resolution: "@stylistic/eslint-plugin@npm:5.10.0" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.9.1" + "@typescript-eslint/types": "npm:^8.56.0" + eslint-visitor-keys: "npm:^4.2.1" + espree: "npm:^10.4.0" + estraverse: "npm:^5.3.0" + picomatch: "npm:^4.0.3" + peerDependencies: + eslint: ^9.0.0 || ^10.0.0 + checksum: 10c0/2cc6e592d5b2ab691290cc39ed377c0f6226667b367ffc6a0bf3a4bf6dd36b011d17c83f377600a48946f6bb037390b35a8b3c273b3c40d161c00b44512a129e + languageName: node + linkType: hard + "@tybys/wasm-util@npm:^0.10.2": version: 0.10.2 resolution: "@tybys/wasm-util@npm:0.10.2" @@ -626,13 +750,27 @@ __metadata: languageName: node linkType: hard -"@types/estree@npm:^1.0.0": +"@types/esrecurse@npm:^4.3.1": + version: 4.3.1 + resolution: "@types/esrecurse@npm:4.3.1" + checksum: 10c0/90dad74d5da3ad27606d8e8e757322f33171cfeaa15ad558b615cf71bb2a516492d18f55f4816384685a3eb2412142e732bbae9a4a7cd2cf3deb7572aa4ebe03 + languageName: node + linkType: hard + +"@types/estree@npm:^1.0.0, @types/estree@npm:^1.0.6, @types/estree@npm:^1.0.8": version: 1.0.9 resolution: "@types/estree@npm:1.0.9" checksum: 10c0/3ad3286ca2988cd550dafb8f2ad599c8474868e954fa601a36655bdfefd8039f7c714b8c1c7f2ae219ffbd58bd4660e66fa7479a0120fc02d4777057d4865387 languageName: node linkType: hard +"@types/json-schema@npm:^7.0.15": + version: 7.0.15 + resolution: "@types/json-schema@npm:7.0.15" + checksum: 10c0/a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db + languageName: node + linkType: hard + "@types/node@npm:^25.3.5": version: 25.9.2 resolution: "@types/node@npm:25.9.2" @@ -649,6 +787,141 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/eslint-plugin@npm:8.61.0": + version: 8.61.0 + resolution: "@typescript-eslint/eslint-plugin@npm:8.61.0" + dependencies: + "@eslint-community/regexpp": "npm:^4.12.2" + "@typescript-eslint/scope-manager": "npm:8.61.0" + "@typescript-eslint/type-utils": "npm:8.61.0" + "@typescript-eslint/utils": "npm:8.61.0" + "@typescript-eslint/visitor-keys": "npm:8.61.0" + ignore: "npm:^7.0.5" + natural-compare: "npm:^1.4.0" + ts-api-utils: "npm:^2.5.0" + peerDependencies: + "@typescript-eslint/parser": ^8.61.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/1141253e18424a9a21d253dcf28e166894b6b914f2138b3e016144e451385f8e23f0f02028c7bd2d21c81953c52e478657aa9e5888cd0bdffdb8d68aab736878 + languageName: node + linkType: hard + +"@typescript-eslint/parser@npm:8.61.0": + version: 8.61.0 + resolution: "@typescript-eslint/parser@npm:8.61.0" + dependencies: + "@typescript-eslint/scope-manager": "npm:8.61.0" + "@typescript-eslint/types": "npm:8.61.0" + "@typescript-eslint/typescript-estree": "npm:8.61.0" + "@typescript-eslint/visitor-keys": "npm:8.61.0" + debug: "npm:^4.4.3" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/39b122ab20a3b5fbd4e66874f60917b37c49f32eb987531797561d2f96b443e81546f1c9d03d44d37dede89098276e5d8d0f05c1e5f9e1b998f8cf6c24e8e5e7 + languageName: node + linkType: hard + +"@typescript-eslint/project-service@npm:8.61.0": + version: 8.61.0 + resolution: "@typescript-eslint/project-service@npm:8.61.0" + dependencies: + "@typescript-eslint/tsconfig-utils": "npm:^8.61.0" + "@typescript-eslint/types": "npm:^8.61.0" + debug: "npm:^4.4.3" + peerDependencies: + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/8ff86b93bfcf103a42e8e996e11c46ded83da07d3a0bc8bd9ec4d536116d7f6253a404786510ab13847e69d6e185b17d15d7140075c26966e9b4f85c03296f21 + languageName: node + linkType: hard + +"@typescript-eslint/scope-manager@npm:8.61.0": + version: 8.61.0 + resolution: "@typescript-eslint/scope-manager@npm:8.61.0" + dependencies: + "@typescript-eslint/types": "npm:8.61.0" + "@typescript-eslint/visitor-keys": "npm:8.61.0" + checksum: 10c0/76cdf1c181ebbc706ddc8b2366e8ebfda529c13d82ff10c0797c96c0b38dd82f6471b24995f58ac267194a753b23d77452d925dd615b1e651922ddbe6e451c6b + languageName: node + linkType: hard + +"@typescript-eslint/tsconfig-utils@npm:8.61.0, @typescript-eslint/tsconfig-utils@npm:^8.61.0": + version: 8.61.0 + resolution: "@typescript-eslint/tsconfig-utils@npm:8.61.0" + peerDependencies: + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/b498675f14ef90a5730de7c58388eb2522085a56c3fcad42ad9f89320b96221eafb5b4f9650375f29092025153d03533e3f23ea8f45ce3bc95a57593059edef3 + languageName: node + linkType: hard + +"@typescript-eslint/type-utils@npm:8.61.0": + version: 8.61.0 + resolution: "@typescript-eslint/type-utils@npm:8.61.0" + dependencies: + "@typescript-eslint/types": "npm:8.61.0" + "@typescript-eslint/typescript-estree": "npm:8.61.0" + "@typescript-eslint/utils": "npm:8.61.0" + debug: "npm:^4.4.3" + ts-api-utils: "npm:^2.5.0" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/6347f451301ca7089500fe6eb3b98e5efd769e56ffda07eb735130fd209b9053c02e952b6fda7a15acf7851fb63f11fc50166ba8bd90513480732c599644b36b + languageName: node + linkType: hard + +"@typescript-eslint/types@npm:8.61.0, @typescript-eslint/types@npm:^8.56.0, @typescript-eslint/types@npm:^8.61.0": + version: 8.61.0 + resolution: "@typescript-eslint/types@npm:8.61.0" + checksum: 10c0/c19407d66fb5ad26e2670cd272bee91d150087d917752422257759e17920220af27cd54593205e9726367a440a237bf8d27ed805cae0b282a79172161f007207 + languageName: node + linkType: hard + +"@typescript-eslint/typescript-estree@npm:8.61.0": + version: 8.61.0 + resolution: "@typescript-eslint/typescript-estree@npm:8.61.0" + dependencies: + "@typescript-eslint/project-service": "npm:8.61.0" + "@typescript-eslint/tsconfig-utils": "npm:8.61.0" + "@typescript-eslint/types": "npm:8.61.0" + "@typescript-eslint/visitor-keys": "npm:8.61.0" + debug: "npm:^4.4.3" + minimatch: "npm:^10.2.2" + semver: "npm:^7.7.3" + tinyglobby: "npm:^0.2.15" + ts-api-utils: "npm:^2.5.0" + peerDependencies: + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/460819feeca826bfd895f821a5008c3eaa79b9495259641976fdc6ec319a7e9587bc28603437ea3d9a10c3b28037f1dea883cbe8d2858616dd33847e8db2179e + languageName: node + linkType: hard + +"@typescript-eslint/utils@npm:8.61.0": + version: 8.61.0 + resolution: "@typescript-eslint/utils@npm:8.61.0" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.9.1" + "@typescript-eslint/scope-manager": "npm:8.61.0" + "@typescript-eslint/types": "npm:8.61.0" + "@typescript-eslint/typescript-estree": "npm:8.61.0" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/f7b2241fc4defd40107243642e26697193707be12af1552c60bc414e71df1285c9cdff429f913b30ed08ae87a7e6e13388eaf05c1be5fb8310f6a63a6c4f7f73 + languageName: node + linkType: hard + +"@typescript-eslint/visitor-keys@npm:8.61.0": + version: 8.61.0 + resolution: "@typescript-eslint/visitor-keys@npm:8.61.0" + dependencies: + "@typescript-eslint/types": "npm:8.61.0" + eslint-visitor-keys: "npm:^5.0.0" + checksum: 10c0/5b656aed426a92dfc9a481f0bf535ceb47321303f476f32ba979f73423c739b51d7a5ad76c81d7be9df0a9beb361f4a11ff530dd86d59f41b89bb5f09af7be9f + languageName: node + linkType: hard + "@vitest/expect@npm:4.1.8": version: 4.1.8 resolution: "@vitest/expect@npm:4.1.8" @@ -738,6 +1011,36 @@ __metadata: languageName: node linkType: hard +"acorn-jsx@npm:^5.3.2": + version: 5.3.2 + resolution: "acorn-jsx@npm:5.3.2" + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: 10c0/4c54868fbef3b8d58927d5e33f0a4de35f59012fe7b12cf9dfbb345fb8f46607709e1c4431be869a23fb63c151033d84c4198fa9f79385cec34fcb1dd53974c1 + languageName: node + linkType: hard + +"acorn@npm:^8.15.0, acorn@npm:^8.16.0": + version: 8.16.0 + resolution: "acorn@npm:8.16.0" + bin: + acorn: bin/acorn + checksum: 10c0/c9c52697227661b68d0debaf972222d4f622aa06b185824164e153438afa7b08273432ca43ea792cadb24dada1d46f6f6bb1ef8de9956979288cc1b96bf9914e + languageName: node + linkType: hard + +"ajv@npm:^6.14.0": + version: 6.15.0 + resolution: "ajv@npm:6.15.0" + dependencies: + fast-deep-equal: "npm:^3.1.1" + fast-json-stable-stringify: "npm:^2.0.0" + json-schema-traverse: "npm:^0.4.1" + uri-js: "npm:^4.2.2" + checksum: 10c0/67966499dd272ecde1c2e467084411132891523d057487587879d39ac04207f4351b7b2324c83198013967fbfa632c1612adc960114a30770fbe07a0773b32c2 + languageName: node + linkType: hard + "argparse@npm:^2.0.1": version: 2.0.1 resolution: "argparse@npm:2.0.1" @@ -752,6 +1055,22 @@ __metadata: languageName: node linkType: hard +"balanced-match@npm:^4.0.2": + version: 4.0.4 + resolution: "balanced-match@npm:4.0.4" + checksum: 10c0/07e86102a3eb2ee2a6a1a89164f29d0dbaebd28f2ca3f5ca786f36b8b23d9e417eb3be45a4acf754f837be5ac0a2317de90d3fcb7f4f4dc95720a1f36b26a17b + languageName: node + linkType: hard + +"brace-expansion@npm:^5.0.5": + version: 5.0.6 + resolution: "brace-expansion@npm:5.0.6" + dependencies: + balanced-match: "npm:^4.0.2" + checksum: 10c0/8c919869b90f61d533b341d3340be5ee4413232ea89b8246cbc2f38eb014f1d8182785c98a006eaf6111d02dc9eeffefdc240d5ac158625b2ed084dccd4bbf9b + languageName: node + linkType: hard + "braces@npm:^3.0.3": version: 3.0.3 resolution: "braces@npm:3.0.3" @@ -823,7 +1142,18 @@ __metadata: languageName: unknown linkType: soft -"debug@npm:^4.1.1": +"cross-spawn@npm:^7.0.6": + version: 7.0.6 + resolution: "cross-spawn@npm:7.0.6" + dependencies: + path-key: "npm:^3.1.0" + shebang-command: "npm:^2.0.0" + which: "npm:^2.0.1" + checksum: 10c0/053ea8b2135caff68a9e81470e845613e374e7309a47731e81639de3eaeb90c3d01af0e0b44d2ab9d50b43467223b88567dfeb3262db942dc063b9976718ffc1 + languageName: node + linkType: hard + +"debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.4.3": version: 4.4.3 resolution: "debug@npm:4.4.3" dependencies: @@ -835,6 +1165,13 @@ __metadata: languageName: node linkType: hard +"deep-is@npm:^0.1.3": + version: 0.1.4 + resolution: "deep-is@npm:0.1.4" + checksum: 10c0/7f0ee496e0dff14a573dc6127f14c95061b448b87b995fc96c017ce0a1e66af1675e73f1d6064407975bc4ea6ab679497a29fff7b5b9c4e99cb10797c1ad0b4c + languageName: node + linkType: hard + "detect-libc@npm:^2.0.3": version: 2.1.2 resolution: "detect-libc@npm:2.1.2" @@ -963,6 +1300,138 @@ __metadata: languageName: node linkType: hard +"escape-string-regexp@npm:^4.0.0": + version: 4.0.0 + resolution: "escape-string-regexp@npm:4.0.0" + checksum: 10c0/9497d4dd307d845bd7f75180d8188bb17ea8c151c1edbf6b6717c100e104d629dc2dfb687686181b0f4b7d732c7dfdc4d5e7a8ff72de1b0ca283a75bbb3a9cd9 + languageName: node + linkType: hard + +"eslint-scope@npm:^9.1.2": + version: 9.1.2 + resolution: "eslint-scope@npm:9.1.2" + dependencies: + "@types/esrecurse": "npm:^4.3.1" + "@types/estree": "npm:^1.0.8" + esrecurse: "npm:^4.3.0" + estraverse: "npm:^5.2.0" + checksum: 10c0/9fb8bca5a73e5741efb6cec84467027b6cb6f4203ff9b43a938e272c5cd30800bde46a5c20dfd1609f840225f0b62b7673be391b20acadf8658ca9fa4729b3dd + languageName: node + linkType: hard + +"eslint-visitor-keys@npm:^3.4.3": + version: 3.4.3 + resolution: "eslint-visitor-keys@npm:3.4.3" + checksum: 10c0/92708e882c0a5ffd88c23c0b404ac1628cf20104a108c745f240a13c332a11aac54f49a22d5762efbffc18ecbc9a580d1b7ad034bf5f3cc3307e5cbff2ec9820 + languageName: node + linkType: hard + +"eslint-visitor-keys@npm:^4.2.1": + version: 4.2.1 + resolution: "eslint-visitor-keys@npm:4.2.1" + checksum: 10c0/fcd43999199d6740db26c58dbe0c2594623e31ca307e616ac05153c9272f12f1364f5a0b1917a8e962268fdecc6f3622c1c2908b4fcc2e047a106fe6de69dc43 + languageName: node + linkType: hard + +"eslint-visitor-keys@npm:^5.0.0, eslint-visitor-keys@npm:^5.0.1": + version: 5.0.1 + resolution: "eslint-visitor-keys@npm:5.0.1" + checksum: 10c0/16190bdf2cbae40a1109384c94450c526a79b0b9c3cb21e544256ed85ac48a4b84db66b74a6561d20fe6ab77447f150d711c2ad5ad74df4fcc133736bce99678 + languageName: node + linkType: hard + +"eslint@npm:^10.4.1": + version: 10.4.1 + resolution: "eslint@npm:10.4.1" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.8.0" + "@eslint-community/regexpp": "npm:^4.12.2" + "@eslint/config-array": "npm:^0.23.5" + "@eslint/config-helpers": "npm:^0.6.0" + "@eslint/core": "npm:^1.2.1" + "@eslint/plugin-kit": "npm:^0.7.2" + "@humanfs/node": "npm:^0.16.6" + "@humanwhocodes/module-importer": "npm:^1.0.1" + "@humanwhocodes/retry": "npm:^0.4.2" + "@types/estree": "npm:^1.0.6" + ajv: "npm:^6.14.0" + cross-spawn: "npm:^7.0.6" + debug: "npm:^4.3.2" + escape-string-regexp: "npm:^4.0.0" + eslint-scope: "npm:^9.1.2" + eslint-visitor-keys: "npm:^5.0.1" + espree: "npm:^11.2.0" + esquery: "npm:^1.7.0" + esutils: "npm:^2.0.2" + fast-deep-equal: "npm:^3.1.3" + file-entry-cache: "npm:^8.0.0" + find-up: "npm:^5.0.0" + glob-parent: "npm:^6.0.2" + ignore: "npm:^5.2.0" + imurmurhash: "npm:^0.1.4" + is-glob: "npm:^4.0.0" + json-stable-stringify-without-jsonify: "npm:^1.0.1" + minimatch: "npm:^10.2.4" + natural-compare: "npm:^1.4.0" + optionator: "npm:^0.9.3" + peerDependencies: + jiti: "*" + peerDependenciesMeta: + jiti: + optional: true + bin: + eslint: bin/eslint.js + checksum: 10c0/c5e6bb5158fb0d62f090c8e2671de5c98283bbb37a58b6d871bada63af3018e683483c96c1a92272fedc8f4e5279a273a0451acf0da67c487406639daa05aedb + languageName: node + linkType: hard + +"espree@npm:^10.4.0": + version: 10.4.0 + resolution: "espree@npm:10.4.0" + dependencies: + acorn: "npm:^8.15.0" + acorn-jsx: "npm:^5.3.2" + eslint-visitor-keys: "npm:^4.2.1" + checksum: 10c0/c63fe06131c26c8157b4083313cb02a9a54720a08e21543300e55288c40e06c3fc284bdecf108d3a1372c5934a0a88644c98714f38b6ae8ed272b40d9ea08d6b + languageName: node + linkType: hard + +"espree@npm:^11.2.0": + version: 11.2.0 + resolution: "espree@npm:11.2.0" + dependencies: + acorn: "npm:^8.16.0" + acorn-jsx: "npm:^5.3.2" + eslint-visitor-keys: "npm:^5.0.1" + checksum: 10c0/cf87e18ffd9dc113eb8d16588e7757701bc10c9934a71cce8b89c2611d51672681a918307bd6b19ac3ccd0e7ba1cbccc2f815b36b52fa7e73097b251014c3d81 + languageName: node + linkType: hard + +"esquery@npm:^1.7.0": + version: 1.7.0 + resolution: "esquery@npm:1.7.0" + dependencies: + estraverse: "npm:^5.1.0" + checksum: 10c0/77d5173db450b66f3bc685d11af4c90cffeedb340f34a39af96d43509a335ce39c894fd79233df32d38f5e4e219fa0f7076f6ec90bae8320170ba082c0db4793 + languageName: node + linkType: hard + +"esrecurse@npm:^4.3.0": + version: 4.3.0 + resolution: "esrecurse@npm:4.3.0" + dependencies: + estraverse: "npm:^5.2.0" + checksum: 10c0/81a37116d1408ded88ada45b9fb16dbd26fba3aadc369ce50fcaf82a0bac12772ebd7b24cd7b91fc66786bf2c1ac7b5f196bc990a473efff972f5cb338877cf5 + languageName: node + linkType: hard + +"estraverse@npm:^5.1.0, estraverse@npm:^5.2.0, estraverse@npm:^5.3.0": + version: 5.3.0 + resolution: "estraverse@npm:5.3.0" + checksum: 10c0/1ff9447b96263dec95d6d67431c5e0771eb9776427421260a3e2f0fdd5d6bd4f8e37a7338f5ad2880c9f143450c9b1e4fc2069060724570a49cf9cf0312bd107 + languageName: node + linkType: hard + "estree-walker@npm:^3.0.3": version: 3.0.3 resolution: "estree-walker@npm:3.0.3" @@ -972,6 +1441,13 @@ __metadata: languageName: node linkType: hard +"esutils@npm:^2.0.2": + version: 2.0.3 + resolution: "esutils@npm:2.0.3" + checksum: 10c0/9a2fe69a41bfdade834ba7c42de4723c97ec776e40656919c62cbd13607c45e127a003f05f724a1ea55e5029a4cf2de444b13009f2af71271e42d93a637137c7 + languageName: node + linkType: hard + "expect-type@npm:^1.3.0": version: 1.3.0 resolution: "expect-type@npm:1.3.0" @@ -986,6 +1462,13 @@ __metadata: languageName: node linkType: hard +"fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": + version: 3.1.3 + resolution: "fast-deep-equal@npm:3.1.3" + checksum: 10c0/40dedc862eb8992c54579c66d914635afbec43350afbbe991235fdcb4e3a8d5af1b23ae7e79bef7d4882d0ecee06c3197488026998fb19f72dc95acff1d1b1d0 + languageName: node + linkType: hard + "fast-glob@npm:^3.3.3": version: 3.3.3 resolution: "fast-glob@npm:3.3.3" @@ -999,6 +1482,20 @@ __metadata: languageName: node linkType: hard +"fast-json-stable-stringify@npm:^2.0.0": + version: 2.1.0 + resolution: "fast-json-stable-stringify@npm:2.1.0" + checksum: 10c0/7f081eb0b8a64e0057b3bb03f974b3ef00135fbf36c1c710895cd9300f13c94ba809bb3a81cf4e1b03f6e5285610a61abbd7602d0652de423144dfee5a389c9b + languageName: node + linkType: hard + +"fast-levenshtein@npm:^2.0.6": + version: 2.0.6 + resolution: "fast-levenshtein@npm:2.0.6" + checksum: 10c0/111972b37338bcb88f7d9e2c5907862c280ebf4234433b95bc611e518d192ccb2d38119c4ac86e26b668d75f7f3894f4ff5c4982899afced7ca78633b08287c4 + languageName: node + linkType: hard + "fastq@npm:^1.6.0": version: 1.20.1 resolution: "fastq@npm:1.20.1" @@ -1020,6 +1517,15 @@ __metadata: languageName: node linkType: hard +"file-entry-cache@npm:^8.0.0": + version: 8.0.0 + resolution: "file-entry-cache@npm:8.0.0" + dependencies: + flat-cache: "npm:^4.0.0" + checksum: 10c0/9e2b5938b1cd9b6d7e3612bdc533afd4ac17b2fc646569e9a8abbf2eb48e5eb8e316bc38815a3ef6a1b456f4107f0d0f055a614ca613e75db6bf9ff4d72c1638 + languageName: node + linkType: hard + "fill-range@npm:^7.1.1": version: 7.1.1 resolution: "fill-range@npm:7.1.1" @@ -1029,6 +1535,33 @@ __metadata: languageName: node linkType: hard +"find-up@npm:^5.0.0": + version: 5.0.0 + resolution: "find-up@npm:5.0.0" + dependencies: + locate-path: "npm:^6.0.0" + path-exists: "npm:^4.0.0" + checksum: 10c0/062c5a83a9c02f53cdd6d175a37ecf8f87ea5bbff1fdfb828f04bfa021441bc7583e8ebc0872a4c1baab96221fb8a8a275a19809fb93fbc40bd69ec35634069a + languageName: node + linkType: hard + +"flat-cache@npm:^4.0.0": + version: 4.0.1 + resolution: "flat-cache@npm:4.0.1" + dependencies: + flatted: "npm:^3.2.9" + keyv: "npm:^4.5.4" + checksum: 10c0/2c59d93e9faa2523e4fda6b4ada749bed432cfa28c8e251f33b25795e426a1c6dbada777afb1f74fcfff33934fdbdea921ee738fcc33e71adc9d6eca984a1cfc + languageName: node + linkType: hard + +"flatted@npm:^3.2.9": + version: 3.4.2 + resolution: "flatted@npm:3.4.2" + checksum: 10c0/a65b67aae7172d6cdf63691be7de6c5cd5adbdfdfe2e9da1a09b617c9512ed794037741ee53d93114276bff3f93cd3b0d97d54f9b316e1e4885dde6e9ffdf7ed + languageName: node + linkType: hard + "fsevents@npm:~2.3.3": version: 2.3.3 resolution: "fsevents@npm:2.3.3" @@ -1057,6 +1590,15 @@ __metadata: languageName: node linkType: hard +"glob-parent@npm:^6.0.2": + version: 6.0.2 + resolution: "glob-parent@npm:6.0.2" + dependencies: + is-glob: "npm:^4.0.3" + checksum: 10c0/317034d88654730230b3f43bb7ad4f7c90257a426e872ea0bf157473ac61c99bf5d205fad8f0185f989be8d2fa6d3c7dce1645d99d545b6ea9089c39f838e7f8 + languageName: node + linkType: hard + "globby@npm:^14.1.0": version: 14.1.0 resolution: "globby@npm:14.1.0" @@ -1085,13 +1627,27 @@ __metadata: languageName: node linkType: hard -"ignore@npm:^7.0.3": +"ignore@npm:^5.2.0": + version: 5.3.2 + resolution: "ignore@npm:5.3.2" + checksum: 10c0/f9f652c957983634ded1e7f02da3b559a0d4cc210fca3792cb67f1b153623c9c42efdc1c4121af171e295444459fc4a9201101fb041b1104a3c000bccb188337 + languageName: node + linkType: hard + +"ignore@npm:^7.0.3, ignore@npm:^7.0.5": version: 7.0.5 resolution: "ignore@npm:7.0.5" checksum: 10c0/ae00db89fe873064a093b8999fe4cc284b13ef2a178636211842cceb650b9c3e390d3339191acb145d81ed5379d2074840cf0c33a20bdbd6f32821f79eb4ad5d languageName: node linkType: hard +"imurmurhash@npm:^0.1.4": + version: 0.1.4 + resolution: "imurmurhash@npm:0.1.4" + checksum: 10c0/8b51313850dd33605c6c9d3fd9638b714f4c4c40250cff658209f30d40da60f78992fb2df5dabee4acf589a6a82bbc79ad5486550754bd9ec4e3fc0d4a57d6a6 + languageName: node + linkType: hard + "is-extglob@npm:^2.1.1": version: 2.1.1 resolution: "is-extglob@npm:2.1.1" @@ -1099,7 +1655,7 @@ __metadata: languageName: node linkType: hard -"is-glob@npm:^4.0.1": +"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3": version: 4.0.3 resolution: "is-glob@npm:4.0.3" dependencies: @@ -1115,6 +1671,13 @@ __metadata: languageName: node linkType: hard +"isexe@npm:^2.0.0": + version: 2.0.0 + resolution: "isexe@npm:2.0.0" + checksum: 10c0/228cfa503fadc2c31596ab06ed6aa82c9976eec2bfd83397e7eaf06d0ccf42cd1dfd6743bf9aeb01aebd4156d009994c5f76ea898d2832c1fe342da923ca457d + languageName: node + linkType: hard + "isexe@npm:^4.0.0": version: 4.0.0 resolution: "isexe@npm:4.0.0" @@ -1140,6 +1703,27 @@ __metadata: languageName: node linkType: hard +"json-buffer@npm:3.0.1": + version: 3.0.1 + resolution: "json-buffer@npm:3.0.1" + checksum: 10c0/0d1c91569d9588e7eef2b49b59851f297f3ab93c7b35c7c221e288099322be6b562767d11e4821da500f3219542b9afd2e54c5dc573107c1126ed1080f8e96d7 + languageName: node + linkType: hard + +"json-schema-traverse@npm:^0.4.1": + version: 0.4.1 + resolution: "json-schema-traverse@npm:0.4.1" + checksum: 10c0/108fa90d4cc6f08243aedc6da16c408daf81793bf903e9fd5ab21983cda433d5d2da49e40711da016289465ec2e62e0324dcdfbc06275a607fe3233fde4942ce + languageName: node + linkType: hard + +"json-stable-stringify-without-jsonify@npm:^1.0.1": + version: 1.0.1 + resolution: "json-stable-stringify-without-jsonify@npm:1.0.1" + checksum: 10c0/cb168b61fd4de83e58d09aaa6425ef71001bae30d260e2c57e7d09a5fd82223e2f22a042dedaab8db23b7d9ae46854b08bb1f91675a8be11c5cffebef5fb66a5 + languageName: node + linkType: hard + "json5@npm:^2.2.3": version: 2.2.3 resolution: "json5@npm:2.2.3" @@ -1149,6 +1733,15 @@ __metadata: languageName: node linkType: hard +"keyv@npm:^4.5.4": + version: 4.5.4 + resolution: "keyv@npm:4.5.4" + dependencies: + json-buffer: "npm:3.0.1" + checksum: 10c0/aa52f3c5e18e16bb6324876bb8b59dd02acf782a4b789c7b2ae21107fab95fab3890ed448d4f8dba80ce05391eeac4bfabb4f02a20221342982f806fa2cf271e + languageName: node + linkType: hard + "kleur@npm:^4.1.5": version: 4.1.5 resolution: "kleur@npm:4.1.5" @@ -1156,6 +1749,16 @@ __metadata: languageName: node linkType: hard +"levn@npm:^0.4.1": + version: 0.4.1 + resolution: "levn@npm:0.4.1" + dependencies: + prelude-ls: "npm:^1.2.1" + type-check: "npm:~0.4.0" + checksum: 10c0/effb03cad7c89dfa5bd4f6989364bfc79994c2042ec5966cb9b95990e2edee5cd8969ddf42616a0373ac49fac1403437deaf6e9050fbbaa3546093a59b9ac94e + languageName: node + linkType: hard + "lightningcss-android-arm64@npm:1.32.0": version: 1.32.0 resolution: "lightningcss-android-arm64@npm:1.32.0" @@ -1276,6 +1879,15 @@ __metadata: languageName: node linkType: hard +"locate-path@npm:^6.0.0": + version: 6.0.0 + resolution: "locate-path@npm:6.0.0" + dependencies: + p-locate: "npm:^5.0.0" + checksum: 10c0/d3972ab70dfe58ce620e64265f90162d247e87159b6126b01314dd67be43d50e96a50b517bce2d9452a79409c7614054c277b5232377de50416564a77ac7aad3 + languageName: node + linkType: hard + "magic-string@npm:^0.30.21": version: 0.30.21 resolution: "magic-string@npm:0.30.21" @@ -1302,6 +1914,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:^10.2.2, minimatch@npm:^10.2.4": + version: 10.2.5 + resolution: "minimatch@npm:10.2.5" + dependencies: + brace-expansion: "npm:^5.0.5" + checksum: 10c0/6bb058bd6324104b9ec2f763476a35386d05079c1f5fe4fbf1f324a25237cd4534d6813ecd71f48208f4e635c1221899bef94c3c89f7df55698fe373aaae20fd + languageName: node + linkType: hard + "minipass@npm:^7.0.4, minipass@npm:^7.1.2": version: 7.1.3 resolution: "minipass@npm:7.1.3" @@ -1334,6 +1955,13 @@ __metadata: languageName: node linkType: hard +"natural-compare@npm:^1.4.0": + version: 1.4.0 + resolution: "natural-compare@npm:1.4.0" + checksum: 10c0/f5f9a7974bfb28a91afafa254b197f0f22c684d4a1731763dda960d2c8e375b36c7d690e0d9dc8fba774c537af14a7e979129bca23d88d052fbeb9466955e447 + languageName: node + linkType: hard + "node-gyp@npm:latest": version: 12.4.0 resolution: "node-gyp@npm:12.4.0" @@ -1372,6 +2000,52 @@ __metadata: languageName: node linkType: hard +"optionator@npm:^0.9.3": + version: 0.9.4 + resolution: "optionator@npm:0.9.4" + dependencies: + deep-is: "npm:^0.1.3" + fast-levenshtein: "npm:^2.0.6" + levn: "npm:^0.4.1" + prelude-ls: "npm:^1.2.1" + type-check: "npm:^0.4.0" + word-wrap: "npm:^1.2.5" + checksum: 10c0/4afb687a059ee65b61df74dfe87d8d6815cd6883cb8b3d5883a910df72d0f5d029821f37025e4bccf4048873dbdb09acc6d303d27b8f76b1a80dd5a7d5334675 + languageName: node + linkType: hard + +"p-limit@npm:^3.0.2": + version: 3.1.0 + resolution: "p-limit@npm:3.1.0" + dependencies: + yocto-queue: "npm:^0.1.0" + checksum: 10c0/9db675949dbdc9c3763c89e748d0ef8bdad0afbb24d49ceaf4c46c02c77d30db4e0652ed36d0a0a7a95154335fab810d95c86153105bb73b3a90448e2bb14e1a + languageName: node + linkType: hard + +"p-locate@npm:^5.0.0": + version: 5.0.0 + resolution: "p-locate@npm:5.0.0" + dependencies: + p-limit: "npm:^3.0.2" + checksum: 10c0/2290d627ab7903b8b70d11d384fee714b797f6040d9278932754a6860845c4d3190603a0772a663c8cb5a7b21d1b16acb3a6487ebcafa9773094edc3dfe6009a + languageName: node + linkType: hard + +"path-exists@npm:^4.0.0": + version: 4.0.0 + resolution: "path-exists@npm:4.0.0" + checksum: 10c0/8c0bd3f5238188197dc78dced15207a4716c51cc4e3624c44fc97acf69558f5ebb9a2afff486fe1b4ee148e0c133e96c5e11a9aa5c48a3006e3467da070e5e1b + languageName: node + linkType: hard + +"path-key@npm:^3.1.0": + version: 3.1.1 + resolution: "path-key@npm:3.1.1" + checksum: 10c0/748c43efd5a569c039d7a00a03b58eecd1d75f3999f5a28303d75f521288df4823bc057d8784eb72358b2895a05f29a070bc9f1f17d28226cc4e62494cc58c4c + languageName: node + linkType: hard + "path-type@npm:^6.0.0": version: 6.0.0 resolution: "path-type@npm:6.0.0" @@ -1418,6 +2092,13 @@ __metadata: languageName: node linkType: hard +"prelude-ls@npm:^1.2.1": + version: 1.2.1 + resolution: "prelude-ls@npm:1.2.1" + checksum: 10c0/b00d617431e7886c520a6f498a2e14c75ec58f6d93ba48c3b639cf241b54232d90daa05d83a9e9b9fef6baa63cb7e1e4602c2372fea5bc169668401eb127d0cd + languageName: node + linkType: hard + "proc-log@npm:^6.0.0": version: 6.1.0 resolution: "proc-log@npm:6.1.0" @@ -1425,6 +2106,13 @@ __metadata: languageName: node linkType: hard +"punycode@npm:^2.1.0": + version: 2.3.1 + resolution: "punycode@npm:2.3.1" + checksum: 10c0/14f76a8206bc3464f794fb2e3d3cc665ae416c01893ad7a02b23766eb07159144ee612ad67af5e84fa4479ccfe67678c4feb126b0485651b302babf66f04f9e9 + languageName: node + linkType: hard + "queue-microtask@npm:^1.2.2": version: 1.2.3 resolution: "queue-microtask@npm:1.2.3" @@ -1522,7 +2210,7 @@ __metadata: languageName: unknown linkType: soft -"semver@npm:^7.3.5": +"semver@npm:^7.3.5, semver@npm:^7.7.3": version: 7.8.4 resolution: "semver@npm:7.8.4" bin: @@ -1531,6 +2219,22 @@ __metadata: languageName: node linkType: hard +"shebang-command@npm:^2.0.0": + version: 2.0.0 + resolution: "shebang-command@npm:2.0.0" + dependencies: + shebang-regex: "npm:^3.0.0" + checksum: 10c0/a41692e7d89a553ef21d324a5cceb5f686d1f3c040759c50aab69688634688c5c327f26f3ecf7001ebfd78c01f3c7c0a11a7c8bfd0a8bc9f6240d4f40b224e4e + languageName: node + linkType: hard + +"shebang-regex@npm:^3.0.0": + version: 3.0.0 + resolution: "shebang-regex@npm:3.0.0" + checksum: 10c0/1dbed0726dd0e1152a92696c76c7f06084eb32a90f0528d11acd764043aacf76994b2fb30aa1291a21bd019d6699164d048286309a278855ee7bec06cf6fb690 + languageName: node + linkType: hard + "siginfo@npm:^2.0.0": version: 2.0.0 resolution: "siginfo@npm:2.0.0" @@ -1626,6 +2330,15 @@ __metadata: languageName: node linkType: hard +"ts-api-utils@npm:^2.5.0": + version: 2.5.0 + resolution: "ts-api-utils@npm:2.5.0" + peerDependencies: + typescript: ">=4.8.4" + checksum: 10c0/767849383c114e7f1971fa976b20e73ac28fd0c70d8d65c0004790bf4d8f89888c7e4cf6d5949f9c1beae9bc3c64835bef77bbe27fddf45a3c7b60cebcf85c8c + languageName: node + linkType: hard + "tsconfck@npm:^3.0.3": version: 3.1.6 resolution: "tsconfck@npm:3.1.6" @@ -1673,6 +2386,30 @@ __metadata: languageName: node linkType: hard +"type-check@npm:^0.4.0, type-check@npm:~0.4.0": + version: 0.4.0 + resolution: "type-check@npm:0.4.0" + dependencies: + prelude-ls: "npm:^1.2.1" + checksum: 10c0/7b3fd0ed43891e2080bf0c5c504b418fbb3e5c7b9708d3d015037ba2e6323a28152ec163bcb65212741fa5d2022e3075ac3c76440dbd344c9035f818e8ecee58 + languageName: node + linkType: hard + +"typescript-eslint@npm:^8.61.0": + version: 8.61.0 + resolution: "typescript-eslint@npm:8.61.0" + dependencies: + "@typescript-eslint/eslint-plugin": "npm:8.61.0" + "@typescript-eslint/parser": "npm:8.61.0" + "@typescript-eslint/typescript-estree": "npm:8.61.0" + "@typescript-eslint/utils": "npm:8.61.0" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/172ba723c7ea07e5b22541ca3e8eb1b5e0b837e21278a915a293a2d5e7cac7ce9f0ad73cd61a3cdec98a511c76586eb3865d1581f2acc0b691e007703d4764b2 + languageName: node + linkType: hard + "typescript@npm:^6.0.3": version: 6.0.3 resolution: "typescript@npm:6.0.3" @@ -1714,6 +2451,15 @@ __metadata: languageName: node linkType: hard +"uri-js@npm:^4.2.2": + version: 4.4.1 + resolution: "uri-js@npm:4.4.1" + dependencies: + punycode: "npm:^2.1.0" + checksum: 10c0/4ef57b45aa820d7ac6496e9208559986c665e49447cb072744c13b66925a362d96dd5a46c4530a6b8e203e5db5fe849369444440cb22ecfc26c679359e5dfa3c + languageName: node + linkType: hard + "vite-tsconfig-paths@npm:^6.1.1": version: 6.1.1 resolution: "vite-tsconfig-paths@npm:6.1.1" @@ -1852,6 +2598,17 @@ __metadata: languageName: node linkType: hard +"which@npm:^2.0.1": + version: 2.0.2 + resolution: "which@npm:2.0.2" + dependencies: + isexe: "npm:^2.0.0" + bin: + node-which: ./bin/node-which + checksum: 10c0/66522872a768b60c2a65a57e8ad184e5372f5b6a9ca6d5f033d4b0dc98aff63995655a7503b9c0a2598936f532120e81dd8cc155e2e92ed662a2b9377cc4374f + languageName: node + linkType: hard + "which@npm:^6.0.0": version: 6.0.1 resolution: "which@npm:6.0.1" @@ -1875,9 +2632,23 @@ __metadata: languageName: node linkType: hard +"word-wrap@npm:^1.2.5": + version: 1.2.5 + resolution: "word-wrap@npm:1.2.5" + checksum: 10c0/e0e4a1ca27599c92a6ca4c32260e8a92e8a44f4ef6ef93f803f8ed823f486e0889fc0b93be4db59c8d51b3064951d25e43d434e95dc8c960cc3a63d65d00ba20 + languageName: node + linkType: hard + "yallist@npm:^5.0.0": version: 5.0.0 resolution: "yallist@npm:5.0.0" checksum: 10c0/a499c81ce6d4a1d260d4ea0f6d49ab4da09681e32c3f0472dee16667ed69d01dae63a3b81745a24bd78476ec4fcf856114cb4896ace738e01da34b2c42235416 languageName: node linkType: hard + +"yocto-queue@npm:^0.1.0": + version: 0.1.0 + resolution: "yocto-queue@npm:0.1.0" + checksum: 10c0/dceb44c28578b31641e13695d200d34ec4ab3966a5729814d5445b194933c096b7ced71494ce53a0e8820685d1d010df8b2422e5bf2cdea7e469d97ffbea306f + languageName: node + linkType: hard