Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
32 changes: 32 additions & 0 deletions tools/alignment/detect-clause-drift.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
import { findClauseReferences } from './detect-clause-drift';
Comment thread
AceHack marked this conversation as resolved.
Outdated
import * as fs from 'fs';
import * as path from 'path';

describe('findClauseReferences', () => {
const testDir = './test-dir';

beforeEach(() => {
// Create test directory
fs.mkdirSync(testDir, { recursive: true });

// Create dummy files
fs.writeFileSync(path.join(testDir, 'file1.md'), 'This file references HC-1 and SD-2.');
fs.writeFileSync(path.join(testDir, 'file2.ts'), 'This file references DIR-3.');
fs.writeFileSync(path.join(testDir, 'file3.txt'), 'This file has no references.');
});

afterEach(() => {
// Clean up test directory
fs.rmSync(testDir, { recursive: true, force: true });
});
Comment thread
AceHack marked this conversation as resolved.
Outdated

it('should find all references to alignment clauses in the specified directory', async () => {
const references = await findClauseReferences(testDir);

expect(references.size).toBe(3);
expect(references.get('HC-1')).toEqual([path.join(testDir, 'file1.md')]);
expect(references.get('SD-2')).toEqual([path.join(testDir, 'file1.md')]);
expect(references.get('DIR-3')).toEqual([path.join(testDir, 'file2.ts')]);
});
});
119 changes: 119 additions & 0 deletions tools/alignment/detect-clause-drift.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import fs from 'fs';
import path from 'path';
Comment thread
AceHack marked this conversation as resolved.
Outdated
Comment thread
AceHack marked this conversation as resolved.
Outdated

Comment thread
AceHack marked this conversation as resolved.
Outdated
const CLAUSE_REGEX = /(HC-[0-9]+|SD-[0-9]+|DIR-[0-9]+)/g;
Comment thread
AceHack marked this conversation as resolved.
Outdated
Comment thread
AceHack marked this conversation as resolved.
Outdated
const IGNORE_DIRS = ['node_modules', '.git', '.vscode', '.idea', 'dist', 'build'];
const IGNORE_EXTS = ['.png', '.jpg', '.jpeg', '.gif', '.svg', '.ico', '.pdf', '.zip', '.gz', '.tar', '.DS_Store'];
Comment thread
AceHack marked this conversation as resolved.
Outdated
Comment thread
AceHack marked this conversation as resolved.
Outdated
Comment thread
AceHack marked this conversation as resolved.
Outdated

interface Match {
file: string;
line: number;
clause: string;
text: string;
}

function searchInFile(filePath: string): Match[] {
const matches: Match[] = [];
if (IGNORE_EXTS.some(ext => filePath.endsWith(ext))) {
return matches;
}

try {
const content = fs.readFileSync(filePath, 'utf-8');
const lines = content.split('\n');

for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line === undefined) continue;
let match;
while ((match = CLAUSE_REGEX.exec(line)) !== null) {
matches.push({
file: filePath,
line: i + 1,
clause: match[0],
text: line.trim(),
});
}
}
} catch (error) {
// Ignore errors from reading binary files etc.
}

return matches;
}

function searchInDirectory(dirPath: string): Match[] {
let allMatches: Match[] = [];
const entries = fs.readdirSync(dirPath, { withFileTypes: true });

for (const entry of entries) {
const fullPath = path.join(dirPath, entry.name);
if (IGNORE_DIRS.includes(entry.name)) {
continue;
}

if (entry.isDirectory()) {
allMatches = allMatches.concat(searchInDirectory(fullPath));
} else if (entry.isFile()) {
allMatches = allMatches.concat(searchInFile(fullPath));
}
}

return allMatches;
}

export async function findClauseReferences(dirPath: string): Promise<Map<string, string[]>> {
const allMatches = searchInDirectory(dirPath);
Comment thread
AceHack marked this conversation as resolved.
Outdated
const references = new Map<string, string[]>();

for (const match of allMatches) {
if (!references.has(match.clause)) {
references.set(match.clause, []);
}
const files = references.get(match.clause);
if (files && !files.includes(match.file)) {
files.push(match.file);
}
}

return references;
}

Comment thread
AceHack marked this conversation as resolved.
Outdated
function main() {
const searchDir = process.cwd();
console.log(`Searching for alignment clause references in ${searchDir}...
Comment thread
AceHack marked this conversation as resolved.
Outdated
`);
Comment thread
AceHack marked this conversation as resolved.
Outdated

Comment thread
AceHack marked this conversation as resolved.
Outdated
const allMatches = searchInDirectory(searchDir);

const targetClause = process.argv[2];

Comment thread
AceHack marked this conversation as resolved.
Outdated
const filteredMatches = targetClause
? allMatches.filter(m => m.clause.toUpperCase() === targetClause.toUpperCase())
: allMatches;

if (filteredMatches.length === 0) {
console.log('No alignment clause references found.');
return;
}

const groupedByClause: { [key: string]: Match[] } = {};
for (const match of filteredMatches) {
(groupedByClause[match.clause] ??= []).push(match);
}

for (const clause in groupedByClause) {
const group = groupedByClause[clause];
if (group === undefined) continue;
console.log(`
--- Found ${group.length} references to ${clause} ---
`);
for (const match of group) {
console.log(`${match.file}:${match.line} - ${match.text}`);
}
}
}

if (require.main === module) {
main();
}
Comment thread
AceHack marked this conversation as resolved.
Outdated
Loading