feat(core,cli): harden install engine and extend skill lint and interview eval

- update only touches the recorded project root; project installs do not
  follow symlinks or junctions; repeated --force rotates .orig backups;
  uninstall never deletes outside the recorded root
- run npm/npx without a shell on Windows and pin optional tool versions
- skip the update check in CI and non-interactive output
- lint: cross-document section pointers, preflight row ids, gate rule
  numbers, per-document size budgets, gate copy equality
- eval: scenarios S19-S21, route and read-cost rejudging, interpretation
  card judging, temp-dir previews and table-form cards no longer fail
This commit is contained in:
Yun Chan 2026-10-06 16:55:38 +09:00
parent ee3bef2ce5
commit 9bcb45cbd3
122 changed files with 5836 additions and 831 deletions

View file

@ -3,6 +3,7 @@
import fs from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { isAllowedSkillFile } from "./skill-allowlist.mjs";
const here = path.dirname(fileURLToPath(import.meta.url));
const src = path.resolve(here, "../../skill");
@ -14,15 +15,25 @@ const dest = path.resolve(here, "../dist/skill");
// 배포물은 어느 OS 에서 만들었는지와 무관하게 같아야 한다.
const TEXT = new Set([".md", ".txt", ".json", ".yml", ".yaml"]);
async function copyDir(from, to) {
await fs.mkdir(to, { recursive: true });
const excluded = [];
async function copyDir(from, to, rel = "") {
for (const e of await fs.readdir(from, { withFileTypes: true })) {
if (e.name === "node_modules" || e.name === "package.json") continue;
if (e.name === "node_modules") continue;
const r = rel ? `${rel}/${e.name}` : e.name;
const s = path.join(from, e.name);
const d = path.join(to, e.name);
if (e.isDirectory()) {
await copyDir(s, d);
} else if (TEXT.has(path.extname(e.name))) {
await copyDir(s, d, r);
continue;
}
// 허용 목록 밖(테스트·스크래치·dotfile·package.json 등)은 사용자 설치물에 싣지 않는다
if (!e.isFile() || !isAllowedSkillFile(r)) {
if (e.name !== "package.json") excluded.push(r);
continue;
}
await fs.mkdir(to, { recursive: true });
if (TEXT.has(path.extname(e.name))) {
const text = await fs.readFile(s, "utf8");
await fs.writeFile(d, text.replace(/\r\n/g, "\n"), "utf8");
} else {
@ -40,3 +51,4 @@ await fs.writeFile(path.join(dest, ".designpaca_version"), `${pkg.version}\n`, "
const count = (await fs.readdir(dest, { recursive: true })).length;
console.log(`스킬 번들 완료: ${count}개 항목 → dist/skill`);
if (excluded.length > 0) console.log(` 설치물에서 제외: ${excluded.join(", ")}`);