{"record":{"id":"ffa52c64ad9b6c55","repo":"affaan-m/ECC","slug":"source-is-missing-the-catalog-count-description","errorCode":null,"errorMessage":"${source} is missing the catalog count description","messagePattern":"(.+?) is missing the catalog count description","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/ci/catalog.js","lineNumber":341,"sourceCode":"      expected: Number(match[1]),\n      source: `${pattern.source} (${pattern.category})`\n    });\n  }\n\n  return expectations;\n}\n\nfunction parseCatalogDescriptionExpectations(content, source, getDescription) {\n  let parsed;\n  try {\n    parsed = JSON.parse(content);\n  } catch (error) {\n    throw new Error(`${source} is not valid JSON: ${error.message}`);\n  }\n\n  const description = getDescription(parsed);\n  if (typeof description !== 'string') {\n    throw new Error(`${source} is missing the catalog count description`);\n  }\n\n  const match = description.match(/(\\d+)\\s+agents,\\s+(\\d+)\\s+skills,\\s+(\\d+)\\s+legacy command shims?/i);\n  if (!match) {\n    throw new Error(`${source} is missing the catalog count description`);\n  }\n\n  return [\n    { category: 'agents', mode: 'exact', expected: Number(match[1]), source },\n    { category: 'skills', mode: 'exact', expected: Number(match[2]), source },\n    { category: 'commands', mode: 'exact', expected: Number(match[3]), source },\n  ];\n}\n\nfunction evaluateExpectations(catalog, expectations) {\n  return expectations.map(expectation => {\n    const actual = catalog[expectation.category].count;\n    const ok = expectation.mode === 'minimum'","sourceCodeStart":323,"sourceCodeEnd":359,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/scripts/ci/catalog.js#L323-L359","documentation":"After JSON-parsing succeeds, `parseCatalogDescriptionExpectations` calls `getDescription(parsed)` and checks `typeof description !== 'string'`. For plugin.json this reads `parsed.description`; for marketplace.json it reads `parsed.plugins?.[0]?.description`. If the field is missing, undefined, null, or a non-string, the throw fires with `${source} is missing the catalog count description`. This is distinct from error 32 (which fires when the description string exists but lacks the count pattern).","triggerScenarios":"Fires when `.claude-plugin/plugin.json` has no `description` key, or `.claude-plugin/marketplace.json` has no `plugins` array, an empty `plugins` array, or `plugins[0]` has no `description` key. Also fires if description is explicitly null or a number.","commonSituations":"plugin.json is regenerated by a scaffolder that omits description. marketplace.json's plugins array is reordered and index 0 now points at a different plugin without a description. The description field is renamed (e.g. to `summary`). The plugins array is empty after a cleanup.","solutions":["For plugin.json: ensure a top-level `\"description\": \"...\"` string key exists.","For marketplace.json: ensure `plugins[0].description` is a string — verify the plugins array is non-empty and index 0 is the intended plugin.","The description string must contain the count pattern `N agents, N skills, N legacy command shims` (see error 32) once restored.","Run `node scripts/ci/catalog.js --write` to populate/sync the description counts after restoring the field."],"exampleFix":"// before (.claude-plugin/marketplace.json — empty plugins array)\n{\n  \"plugins\": []\n}\n\n// after\n{\n  \"plugins\": [\n    {\n      \"name\": \"ecc\",\n      \"description\": \"ECC plugin — 68 agents, 284 skills, 94 legacy command shims\"\n    }\n  ]\n}","handlingStrategy":"validation","validationCode":"const fs = require('fs');\nfunction checkDesc(file, getterDesc) {\n  const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));\n  let obj = parsed;\n  for (const key of getterDesc) {\n    if (obj == null || typeof obj !== 'object') { console.error(`${file}: missing ${getterDesc.join('.')}`); process.exit(1); }\n    obj = obj[key];\n  }\n  if (typeof obj !== 'string') { console.error(`${file}: ${getterDesc.join('.')} is not a string`); process.exit(1); }\n}\ncheckDesc('.claude-plugin/plugin.json', ['description']);\ncheckDesc('.claude-plugin/marketplace.json', ['plugins', '0', 'description']);","typeGuard":"function hasDescriptionField(parsed, marketplace) {\n  if (!marketplace) return typeof parsed?.description === 'string';\n  return Array.isArray(parsed?.plugins) && parsed.plugins.length > 0 && typeof parsed.plugins[0]?.description === 'string';\n}","tryCatchPattern":"try {\n  runCatalogCheck();\n} catch (error) {\n  if (/is missing the catalog count description/i.test(error.message)) {\n    console.error('Add a string description field (plugin.json: description; marketplace.json: plugins[0].description) containing the count pattern.');\n  }\n  throw error;\n}","preventionTips":["Ensure plugin.json always has a top-level string `description`.","Ensure marketplace.json has a non-empty plugins array with plugins[0].description as a string.","Validate the manifest shape in CI with a JSON schema.","Run `--write` only after the description field exists — it will not create the field for you."],"tags":["catalog","ci","json","plugin-manifest","marketplace-json","schema"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}