Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
8f417d5
Initial plan
Copilot Aug 29, 2026
ca9a3bd
fix: scope README and Mermaid validation to changed files
Copilot Aug 29, 2026
f876351
fix: harden docs validation file-list handling
Copilot Aug 29, 2026
5c6fab0
fix: tighten mermaid target filtering and test cleanup
Copilot Aug 29, 2026
f58461a
fix: restrict mermaid validators to markdown inputs
Copilot Aug 29, 2026
83a02d9
test: cover frontmatter target-files guard clauses
Copilot Aug 29, 2026
73dad3d
Merge branch 'develop' into copilot/fix-mermaid-accessibility-readme-…
ashleyshaw Aug 29, 2026
6b629a7
Potential fix for pull request finding
ashleyshaw Aug 29, 2026
09d3919
fix: match vendor paths at repo root in colour-contrast validator
Copilot Aug 29, 2026
53b4921
Initial plan
Copilot Aug 29, 2026
5271f63
fix: scope README and Mermaid validation to changed files
Copilot Aug 29, 2026
ff2d80f
fix: harden docs validation file-list handling
Copilot Aug 29, 2026
e9c6380
fix: tighten mermaid target filtering and test cleanup
Copilot Aug 29, 2026
66bc288
fix: restrict mermaid validators to markdown inputs
Copilot Aug 29, 2026
ae3d0ec
test: cover frontmatter target-files guard clauses
Copilot Aug 29, 2026
c311d33
Potential fix for pull request finding
ashleyshaw Aug 29, 2026
19c8e2e
fix: match vendor paths at repo root in colour-contrast validator
Copilot Aug 29, 2026
34d144b
chore: add missing npm scripts for workflow validation
ashleyshaw Sep 1, 2026
64d2c40
fix: resolve npm ci compatibility and linting issues
ashleyshaw Sep 1, 2026
cae29d9
Merge branch 'develop' into copilot/fix-mermaid-accessibility-readme-…
ashleyshaw Sep 2, 2026
de45a02
Merge branch 'copilot/fix-mermaid-accessibility-readme-validation' of…
Sep 2, 2026
5d62122
Merge branch 'develop' into copilot/fix-mermaid-accessibility-readme-…
ashleyshaw Sep 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .eslint.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ module.exports = [
clearImmediate: "readonly",
clearInterval: "readonly",
clearTimeout: "readonly",
fetch: "readonly",
global: "readonly",
process: "readonly",
setImmediate: "readonly",
Expand Down Expand Up @@ -188,6 +189,7 @@ module.exports = [
clearImmediate: "readonly",
clearInterval: "readonly",
clearTimeout: "readonly",
fetch: "readonly",
global: "readonly",
process: "readonly",
require: "readonly",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ async function main() {
process.exit(0);
}

// Only run main if this module is executed directly, not when imported
// Only run main if executed directly, not when imported as a module
if (require.main === module) {
main().catch((error) => {
console.error("Fatal error:", error);
Expand Down
24 changes: 16 additions & 8 deletions .github/workflows/docs-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ jobs:
- name: Check for Mermaid diagrams in changed files
id: has_diagrams
if: steps.changed.outputs.has_changes == 'true'
env:
CHANGED_FILES: ${{ steps.changed.outputs.files }}
run: |
cat > "$RUNNER_TEMP/changed-md-files.txt" << 'CHANGED_FILES_EOF'
${{ steps.changed.outputs.files }}
CHANGED_FILES_EOF
printf '%s\n' "$CHANGED_FILES" > "$RUNNER_TEMP/changed-md-files.txt"
result=$(bash scripts/check-mermaid-diagrams.sh < "$RUNNER_TEMP/changed-md-files.txt")
echo "result=$result" >> "$GITHUB_OUTPUT"

Expand All @@ -77,22 +77,30 @@ jobs:
- name: Validate diagram syntax
id: syntax
if: steps.has_diagrams.outputs.result == 'true'
run: npm run validate:mermaid-syntax
env:
CHANGED_FILES: ${{ steps.changed.outputs.files }}
run: |
printf '%s\n' "$CHANGED_FILES" > "$RUNNER_TEMP/changed-md-files.txt"
node scripts/validation/validate-mermaid-syntax.js --changed-files-list="$RUNNER_TEMP/changed-md-files.txt"
continue-on-error: true

- name: Validate accessibility (accTitle / accDescr)
id: accessibility
if: steps.has_diagrams.outputs.result == 'true'
run: npm run validate:mermaid-accessibility
env:
CHANGED_FILES: ${{ steps.changed.outputs.files }}
run: |
printf '%s\n' "$CHANGED_FILES" > "$RUNNER_TEMP/changed-md-files.txt"
node scripts/validation/validate-mermaid-accessibility.js --changed-files-list="$RUNNER_TEMP/changed-md-files.txt"
continue-on-error: true

- name: Validate colour contrast (WCAG 2.2 AA)
id: contrast
if: steps.has_diagrams.outputs.result == 'true'
env:
CHANGED_FILES: ${{ steps.changed.outputs.files }}
run: |
cat > "$RUNNER_TEMP/changed-md-files.txt" << 'CHANGED_FILES_EOF'
${{ steps.changed.outputs.files }}
CHANGED_FILES_EOF
printf '%s\n' "$CHANGED_FILES" > "$RUNNER_TEMP/changed-md-files.txt"
node scripts/validation/validate-mermaid-colour-contrast.js --changed-files-list="$RUNNER_TEMP/changed-md-files.txt"
Comment thread
ashleyshaw marked this conversation as resolved.
continue-on-error: true

Expand Down
8 changes: 8 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@
"validate:plugins": "node scripts/validation/validate-plugins.js",
"validate:links": "node scripts/validation/validate-links.js",
"validate:agents": "node scripts/validation/validate-agent-frontmatter.js",
"validate:branch-name": "node scripts/validation/validate-branch-name.js",
"validate:issue-fields": "node scripts/validation/validate-issue-fields.cjs",
"validate:retired-doc-links": "node scripts/validation/validate-retired-doc-links.cjs",
"validate:workflow-npm-scripts": "node scripts/validation/validate-workflow-npm-scripts.cjs",
"validate:frontmatter": "node scripts/validation/validate-frontmatter.js",
"validate:frontmatter:changed": "node scripts/validation/validate-frontmatter.js",
"validate:workflow-npm-scripts": "node scripts/validation/validate-workflow-npm-scripts.cjs",
Expand Down
1 change: 1 addition & 0 deletions scripts/automation/distribute-unallocated-milestones.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env node
/* global fetch */

/**
* distribute-unallocated-milestones.js
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const fs = require("fs");
const os = require("os");
const path = require("path");

const { resolveCliTargetFiles } = require("../validate-frontmatter.js");

describe("resolveCliTargetFiles", () => {
let tmpRoot;

afterEach(() => {
if (tmpRoot) {
fs.rmSync(tmpRoot, { recursive: true, force: true });
tmpRoot = null;
}
});

it("resolves relative files, de-duplicates, and skips missing files", () => {
tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "frontmatter-targets-"));
const existing = path.join(tmpRoot, "README.md");
fs.writeFileSync(existing, "# test");

const result = resolveCliTargetFiles(
["README.md", existing, "missing.md"],
tmpRoot,
);

expect(result).toEqual([existing]);
});

it("returns empty array when fileArgs is not an array", () => {
expect(resolveCliTargetFiles(null, "/tmp")).toEqual([]);
});

it("returns empty array for an empty file list", () => {
expect(resolveCliTargetFiles([], "/tmp")).toEqual([]);
});
});
76 changes: 71 additions & 5 deletions scripts/validation/validate-frontmatter.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const CONFIG = {
"logs/**",
"**/package-lock.json",
],
targetFiles: [],
};

// Logging utility
Expand Down Expand Up @@ -375,6 +376,20 @@ class FileDiscovery {
}
}

function resolveCliTargetFiles(fileArgs, rootDir) {
if (!Array.isArray(fileArgs) || fileArgs.length === 0) {
return [];
}

return [
...new Set(
fileArgs.map((filePath) =>
path.isAbsolute(filePath) ? filePath : path.resolve(rootDir, filePath),
),
),
].filter((filePath) => fs.existsSync(filePath));
}

function runAltValidation() {
try {
const schemaContent = fs.readFileSync(CONFIG.schemaPath, "utf8");
Expand Down Expand Up @@ -406,18 +421,22 @@ async function validateFrontmatter() {
rootDir: CONFIG.rootDir,
patterns: CONFIG.patterns,
excludePatterns: CONFIG.excludePatterns,
targetFiles: CONFIG.targetFiles,
});

try {
// Initialize validator
const validator = new FrontmatterValidator(CONFIG.schemaPath, logger);

// Discover files
const files = FileDiscovery.findFiles(
CONFIG.patterns,
CONFIG.excludePatterns,
CONFIG.rootDir,
);
const files =
CONFIG.targetFiles.length > 0
? resolveCliTargetFiles(CONFIG.targetFiles, CONFIG.rootDir)
: FileDiscovery.findFiles(
CONFIG.patterns,
CONFIG.excludePatterns,
CONFIG.rootDir,
);

logger.info(`Found ${files.length} files to validate`);

Expand Down Expand Up @@ -485,6 +504,52 @@ Examples:
CONFIG.outputFile = path.resolve(args[outputIndex + 1]);
}

const knownOptionIndices = new Set();
const baseIndex = args.indexOf("--base");
const headIndex = args.indexOf("--head");
const baseSha = baseIndex !== -1 ? args[baseIndex + 1] : null;
const headSha = headIndex !== -1 ? args[headIndex + 1] : null;

for (let i = 0; i < args.length; i++) {
if (
args[i] === "--schema" ||
args[i] === "--root" ||
args[i] === "--output" ||
args[i] === "--base" ||
args[i] === "--head"
) {
knownOptionIndices.add(i);
knownOptionIndices.add(i + 1);
} else if (args[i] === "--help" || args[i] === "-h" || args[i] === "--alt") {
knownOptionIndices.add(i);
}
}

let rawTargets = args.filter((_, index) => !knownOptionIndices.has(index));

if (rawTargets.length === 0 && baseSha && headSha) {
const { execFileSync } = require("child_process");
rawTargets = execFileSync(
"git",
["diff", "--name-only", baseSha, headSha, "--", "*.md", "*.yml", "*.yaml"],
{ cwd: CONFIG.rootDir, encoding: "utf8", maxBuffer: 1024 * 1024 },
)
.trim()
.split("\n")
.filter(Boolean);
}

const unknownFlags = rawTargets.filter((arg) => arg.startsWith("--"));

if (unknownFlags.length > 0) {
console.error(
`Unknown option(s): ${unknownFlags.join(", ")}. Use --help for supported options.`,
);
process.exit(1);
}
Comment thread
Copilot marked this conversation as resolved.

CONFIG.targetFiles = rawTargets;

if (altMode) {
runAltValidation();
} else {
Expand All @@ -498,4 +563,5 @@ module.exports = {
FileDiscovery,
Logger,
CONFIG,
resolveCliTargetFiles,
};
69 changes: 50 additions & 19 deletions scripts/validation/validate-mermaid-accessibility.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,23 @@ import { globSync } from "glob";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.join(__dirname, "../../");

const MARKDOWN_FILES = globSync("**/*.{md,mdx}", {
cwd: ROOT,
ignore: [
"**/node_modules/**",
"**/.git/**",
"**/.claude/**",
"**/coverage/**",
"**/logs/**",
"**/.github/projects/**",
"**/plugin-provided/**",
"**/platform-managed/**",
"**/directory-installed/**",
"**/agentskills-main/**",
],
dot: true,
}).sort();
const getMarkdownFiles = () =>
globSync("**/*.{md,mdx}", {
cwd: ROOT,
ignore: [
"**/node_modules/**",
"**/.git/**",
"**/.claude/**",
"**/coverage/**",
"**/logs/**",
"**/.github/projects/**",
"**/plugin-provided/**",
"**/platform-managed/**",
"**/directory-installed/**",
"**/agentskills-main/**",
],
dot: true,
}).sort();

function extractMermaidDiagrams(content) {
const diagrams = [];
Expand Down Expand Up @@ -163,6 +164,36 @@ function validateAccessibility(content) {
}

async function main() {
const args = process.argv.slice(2);
const changedFilesArg = args.find((a) => a.startsWith("--changed-files="));
const changedFilesListArg = args.find((a) =>
a.startsWith("--changed-files-list="),
);
const isVendorPath = (filePath) =>
/(^|\/)(plugin-provided|platform-managed|directory-installed|agentskills-main)\//.test(
filePath,
);
const targetFiles = (
changedFilesListArg
? fs
.readFileSync(
changedFilesListArg.replace("--changed-files-list=", ""),
"utf8",
)
.split("\n")
.map((f) => f.trim())
.filter(Boolean)
: changedFilesArg
? changedFilesArg
.replace("--changed-files=", "")
.split(",")
.map((f) => f.trim())
.filter(Boolean)
: getMarkdownFiles()
)
.filter((f) => /\.mdx?$/i.test(f))
.filter((f) => !isVendorPath(f));

console.log("♿ Validating Mermaid diagram accessibility compliance...\n");

const report = {
Expand All @@ -176,8 +207,8 @@ async function main() {
"File,Diagram Number,Diagram Type,Has accTitle,Has accDescr,Missing Attributes,Compliance Status",
];

for (const file of MARKDOWN_FILES) {
const filePath = path.join(ROOT, file);
for (const file of targetFiles) {
const filePath = path.isAbsolute(file) ? file : path.join(ROOT, file);

if (!fs.existsSync(filePath)) {
console.log(`⚠️ File not found: ${file}`);
Expand Down Expand Up @@ -310,7 +341,7 @@ stability: stable

## Files Analyzed

${MARKDOWN_FILES.map((f) => `- ${f}`).join("\n")}
${targetFiles.map((f) => `- ${f}`).join("\n")}

## Compliance Criteria

Expand Down
2 changes: 1 addition & 1 deletion scripts/validation/validate-mermaid-colour-contrast.js
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ async function main() {
// getMarkdownFiles()'s glob `ignore` patterns entirely, so vendor paths
// must be filtered again here.
const isVendorPath = (filePath) =>
/\/(plugin-provided|platform-managed|directory-installed|agentskills-main)\//.test(
/(^|\/)(plugin-provided|platform-managed|directory-installed|agentskills-main)\//.test(
filePath,
);

Expand Down
Loading
Loading