/*
RFC-0364: Astro file normalizer using @astrojs/compiler AST — semantic hash invariant to formatting and comment changes.
- Do not hash raw bytes — that is the binary normalizer's job.
- RFC-1097: step 6 — compass.migrate codemod run
Mechanical v1 to v2 header migration across the workspace: 942 files rewritten — CHANGE_SUMMARY windows collapsed into history, forbidden v1 blocks stripped, KEY_DECISIONS seeded from @ai-invariant comments (5 files) or TODO placeholders (103 files), blocks reordered to canonical order.
- RFC-1097: sweep — werkstatt-engine clean
Sweep batch 4: 73 Compass headers on headerless engine files (certification, component-runtime, isolation, evolution, testing), real KEY_DECISIONS on 75 files (kernel, cache, dht, swim, gitmesh, runtime), ~80 purpose expansions (CONTRACT-02/PURPOSE-02), non-goals on 13 CONTRACT-03 files, CS-07 history literal fix repo-wide (253 files). Policy: .template.ts/.template.astro excludedPaths. werkstatt-engine now 0 diagnostics.
*/
import { parse as parseAstro } from "@astrojs/compiler";
import type { Node } from "@astrojs/compiler/types";
import { byteHash } from "@warpgogol/werkstatt-shared/fingerprint";
interface NormalizedAttribute {
name: string;
value: string;
kind: string;
}
interface NormalizedAstroNode {
type: string;
name?: string;
value?: string;
attributes?: NormalizedAttribute[];
children?: NormalizedAstroNode[];
}
function normalizeNode(node: Node): NormalizedAstroNode {
if (node.type === "frontmatter") {
return { type: "frontmatter", value: node.value.trim() };
}
if (
node.type === "element" ||
node.type === "component" ||
node.type === "custom-element" ||
node.type === "fragment"
) {
return {
type: node.type,
name: node.name,
attributes: (node.attributes ?? []).map((attr) => ({
name: attr.name,
value: attr.value?.trim() ?? "",
kind: attr.kind,
})),
children: (node.children ?? []).map(normalizeNode),
};
}
if (node.type === "text") {
return { type: "text", value: (node.value ?? "").trim() };
}
if (node.type === "expression") {
return { type: "expression", children: (node.children ?? []).map(normalizeNode) };
}
if (node.type === "root") {
return { type: "root", children: (node.children ?? []).map(normalizeNode) };
}
return { type: node.type };
}
export async function normalizeAstro(content: string): Promise {
const result = await parseAstro(content, { position: false });
const normalized = normalizeNode(result.ast);
return byteHash(JSON.stringify(normalized));
}