@tenantegroup/ai-rules-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INSTALLATION.md +52 -0
- package/README.md +57 -0
- package/USAGE.md +46 -0
- package/package.json +57 -0
- package/rules/cloudflare/api-services.md +80 -0
- package/rules/cloudflare/cicd-deployment.md +56 -0
- package/rules/cloudflare/database-orm.md +28 -0
- package/rules/cloudflare/edge-parity.md +24 -0
- package/rules/cloudflare/kv-usage.md +31 -0
- package/rules/cloudflare/logging-observability.md +66 -0
- package/rules/cloudflare/performance.md +44 -0
- package/rules/cloudflare/realtime-background.md +58 -0
- package/rules/cloudflare/security.md +162 -0
- package/rules/cloudflare/seeding.md +27 -0
- package/rules/cloudflare/workflows.md +593 -0
- package/rules/dotnet/api.md +26 -0
- package/rules/dotnet/architecture.md +27 -0
- package/rules/dotnet/cli.md +26 -0
- package/rules/dotnet/configuration.md +26 -0
- package/rules/dotnet/logging.md +25 -0
- package/rules/dotnet/maui.md +26 -0
- package/rules/dotnet/mvvm.md +26 -0
- package/rules/dotnet/packaging.md +24 -0
- package/rules/dotnet/project-structure.md +26 -0
- package/rules/dotnet/sqlite.md +29 -0
- package/rules/dotnet/testing.md +24 -0
- package/rules/flutter/api.md +29 -0
- package/rules/flutter/architecture.md +34 -0
- package/rules/flutter/auth.md +27 -0
- package/rules/flutter/configuration.md +24 -0
- package/rules/flutter/database.md +30 -0
- package/rules/flutter/logging.md +27 -0
- package/rules/flutter/navigation.md +28 -0
- package/rules/flutter/offline-sync.md +26 -0
- package/rules/flutter/platform.md +30 -0
- package/rules/flutter/project-structure.md +32 -0
- package/rules/flutter/riverpod.md +32 -0
- package/rules/flutter/testing.md +31 -0
- package/rules/nuxt/architecture-principles.md +31 -0
- package/rules/nuxt/authentication.md +35 -0
- package/rules/nuxt/code-quality.md +71 -0
- package/rules/nuxt/configuration.md +31 -0
- package/rules/nuxt/core-directives.md +12 -0
- package/rules/nuxt/project-initialization.md +53 -0
- package/rules/nuxt/project-structure.md +44 -0
- package/rules/nuxt/testing.md +48 -0
- package/src/index.js +757 -0
- package/templates/cloudflare/compile-context.js +43 -0
- package/templates/cloudflare/hooks/post-checkout +5 -0
- package/templates/cloudflare/hooks/pre-commit +14 -0
- package/templates/cloudflare/install-hooks.js +34 -0
- package/templates/cloudflare/validate-code.js +57 -0
- package/templates/dotnet/compile-context.js +43 -0
- package/templates/dotnet/hooks/post-checkout +5 -0
- package/templates/dotnet/hooks/pre-commit +14 -0
- package/templates/dotnet/install-hooks.js +34 -0
- package/templates/dotnet/validate-code.js +84 -0
- package/templates/flutter/compile-context.js +43 -0
- package/templates/flutter/hooks/post-checkout +5 -0
- package/templates/flutter/hooks/pre-commit +14 -0
- package/templates/flutter/install-hooks.js +34 -0
- package/templates/flutter/validate-code.js +64 -0
- package/templates/nuxt/compile-context.js +43 -0
- package/templates/nuxt/hooks/post-checkout +5 -0
- package/templates/nuxt/hooks/pre-commit +14 -0
- package/templates/nuxt/install-hooks.js +34 -0
- package/templates/nuxt/validate-code.js +57 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = path.dirname(__filename);
|
|
7
|
+
|
|
8
|
+
const rulesDir = path.join(__dirname, '../rules');
|
|
9
|
+
const rootDir = path.join(__dirname, '../../');
|
|
10
|
+
|
|
11
|
+
// Read all markdown files in .ai/rules/ and sort them
|
|
12
|
+
const ruleFiles = fs.readdirSync(rulesDir)
|
|
13
|
+
.filter(f => f.endsWith('.md') && f !== 'README.md')
|
|
14
|
+
.sort();
|
|
15
|
+
|
|
16
|
+
let compiledRules = "## MANDATORY ENGINEERING STANDARDS\n\n";
|
|
17
|
+
compiledRules += "The following rules are STRICT CONSTRAINTS. You must NEVER violate them.\n\n";
|
|
18
|
+
|
|
19
|
+
for (const file of ruleFiles) {
|
|
20
|
+
const content = fs.readFileSync(path.join(rulesDir, file), 'utf-8');
|
|
21
|
+
compiledRules += `### Rule Set: ${file}\n${content}\n\n`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Build the final CLAUDE.md / GEMINI.md content
|
|
25
|
+
const systemPrompt = `
|
|
26
|
+
# CRITICAL SYSTEM INSTRUCTIONS
|
|
27
|
+
You are operating within a Cloudflare Workers edge ecosystem.
|
|
28
|
+
Your outputs are strictly governed by the rules below.
|
|
29
|
+
|
|
30
|
+
${compiledRules}
|
|
31
|
+
|
|
32
|
+
## POST-GENERATION VALIDATION HOOK
|
|
33
|
+
Whenever you modify TypeScript or JavaScript backend route files, you MUST run the validation script before concluding your response:
|
|
34
|
+
\`node .ai/scripts/validate-code.js\`
|
|
35
|
+
If the script returns errors, you MUST fix the violations immediately.
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
// Write to AI specific configuration files
|
|
39
|
+
fs.writeFileSync(path.join(rootDir, 'CLAUDE.md'), systemPrompt);
|
|
40
|
+
fs.writeFileSync(path.join(rootDir, 'GEMINI.md'), systemPrompt);
|
|
41
|
+
fs.writeFileSync(path.join(rootDir, '.cursorrules'), systemPrompt);
|
|
42
|
+
|
|
43
|
+
console.log("✅ AI Context successfully compiled into CLAUDE.md, GEMINI.md, and .cursorrules");
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Pre-commit hook to enforce AI rules locally
|
|
3
|
+
|
|
4
|
+
echo "🔍 Running AI Standards Validation Hook..."
|
|
5
|
+
node .ai/scripts/validate-code.js
|
|
6
|
+
|
|
7
|
+
if [ $? -ne 0 ]; then
|
|
8
|
+
echo "❌ Commit rejected: Code violates mandatory Cloudflare engineering standards."
|
|
9
|
+
echo "Please fix the issues or ask your AI assistant to fix them."
|
|
10
|
+
exit 1
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
echo "✅ Standards validation passed."
|
|
14
|
+
exit 0
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = path.dirname(__filename);
|
|
7
|
+
|
|
8
|
+
const hooksDir = path.join(__dirname, '../hooks');
|
|
9
|
+
const gitHooksDir = path.join(__dirname, '../../.git/hooks');
|
|
10
|
+
|
|
11
|
+
// Check if .git/hooks exists
|
|
12
|
+
if (!fs.existsSync(gitHooksDir)) {
|
|
13
|
+
console.error("❌ .git/hooks directory not found. Please run this script from the root of a git repository.");
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Get all files in .ai/hooks
|
|
18
|
+
const hookFiles = fs.readdirSync(hooksDir);
|
|
19
|
+
|
|
20
|
+
for (const file of hookFiles) {
|
|
21
|
+
const source = path.join(hooksDir, file);
|
|
22
|
+
const dest = path.join(gitHooksDir, file);
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
fs.copyFileSync(source, dest);
|
|
26
|
+
// Ensure it's executable (matters more on Unix, but good practice)
|
|
27
|
+
fs.chmodSync(dest, '755');
|
|
28
|
+
console.log(`✅ Installed hook: ${file}`);
|
|
29
|
+
} catch (err) {
|
|
30
|
+
console.error(`❌ Failed to install hook ${file}:`, err.message);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log("🎉 Git hooks installed successfully!");
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
|
|
4
|
+
let hasErrors = false;
|
|
5
|
+
|
|
6
|
+
function logError(file, message) {
|
|
7
|
+
console.error(`[VIOLATION] ${file}: ${message}`);
|
|
8
|
+
hasErrors = true;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Get staged files. If no staged files, exit early to be safe on non-git paths or when debugging
|
|
12
|
+
let stagedFiles = [];
|
|
13
|
+
try {
|
|
14
|
+
const diffOut = execSync('git diff --cached --name-only').toString();
|
|
15
|
+
stagedFiles = diffOut.split('\n').filter(Boolean);
|
|
16
|
+
} catch (e) {
|
|
17
|
+
console.warn("⚠️ Not in a git repository or git command failed. Skipping strict validation.");
|
|
18
|
+
process.exit(0);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (stagedFiles.length === 0) {
|
|
22
|
+
console.log("✅ No staged files to validate.");
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
stagedFiles.forEach(file => {
|
|
27
|
+
if (!fs.existsSync(file)) return;
|
|
28
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
29
|
+
|
|
30
|
+
// Rule: Cloudflare Edge Parity (No Node.js core modules or process.env)
|
|
31
|
+
if (file.endsWith('.ts') || file.endsWith('.vue') || file.endsWith('.js')) {
|
|
32
|
+
// Simple regex check for fs, path, net imports
|
|
33
|
+
if (/import .* from ['"](fs|path|net|child_process|crypto|node:.*)['"]/.test(content)) {
|
|
34
|
+
// Check if it's not a build script or dev script
|
|
35
|
+
if (!file.includes('nuxt.config') && !file.includes('.ai/scripts')) {
|
|
36
|
+
logError(file, "Node.js core modules are strictly forbidden in Cloudflare edge runtime. Use Web Standards.");
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (content.includes('process.env') && !file.includes('nuxt.config') && !file.includes('.ai/scripts')) {
|
|
40
|
+
logError(file, "Do not use process.env. Access environment variables via useRuntimeConfig() or Nitro bindings context.");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Rule: Database & ORM (No direct SQL execution without Drizzle)
|
|
45
|
+
if (file.includes('server/api') || file.includes('server/services')) {
|
|
46
|
+
if (content.match(/\.query\(["'\`]\s*(SELECT|UPDATE|INSERT|DELETE)/i)) {
|
|
47
|
+
logError(file, "Raw SQL queries detected. All queries MUST be encapsulated within Drizzle ORM query builders.");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
if (hasErrors) {
|
|
53
|
+
console.error("❌ Validation failed.");
|
|
54
|
+
process.exit(1);
|
|
55
|
+
} else {
|
|
56
|
+
console.log("✅ Standards validation passed.");
|
|
57
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = path.dirname(__filename);
|
|
7
|
+
|
|
8
|
+
const rulesDir = path.join(__dirname, '../rules');
|
|
9
|
+
const rootDir = path.join(__dirname, '../../');
|
|
10
|
+
|
|
11
|
+
// Read all markdown files in .ai/rules/ and sort them
|
|
12
|
+
const ruleFiles = fs.readdirSync(rulesDir)
|
|
13
|
+
.filter(f => f.endsWith('.md') && f !== 'README.md')
|
|
14
|
+
.sort();
|
|
15
|
+
|
|
16
|
+
let compiledRules = "## MANDATORY ENGINEERING STANDARDS\n\n";
|
|
17
|
+
compiledRules += "The following rules are STRICT CONSTRAINTS. You must NEVER violate them.\n\n";
|
|
18
|
+
|
|
19
|
+
for (const file of ruleFiles) {
|
|
20
|
+
const content = fs.readFileSync(path.join(rulesDir, file), 'utf-8');
|
|
21
|
+
compiledRules += `### Rule Set: ${file}\n${content}\n\n`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Build the final CLAUDE.md / GEMINI.md content
|
|
25
|
+
const systemPrompt = `
|
|
26
|
+
# CRITICAL SYSTEM INSTRUCTIONS
|
|
27
|
+
You are operating within a .NET MAUI cross-platform client-side ecosystem.
|
|
28
|
+
Your outputs are strictly governed by the rules below.
|
|
29
|
+
|
|
30
|
+
${compiledRules}
|
|
31
|
+
|
|
32
|
+
## POST-GENERATION VALIDATION HOOK
|
|
33
|
+
Whenever you modify C# or XAML source files, you MUST run the validation script before concluding your response:
|
|
34
|
+
\`node .ai/scripts/validate-code.js\`
|
|
35
|
+
If the script returns errors, you MUST fix the violations immediately.
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
// Write to AI specific configuration files
|
|
39
|
+
fs.writeFileSync(path.join(rootDir, 'CLAUDE.md'), systemPrompt);
|
|
40
|
+
fs.writeFileSync(path.join(rootDir, 'GEMINI.md'), systemPrompt);
|
|
41
|
+
fs.writeFileSync(path.join(rootDir, '.cursorrules'), systemPrompt);
|
|
42
|
+
|
|
43
|
+
console.log("✅ AI Context successfully compiled into CLAUDE.md, GEMINI.md, and .cursorrules");
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Pre-commit hook to enforce AI rules locally
|
|
3
|
+
|
|
4
|
+
echo "🔍 Running AI Standards Validation Hook..."
|
|
5
|
+
node .ai/scripts/validate-code.js
|
|
6
|
+
|
|
7
|
+
if [ $? -ne 0 ]; then
|
|
8
|
+
echo "❌ Commit rejected: Code violates mandatory .NET engineering standards."
|
|
9
|
+
echo "Please fix the issues or ask your AI assistant to fix them."
|
|
10
|
+
exit 1
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
echo "✅ Standards validation passed."
|
|
14
|
+
exit 0
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = path.dirname(__filename);
|
|
7
|
+
|
|
8
|
+
const hooksDir = path.join(__dirname, '../hooks');
|
|
9
|
+
const gitHooksDir = path.join(__dirname, '../../.git/hooks');
|
|
10
|
+
|
|
11
|
+
// Check if .git/hooks exists
|
|
12
|
+
if (!fs.existsSync(gitHooksDir)) {
|
|
13
|
+
console.error("❌ .git/hooks directory not found. Please run this script from the root of a git repository.");
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Get all files in .ai/hooks
|
|
18
|
+
const hookFiles = fs.readdirSync(hooksDir);
|
|
19
|
+
|
|
20
|
+
for (const file of hookFiles) {
|
|
21
|
+
const source = path.join(hooksDir, file);
|
|
22
|
+
const dest = path.join(gitHooksDir, file);
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
fs.copyFileSync(source, dest);
|
|
26
|
+
// Ensure it's executable (matters more on Unix, but good practice)
|
|
27
|
+
fs.chmodSync(dest, '755');
|
|
28
|
+
console.log(`✅ Installed hook: ${file}`);
|
|
29
|
+
} catch (err) {
|
|
30
|
+
console.error(`❌ Failed to install hook ${file}:`, err.message);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log("🎉 Git hooks installed successfully!");
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
|
|
4
|
+
let hasErrors = false;
|
|
5
|
+
|
|
6
|
+
function logError(file, message) {
|
|
7
|
+
console.error(`[VIOLATION] ${file}: ${message}`);
|
|
8
|
+
hasErrors = true;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Get staged files. If no staged files, exit early to be safe on non-git paths or when debugging
|
|
12
|
+
let stagedFiles = [];
|
|
13
|
+
try {
|
|
14
|
+
const diffOut = execSync('git diff --cached --name-only').toString();
|
|
15
|
+
stagedFiles = diffOut.split('\n').filter(Boolean);
|
|
16
|
+
} catch (e) {
|
|
17
|
+
console.warn("⚠️ Not in a git repository or git command failed. Skipping strict validation.");
|
|
18
|
+
process.exit(0);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (stagedFiles.length === 0) {
|
|
22
|
+
console.log("✅ No staged files to validate.");
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
stagedFiles.forEach(file => {
|
|
27
|
+
if (!fs.existsSync(file)) return;
|
|
28
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
29
|
+
|
|
30
|
+
// Rule: Avoid synchronous blocking on Task objects (.Result or .Wait())
|
|
31
|
+
if (file.endsWith('.cs')) {
|
|
32
|
+
const lines = content.split('\n');
|
|
33
|
+
|
|
34
|
+
lines.forEach((line, index) => {
|
|
35
|
+
const cleanLine = line.trim();
|
|
36
|
+
// Skip commented lines
|
|
37
|
+
if (cleanLine.startsWith('//') || cleanLine.startsWith('/*')) return;
|
|
38
|
+
|
|
39
|
+
if (cleanLine.includes('.Result') && !cleanLine.includes('QueryResult') && !file.includes('test/')) {
|
|
40
|
+
logError(file, `Line ${index + 1}: Synchronous task blocking via '.Result' is forbidden. Use proper async/await syntax.`);
|
|
41
|
+
}
|
|
42
|
+
if (cleanLine.includes('.Wait(') && !cleanLine.includes('Task.Delay') && !file.includes('test/')) {
|
|
43
|
+
logError(file, `Line ${index + 1}: Synchronous task blocking via '.Wait()' is forbidden. Use proper async/await syntax.`);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Rule: Async methods returning Task/ValueTask must end with the "Async" suffix
|
|
48
|
+
// Let's use a regex to look for public/private async Task/ValueTask method signatures without Async suffix
|
|
49
|
+
// e.g. "public async Task MyMethod(" or "async Task<T> MyMethod("
|
|
50
|
+
// Exclude test files
|
|
51
|
+
if (!file.includes('test/') && !file.includes('Tests/')) {
|
|
52
|
+
const lines = content.split('\n');
|
|
53
|
+
lines.forEach((line, index) => {
|
|
54
|
+
const cleanLine = line.trim();
|
|
55
|
+
if (cleanLine.startsWith('//') || cleanLine.startsWith('/*')) return;
|
|
56
|
+
|
|
57
|
+
// Matches declaration: async Task<Option> MethodName( or async Task MethodName(
|
|
58
|
+
// Captures return type in group 1, method name in group 2
|
|
59
|
+
const match = cleanLine.match(/\basync\s+(Task|ValueTask|Task<[^>]+>|ValueTask<[^>]+>)\s+(\w+)\s*\(/);
|
|
60
|
+
if (match) {
|
|
61
|
+
const methodName = match[2];
|
|
62
|
+
if (!methodName.endsWith('Async') && methodName !== 'Main') {
|
|
63
|
+
logError(file, `Line ${index + 1}: Async method '${methodName}' must have the 'Async' suffix (e.g. '${methodName}Async').`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Rule: MVVM Separation of Concerns
|
|
70
|
+
// Views (.xaml.cs code-behind) should not instantiate services, perform DB calls, or run business logic
|
|
71
|
+
if (file.endsWith('.xaml.cs')) {
|
|
72
|
+
if (content.includes('new SqliteConnection') || content.includes('SQLiteConnection') || content.includes('HttpClient')) {
|
|
73
|
+
logError(file, "Code-behind Views (.xaml.cs) must never perform database calls or directly instantiate HttpClients. Enforce MVVM and handle this in ViewModels or Services.");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
if (hasErrors) {
|
|
80
|
+
console.error("❌ Validation failed.");
|
|
81
|
+
process.exit(1);
|
|
82
|
+
} else {
|
|
83
|
+
console.log("✅ .NET standards validation passed.");
|
|
84
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = path.dirname(__filename);
|
|
7
|
+
|
|
8
|
+
const rulesDir = path.join(__dirname, '../rules');
|
|
9
|
+
const rootDir = path.join(__dirname, '../../');
|
|
10
|
+
|
|
11
|
+
// Read all markdown files in .ai/rules/ and sort them
|
|
12
|
+
const ruleFiles = fs.readdirSync(rulesDir)
|
|
13
|
+
.filter(f => f.endsWith('.md') && f !== 'README.md')
|
|
14
|
+
.sort();
|
|
15
|
+
|
|
16
|
+
let compiledRules = "## MANDATORY ENGINEERING STANDARDS\n\n";
|
|
17
|
+
compiledRules += "The following rules are STRICT CONSTRAINTS. You must NEVER violate them.\n\n";
|
|
18
|
+
|
|
19
|
+
for (const file of ruleFiles) {
|
|
20
|
+
const content = fs.readFileSync(path.join(rulesDir, file), 'utf-8');
|
|
21
|
+
compiledRules += `### Rule Set: ${file}\n${content}\n\n`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Build the final CLAUDE.md / GEMINI.md content
|
|
25
|
+
const systemPrompt = `
|
|
26
|
+
# CRITICAL SYSTEM INSTRUCTIONS
|
|
27
|
+
You are operating within a Flutter and Dart cross-platform client-side ecosystem.
|
|
28
|
+
Your outputs are strictly governed by the rules below.
|
|
29
|
+
|
|
30
|
+
${compiledRules}
|
|
31
|
+
|
|
32
|
+
## POST-GENERATION VALIDATION HOOK
|
|
33
|
+
Whenever you modify Dart or Flutter source files, you MUST run the validation script before concluding your response:
|
|
34
|
+
\`node .ai/scripts/validate-code.js\`
|
|
35
|
+
If the script returns errors, you MUST fix the violations immediately.
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
// Write to AI specific configuration files
|
|
39
|
+
fs.writeFileSync(path.join(rootDir, 'CLAUDE.md'), systemPrompt);
|
|
40
|
+
fs.writeFileSync(path.join(rootDir, 'GEMINI.md'), systemPrompt);
|
|
41
|
+
fs.writeFileSync(path.join(rootDir, '.cursorrules'), systemPrompt);
|
|
42
|
+
|
|
43
|
+
console.log("✅ AI Context successfully compiled into CLAUDE.md, GEMINI.md, and .cursorrules");
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Pre-commit hook to enforce AI rules locally
|
|
3
|
+
|
|
4
|
+
echo "🔍 Running AI Standards Validation Hook..."
|
|
5
|
+
node .ai/scripts/validate-code.js
|
|
6
|
+
|
|
7
|
+
if [ $? -ne 0 ]; then
|
|
8
|
+
echo "❌ Commit rejected: Code violates mandatory Flutter engineering standards."
|
|
9
|
+
echo "Please fix the issues or ask your AI assistant to fix them."
|
|
10
|
+
exit 1
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
echo "✅ Standards validation passed."
|
|
14
|
+
exit 0
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = path.dirname(__filename);
|
|
7
|
+
|
|
8
|
+
const hooksDir = path.join(__dirname, '../hooks');
|
|
9
|
+
const gitHooksDir = path.join(__dirname, '../../.git/hooks');
|
|
10
|
+
|
|
11
|
+
// Check if .git/hooks exists
|
|
12
|
+
if (!fs.existsSync(gitHooksDir)) {
|
|
13
|
+
console.error("❌ .git/hooks directory not found. Please run this script from the root of a git repository.");
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Get all files in .ai/hooks
|
|
18
|
+
const hookFiles = fs.readdirSync(hooksDir);
|
|
19
|
+
|
|
20
|
+
for (const file of hookFiles) {
|
|
21
|
+
const source = path.join(hooksDir, file);
|
|
22
|
+
const dest = path.join(gitHooksDir, file);
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
fs.copyFileSync(source, dest);
|
|
26
|
+
// Ensure it's executable (matters more on Unix, but good practice)
|
|
27
|
+
fs.chmodSync(dest, '755');
|
|
28
|
+
console.log(`✅ Installed hook: ${file}`);
|
|
29
|
+
} catch (err) {
|
|
30
|
+
console.error(`❌ Failed to install hook ${file}:`, err.message);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log("🎉 Git hooks installed successfully!");
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
|
|
4
|
+
let hasErrors = false;
|
|
5
|
+
|
|
6
|
+
function logError(file, message) {
|
|
7
|
+
console.error(`[VIOLATION] ${file}: ${message}`);
|
|
8
|
+
hasErrors = true;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Get staged files. If no staged files, exit early to be safe on non-git paths or when debugging
|
|
12
|
+
let stagedFiles = [];
|
|
13
|
+
try {
|
|
14
|
+
const diffOut = execSync('git diff --cached --name-only').toString();
|
|
15
|
+
stagedFiles = diffOut.split('\n').filter(Boolean);
|
|
16
|
+
} catch (e) {
|
|
17
|
+
console.warn("⚠️ Not in a git repository or git command failed. Skipping strict validation.");
|
|
18
|
+
process.exit(0);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (stagedFiles.length === 0) {
|
|
22
|
+
console.log("✅ No staged files to validate.");
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
stagedFiles.forEach(file => {
|
|
27
|
+
if (!fs.existsSync(file)) return;
|
|
28
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
29
|
+
|
|
30
|
+
// Only scan Dart files
|
|
31
|
+
if (file.endsWith('.dart')) {
|
|
32
|
+
// Rule: Avoid print statements in production code
|
|
33
|
+
if (content.includes('print(') && !file.includes('test/') && !file.includes('.ai/')) {
|
|
34
|
+
// Check if it's not a commented line
|
|
35
|
+
const lines = content.split('\n');
|
|
36
|
+
lines.forEach((line, index) => {
|
|
37
|
+
if (line.includes('print(') && !line.trim().startsWith('//') && !line.trim().startsWith('/*')) {
|
|
38
|
+
logError(file, `Line ${index + 1}: Raw print() statements are forbidden in production. Use AppLogger or standard logging wrappers.`);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Rule: Prevent direct Drift database imports/queries in View files
|
|
44
|
+
if (file.includes('/views/') || file.includes('/screens/') || file.includes('/widgets/')) {
|
|
45
|
+
if (content.includes("import 'package:drift/drift.dart'") || content.includes(".select(") || content.includes(".into(")) {
|
|
46
|
+
logError(file, "Views/Widgets must never access the Drift database directly. Enforce MVVM/Riverpod layers and access data through Providers.");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Rule: Riverpod Providers global registration check
|
|
51
|
+
// Check for providers declared without keepAlive or autodispose (warn/rule depending on structure)
|
|
52
|
+
// e.g., enforcing clean provider syntax: Providers must be annotated with @riverpod
|
|
53
|
+
if (content.includes('final ') && content.includes('Provider(') && !content.includes('Provider.autoDispose') && !content.includes('@riverpod')) {
|
|
54
|
+
console.warn(`[WARNING] ${file}: Manual Provider declaration detected without autodispose. Consider using @riverpod generator for state lifecycle control.`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (hasErrors) {
|
|
60
|
+
console.error("❌ Validation failed.");
|
|
61
|
+
process.exit(1);
|
|
62
|
+
} else {
|
|
63
|
+
console.log("✅ Flutter standards validation passed.");
|
|
64
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = path.dirname(__filename);
|
|
7
|
+
|
|
8
|
+
const rulesDir = path.join(__dirname, '../rules');
|
|
9
|
+
const rootDir = path.join(__dirname, '../../');
|
|
10
|
+
|
|
11
|
+
// Read all markdown files in .ai/rules/ and sort them
|
|
12
|
+
const ruleFiles = fs.readdirSync(rulesDir)
|
|
13
|
+
.filter(f => f.endsWith('.md') && f !== 'README.md')
|
|
14
|
+
.sort();
|
|
15
|
+
|
|
16
|
+
let compiledRules = "## MANDATORY ENGINEERING STANDARDS\n\n";
|
|
17
|
+
compiledRules += "The following rules are STRICT CONSTRAINTS. You must NEVER violate them.\n\n";
|
|
18
|
+
|
|
19
|
+
for (const file of ruleFiles) {
|
|
20
|
+
const content = fs.readFileSync(path.join(rulesDir, file), 'utf-8');
|
|
21
|
+
compiledRules += `### Rule Set: ${file}\n${content}\n\n`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Build the final CLAUDE.md / GEMINI.md content
|
|
25
|
+
const systemPrompt = `
|
|
26
|
+
# CRITICAL SYSTEM INSTRUCTIONS
|
|
27
|
+
You are operating within a Nuxt 4 frontend ecosystem.
|
|
28
|
+
Your outputs are strictly governed by the rules below.
|
|
29
|
+
|
|
30
|
+
${compiledRules}
|
|
31
|
+
|
|
32
|
+
## POST-GENERATION VALIDATION HOOK
|
|
33
|
+
Whenever you modify Vue, TypeScript, or JavaScript files, you MUST run the validation script before concluding your response:
|
|
34
|
+
\`node .ai/scripts/validate-code.js\`
|
|
35
|
+
If the script returns errors, you MUST fix the violations immediately.
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
// Write to AI specific configuration files
|
|
39
|
+
fs.writeFileSync(path.join(rootDir, 'CLAUDE.md'), systemPrompt);
|
|
40
|
+
fs.writeFileSync(path.join(rootDir, 'GEMINI.md'), systemPrompt);
|
|
41
|
+
fs.writeFileSync(path.join(rootDir, '.cursorrules'), systemPrompt);
|
|
42
|
+
|
|
43
|
+
console.log("✅ AI Context successfully compiled into CLAUDE.md, GEMINI.md, and .cursorrules");
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Pre-commit hook to enforce AI rules locally
|
|
3
|
+
|
|
4
|
+
echo "🔍 Running AI Standards Validation Hook..."
|
|
5
|
+
node .ai/scripts/validate-code.js
|
|
6
|
+
|
|
7
|
+
if [ $? -ne 0 ]; then
|
|
8
|
+
echo "❌ Commit rejected: Code violates mandatory Nuxt engineering standards."
|
|
9
|
+
echo "Please fix the issues or ask your AI assistant to fix them."
|
|
10
|
+
exit 1
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
echo "✅ Standards validation passed."
|
|
14
|
+
exit 0
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = path.dirname(__filename);
|
|
7
|
+
|
|
8
|
+
const hooksDir = path.join(__dirname, '../hooks');
|
|
9
|
+
const gitHooksDir = path.join(__dirname, '../../.git/hooks');
|
|
10
|
+
|
|
11
|
+
// Check if .git/hooks exists
|
|
12
|
+
if (!fs.existsSync(gitHooksDir)) {
|
|
13
|
+
console.error("❌ .git/hooks directory not found. Please run this script from the root of a git repository.");
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Get all files in .ai/hooks
|
|
18
|
+
const hookFiles = fs.readdirSync(hooksDir);
|
|
19
|
+
|
|
20
|
+
for (const file of hookFiles) {
|
|
21
|
+
const source = path.join(hooksDir, file);
|
|
22
|
+
const dest = path.join(gitHooksDir, file);
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
fs.copyFileSync(source, dest);
|
|
26
|
+
// Ensure it's executable (matters more on Unix, but good practice)
|
|
27
|
+
fs.chmodSync(dest, '755');
|
|
28
|
+
console.log(`✅ Installed hook: ${file}`);
|
|
29
|
+
} catch (err) {
|
|
30
|
+
console.error(`❌ Failed to install hook ${file}:`, err.message);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log("🎉 Git hooks installed successfully!");
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
|
|
4
|
+
let hasErrors = false;
|
|
5
|
+
|
|
6
|
+
function logError(file, message) {
|
|
7
|
+
console.error(`[VIOLATION] ${file}: ${message}`);
|
|
8
|
+
hasErrors = true;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Get staged files. If no staged files, exit early to be safe on non-git paths or when debugging
|
|
12
|
+
let stagedFiles = [];
|
|
13
|
+
try {
|
|
14
|
+
const diffOut = execSync('git diff --cached --name-only').toString();
|
|
15
|
+
stagedFiles = diffOut.split('\n').filter(Boolean);
|
|
16
|
+
} catch (e) {
|
|
17
|
+
console.warn("⚠️ Not in a git repository or git command failed. Skipping strict validation.");
|
|
18
|
+
process.exit(0);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (stagedFiles.length === 0) {
|
|
22
|
+
console.log("✅ No staged files to validate.");
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
stagedFiles.forEach(file => {
|
|
27
|
+
if (!fs.existsSync(file)) return;
|
|
28
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
29
|
+
|
|
30
|
+
// Rule: Cloudflare Edge Parity (No Node.js core modules or process.env)
|
|
31
|
+
if (file.endsWith('.ts') || file.endsWith('.vue') || file.endsWith('.js')) {
|
|
32
|
+
// Simple regex check for fs, path, net imports
|
|
33
|
+
if (/import .* from ['"](fs|path|net|child_process|crypto|node:.*)['"]/.test(content)) {
|
|
34
|
+
// Check if it's not a build script or dev script
|
|
35
|
+
if (!file.includes('nuxt.config') && !file.includes('.ai/scripts')) {
|
|
36
|
+
logError(file, "Node.js core modules are strictly forbidden in Cloudflare edge runtime. Use Web Standards.");
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (content.includes('process.env') && !file.includes('nuxt.config') && !file.includes('.ai/scripts')) {
|
|
40
|
+
logError(file, "Do not use process.env. Access environment variables via useRuntimeConfig() or Nitro bindings context.");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Rule: Database & ORM (No direct SQL execution without Drizzle)
|
|
45
|
+
if (file.includes('server/api') || file.includes('server/services')) {
|
|
46
|
+
if (content.match(/\.query\(["'\`]\s*(SELECT|UPDATE|INSERT|DELETE)/i)) {
|
|
47
|
+
logError(file, "Raw SQL queries detected. All queries MUST be encapsulated within Drizzle ORM query builders.");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
if (hasErrors) {
|
|
53
|
+
console.error("❌ Validation failed.");
|
|
54
|
+
process.exit(1);
|
|
55
|
+
} else {
|
|
56
|
+
console.log("✅ Standards validation passed.");
|
|
57
|
+
}
|