Skip to content

Commit 652f543

Browse files
committed
ci: add policy linter for copilot-instructions SSOT tags and sections (P3-22)
1 parent 64287c1 commit 652f543

File tree

2 files changed

+81
-0
lines changed

2 files changed

+81
-0
lines changed

.github/workflows/policy-lint.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: Policy Lint
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches: [ main ]
7+
8+
jobs:
9+
validate-policy:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- name: Checkout
13+
uses: actions/checkout@v4
14+
15+
- name: Setup Node.js
16+
uses: actions/setup-node@v4
17+
with:
18+
node-version: '20'
19+
20+
- name: Run policy validator
21+
run: |
22+
node scripts/validate-policy.js

scripts/validate-policy.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#!/usr/bin/env node
2+
/*
3+
Validates that `.github/copilot-instructions.md` contains required SSOT tags/sections.
4+
Checks:
5+
- Presence of XML-like tags: <CRITICAL_REQUIREMENT>, <WORKFLOW_ENFORCEMENT>, <NAMING_REQUIREMENTS>, <COMMIT_REQUIREMENTS>, <PROCESS_REQUIREMENTS>, <CODING_REQUIREMENTS>
6+
- Presence of anchor #quality-policy
7+
- Presence of key headings: Project Methodologies, Coding Standards
8+
Exit non-zero with a readable message if any check fails.
9+
*/
10+
11+
const fs = require('fs');
12+
const path = require('path');
13+
14+
function fail(msg) {
15+
console.error(msg);
16+
process.exit(1);
17+
}
18+
19+
function main() {
20+
const repoRoot = process.cwd();
21+
const filePath = path.join(repoRoot, '.github', 'copilot-instructions.md');
22+
if (!fs.existsSync(filePath)) fail(`File not found: ${filePath}`);
23+
const content = fs.readFileSync(filePath, 'utf8');
24+
25+
const requiredTags = [
26+
'<CRITICAL_REQUIREMENT',
27+
'<WORKFLOW_ENFORCEMENT',
28+
'<NAMING_REQUIREMENTS',
29+
'<COMMIT_REQUIREMENTS',
30+
'<PROCESS_REQUIREMENTS',
31+
'<CODING_REQUIREMENTS'
32+
];
33+
const missingTags = requiredTags.filter(t => !content.includes(t));
34+
if (missingTags.length) {
35+
fail('Missing required tags: ' + missingTags.join(', '));
36+
}
37+
38+
if (!content.includes('# Quality & Coverage Policy') && !content.includes('# Quality & Coverage Policy'.toLowerCase())) {
39+
// Also allow anchor reference if heading case changes
40+
if (!content.toLowerCase().includes('## quality & coverage policy') && !content.includes('#quality-policy')) {
41+
fail('Missing Quality & Coverage Policy section or anchor (#quality-policy).');
42+
}
43+
}
44+
45+
const requiredHeadings = [
46+
'## Project Methodologies',
47+
'## Coding Standards'
48+
];
49+
const missingHeadings = requiredHeadings.filter(h => !content.includes(h));
50+
if (missingHeadings.length) {
51+
fail('Missing required headings: ' + missingHeadings.join(', '));
52+
}
53+
54+
console.log('Policy validation passed.');
55+
}
56+
57+
if (require.main === module) {
58+
main();
59+
}

0 commit comments

Comments
 (0)