Вспомогательная функция начинает возвращать секунды вместо миллисекунд. Аргументы и тип результата выглядят прежними. Один вызов перестаёт запрашивать обновление, другой продолжает правильно сортировать, а столбец CSV незаметно уменьшается в тысячу раз. Один список ссылок не объяснит, какой из этих эффектов произошёл.

Допустим, небольшое приложение показывает сессии, решает, когда обновлять их, и экспортирует их возраст. Это учебный пример; результаты ниже получены запуском JavaScript с фиксированными входами и неизменёнными утверждениями. Скачайте полный пример, чтобы воспроизвести их.

Уточните изменение до перечисления вызовов

Архив содержит обе версии функции в elapsed.mjs, чтобы одни и те же потребители могли работать с любой из них:

export function elapsedBefore(nowMs, startedMs) {
  return nowMs - startedMs;
}

export function elapsedAfter(nowMs, startedMs) {
  return (nowMs - startedMs) / 1000;
}

Оба входа остаются временными метками в миллисекундах. Предлагаемый результат — секунды, включая дробную часть: 2_500 миллисекунд превращаются в 2.5, а не 2. Для nowMs = 60_000 и startedMs = 15_000 результат меняется с 45_000 на 45.

Это задаёт точный вопрос: каким потребителям нужна старая единица, а каким — лишь свойство, сохраняемое преобразованием? Проверка типов, видящая число в обеих версиях, на него не ответит.

Если входные значения незнакомы, сначала проследите одно значение от источника по коду. Изменение единицы входа и выхода — разные правки; здесь меняется только выход.

Соберите список кандидатов по ссылкам

Найдите elapsed в редакторе или выполните:

rg -n 'elapsed' --glob '*.mjs' .

Откройте consumers.mjs. Стоит изучить два вызова:

export function needsRefresh(elapsed, nowMs, lastSyncMs) {
  return elapsed(nowMs, lastSyncMs) >= 30_000;
}

export function oldestFirst(elapsed, nowMs, sessions) {
  return [...sessions]
    .sort((a, b) => elapsed(nowMs, b.startedMs) - elapsed(nowMs, a.startedMs))
    .map((session) => session.id);
}

needsRefresh сравнивает значение с фиксированным порогом. oldestFirst сравнивает два значения той же функции. Отметьте оба как кандидатов и запишите обещания вызывающему коду: обновлять при возрасте тридцать секунд; возвращать ID сессий от старой к новой без изменения входного массива.

Ссылки установленного и настроенного языкового сервера помогают навигации по доступному проекту. Это подсказки для поиска. Псевдонимы, внедряемые функции, регистрация и конфигурация выполнения требуют проверки; список ссылок не является полным перечнем потребителей, выбираемых развёртыванием.

Проследите функцию через реестр

app.mjs передаёт функцию не только двум прямым потребителям. Он выбирает фабрику по конфигурации и передаёт ей функцию под другим именем:

const factory = registry.get(config.exporter);
if (!factory) throw new Error(`Unknown exporter: ${config.exporter}`);
const exporter = factory({ measureAge: elapsed });

Проследите register(registry) выше в том же файле. В режимах before и after он вызывает registerAgeColumn, реализованный в age-column.mjs:

export function registerAgeColumn(registry) {
  registry.set('age-column', ({ measureAge }) => ({
    kind: 'age-column',
    rows(nowMs, sessions) {
      return [
        'id,age_seconds',
        ...sessions.map(
          (session) =>
            `${session.id},${measureAge(nowMs, session.startedMs) / 1000}`,
        ),
      ].join('\n');
    },
  }));
}

Заголовок CSV обещает секунды. Со старой функцией деление на 1000 даёт их. С новой деление происходит дважды.

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

rg -n 'measureAge|age-column|exporter' --glob '*.mjs' .

Тестовое приложение явно выбирает { exporter: 'age-column' } и проверяет selectedExporter. Это связывает запись реестра с реально выполненным путём. Обнаружение фабрики без проверки выбора оставило бы её использование неясным. На сервере аналогичная работа может начинаться с маршрута и конфигурации, выбирающих путь запроса.

