affaan-m/ECC · error · Error

spec.schedule[ ] must be a dense six-cell array

Error message

spec.schedule[${rowIndex}] must be a dense six-cell array

What it means

Each element of spec.schedule must be a dense array of exactly six cells. The renderer iterates rows and rejects any row that is not an array, has length !== 6, or contains sparse holes (Object.hasOwn check fails for an index). The thrown message names the offending row index.

Solutions

  1. Make every row exactly six cells: pad missing cells with '' or 0, and trim extras.
  2. Build rows with Array.of(...) or a literal so all six indices are own properties (no holes).
  3. If a cell is optional, pass an empty string rather than deleting the slot.
  4. Pre-validate: rows.every(r => Array.isArray(r) && r.length === 6 && Object.hasOwn(r, 5)).

Example fix

// before
const row = new Array(6); row[0] = 'monday';           // sparse row
// after
const row = ['monday', '09:00', '17:00', 'lunch', 60, '']; // dense six-cell row
Defensive patterns

Strategy: validation

Validate before calling

function assertDenseRows(schedule) {
  schedule.forEach((row, i) => {
    if (!Array.isArray(row) || row.length !== 6 || !Object.hasOwn(row, 5)) {
      throw new TypeError(`spec.schedule[${i}] must be a dense six-cell array`);
    }
  });
}

Type guard

const isDenseSixCellRow = (r) => Array.isArray(r) && r.length === 6 && Object.hasOwn(r, 5);

Try / catch

try {
  buildAgreement(spec);
} catch (e) {
  const m = e.message.match(/spec\.schedule\[(\d+)\]/);
  if (m) {
    const i = Number(m[1]);
    spec.schedule = spec.schedule.map((r, idx) =>
      idx === i ? padOrTrimRow(r, 6) : r);
    return buildAgreement(spec);
  }
  throw e;
}

Prevention

When it happens

Trigger: spec.schedule = [['a','b','c']] (three cells), spec.schedule = [row0, null], spec.schedule with a deleted index creating a hole (sparse array), or a row built with `new Array(6)` without filling all slots.

Common situations: Editing a spreadsheet-exported row and dropping a column; programmatically building rows with Array(6) (sparse) instead of Array.from/fill; merging rows of differing widths; optional trailing cells removed making the row short.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/bee494b54e75b0a4. Report an issue: GitHub.

Appendix: source

Thrown at skills/master-agreement-generator/scripts/build-agreement.js:65

  // Entities keep data out of Markdown/HTML syntax, including smart punctuation.
  // Preserve single internal spaces and ordinary dates/example text as written.
  return String(cell).replace(/\r\n|\r|\n/g, ' ')
    .replace(/[\\|`*_{}[\]<>!&#~^$'"@]/g, entity)
    .replace(/-{2,}|\.{3,}/g, run => [...run].map(entity).join(''))
    .replace(/^ +| +$| {2,}|[^\S ]/gu, run => [...run].map(entity).join(''));
}

function renderScheduleRows(rows) {
  if (rows === undefined) {
    return EMPTY_SCHEDULE_ROW;
  }
  if (!Array.isArray(rows)) {
    throw new Error('spec.schedule must be an array of six-cell rows');
  }
  if (rows.length === 0) return EMPTY_SCHEDULE_ROW;
  return Array.from(rows, (row, rowIndex) => {
    if (!Object.hasOwn(rows, rowIndex) || !Array.isArray(row) || row.length !== 6) {
      throw new Error(`spec.schedule[${rowIndex}] must be a dense six-cell array`);
    }
    const cells = Array.from(row, (cell, cellIndex) => {
      if (!Object.hasOwn(row, cellIndex) ||
          !((typeof cell === 'string' && !/\p{Surrogate}/u.test(cell)) ||
            (typeof cell === 'number' && Number.isFinite(cell)))) {
        throw new Error(`spec.schedule[${rowIndex}][${cellIndex}] must be valid Unicode text or a finite number`);
      }
      return encodeScheduleCell(cell);
    });
    return `| ${cells.join(' | ')} |`;
  }).join('\n');
}

function buildValues(spec, now) {
  if (!spec || typeof spec !== 'object') {
    throw new Error('spec must be an object');
  }
  for (const key of ['file', 'short', 'role']) {

View on GitHub (pinned to 8321021c54)