세 가지를 고친다. 전부 실제 사용에서 드러난 것이다.
1) 0단계에서 가정으로 채우던 것을 인터뷰로 바꾼다
"꽃집 사이트 만들어보자"를 받고 업종 성격·목표 행동·톤·이름을 혼자 정했다.
물어보니 넷 중 넷이 달랐다(일상 구독 → 하이엔드 스튜디오, 문의 하나 → 넷 다,
톤 미정 → 에디토리얼, 이름 지어냄 → 목요일의 화원).
그대로 갔으면 레퍼런스 세 개를 전부 틀린 방향에서 골랐다.
SKILL.md 0단계에 질문 도구로 한 번에 묻는 절차를 넣었다. 무엇을 묻고
무엇을 묻지 않는지, 답이 모순될 때 어떻게 정리하는지까지 적었다.
2) 레퍼런스가 막히면 브라우저를 띄운다
Aesop(403) → Kinto(404) → Hasami(DNS) → MUJI(타임아웃)로 네 번 왕복하고
톤 레퍼런스를 하나도 못 얻었다. 좋은 레퍼런스일수록 봇을 막는다.
galleries.md 에 headed 브라우저로 직접 열어 스크린샷과 실측값을 받는
방법을 넣고, 접근 실패 2회면 바로 전환하도록 규칙을 세웠다.
Playwright 를 선택 의존성으로 잡았다(core/tools.ts).
- 온보딩 마지막에 설치 여부를 묻는다. 건너뛰어도 스킬은 동작한다
- `designpaca tools` 로 상태 확인, `--yes` 로 설치
- 수백 MB 라 --yes 없이는 상태만 보여준다
- MCP 서버는 설치만 하고 등록 명령은 안내만 한다(에이전트 설정을
대신 건드리지 않는다)
3) 온보딩 말투
"이제 브리프를 던져라", "설치해라", "건너뛴다" — 사용자를 향한 문구가
명령조였다. 스킬 문서의 단정한 반말은 의도지만 CLI UI 는 다르다.
전부 존댓말로 바꾸고, 각 단계가 왜 필요한지 설명을 붙였다.
명령조가 다시 섞이지 않도록 검사하는 테스트를 넣었다(테스트 22 → 25개).
55 lines
2.1 KiB
TypeScript
55 lines
2.1 KiB
TypeScript
import { inspectDrift, readManifest, tildify } from "@designpaca/core";
|
|
import { getVersion } from "../skill.ts";
|
|
import { runInstall, printOutcomes } from "./install.ts";
|
|
import { dim, heading, info, ok, warn } from "../ui.ts";
|
|
|
|
/**
|
|
* 설치 기록을 그대로 따라가며 재설치한다.
|
|
* 사용자가 고친 파일은 기본적으로 보존된다(applyPlan 이 판단) — 강제로 맞추려면 --force.
|
|
*/
|
|
export async function runUpdate(opts: { force?: boolean } = {}): Promise<number> {
|
|
const version = await getVersion();
|
|
const manifest = await readManifest();
|
|
|
|
if (manifest.installs.length === 0) {
|
|
console.log(warn("설치 기록이 없습니다. `npx designpaca install` 을 먼저 실행해 주세요."));
|
|
return 1;
|
|
}
|
|
|
|
console.log(heading(`업데이트 → v${version}`));
|
|
const stale = manifest.installs.filter((i) => i.version !== version);
|
|
|
|
if (stale.length === 0) {
|
|
console.log(ok("설치된 스킬이 모두 최신 버전입니다"));
|
|
|
|
// 버전이 같아도 파일이 사라졌거나 수정됐을 수 있다. 조용히 넘기면 사용자가 모른다.
|
|
let broken = 0;
|
|
for (const rec of manifest.installs) {
|
|
const drift = await inspectDrift(rec);
|
|
const missing = drift.files.filter((f) => f.status === "missing");
|
|
if (missing.length > 0) {
|
|
broken += missing.length;
|
|
console.log(warn(`${rec.target}: 파일 ${missing.length}개가 사라졌다 ${dim(tildify(rec.root))}`));
|
|
}
|
|
if (drift.modified.length > 0) {
|
|
console.log(info(`${rec.target}: 직접 수정한 파일 ${drift.modified.length}개는 그대로 둔다`));
|
|
}
|
|
}
|
|
|
|
if (!opts.force) {
|
|
console.log(dim(broken > 0 ? " 사라진 파일을 복구하려면 --force" : " 강제로 다시 쓰려면 --force"));
|
|
return broken > 0 ? 1 : 0;
|
|
}
|
|
}
|
|
|
|
for (const rec of manifest.installs) {
|
|
console.log(info(`${rec.target} (${rec.scope}) ${dim(tildify(rec.root))} — ${rec.version} → ${version}`));
|
|
const outcomes = await runInstall({
|
|
targets: [rec.target],
|
|
scope: rec.scope,
|
|
force: opts.force,
|
|
});
|
|
printOutcomes(outcomes);
|
|
}
|
|
return 0;
|
|
}
|