설정 파일에는 limit: 6이라고 되어 있는데 보고서는 네 행만 출력합니다. 이유를 찾아 달라는 요청을 받았지만 이 저장소는 처음 봅니다. CLI, 설정 로더, 몇 개의 서비스, 테스트 폴더가 있습니다. 모두 읽으면 언젠가 답을 찾겠지만, 행 수를 정하는 값 하나를 따라가는 편이 훨씬 빠릅니다.

질문을 구체화하세요

“보고 모듈 이해하기”에는 끝이 없습니다. 구체적인 답을 얻을 수 있는 질문에는 끝이 있습니다.

설정은 limit: 6이고 환경에는 REPORT_LIMIT=4가 있습니다. 보고서는 네 행을 출력합니다. 어느 쪽이 우선했고, REPORT_LIMIT가 빈 문자열이면 어떻게 되나요?

실행한 명령, 디렉터리, 커밋을 적으세요. 입력에 네 개보다 많은 레코드가 있는지도 확인하세요. 원본 데이터가 네 행뿐이라면 네 행이 반환되어도 제한값에 관해 알 수 있는 것이 없습니다.

가진 단서로 시작할 파일을 찾으세요

처음 유용한 파일은 제한값을 고르는 함수가 아니라 실행기, 포매터, 테스트일 수 있습니다. 재현 가능한 단서로 함수에 도달하고 실제로 확인한 연결을 기록하세요.

보고서 진입점 실습을 다운로드하고 압축을 푼 뒤 report-entry-lab으로 들어가세요. 이 작은 프로젝트는 이 튜토리얼용으로 만들었습니다. 아래와 같은 우선순위 규칙 주위에 실제 명령, 디스패치 테이블, 로컬 fixture 로더를 더했습니다. Node.js 22 이상, npm, ripgrep(rg)이 필요하며 의존성 설치나 모델 계정은 필요하지 않습니다. 여기의 셸 명령은 macOS와 Linux에서 사용할 수 있습니다.

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

설정 fixture에는 limit: 6, 데이터 fixture에는 여덟 행이 있습니다. 첫 줄은 헤더이고 뒤에 데이터 네 행이 나옵니다. 짧은 입력 파일 때문에 생긴 현상이 아니라 실제 불일치입니다. 프로그램은 포함된 로컬 fixture만 읽고 터미널에 출력합니다. 테스트는 로컬 Node 프로세스를 실행하며 네트워크 요청이나 파일 쓰기는 하지 않습니다.

가진 단서 시작할 곳 더 확인할 내용
동작을 재현하는 명령 호출하는 패키지 스크립트나 실행 파일 어떤 인자가 어떤 핸들러를 선택하는가
특징적인 출력이나 오류 문자열 문자열을 만드는 코드 결과가 생산자, fixture, 검증문 중 무엇이며 생산자는 누가 호출하는가
이름을 아는 테스트 import와 테스트 안의 호출 실제 명령을 시작하는가, 대체 의존성을 전달하는가
설정 키나 환경 변수 값을 읽고 변환하는 코드 어떤 호출자가 도달하며 어떤 로더가 설정을 공급하는가

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의 포매터 import와 호출을 따라가세요.
  3. src/report.mjs, src/limits.mjs, 관련 테스트를 탭으로 열어 두세요. 각 단계를 뒷받침하는 파일과 줄을 적고 아래 터미널 검사로 직접 함수 테스트와 실제 CLI 실행을 구분하세요.

파일 탐색과 텍스트 검색에는 모델 연결이나 JavaScript 언어 서버가 필요하지 않습니다. Mac용 Yenpo를 다운로드해 같은 압축 해제 폴더를 열어 보세요. 읽기 도움말에서 두 검색 도구를 설명합니다. 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 속성은 report-config.json과 rows.json의 로더를 넣어 runReport를 호출합니다. 옆의 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 import와 선택한 이름을 전달하는 호출을 모두 확인하세요. 이어서 runReport를 commands.mjs의 report 등록까지 거슬러 올라가고 선택된 핸들러에서 cli.mjs의 stdout 쓰기까지 따라가세요.

이제 위 명령으로 검증할 경로가 있습니다. 실제 출력이 여러 조각으로 조립된다면 안정적인 일부를 검색하고 주변 식이 만들어지는 과정을 읽으세요. 공용 포매터에 호출자가 여러 개면 실행 인자나 좁은 테스트로 하나를 선택할 때까지 후보로 남겨 두세요. 전부 같은 실행 경로로 합치지 마세요.

