Отчёт выводит четыре строки, хотя в конфигурации указано limit: 6, и вам поручили выяснить причину. Вы впервые видите этот репозиторий. В нём есть CLI, загрузчик конфигурации, несколько сервисов и каталог тестов. Прочитав всё, вы рано или поздно нашли бы ответ. Проследить одно значение, задающее число строк, гораздо быстрее.

Сформулируйте конкретный вопрос

У задачи «разобраться в модуле отчётов» нет чёткой границы завершения. У конкретного вопроса она есть:

В конфигурации задано limit: 6, а в окружении — REPORT_LIMIT=4. Отчёт выводит четыре строки. Какое значение победило и что произойдёт, если REPORT_LIMIT окажется пустой строкой?

Запишите выполненную команду, каталог и коммит. Убедитесь также, что вход содержит больше четырёх записей. Если в исходных данных всего четыре строки, такой вывод ничего не говорит об ограничении.

Найдите начальный файл по имеющейся зацепке

Первым полезным файлом может оказаться модуль запуска, форматирования или тест, а не функция выбора лимита. Двигайтесь к ней от воспроизводимой зацепки и отмечайте, какие связи вы действительно проверили.

Скачайте учебный проект поиска точки входа отчёта, распакуйте его и перейдите в report-entry-lab. Этот небольшой проект создан для урока. Он дополняет приведённые ниже правила приоритета реальной командой, таблицей диспетчеризации и загрузчиками локальных фикстур. Нужны Node.js 22 или новее, npm и ripgrep (rg); устанавливать зависимости или заводить аккаунт модели не требуется. Команды оболочки работают в macOS и Linux.

REPORT_LIMIT=4 npm run --silent report
Report rows: 4
Aster
Birch
Clover
Dahlia

В фикстуре конфигурации задано limit: 6, а фикстура данных содержит восемь строк. Первой выводится строка заголовка, затем четыре строки данных. Значит, расхождение реально, а не вызвано коротким входным файлом. Программа читает только приложенные локальные фикстуры и пишет в терминал; тесты запускают локальные процессы Node без сетевых запросов и записи файлов.

Имеющаяся зацепка С чего начать Что ещё проверить
Команда, воспроизводящая поведение Вызываемый ею скрипт пакета или исполняемый файл Какой аргумент выбирает какой обработчик
Характерная строка вывода или ошибки Код, который её формирует Является ли совпадение источником строки, фикстурой или проверкой и кто вызывает источник
Название теста Его импорт и вызов внутри теста Запускает ли он реальную команду или подставляет зависимости
Ключ конфигурации или переменная окружения Чтение и преобразование значения Какой вызывающий код достигает этого места и какой загрузчик передаёт конфигурацию

Изучите тот же проект в Yenpo

Используйте скачанный report-entry-lab, чтобы ответить на вопрос: «Какая точка входа вывела эти четыре строки?» Результатом должна стать короткая запись о команде запуска, выбранном обработчике, входных файлах и вызове вывода.

  1. Откройте распакованный каталог report-entry-lab в Yenpo, выбрав корень с package.json. Через Command-P (Перейти к файлу…) откройте package.json, затем src/cli.mjs и src/commands.mjs. Проверьте скрипт запуска и обработчик, зарегистрированный для report.
  2. Выберите Искать во всех файлах в боковой панели и найдите Report rows: с выключенными регулярными выражениями. Сопоставьте src/format-report.mjs с совпадениями в тестах: один файл создаёт текст, остальные проверяют ожидаемый результат. Проследите импорт и вызов форматирования в src/report.mjs, прежде чем считать его частью этой команды.
  3. Оставьте src/report.mjs, src/limits.mjs и нужный тест открытыми во вкладках. Для каждого шага запишите подтверждающие файл и строку, затем с помощью команд ниже отличите прямой тест функции от запуска через реальную CLI.

