أنشئ مراجعًا لطلبات السحب في TypeScript يجمع بين فروقات Git، وتشخيصات TypeScript Compiler API، وقواعد AST المحلية، والمخرجات المنظمة من OpenAI. والنتيجة هي أداة CLI صغيرة تعمل محليًا أو ضمن CI، مع إبقاء إخفاقات المترجم منفصلة عن ملاحظات الذكاء الاصطناعي السياقية.
ما الذي ستبنيه
تكتسب TypeScript قيمتها لأنها تجعل العقود البرمجية مرئية قبل تشغيل الكود. وقد يضعف طلب السحب هذه العقود من دون ظهور خطأ فوري من المترجم: إذ يمكن لتعليق any جديد أن يلغي التحقق عند إحدى نقاط الحدود، كما قد يفرض التحويل عبر unknown قيمًا غير متوافقة على نوع موثوق، وقد يخفي تعطيل التشخيص عدم تطابق حقيقيًا. ليست هذه الأنماط أخطاءً دائمًا، لكنها تستحق مراجعة واعية.
يبني هذا الشرح العملي أداة type-guardian، وهي مراجع سطر أوامر لفرع Git الحالي. وتعتمد نهجًا متعدد الطبقات:
- يحدد فرق Git نطاق طلب السحب.
- تتعرف قواعد TypeScript AST على أنماط سياسات دقيقة وحتمية.
- يجمع TypeScript Compiler API تشخيصات ما قبل الإصدار من
tsconfig.json. - توفر OpenAI ملاحظات مراجعة سياقية ضمن JSON منظم ومتحقق منه.
يبقى المترجم هو المرجع الحاسم لأخطاء TypeScript. وتبقى القواعد المحلية هي المرجع الحاسم للسياسات مثل الإبلاغ عن @ts-ignore. ويفيد النموذج في شرح نقطة حدود قد تكون غير آمنة أو رصد سياق لا تستطيع قاعدة نحوية ضيقة إثباته. لكن لا ينبغي أن يغير الكود بصمت، أو يتجاوز المترجم، أو يصبح بوابة الدمج الوحيدة.
يكتسب هذا التقسيم أهمية خاصة لفرق الهندسة في دول الخليج والشرق الأوسط التي توسّع تسليم البرمجيات المدعوم بالذكاء الاصطناعي بالتوازي مع متطلبات الحوكمة. ويمكن للمؤسسات المساهمة في مبادرات مثل رؤية السعودية ٢٠٣٠ أو الاستراتيجية الوطنية للذكاء الاصطناعي في الإمارات تطبيق النمط نفسه: فرض ضوابط هندسية حتمية محليًا، ثم تفعيل المراجعة السياقية الخارجية فقط بعد تحديد المواد المصدرية المسموح لها بمغادرة بيئة التطوير.
المتطلبات المسبقة وإعداد المشروع
تحتاج إلى مستودع TypeScript يحتوي على Git وملف tsconfig.json، بالإضافة إلى مفتاح OpenAI API إذا كنت تنوي تشغيل مرحلة الذكاء الاصطناعي. يستخدم الكود وحدات ECMAScript والاتجاه الحالي في OpenAI JavaScript SDK: وهو Responses API. ويعد TypeScript Compiler API أساسًا مناسبًا لتحليل الشيفرة المصدرية والتشخيصات، كما يُستخدم في أعمال تقنية منشورة لتحليل ملفات تعريف TypeScript ونمذجة معلومات الأنواع.
mkdir type-guardian
cd type-guardian
npm init -y
npm install openai dotenv zod
npm install --save-dev typescript tsx vitest @types/node
npm pkg set type=module
npm pkg set scripts.build="tsc -p tsconfig.json"
npm pkg set scripts.review="tsx src/index.ts --base origin/main"
npm pkg set scripts.test="vitest run"
mkdir src testأنشئ .env محليًا، ولا تقم بإضافته إلى المستودع. ضمن CI، مرر المفتاح عبر آلية الأسرار الخاصة بمنصة CI. فقد يحتوي الفرق على بيانات اعتماد أو معرّفات عملاء أو بيانات مولدة أو تفاصيل تنفيذ داخلية؛ لذلك يقيّد هذا الشرح عمدًا المواد المضمنة في الطلب الخارجي.
OPENAI_API_KEY=your-api-key
OPENAI_MODEL=gpt-5.6
TYPE_GUARDIAN_MAX_DIFF_CHARS=24000
TYPE_GUARDIAN_MAX_FILES=30
TYPE_GUARDIAN_FAIL_ON=highnode_modules/
dist/
.env
.env.*
coverage/اسم النموذج قابل للضبط لأن مدى توفره وموافقات المؤسسة تختلف. راجع إرشادات نماذج OpenAI الحالية قبل اختيار نموذج للإنتاج. استخدم --no-ai عندما تريد مراجعة محلية بالكامل تعتمد على المترجم والسياسات.
الخطوة ١: تحديد إعدادات مترجم صارمة وأنواع مشتركة
أنشئ tsconfig.json. الإعدادات الصارمة مقصودة: فالأداة التي تبلغ عن افتراضات غير آمنة يجب أن توضح بدورها القيم الاختيارية والأخطاء غير المعروفة.
{
"compilerOptions": {
"target": "ES2023",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"rootDir": "./src",
"outDir": "./dist",
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"useUnknownInCatchVariables": true,
"skipLibCheck": true
},
"include": ["src/**/*.ts"]
}أنشئ الآن src/types.ts. تمثل هذه الأنواع العقد المشترك بين التحليل المحلي وتحليل الذكاء الاصطناعي ومخرجات الطرفية وتقارير CI المستقبلية.
export type Severity = "low" | "medium" | "high" | "critical";
export type FindingCategory =
| "explicit-any"
| "unsafe-type-assertion"
| "typescript-suppression"
| "compiler-error"
| "ai-review";
export interface SourceLocation {
file: string;
line: number;
column: number;
}
export interface Finding {
id: string;
severity: Severity;
category: FindingCategory;
title: string;
explanation: string;
recommendation: string;
evidence: string;
location: SourceLocation;
confidence: number;
}
export interface ChangedFile {
path: string;
patch: string;
}
export interface ReviewOptions {
baseRef: string;
maxDiffChars: number;
maxFiles: number;
includeAiReview: boolean;
}
export interface ReviewReport {
generatedAt: string;
baseRef: string;
changedFiles: number;
compilerDiagnostics: number;
aiReviewIncluded: boolean;
findings: Finding[];
}تستخدم المواقع فهرسة تبدأ من واحد، لأنها الصيغة التي يراها المطورون في الطرفيات وواجهات استضافة الكود. يستخدم Compiler API مواضع يجب تحويلها عند حدود التكامل. والثقة رقم من صفر إلى واحد: يمكن إسناد ثقة مرتفعة إلى المطابقات النحوية الحتمية، بينما تبقى نتائج الذكاء الاصطناعي أدلةً على المراجع تقييمها.
الخطوة ٢: قراءة الفرق وتشغيل الفحوصات الحتمية
أنشئ src/analyze.ts. يحصل هذا الملف على ملفات TypeScript المتغيرة من مقارنة قاعدة الدمج، ويمر على مصدر شجرة العمل الحالية باستخدام محلل TypeScript، ويستخرج تشخيصات المترجم من إعدادات المستودع. ويُعد النطاق ثلاثي النقاط، base...HEAD، مناسبًا لمقارنة طلب السحب الشائعة مقابل قاعدة الدمج.
import { execFileSync } from "node:child_process";
import { existsSync, readFileSync } from "node:fs";
import path from "node:path";
import ts from "typescript";
import type {
ChangedFile,
Finding,
FindingCategory,
ReviewOptions,
Severity,
} from "./types.js";function runGit(args: string[]): string {
try {
return execFileSync("git", args, {
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"],
});
} catch (error: unknown) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`Git command failed: git ${args.join(" ")}: ${message}`);
}
}function finding(
category: FindingCategory,
severity: Severity,
file: string,
line: number,
title: string,
explanation: string,
recommendation: string,
evidence: string,
confidence: number,
): Finding {
return {
id: `${category}:${file}:${line}:${title}`,
category,
severity,
title,
explanation,
recommendation,
evidence: evidence.trim().slice(0, 500),
location: { file, line, column: 1 },
confidence,
};
}export function getChangedFiles(options: ReviewOptions): ChangedFile[] {
const paths = runGit([
"diff", "--name-only", "--diff-filter=ACMR",
`${options.baseRef}...HEAD`, "--", "*.ts", "*.tsx",
])
.split(/r?n/)
.map((value) => value.trim())
.filter(Boolean)
.slice(0, options.maxFiles);return paths.map((file) => ({
path: file,
patch: runGit(["diff", "--unified=3", `${options.baseRef}...HEAD`, "--", file]),
}));
}export function findLocalPolicyViolations(files: ChangedFile[]): Finding[] {
const results: Finding[] = [];for (const file of files) {
if (!existsSync(file.path)) continue;
const text = readFileSync(file.path, "utf8");
const lines = text.split(/r?n/);
const source = ts.createSourceFile(file.path, text, ts.ScriptTarget.Latest, true);const visit = (node: ts.Node): void => {
const position = source.getLineAndCharacterOfPosition(node.getStart(source));
const line = position.line + 1;
const evidence = lines[position.line] ?? "";if (node.kind === ts.SyntaxKind.AnyKeyword) {
results.push(finding(
"explicit-any", "medium", file.path, line,
"Explicit any weakens a type boundary",
"The any type disables static checking for values flowing through this declaration.",
"Use unknown with runtime validation, or define the smallest accurate type.",
evidence, 0.95,
));
}if (ts.isAsExpression(node) && ts.isAsExpression(node.expression)
&& node.expression.type.kind === ts.SyntaxKind.UnknownKeyword) {
...تابع القراءة
سجل دخولك مجاناً لقراءة المقال كاملاً والوصول إلى أدوات الذكاء الاصطناعي.
تسجيل الدخول / إنشاء حساب