테스트 이름을 알고 있는 경우

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

해당 테스트는 runReport를 직접 import하고 자체 환경, 설정 로더, 행 로더를 전달합니다. 함수 안에서 4가 6보다 우선함은 확인하지만 cli.mjs를 시작하거나 설정 파일을 읽지는 않습니다. 실제 명령이 어떤 파일을 읽는지는 알려 주지 못합니다.

import를 따라 동작을 찾은 뒤 실제 commands.report 바인딩을 확인하세요. 별도 테스트가 빠진 경계를 통과합니다.

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

이 테스트는 REPORT_LIMIT=4를 넣어 자식 프로세스에서 실제 CLI를 실행하고 종료 상태와 stdout을 확인합니다. 선택한 두 테스트 모두 통과합니다. 하나는 의존성을 직접 선택하고 다른 하나는 실행과 파일 어댑터를 거친다는 차이를 적어 두세요. 직접 함수 테스트의 통과가 후자를 대신하지는 않습니다.

설정 이름을 알고 있는 경우

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

소스의 읽기는 limits.mjs에 있고 다른 결과는 테스트 입력을 준비합니다. report.mjs의 resolveLimit 호출을 따라간 뒤 commands.report가 공급하는 loadConfig 함수를 확인하세요. new URL(..., import.meta.url)이 모듈 기준으로 포함된 fixture를 선택합니다. 파일명 일치만으로 이 핸들러가 해당 파일을 사용한다고 확인할 수는 없습니다.

아래 값 조사에 필요한 출발점이 마련되었습니다. 명령, 선택된 핸들러, 실제 입력 파일, 출력 위치를 확인했으므로 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');
}

헬퍼를 열기 전부터 이 함수는 순서를 보여 줍니다. 인자 파싱, 설정 읽기, 제한 선택, 행 읽기, 출력입니다. 놓치기 쉬운 점도 있습니다. loadConfig()가 성공해야 resolveLimit()가 실행됩니다. 나중에 중요해집니다.

실제 코드에서는 import를 따라가세요. 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로 종료하고 stderr에 limit must be an integer from 1 to 100을 출력하며 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가 오류를 어떻게 보여 주는지 등입니다. 네 행이 나온 이유와 열린 질문을 설명할 수 있으면 일단 멈춰도 됩니다. 아직 프로젝트 전체를 이해할 필요는 없습니다.

다른 관리자가 인수인계 내용을 확인하게 하세요

대화 기록 없이 다른 사람이 재현할 수 있으면 읽기 메모가 더 유용해집니다. poster-desk 인수인계 ZIP과 간단한 인수인계 요약을 사용해 보세요. 이 작은 오프라인 프로젝트와 주장은 학습용으로 만들었으며 실제 AI 생성 앱이나 실제 인수인계 기록은 아닙니다.

받는 사람은 고정된 소스 스냅샷에서 네 가지 약속을 확인합니다. 새 프로세스가 로컬 카드를 읽을 수 있는지, 정확히 같은 순차 재시도가 레코드를 재사용하는지, 영수증은 fixture 미리보기뿐인지, 배포 교체 후 보존 여부가 여전히 미확인인지입니다. “구현됨”은 테스트한 조건만 뜻합니다. 순차 재시도는 동시성 안전성을 증명하지 않고 mock은 아무것도 전송하지 않으며 패키지에는 배포 저장소 증거가 없습니다.

요약에 따라 RECEIVER.md를 압축 해제한 패키지 밖으로 복사하고 명령, 결과, 소스 위치, 열린 질문을 채우세요. 전달받은 템플릿은 그대로 유지한 채 원본 패키지를 다시 검증하세요.

ZIP SHA-256
c39d19b210c05f4667cd74db4934459a079408d14c5e7678c24cd087e1df5185

Yenpo에서는 파일로 이동과 워크스페이스 검색으로 핸들러, 해석 함수, 테스트를 나란히 열어 둘 수 있습니다. AI Reader에 질문할 때는 하나의 값에 집중하세요. “REPORT_LIMIT를 읽는 곳부터 사용하는 곳까지 줄 참조와 함께 추적해 주세요.” 프로젝트 전체의 요약 대신 단계별로 확인할 경로를 얻을 수 있습니다.

가진 증거에 따라 다음 읽기 작업을 선택하세요.