feat(gen-plugin-files): 新增 README 五個共通章節的順序漂移檢查,並把 persona 納入 AGENTS.md 產生範圍
- checkHeadingOrder():驗證五個共通章節的相對順序是否與 DEFAULT_HEADINGS 一致,不一致時報錯並指出違規的 repo 與章節,只報錯不自動搬移。 - AGENTS_APPLIES_TO 加入 persona,改用 agentsExtraBullets 機制產生其 AGENTS.md,不再是唯一排除在產生範圍外的 repo。 - 順帶修正 renderSectionForCompare() 對「檔案最後一節」結尾空行數的錯誤假設,避免內容其實相同卻被誤報「會變動」。
This commit is contained in:
@@ -139,10 +139,10 @@ function buildCodexMarketplaceJson(meta) {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AGENTS.md(只給 shared / doc / code;persona 不套用)
|
||||
// AGENTS.md(四個 repo 皆套用;persona 特有內容走 agentsExtraBullets,見 L1-5)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const AGENTS_APPLIES_TO = new Set(['shared', 'doc', 'code']);
|
||||
const AGENTS_APPLIES_TO = new Set(['shared', 'doc', 'code', 'persona']);
|
||||
|
||||
function buildAgentsMd(meta) {
|
||||
const tmpl = fs.readFileSync(path.join(TEMPLATES_DIR, 'AGENTS.md.tmpl'), 'utf8');
|
||||
@@ -170,6 +170,34 @@ function getHeadings(meta) {
|
||||
return { ...DEFAULT_HEADINGS, ...(meta.readmeHeadings || {}) };
|
||||
}
|
||||
|
||||
/**
|
||||
* 檢查五個共通章節在該份 README 裡的**相對順序**是否與 DEFAULT_HEADINGS 的宣告順序一致
|
||||
* (L1-4):只驗證順序,不驗證內容——內容正確性屬於 buildReadme() 各章節自己的職責。
|
||||
* 找不到的章節(例如該 repo 沒有這節)直接跳過,不算順序錯誤。
|
||||
* 順序不一致時丟出 Error,訊息帶 repo 短名與違規的章節名稱,呼叫端不吞掉這個例外,
|
||||
* 讓產生器對這個 repo 的處理直接中止(依 L1-4「只報錯、不自動搬移」)。
|
||||
*/
|
||||
function checkHeadingOrder(shortName, currentReadmeText, headings) {
|
||||
const order = Object.keys(DEFAULT_HEADINGS); // prefixTable, dirTree, install, headless, addSkill
|
||||
const positions = [];
|
||||
for (const key of order) {
|
||||
const heading = headings[key];
|
||||
const sec = findSection(currentReadmeText, heading);
|
||||
if (sec === null) continue; // 該 repo 沒有這節,不納入順序比較
|
||||
positions.push({ key, heading, line: sec.startLine });
|
||||
}
|
||||
for (let i = 1; i < positions.length; i++) {
|
||||
if (positions[i].line < positions[i - 1].line) {
|
||||
throw new Error(
|
||||
`README 章節順序漂移(repo: ${shortName}):「## ${positions[i].heading}」(第 ${positions[i].line + 1} 行)` +
|
||||
`出現在「## ${positions[i - 1].heading}」(第 ${positions[i - 1].line + 1} 行)之前,` +
|
||||
`五個共通章節的相對順序須為:${order.map((k) => headings[k]).join(' → ')}。` +
|
||||
`本工具只報錯、不自動搬移章節位置,請人工調整後重跑。`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 找出「## <heading>」章節在整份文件中的字元範圍:[sectionStart, sectionEnd)。
|
||||
* sectionStart 指向標題行開頭;sectionEnd 指向下一個獨立一行 `---` 或下一個 `## ` 標題(不含),或檔尾。
|
||||
@@ -217,13 +245,21 @@ function replaceSection(text, heading, bodyLines) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 章節文字的「可比較表示」:既有章節(sec.sectionText)永遠包含結尾那一行空行(緊接在 `---` 之前),
|
||||
* 所以這裡也補上同一個結尾空行,才能跟 oldSectionText 公平比較,避免只因為少了那一行空行就誤報「有變動」。
|
||||
* 章節文字的「可比較表示」:一般章節後面接 `---`,`sec.sectionText` 會包含結尾那一行空行,
|
||||
* 所以這裡補上同一個結尾空行才能公平比較。但**檔案最後一節**(後面沒有 `---`/`## `)的
|
||||
* `sec.sectionText` 直接切到檔尾,結尾空行數等於檔案實際的結尾換行數(可能是 1 行、2 行或
|
||||
* 更多),不是固定 1 行——若仍然無條件補一行,會跟只有單一結尾換行的檔案產生假性「有變動」
|
||||
* (已於 L1-6 驗證時發現:`replaceSection()` 實際寫出的內容其實逐字元相同,只有這裡的比較
|
||||
* 基準算錯)。比較前一律把兩邊的結尾空行正規化掉,只在意內容本身是否改變。
|
||||
*/
|
||||
function renderSectionForCompare(heading, bodyLines) {
|
||||
return [`## ${heading}`, '', ...bodyLines, ''].join('\n');
|
||||
}
|
||||
|
||||
function normalizeForCompare(sectionText) {
|
||||
return sectionText.replace(/\n+$/, '');
|
||||
}
|
||||
|
||||
// ---- 章節 1/5:前綴與呼叫方式(完全模板化) ----
|
||||
|
||||
function buildPrefixTableBodyLines(meta) {
|
||||
@@ -297,6 +333,7 @@ function buildPassthroughBodyLines(currentReadmeText, heading) {
|
||||
*/
|
||||
function buildReadme(meta, currentReadmeText) {
|
||||
const headings = getHeadings(meta);
|
||||
checkHeadingOrder(meta.shortName, currentReadmeText, headings);
|
||||
const changes = [];
|
||||
let text = currentReadmeText;
|
||||
|
||||
@@ -308,7 +345,7 @@ function buildReadme(meta, currentReadmeText) {
|
||||
changes.push({
|
||||
key: 'prefixTable',
|
||||
heading: headings.prefixTable,
|
||||
changed: oldSectionText !== newSectionText,
|
||||
changed: normalizeForCompare(oldSectionText) !== normalizeForCompare(newSectionText),
|
||||
oldText: oldSectionText,
|
||||
newText: newSectionText,
|
||||
});
|
||||
@@ -326,7 +363,7 @@ function buildReadme(meta, currentReadmeText) {
|
||||
changes.push({
|
||||
key: 'dirTree',
|
||||
heading: headings.dirTree,
|
||||
changed: oldSectionText !== newSectionText,
|
||||
changed: normalizeForCompare(oldSectionText) !== normalizeForCompare(newSectionText),
|
||||
oldText: oldSectionText,
|
||||
newText: newSectionText,
|
||||
});
|
||||
@@ -342,7 +379,7 @@ function buildReadme(meta, currentReadmeText) {
|
||||
changes.push({
|
||||
key: 'install',
|
||||
heading: headings.install,
|
||||
changed: oldSectionText !== newSectionText,
|
||||
changed: normalizeForCompare(oldSectionText) !== normalizeForCompare(newSectionText),
|
||||
oldText: oldSectionText,
|
||||
newText: newSectionText,
|
||||
});
|
||||
@@ -360,7 +397,7 @@ function buildReadme(meta, currentReadmeText) {
|
||||
changes.push({
|
||||
key: 'headless',
|
||||
heading: headings.headless,
|
||||
changed: oldSectionText !== newSectionText,
|
||||
changed: normalizeForCompare(oldSectionText) !== normalizeForCompare(newSectionText),
|
||||
oldText: oldSectionText,
|
||||
newText: newSectionText,
|
||||
});
|
||||
@@ -379,7 +416,7 @@ function buildReadme(meta, currentReadmeText) {
|
||||
changes.push({
|
||||
key: 'addSkill',
|
||||
heading: headings.addSkill,
|
||||
changed: oldSectionText !== newSectionText,
|
||||
changed: normalizeForCompare(oldSectionText) !== normalizeForCompare(newSectionText),
|
||||
oldText: oldSectionText,
|
||||
newText: newSectionText,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user