Проверьте одинаковые ожидания на обеих версиях

Распакуйте архив и перейдите в code-change-impact-analysis. Сторонние зависимости не нужны. Зафиксированный запуск использовал Node.js 22.22.1; используйте Node.js 22 или новее и выполняйте команды отдельно в терминале:

node run.mjs before
node run.mjs after
node run.mjs migrated

Ожидается, что after завершится кодом 1. Не объединяйте команды через &&: выполнение остановится до мигрированной версии. Все три режима загружают одни утверждения из tests.mjs.

Версия Успешно Сбой Код выхода
before: функция в миллисекундах, исходные потребители 7 0 0
after: функция в секундах, исходные потребители 4 3 1, ожидаемый сбой
migrated: функция в секундах, обновлённые потребители, зависящие от единицы 7 0 0

Три сбоя в after — решения об обновлении при 30_000 и 31_000 миллисекундах и вывод CSV. Проверка чуть ниже порога всё ещё проходит. Оба теста сортировки и отклонение неизвестного экспортёра также проходят.

Чтобы изучить значения вместе с утверждениями, выполните:

node demo.mjs

При nowMs = 60_000 сессия alpha начинается в 15_000, а beta — в 57_500. Выводимые наблюдения включают:

Наблюдение before after migrated
Обновление при возрасте 30 секунд true false true
ID от старой к новой alpha, beta alpha, beta alpha, beta
Возраст alpha в CSV 45 0.045 45
Возраст beta в CSV 2.5 0.0025 2.5

demo.mjs печатает наблюдения; tests.mjs решает, соответствуют ли они ожидаемому поведению. По умолчанию node --test run.mjs запускает только before, поэтому успешный запуск по умолчанию не проверяет предлагаемое изменение.

Отделите сломанного потребителя от проверенного исправного

Результат полезнее количества ссылок:

Потребитель или область Вывод Подтверждение
needsRefresh Влияние подтверждено Неизменённые тесты порога тридцать секунд падают с функцией в секундах.
oldestFirst Регрессия исключена для проверенных входов Разный возраст, дробные секунды, равный возраст, разница в миллисекунду, пустой список и один элемент сохраняют ожидаемый порядок; вход не меняется.
Выбранный экспортёр age-column Влияние подтверждено для этой конфигурации Тест выбирает фабрику и сравнивает полную строку CSV. После изменения оба возраста в тысячу раз меньше нужного.
Другие конфигурации развёртывания или потребители вне архива Неизвестно Они не предоставлены и не запускались.

Результат сортировки тоже объясним: сравнение использует разность двух возрастов. Деление обоих на один положительный множитель сохраняет знак для этих входов. Сортировке нужен относительный порядок, а порогу и экспортёру — конкретная единица.

Это рассуждение помогает выбрать тесты, но не позволяет утверждать, что проверены все числовые входы. Фикстуры используют небольшие конечные целочисленные метки миллисекунд. Экстремальные числа с плавающей точкой, неверные входы, асинхронное планирование обновлений и реальный читатель CSV не входят в запуск. Архив содержит один экспортёр и ничего не утверждает о производственном наборе плагинов.

Обновите потребителей, зависящих от единицы

Оставьте elapsedAfter. В consumers.mjs обновлённая функция сравнивает секунды с секундами:

export function needsRefreshSeconds(elapsed, nowMs, lastSyncMs) {
  return elapsed(nowMs, lastSyncMs) >= 30;
}

В age-column.mjs функция registerAgeColumnSeconds выводит measureAge(...) напрямую, убирая второе деление. app.mjs выбирает две обновлённые реализации в режиме migrated. oldestFirst не меняется.

Тесты по-прежнему требуют те же видимые результаты: обновление через тридцать секунд, те же отсортированные ID и секунды в CSV. Семь успешных финальных проверок показывают восстановление результатов для фикстуры. Если патч меняет ожидания, проверьте утверждения вместе с реализацией, прежде чем считать зелёный запуск эквивалентным.

Попросите ИИ предложить следующий файл для изучения

При чтении необязательный вопрос ИИ может сузить поиск:

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

Проверяйте предложенные пути по исходникам и запускайте команды сами.