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:
parent
ee3bef2ce5
commit
9bcb45cbd3
122 changed files with 5836 additions and 831 deletions
|
|
@ -1,7 +1,6 @@
|
|||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { manifestPath } from "./paths.ts";
|
||||
import { readIfExists, sha256, writeAtomic } from "./fsx.ts";
|
||||
import { readIfExists, samePath, sha256, writeAtomic } from "./fsx.ts";
|
||||
import { extractBlock } from "./marker.ts";
|
||||
import type { DriftReport, InstallRecord, Manifest, Scope, TargetId } from "./types.ts";
|
||||
|
||||
|
|
@ -10,22 +9,34 @@ const EMPTY: Manifest = { schema: 1, installs: [] };
|
|||
export async function readManifest(home?: string): Promise<Manifest> {
|
||||
const raw = await readIfExists(manifestPath(home));
|
||||
if (!raw) return structuredClone(EMPTY);
|
||||
// 손상된 매니페스트로 설치 전체가 막히지 않도록 빈 것으로 되돌린다(원본은 writeManifest 가 보존한다)
|
||||
return parseManifest(raw) ?? structuredClone(EMPTY);
|
||||
}
|
||||
|
||||
function parseManifest(raw: string): Manifest | null {
|
||||
try {
|
||||
const parsed = JSON.parse(raw) as Manifest;
|
||||
if (parsed.schema !== 1 || !Array.isArray(parsed.installs)) return structuredClone(EMPTY);
|
||||
if (parsed.schema !== 1 || !Array.isArray(parsed.installs)) return null;
|
||||
return parsed;
|
||||
} catch {
|
||||
// 손상된 매니페스트로 설치 전체가 막히지 않도록 빈 것으로 되돌린다
|
||||
return structuredClone(EMPTY);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function writeManifest(m: Manifest, home?: string): Promise<void> {
|
||||
await writeAtomic(manifestPath(home), JSON.stringify(m, null, 2) + "\n");
|
||||
const file = manifestPath(home);
|
||||
// 손상된 매니페스트를 빈 것으로 읽은 뒤 그대로 덮으면 다른 설치 기록이 영구 소실된다.
|
||||
// 덮기 전에 원본을 manifest.json.corrupt-<시각> 으로 옮겨 복구 여지를 남긴다.
|
||||
const prev = await readIfExists(file);
|
||||
if (prev !== null && prev.trim().length > 0 && parseManifest(prev) === null) {
|
||||
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
||||
await fs.rename(file, `${file}.corrupt-${stamp}`).catch(() => undefined);
|
||||
}
|
||||
await writeAtomic(file, JSON.stringify(m, null, 2) + "\n");
|
||||
}
|
||||
|
||||
function sameInstall(a: InstallRecord, target: TargetId, scope: Scope, root: string): boolean {
|
||||
return a.target === target && a.scope === scope && path.resolve(a.root) === path.resolve(root);
|
||||
return a.target === target && a.scope === scope && samePath(a.root, root);
|
||||
}
|
||||
|
||||
export function findInstall(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue