diff --git a/src/services/workspace.ts b/src/services/workspace.ts index 5295605..34b1c16 100644 --- a/src/services/workspace.ts +++ b/src/services/workspace.ts @@ -134,6 +134,34 @@ function normalizeFileIndex(raw: unknown): string[] { return Object.keys(asRecord(raw)).map(normalizeKey); } +function normalizeFrameworkManifest(raw: unknown): FrameworkManifest | undefined { + if (Array.isArray(raw)) { + const manifest: FrameworkManifest = {}; + for (const entry of raw) { + const framework = asRecord(entry); + const name = + (typeof framework.name === "string" && framework.name) || + (typeof framework.framework === "string" && framework.framework) || + undefined; + if (name) { + const version = framework.version; + manifest[name] = + typeof version === "string" || typeof version === "number" || typeof version === "boolean" ? version : true; + continue; + } + for (const [key, value] of Object.entries(framework)) { + if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") { + manifest[key] = value; + } + } + } + return Object.keys(manifest).length > 0 ? manifest : undefined; + } + + const manifest = asRecord(raw); + return Object.keys(manifest).length > 0 ? manifest : undefined; +} + export function normalizeWorkspace(sourcePath: string, parsed: unknown): NormalizedWorkspace { const root = optionalRecord(parsed, "root"); const manual = optionalRecord(root.manual, "manual"); @@ -147,11 +175,7 @@ export function normalizeWorkspace(sourcePath: string, parsed: unknown): Normali (typeof root.schemaVersion === "string" && root.schemaVersion) || undefined; - const frameworkRaw = generated.frameworkManifest; - const frameworkManifest = - frameworkRaw && typeof frameworkRaw === "object" && !Array.isArray(frameworkRaw) - ? (frameworkRaw as FrameworkManifest) - : undefined; + const frameworkManifest = normalizeFrameworkManifest(generated.frameworkManifest); // STABLE SURFACE ONLY (per live-state audit): the four externally consumed // paths. health.*, sidecar files, generated.fragility, and generated.coChange diff --git a/tests/unit/workspace.test.ts b/tests/unit/workspace.test.ts index 012bbd2..efd60ee 100644 --- a/tests/unit/workspace.test.ts +++ b/tests/unit/workspace.test.ts @@ -129,14 +129,14 @@ describe("normalizeWorkspace", () => { expect(ws.fileIndex).toEqual(["src/one.ts", "src/two.ts"]); }); - it("degrades an array-shaped frameworkManifest (the real spec's schema, a list of detected frameworks) to undefined instead of misreading it as the legacy {runtime,testRunner} record", () => { + it("normalizes an array-shaped frameworkManifest emitted by the current producer", () => { const raw = { generated: { frameworkManifest: [{ name: "node", version: "22.0.0", confidence: 0.9 }], }, }; const ws = normalizeWorkspace("/tmp/workspace.json", raw); - expect(ws.frameworkManifest).toBeUndefined(); + expect(ws.frameworkManifest).toEqual({ node: "22.0.0" }); }); it("tolerates an adjacency map for co-change patterns", () => {