diff --git a/.github/workflows/validate-all.yml b/.github/workflows/validate-all.yml index 3b4d8a7..c1d756a 100644 --- a/.github/workflows/validate-all.yml +++ b/.github/workflows/validate-all.yml @@ -91,6 +91,20 @@ jobs: working-directory: afi-reactor run: npm ci + # D8: the reactor↔afi-core boundary is genuinely typechecked now (strict, + # real afi-core dist/*.d.ts, no ambient `any` shims). `npm run build` only + # compiles src/, and ts-jest only checks the suites jest actually runs, so + # this step is what keeps the FULL typecheck config honest β€” including the + # test tree. If it fails, do not re-add a shim: fix resolution, or install + # the package's @types. See reports/afi-d8-type-boundary-v0.1.md. + - name: Typecheck (src + tests, strict) + id: typecheck + working-directory: afi-reactor + run: | + echo "πŸ”Ž Typechecking AFI Reactor (strict, afi-core boundary included)..." + npm run typecheck + echo "βœ… Typecheck clean" + - name: Build id: build working-directory: afi-reactor diff --git a/package-lock.json b/package-lock.json index d2c28f7..44c2d35 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "zod": "^4.0.14" }, "devDependencies": { + "@types/express": "^5.0.6", "@types/jest": "^30.0.0", "@types/supertest": "^6.0.3", "jest": "^29.0.0", @@ -2544,6 +2545,17 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, "node_modules/@types/chai": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", @@ -2555,6 +2567,16 @@ "assertion-error": "^2.0.1" } }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/cookiejar": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", @@ -2576,6 +2598,31 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.3.tgz", + "integrity": "sha512-dPfW8NFiOF4wOHc7+N/QSxlY9cfSsenewGbAz8C8U/MULPd/YZ27LvJUIlzaXie7e6Ove9YunJGgC9tbHD2cKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -2586,6 +2633,13 @@ "@types/node": "*" } }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -2650,6 +2704,41 @@ "undici-types": "~8.3.0" } }, + "node_modules/@types/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" + } + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", diff --git a/package.json b/package.json index 410dcd7..7edce9d 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "elizaos": "./src" }, "scripts": { - "build": "tsc", + "build": "tsc -p tsconfig.build.json", "typecheck": "tsc --noEmit", "start:demo": "node dist/src/server.js", "test": "jest && node test/operational/captureOutcomesDerivation.test.mjs", @@ -24,6 +24,7 @@ "esm:check": "bash scripts/esm-check.sh" }, "devDependencies": { + "@types/express": "^5.0.6", "@types/jest": "^30.0.0", "@types/supertest": "^6.0.3", "jest": "^29.0.0", diff --git a/src/adapters/exchanges/blofinPriceFeedAdapter.ts b/src/adapters/exchanges/blofinPriceFeedAdapter.ts index e172c6e..b45df04 100644 --- a/src/adapters/exchanges/blofinPriceFeedAdapter.ts +++ b/src/adapters/exchanges/blofinPriceFeedAdapter.ts @@ -12,7 +12,7 @@ * @see https://docs.ccxt.com/ */ -import ccxt from "ccxt"; +import ccxt, { type OHLCV } from "ccxt"; import type { PriceFeedAdapter, OHLCVCandle, TickerSnapshot } from "./types.js"; import { toVenueSymbol } from "../symbolRegistry.js"; @@ -28,7 +28,13 @@ class BloFinPriceFeedAdapter implements PriceFeedAdapter { public readonly supportsPerps = true; public readonly supportsSpot = true; - private exchange: any; // ccxt.Exchange type + // D8-R2: this `any` is why the OHLCV mapping below type-checks at all. + // Typing it `ccxt.Exchange` surfaces that ccxt declares every OHLCV + // element as `Num` (number | undefined) and `ticker.symbol` as `Str`, + // neither of which satisfies our OHLCVCandle/TickerSnapshot contracts. + // Left as-is deliberately: tightening it forces a reject-or-default + // decision that changes scoring behaviour. See the D8 report. + private exchange: any; // ccxt.Exchange constructor() { // Initialize ccxt BloFin exchange @@ -93,8 +99,14 @@ class BloFinPriceFeedAdapter implements PriceFeedAdapter { limit ); - // Transform ccxt format to our OHLCVCandle type - const candles: OHLCVCandle[] = ohlcvData.map((candle) => ({ + // Transform ccxt format to our OHLCVCandle type. + // ccxt types every OHLCV element as `Num` (number | undefined) β€” a venue + // MAY return a partial candle β€” while OHLCVCandle requires `number`. The + // assignment still compiles only because `exchange` below is `any`, so + // presence is asserted, not checked. That is the pre-existing runtime + // contract and is deliberately left unchanged: rejecting or defaulting a + // partial candle would alter scoring behaviour. Recorded as D8-R2. + const candles: OHLCVCandle[] = ohlcvData.map((candle: OHLCV) => ({ timestamp: candle[0], open: candle[1], high: candle[2], diff --git a/src/adapters/exchanges/coinbasePriceFeedAdapter.ts b/src/adapters/exchanges/coinbasePriceFeedAdapter.ts index a4b6b32..00dcc71 100644 --- a/src/adapters/exchanges/coinbasePriceFeedAdapter.ts +++ b/src/adapters/exchanges/coinbasePriceFeedAdapter.ts @@ -15,7 +15,7 @@ * @see https://docs.ccxt.com/ */ -import ccxt from "ccxt"; +import ccxt, { type OHLCV } from "ccxt"; import type { PriceFeedAdapter, OHLCVCandle, TickerSnapshot } from "./types.js"; import { toVenueSymbol } from "../symbolRegistry.js"; @@ -31,7 +31,13 @@ class CoinbasePriceFeedAdapter implements PriceFeedAdapter { public readonly supportsPerps = false; // Coinbase spot only public readonly supportsSpot = true; - private exchange: any; // ccxt.Exchange type + // D8-R2: this `any` is why the OHLCV mapping below type-checks at all. + // Typing it `ccxt.Exchange` surfaces that ccxt declares every OHLCV + // element as `Num` (number | undefined) and `ticker.symbol` as `Str`, + // neither of which satisfies our OHLCVCandle/TickerSnapshot contracts. + // Left as-is deliberately: tightening it forces a reject-or-default + // decision that changes scoring behaviour. See the D8 report. + private exchange: any; // ccxt.Exchange constructor() { // Initialize ccxt Coinbase exchange @@ -84,8 +90,14 @@ class CoinbasePriceFeedAdapter implements PriceFeedAdapter { limit ); - // Transform ccxt format to our OHLCVCandle type - const candles: OHLCVCandle[] = ohlcvData.map((candle) => ({ + // Transform ccxt format to our OHLCVCandle type. + // ccxt types every OHLCV element as `Num` (number | undefined) β€” a venue + // MAY return a partial candle β€” while OHLCVCandle requires `number`. The + // assignment still compiles only because `exchange` below is `any`, so + // presence is asserted, not checked. That is the pre-existing runtime + // contract and is deliberately left unchanged: rejecting or defaulting a + // partial candle would alter scoring behaviour. Recorded as D8-R2. + const candles: OHLCVCandle[] = ohlcvData.map((candle: OHLCV) => ({ timestamp: candle[0], open: candle[1], high: candle[2], diff --git a/src/pipeline/nodes/mergeEnrichedView.ts b/src/pipeline/nodes/mergeEnrichedView.ts index 9660868..58226f5 100644 --- a/src/pipeline/nodes/mergeEnrichedView.ts +++ b/src/pipeline/nodes/mergeEnrichedView.ts @@ -32,6 +32,7 @@ import type { SupportedLens, } from "../../types/UssLenses.js"; import type { NewsFeatures } from "../../news/newsFeatures.js"; +import type { AfiMarketType, VenueType } from "../../utils/marketUtils.js"; import { ok, type AnalysisNodePlugin, @@ -53,6 +54,36 @@ export interface MergeNodeInput { parents: Record; } +/** + * The enriched view as it LEAVES this node: afi-core's analyst-facing + * `FroggyEnrichedView` plus three reactor-only runtime attachments. + * + * These three are deliberately NOT part of afi-core's contract β€” the analyst + * adapter and scorer never read them β€” and the Evidence V3 record structurally + * rejects them as runtime/storage baggage (`additionalProperties: false`; see + * pipeline/governed-schema/scored-signal-evidence.v3.schema.json). They exist + * for the scored-response projection and the analytics plane: + * - `lenses` β†’ ReactorScoredSignalV1.lenses, analytics scoring_context + * - `_priceFeedMetadata` β†’ graphScoringService, analytics scoring_context + * - `_enrichmentSummary` β†’ NO READER in either repo (dormant; see D8-D3 in + * reports/afi-dormant-surface-inventory-v0.1.md) + * + * Declaring the extension keeps the attachment sites honest: they were + * previously three `as Record` casts, which suppressed the + * fact that this node widens a cross-repo type. + */ +interface ReactorEnrichedView extends FroggyEnrichedView { + lenses?: SupportedLens[]; + _priceFeedMetadata?: { + priceSource: string; + venueType: VenueType; + marketType: AfiMarketType; + technicalIndicators?: TechnicalLensV1["payload"]; + patternSignals?: PatternLensV1["payload"]; + }; + _enrichmentSummary?: string; +} + interface CategorizedResult { category: string; [field: string]: unknown; @@ -204,7 +235,7 @@ export function createMergeEnrichedViewNode(): AnalysisNodePlugin { enrichmentSummary += `. Pattern: ${pattern.patternName}`; } - const enriched: FroggyEnrichedView = { + const enriched: ReactorEnrichedView = { signalId, symbol, market: normalizedMarketType, @@ -222,15 +253,15 @@ export function createMergeEnrichedViewNode(): AnalysisNodePlugin { }, }; - (enriched as Record).lenses = lenses; - (enriched as Record)._priceFeedMetadata = { + enriched.lenses = lenses; + enriched._priceFeedMetadata = { priceSource: actualPriceSource, venueType, marketType: normalizedMarketType, technicalIndicators: technicalLensPayload || undefined, patternSignals: patternLensPayload || undefined, }; - (enriched as Record)._enrichmentSummary = enrichmentSummary; + enriched._enrichmentSummary = enrichmentSummary; ctx.logger.info("enriched view assembled", { categories: enrichedCategories }); return ok(enriched); diff --git a/src/server.ts b/src/server.ts index cfe5135..ffaab66 100644 --- a/src/server.ts +++ b/src/server.ts @@ -697,7 +697,14 @@ app.post("/api/ingest/cpj", async (req: Request, res: Response) => { const ingestHash = canonicalUss.provenance.ingestHash; const signalId = canonicalUss.provenance.signalId; - const duplicate = checkDuplicate(ingestHash); + // The governed USS v1.1 schema requires only source/providerId/signalId, so + // provenance.ingestHash is legitimately optional. mapCpjToUssV11 always + // populates it, so these guards are no-ops on every payload this route can + // produce today. They exist because an absent hash would otherwise be used + // as a literal cache key: with dedupe enabled (AFI_INGEST_DEDUPE=1) the + // first hashless signal would be recorded under `undefined` and every + // subsequent hashless signal rejected 409 as a duplicate. See D6. + const duplicate = ingestHash ? checkDuplicate(ingestHash) : undefined; if (duplicate) { console.warn(`⚠️ Duplicate CPJ ingestion detected:`, { ingestHash, @@ -715,7 +722,7 @@ app.post("/api/ingest/cpj", async (req: Request, res: Response) => { } // Record this ingest for future dedupe checks - recordIngest(ingestHash, signalId); + if (ingestHash) recordIngest(ingestHash, signalId); // βœ… STEP 4: Execute the RESOLVED registered composition through the // manifest-driven GraphExecutor (boot-validated registry composition). diff --git a/src/uss/cpjMapper.ts b/src/uss/cpjMapper.ts index 9da3162..ea0772a 100644 --- a/src/uss/cpjMapper.ts +++ b/src/uss/cpjMapper.ts @@ -177,7 +177,7 @@ function canonicalizeCpjForHashing(cpj: CpjV01Payload): any { if (Array.isArray(clone.extracted?.takeProfits)) { clone.extracted.takeProfits = clone.extracted.takeProfits .slice() // Copy array - .sort((a, b) => { + .sort((a: { price: number } | number, b: { price: number } | number) => { const priceA = typeof a === "object" ? a.price : a; const priceB = typeof b === "object" ? b.price : b; return priceA - priceB; @@ -188,7 +188,7 @@ function canonicalizeCpjForHashing(cpj: CpjV01Payload): any { if (Array.isArray(clone.extracted?.stopLoss)) { clone.extracted.stopLoss = clone.extracted.stopLoss .slice() - .sort((a, b) => a - b); + .sort((a: number, b: number) => a - b); } return clone; diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..5925559 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,24 @@ +{ + // BUILD config β€” what actually ships. + // + // tsconfig.json is the TYPECHECK config: it includes test/**/* so the compiler + // checks the suite (D8). That is correct for `tsc --noEmit` and for ts-jest, + // but `tsc` with an outDir would then EMIT the compiled tests into dist/ and + // ship test code in the production image. This config inherits every + // compilerOption (strict, bundler resolution, …) and narrows the input set + // back to the shipping sources. + // + // `npm run build` β†’ this file (emit src only) + // `npm run typecheck` β†’ tsconfig.json (check src + test, no emit) + "extends": "./tsconfig.json", + // Matches main's emitted output exactly: dist/src only, 87 .js files. + "include": [ + "src/**/*", + "typings.d.ts" + ], + "exclude": [ + "node_modules", + "dist", + "test" + ] +} diff --git a/tsconfig.json b/tsconfig.json index 65d3d38..fd04609 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,24 @@ { "compilerOptions": { "target": "ES2022", - "module": "ESNext", - "moduleResolution": "node", + // D8: was module ESNext + moduleResolution "node". "node" (node10) cannot + // read afi-core's package `exports` map, and that failure is why the + // afi-core boundary was papered over with ambient `declare module` shims + // typing it as `any`. + // + // NodeNext is chosen over "bundler" deliberately: this package runs as real + // Node ESM ("type": "module", entrypoint dist/src/server.js), so NodeNext is + // the semantics that actually apply at runtime. It resolves the exports map + // AND enforces `.js`-suffixed relative specifiers at compile time β€” which + // "bundler" does not, leaving that invariant to scripts/esm-check.sh (which + // is not run by CI). Measured: both settings typecheck clean; NodeNext is + // the one that cannot drift. + "module": "NodeNext", + "moduleResolution": "NodeNext", "outDir": "./dist", "rootDir": "./", - "strict": false, + "strict": true, + // Third-party declaration files only; does NOT relax the afi-core seam. "skipLibCheck": true, "allowJs": true, "resolveJsonModule": true, @@ -18,13 +31,36 @@ }, "include": [ "src/**/*", + "test/**/*", "typings.d.ts" ], + // The excluded test paths mirror jest.config.js `testPathIgnorePatterns` + // exactly β€” the files jest is explicitly configured to skip. They are excluded + // because both currently-rotten ones would otherwise gate CI on code that no + // longer runs. + // + // NOTE: this is NOT the same as "everything jest never executes". jest's + // `testMatch` is an ALLOWLIST, so three further test files match no pattern + // and never run either, yet are still typechecked here (deliberately β€” they + // compile clean, and typechecking them is what would catch them rotting). + // They are recorded as D8-D3. Keep this list in sync with + // `testPathIgnorePatterns`, not with `testMatch`. + // + // The two rotten exclusions are recorded as dormant surfaces (D8-D1/D8-D2 in + // reports/afi-dormant-surface-inventory-v0.1.md) with deletion proposed: + // - test/receiptProvenanceService.test.ts imports src/types/TssdSignalDocument.js, + // which does not exist (retired TSSD framing) β€” it can never run. + // - test/uss/ussMapper.test.ts calls mapTradingViewToUssV11 with 1 arg; the + // function has required 2 since resolution-precedes-mapping landed, and + // both live callers pass 2. + // Keep this list in sync with jest.config.js. "exclude": [ "node_modules", "dist", - "**/*.test.ts", - "**/*.spec.ts" + "test/receiptProvenanceService.test.ts", + "test/vaultReplayService.test.ts", + "test/uss", + "test/cpj" ], "ts-node": { "esm": true diff --git a/typings.d.ts b/typings.d.ts index 11346b3..87c7138 100644 --- a/typings.d.ts +++ b/typings.d.ts @@ -1,93 +1,23 @@ -// Legacy ambient typings shim for older tests/missing module declarations. -// Prefer real @types packages; this is a fallback only. - -// Optional: global placeholders for test frameworks -declare var describe: any; -declare var it: any; -declare var test: any; - -// Minimal ambient modules for build-time compatibility (scored-only public surface) -declare module "afi-core/analyst" { - export type AnalystScoreTemplate = any; -} -declare module "afi-core/analysts/froggy.enrichment_adapter.js" { - export type FroggyEnrichedView = any; - export function buildFroggyTrendPullbackInputFromEnriched(enriched: any): any; -} -declare module "afi-core/analysts/froggy.trend_pullback_v1.js" { - export type FroggyTrendPullbackScore = any; - export function scoreFroggyTrendPullback(input: any, config?: any, enriched?: any): any; - export function scoreFroggyTrendPullbackFromEnriched(enriched: any): any; -} -declare module "afi-core/validators/ValidatorDecision.js" { - export type ValidatorDecisionBase = any; -} -declare module "afi-core/validators/NoveltyScorer.js" { - export type NoveltySignalInput = any; -} -declare module "afi-core/validators/NoveltyTypes.js" { - export type NoveltyResult = any; - export type NoveltyClass = any; -} -declare module "afi-core/decay" { - export type DecayParams = any; - export const DEFAULT_DECAY_TEMPLATES_BY_HORIZON: Record< - string, - { templateId: string; [key: string]: any } - >; - export function pickDecayParamsForAnalystScore(...args: any[]): any; -} -declare module "ccxt" { - const ccxt: any; - export = ccxt; -} -declare module "node-telegram-bot-api" { - export default class TelegramBot { - constructor(token: string, options?: any); - on(event: string, callback: (...args: any[]) => void): void; - sendMessage(chatId: any, text: string, opts?: any): void; - } -} -declare module "telegram" { - export class TelegramClient { - constructor(session: any, apiId: any, apiHash: any, options?: any); - connect(): Promise; - start(options: any): Promise; - } - export class StringSession { - constructor(session?: string); - save(): string; - } - export type Message = any; -} -declare module "telegram/events/index.js" { - export class NewMessage { - constructor(options?: any); - } - export type NewMessageEvent = any; -} -declare module "telegram/sessions/index.js" { - export const StringSession: any; -} +// Ambient module declarations β€” the JUSTIFIED MINIMUM. +// +// An ambient `declare module` BEATS real module resolution: if a package is +// declared here, TypeScript binds every import of it to this declaration even +// when the real types resolve successfully. This file previously declared 16 +// modules, 7 of them typing the whole afi-core boundary as `any`, which is why +// no compiler had ever checked that seam (D8). +// +// Everything else was removed because the package ships real types that this +// file was shadowing: afi-core (via its `exports` map, reached by +// moduleResolution "bundler"), ajv, ajv-formats, ccxt, trading-signals, +// telegram, node-telegram-bot-api, plus express via @types/express and the +// jest globals via @types/jest. +// +// DO NOT add a module here to silence a type error. If the package ships types, +// fix resolution; if it does not, install its @types package. Only a package +// with no types from any source belongs below. +// +// `input` (v1.0.1) ships no type declarations and has no @types package. declare module "input" { const input: any; export = input; } -declare module "ajv" { - export type ValidateFunction = any; - export type ErrorObject = any; - export class Ajv { - constructor(options?: any); - addSchema(schema: any, key?: string): void; - getSchema(key: string): ValidateFunction | undefined; - compile(schema: any): ValidateFunction; - addVocabulary(keywords: string[]): void; - } - export default Ajv; -} -declare module "ajv-formats" { - export default function addFormats(ajv: any): void; -} -declare module "trading-signals" { - export const StochasticRSI: any; -}