{"record":{"id":"b7c1236c1feea8e6","repo":"can1357/oh-my-pi","slug":"mnemopiplugin-is-abstract","errorCode":null,"errorMessage":"MnemopiPlugin is abstract","messagePattern":"MnemopiPlugin is abstract","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/mnemopi/src/core/plugins.ts","lineNumber":19,"sourceCode":"import { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport const DEFAULT_PLUGIN_DIR = join(homedir(), \".hermes\", \"mnemopi\", \"plugins\");\n\nexport type PluginConfig = Record<string, unknown>;\nexport type MemoryDict = Record<string, unknown>;\n\nexport class MnemopiPlugin {\n\tstatic readonly abstractBase = true;\n\tname = \"\";\n\tversion = \"1.0.0\";\n\tenabled = true;\n\tprotected initialized = false;\n\treadonly config: PluginConfig;\n\n\tconstructor(config: PluginConfig = {}) {\n\t\tif (new.target === MnemopiPlugin) throw new TypeError(\"MnemopiPlugin is abstract\");\n\t\tthis.config = config;\n\t\tconst ctor = this.constructor as typeof MnemopiPlugin;\n\t\tthis.name =\n\t\t\t(ctor.prototype.name as string | undefined) ?? (ctor as unknown as { name?: string }).name ?? this.name;\n\t\tthis.version = (ctor.prototype.version as string | undefined) ?? this.version;\n\t\tthis.enabled = (ctor.prototype.enabled as boolean | undefined) ?? this.enabled;\n\t}\n\n\tinitialize(): void {\n\t\tthis.initialized = true;\n\t}\n\n\tshutdown(): void {\n\t\tthis.initialized = false;\n\t}\n\n\tonRemember(_memory: MemoryDict): void {\n\t\tthrow new TypeError(\"Plugin must implement onRemember\");","sourceCodeStart":1,"sourceCodeEnd":37,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/mnemopi/src/core/plugins.ts#L1-L37","documentation":"MnemopiPlugin is an abstract base class for mnemopi plugins. Its constructor uses new.target to detect direct instantiation (new MnemopiPlugin(...)) and throws this TypeError immediately, because the base class carries no plugin behavior — subclasses must provide name and hook implementations.","triggerScenarios":"Calling new MnemopiPlugin(config) directly, or a plugin factory/registry that instantiates classes from config strings and resolves to the base class (e.g. a misspelled or unregistered subclass name falling back to the base).","commonSituations":"Copy-pasting setup code that instantiates the base instead of LoggingPlugin/MetricsPlugin or a custom subclass; a dynamic plugin loader mapping a config name to the wrong class; migrating code that previously allowed the base class.","solutions":["Instantiate a concrete subclass (e.g. new LoggingPlugin(config), new MetricsPlugin(config)) or your own class extending MnemopiPlugin","Create a subclass that overrides at least the hooks you need and set name/version on the subclass","If using a registry/factory, check that the plugin name maps to a registered concrete class, not the base"],"exampleFix":"// before\nconst plugin = new MnemopiPlugin(config);\n// after\nclass MyPlugin extends MnemopiPlugin { override name = \"my\"; override onRemember(m: MemoryDict): void { /* ... */ } }\nconst plugin = new MyPlugin(config);","handlingStrategy":"type-guard","validationCode":"type PluginCtor = new (config?: PluginConfig) => MnemopiPlugin;\nfunction instantiateConcrete(Ctor: PluginCtor, config?: PluginConfig): MnemopiPlugin {\n  if (Ctor === MnemopiPlugin) throw new TypeError(\"Pass a concrete plugin subclass, not MnemopiPlugin\");\n  return new Ctor(config);\n}","typeGuard":"function isConcretePluginClass(C: unknown): C is new (config?: PluginConfig) => MnemopiPlugin {\n  return typeof C === \"function\" && C !== MnemopiPlugin && C.prototype instanceof MnemopiPlugin;\n}","tryCatchPattern":"try {\n  plugin = instantiate(pluginName, config);\n} catch (err) {\n  if (err instanceof TypeError && err.message === \"MnemopiPlugin is abstract\") {\n    logger.error(`plugin name \"${pluginName}\" resolved to the abstract base; check registration`);\n  } else throw err;\n}","preventionTips":["Never write `new MnemopiPlugin(...)`; always instantiate a subclass","In dynamic loaders, validate the resolved class !== MnemopiPlugin before constructing","TypeScript-only codebases can enforce this at compile time with abstract class syntax","Add a startup check that every configured plugin name maps to a registered concrete class"],"tags":["typescript","abstract-class","plugins"],"backgroundTag":"abstract-class-instantiation","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}