라이프스타일

Skill 선택과 결과 검증

형식, 선택, 결과를 나누어 시험하고 맞는 요청과 맞지 않는 요청으로 설명을 개선합니다.

읽는 데 약 15분 · 실습 30 분

직접 제작한 흐름도이며 제품 화면이 아닙니다.
사진: Mokaair (© Mokaair)
이 글의 목차
  1. 목표와 준비
  2. 1단계: 결과와 실패 조건 정의
  3. 2단계: 반복 가능한 테스트 여덟 개
  4. 3단계: 테스트가 결함을 잡는지 확인
  5. 4단계: 스킬 행동 검수 설계
  6. 5단계: 기록, 수정, 재검증

목표와 준비

이 단락의 학습 자료:

통과의 범위는 두 가지입니다. 자동 테스트 여덟 개는 지정 입력에 대한 프로그램 동작만 확인합니다. 스킬 검수는 선택, 파일 읽기, 도구 기록, 응답도 관찰해야 합니다. Node.js 성공을 올바른 스킬 활성화로 간주하지 않도록 따로 기록하세요. 특정 모델은 필요 없으며 실제 진입점과 설정을 남깁니다.

1단계: 결과와 실패 조건 정의

테스트 전에 표를 확인하세요. 정상 사례는 Read 두 개를 보존하고 빈 배열은 유효합니다. 중복 ID, 잘못된 타입, 손상된 JSON은 명확히 실패해야 하며 문제 행을 지운 뒤 그럴듯한 개수를 반환하면 안 됩니다. 잘못된 입력에서 stdout에 부분 성공 JSON이 섞이면 후속 스크립트가 오인할 수 있습니다.

사례검사기 종료허용 결과
동명 포함 세 작업0total 3, active 1, completed 2
빈 배열0세 개수 모두 0
중복 ID1Duplicate id, 성공 JSON 없음
completed가 문자열1Invalid task, 자동 변환 없음
배열 아님, 손상 JSON, 누락 파일/인수1명확한 오류, 성공 집계 없음

2단계: 반복 가능한 테스트 여덟 개

codex-skill-lab 루트에 skill.test.mjs를 만들고 전체 코드를 붙이세요. Node.js 내장 테스트라 추가 패키지가 필요 없습니다. 각 사례는 임시 입력을 만들고 실제 검사기를 실행해 종료 코드, 출력, 원본 바이트를 비교합니다. 정리는 방금 만든 임시 폴더만 삭제하며 data/tasks.json은 건드리지 않습니다.

skill.test.mjs · javascript
import test from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, writeFileSync, readFileSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { resolve, join, dirname } from 'node:path';
import { spawnSync } from 'node:child_process';

const script = resolve('.agents/skills/todo-summary/scripts/count-tasks.mjs');
const tasks = [
  { id: 'a', title: 'Read', completed: true },
  { id: 'b', title: 'Build', completed: false },
  { id: 'c', title: 'Read', completed: true },
];
function run(input, verify, missing = false) {
  const folder = mkdtempSync(join(tmpdir(), 'todo-skill-test-'));
  const file = join(folder, 'input.json');
  try {
    if (!missing) writeFileSync(file, input, 'utf8');
    const before = missing ? null : readFileSync(file);
    const result = spawnSync(process.execPath, [script, file], { encoding: 'utf8' });
    assert.equal(result.error, undefined);
    verify(result);
    if (!missing) assert.deepEqual(readFileSync(file), before, 'Input changed');
  } finally {
    assert.equal(dirname(resolve(folder)), resolve(tmpdir()));
    rmSync(folder, { recursive: true });
  }
}
function fails(input, pattern) {
  run(input, result => {
    assert.equal(result.status, 1);
    assert.equal(result.stdout, '');
    assert.match(result.stderr, pattern);
  });
}
test('keeps repeated titles as three records', () => run(JSON.stringify(tasks), result => {
  assert.equal(result.status, 0);
  assert.equal(result.stderr, '');
  assert.deepEqual(JSON.parse(result.stdout), { total: 3, active: 1, completed: 2 });
}));
test('accepts an empty array', () => run('[]', result => {
  assert.equal(result.status, 0);
  assert.deepEqual(JSON.parse(result.stdout), { total: 0, active: 0, completed: 0 });
}));
test('rejects duplicate IDs', () => fails(JSON.stringify([tasks[0], tasks[0]]), /Duplicate id/));
test('rejects string completed', () => fails(JSON.stringify([{ ...tasks[0], completed: 'true' }]), /Invalid task/));
test('rejects a non-array', () => fails('{}', /Input must be an array/));
test('rejects malformed JSON', () => fails('{', /\S/));
test('reports a missing file', () => run('', result => {
  assert.equal(result.status, 1);
  assert.equal(result.stdout, '');
  assert.match(result.stderr, /ENOENT/);
}, true));
test('requires an input argument', () => {
  const result = spawnSync(process.execPath, [script], { encoding: 'utf8' });
  assert.equal(result.status, 1);
  assert.equal(result.stdout, '');
  assert.match(result.stderr, /Usage:/);
});

