sillyspec 3.28.13 → 3.29.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/package.json +1 -1
- package/src/blast-surface.js +168 -0
- package/src/ceremony-tier.js +359 -0
- package/src/change-risk-profile.js +72 -199
- package/src/config-schema.js +21 -0
- package/src/decisions-io.js +32 -0
- package/src/design-facts.js +1 -0
- package/src/doctor-diagnostics.js +119 -1
- package/src/index.js +92 -5
- package/src/knowledge-stats.js +25 -0
- package/src/modules.js +28 -0
- package/src/prefill.js +491 -0
- package/src/probe7-anchor-check.js +8 -6
- package/src/quick-gate-profile.js +8 -4
- package/src/quick-session-owner.js +63 -0
- package/src/review-dispatch.js +473 -1
- package/src/review-material-pack.js +265 -0
- package/src/review-tier.js +185 -41
- package/src/run/archive-distill.js +6 -0
- package/src/run/command.js +158 -87
- package/src/run/complete-handlers.js +82 -1
- package/src/run/complete.js +37 -1
- package/src/run/gates.js +301 -19
- package/src/run/prompt.js +423 -21
- package/src/run/shared.js +36 -0
- package/src/run/verify-quality-scan.js +14 -6
- package/src/scope-audit.js +8 -1
- package/src/span-risk-surface.js +127 -0
- package/src/stage-contract-spec.js +2 -2
- package/src/stage-contract.js +102 -81
- package/src/stage-review.js +26 -2
- package/src/stages/brainstorm.js +28 -15
- package/src/stages/execute.js +10 -2
- package/src/stages/plan.js +29 -12
- package/src/stages/verify.js +7 -7
- package/src/taskcard.js +3 -0
- package/src/verify-postcheck.js +375 -20
- package/src/verify-probes.js +117 -10
- package/src/worktree.js +1 -1
- package/templates/prompts/taskcard-rules.md +1 -0
- package/templates/prompts/verify-probes.md +1 -1
package/src/index.js
CHANGED
|
@@ -41,6 +41,8 @@ SillySpec CLI — 规范驱动开发工具包
|
|
|
41
41
|
--reset 重置阶段(从头开始)
|
|
42
42
|
--reopen --from-step <序号|名称> 重新打开已完成阶段进入修订
|
|
43
43
|
--wait --reason "..." --options "A,B" 暂停等用户决策
|
|
44
|
+
--wait --inherit-from <D-xxx@vN> 决策继承盖章:答案轮由 decisions.md 既有决策盖印
|
|
45
|
+
(仅 --wait 场景合法;ID 不存在则 exit 2 fail-closed)
|
|
44
46
|
--continue --answer "..." 恢复等待中的步骤
|
|
45
47
|
--done --answer "..." --output "..." 一步完成 wait+done
|
|
46
48
|
--change <名> 指定变更名(多活跃变更必填,单变更可省)
|
|
@@ -107,6 +109,7 @@ SillySpec CLI — 规范驱动开发工具包
|
|
|
107
109
|
单 task review.json 命令式写入:verdict/notes 你给,base/head/changedFiles/diffPaths CLI 代算
|
|
108
110
|
sillyspec symbol-impact --change <name> 生成 symbol-impact.md 逐 task <!--TODO--> 骨架(gate 拒绝未替换占位,防骨架直接过门)
|
|
109
111
|
sillyspec design-init --change <name> [--force] 从 decisions.md 生成 design.md 十三章节骨架(决策追踪表预填;已存在不覆盖)
|
|
112
|
+
sillyspec prefill-refresh --change <name> [--json] 重放三槽预填(design 文件清单/决策追踪表 + task 卡 ids;已确认槽不覆盖,幂等)
|
|
110
113
|
sillyspec fourpiece-init --change <name> 生成 proposal/requirements/decisions 骨架(frontmatter+章节+模板占位;已存在不覆盖)
|
|
111
114
|
sillyspec delta --change <name> [--json] 生成变更 delta.md(Before/Delta/After 三段式聚合;幂等覆盖重跑即刷新)
|
|
112
115
|
sillyspec next 项目状态探测:输出当前状态 + 下一步命令 + 依据(吸收 continue/resume 手工探测表)
|
|
@@ -1206,7 +1209,7 @@ task 进行中状态标记:开工/完工落 .runtime/task-progress/,list 标
|
|
|
1206
1209
|
const vpMatrix = ensureAcceptanceMatrixSection(vpReportPath, vpResult.probe7)
|
|
1207
1210
|
if (vpMatrix.added) {
|
|
1208
1211
|
try { await mirrorInitArtifact(vpReportPath, 'verify-result.md', readFileSync(vpReportPath, 'utf8')) } catch { /* 镜像失败不阻断 */ }
|
|
1209
|
-
console.log(`🧩 探针 7
|
|
1212
|
+
console.log(`🧩 探针 7 验收×测试覆盖矩阵骨架段已补齐(判定=五选一/证据两槽待填,命中≠判定;不触碰既有正文): ${vpReportPath}${vpPlatformNote}`)
|
|
1210
1213
|
}
|
|
1211
1214
|
} catch { /* 矩阵补段失败不阻断 --init */ }
|
|
1212
1215
|
// P0-1(noai-ir-roadmap §3)两件机械预填,均幂等 fail-soft:
|
|
@@ -1869,6 +1872,7 @@ change: ${fpChange}
|
|
|
1869
1872
|
# 决策记录(Decisions)
|
|
1870
1873
|
|
|
1871
1874
|
<!-- 增量落盘:每解决一个有实现影响的问题当场追加一条(格式见 brainstorm Step 3 模板);幂等按 D-xxx@vN 判重 -->
|
|
1875
|
+
<!-- 引用规范:evidence 等处的源码位置写仓根相对全路径+行号(src/foo.js:123)——裸文件名在 docs-check 层1 靠 basename 全仓扫描找候选,找不到候选或关键词窗口不匹配即失效,到 pre-push 才拦(2026-09-19 实证 64 处返工) -->
|
|
1872
1876
|
`);
|
|
1873
1877
|
if (json) {
|
|
1874
1878
|
console.log(JSON.stringify({ command: 'fourpiece-init', change: fpChange, generated }, null, 2));
|
|
@@ -1883,12 +1887,13 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
1883
1887
|
// 从零手写整份设计文档,章节漏项到 stage-contract/plan 门禁才暴露。本命令从 decisions.md
|
|
1884
1888
|
// 当前版本 D 条目预填决策追踪表,章节标题逐字取自 brainstorm Step 6 模板(R-03,渲染本体在
|
|
1885
1889
|
// design-facts.js generateDesignSkeleton——本 case 只负责读盘/幂等保护/落盘)。已存在不覆盖
|
|
1886
|
-
// (幂等,agent 产出优先),--force 覆盖重生成。
|
|
1890
|
+
// (幂等,agent 产出优先),--force 覆盖重生成。task-02(2026-09-18-artifact-prefill):追踪
|
|
1891
|
+
// 表行落盘前经 prefill.js prefillDecisionTable 替换为带来源注的预填行(见下方接线块)。
|
|
1887
1892
|
const diChangeIdx = args.indexOf('--change');
|
|
1888
1893
|
const diChange = diChangeIdx >= 0 && args[diChangeIdx + 1] && !String(args[diChangeIdx + 1]).startsWith("--") ? args[diChangeIdx + 1] : null;
|
|
1889
1894
|
const diForce = args.includes('--force');
|
|
1890
1895
|
if (!diChange) {
|
|
1891
|
-
console.error('用法: sillyspec design-init --change <name> [--force] [--json] [--spec-dir <path>]\n 从变更 decisions.md 生成 design.md
|
|
1896
|
+
console.error('用法: sillyspec design-init --change <name> [--force] [--json] [--spec-dir <path>]\n 从变更 decisions.md 生成 design.md 十三章节骨架(决策追踪表按 D 条目预填、行带来源注;已存在不覆盖,--force 覆盖)');
|
|
1892
1897
|
process.exit(2);
|
|
1893
1898
|
}
|
|
1894
1899
|
assertSafeChangeName(diChange, '--change 变更名');
|
|
@@ -1906,12 +1911,35 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
1906
1911
|
let diAuthor = 'unknown';
|
|
1907
1912
|
try { diAuthor = git(dir, ['config', 'user.name']) || 'unknown' } catch { /* git 不可用 → unknown */ }
|
|
1908
1913
|
const { nowWallClock } = await import('./datetime.js');
|
|
1909
|
-
const
|
|
1914
|
+
const diSkeletonBase = generateDesignSkeleton({
|
|
1910
1915
|
changeName: diChange,
|
|
1911
1916
|
decisionsText: readFileSync(diDecisionsPath, 'utf8'),
|
|
1912
1917
|
author: diAuthor,
|
|
1913
1918
|
now: nowWallClock(),
|
|
1914
1919
|
});
|
|
1920
|
+
// task-02(2026-09-18-artifact-prefill)决策追踪表接线:design-facts 骨架行(无注)替换为
|
|
1921
|
+
// prefillDecisionTable 预填行(状态列带来源注——D-003 预填≠结论,agent 逐行核对后删注=确认)。
|
|
1922
|
+
// 推导单点在 prefill.js(与 prefill-refresh 重放同源,两处不各自重写防漂移)。decisions.md
|
|
1923
|
+
// 无 canonical「## D-xxx@vN」条目时返回 [] → 骨架原样落盘(design-facts 自带「未解析出条目」
|
|
1924
|
+
// 注释行,现状不变)。段定位对齐 prefill.js DECISION_SECTION_RE 同义词集;骨架十三章节形态
|
|
1925
|
+
// 固定(表头|分隔行|内容行),定位失败 fail-soft 回退原骨架——预填是 advisory 能力不阻断。
|
|
1926
|
+
const { prefillDecisionTable } = await import('./prefill.js');
|
|
1927
|
+
const diDecisionRows = prefillDecisionTable({ changeDir: diChangeDir });
|
|
1928
|
+
let diSkeleton = diSkeletonBase;
|
|
1929
|
+
if (diDecisionRows.length > 0) {
|
|
1930
|
+
const diLines = diSkeletonBase.split('\n');
|
|
1931
|
+
const diSecIdx = diLines.findIndex((l) => /^#{2,3}\s*(?:\d+[.)]\s*)?决策追踪/.test(l));
|
|
1932
|
+
if (diSecIdx !== -1) {
|
|
1933
|
+
let diSepIdx = diSecIdx + 1;
|
|
1934
|
+
while (diSepIdx < diLines.length && !(diLines[diSepIdx].startsWith('|') && /^\|[-:\s|]+\|$/.test(diLines[diSepIdx]))) diSepIdx++;
|
|
1935
|
+
if (diSepIdx < diLines.length) {
|
|
1936
|
+
let diRowEnd = diSepIdx + 1;
|
|
1937
|
+
while (diRowEnd < diLines.length && diLines[diRowEnd].startsWith('|') && !/^\|[-:\s|]+\|$/.test(diLines[diRowEnd])) diRowEnd++;
|
|
1938
|
+
diLines.splice(diSepIdx + 1, diRowEnd - diSepIdx - 1, ...diDecisionRows);
|
|
1939
|
+
diSkeleton = diLines.join('\n');
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1915
1943
|
const diDesignPath = join(diChangeDir, 'design.md');
|
|
1916
1944
|
const diExisted = existsSync(diDesignPath);
|
|
1917
1945
|
if (diExisted && !diForce) {
|
|
@@ -1939,7 +1967,35 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
1939
1967
|
break;
|
|
1940
1968
|
}
|
|
1941
1969
|
console.log(`✅ 已生成 design.md 骨架: ${diDesignPath}${diExisted ? '(--force 覆盖既有文件)' : ''}`);
|
|
1942
|
-
console.log(' 决策追踪表已按 decisions.md
|
|
1970
|
+
console.log(' 决策追踪表已按 decisions.md D 条目预填(状态列带「(预填:核对后删本注)」来源注——预填≠结论,逐行核对后删注确认);回到 brainstorm Step 6(写设计文档并自审)逐节填散文,填毕删除生成注释。');
|
|
1971
|
+
break;
|
|
1972
|
+
}
|
|
1973
|
+
case 'prefill-refresh': {
|
|
1974
|
+
// Phase 3(2026-09-18-artifact-prefill task-02,D-005):三槽预填重放——design.md 文件变更
|
|
1975
|
+
// 清单/决策追踪两表槽 + tasks/*.md 的 requirement_ids/decision_ids。仅白名单槽(D-001);
|
|
1976
|
+
// 已确认槽(预填注已删/人工内容)跳过不覆盖;幂等可重跑。骨架生成时源未就绪(task 卡先于
|
|
1977
|
+
// requirements/decisions 落盘等)的槽由此命令补齐——plan 阶段后主通道。CLI 单一写入方
|
|
1978
|
+
// 不变(D-002):本路由只透传 runPrefillRefresh 的结果与计数,不自建写盘逻辑。
|
|
1979
|
+
const prChangeIdx = args.indexOf('--change');
|
|
1980
|
+
const prChange = prChangeIdx >= 0 && args[prChangeIdx + 1] && !String(args[prChangeIdx + 1]).startsWith("--") ? args[prChangeIdx + 1] : null;
|
|
1981
|
+
if (!prChange) {
|
|
1982
|
+
console.error('用法: sillyspec prefill-refresh --change <name> [--spec-dir <path>] [--json]\n 重放三槽预填(design 文件变更清单/决策追踪表行 + task 卡 requirement_ids/decision_ids);已确认槽(预填注已删)跳过不覆盖;幂等可重跑');
|
|
1983
|
+
process.exit(2);
|
|
1984
|
+
}
|
|
1985
|
+
assertSafeChangeName(prChange, '--change 变更名');
|
|
1986
|
+
const prSpecBase = resolvePlatformSpecDir(dir, specDir) || join(dir, '.sillyspec');
|
|
1987
|
+
if (!existsSync(join(prSpecBase, 'changes', prChange))) {
|
|
1988
|
+
console.error(`❌ 变更目录不存在: ${join(prSpecBase, 'changes', prChange)}`);
|
|
1989
|
+
process.exit(1);
|
|
1990
|
+
}
|
|
1991
|
+
const { runPrefillRefresh } = await import('./prefill.js');
|
|
1992
|
+
const prResult = await runPrefillRefresh({ cwd: dir, specBase: prSpecBase, changeName: prChange });
|
|
1993
|
+
if (json) {
|
|
1994
|
+
console.log(JSON.stringify({ command: 'prefill-refresh', change: prChange, ok: true, ...prResult }, null, 2));
|
|
1995
|
+
break;
|
|
1996
|
+
}
|
|
1997
|
+
for (const l of prResult.lines) console.log(l);
|
|
1998
|
+
console.log(`📊 预填重放完成:filled ${prResult.filled} / skipped ${prResult.skipped} / confirmed ${prResult.confirmed}(已确认跳过)`);
|
|
1943
1999
|
break;
|
|
1944
2000
|
}
|
|
1945
2001
|
case 'delta': {
|
|
@@ -2298,6 +2354,7 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
2298
2354
|
}
|
|
2299
2355
|
console.log(`\n修复指引:行号漂移 → 更新文档行号到当前源码;文件删改名 → 更新引用路径;`);
|
|
2300
2356
|
console.log(`关键词缺失但行号正确 → 确认符号是否改名,改文档 token 或行号。`);
|
|
2357
|
+
console.log(`裸文件名引用(foo.js:123 形态)→ 改写为仓根相对全路径(src/foo.js:123)——裸名靠 basename 全仓扫描找候选,找不到候选或关键词窗口不匹配即失效;变更文档写作期就写全路径可免此返工。`);
|
|
2301
2358
|
}
|
|
2302
2359
|
// 变更名提名 advisory 段(主结果之后、exit 之前;不参与 ok/invalid 与 exit code)。
|
|
2303
2360
|
// 零输出原则(docs-check-fix 契约:无新 flag 时 stdout 逐字节一致;决策规则族同款
|
|
@@ -3886,6 +3943,36 @@ checkbox 行;depends_on 自动反填行内注解 "(depends_on: task-01,02)";
|
|
|
3886
3943
|
// 对齐 endpoints 等命令:先 resolvePlatformSpecDir(指针 fail-closed 语义同源)。
|
|
3887
3944
|
const tcPlatformSpecDir = resolvePlatformSpecDir(dir, specDir) || specDir;
|
|
3888
3945
|
const result = cmdTaskcard(tcName, { cwd: dir, specDir: tcPlatformSpecDir, taskIds, title: titleVal, titleZh: titleZhVal, force, sets });
|
|
3946
|
+
// task-02(2026-09-18-artifact-prefill)taskcard ids 直填接线:本次 created 的骨架卡,
|
|
3947
|
+
// 占位行「requirement_ids: [FR-XX] / decision_ids: [D-XXX@vN]」立即改写为 prefillCardIds
|
|
3948
|
+
// 推导值——行格式与 runPrefillRefresh 重放逐字一致(后续 refresh 幂等零改写)。源文件在场
|
|
3949
|
+
// → 直填+来源注(D-003 预填≠结论,核对后删注=确认);源缺/推导为空 → 空数组+提示行
|
|
3950
|
+
// (D-005:plan 阶段源就绪后跑 prefill-refresh 重放补齐)。只整行替换占位字面量:
|
|
3951
|
+
// --set 显式覆盖过的行已非占位形态天然不动;skipped(已存在)卡不触碰,幂等纪律不变。
|
|
3952
|
+
// 须在下方平台镜像块之前完成——镜像 readFileSync 回读的是改写后的终稿。
|
|
3953
|
+
const { prefillCardIds, PREFILL_NOTE } = await import('./prefill.js');
|
|
3954
|
+
const tcPrefillIds = prefillCardIds({ changeDir: join(tcPlatformSpecDir, 'changes', tcName) });
|
|
3955
|
+
const tcIdLine = (field, ids) => ids.length > 0
|
|
3956
|
+
? `${field}: [${ids.join(', ')}] # ${PREFILL_NOTE}`
|
|
3957
|
+
: `${field}: [] # (预填源未就绪:plan 阶段后跑 prefill-refresh)`;
|
|
3958
|
+
for (const f of result.created) {
|
|
3959
|
+
try {
|
|
3960
|
+
const tcRaw = readFileSync(f, 'utf8');
|
|
3961
|
+
// 替换值经函数返回:避免 String.replace 替换串里 $ 序列的转义歧义
|
|
3962
|
+
const tcNext = tcRaw
|
|
3963
|
+
.replace('requirement_ids: [FR-XX]', () => tcIdLine('requirement_ids', tcPrefillIds.requirementIds))
|
|
3964
|
+
.replace('decision_ids: [D-XXX@vN]', () => tcIdLine('decision_ids', tcPrefillIds.decisionIds));
|
|
3965
|
+
if (tcNext !== tcRaw) writeFileSync(f, tcNext);
|
|
3966
|
+
} catch { /* 单卡预填改写失败不连坐(骨架已落盘,prefill-refresh 可补) */ }
|
|
3967
|
+
}
|
|
3968
|
+
if (result.created.length > 0) {
|
|
3969
|
+
const tcSetKeys = new Set(Object.keys(sets));
|
|
3970
|
+
const tcIdSummary = (field, ids) => {
|
|
3971
|
+
if (tcSetKeys.has(field)) return '(--set 显式覆盖,预填不碰)';
|
|
3972
|
+
return ids.length > 0 ? `[${ids.join(', ')}]` : '(源未就绪留 [] + 提示行)';
|
|
3973
|
+
};
|
|
3974
|
+
console.log(` ids 预填:requirement_ids ${tcIdSummary('requirement_ids', tcPrefillIds.requirementIds)} / decision_ids ${tcIdSummary('decision_ids', tcPrefillIds.decisionIds)}——预填≠结论,核对后删注。`);
|
|
3975
|
+
}
|
|
3889
3976
|
// 平台模式产物双写镜像(坑 platform-init-artifact-daemon-dir-only 同族,2026-09-10 驾驭
|
|
3890
3977
|
// 小结第六批①,用户实证 taskcard --all 落 daemon 镜像「中间一度两份副本」):agent 本地
|
|
3891
3978
|
// (pointer 态、非显式 --spec-dir)生成的卡片即时镜像主仓 changeDir——不再等 spec-sync
|
package/src/knowledge-stats.js
CHANGED
|
@@ -122,12 +122,21 @@ export function buildFrIndexStats(knowledgeDir, runtimeDir, { sinceDays = 30 } =
|
|
|
122
122
|
const supersedeChanges = new Set()
|
|
123
123
|
const duplicateCandidates = new Set()
|
|
124
124
|
const unreferencedByDomain = new Map()
|
|
125
|
+
// fr-inject 按来源分组(ql-20260919-002 口径:source='module-inject'(注入期 join,四面消费)
|
|
126
|
+
// vs 'digest'(brainstorm step8 写作期)。存量事件无 source 读数归 digest 桶——保总数口径稳定,
|
|
127
|
+
// 只增归因维度;新发射面一律带 source 勿开新 type(未知 fr-* 类型静默漏过下方 if 链)。
|
|
128
|
+
const frInjectBySource = new Map()
|
|
129
|
+
const rotSuspectByDomain = new Map()
|
|
130
|
+
const rotSuspectChanges = new Set()
|
|
125
131
|
let frInject = 0
|
|
126
132
|
let frSupersede = 0
|
|
127
133
|
let frDuplicateWarning = 0
|
|
134
|
+
let frRotSuspect = 0
|
|
128
135
|
for (const r of frEvents) {
|
|
129
136
|
if (r.type === 'fr-inject') {
|
|
130
137
|
frInject += 1
|
|
138
|
+
const src = typeof r.source === 'string' && r.source ? r.source : 'digest'
|
|
139
|
+
frInjectBySource.set(src, (frInjectBySource.get(src) || 0) + 1)
|
|
131
140
|
if (r.change) injectChanges.add(r.change)
|
|
132
141
|
} else if (r.type === 'fr-supersede') {
|
|
133
142
|
frSupersede += 1
|
|
@@ -135,6 +144,18 @@ export function buildFrIndexStats(knowledgeDir, runtimeDir, { sinceDays = 30 } =
|
|
|
135
144
|
} else if (r.type === 'fr-duplicate-warning') {
|
|
136
145
|
frDuplicateWarning += 1
|
|
137
146
|
if (r.candidate) duplicateCandidates.add(r.candidate)
|
|
147
|
+
} else if (r.type === 'fr-rot-suspect') {
|
|
148
|
+
// 钩子 #1(quick 写面腐烂 suspect,source=quick-done):按域求和(count=触达 active FR 数),
|
|
149
|
+
// 总数按事件计(多域事件不重复计);涉及变更去重——L3 裁决核心读数。盲区:unmapped 漏
|
|
150
|
+
// join / map 漂移——此计数是下界不是全量。
|
|
151
|
+
frRotSuspect += 1
|
|
152
|
+
for (const domain of Array.isArray(r.domains) ? r.domains : [r.domain || 'unknown']) {
|
|
153
|
+
const row = rotSuspectByDomain.get(domain) || { domain, events: 0, count: 0 }
|
|
154
|
+
row.events += 1
|
|
155
|
+
row.count += Number.isFinite(Number(r.count)) ? Number(r.count) : 0
|
|
156
|
+
rotSuspectByDomain.set(domain, row)
|
|
157
|
+
}
|
|
158
|
+
if (r.change) rotSuspectChanges.add(r.change)
|
|
138
159
|
} else if (r.type === 'fr-unreferenced') {
|
|
139
160
|
const domain = r.domain || 'unknown'
|
|
140
161
|
const row = unreferencedByDomain.get(domain) || { domain, events: 0, count: 0 }
|
|
@@ -157,6 +178,10 @@ export function buildFrIndexStats(knowledgeDir, runtimeDir, { sinceDays = 30 } =
|
|
|
157
178
|
present: frDirExists || frEvents.length > 0,
|
|
158
179
|
events: {
|
|
159
180
|
frInject,
|
|
181
|
+
frInjectBySource: Object.fromEntries(frInjectBySource),
|
|
182
|
+
frRotSuspect,
|
|
183
|
+
frRotSuspectByDomain: [...rotSuspectByDomain.values()],
|
|
184
|
+
frRotSuspectChanges: rotSuspectChanges.size,
|
|
160
185
|
frInjectChanges: injectChanges.size,
|
|
161
186
|
frSupersede,
|
|
162
187
|
frSupersedeChanges: supersedeChanges.size,
|
package/src/modules.js
CHANGED
|
@@ -122,6 +122,7 @@ export async function rebuildModuleMap(cwd, { force = false } = {}) {
|
|
|
122
122
|
if (headCommit) yaml += `source_commit: ${headCommit}\n`;
|
|
123
123
|
yaml += `\n# Module Context Index — 机器可读的模块上下文索引\n`;
|
|
124
124
|
yaml += `# scan 阶段自动生成,brainstorm/plan/execute 阶段按任务命中模块精准注入上下文\n`;
|
|
125
|
+
yaml += `# ⚠️ 顶层手工段(如 blast 危险面声明)随 --force 写盘从原文回插保留;段内列表项手工维护\n`;
|
|
125
126
|
yaml += `\nmodules:\n`;
|
|
126
127
|
|
|
127
128
|
// 合并:已有映射 + 新卡片
|
|
@@ -175,6 +176,33 @@ export async function rebuildModuleMap(cwd, { force = false } = {}) {
|
|
|
175
176
|
yaml += `\n`;
|
|
176
177
|
}
|
|
177
178
|
|
|
179
|
+
// ── 顶层手工段保留(2026-09-19-ceremony-pricing-five-cuts D-008@v2)──────────────────
|
|
180
|
+
// rebuild 重发射只生成头部与 modules: 段——existingMap 里的其他顶层段(blast 危险面声明等
|
|
181
|
+
// 手工维护段)在 --force 写盘时会整体丢失(merge 白名单只覆盖 modules 段内 per-module 字段,
|
|
182
|
+
// 顶层段无此机制)。修法:从 existingMap 原文提取非重建头的顶层段原样回插(通用机制,不只为
|
|
183
|
+
// blast 特判)。提取口径:顶层键行(行首无缩进 `key:`)且 ∉ 重建头五族(schema_version/
|
|
184
|
+
// generated_at/generator/source_commit/modules);范围=该键行起至下一顶层键行前(段内注释/
|
|
185
|
+
// 列表项随之保留)。段缺失/无手工段 → 不追加不告警(rebuild 本就是重建语义,宁失勿错)。
|
|
186
|
+
const TOP_LEVEL_REGENERATED = new Set(['schema_version', 'generated_at', 'generator', 'source_commit', 'modules']);
|
|
187
|
+
const preservedTopSections = [];
|
|
188
|
+
if (existingMap) {
|
|
189
|
+
let current = null;
|
|
190
|
+
for (const line of existingMap.split('\n')) {
|
|
191
|
+
const m = line.match(/^([A-Za-z_][\w-]*):/);
|
|
192
|
+
if (m) {
|
|
193
|
+
if (current) { preservedTopSections.push(current.join('\n') + '\n'); current = null; }
|
|
194
|
+
if (!TOP_LEVEL_REGENERATED.has(m[1])) current = [line];
|
|
195
|
+
} else if (current) {
|
|
196
|
+
current.push(line);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (current) preservedTopSections.push(current.join('\n') + '\n');
|
|
200
|
+
}
|
|
201
|
+
for (const section of preservedTopSections) {
|
|
202
|
+
const trimmed = section.replace(/\n+$/, '');
|
|
203
|
+
if (trimmed.trim()) yaml += `\n${trimmed}\n`;
|
|
204
|
+
}
|
|
205
|
+
|
|
178
206
|
// 写入保护(multi-agent-platform 坑 modules-rebuild-destructive):默认 dry-run 只预览不写。
|
|
179
207
|
// P2-c 后 --force 为 merge 语义(手动字段全量保留),但重写仍会刷新 generated_at/source_commit
|
|
180
208
|
// 与排版——dry-run 预览默认保留,确认写入用 --force。
|