You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

117 lines
3.5 KiB

const fs = require('fs');
const path = require('path');
const MAX_FILES = 500;
const MAX_PY = 16000;
const MAX_CSV = 8000;
function walk(dir, base, out) {
if (out.length >= MAX_FILES) return;
let entries;
try {
entries = fs.readdirSync(dir, { withFileTypes: true });
} catch {
return;
}
for (const ent of entries) {
if (out.length >= MAX_FILES) return;
if (ent.name === '.git') continue;
const full = path.join(dir, ent.name);
const rel = path.posix.join(base.replace(/\\/g, '/'), ent.name.replace(/\\/g, '/'));
if (ent.isDirectory()) {
walk(full, rel, out);
} else {
out.push(rel);
}
}
}
function readSnippet(repoRoot, rel, maxChars) {
const full = path.join(repoRoot, rel);
if (!fs.existsSync(full) || !fs.statSync(full).isFile()) return null;
const buf = fs.readFileSync(full);
const text = buf.toString('utf8').slice(0, maxChars);
return { path: rel, bytes: buf.length, truncated: buf.length > maxChars, snippet: text };
}
/**
* 扫描仓库中与成绩实训相关的文件(启发式路径 + 内容片段,供大模型引用)
*/
function scanStudentRepo(repoRoot) {
const files = [];
walk(repoRoot, '', files);
const norm = (r) => r.replace(/\\/g, '/').toLowerCase();
const byEnd = (suffix) => files.find((f) => norm(f).endsWith(suffix));
const byName = (name) => files.find((f) => path.basename(f).toLowerCase() === name.toLowerCase());
let py =
byEnd('score_analysis.py') ||
byName('score_analysis.py') ||
files.find((f) => /\.py$/i.test(f) && /score|analysis|grade/i.test(f));
let csv =
byEnd('scores.csv') ||
byName('scores.csv') ||
files.find((f) => /\.csv$/i.test(f) && /score|grade|成绩/i.test(f));
let png =
byEnd('score_chart.png') ||
byName('score_chart.png') ||
files.find((f) => /\.png$/i.test(f) && /score|chart/i.test(f));
const myshixunDir = files.map((f) => f.split('/')[0]).find((seg) => /myshixun/i.test(seg));
if (myshixunDir && !py) {
py = files.find((f) => f.startsWith(myshixunDir + '/') && /\.py$/i.test(f) && /score|analysis/i.test(f));
}
const pySnip = py ? readSnippet(repoRoot, py, MAX_PY) : null;
const csvSnip = csv ? readSnippet(repoRoot, csv, MAX_CSV) : null;
let pngInfo = { exists: false, path: png || null, bytes: 0 };
if (png && fs.existsSync(path.join(repoRoot, png))) {
pngInfo = { exists: true, path: png, bytes: fs.statSync(path.join(repoRoot, png)).size };
}
const snippet = pySnip?.snippet || '';
const flags = {
has_read_csv: /read_csv|pandas|pd\.read/i.test(snippet),
has_mean: /\bmean\s*\(/i.test(snippet),
has_matplotlib: /matplotlib|pyplot|plt\./i.test(snippet),
};
return {
repo_root: repoRoot,
file_count: files.length,
file_list_sample: files.slice(0, 100),
score_analysis_py: pySnip,
scores_csv: csvSnip,
score_chart_png: pngInfo,
flags,
};
}
/**
* 列出仓库内文件(相对路径 + 字节数),按路径排序
*/
function listRepoFiles(repoRoot, maxFiles = 500) {
const rels = [];
walk(repoRoot, '', rels);
rels.sort((a, b) => a.localeCompare(b, 'en'));
const out = [];
for (const rel of rels) {
if (out.length >= maxFiles) break;
const full = path.join(repoRoot, rel);
try {
const st = fs.statSync(full);
if (!st.isFile()) continue;
out.push({
path: rel.replace(/\\/g, '/'),
size: st.size,
});
} catch {
/* skip */
}
}
return out;
}
module.exports = { scanStudentRepo, listRepoFiles };