실습 루트의 PowerShell, macOS, Linux에서 다음 명령을 실행하세요. 올바른 검사기는 8통과 0실패, 테스트 실행기 종료 0이어야 합니다. 거부해야 할 입력은 올바르게 거부되면 테스트가 통과합니다. 검사기의 종료 1과 테스트 묶음 자체의 실패를 구분하세요.

실습 루트에서 실행 · sh
node --test skill.test.mjs

스크립트를 못 찾으면 루트와 실제 경로를, 모두 Usage이면 file 인수가 전달되는지 확인하세요. JSON 오류 문구는 Node 버전에 따라 달라 해당 테스트는 특정 문장부호 대신 비어 있지 않은 오류를 요구합니다. 실패 출력을 보존하고 버전을 비교하며 초록 결과를 만들려고 부정 사례를 삭제하지 마세요.

3단계: 테스트가 결함을 잡는지 확인

count-tasks.mjs를 count-tasks.saved.mjs로 복사하고 백업을 확인하세요. 원본 result 행의 total: tasks.length만 다음 조각으로 바꿔 동명 두 개를 하나로 세는 오류를 만듭니다. active와 completed는 원래 개수입니다. 의도적인 국소 결함이며 최종 버전이 아닙니다.

result 안의 total만 교체 · javascript
total: new Set(tasks.map(task => task.title)).size

같은 명령을 실행하면 7통과 1실패이며 keeps repeated titles as three records가 실패해야 합니다. actual total 2와 expected 3으로 계약 위반을 잡은 것을 확인합니다. 검사기만 백업에서 복원하고 다시 8통과인지 확인하세요. 마지막 초록 결과뿐 아니라 기준, 결함, 복원 결과를 모두 남깁니다.

4단계: 스킬 행동 검수 설계

복원 후 Codex를 검수합니다. 표의 각 행마다 새 작업에서 폴더를 확인하고 이전 규격, 완성 보고서, 수동으로 준 정답이 섞이지 않게 합니다. 정확한 요청, 수동 선택 여부, 읽은 파일, 명령, 종료 값, 결과를 기록해 선택 문제와 실행 문제를 구분하세요.

상황자연어 요청확인점
명시todo-summary 선택 후 data/tasks.json을 응답으로만 집계자원 읽기, 실행, 3/1/2, 원본 보존
적합로컬 작업 JSON의 전체/미완료/완료 개수 집계선택 여부 기록, 사용했다고 거짓 주장하지 않음
범위 밖사이트 배경을 파랑으로 바꾸는 계획만, 수정 금지이 요청 때문에 작업 검사기를 실행하지 않음
필수 자원 없음규격 이름을 바꾼 뒤 검증 완료 보고서 명시 요청누락 보고 후 중단, 규격/결과를 지어내지 않음

데스크톱은 Skills나 @, CLI/IDE는 /skills나 $를 사용합니다. 자동 선택은 설명과 문맥에 따라 달라 미선택만으로 설치 실패라 단정할 수 없습니다. 먼저 명시 선택이 성공하는지 확인하고 description의 용도와 제외 조건을 점검하세요. 모든 작업에 사용한다고 넓히면 관련 없는 작업에도 적용됩니다.

명시적 호출만 허용하는 비교 연습

이 독립 todo-summary 스킬에 아래 설정으로 agents/openai.yaml을 만듭니다. 이미 있다면 복사본을 저장하고 interface와 dependencies를 보존하면서 policy만 병합합니다. 공식 스킬 문서에 따르면 false는 암시적 호출을 막지만 명시적 언급은 계속 허용합니다. 호출 방식의 설정이지 샌드박스나 도구 권한 부여가 아닙니다.

agents/openai.yaml: 병합할 조각 · yaml
policy:
  allow_implicit_invocation: false

저장 후 data/tasks-next.json을 대상으로 새 작업 두 개를 만듭니다. 하나는 스킬 선택 없이 개수만 요청하고, 다른 하나는 todo-summary를 명시적으로 선택합니다. 전자는 이 스킬을 자동 선택하지 않아야 하고 후자는 사용할 수 있어야 합니다. 파일 읽기와 도구 활동을 각각 기록하세요. 일반 파일 도구로 숫자를 맞혔다고 스킬 호출의 증거가 되는 것은 아닙니다. 갱신이 반영되지 않으면 Codex를 재시작해 재검사하고 미확인 항목은 남깁니다.