Для просмотра файлов и текстового поиска не нужны подключение к модели или языковой сервер JavaScript. Чтобы попробовать, скачайте Yenpo для Mac и откройте тот же распакованный каталог; справка по чтению кода описывает оба инструмента поиска. В ZIP есть исходники, но нет истории Git, поэтому упражнение посвящено чтению файлов, а не закреплённому сравнению коммитов.

У вас есть команда терминала

Изучите скрипт, прежде чем определять точку входа по имени каталога:

npm pkg get scripts.report
rg -n 'process.argv|commands\[command\]' src/cli.mjs
rg -n 'report:|runReport|report-config.json' src/commands.mjs

Первая команда выводит "node src/cli.mjs report". В cli.mjs выражение process.argv.slice(2) делает report именем команды; остальные аргументы передаются выбранному обработчику. commands[command] — поиск в таблице, поэтому обнаружить модуль запуска — лишь половина дела. Откройте commands.mjs: свойство report вызывает runReport с загрузчиками report-config.json и rows.json. Соседнее свойство help вместо этого возвращает справку.

Теперь внесите одно контролируемое изменение через тот же запуск:

REPORT_LIMIT=4 npm run --silent report -- --limit 2

Будет выведено Report rows: 2, затем Aster и Birch. npm передаёт скрипту аргументы после разделителя из двух дефисов. Прочитайте поиск в таблице и зарегистрированный обработчик, чтобы установить путь; изменённый вывод затем подтвердит, что команда достигает логики ограничения. По одному выводу нельзя определить все промежуточные вызовы.

У вас есть только строка вывода

rg -n -F 'Report rows:' src tests
rg -n 'renderReport|runReport' src

Первый поиск находит format-report.mjs и проверки в тестах. Форматирование создаёт заголовок, тесты сравнивают ожидаемый текст. Ни один вид совпадений сам по себе не является точкой входа команды. В report.mjs проверьте и импорт renderReport, и вызов с выбранными именами. Затем проследите runReport назад до регистрации report в commands.mjs, а выбранный обработчик — до записи в stdout в cli.mjs.

Теперь у вас есть маршрут, который можно проверить командой выше. Если реальный вывод собирается из частей, найдите устойчивый фрагмент и изучите окружающее выражение. Если общий форматировщик вызывают из нескольких мест, сохраняйте их как варианты, пока аргументы запуска или целевой тест не выберут один; не объединяйте их в единый путь исполнения.

У вас есть название теста

rg -n -F 'environment wins over config' tests
node --test --test-name-pattern='environment wins over config' tests/report.test.mjs

Найденный тест напрямую импортирует runReport и передаёт собственное окружение, загрузчик конфигурации и загрузчик строк. Он подтверждает, что внутри функции четыре имеет приоритет над шестью, но не запускает cli.mjs и не читает конфигурационный файл. Он не показывает, какой файл загружает реальная команда.

Проследите импорт до нужной логики, затем изучите реальную привязку commands.report. Отдельный тест пересекает оставшуюся непроверенной границу:

node --test --test-name-pattern='CLI uses REPORT_LIMIT' tests/cli.test.mjs

Он запускает реальную CLI в дочернем процессе с REPORT_LIMIT=4 и проверяет код завершения и stdout. Оба выбранных теста проходят. Сохраните различие в заметке: один сам выбирает зависимости, другой проверяет запуск и файловые адаптеры. Успешный прямой тест функции не заменяет второй вариант.

У вас есть имя настройки

rg -n -F 'REPORT_LIMIT' src tests
rg -n 'resolveLimit|loadConfig|report-config.json' src

Значение читается в limits.mjs; остальные совпадения подготавливают тестовые входы. Проследите вызов resolveLimit в report.mjs, затем проверьте функцию loadConfig, переданную из commands.report. Выражение new URL(..., import.meta.url) выбирает приложенную фикстуру относительно модуля. Одного совпадения имени файла недостаточно, чтобы доказать его использование этим обработчиком.

