HookStack
Back to catalogue
ValidationFileChanged

Validate OKF bundle on edit

Catch a malformed knowledge entry the instant you save it

Whenever a file under okf/ changes, runs OKF v0.1 strict validation (frontmatter, required type, broken links, recommended fields) and surfaces errors immediately as context — instead of waiting for a manual validate pass. Silent when the bundle is conformant or when okf.mjs is absent.

What does the Validate OKF bundle on edit hook do?

Validate OKF bundle on edit is a Claude Code FileChanged hook. It fires automatically at that lifecycle event — outside the model, so it can't be skipped or forgotten. Catch a malformed knowledge entry the instant you save it.

Because it is a deterministic Node.js script, it executes on every matching event without relying on the model to remember — the guarantee that makes agentic workflows safe to automate.

Use cases

  • Instant feedback while authoring OKF concepts
  • Guarantee the knowledge bundle stays conformant to OKF v0.1

Tags

#validation#okf#knowledge-management#quality

settings.json fragment

{
  "hooks": {
    "FileChanged": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "node $CLAUDE_PROJECT_DIR/.claude/hooks/okf-validate-on-change.mjs"
          }
        ]
      }
    ]
  }
}

Script · .claude/hooks/okf-validate-on-change.mjs

#!/usr/bin/env node
// @hookstack okf-validate-on-change
// Valide le bundle OKF (frontmatter notamment) dès qu'un fichier okf/**/*.md
// change (FileChanged) — feedback immédiat au lieu d'attendre node scripts/okf.mjs validate.
import { execSync } from "node:child_process";
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";

function defaultExec(projectDir) {
	return execSync("node scripts/okf.mjs validate --strict --json", {
		timeout: 15_000,
		cwd: projectDir,
		encoding: "utf8",
		stdio: "pipe",
	});
}

export function run(
	input,
	{ exec = defaultExec, projectDir = process.env.CLAUDE_PROJECT_DIR } = {},
) {
	const filePath = input.file_path ?? "";
	if (!/(^|\/)okf\/.*\.md$/.test(filePath) || !projectDir) return null;

	let out;
	try {
		out = exec(projectDir);
	} catch (e) {
		out = e?.stdout ?? "";
	}

	let report;
	try {
		report = JSON.parse(out);
	} catch {
		return null; // sortie inattendue → ne pas bloquer sur un souci d'outillage
	}
	if (report.passed) return null; // conforme → silencieux

	return {
		hookSpecificOutput: {
			hookEventName: "FileChanged",
			additionalContext:
				`okf/ bundle is INVALID against OKF v0.1 (strict mode):\n` +
				report.errors.map((e) => `✗ ERROR  ${e}`).join("\n") +
				(report.errors.length && report.warnings.length ? "\n" : "") +
				report.warnings.map((w) => `! warn   ${w}`).join("\n") +
				`\nFix the entries above (frontmatter, broken links) before continuing.`,
		},
	};
}

/* v8 ignore next 5 */
if (process.argv[1] === fileURLToPath(import.meta.url)) {
	const input = JSON.parse(readFileSync(0, "utf8"));
	const result = run(input);
	if (result) process.stdout.write(JSON.stringify(result));
}

Learn more

Related hooks