이 비교는 지원되는 실제 제품에서 실행해야 합니다. YAML 파싱 성공이나 Node 테스트 8개 통과로 호출 정책을 검증할 수는 없습니다. 마칠 때 이번에 새로 만든 openai.yaml만 제거하거나 기존 파일을 백업에서 복원하고 새 작업에서 원래 설정을 확인합니다. 명시적 호출만 원하면 false를 유지하고 그 결정과 파일 경로를 기록합니다.

5단계: 기록, 수정, 재검증

다음 검수 기록에서 측정하지 않은 값은 미실행으로 쓰고 예상값을 채우지 마세요. 자원 문제는 경로, 잘못된 선택은 description, 집계 문제는 코드처럼 하나씩 수정합니다. 이전 버전과 실패 사례를 보존하고 영향받은 테스트와 새 작업을 다시 실행하세요. SKILL.md 수정만으로 이전 작업의 재로딩을 입증할 수 없습니다.

검수 기록 템플릿 · markdown
# Skill acceptance record

Date and surface: <observed>
Node / Codex versions: <observed>
Model and effort, if shown: <observed or unavailable>
Skill path and revision: <exact local path and saved version>
Program tests: <command, exit, passes, failures>
Behavior case: <explicit / matching / outside-scope / missing-resource>
Request: <exact text>
Selected skill: <observed / not selected / unconfirmed>
Resources read and command executed: <evidence or not run>
Actual result and preserved input: <evidence>
Failure and one change: <description>
Fresh-task retest: <result or not run>
Remaining checks: <not run>

실행했다고 주장하지만 예상값만 제시하면 실제 명령과 도구 출력을 요구하고 확인할 수 없으면 미확인 상태를 유지하세요. 같은 이름의 스킬이 여러 개면 선택 경로를 기록하고 자신이 만든 중복만 비활성화하거나 옮긴 뒤 재검증합니다. 다른 사람의 스킬을 보존하고 로컬 경로 오류를 전역 설정 변경으로 숨기지 마세요.

마치기 전에 input-format.md 이름과 올바른 검사기를 복원하고 data/tasks.json이 그대로이며 마지막 여덟 테스트가 통과하는지 확인하세요. 코드와 스킬 행동 결과는 따로 보고하고 macOS/Linux를 조작하지 않았다면 문서와 공통 Node.js 사용법으로 확인했다고 기록합니다. 참고 테스트는 모든 진입점에서 모델을 실행했다는 증거가 아닙니다.

새 스킬도 입력을 고정하고 실패 조건을 정한 뒤 반복 검사를 남기세요. 재사용 요청이나 문서 틀은 , 여러 스킬 배포는 를 읽으세요. 그림 1은 사례 고정, 2는 실행과 관찰, 3은 수정 후 재검사입니다.

직접 제작한 흐름도이며 제품 화면이 아닙니다.
직접 제작한 흐름도이며 제품 화면이 아닙니다. · 사진: Mokaair (© Mokaair)
자세한 설명 보기

Three numbered stages: identify the starting point, perform the exercise, and verify the result. Original illustration, not a product screenshot.

전체 목차

  • 라이프스타일

    Codex 학습 센터: 전체 튜토리얼 목차

    설치와 첫 작업부터 MD 지침과 고급 연동까지 60개 강의, 열 개 단원을 계획합니다. 수준, 환경, 목표, 명령으로 다음 글을 찾고 미게시 항목의 상태를 확인할 수 있습니다.

  • 라이프스타일

    Worktree와 작업 격리

    Worktree는 하나의 Git 저장소에 다른 브랜치의 작업 폴더를 만듭니다. 파일이 분리되어도 DB, 포트와 외부 서비스는 공유될 수 있습니다.

  • 라이프스타일

    실습: 작은 웹사이트 만들기

    brief.md에서 Small Steps 할 일 사이트를 계획하고 추가·완료·삭제·필터·로컬 저장을 구현합니다. HTML·CSS·데이터 함수·화면 이벤트·시험을 분리하고 Node와 브라우저로 검증한 뒤 재시작·복원 인계 기록을 남깁니다.

  • 라이프스타일

    사용량과 효율: 재작업 줄이기

    조건, 모델 선택, 시간, 결과를 기록해 불필요한 재시도와 과도한 문맥을 줄입니다.

최신 여행 소식·가이드

출처

라이프스타일