Add ESLint: typescript-eslint strict-type-checked + stylistic formatting
Flat config with two layers. Correctness (type-checked): the headline rules for this codebase are no-floating-promises / no-misused-promises (a lost promise in the agent loop is our primary bug class), switch-exhaustiveness-check (we switch over merge-extensible unions everywhere), no-unnecessary-condition, require-await, and no-explicit-any. Style (@stylistic): 2-space, no semicolons, single quotes, trailing commas, max-len 140 — the existing house style, now enforced instead of drifting between agents. vendor/ is excluded (vendored source keeps upstream style); tests relax the rules that fight test ergonomics (non-null assertions after expects, async mock signatures, non-Error throws). Code adjusted to pass: registry disposers wrap ctx.effect's promise-returning disposer behind a sync () => void (our public API), BlockAssembler gains an invariant-checking mustGet instead of non-null assertions, lastTurnNumber uses findLast, waterfall tails return Promise.resolve instead of async-without-await arrows, and the two deliberate suppressions (non-exhaustive derivation switch, unbound execute pass-through) carry justification comments. yarn lint / yarn lint:fix added.
This commit is contained in:
@@ -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<void>; 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)
|
||||
}
|
||||
|
||||
|
||||
@@ -60,11 +60,11 @@ export type SchemaSpec = Record<string, SchemaProp>
|
||||
/** Map a {@link SchemaType} to its TS primitive type. */
|
||||
type TypeOf<T extends SchemaType> =
|
||||
T extends 'string' ? string :
|
||||
T extends 'number' ? number :
|
||||
T extends 'boolean' ? boolean :
|
||||
T extends 'object' ? Record<string, unknown> :
|
||||
T extends 'array' ? unknown[] :
|
||||
never
|
||||
T extends 'number' ? number :
|
||||
T extends 'boolean' ? boolean :
|
||||
T extends 'object' ? Record<string, unknown> :
|
||||
T extends 'array' ? unknown[] :
|
||||
never
|
||||
|
||||
/** Flatten an intersection into one object type for readable hovers. */
|
||||
type Simplify<T> = { [K in keyof T]: T[K] } & {}
|
||||
@@ -82,8 +82,8 @@ type RequiredKeys<S extends SchemaSpec> =
|
||||
*/
|
||||
type InferPropValue<P extends SchemaProp> =
|
||||
P extends { type: 'object'; properties: infer Sub extends SchemaSpec } ? InferArgs<Sub> :
|
||||
P extends { type: 'array'; items: infer Item extends SchemaProp } ? InferPropValue<Item>[] :
|
||||
TypeOf<P['type']>
|
||||
P extends { type: 'array'; items: infer Item extends SchemaProp } ? InferPropValue<Item>[] :
|
||||
TypeOf<P['type']>
|
||||
|
||||
/**
|
||||
* Infer the TS argument type for a complete {@link SchemaSpec}.
|
||||
@@ -117,7 +117,7 @@ function propToJsonSchema(prop: SchemaProp): { schema: Record<string, unknown>;
|
||||
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<S extends SchemaSpec>(options: DefineToolOptions<S>):
|
||||
description: options.description,
|
||||
parameters: schemaSpecToJsonSchema(options.parameters) as unknown as Record<string, unknown>,
|
||||
...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,
|
||||
}
|
||||
}
|
||||
@@ -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' }
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user