{"record":{"id":"b24e8749ce3b5d93","repo":"linshenkx/prompt-optimizer","slug":"context-error-codes-invalid-store","errorCode":"CONTEXT_ERROR_CODES.INVALID_STORE","errorMessage":"Invalid document structure","messagePattern":"Invalid document structure","errorType":"error_code","errorClass":"ContextError","httpStatus":null,"severity":"error","filePath":"packages/core/src/services/context/repo.ts","lineNumber":142,"sourceCode":"      const doc: ContextStoreDoc = {\n        version: CONTEXT_STORE_VERSION,\n        currentId: DEFAULT_CONTEXT_CONFIG.id,\n        contexts: {\n          [DEFAULT_CONTEXT_CONFIG.id]: defaultContext\n        }\n      };\n\n      // 立即保存初始文档\n      await this.storage.setItem(CONTEXT_STORE_KEY, JSON.stringify(doc));\n      return doc;\n    }\n\n    try {\n      const doc = JSON.parse(data) as ContextStoreDoc;\n\n      // 基础验证\n      if (!doc.currentId || !doc.contexts || typeof doc.contexts !== 'object') {\n        throw new ContextError(CONTEXT_ERROR_CODES.INVALID_STORE, 'Invalid document structure');\n      }\n\n      // 迁移逻辑：为旧文档的上下文补写 mode 字段\n      let migrated = false;\n      for (const ctx of Object.values(doc.contexts)) {\n        if (!ctx.mode) {\n          ctx.mode = DEFAULT_CONTEXT_MODE;\n          migrated = true;\n        }\n      }\n\n      // 如果有迁移，需要保存回存储\n      if (migrated) {\n        await this.storage.setItem(CONTEXT_STORE_KEY, JSON.stringify(doc));\n        if (process.env.NODE_ENV === 'development') {\n          console.log('[ContextRepo] Migrated contexts to add mode field');\n        }\n      }","sourceCodeStart":124,"sourceCodeEnd":160,"githubUrl":"https://github.com/linshenkx/prompt-optimizer/blob/3e677b1d9f7e0493c142c175560531e7ae786dce/packages/core/src/services/context/repo.ts#L124-L160","documentation":"parseJsonObject successfully ran JSON.parse but the result failed the isRecord check — the parsed value is not a plain object (it's an array, string, number, boolean, or null). This inner error is deliberately re-thrown verbatim in the catch block so it isn't confused with a JSON syntax failure.","triggerScenarios":"Calling the parse path with model output like '[1,2,3]', '\"text\"', '42', 'true', or 'null' — valid JSON whose top-level value is not a record.","commonSituations":"Model returns a JSON array instead of an object; model returns a bare string; extraction slice grabs a quoted string or number fragment from surrounding prose.","solutions":["Adjust the prompt to require a top-level JSON object with named keys","If arrays are legitimately expected, accept them explicitly instead of relying on isRecord","Fix the slice/extraction logic when it captures a non-object fragment"],"exampleFix":"// before\nmodelInstruction = 'Return the results as JSON.'\n\n// after\nmodelInstruction =\n  'Return the results as a single JSON OBJECT with keys \"prompt\" and \"variables\". Never return a top-level array.'","handlingStrategy":"type-guard","validationCode":"function isJsonObjectText(text: string): boolean {\n  try {\n    return isRecord(JSON.parse(text))\n  } catch {\n    return false\n  }\n}","typeGuard":"function isRecord(v: unknown): v is Record<string, unknown> {\n  return typeof v === 'object' && v !== null && !Array.isArray(v)\n}","tryCatchPattern":"try {\n  const parsed = parseJsonObject(candidate)\n} catch (error) {\n  if (error instanceof Error && error.message === 'Model response is not a valid JSON object') {\n    // valid JSON but wrong shape (array/scalar) — prompt fix needed, retrying may help\n    return coerceToObjectLikeShape(candidate)\n  }\n  throw error\n}","preventionTips":["Prompt for a top-level object with named keys, never an array","Check parsed shape with isRecord before using it","If arrays are expected, wrap or accept them explicitly rather than relying on the record check"],"tags":["structured-output","json-parsing","type-mismatch"],"backgroundTag":"invalid-model-json-output","analyzedSha":"3e677b1d9f7e0493c142c175560531e7ae786dce","analyzedAt":"2026-08-27T21:29:16.709Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}