@zhouchangui/math-ati 0.1.3 → 0.1.4
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/AGENTS.md +1 -0
- package/bin/math-ati.js +136 -5
- package/dist/assets/{index--Um9OfFu.css → index-DOg8CQsE.css} +1 -1
- package/dist/assets/index-DyfeTKmg.js +22 -0
- package/dist/index.html +2 -2
- package/package.json +7 -4
- package/prompts/grading.system.md +3 -1
- package/prompts/knowledge-structure.system.md +75 -0
- package/prompts/knowledge-summarize.system.md +13 -1
- package/prompts/pdf-grading.system.md +4 -1
- package/prompts/pdf-recheck.system.md +2 -0
- package/prompts/practice-answers.system.md +154 -0
- package/prompts/practice-coverage-repair.system.md +112 -0
- package/prompts/practice-generate.system.md +45 -5
- package/prompts/practice-rules.md +61 -0
- package/server/fileStore.js +9 -2
- package/server/index.js +48 -0
- package/server/knowledgeExtractor.js +218 -59
- package/server/knowledgeFeedback.js +69 -0
- package/server/practiceGenerator.js +76 -82
- package/server/promptStore.js +14 -0
- package/dist/assets/index-CS-PgjYi.js +0 -22
|
@@ -3,7 +3,7 @@ import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { chapterDataPaths, isActionableMistake, readChapterMistakes, readJson } from './fileStore.js';
|
|
5
5
|
import { flattenKnowledgePoints } from './knowledgeBase.js';
|
|
6
|
-
import { promptPayload, readPrompt } from './promptStore.js';
|
|
6
|
+
import { promptPayload, readPrompt, readRules } from './promptStore.js';
|
|
7
7
|
import { ABILITY_CATALOG } from './abilityService.js';
|
|
8
8
|
import { verifyAndRepairSvgFigures } from './svgFigureVerifier.js';
|
|
9
9
|
|
|
@@ -241,23 +241,8 @@ function compactPreviousStem(stem) {
|
|
|
241
241
|
}
|
|
242
242
|
|
|
243
243
|
function contextForAnswerMetadata(baseContext, questions) {
|
|
244
|
-
const usedIds = new Set();
|
|
245
|
-
for (const question of questions || []) {
|
|
246
|
-
for (const pointId of question.knowledgePointIds || []) usedIds.add(pointId);
|
|
247
|
-
}
|
|
248
244
|
return {
|
|
249
245
|
chapter: baseContext.chapter,
|
|
250
|
-
options: {
|
|
251
|
-
type: baseContext.options.type,
|
|
252
|
-
questionCount: baseContext.options.questionCount,
|
|
253
|
-
difficulty: baseContext.options.difficulty,
|
|
254
|
-
questionKind: baseContext.options.questionKind,
|
|
255
|
-
knowledgePointIds: [...usedIds],
|
|
256
|
-
abilityIds: baseContext.options.abilityIds || []
|
|
257
|
-
},
|
|
258
|
-
abilitySpecs: baseContext.abilitySpecs || [],
|
|
259
|
-
targetKnowledgePoints: baseContext.targetKnowledgePoints
|
|
260
|
-
.filter((point) => usedIds.size === 0 || usedIds.has(point.id)),
|
|
261
246
|
questions
|
|
262
247
|
};
|
|
263
248
|
}
|
|
@@ -471,13 +456,12 @@ async function runFinalReview({
|
|
|
471
456
|
initialReview
|
|
472
457
|
},
|
|
473
458
|
requirements: [
|
|
474
|
-
'
|
|
459
|
+
'这是定稿复审;遵守通用规则 R01-R20(已自动加载到 system prompt)。',
|
|
460
|
+
'评审题目质量、可作答性、重复风险、题量规划和 knowledgePointIds。',
|
|
475
461
|
'如果仍有 blocker,必须 passed=false,并指出具体题号和修正原因。',
|
|
476
462
|
'必须拦截题目条件缺失、答案不唯一、选项不完整、知识点绑定不在 targetKnowledgePoints 中的问题。',
|
|
477
463
|
'覆盖判断必须以 context.localCoverageCheck 为准;只有 missingPointIds 或 outsidePointIds 非空时,才能给 bad_coverage blocker。',
|
|
478
|
-
'
|
|
479
|
-
isAbilityAssessment ? '能力评估题必须检查 abilityIds 是否完整且只来自 context.options.abilityIds。' : '',
|
|
480
|
-
'定稿复审必须拦截 LaTeX 反斜杠损坏、制表符残留、几何/SVG 标注与题干不一致、标签遮挡关键条件。'
|
|
464
|
+
isAbilityAssessment ? '能力评估题必须检查 abilityIds 是否完整且只来自 context.options.abilityIds。' : ''
|
|
481
465
|
].filter(Boolean),
|
|
482
466
|
schema: {
|
|
483
467
|
passed: false,
|
|
@@ -543,6 +527,7 @@ async function repairFinalBlockers({
|
|
|
543
527
|
},
|
|
544
528
|
requirements: [
|
|
545
529
|
'必须逐条修复终审 blocker,不能只复述问题。',
|
|
530
|
+
'遵守通用规则 R01-R20(已自动加载到 system prompt)。',
|
|
546
531
|
'如果 blocker 类型是 bad_options、unanswerable 或指出答案不唯一,必须先保证题目存在唯一、明确、可判分的答案;选择题必须确保四个选项中恰好一个正确。',
|
|
547
532
|
'遇到选项唯一性问题时,不要只微调一个模糊选项;如果不能确信唯一正确,必须重写整道题的题干和全部选项,必要时改成填空题或简答题。',
|
|
548
533
|
'正方体展开图等视觉题不要使用“可能、并非明显、也许可以”这类不确定条件作为错误选项;错误选项必须是概念上明确错误或与给定图形明确矛盾。',
|
|
@@ -672,6 +657,7 @@ function compactKnowledgePoint(point, mastery = null) {
|
|
|
672
657
|
sectionTitle: point.sectionTitle,
|
|
673
658
|
summary: point.summary,
|
|
674
659
|
pitfalls: Array.isArray(point.pitfalls) ? point.pitfalls.slice(0, 3) : [],
|
|
660
|
+
teachingTips: point.teachingTips || null,
|
|
675
661
|
masteryStatus: mastery?.status || 'not_started',
|
|
676
662
|
masteryStats: mastery
|
|
677
663
|
? {
|
|
@@ -747,11 +733,12 @@ async function readPracticeSystemPrompt(promptName, context) {
|
|
|
747
733
|
|
|
748
734
|
async function callPracticeAgent({ promptName, task, context, requirements, schema, timeoutMs = 120000, temperature = 0.2, retries = 1 }) {
|
|
749
735
|
const systemPrompt = await readPracticeSystemPrompt(promptName, context);
|
|
736
|
+
const practiceRules = await readRules();
|
|
750
737
|
return callChatAgent({
|
|
751
738
|
timeoutMs,
|
|
752
739
|
retries,
|
|
753
740
|
temperature,
|
|
754
|
-
system: systemPrompt
|
|
741
|
+
system: `${systemPrompt}\n\n${practiceRules}`,
|
|
755
742
|
user: promptPayload({
|
|
756
743
|
task,
|
|
757
744
|
context,
|
|
@@ -930,6 +917,7 @@ export async function generatePracticeContent(input) {
|
|
|
930
917
|
: isMistakeRepair
|
|
931
918
|
? '本次是易错点覆盖/巩固卷:优先围绕 recentMistakes、needs_review、wrongCount 高的知识点和易错类型设计题目;如果当前没有可用易错点,则生成基础巩固卷,但题目仍要体现常见易错辨析,不要伪装成首轮知识点覆盖。'
|
|
932
919
|
: '本次是章节知识点覆盖卷,不要要求用户提供知识点编号;首轮覆盖尚未完成时,knowledge_coverage 必须优先从 previousKnowledgePointIds 没有覆盖过的 targetKnowledgePoints 出题;已覆盖点只做必要合并抽样。首轮完成后,再按 needs_review、wrongCount 高和 recentMistakes 命中的点优先。',
|
|
920
|
+
'每个 targetKnowledgePoint 可能包含 teachingTips,其中 commonMisconceptions 是孩子常见的具体误解,checkUnderstandingQuestions 是检查是否真正理解的问法。设计题目时,利用 commonMisconceptions 来设计选择题的干扰项或陷阱条件,让题目能有效暴露这些误解。',
|
|
933
921
|
isAbilityAssessment
|
|
934
922
|
? '能力评估第一版只评估计算准确性,不纳入用时、速度、限时达标;题目以短题、多题、低语境为主,尽量暴露计算准确性和常见计算错误。'
|
|
935
923
|
: '',
|
|
@@ -939,19 +927,18 @@ export async function generatePracticeContent(input) {
|
|
|
939
927
|
isAbilityAssessment
|
|
940
928
|
? 'skillAtoms 和 expectedAbilityErrors 是讲解版胶囊标签,只能写纯中文短标签;禁止使用 $...$、\\(...\\)、\\[...\\]、HTML 或复杂公式包装。'
|
|
941
929
|
: '',
|
|
942
|
-
'
|
|
943
|
-
'
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
'题面不给学生显示分值,不要在题干里写“本题多少分”。',
|
|
930
|
+
'遵守通用出题规则 R01-R20(已自动加载到 system prompt)。',
|
|
931
|
+
isAbilityAssessment || isMistakeRepair ? '' : (() => {
|
|
932
|
+
const trend = profile.learningState?.recentAccuracyTrend || 'stable';
|
|
933
|
+
const fatigue = profile.learningState?.lastSessionFatigue || 'medium';
|
|
934
|
+
const frustrationTopics = profile.learningState?.frustrationTopics || [];
|
|
935
|
+
const parts = [];
|
|
936
|
+
if (trend === 'declining') parts.push('最近正确率呈下降趋势,建议增加基础题比例(≥70%),降低题目难度,优先让孩子重建信心。');
|
|
937
|
+
else if (trend === 'improving') parts.push('最近正确率呈上升趋势,保持当前节奏,可以在最后 1-2 题适当增加挑战。');
|
|
938
|
+
if (fatigue === 'high') parts.push('孩子可能处于疲劳状态,建议本次练习题量偏少、题目偏简单,多给正向反馈型题目。');
|
|
939
|
+
if (frustrationTopics.length) parts.push(`以下知识点孩子已连续多次出错,设计相关题目时应降低难度、拆成更小步骤,或先换其他知识点避免连续打击:${frustrationTopics.slice(0, 3).join('、')}。`);
|
|
940
|
+
return parts.join(' ');
|
|
941
|
+
})(),
|
|
955
942
|
'第一阶段只生成题面、题型、知识点绑定、预期错因、分值和留白行数;answer、solutionSteps、rubric 会在题目定稿后由第二阶段分批补全。'
|
|
956
943
|
].filter(Boolean);
|
|
957
944
|
onProgress({
|
|
@@ -1076,13 +1063,10 @@ export async function generatePracticeContent(input) {
|
|
|
1076
1063
|
},
|
|
1077
1064
|
requirements: [
|
|
1078
1065
|
'至少完成一次 LLM review;发现问题必须给出具体修正指令。',
|
|
1066
|
+
'遵守通用规则 R01-R20(已自动加载到 system prompt),逐条检查题目是否符合规范。',
|
|
1079
1067
|
'评审题目质量、可作答性、题量规划和 knowledgePointIds。',
|
|
1080
|
-
'必须检查 knowledgePoints、expectedErrorTypes、skillAtoms、expectedAbilityErrors 是否为纯文本标签;出现 $...$、\\(...\\)、\\[...\\]、HTML 或公式包装时必须要求修正。',
|
|
1081
1068
|
isAbilityAssessment ? '能力评估题必须检查 abilityIds 是否完整且只来自 context.options.abilityIds。' : '',
|
|
1082
|
-
'如果题目缺少必要条件、答案无法唯一推出、选项不完整或题型不合适,必须判 blocker。'
|
|
1083
|
-
'必须检查 LaTeX 反斜杠是否损坏,尤其是 \\triangle、\\angle、\\perp、\\parallel、\\text{};发现丢反斜杠或制表符残留必须判 blocker。',
|
|
1084
|
-
'必须检查几何/SVG 题的题干与图中角标、垂足、切点、边名、长度和平行/垂直标注是否一致;不一致必须判 blocker。',
|
|
1085
|
-
'必须由评审判断每道题是否需要视觉信息才能作答。若没有 svg 但题干仍信息完整、唯一可答,则不要因为题目属于几何题而判错;若缺少图会导致学生凭想象补条件、空间位置不明确或答案不唯一,必须判 blocker 并要求补 SVG 或改成纯文字题。'
|
|
1069
|
+
'如果题目缺少必要条件、答案无法唯一推出、选项不完整或题型不合适,必须判 blocker。'
|
|
1086
1070
|
].filter(Boolean),
|
|
1087
1071
|
schema: {
|
|
1088
1072
|
passed: false,
|
|
@@ -1151,13 +1135,11 @@ export async function generatePracticeContent(input) {
|
|
|
1151
1135
|
},
|
|
1152
1136
|
requirements: [
|
|
1153
1137
|
'必须根据 review 修正后输出最终题目。',
|
|
1138
|
+
'遵守通用规则 R01-R20(已自动加载到 system prompt)。',
|
|
1154
1139
|
'只输出 practiceDraft.questions 中的题目,不新增题目,不删除题目,不改变题目 ID。',
|
|
1155
1140
|
'修订版必须包含题目、题型、难度、knowledgePointIds、knowledgePoints、expectedErrorTypes、配图字段和答题空间行数。',
|
|
1156
1141
|
isAbilityAssessment ? '能力评估修订版还必须保留 abilityIds、skillAtoms、expectedAbilityErrors。' : '',
|
|
1157
|
-
'knowledgePoints 和 expectedErrorTypes 必须是纯文本短标签,不能包含 $...$、\\(...\\)、\\[...\\]、HTML 或公式包装。',
|
|
1158
|
-
isAbilityAssessment ? 'skillAtoms 和 expectedAbilityErrors 必须是纯文本短标签,不能包含 $...$、\\(...\\)、\\[...\\]、HTML 或公式包装。' : '',
|
|
1159
1142
|
'不要输出 answer、solutionSteps 或 rubric;这些字段会在题目定稿后分批补全。',
|
|
1160
|
-
'必须修复 LaTeX 反斜杠损坏,并同步校准几何/SVG 题的题干与图中角标、垂足、切点、边名、长度和平行/垂直标注。',
|
|
1161
1143
|
'不要输出未修正草稿。'
|
|
1162
1144
|
].filter(Boolean),
|
|
1163
1145
|
schema: questionRevisionSchema()
|
|
@@ -1180,47 +1162,59 @@ export async function generatePracticeContent(input) {
|
|
|
1180
1162
|
onProgress({ step: 'practice_generate.revision_done', message: `题目修订完成:${revision.questions.length} 题。` });
|
|
1181
1163
|
let revisionCoverage = coverageSummaryForQuestions(targetKnowledgePoints, revision.questions);
|
|
1182
1164
|
if (!isAbilityAssessment && revisionCoverage.missingPointIds.length) {
|
|
1165
|
+
const missingPointLabels = revisionCoverage.missingPointIds.join('、');
|
|
1183
1166
|
const missingPoints = targetKnowledgePoints.filter((point) => revisionCoverage.missingPointIds.includes(point.id));
|
|
1184
1167
|
onProgress({
|
|
1185
1168
|
step: 'practice_generate.coverage_repair',
|
|
1186
|
-
message: `正在修复知识点覆盖缺口:${
|
|
1187
|
-
});
|
|
1188
|
-
const repair = assertAgentQuestions(await callPracticeAgentCached({
|
|
1189
|
-
cacheDir: stageCacheDir,
|
|
1190
|
-
cacheFile: 'coverage-repair.json',
|
|
1191
|
-
onProgress,
|
|
1192
|
-
promptName: 'practice-revise.system.md',
|
|
1193
|
-
task: '在不新增题目、不删除题目的前提下修复定稿题目的知识点覆盖缺口',
|
|
1194
|
-
context: {
|
|
1195
|
-
...baseContext,
|
|
1196
|
-
practiceDraft: omitSvgSourcesForTextReview(revision),
|
|
1197
|
-
localCoverageCheck: revisionCoverage,
|
|
1198
|
-
missingTargetKnowledgePoints: missingPoints
|
|
1199
|
-
},
|
|
1200
|
-
requirements: [
|
|
1201
|
-
'只修复覆盖缺口,不新增题目,不删除题目,不改变题目 ID。',
|
|
1202
|
-
`必须让修订后的 questions 覆盖这些 missingPointIds:${revisionCoverage.missingPointIds.join('、')}。`,
|
|
1203
|
-
'优先选择最相关的已有题目进行小幅改写或补充 knowledgePointIds;不能硬塞不相关知识点。',
|
|
1204
|
-
'如果题面需要随知识点绑定变化而调整,必须同步改题干,保证学生可作答且答案唯一。',
|
|
1205
|
-
'继续遵守视觉信息判断:题目需要图才能唯一作答时必须提供 SVG,否则应改成纯文字可答题。',
|
|
1206
|
-
'不要输出 answer、solutionSteps 或 rubric;这些字段会在题目定稿后补全。'
|
|
1207
|
-
],
|
|
1208
|
-
schema: questionRevisionSchema()
|
|
1209
|
-
}), 'coverage_repair');
|
|
1210
|
-
revision = {
|
|
1211
|
-
...revision,
|
|
1212
|
-
title: normalizePracticeTitle(repair.title || revision.title),
|
|
1213
|
-
personalizationBasis: repair.personalizationBasis || revision.personalizationBasis || [],
|
|
1214
|
-
revisionSummary: [revision.revisionSummary, repair.revisionSummary]
|
|
1215
|
-
.filter(Boolean)
|
|
1216
|
-
.join('\n'),
|
|
1217
|
-
questions: mergeRevisionQuestions(revision.questions, repair.questions)
|
|
1218
|
-
};
|
|
1219
|
-
revisionCoverage = coverageSummaryForQuestions(targetKnowledgePoints, revision.questions);
|
|
1220
|
-
onProgress({
|
|
1221
|
-
step: 'practice_generate.coverage_repair_done',
|
|
1222
|
-
message: `覆盖修复完成:已覆盖 ${revisionCoverage.coveredCount}/${revisionCoverage.intendedCount} 个目标点。`
|
|
1169
|
+
message: `正在修复知识点覆盖缺口:${missingPointLabels}。`
|
|
1223
1170
|
});
|
|
1171
|
+
try {
|
|
1172
|
+
const repairResult = await callPracticeAgentCached({
|
|
1173
|
+
cacheDir: stageCacheDir,
|
|
1174
|
+
cacheFile: 'coverage-repair.json',
|
|
1175
|
+
onProgress,
|
|
1176
|
+
promptName: 'practice-coverage-repair.system.md',
|
|
1177
|
+
task: '在不新增题目、不删除题目的前提下修复定稿题目的知识点覆盖缺口',
|
|
1178
|
+
context: {
|
|
1179
|
+
...baseContext,
|
|
1180
|
+
practiceDraft: omitSvgSourcesForTextReview(revision),
|
|
1181
|
+
localCoverageCheck: revisionCoverage,
|
|
1182
|
+
missingTargetKnowledgePoints: missingPoints
|
|
1183
|
+
},
|
|
1184
|
+
requirements: [
|
|
1185
|
+
'遵守通用规则 R01-R20(已自动加载到 system prompt)。',
|
|
1186
|
+
'只修复覆盖缺口,不新增题目,不删除题目,不改变题目 ID。',
|
|
1187
|
+
`必须让修订后的 questions 覆盖这些 missingPointIds:${missingPointLabels}。`,
|
|
1188
|
+
'优先选择最相关的已有题目进行小幅改写或补充 knowledgePointIds;不能硬塞不相关知识点。',
|
|
1189
|
+
'如果题面需要随知识点绑定变化而调整,必须同步改题干,保证学生可作答且答案唯一。',
|
|
1190
|
+
'不要输出 answer、solutionSteps 或 rubric;这些字段会在题目定稿后补全。'
|
|
1191
|
+
],
|
|
1192
|
+
schema: questionRevisionSchema(),
|
|
1193
|
+
retries: 2
|
|
1194
|
+
});
|
|
1195
|
+
const repair = assertAgentQuestions(repairResult, 'coverage_repair');
|
|
1196
|
+
revision = {
|
|
1197
|
+
...revision,
|
|
1198
|
+
title: normalizePracticeTitle(repair.title || revision.title),
|
|
1199
|
+
personalizationBasis: repair.personalizationBasis || revision.personalizationBasis || [],
|
|
1200
|
+
revisionSummary: [revision.revisionSummary, repair.revisionSummary]
|
|
1201
|
+
.filter(Boolean)
|
|
1202
|
+
.join('\n'),
|
|
1203
|
+
questions: mergeRevisionQuestions(revision.questions, repair.questions)
|
|
1204
|
+
};
|
|
1205
|
+
revisionCoverage = coverageSummaryForQuestions(targetKnowledgePoints, revision.questions);
|
|
1206
|
+
onProgress({
|
|
1207
|
+
step: 'practice_generate.coverage_repair_done',
|
|
1208
|
+
message: `覆盖修复完成:已覆盖 ${revisionCoverage.coveredCount}/${revisionCoverage.intendedCount} 个目标点。`
|
|
1209
|
+
});
|
|
1210
|
+
} catch (error) {
|
|
1211
|
+
repairSkipped = true;
|
|
1212
|
+
onProgress({
|
|
1213
|
+
step: 'practice_generate.coverage_repair_skipped',
|
|
1214
|
+
message: `覆盖缺口修复未成功,跳过:${missingPointLabels}。当前卷已覆盖 ${revisionCoverage.coveredCount}/${revisionCoverage.intendedCount} 个点。建议改用显式 knowledgePointIds 指定目标点重新生成以覆盖剩余缺口。`,
|
|
1215
|
+
detail: error.message
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1224
1218
|
}
|
|
1225
1219
|
const shouldRunSvgVerification = verifySvgFigures || requireSvg || minSvgQuestions > 0;
|
|
1226
1220
|
if (shouldRunSvgVerification) {
|
|
@@ -1403,14 +1397,14 @@ export async function generatePracticeContent(input) {
|
|
|
1403
1397
|
cacheDir: stageCacheDir,
|
|
1404
1398
|
cacheFile: `answer-chunk-${String(index + 1).padStart(2, '0')}.json`,
|
|
1405
1399
|
onProgress,
|
|
1406
|
-
promptName: 'practice-
|
|
1400
|
+
promptName: 'practice-answers.system.md',
|
|
1407
1401
|
task: `为定稿题目补全答案元数据(第 ${index + 1}/${answerQuestionChunks.length} 批)`,
|
|
1408
1402
|
context: contextForAnswerMetadata(baseContext, questions),
|
|
1409
1403
|
requirements: [
|
|
1410
1404
|
'只输出输入 questions 中已有题目的 id、answer、solutionSteps、rubric。',
|
|
1411
1405
|
'不得修改题目 ID、题干、题型、难度、knowledgePointIds、knowledgePoints、expectedErrorTypes、abilityIds、skillAtoms、expectedAbilityErrors、score、answerSpaceLines、svg 或 imagePrompt。',
|
|
1412
|
-
'answer
|
|
1413
|
-
'solutionSteps
|
|
1406
|
+
'answer 必须是可核对的标准答案;选择题写正确选项和简短结论,填空/问答题写完整答案。',
|
|
1407
|
+
'solutionSteps 分级输出:简单选择题/填空题只写 1 条简短说明(考察什么、关键辨析点);需要推导、几何观察或多步判断的题写 2-4 条解题要点。',
|
|
1414
1408
|
'solutionSteps 不要重复标准答案,不要写成批改标准;语言要像给孩子看的讲解。',
|
|
1415
1409
|
'rubric 必须可用于批改,至少包含关键结论和关键过程;各项 score 之和应接近题目 score。',
|
|
1416
1410
|
'公式继续使用 LaTeX 分隔符,并保留完整反斜杠。'
|
package/server/promptStore.js
CHANGED
|
@@ -6,6 +6,15 @@ export async function readPrompt(name) {
|
|
|
6
6
|
return readFile(path.join(paths.rootDir, 'prompts', name), 'utf8');
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
let rulesCache = null;
|
|
10
|
+
|
|
11
|
+
export async function readRules() {
|
|
12
|
+
if (rulesCache) return rulesCache;
|
|
13
|
+
const raw = await readFile(path.join(paths.rootDir, 'prompts', 'practice-rules.md'), 'utf8');
|
|
14
|
+
rulesCache = raw;
|
|
15
|
+
return raw;
|
|
16
|
+
}
|
|
17
|
+
|
|
9
18
|
export function promptPayload({ task, context, schema, requirements = [] }) {
|
|
10
19
|
return JSON.stringify({
|
|
11
20
|
task,
|
|
@@ -14,3 +23,8 @@ export function promptPayload({ task, context, schema, requirements = [] }) {
|
|
|
14
23
|
schema
|
|
15
24
|
});
|
|
16
25
|
}
|
|
26
|
+
|
|
27
|
+
export function expandRuleRefs(requirements, ruleIds = []) {
|
|
28
|
+
if (!ruleIds.length) return requirements;
|
|
29
|
+
return [...ruleIds.map((id) => `R${String(id).padStart(2, '0')}`), ...requirements];
|
|
30
|
+
}
|