Track HiveGrid Cloud planning reference

This commit is contained in:
david committed 2026-06-26 20:48:01 +02:00
1 parent b3d5ea6764
commit e341c5938a
5784 files changed
+1104929 -3

No files matched your search

@@ -0,0 +1,13 @@
{
"name": "@toolctrl/plugins",
"version": "0.0.0",
"private": true,
"type": "module",
"sideEffects": false,
"exports": {
".": "./src/index.ts"
},
"scripts": {
"test": "cd ../.. && vitest run packages/plugins/src packages/plugins/test"
}
}
@@ -0,0 +1,10 @@
import { describe, expect, it } from "vitest";
describe("@toolctrl/plugins package entrypoint", () => {
it("exports the manifest schema API", async () => {
const module = await import("../src/index.ts");
expect(module.validateFeatureManifestShape).toEqual(expect.any(Function));
expect(module.loadFeatureManifests).toEqual(expect.any(Function));
});
});
@@ -0,0 +1,19 @@
export type {
FeatureManifest,
FeatureManifestClassification,
FeatureManifestComponent,
FeatureManifestDiagnostic,
FeatureManifestDiagnosticCode,
FeatureManifestStatus,
FeatureManifestValidationContext,
FeatureManifestValidationResult,
} from "./manifest-schema";
export { validateFeatureManifestShape } from "./manifest-schema";
export type {
FeatureManifestFile,
FeatureManifestLoaderDiagnostic,
FeatureManifestLoaderDiagnosticCode,
FeatureManifestLoaderOptions,
FeatureManifestLoaderResult,
} from "./manifest-loader";
export { loadFeatureManifests } from "./manifest-loader";
@@ -0,0 +1,131 @@
import { readdir, readFile } from "node:fs/promises";
import path from "node:path";
import type { FeatureManifest, FeatureManifestDiagnostic } from "./manifest-schema";
import { validateFeatureManifestShape } from "./manifest-schema";
export type FeatureManifestLoaderDiagnosticCode = FeatureManifestDiagnostic["code"] | "missing_directory" | "invalid_json" | "missing_dependency";
export interface FeatureManifestLoaderDiagnostic {
code: FeatureManifestLoaderDiagnosticCode;
manifestId: string | null;
path: string;
message: string;
}
export interface FeatureManifestLoaderOptions {
rootDir?: string;
manifestDir?: string;
schemaFileName?: string;
includeSchema?: boolean;
}
export interface FeatureManifestFile {
fileName: string;
filePath: string;
manifestId: string | null;
manifest: FeatureManifest | null;
diagnostics: FeatureManifestLoaderDiagnostic[];
}
export interface FeatureManifestLoaderResult {
manifests: FeatureManifest[];
manifestFiles: FeatureManifestFile[];
diagnostics: FeatureManifestLoaderDiagnostic[];
ok: boolean;
}
const DEFAULT_MANIFEST_DIR = path.join("docs", "feature-manifests");
const DEFAULT_SCHEMA_FILE_NAME = "schema.json";
function resolveManifestDirectory(options: FeatureManifestLoaderOptions) {
const rootDir = options.rootDir ?? process.cwd();
const manifestDir = options.manifestDir ?? DEFAULT_MANIFEST_DIR;
return path.isAbsolute(manifestDir) ? manifestDir : path.join(rootDir, manifestDir);
}
function toLoaderDiagnostic(diagnostic: FeatureManifestDiagnostic, filePath: string): FeatureManifestLoaderDiagnostic {
return {
...diagnostic,
path: `${filePath}#${diagnostic.path}`,
};
}
function manifestIdFromUnknownJson(input: unknown): string | null {
if (typeof input !== "object" || input === null || Array.isArray(input)) return null;
const id = (input as { id?: unknown }).id;
return typeof id === "string" && id.length > 0 ? id : null;
}
async function readManifestFile(fileName: string, filePath: string): Promise<FeatureManifestFile> {
let parsed: unknown;
try {
parsed = JSON.parse(await readFile(filePath, "utf8"));
} catch (error) {
const message = error instanceof Error ? error.message : "Unknown JSON parse failure.";
const diagnostic: FeatureManifestLoaderDiagnostic = {
code: "invalid_json",
manifestId: null,
path: filePath,
message: `Failed to parse ${fileName}: ${message}`,
};
return { fileName, filePath, manifestId: null, manifest: null, diagnostics: [diagnostic] };
}
const validation = validateFeatureManifestShape(parsed, { manifestId: manifestIdFromUnknownJson(parsed) ?? undefined, path: filePath });
const diagnostics = validation.diagnostics.map((diagnostic) => toLoaderDiagnostic(diagnostic, filePath));
return {
fileName,
filePath,
manifestId: validation.manifest?.id ?? manifestIdFromUnknownJson(parsed),
manifest: validation.manifest,
diagnostics,
};
}
function validateDependencies(manifestFiles: FeatureManifestFile[]): FeatureManifestLoaderDiagnostic[] {
const manifestIds = new Set(manifestFiles.flatMap((file) => (file.manifest ? [file.manifest.id] : [])));
return manifestFiles.flatMap((file) => {
if (!file.manifest) return [];
return file.manifest.dependencies
.filter((dependencyId) => !manifestIds.has(dependencyId))
.map((dependencyId) => ({
code: "missing_dependency" as const,
manifestId: file.manifest!.id,
path: `${file.filePath}#dependencies`,
message: `${file.manifest!.id} depends on missing feature manifest ${dependencyId}.`,
}));
});
}
export async function loadFeatureManifests(options: FeatureManifestLoaderOptions = {}): Promise<FeatureManifestLoaderResult> {
const manifestDirectory = resolveManifestDirectory(options);
const schemaFileName = options.schemaFileName ?? DEFAULT_SCHEMA_FILE_NAME;
let entries: string[];
try {
entries = await readdir(manifestDirectory);
} catch (error) {
const message = error instanceof Error ? error.message : "Unknown directory read failure.";
const diagnostic: FeatureManifestLoaderDiagnostic = {
code: "missing_directory",
manifestId: null,
path: manifestDirectory,
message: `Failed to read feature manifest directory: ${message}`,
};
return { manifests: [], manifestFiles: [], diagnostics: [diagnostic], ok: false };
}
const fileNames = entries.filter((entry) => entry.endsWith(".json") && (options.includeSchema === true || entry !== schemaFileName)).sort((a, b) => a.localeCompare(b));
const manifestFiles = await Promise.all(fileNames.map((fileName) => readManifestFile(fileName, path.join(manifestDirectory, fileName))));
const dependencyDiagnostics = validateDependencies(manifestFiles);
const diagnostics = [...manifestFiles.flatMap((file) => file.diagnostics), ...dependencyDiagnostics];
return {
manifests: manifestFiles.flatMap((file) => (file.manifest ? [file.manifest] : [])),
manifestFiles,
diagnostics,
ok: diagnostics.length === 0,
};
}
@@ -0,0 +1,218 @@
export type FeatureManifestClassification = "core" | "plugin-candidate" | "remove-candidate" | "mixed";
export type FeatureManifestStatus = "active" | "proposed" | "review-needed";
export type FeatureManifestDiagnosticCode = "invalid_type" | "missing_required" | "invalid_enum" | "invalid_format" | "invalid_item";
export interface FeatureManifestDiagnostic {
code: FeatureManifestDiagnosticCode;
manifestId: string | null;
path: string;
message: string;
}
export interface FeatureManifestValidationContext {
manifestId?: string;
path?: string;
}
export interface FeatureManifestValidationResult {
ok: boolean;
manifest: FeatureManifest | null;
diagnostics: FeatureManifestDiagnostic[];
}
export interface FeatureManifestComponent {
id: string;
title: string;
rationale: string;
codeAreas: string[];
notes?: string;
}
export interface FeatureManifest {
schemaVersion: "1.0";
id: string;
title: string;
classification: FeatureManifestClassification;
status: FeatureManifestStatus;
owner: string;
description: string;
rationale: string[];
codeAreas: string[];
dependencies: string[];
consumers: string[];
components?: FeatureManifestComponent[];
integration: {
ownership: "tenant-owned" | "admin-managed" | "hybrid" | "system-only";
surfaces: { adminRoutes: string[]; tenantRoutes: string[]; publicRoutes: string[]; apiRoutes: string[] };
configuration: { admin: string[]; tenant: string[]; environment: string[]; secrets: string[] };
workers: { required: boolean; queues: string[]; jobs: string[]; notes: string[] };
externalProviders: string[];
permissions: string[];
auditEvents: string[];
billingImpacts: string[];
uat: string[];
};
boundaries: { mustKeep: string[]; mayMoveToPlugin: string[]; mustNotTouch: string[] };
pruning: { phase: string; action: "keep" | "extract-plugin" | "remove" | "review"; notes: string[] };
validation: { source: string[]; lastReviewed: string };
}
const ID_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
const DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
const PERMISSION_TOKEN_PATTERN = /^[a-z][A-Za-z0-9]*(?:\.(?:[A-Za-z][A-Za-z0-9]*|\*))+$/;
const AUDIT_EVENT_PATTERN = /^[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)+$/;
const CLASSIFICATIONS = ["core", "plugin-candidate", "remove-candidate", "mixed"] as const;
const STATUSES = ["active", "proposed", "review-needed"] as const;
const OWNERSHIPS = ["tenant-owned", "admin-managed", "hybrid", "system-only"] as const;
const PRUNING_ACTIONS = ["keep", "extract-plugin", "remove", "review"] as const;
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function add(diagnostics: FeatureManifestDiagnostic[], code: FeatureManifestDiagnosticCode, manifestId: string | null, path: string, message: string) {
diagnostics.push({ code, manifestId, path, message });
}
function manifestIdFor(input: Record<string, unknown>, context: FeatureManifestValidationContext): string | null {
return context.manifestId ?? (typeof input.id === "string" && input.id.length > 0 ? input.id : null);
}
function requireString(source: Record<string, unknown>, key: string, path: string, diagnostics: FeatureManifestDiagnostic[], manifestId: string | null, options: { nonEmpty?: boolean; pattern?: RegExp; constValue?: string } = {}) {
const value = source[key];
if (value === undefined) return add(diagnostics, "missing_required", manifestId, path, `${path} is required.`);
if (typeof value !== "string") return add(diagnostics, "invalid_type", manifestId, path, `${path} must be a string.`);
if (options.nonEmpty && value.length === 0) add(diagnostics, "invalid_format", manifestId, path, `${path} must not be empty.`);
if (options.constValue !== undefined && value !== options.constValue) add(diagnostics, "invalid_enum", manifestId, path, `${path} must be ${options.constValue}.`);
if (options.pattern && !options.pattern.test(value)) add(diagnostics, "invalid_format", manifestId, path, `${path} has an invalid format.`);
}
function requireEnum<T extends readonly string[]>(source: Record<string, unknown>, key: string, values: T, path: string, diagnostics: FeatureManifestDiagnostic[], manifestId: string | null) {
const value = source[key];
if (value === undefined) return add(diagnostics, "missing_required", manifestId, path, `${path} is required.`);
if (typeof value !== "string") return add(diagnostics, "invalid_type", manifestId, path, `${path} must be a string.`);
if (!values.includes(value)) add(diagnostics, "invalid_enum", manifestId, path, `${path} must be one of: ${values.join(", ")}.`);
}
function requireStringArray(source: Record<string, unknown>, key: string, path: string, diagnostics: FeatureManifestDiagnostic[], manifestId: string | null, options: { itemPattern?: RegExp } = {}) {
const value = source[key];
if (value === undefined) return add(diagnostics, "missing_required", manifestId, path, `${path} is required.`);
if (!Array.isArray(value)) return add(diagnostics, "invalid_type", manifestId, path, `${path} must be an array.`);
value.forEach((item, index) => {
const itemPath = `${path}.${index}`;
if (typeof item !== "string") return add(diagnostics, "invalid_item", manifestId, itemPath, `${itemPath} must be a string.`);
if (options.itemPattern && !options.itemPattern.test(item)) add(diagnostics, "invalid_format", manifestId, itemPath, `${itemPath} has an invalid format.`);
});
}
function requireObject(source: Record<string, unknown>, key: string, path: string, diagnostics: FeatureManifestDiagnostic[], manifestId: string | null): Record<string, unknown> | null {
const value = source[key];
if (value === undefined) {
add(diagnostics, "missing_required", manifestId, path, `${path} is required.`);
return null;
}
if (!isRecord(value)) {
add(diagnostics, "invalid_type", manifestId, path, `${path} must be an object.`);
return null;
}
return value;
}
function requireBoolean(source: Record<string, unknown>, key: string, path: string, diagnostics: FeatureManifestDiagnostic[], manifestId: string | null) {
const value = source[key];
if (value === undefined) return add(diagnostics, "missing_required", manifestId, path, `${path} is required.`);
if (typeof value !== "boolean") add(diagnostics, "invalid_type", manifestId, path, `${path} must be a boolean.`);
}
function validateComponents(input: Record<string, unknown>, diagnostics: FeatureManifestDiagnostic[], manifestId: string | null) {
if (input.components === undefined) return;
if (!Array.isArray(input.components)) return add(diagnostics, "invalid_type", manifestId, "components", "components must be an array.");
input.components.forEach((component, index) => {
const path = `components.${index}`;
if (!isRecord(component)) return add(diagnostics, "invalid_item", manifestId, path, `${path} must be an object.`);
requireString(component, "id", `${path}.id`, diagnostics, manifestId, { pattern: ID_PATTERN });
requireString(component, "title", `${path}.title`, diagnostics, manifestId);
requireString(component, "rationale", `${path}.rationale`, diagnostics, manifestId);
requireStringArray(component, "codeAreas", `${path}.codeAreas`, diagnostics, manifestId);
if (component.notes !== undefined && typeof component.notes !== "string") add(diagnostics, "invalid_type", manifestId, `${path}.notes`, `${path}.notes must be a string.`);
});
}
function validateIntegration(input: Record<string, unknown>, diagnostics: FeatureManifestDiagnostic[], manifestId: string | null) {
const integration = requireObject(input, "integration", "integration", diagnostics, manifestId);
if (!integration) return;
requireEnum(integration, "ownership", OWNERSHIPS, "integration.ownership", diagnostics, manifestId);
const surfaces = requireObject(integration, "surfaces", "integration.surfaces", diagnostics, manifestId);
if (surfaces) {
requireStringArray(surfaces, "adminRoutes", "integration.surfaces.adminRoutes", diagnostics, manifestId);
requireStringArray(surfaces, "tenantRoutes", "integration.surfaces.tenantRoutes", diagnostics, manifestId);
requireStringArray(surfaces, "publicRoutes", "integration.surfaces.publicRoutes", diagnostics, manifestId);
requireStringArray(surfaces, "apiRoutes", "integration.surfaces.apiRoutes", diagnostics, manifestId);
}
const configuration = requireObject(integration, "configuration", "integration.configuration", diagnostics, manifestId);
if (configuration) {
requireStringArray(configuration, "admin", "integration.configuration.admin", diagnostics, manifestId);
requireStringArray(configuration, "tenant", "integration.configuration.tenant", diagnostics, manifestId);
requireStringArray(configuration, "environment", "integration.configuration.environment", diagnostics, manifestId);
requireStringArray(configuration, "secrets", "integration.configuration.secrets", diagnostics, manifestId);
}
const workers = requireObject(integration, "workers", "integration.workers", diagnostics, manifestId);
if (workers) {
requireBoolean(workers, "required", "integration.workers.required", diagnostics, manifestId);
requireStringArray(workers, "queues", "integration.workers.queues", diagnostics, manifestId);
requireStringArray(workers, "jobs", "integration.workers.jobs", diagnostics, manifestId);
requireStringArray(workers, "notes", "integration.workers.notes", diagnostics, manifestId);
}
requireStringArray(integration, "externalProviders", "integration.externalProviders", diagnostics, manifestId);
requireStringArray(integration, "permissions", "integration.permissions", diagnostics, manifestId, { itemPattern: PERMISSION_TOKEN_PATTERN });
requireStringArray(integration, "auditEvents", "integration.auditEvents", diagnostics, manifestId, { itemPattern: AUDIT_EVENT_PATTERN });
requireStringArray(integration, "billingImpacts", "integration.billingImpacts", diagnostics, manifestId);
requireStringArray(integration, "uat", "integration.uat", diagnostics, manifestId);
}
function isValidDate(value: string) {
if (!DATE_PATTERN.test(value)) return false;
const date = new Date(`${value}T00:00:00.000Z`);
return !Number.isNaN(date.getTime()) && date.toISOString().slice(0, 10) === value;
}
export function validateFeatureManifestShape(input: unknown, context: FeatureManifestValidationContext = {}): FeatureManifestValidationResult {
if (!isRecord(input)) {
const path = context.path ?? "$";
return { ok: false, manifest: null, diagnostics: [{ code: "invalid_type", manifestId: context.manifestId ?? null, path, message: `${path} must be an object.` }] };
}
const diagnostics: FeatureManifestDiagnostic[] = [];
const manifestId = manifestIdFor(input, context);
requireString(input, "schemaVersion", "schemaVersion", diagnostics, manifestId, { constValue: "1.0" });
requireString(input, "id", "id", diagnostics, manifestId, { pattern: ID_PATTERN });
requireString(input, "title", "title", diagnostics, manifestId, { nonEmpty: true });
requireEnum(input, "classification", CLASSIFICATIONS, "classification", diagnostics, manifestId);
requireEnum(input, "status", STATUSES, "status", diagnostics, manifestId);
requireString(input, "owner", "owner", diagnostics, manifestId);
requireString(input, "description", "description", diagnostics, manifestId);
requireStringArray(input, "rationale", "rationale", diagnostics, manifestId);
requireStringArray(input, "codeAreas", "codeAreas", diagnostics, manifestId);
requireStringArray(input, "dependencies", "dependencies", diagnostics, manifestId, { itemPattern: ID_PATTERN });
requireStringArray(input, "consumers", "consumers", diagnostics, manifestId);
validateComponents(input, diagnostics, manifestId);
validateIntegration(input, diagnostics, manifestId);
const boundaries = requireObject(input, "boundaries", "boundaries", diagnostics, manifestId);
if (boundaries) {
requireStringArray(boundaries, "mustKeep", "boundaries.mustKeep", diagnostics, manifestId);
requireStringArray(boundaries, "mayMoveToPlugin", "boundaries.mayMoveToPlugin", diagnostics, manifestId);
requireStringArray(boundaries, "mustNotTouch", "boundaries.mustNotTouch", diagnostics, manifestId);
}
const pruning = requireObject(input, "pruning", "pruning", diagnostics, manifestId);
if (pruning) {
requireString(pruning, "phase", "pruning.phase", diagnostics, manifestId);
requireEnum(pruning, "action", PRUNING_ACTIONS, "pruning.action", diagnostics, manifestId);
requireStringArray(pruning, "notes", "pruning.notes", diagnostics, manifestId);
}
const validation = requireObject(input, "validation", "validation", diagnostics, manifestId);
if (validation) {
requireStringArray(validation, "source", "validation.source", diagnostics, manifestId);
requireString(validation, "lastReviewed", "validation.lastReviewed", diagnostics, manifestId);
if (typeof validation.lastReviewed === "string" && !isValidDate(validation.lastReviewed)) add(diagnostics, "invalid_format", manifestId, "validation.lastReviewed", "validation.lastReviewed must be an ISO date in YYYY-MM-DD format.");
}
return { ok: diagnostics.length === 0, manifest: diagnostics.length === 0 ? (input as unknown as FeatureManifest) : null, diagnostics };
}
@@ -0,0 +1,148 @@
import { mkdtemp, mkdir, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
import pluginCandidates from "../../../docs/feature-manifests/plugin-candidates.json";
import { loadFeatureManifests } from "../src/manifest-loader";
function manifestWith(overrides: Record<string, unknown>) {
return structuredClone({ ...pluginCandidates, ...overrides });
}
async function makeFixtureDirectory() {
const rootDir = await mkdtemp(path.join(os.tmpdir(), "feature-manifests-"));
const manifestDir = path.join(rootDir, "docs", "feature-manifests");
await mkdir(manifestDir, { recursive: true });
return { rootDir, manifestDir };
}
async function writeManifest(manifestDir: string, fileName: string, overrides: Record<string, unknown> = {}) {
const manifest = manifestWith(overrides);
await writeFile(path.join(manifestDir, fileName), `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
return manifest;
}
describe("loadFeatureManifests", () => {
it("loads valid docs manifests in deterministic order and skips the schema by default", async () => {
const result = await loadFeatureManifests();
expect(result.ok).toBe(true);
expect(result.diagnostics).toEqual([]);
expect(result.manifests.map((manifest) => manifest.id)).toContain("plugin-candidates");
expect(result.manifestFiles.map((file) => file.fileName)).toEqual([...result.manifestFiles.map((file) => file.fileName)].sort((a, b) => a.localeCompare(b)));
expect(result.manifestFiles.some((file) => file.fileName === "schema.json")).toBe(false);
});
it("reports a missing manifest directory", async () => {
const rootDir = await mkdtemp(path.join(os.tmpdir(), "feature-manifests-missing-"));
const result = await loadFeatureManifests({ rootDir });
expect(result).toMatchObject({ ok: false, manifests: [], manifestFiles: [] });
expect(result.diagnostics[0]).toMatchObject({ code: "missing_directory", manifestId: null, path: path.join(rootDir, "docs", "feature-manifests") });
});
it("preserves sibling valid manifests when one file contains malformed JSON", async () => {
const { rootDir, manifestDir } = await makeFixtureDirectory();
await writeManifest(manifestDir, "plugin-candidates.json", { id: "plugin-candidates", dependencies: [] });
await writeFile(path.join(manifestDir, "broken.json"), "{ not json", "utf8");
const result = await loadFeatureManifests({ rootDir });
expect(result.ok).toBe(false);
expect(result.manifests.map((manifest) => manifest.id)).toEqual(["plugin-candidates"]);
expect(result.diagnostics).toContainEqual(expect.objectContaining({ code: "invalid_json", manifestId: null, path: path.join(manifestDir, "broken.json") }));
});
it("loads from an absolute manifest directory", async () => {
const { manifestDir } = await makeFixtureDirectory();
await writeManifest(manifestDir, "plugin-candidates.json", { id: "plugin-candidates", dependencies: [] });
const result = await loadFeatureManifests({ rootDir: "/ignored/root", manifestDir });
expect(result.ok).toBe(true);
expect(result.manifests.map((manifest) => manifest.id)).toEqual(["plugin-candidates"]);
expect(result.manifestFiles[0]).toMatchObject({
fileName: "plugin-candidates.json",
filePath: path.join(manifestDir, "plugin-candidates.json"),
manifestId: "plugin-candidates",
diagnostics: [],
});
});
it("can include schema-like json files when requested", async () => {
const { rootDir, manifestDir } = await makeFixtureDirectory();
await writeManifest(manifestDir, "schema.json", { id: "schema-file", dependencies: [] });
await writeManifest(manifestDir, "plugin-candidates.json", { id: "plugin-candidates", dependencies: [] });
const result = await loadFeatureManifests({ rootDir, includeSchema: true });
expect(result.ok).toBe(true);
expect(result.manifestFiles.map((file) => file.fileName)).toEqual(["plugin-candidates.json", "schema.json"]);
expect(result.manifests.map((manifest) => manifest.id)).toEqual(["plugin-candidates", "schema-file"]);
});
it("uses custom schema file names for the default skip rule", async () => {
const { rootDir, manifestDir } = await makeFixtureDirectory();
await writeManifest(manifestDir, "manifest.schema.json", { id: "custom-schema", dependencies: [] });
await writeManifest(manifestDir, "schema.json", { id: "ordinary-schema-name", dependencies: [] });
const result = await loadFeatureManifests({ rootDir, schemaFileName: "manifest.schema.json" });
expect(result.ok).toBe(true);
expect(result.manifestFiles.map((file) => file.fileName)).toEqual(["schema.json"]);
expect(result.manifests.map((manifest) => manifest.id)).toEqual(["ordinary-schema-name"]);
});
it("keeps manifest ids null for non-object json payloads", async () => {
const { rootDir, manifestDir } = await makeFixtureDirectory();
const filePath = path.join(manifestDir, "array.json");
await writeFile(filePath, "[]\n", "utf8");
const result = await loadFeatureManifests({ rootDir });
expect(result.ok).toBe(false);
expect(result.manifests).toEqual([]);
expect(result.manifestFiles).toEqual([
{
fileName: "array.json",
filePath,
manifestId: null,
manifest: null,
diagnostics: [{ code: "invalid_type", manifestId: null, path: `${filePath}#${filePath}`, message: `${filePath} must be an object.` }],
},
]);
});
it("reports invalid manifest shape diagnostics with source file metadata", async () => {
const { rootDir, manifestDir } = await makeFixtureDirectory();
await writeManifest(manifestDir, "invalid-shape.json", { id: "invalid-shape", classification: "optional", dependencies: [] });
const result = await loadFeatureManifests({ rootDir });
expect(result.ok).toBe(false);
expect(result.manifests).toEqual([]);
expect(result.diagnostics).toContainEqual({
code: "invalid_enum",
manifestId: "invalid-shape",
path: `${path.join(manifestDir, "invalid-shape.json")}#classification`,
message: "classification must be one of: core, plugin-candidate, remove-candidate, mixed.",
});
});
it("reports missing dependency references after loading valid manifests", async () => {
const { rootDir, manifestDir } = await makeFixtureDirectory();
await writeManifest(manifestDir, "plugin-candidates.json", { id: "plugin-candidates", dependencies: ["missing-feature"] });
const result = await loadFeatureManifests({ rootDir });
expect(result.ok).toBe(false);
expect(result.manifests.map((manifest) => manifest.id)).toEqual(["plugin-candidates"]);
expect(result.diagnostics).toContainEqual({
code: "missing_dependency",
manifestId: "plugin-candidates",
path: `${path.join(manifestDir, "plugin-candidates.json")}#dependencies`,
message: "plugin-candidates depends on missing feature manifest missing-feature.",
});
});
});
@@ -0,0 +1,218 @@
import { describe, expect, it } from "vitest";
import pluginCandidates from "../../../docs/feature-manifests/plugin-candidates.json";
import { validateFeatureManifestShape } from "../src/index";
function manifestWith(overrides: Record<string, unknown>) {
return structuredClone({ ...pluginCandidates, ...overrides });
}
describe("validateFeatureManifestShape", () => {
it("accepts the plugin candidates fixture", () => {
const result = validateFeatureManifestShape(pluginCandidates, { path: "docs/feature-manifests/plugin-candidates.json" });
expect(result.ok).toBe(true);
expect(result.manifest?.id).toBe("plugin-candidates");
expect(result.diagnostics).toEqual([]);
});
it("reports invalid classification enum values with manifest context", () => {
const result = validateFeatureManifestShape(manifestWith({ classification: "optional" }));
expect(result.ok).toBe(false);
expect(result.diagnostics).toContainEqual({
code: "invalid_enum",
manifestId: "plugin-candidates",
path: "classification",
message: "classification must be one of: core, plugin-candidate, remove-candidate, mixed.",
});
});
it("reports missing integration worker sections", () => {
const integration = structuredClone(pluginCandidates.integration) as Record<string, unknown>;
delete integration.workers;
const result = validateFeatureManifestShape(manifestWith({ integration }));
expect(result.ok).toBe(false);
expect(result.diagnostics).toContainEqual({
code: "missing_required",
manifestId: "plugin-candidates",
path: "integration.workers",
message: "integration.workers is required.",
});
});
it("reports non-array dependencies", () => {
const result = validateFeatureManifestShape(manifestWith({ dependencies: "core" }));
expect(result.ok).toBe(false);
expect(result.diagnostics).toContainEqual({
code: "invalid_type",
manifestId: "plugin-candidates",
path: "dependencies",
message: "dependencies must be an array.",
});
});
it("reports invalid component ids", () => {
const components = structuredClone(pluginCandidates.components);
components[0] = { ...components[0], id: "CRM" };
const result = validateFeatureManifestShape(manifestWith({ components }));
expect(result.ok).toBe(false);
expect(result.diagnostics).toContainEqual({
code: "invalid_format",
manifestId: "plugin-candidates",
path: "components.0.id",
message: "components.0.id has an invalid format.",
});
});
it("reports invalid optional component collections and notes", () => {
const components = [false, { ...pluginCandidates.components[0], notes: ["not prose"] }];
const result = validateFeatureManifestShape(manifestWith({ components }));
expect(result.ok).toBe(false);
expect(result.diagnostics).toContainEqual({
code: "invalid_item",
manifestId: "plugin-candidates",
path: "components.0",
message: "components.0 must be an object.",
});
expect(result.diagnostics).toContainEqual({
code: "invalid_type",
manifestId: "plugin-candidates",
path: "components.1.notes",
message: "components.1.notes must be a string.",
});
});
it("reports invalid nested integration section types", () => {
const integration = {
...pluginCandidates.integration,
surfaces: [],
configuration: "env",
workers: { ...pluginCandidates.integration.workers, required: "yes", queues: ["mail", 7] },
};
const result = validateFeatureManifestShape(manifestWith({ integration }));
expect(result.ok).toBe(false);
expect(result.diagnostics).toContainEqual({
code: "invalid_type",
manifestId: "plugin-candidates",
path: "integration.surfaces",
message: "integration.surfaces must be an object.",
});
expect(result.diagnostics).toContainEqual({
code: "invalid_type",
manifestId: "plugin-candidates",
path: "integration.configuration",
message: "integration.configuration must be an object.",
});
expect(result.diagnostics).toContainEqual({
code: "invalid_type",
manifestId: "plugin-candidates",
path: "integration.workers.required",
message: "integration.workers.required must be a boolean.",
});
expect(result.diagnostics).toContainEqual({
code: "invalid_item",
manifestId: "plugin-candidates",
path: "integration.workers.queues.1",
message: "integration.workers.queues.1 must be a string.",
});
});
it("reports invalid top-level constants, empty strings, and dependency tokens", () => {
const result = validateFeatureManifestShape(manifestWith({ schemaVersion: "2.0", title: "", dependencies: ["CRM"] }));
expect(result.ok).toBe(false);
expect(result.diagnostics).toContainEqual({
code: "invalid_enum",
manifestId: "plugin-candidates",
path: "schemaVersion",
message: "schemaVersion must be 1.0.",
});
expect(result.diagnostics).toContainEqual({
code: "invalid_format",
manifestId: "plugin-candidates",
path: "title",
message: "title must not be empty.",
});
expect(result.diagnostics).toContainEqual({
code: "invalid_format",
manifestId: "plugin-candidates",
path: "dependencies.0",
message: "dependencies.0 has an invalid format.",
});
});
it("uses context manifest ids when the input id is empty", () => {
const result = validateFeatureManifestShape(manifestWith({ id: "" }), { manifestId: "fixture-id" });
expect(result.ok).toBe(false);
expect(result.diagnostics).toContainEqual({
code: "invalid_format",
manifestId: "fixture-id",
path: "id",
message: "id has an invalid format.",
});
});
it("reports invalid lastReviewed dates", () => {
const validation = { ...pluginCandidates.validation, lastReviewed: "2026-99-05" };
const result = validateFeatureManifestShape(manifestWith({ validation }));
expect(result.ok).toBe(false);
expect(result.diagnostics).toContainEqual({
code: "invalid_format",
manifestId: "plugin-candidates",
path: "validation.lastReviewed",
message: "validation.lastReviewed must be an ISO date in YYYY-MM-DD format.",
});
});
it("rejects prose in permission and audit-event token arrays", () => {
const integration = {
...pluginCandidates.integration,
permissions: ["admin.email.* when settings are exposed"],
auditEvents: ["Provider key updated"],
};
const result = validateFeatureManifestShape(manifestWith({ integration }));
expect(result.ok).toBe(false);
expect(result.diagnostics).toContainEqual({
code: "invalid_format",
manifestId: "plugin-candidates",
path: "integration.permissions.0",
message: "integration.permissions.0 has an invalid format.",
});
expect(result.diagnostics).toContainEqual({
code: "invalid_format",
manifestId: "plugin-candidates",
path: "integration.auditEvents.0",
message: "integration.auditEvents.0 has an invalid format.",
});
});
it("accepts wildcard and camel-case permission tokens plus dot-separated audit events", () => {
const integration = {
...pluginCandidates.integration,
permissions: ["admin.emails.*", "app.settings.apiKeys.view"],
auditEvents: ["mail.provider.updated", "admin.ai.provider_key.updated"],
};
const result = validateFeatureManifestShape(manifestWith({ integration }));
expect(result.ok).toBe(true);
});
it("returns diagnostics instead of throwing for non-object input", () => {
const result = validateFeatureManifestShape(null, { manifestId: "bad", path: "fixture" });
expect(result).toEqual({
ok: false,
manifest: null,
diagnostics: [{ code: "invalid_type", manifestId: "bad", path: "fixture", message: "fixture must be an object." }],
});
});
});
@@ -0,0 +1,14 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"allowJs": false,
"allowImportingTsExtensions": false,
"lib": ["ES2022"],
"module": "NodeNext",
"moduleResolution": "NodeNext",
"noEmit": true,
"target": "ES2022",
"types": ["node"]
},
"include": ["src/**/*.ts"]
}