Теперь есть отправная точка для исследования значения ниже. Оставьте help и несвязанные тесты: вы проверили команду, выбранный обработчик, реальные входные файлы и место вывода. Если настоящая команда использует другую копию репозитория, обёртку или источник окружения, выясните это до расширения поиска. Иначе следующий вопрос — приоритет значения, а не остальная часть репозитория.

Проследите выбранный обработчик

Файл report.mjs учебного проекта разбирает аргументы, загружает конфигурацию, выбирает лимит, загружает строки и вызывает renderReport, добавляя заголовок. CLI выводит возвращённую строку. Более ранний автономный пример ниже оставляет только строки данных, чтобы изучать значение без связей запуска команды. В обеих версиях загрузчики передаются аргументами, поэтому тесты могут наблюдать порядок без чтения файлов:

export async function runReport({ args, env, loadConfig, loadRows }) {
  const { values } = parseArgs({
    args,
    options: { limit: { type: 'string' } },
    strict: true,
  });
  const config = await loadConfig();
  const limit = resolveLimit(values, env, config);
  const rows = await loadRows();
  return rows
    .slice(0, limit)
    .map((row) => row.name)
    .join('\n');
}

Ещё до открытия вспомогательных функций виден порядок: разобрать аргументы, загрузить конфигурацию, выбрать лимит, загрузить строки, вывести. Видна и деталь, которую легко упустить: resolveLimit() выполняется лишь при успехе loadConfig(). Позже это окажется важным.

В реальном проекте прослеживайте импорты. Функция loadConfig может читать один JSON-файл, объединять несколько или обращаться к удалённому сервису. Имя не даёт ответа.

Как выбирается лимит

export function resolveLimit(cli, env, config) {
  const raw = cli.limit ?? env.REPORT_LIMIT ?? config.limit ?? 10;
  const limit = Number(raw);
  if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100) {
    throw new Error('limit must be an integer from 1 to 100');
  }
  return limit;
}

Здесь происходят три действия: выбор источника, преобразование в число, проверка диапазона. Разделяйте их в заметках. Если написать только «окружение перекрывает конфигурацию», легко предположить, что при неверном значении окружения будет использован файл. Этот код так не делает.

CLI --limit REPORT_LIMIT limit в конфигурации Результат
2 4 6 2, из командной строки
отсутствует 4 6 4, из окружения
отсутствует отсутствует 6 6, из файла
отсутствует отсутствует отсутствует значение по умолчанию 10
отсутствует пустая строка 6 ошибка проверки
0 4 6 ошибка проверки

?? пропускает только null и undefined. Пустая строка остаётся значением, поэтому побеждает REPORT_LIMIT=''. Number('') даёт 0, проверка диапазона выбрасывает ошибку, и функция не обращается к значению из конфигурации.

Замена ?? на || пропустит пустые строки, но также сочтёт 0 незаданным значением. Это изменение правил, а не косметическая правка. Сначала решите, что требуется: явно заданное неверное значение должно приводить к ошибке или незаметно заменяться резервным?

Проследите значение до места использования

Правильный лимит не гарантирует правильный вывод. Проследите limit до rows.slice(0, limit), затем выясните, откуда берётся rows.

Здесь loadRows() возвращает все строки, а обработчик оставляет первые несколько. На уровне источника нет сортировки, фильтрации или постраничной выборки. Лимит ограничивает число выводимых строк, а не объём прочитанных или находящихся в памяти данных.

Это важно при другом описании ошибки. Если «отчёт исчерпывает память даже с --limit 2», искать причину в приоритетах неверно. Нужно изучить loadRows и проверить, должен ли лимит доходить до источника данных. Тот же обработчик, другой вопрос, другой следующий файл.

Намеренно вызовите сбой

Теперь попробуйте повреждённую конфигурацию вместе с --limit 2. У командной строки наивысший приоритет, значит, команда всё равно должна работать?

Нет. Конфигурация загружается до выбора лимита, поэтому при ошибке loadConfig() дальнейший код не выполняется. Приоритет определяет используемое значение. Он ничего не говорит о том, какие действия должны успешно завершиться до выбора.

