{"record":{"id":"dbee56bcedf62be1","repo":"ruvnet/ruflo","slug":"project-flywheel-anchor-requires-at-least-4-labell","errorCode":null,"errorMessage":"project flywheel anchor requires at least 4 labelled tasks","messagePattern":"project flywheel anchor requires at least 4 labelled tasks","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/services/harness-project-anchor.ts","lineNumber":84,"sourceCode":"  const actual = realpathSync(absolute);\n  const physical = relative(root, actual);\n  if (physical === '..' || physical.startsWith(`..${sep}`) || isAbsolute(physical)) {\n    throw new Error('flywheel anchor symlink escapes project root');\n  }\n  return actual;\n}\n\nfunction parseTasks(path: string): { version: string; tasks: HumanEvalTask[] } {\n  const parsed = JSON.parse(readFileSync(path, 'utf8')) as {\n    schemaVersion?: string;\n    version?: string;\n    tasks?: HumanEvalTask[];\n  };\n  if (parsed.schemaVersion && parsed.schemaVersion !== PROJECT_ANCHOR_SCHEMA) {\n    throw new Error(`unsupported flywheel anchor schema: ${parsed.schemaVersion}`);\n  }\n  if (!Array.isArray(parsed.tasks) || parsed.tasks.length < 4) {\n    throw new Error('project flywheel anchor requires at least 4 labelled tasks');\n  }\n  const ids = new Set<string>();\n  for (const [index, task] of parsed.tasks.entries()) {\n    if (!task || typeof task.id !== 'string' || !/^[A-Za-z0-9._-]{1,128}$/.test(task.id)) {\n      throw new Error(`invalid anchor task id at index ${index}`);\n    }\n    if (ids.has(task.id)) throw new Error(`duplicate anchor task id: ${task.id}`);\n    ids.add(task.id);\n    if (typeof task.q !== 'string' || task.q.trim().length === 0) {\n      throw new Error(`anchor task ${task.id} has no query`);\n    }\n    if (!Array.isArray(task.labels) || task.labels.length === 0 || task.labels.some((label) => typeof label !== 'string' || !label.trim())) {\n      throw new Error(`anchor task ${task.id} requires non-empty string labels`);\n    }\n  }\n  return { version: parsed.version ?? 'project-anchor-v1', tasks: parsed.tasks };\n}\n","sourceCodeStart":66,"sourceCodeEnd":102,"githubUrl":"https://github.com/ruvnet/ruflo/blob/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/v3/@claude-flow/cli/src/services/harness-project-anchor.ts#L66-L102","documentation":"Thrown by parseTasks() when parsed.tasks is either not an array or contains fewer than 4 entries. The flywheel harness requires a minimum of 4 labelled retrieval tasks to make the benchmark statistically meaningful; fewer tasks flatten the objective and make the result indistinguishable from 'already optimal'. This is a hard floor on anchor quality, not a recommendation.","triggerScenarios":"Loading an anchor file whose tasks field is missing, is a non-array (e.g. an object), is an empty array, or has 1-3 entries. Also fires when the JSON key is mistyped (e.g. \"task\" instead of \"tasks\") so parsed.tasks is undefined.","commonSituations":"Starting with a stub anchor file containing one sample task during scaffolding and forgetting to expand it; renaming the \"tasks\" key; truncating a generated anchor during a copy-paste; a CI-generated anchor that filtered out invalid tasks down to <4.","solutions":["Add labelled tasks until the array has at least 4 entries, each with a valid id, q, and non-empty labels.","Verify the key is spelled exactly \"tasks\" (plural) and is a JSON array, not an object or wrapper.","Run JSON.parse on the file and assert Array.isArray(tasks) && tasks.length >= 4 before pointing the harness at it."],"exampleFix":"// before\n{ \"schemaVersion\": \"ruflo.flywheel-anchor/v1\", \"tasks\": [{\"id\":\"t1\",\"q\":\"...\",\"labels\":[\"x\"]}] }\n// after — add at least 3 more labelled tasks\n{ \"schemaVersion\": \"ruflo.flywheel-anchor/v1\", \"tasks\": [\n  {\"id\":\"t1\",\"q\":\"...\",\"labels\":[\"x\"]},\n  {\"id\":\"t2\",\"q\":\"...\",\"labels\":[\"y\"]},\n  {\"id\":\"t3\",\"q\":\"...\",\"labels\":[\"z\"]},\n  {\"id\":\"t4\",\"q\":\"...\",\"labels\":[\"w\"]}\n]}","handlingStrategy":"validation","validationCode":"import { readFileSync } from 'fs';\nfunction assertEnoughTasks(path: string): void {\n  const parsed = JSON.parse(readFileSync(path, 'utf8'));\n  if (!Array.isArray(parsed.tasks) || parsed.tasks.length < 4) {\n    throw new Error(`anchor needs >=4 tasks, has ${Array.isArray(parsed.tasks) ? parsed.tasks.length : 0}`);\n  }\n}","typeGuard":"function hasMinTasks(parsed: unknown): boolean {\n  if (typeof parsed !== 'object' || parsed === null) return false;\n  const tasks = (parsed as { tasks?: unknown }).tasks;\n  return Array.isArray(tasks) && tasks.length >= 4;\n}","tryCatchPattern":"try {\n  loadEffectiveFlywheelAnchor(root, opts);\n} catch (e) {\n  if (e instanceof Error && /requires at least 4 labelled tasks/.test(e.message)) {\n    // add more labelled tasks to the anchor, then retry\n  }\n  throw e;\n}","preventionTips":["Author anchors with at least 4 tasks from the start; never commit a stub.","Assert tasks.length >= 4 in your anchor generator's tests.","Double-check the plural key name 'tasks'."],"tags":["flywheel-anchor","validation","configuration","json"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}