Обратный случай тоже заслуживает теста: конфигурация загружена, но выбранный лимит неверен. Тогда строки не должны читаться вообще. Посчитайте чтения для проверки:

let reads = 0;
await assert.rejects(
  runReport({
    args: [],
    env: { REPORT_LIMIT: '' },
    loadConfig: async () => ({ limit: 6 }),
    loadRows: async () => {
      reads++;
      return [];
    },
  }),
  /integer/,
);
assert.equal(reads, 0);

Если проверить лишь наличие исключения, пройдёт и версия, сначала читающая все строки и лишь затем отклоняющая лимит. Именно reads === 0 фиксирует порядок.

Запустите пример

Скачайте configuration-flow.mjs и запустите с Node.js 22 или новее. Зависимостей нет:

node --test configuration-flow.mjs

Исходный однофайловый пример по-прежнему доступен. Шесть тестов проверяют приоритеты, пустые и неверные значения, вывод, пропуск чтения строк после неверного ввода и повреждённую конфигурацию. Они проверяют функции отдельно; в своём проекте нужно ещё выяснить, как реальная CLI загружает окружение и выводит ошибки.

Новый учебный проект проверяет и связи запуска команды. Из его каталога выполните:

node --test tests/*.test.mjs
REPORT_LIMIT='' npm run --silent report

Все девять тестов проходят. Вторая команда намеренно завершается с кодом 1, выводит limit must be an integer from 1 to 100 в stderr и ничего в stdout. Модульный тест также считает чтения строк, поэтому случай с пустым значением проверяет описанный порядок. ZIP содержит манифест SHA-256; проверены и архив, и извлечённые тесты.

Запишите результаты

Достаточно короткой заметки: в следующий раз прочитать её будет гораздо быстрее, чем код:

runReport
  parseArgs → loadConfig → resolveLimit → loadRows → slice → output

limit: command line > environment > config > 10
invalid selected value: error; no fallback; rows are not loaded
config read failure: stops even when --limit is present
scope: limit bounds output, not the row loader's work

В реальном проекте укажите файл и строку для каждого шага, а также непроверенные вопросы: какой файл читает loadConfig, задаёт ли родительский процесс REPORT_LIMIT, как CLI показывает ошибки. Когда вы можете объяснить четыре строки и назвать открытые вопросы, на этом можно остановиться. Остальной проект пока понимать необязательно.

Дайте другому разработчику проверить передачу проекта

Заметка полезнее, если другой человек может воспроизвести её без истории вашего разговора. Попробуйте ZIP для передачи poster-desk и краткое описание передачи. Этот небольшой автономный проект и его утверждения созданы для обучения; это не реальное приложение, сгенерированное ИИ, и не настоящая передача проекта.

Принимающая сторона должна проверить четыре обещания на фиксированном снимке исходников: локальные карточки читаются новым процессом, точные последовательные повторы используют ту же запись, квитанции являются лишь предпросмотром фикстур, а сохранность данных при замене развёртывания неизвестна. «Реализовано» относится к проверенным условиям; последовательные повторы не доказывают безопасность конкурентного доступа, заглушка ничего не отправляет, а пакет не содержит доказательств о хранилище развёртывания.

Следуя описанию, скопируйте RECEIVER.md за пределы распакованного пакета и внесите в копию команды, результаты, ссылки на исходники и открытые вопросы. Не меняйте поставленный шаблон, затем повторно проверьте оригинальный пакет.

SHA-256 архива ZIP
c39d19b210c05f4667cd74db4934459a079408d14c5e7678c24cd087e1df5185

В Yenpo команды «Перейти к файлу…» и поиска по проекту позволяют держать обработчик, функцию разрешения и тесты рядом. Задайте ИИ для чтения кода вопрос об одном значении: «Проследи REPORT_LIMIT от чтения до использования со ссылками на строки». Вы получите путь, который можно проверить по шагам, вместо очередного обзора проекта.

Выберите следующий шаг по имеющимся данным: