sillyspec 3.28.8 → 3.28.10
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/.claude/full.json +1 -0
- package/.claude/skills/sillyspec-execute/SKILL.md +222 -224
- package/.claude/skills/sillyspec-plan/SKILL.md +156 -156
- package/.claude/skills/sillyspec-quick/SKILL.md +118 -117
- package/AGENTS.md +3 -2
- package/README.md +35 -11
- package/SKILL.md +24 -10
- package/SillySpec-/350/203/275/345/212/233/344/272/256/347/202/271/345/205/250/346/231/257-2026-09-14.pptx +0 -0
- package/package.json +1 -1
- package/src/change-list.js +73 -20
- package/src/change-risk-profile.js +20 -0
- package/src/config-schema.js +62 -0
- package/src/contract-matrix.js +84 -5
- package/src/cross-repo-reconcile.js +134 -0
- package/src/db.js +11 -3
- package/src/decision-distill.js +15 -3
- package/src/design-facts.js +89 -20
- package/src/docs-gate.js +105 -6
- package/src/doctor-diagnostics.js +224 -2
- package/src/friction-ledger.js +171 -0
- package/src/hooks/worktree-guard.js +13 -1
- package/src/index.js +206 -18
- package/src/knowledge-classify.js +382 -0
- package/src/knowledge-hits.js +74 -0
- package/src/knowledge-stats.js +180 -0
- package/src/probe7-anchor-check.js +69 -0
- package/src/progress/change-registry.js +183 -8
- package/src/progress/shared.js +3 -2
- package/src/progress.js +100 -8
- package/src/quick-gate-profile.js +251 -0
- package/src/quicklog.js +164 -18
- package/src/review-dispatch.js +2 -1
- package/src/run/command.js +131 -13
- package/src/run/complete-handlers.js +511 -21
- package/src/run/complete.js +123 -23
- package/src/run/concurrent-detect.js +98 -0
- package/src/run/gate-snapshot.js +110 -2
- package/src/run/gates.js +141 -16
- package/src/run/prompt.js +221 -11
- package/src/run/quick-audit.js +111 -13
- package/src/run/shared.js +147 -11
- package/src/run/stage.js +6 -0
- package/src/scan-diff.js +61 -30
- package/src/scan-postcheck.js +74 -0
- package/src/scan-refresh.js +477 -0
- package/src/scan-staleness.js +25 -13
- package/src/scope-audit.js +456 -56
- package/src/stage-contract.js +185 -2
- package/src/stage-review.js +23 -7
- package/src/stages/brainstorm.js +6 -0
- package/src/stages/cmd-existence.js +9 -3
- package/src/stages/execute.js +85 -3
- package/src/stages/knowledge.js +76 -6
- package/src/stages/plan-postcheck.js +32 -5
- package/src/stages/quick.js +1 -1
- package/src/stages/verify.js +1 -0
- package/src/sync-noise.js +170 -6
- package/src/sync.js +141 -9
- package/src/task-review.js +366 -46
- package/src/verify-postcheck.js +212 -55
- package/src/verify-probes.js +727 -16
- package/src/worktree-apply.js +685 -48
- package/src/worktree-deps.js +124 -3
- package/src/worktree.js +391 -15
- package/templates/agents-instruction.md +2 -2
- package/templates/prompts/testcase-design.md +2 -1
- package/templates/prompts/verify-probes.md +4 -1
- package/.patch16-tmp.mjs +0 -169
- package/sa-rename-probe/src/new.js +0 -4
package/src/run/gates.js
CHANGED
|
@@ -27,7 +27,7 @@ import { writeAtomicSync } from '../fs-atomic.js'
|
|
|
27
27
|
import { triggerSync, resolveChangeDir, resolveRuntimeRoot } from './shared.js'
|
|
28
28
|
import { runValidators } from '../stage-contract.js'
|
|
29
29
|
import { handleScanStageCompleted, handleExecuteWorktreeCleanup } from './complete-handlers.js'
|
|
30
|
-
import { detectConcurrentChanges, formatConcurrentWarning } from './concurrent-detect.js'
|
|
30
|
+
import { detectConcurrentChanges, formatConcurrentWarning, detectCommittedDrift, formatCommittedDriftWarning } from './concurrent-detect.js'
|
|
31
31
|
import { stageRegistry } from '../stages/index.js'
|
|
32
32
|
import { normalizeTaskId } from '../taskcard.js'
|
|
33
33
|
import { recordFrictionEvent } from '../friction-tally.js'
|
|
@@ -322,12 +322,14 @@ export async function enforceDepsGate(stageName, cwd, changeName, step, steps, c
|
|
|
322
322
|
// 下游已有物理目录存在性判定兜底(G2/R3),这里 warn 留下根因线索,避免静默误诊。
|
|
323
323
|
console.warn(`⚠️ worktree meta 读取失败(deps gate 将走物理目录判定): ${e.message}`)
|
|
324
324
|
}
|
|
325
|
-
const
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
325
|
+
const depsOk = (s) => ['linked', 'installed', 'n/a'].includes(s)
|
|
326
|
+
const changeDir = changeName ? join(specBase, 'changes', changeName) : null
|
|
327
|
+
|
|
328
|
+
// main 侧就绪后的跨仓 worktree deps 校验(坑 cross-repo-no-worktree-isolation):
|
|
329
|
+
// 跨仓仓供给失败同样无构建/测试能力(前端仓无 node_modules 建不了 build)。只校验已建
|
|
330
|
+
// worktree 的跨仓仓(legacy 直写模式无 meta,维持原语义,下游 Task Review 兜底)。
|
|
331
|
+
// 放行返回 true;跨仓未就绪置 blocked + exit(1)。
|
|
332
|
+
const crossCheckOrExit = async () => {
|
|
331
333
|
let crossFailed = []
|
|
332
334
|
try {
|
|
333
335
|
const crossSpecBase = platformOpts?.specRoot || join(cwd, '.sillyspec')
|
|
@@ -351,8 +353,39 @@ export async function enforceDepsGate(stageName, cwd, changeName, step, steps, c
|
|
|
351
353
|
if (persist) { try { await persist() } catch { /* 落盘失败不吞阻断语义 */ } }
|
|
352
354
|
process.exit(1)
|
|
353
355
|
}
|
|
354
|
-
|
|
356
|
+
|
|
357
|
+
if (depsOk(meta?.depsStatus)) return await crossCheckOrExit()
|
|
358
|
+
|
|
359
|
+
// wave 级 opt-out(先于重供给:无需 deps 能力的 wave 不花 install 成本)
|
|
355
360
|
if (isCurrentWaveAllNoDepsVerify(step?.name, changeDir)) return true
|
|
361
|
+
|
|
362
|
+
// ── --done 重供给重试(坑 deps-gate-manual-retry-ineffective,2026-09-15 wp 会话实证)──
|
|
363
|
+
// 阻断提示承诺「在 worktree 内手动安装依赖后重试」,但 manual install 不改 meta.depsStatus,
|
|
364
|
+
// failed 态直读 meta 永不翻绿 → 重试 --done 必撞同一堵墙,唯一出路 doctor --fix(wp 会话
|
|
365
|
+
// 正是手动编译成功后重试仍被拦,被迫多跑一轮 doctor)。对齐 run execute 入口
|
|
366
|
+
// ensureDepsFreshness 的同款供给:阻断前重供给一次并写回 meta——PATH 已修好的 shell 里
|
|
367
|
+
// 重跑 --done 即等效 doctor --fix;marker 型生态(nodejs)手动安装补齐 node_modules 后
|
|
368
|
+
// 重供给的 install 也能自然通过。仍失败则维持阻断,不无限重试。
|
|
369
|
+
let depsStatus = meta?.depsStatus
|
|
370
|
+
if (wm && changeName) {
|
|
371
|
+
let wtPath = null
|
|
372
|
+
try { wtPath = wm.getWorktreePath(changeName) } catch { /* meta 缺失走物理目录判定 */ }
|
|
373
|
+
if (wtPath && existsSync(wtPath)) {
|
|
374
|
+
try {
|
|
375
|
+
const { provisionDeps } = await import('../worktree-deps.js')
|
|
376
|
+
console.log(`🔄 deps 门控:depsStatus=${depsStatus || 'unknown'},阻断前按 --done 重试路径重供给一次(与 doctor --fix 同款)...`)
|
|
377
|
+
const deps = provisionDeps(wtPath, cwd, { specBase }) || {}
|
|
378
|
+
meta = { ...meta, ...deps }
|
|
379
|
+
writeAtomicSync(join(wtPath, 'meta.json'), JSON.stringify(meta, null, 2) + '\n')
|
|
380
|
+
depsStatus = meta.depsStatus
|
|
381
|
+
console.log(` 重供给完成:depsStatus=${depsStatus || 'unknown'}${meta.depsError ? `(${meta.depsError})` : ''}`)
|
|
382
|
+
if (depsOk(depsStatus)) return await crossCheckOrExit()
|
|
383
|
+
} catch (e) {
|
|
384
|
+
console.warn(`⚠️ deps 门控重供给失败(维持阻断判定):${e.message}`)
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
356
389
|
if (steps && steps[currentIdx]) {
|
|
357
390
|
steps[currentIdx].status = 'blocked'
|
|
358
391
|
// 阻断原因持久化(坑 deps-gate-blocked-invisible):--status/诊断时可见,不依赖阻断当刻的
|
|
@@ -643,6 +676,28 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
|
|
|
643
676
|
// 再由 CLI 亲自执行 local.yaml 的测试命令,与 verify-result.md 的自报告对账:
|
|
644
677
|
// 自报告 PASS 但实测失败 → 阻断(防止"文案通过"绕过验证)。
|
|
645
678
|
if (stageName === 'verify') {
|
|
679
|
+
// 坑 mixed-baseline-drift-hint(ql-20260915-004):verify 实测门执行前的混合基线 advisory
|
|
680
|
+
//(提前预警不阻断)——apply-manifest 的基点后主仓已提交推进若触及本变更相关文件(含
|
|
681
|
+
// .test. 变体),worktree 快照内验证过的断言可能漂移,本次实测红先做合并态归因。推进面
|
|
682
|
+
// 剔除本变更自身文件面(apply 已提交时 baseHash..HEAD 含自身交付,不剔会自污染误报);
|
|
683
|
+
// 剔除后精确命中信号由 apply 时点提示兜底,此处保变体方向(他者推进我源文件的测试变体 /
|
|
684
|
+
// 我测试文件对应的源文件)。无 manifest(in-place/quick)→ no-op 零行为变化。
|
|
685
|
+
try {
|
|
686
|
+
const driftManifestPath = join(specBase, 'changes', changeName, 'apply-manifest.json')
|
|
687
|
+
if (existsSync(driftManifestPath)) {
|
|
688
|
+
const driftManifest = JSON.parse(readFileSync(driftManifestPath, 'utf8'))
|
|
689
|
+
const driftFace = (driftManifest && Array.isArray(driftManifest.files)
|
|
690
|
+
? driftManifest.files.map(f => f && f.path).filter(Boolean) : [])
|
|
691
|
+
if (driftManifest && driftManifest.baseHash && driftFace.length > 0) {
|
|
692
|
+
const drift = detectCommittedDrift({
|
|
693
|
+
projectRoot: cwd, baseHash: driftManifest.baseHash,
|
|
694
|
+
touchedFiles: driftFace, excludeFiles: driftFace,
|
|
695
|
+
})
|
|
696
|
+
const driftMsg = formatCommittedDriftWarning(drift, { tailNote: '本次实测若红,先做合并态归因' })
|
|
697
|
+
if (driftMsg) console.warn(driftMsg)
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
} catch { /* fail-soft:drift 检测异常不影响 verify 门 */ }
|
|
646
701
|
const { runVerifyTestCheck, printVerifyTestCheck } = await import('../verify-postcheck.js')
|
|
647
702
|
// P0-1 指纹复用(noai-ir-roadmap §3 前置一·防重复跑):noAI 质量扫描步已实测且代码指纹
|
|
648
703
|
// 未变 → 直接复用免重跑(长套件 2~10 分钟不再跑两遍);无记录/失配/git 不可用 → 照旧
|
|
@@ -823,6 +878,11 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
|
|
|
823
878
|
})
|
|
824
879
|
const reconcileEnvelope = buildReconcileTargetFilesEnvelope(reconcileCheck)
|
|
825
880
|
const reconcileBlocked = printReconcileTargetFilesCheck(reconcileCheck, reconcileEnvelope)
|
|
881
|
+
// 跨仓 per-repo 对账明细渲染(坑 cross-repo-reconcile-blindness,2026-09-15 复盘兑现 D-004
|
|
882
|
+
// 分期):reconcileTargetFiles 的 crossRepo 字段(advisory 不阻断)——各仓 对上/②缺/③多
|
|
883
|
+
// 逐仓一行 + ②缺明细(跨仓声明落空是该去对应仓干活的信号,但不翻主仓状态——锚点窗口
|
|
884
|
+
// 脆弱期会假信号)。无跨仓卡时 crossRepo 为空数组零输出。
|
|
885
|
+
printCrossRepoReconcile(reconcileCheck.crossRepo)
|
|
826
886
|
// 结果落盘(design Wave 2 承诺:对齐 .runtime/verify-runs/<ts>/ 先例)——五状态全落(含
|
|
827
887
|
// 放行态),fail-soft 不影响 gate 判定;②类阻断回执同样留档供平台/审计消费
|
|
828
888
|
writeReconcileRunResult({ runtimeRoot: reconcileRuntimeRoot, changeName, envelope: reconcileEnvelope, result: reconcileCheck })
|
|
@@ -850,6 +910,23 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
|
|
|
850
910
|
})
|
|
851
911
|
const probeEnvelope = buildProbeConsistencyEnvelope(probeCheck)
|
|
852
912
|
const probeBlocked = printProbeConsistencyCheck(probeCheck, probeEnvelope)
|
|
913
|
+
// 探针7 covered 证据锚点 advisory(坑 probe7-covered-anchor-missing,2026-09-15 复盘:7 行
|
|
914
|
+
// covered 证据缺 file:line 被审查打回——预填说明已载明口径,机器在 --done 时点再拦一道,
|
|
915
|
+
// 省一轮人工往返)。advisory 不阻断;独立模块零环(verify-probes 是多会话冲突面,不碰)。
|
|
916
|
+
try {
|
|
917
|
+
const { checkProbe7AnchorCoverage } = await import('../probe7-anchor-check.js')
|
|
918
|
+
const p7ReportPath = join(specBase, 'changes', changeName, 'verify-result.md')
|
|
919
|
+
if (existsSync(p7ReportPath)) {
|
|
920
|
+
const p7Anchor = checkProbe7AnchorCoverage(readFileSync(p7ReportPath, 'utf8'))
|
|
921
|
+
if (p7Anchor.applicable && p7Anchor.missingAnchors.length > 0) {
|
|
922
|
+
console.warn(`\n⚠️ 探针7 锚点校验:${p7Anchor.missingAnchors.length}/${p7Anchor.coveredRows} 行判定为 covered 但证据列缺 file:line 锚点(advisory 不阻断,审查会要求回补):`)
|
|
923
|
+
for (const m of p7Anchor.missingAnchors.slice(0, 10)) {
|
|
924
|
+
console.warn(` - ${m.task}:${m.acceptance || '(空 acceptance)'}——证据当前为「${m.evidence || '(空)'}」,应给 \`测试文件路径:行号\` 首命中锚点`)
|
|
925
|
+
}
|
|
926
|
+
if (p7Anchor.missingAnchors.length > 10) console.warn(` …还有 ${p7Anchor.missingAnchors.length - 10} 行`)
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
} catch { /* 锚点校验异常 fail-soft 不影响 gate */ }
|
|
853
930
|
// 结果落盘(对齐 writeReconcileRunResult 先例):独立文件 probe-consistency-result.json——
|
|
854
931
|
// 与 reconcile-result.json 同 gate 运行同目录组织(verify-runs/<ts>/),但不并入后者:
|
|
855
932
|
// reconcile-result.json 的 schema 已被 P3a 消费方锁定,塞探针字段(读改写/扩参)只会引入
|
|
@@ -1151,13 +1228,17 @@ export function readDesignScale(specBase, changeName) {
|
|
|
1151
1228
|
/**
|
|
1152
1229
|
* 校验变更目录下近 10 分钟新增的 .md/.yaml/.yml 文件含 author/created_at 元数据(advisory 打印)。
|
|
1153
1230
|
* 从 complete.js 迁入(completeStageGates 共享收尾管线消费)。
|
|
1231
|
+
* 按变更隔离(2026-09-14 quick,用户反馈③):带 changeName 时本变更目录的缺元数据文件逐个
|
|
1232
|
+
* 列出;其他变更目录的命中折叠为一行汇总并点名目录与计数——多 agent 并发仓里并行会话的他者
|
|
1233
|
+
* 文件不再逐条混入本变更收尾输出(同批后续变更包也走汇总行,agent 按目录名自认领)。
|
|
1234
|
+
* changeName 缺省(历史调用面)保持全量逐列。
|
|
1154
1235
|
*/
|
|
1155
|
-
export function validateMetadata(cwd, stageName, specBase) {
|
|
1236
|
+
export function validateMetadata(cwd, stageName, specBase, changeName) {
|
|
1156
1237
|
const changesDir = join(specBase, 'changes')
|
|
1157
1238
|
if (!existsSync(changesDir)) return
|
|
1158
1239
|
|
|
1159
1240
|
const cutoff = Date.now() - 10 * 60 * 1000
|
|
1160
|
-
const missing =
|
|
1241
|
+
const missing = new Set()
|
|
1161
1242
|
|
|
1162
1243
|
function walk(dir) {
|
|
1163
1244
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
@@ -1168,19 +1249,34 @@ export function validateMetadata(cwd, stageName, specBase) {
|
|
|
1168
1249
|
const mtime = statSync(full).mtimeMs
|
|
1169
1250
|
if (mtime < cutoff) continue
|
|
1170
1251
|
const content = readFileSync(full, 'utf-8')
|
|
1171
|
-
if (!content.includes('author:') && !content.includes('author:')) missing.
|
|
1172
|
-
if (!content.includes('created_at:') && !content.includes('created_at:')) missing.
|
|
1252
|
+
if (!content.includes('author:') && !content.includes('author:')) missing.add(full)
|
|
1253
|
+
if (!content.includes('created_at:') && !content.includes('created_at:')) missing.add(full)
|
|
1173
1254
|
} catch (e) { /* skip unreadable files */ }
|
|
1174
1255
|
}
|
|
1175
1256
|
}
|
|
1176
1257
|
|
|
1177
1258
|
walk(changesDir)
|
|
1178
|
-
|
|
1179
|
-
|
|
1259
|
+
if (missing.size === 0) return
|
|
1260
|
+
|
|
1261
|
+
// 归属分组:changes/ 下第一段目录名([\\/] 切分兼容 Windows join 产物的反斜杠)
|
|
1262
|
+
const current = []
|
|
1263
|
+
const otherCounts = new Map()
|
|
1264
|
+
for (const f of missing) {
|
|
1265
|
+
const top = relative(changesDir, f).split(/[\\/]/)[0]
|
|
1266
|
+
if (!changeName || top === changeName) current.push(f)
|
|
1267
|
+
else otherCounts.set(top, (otherCounts.get(top) || 0) + 1)
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
if (current.length > 0) {
|
|
1180
1271
|
console.log(`\n⚠️ 以下文件缺少 author 或 created_at 元数据:`)
|
|
1181
|
-
|
|
1272
|
+
current.forEach(f => console.log(` - ${relative(cwd, f) || f}`))
|
|
1182
1273
|
console.log('请在文件头部添加 author(git 用户名)和 created_at(精确到秒)')
|
|
1183
1274
|
}
|
|
1275
|
+
if (otherCounts.size > 0) {
|
|
1276
|
+
const parts = [...otherCounts.entries()].map(([dir, n]) => `${dir}×${n}`).join('、')
|
|
1277
|
+
const total = [...otherCounts.values()].reduce((a, b) => a + b, 0)
|
|
1278
|
+
console.log(`\nℹ️ 另有 ${total} 个其他变更目录的文件近 10 分钟新增且缺元数据(${parts})——已按变更隔离折叠:并行会话产物请忽略;若为本步骤创建的同批变更包,请补 author/created_at`)
|
|
1279
|
+
}
|
|
1184
1280
|
}
|
|
1185
1281
|
|
|
1186
1282
|
/**
|
|
@@ -1339,7 +1435,7 @@ export async function completeStageGates({ stageName, cwd, changeName, platformO
|
|
|
1339
1435
|
const settledCount = steps.filter(s => s.status === 'completed' || s.status === 'skipped').length
|
|
1340
1436
|
const total = steps.length
|
|
1341
1437
|
|
|
1342
|
-
validateMetadata(cwd, stageName, specBase)
|
|
1438
|
+
validateMetadata(cwd, stageName, specBase, changeName)
|
|
1343
1439
|
|
|
1344
1440
|
// 验证关键文件位置(仅当所有步骤已结案 completed‖skipped 时才校验)
|
|
1345
1441
|
if (settledCount === total && total > 0) {
|
|
@@ -1513,6 +1609,35 @@ export function writeReconcileRunResult({ runtimeRoot, changeName, envelope, res
|
|
|
1513
1609
|
* @param {object} [envelope] 预构造信封(缺省现算;显式传入便于调用方先断言再打印)
|
|
1514
1610
|
* @returns {boolean} true = 阻断(status='missing_declared')
|
|
1515
1611
|
*/
|
|
1612
|
+
/**
|
|
1613
|
+
* 打印跨仓 per-repo 对账结果(cross-repo-reconcile.js 产出的 advisory 明细,verify 块接线)。
|
|
1614
|
+
* 恒不阻断(返回 void):跨仓 diff 锚点=各仓最近提交窗口,锚点脆弱期(已 commit 多笔/全在
|
|
1615
|
+
* working-tree)会产②类假信号——明细供人工裁决,不翻主仓对账状态。
|
|
1616
|
+
* @param {Array<{repo:string,repoPath:string|null,declaredCount:number,actualCount:number,
|
|
1617
|
+
* matched:string[],missing:Array<{task:string,path:string,isNew?:boolean}>,
|
|
1618
|
+
* undeclared:string[],scaffoldCount:number,degradedReason:string|null}>|undefined} crossRepo
|
|
1619
|
+
*/
|
|
1620
|
+
function printCrossRepoReconcile(crossRepo) {
|
|
1621
|
+
if (!Array.isArray(crossRepo) || crossRepo.length === 0) return
|
|
1622
|
+
console.log(`\n🔀 跨仓 per-repo 对账(advisory 不阻断,锚点=各仓最近提交窗口):`)
|
|
1623
|
+
for (const r of crossRepo) {
|
|
1624
|
+
if (r.degradedReason) {
|
|
1625
|
+
console.warn(` ⚠️ ${r.repo}:${r.degradedReason}`)
|
|
1626
|
+
continue
|
|
1627
|
+
}
|
|
1628
|
+
const flag = (r.missing.length > 0 || r.undeclared.length > 0) ? '⚠️ ' : '✅ '
|
|
1629
|
+
console.log(` ${flag}${r.repo}(${r.repoPath}):声明 ${r.declaredCount} / 实测 ${r.actualCount} / 对上 ${r.matched.length} / ②缺 ${r.missing.length} / ③多 ${r.undeclared.length}${r.scaffoldCount > 0 ? ` / 脚手架聚合 ${r.scaffoldCount}` : ''}`)
|
|
1630
|
+
for (const m of r.missing.slice(0, 10)) {
|
|
1631
|
+
console.warn(` ②缺 ${m.task}: ${m.path}${m.isNew ? '(NEW: 声明新建,该仓未见)' : ''}——锚点窗口外已提交或多笔时可能假信号,到该仓 git log 核实`)
|
|
1632
|
+
}
|
|
1633
|
+
if (r.missing.length > 10) console.warn(` …还有 ${r.missing.length - 10} 条②缺`)
|
|
1634
|
+
for (const u of r.undeclared.slice(0, 10)) {
|
|
1635
|
+
console.warn(` ③多 ${u}(该仓最近窗口实际改动,未在任何跨仓卡声明)`)
|
|
1636
|
+
}
|
|
1637
|
+
if (r.undeclared.length > 10) console.warn(` …还有 ${r.undeclared.length - 10} 条③多`)
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1516
1641
|
export function printReconcileTargetFilesCheck(r, envelope = buildReconcileTargetFilesEnvelope(r)) {
|
|
1517
1642
|
// ②类(声明未做=计划落空):ERROR 阻断
|
|
1518
1643
|
if (r.status === 'missing_declared') {
|
package/src/run/prompt.js
CHANGED
|
@@ -29,6 +29,11 @@ import { nowWallClock } from '../datetime.js'
|
|
|
29
29
|
import { parseModuleMapSimple } from '../modules.js'
|
|
30
30
|
import { readModuleRecentChanges } from '../module-changelog.js'
|
|
31
31
|
import { REVIEW_SCHEMA_VERSION, isValidExecuteRunId } from '../task-review.js'
|
|
32
|
+
// 机械知识注入(2026-09-14-knowledge-loop-close task-04,FR-04):两者均为 fs/path 叶子模块,
|
|
33
|
+
// 静态 import 不引入环(stages/* 反向引用本文件会撞 index.js 顶层 stageRegistry TDZ——见
|
|
34
|
+
// buildKnowledgeInjection docstring)。
|
|
35
|
+
import { matchKnowledge } from '../knowledge-match.js'
|
|
36
|
+
import { appendKnowledgeHit } from '../knowledge-hits.js'
|
|
32
37
|
|
|
33
38
|
/**
|
|
34
39
|
* 从 _module-map.yaml 读取模块上下文索引
|
|
@@ -220,8 +225,159 @@ export function buildQuickSemanticGuardInjection({ specBase, cwd, changeName } =
|
|
|
220
225
|
}
|
|
221
226
|
}
|
|
222
227
|
|
|
228
|
+
// ── 机械知识注入共享底座(2026-09-14-knowledge-loop-close task-04,FR-04 / D-002@v1 总体方案 D)──
|
|
229
|
+
// 知识消费从 report 级升级为正文级:matchKnowledge 命中 → 按 entries 出现序(INDEX 行序)取
|
|
230
|
+
// 前 3 个不同 file,单文件首 40 行截断 + 截断标记,渲染「📚 命中知识」段(R-02 膨胀控制);
|
|
231
|
+
// 未命中 section=''(调用方零字节零变化)。命中同时经 task-01 的 appendKnowledgeHit 落
|
|
232
|
+
// .runtime/knowledge-hits.jsonl 一条 type:inject 记录;既有 knowledge-hit-report.json 由
|
|
233
|
+
// execute 调用方照旧落盘(X-004 升级既有机制而非并行新建,新旧遥测共存)。
|
|
234
|
+
//
|
|
235
|
+
// ⚠️ 同款格式在 src/stages/execute.js buildWavePrompt 内有本地孪生实现(Wave 粒度注入)——
|
|
236
|
+
// 本文件无法被 execute.js 静态 import(prompt.js → stages/index.js → execute.js 既有依赖方向,
|
|
237
|
+
// 反向边会在 execute.js 作模块图入口时触发 index.js 顶层 stageRegistry 对其 definition const 的
|
|
238
|
+
// TDZ 求值,2026-09-14 ESM 环实证)。格式漂移由 test/knowledge-inject.test.mjs 的格式等价断言
|
|
239
|
+
// 锁定——改此处必同步改孪生。
|
|
240
|
+
export const KNOWLEDGE_INJECT_MAX_FILES = 3
|
|
241
|
+
export const KNOWLEDGE_INJECT_MAX_LINES = 40
|
|
242
|
+
|
|
243
|
+
/** top-N 选取:按 entries 出现序(INDEX 行序)取前 N 个不同 file(matchKnowledge 布尔 filter
|
|
244
|
+
* 无相关度排序,X-009——出现序即唯一稳定序),每 file 取首个命中 entry 作代表。 */
|
|
245
|
+
function pickTopKnowledgeEntries(entries, maxFiles) {
|
|
246
|
+
const seen = new Set()
|
|
247
|
+
const picked = []
|
|
248
|
+
for (const e of entries) {
|
|
249
|
+
if (seen.has(e.file)) continue
|
|
250
|
+
seen.add(e.file)
|
|
251
|
+
picked.push(e)
|
|
252
|
+
if (picked.length >= maxFiles) break
|
|
253
|
+
}
|
|
254
|
+
return picked
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** 命中正文段渲染(段头 + Status/Sources 命中报告语义 + top-3 文件截断正文)。 */
|
|
258
|
+
function renderKnowledgeInjectSection(knowledgeResult, { knowledgeDir, maxFiles, maxLines }) {
|
|
259
|
+
const ref = e => (e.anchor ? `${e.file}#${e.anchor}` : e.file)
|
|
260
|
+
const lines = []
|
|
261
|
+
lines.push(`📚 命中知识(CLI 按任务描述机械匹配,top-${maxFiles})`)
|
|
262
|
+
lines.push(`Status: matched | Entries: ${knowledgeResult.entries.length} | Sources:`)
|
|
263
|
+
for (const e of knowledgeResult.entries) lines.push(` - ${ref(e)}`)
|
|
264
|
+
lines.push(`(命中清单如上;正文按 INDEX 行序注入前 ${maxFiles} 个不同文件,单文件首 ${maxLines} 行截断——未注入条目需要时按 Sources 路径自行读取)`)
|
|
265
|
+
for (const e of pickTopKnowledgeEntries(knowledgeResult.entries, maxFiles)) {
|
|
266
|
+
lines.push('')
|
|
267
|
+
lines.push(`── ${ref(e)}${e.display ? `(${e.display})` : ''} ──`)
|
|
268
|
+
let body = ''
|
|
269
|
+
try {
|
|
270
|
+
// CRLF/CR 归一(Windows 知识文件 + 行数截断按 \n 切分)
|
|
271
|
+
body = readFileSync(join(knowledgeDir, e.file), 'utf8').replace(/\r\n?/g, '\n')
|
|
272
|
+
} catch (err) {
|
|
273
|
+
lines.push(`(文件不可读:${err && err.message ? err.message : err})`)
|
|
274
|
+
continue
|
|
275
|
+
}
|
|
276
|
+
const bodyLines = body.split('\n')
|
|
277
|
+
if (bodyLines.length > maxLines) {
|
|
278
|
+
lines.push(bodyLines.slice(0, maxLines).join('\n'))
|
|
279
|
+
lines.push('…(截断)')
|
|
280
|
+
} else {
|
|
281
|
+
lines.push(body.replace(/\n+$/, ''))
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return lines.join('\n')
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* 机械知识注入一站式入口(execute「确认执行范围」step 与 quick step1 两个 prompt.js 内注入点共用)。
|
|
289
|
+
*
|
|
290
|
+
* @param {object} opts
|
|
291
|
+
* @param {string} opts.knowledgeDir - knowledge 目录(INDEX.md 所在)
|
|
292
|
+
* @param {string} opts.runtimeDir - .runtime 目录(hits.jsonl 落点)
|
|
293
|
+
* @param {string} opts.change - 变更名 / quick sessionId(进遥测记录 change 字段)
|
|
294
|
+
* @param {string} opts.query - 匹配查询串(execute: changeName+tasks.md 任务行;quick: guard.taskDescription)
|
|
295
|
+
* @returns {{matched: boolean, section: string, report: string, json: object}}
|
|
296
|
+
* section:命中时的「📚 命中知识」段(含段头与正文);未命中恒 ''(调用方零字节)。
|
|
297
|
+
* report/json:matchKnowledge 原样透传(execute 调用方沿用旧 report.json 落盘与未命中替换口径)。
|
|
298
|
+
* 遥测 append 单独 fail-soft(hits 写失败不影响注入本体);匹配/渲染异常向上抛由调用方降级。
|
|
299
|
+
*/
|
|
300
|
+
export function buildKnowledgeInjection({ knowledgeDir, runtimeDir, change, query, maxFiles = KNOWLEDGE_INJECT_MAX_FILES, maxLines = KNOWLEDGE_INJECT_MAX_LINES } = {}) {
|
|
301
|
+
const knowledgeResult = matchKnowledge(knowledgeDir, String(query || ''))
|
|
302
|
+
if (!knowledgeResult.matched) {
|
|
303
|
+
return { matched: false, section: '', report: knowledgeResult.report, json: knowledgeResult.json }
|
|
304
|
+
}
|
|
305
|
+
const section = renderKnowledgeInjectSection(knowledgeResult, { knowledgeDir, maxFiles, maxLines })
|
|
306
|
+
try {
|
|
307
|
+
appendKnowledgeHit(runtimeDir, {
|
|
308
|
+
type: 'inject',
|
|
309
|
+
change: change || '',
|
|
310
|
+
query: String(query || ''),
|
|
311
|
+
matchedFiles: knowledgeResult.entries.map(e => (e.anchor ? `${e.file}#${e.anchor}` : e.file)),
|
|
312
|
+
})
|
|
313
|
+
} catch { /* 遥测 fail-soft(R-04):hits 落盘失败不阻断注入本体 */ }
|
|
314
|
+
return { matched: true, section, report: knowledgeResult.report, json: knowledgeResult.json }
|
|
315
|
+
}
|
|
316
|
+
|
|
223
317
|
// parseModuleMapSimple 复用 modules.js 的 canonical 实现(合并历史 copy-paste 副本,2026-08-07;
|
|
224
318
|
// 无循环依赖:modules.js 仅 import fs/path/db.js,prompt.js → modules.js 单向)。
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* 等效验证先例库(坑 verify-precedent-only-in-prose,2026-09-15 EHS 生产实证:mvn test 被
|
|
322
|
+
* 公司框架 parent pom pluginManagement 硬编码 surefire skip=true、-D 覆盖无效——等效口径
|
|
323
|
+
* dependency:build-classpath+javac+JUnitCore 第二次复用靠 agent 翻旧 verify-result.md 正文
|
|
324
|
+
* 续命;先例只活在散文里 = 换个 agent/换个变更就断档)。结构化登记进 local.yaml
|
|
325
|
+
* verify_precedents 段,verify prompt 时点注入提示(与 test_strategy 预检提示同挂点)。
|
|
326
|
+
*
|
|
327
|
+
* 对象数组形态(行扫描,同 extractKnownFailures 免 YAML 依赖口径——注释/空行/块界容错):
|
|
328
|
+
* verify_precedents:
|
|
329
|
+
* - id: mvn-test-surefire-skip
|
|
330
|
+
* standard_command: mvn test
|
|
331
|
+
* reason: <标准命令为何不可用>
|
|
332
|
+
* equivalent: <已验证的等效口径>
|
|
333
|
+
* established_by: <确立该口径的变更名>
|
|
334
|
+
* notes: <可选补充>
|
|
335
|
+
* 残项(standard_command 与 equivalent 双缺)过滤——登记不完整的先例没有可执行口径。
|
|
336
|
+
* @param {string|null} yamlText local.yaml 全文
|
|
337
|
+
* @returns {Array<object>} 先例对象数组
|
|
338
|
+
*/
|
|
339
|
+
export function parseVerifyPrecedents(yamlText) {
|
|
340
|
+
const lines = String(yamlText || '').split(/\r?\n/)
|
|
341
|
+
const headIdx = lines.findIndex(l => /^verify_precedents:\s*(?:#.*)?$/.test(l))
|
|
342
|
+
if (headIdx < 0) return []
|
|
343
|
+
const items = []
|
|
344
|
+
let cur = null
|
|
345
|
+
const clean = (v) => String(v || '').replace(/\s+#.*$/, '').trim()
|
|
346
|
+
for (let i = headIdx + 1; i < lines.length; i++) {
|
|
347
|
+
const l = lines[i]
|
|
348
|
+
if (/^\S/.test(l)) break // 下一个顶层键 → 块结束
|
|
349
|
+
if (!l.trim() || /^\s*#/.test(l)) continue
|
|
350
|
+
const item = l.match(/^\s*-\s+([A-Za-z_][\w-]*):\s*(.*)$/)
|
|
351
|
+
if (item) {
|
|
352
|
+
cur = {}
|
|
353
|
+
items.push(cur)
|
|
354
|
+
cur[item[1]] = clean(item[2])
|
|
355
|
+
continue
|
|
356
|
+
}
|
|
357
|
+
const kv = l.match(/^\s*([A-Za-z_][\w-]*):\s*(.*)$/)
|
|
358
|
+
if (kv && cur) cur[kv[1]] = clean(kv[2])
|
|
359
|
+
}
|
|
360
|
+
return items.filter(x => x && (x.standard_command || x.equivalent))
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* 先例提示渲染(verify prompt 注入用;空输入 → '',调用方零输出)。
|
|
365
|
+
* 行动指引刻意指向「把等效命令配进 commands.test 照常全量实测对账」而非 test_strategy: skip
|
|
366
|
+
* 放行——EHS 实证里被环境阻断 deferred 的集成测试正是 P1 缺陷藏身处,能实测就实测。
|
|
367
|
+
* @param {Array<object>|null} precedents
|
|
368
|
+
* @returns {string}
|
|
369
|
+
*/
|
|
370
|
+
export function renderVerifyPrecedentHint(precedents) {
|
|
371
|
+
if (!Array.isArray(precedents) || precedents.length === 0) return ''
|
|
372
|
+
const L = [`【验证先例提示】本仓已登记 ${precedents.length} 条等效验证先例(标准命令不可用时的既有替代口径):`]
|
|
373
|
+
for (const p of precedents.slice(0, 5)) {
|
|
374
|
+
L.push(`- ${p.id || '(无 id)'}:\`${p.standard_command || '?'}\` 不可用(${p.reason || '原因未注'})→ 等效口径 \`${p.equivalent || '?'}\`(确立于 ${p.established_by || '?'}${p.notes ? `;${p.notes}` : ''})`)
|
|
375
|
+
}
|
|
376
|
+
if (precedents.length > 5) L.push(`- …另有 ${precedents.length - 5} 条未展开(读 local.yaml verify_precedents 段看全量)`)
|
|
377
|
+
L.push('适用时把等效命令配进 local.yaml commands.test(或 modules.<m>.test)后照常全量实测对账,而不是只靠 test_strategy: skip 放行;先例不适用(环境已变/口径失效)可忽略并在 verify-result 注记。')
|
|
378
|
+
return L.join('\n')
|
|
379
|
+
}
|
|
380
|
+
|
|
225
381
|
/**
|
|
226
382
|
* 提示词路径根的单一解析:specRoot(平台) > specDriftAnchor(worktree 漂移锚定主仓) > cwd/.sillyspec(本地)。
|
|
227
383
|
* 治 execute review.json 提示路径写 worktree 副本、--done 校验读主仓的分裂(坑 execute-prompt-spec-drift):
|
|
@@ -754,9 +910,13 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
754
910
|
}
|
|
755
911
|
|
|
756
912
|
// Knowledge hit report: execute 阶段注入匹配结果
|
|
913
|
+
// 2026-09-14-knowledge-loop-close task-04(FR-04,X-004 升级既有机制):命中清单报告 → 命中
|
|
914
|
+
// 正文注入——命中时占位符替换为「📚 命中知识」段(top-3 不同 file + 首 40 行截断,段头保留
|
|
915
|
+
// Status/Entries/Sources 命中报告语义),并逐条 appendKnowledgeHit 落 hits.jsonl 遥测;
|
|
916
|
+
// 未命中替换值与升级前字节一致(knowledgeResult.report == 'Status: no matches',零膨胀)。
|
|
917
|
+
// 既有 knowledge-hit-report.json 照旧落盘(未命中也写 matched:false 快照,行为不变)。
|
|
757
918
|
if (stageName === 'execute' && promptText.includes('{KNOWLEDGE_HIT_REPORT}')) {
|
|
758
919
|
try {
|
|
759
|
-
const { matchKnowledge } = await import('../knowledge-match.js')
|
|
760
920
|
const effectiveSpecBase = resolvePromptSpecBase(platformOpts, cwd)
|
|
761
921
|
const knowledgeDir = join(effectiveSpecBase, 'knowledge')
|
|
762
922
|
// taskContext: changeName + tasks.md 任务名(注册表唯一真相,2026-08-20-task-truth-unify)
|
|
@@ -774,12 +934,20 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
774
934
|
}
|
|
775
935
|
} catch {}
|
|
776
936
|
}
|
|
777
|
-
const knowledgeResult = matchKnowledge(knowledgeDir, taskContext)
|
|
778
|
-
promptText = promptText.replace(/\{KNOWLEDGE_HIT_REPORT\}/g, knowledgeResult.report)
|
|
779
|
-
// 写入 runtime JSON
|
|
780
937
|
const runtimeDir = join(effectiveSpecBase, '.runtime')
|
|
938
|
+
const knowledgeInjection = buildKnowledgeInjection({
|
|
939
|
+
knowledgeDir,
|
|
940
|
+
runtimeDir,
|
|
941
|
+
change: changeName || '',
|
|
942
|
+
query: taskContext,
|
|
943
|
+
})
|
|
944
|
+
promptText = promptText.replace(
|
|
945
|
+
/\{KNOWLEDGE_HIT_REPORT\}/g,
|
|
946
|
+
knowledgeInjection.matched ? knowledgeInjection.section : knowledgeInjection.report
|
|
947
|
+
)
|
|
948
|
+
// 写入 runtime JSON(既有机制照旧,新旧遥测共存)
|
|
781
949
|
mkdirSync(runtimeDir, { recursive: true })
|
|
782
|
-
writeAtomicSync(join(runtimeDir, 'knowledge-hit-report.json'), JSON.stringify(
|
|
950
|
+
writeAtomicSync(join(runtimeDir, 'knowledge-hit-report.json'), JSON.stringify(knowledgeInjection.json, null, 2) + '\n')
|
|
783
951
|
} catch (e) {
|
|
784
952
|
promptText = promptText.replace(/\{KNOWLEDGE_HIT_REPORT\}/g, 'Status: no matches (error: ' + e.message + ')')
|
|
785
953
|
}
|
|
@@ -1023,13 +1191,14 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1023
1191
|
// 推荐组合、无从否决——故在 prompt 时点经 {EVIDENCE_AUTO_RECOMMENDATION} 把 task-11
|
|
1024
1192
|
// resolveTestStrategy 的 evidence_auto_recommendation.summary 渲染进 step prompt(含推荐
|
|
1025
1193
|
// 理由、降级注记与「可在 verify-result.md 否决并改跑全量」路径),供用户否决(FR-11)。
|
|
1026
|
-
// 仅 test_strategy=evidence-auto 且 recommendation
|
|
1027
|
-
//
|
|
1194
|
+
// 仅 test_strategy=evidence-auto 且 recommendation 非空时渲染推荐;full/module/skip/已配置
|
|
1195
|
+
// 替换为空串零输出;**未配置**且 commands.test 已配时渲染 test_strategy 预检提示(2026-09-15
|
|
1196
|
+
// 复盘:环境硬伤的全量实测打回返工,提前在 prompt 时点给配置指引)。fail-soft:读取/解析异常降级单行说明不抛,
|
|
1028
1197
|
// 绝不阻断 verify prompt 输出(对齐上方 {WORKTREE_BASELINE_INFO} 注入先例)。
|
|
1029
1198
|
if (stageName === 'verify' && promptText.includes('{EVIDENCE_AUTO_RECOMMENDATION}')) {
|
|
1030
1199
|
let eaInjected = ''
|
|
1031
1200
|
try {
|
|
1032
|
-
const { resolveTestStrategy } = await import('../verify-postcheck.js')
|
|
1201
|
+
const { resolveTestStrategy, extractTestCommand, extractTestStrategy } = await import('../verify-postcheck.js')
|
|
1033
1202
|
const eaSpecBase = resolvePromptSpecBase(platformOpts, cwd)
|
|
1034
1203
|
// 信任边界:local.yaml 只读 specBase(平台 specRoot / 漂移锚定主仓 / cwd 本地),与
|
|
1035
1204
|
// runVerifyTestCheck 的 specBase 来源同口径,不读 agent 可写的 worktree 副本。
|
|
@@ -1038,7 +1207,20 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1038
1207
|
const eaChangeDir = changeName ? join(eaSpecBase, 'changes', changeName) : null
|
|
1039
1208
|
const resolution = resolveTestStrategy({ yamlText: eaYamlText, changeDir: eaChangeDir })
|
|
1040
1209
|
const rec = resolution.evidence_auto_recommendation
|
|
1041
|
-
if (rec && rec.summary)
|
|
1210
|
+
if (rec && rec.summary) {
|
|
1211
|
+
eaInjected = rec.summary
|
|
1212
|
+
} else if (eaYamlText && extractTestCommand(eaYamlText) && !extractTestStrategy(eaYamlText)) {
|
|
1213
|
+
// test_strategy 预检提示(2026-09-15 复盘教训「test_strategy 该在进 verify 前配好,
|
|
1214
|
+
// 而不是被 verify-test gate 打回后才补」):已配 commands.test 但顶层 test_strategy
|
|
1215
|
+
// 未设(缺省=全量实测对账),prompt 时点先给配置指引——环境注定跑不了全量时省一轮
|
|
1216
|
+
// 「--done 实测打回 → 补配置 → 重跑」返工
|
|
1217
|
+
eaInjected = '【test_strategy 预检提示】local.yaml 已配置 commands.test 但未设顶层 test_strategy(缺省=全量实测对账)。若该命令在当前环境无法真实执行(框架/环境硬伤,如测试框架硬编码 skip、依赖外部服务),先在 local.yaml 显式配 `test_strategy: skip`(或 `module` + modules 映射收窄)并注释理由再继续——避免 verify --done 实测对账打回后返工。'
|
|
1218
|
+
}
|
|
1219
|
+
// 等效验证先例注入(verify-precedent-only-in-prose,2026-09-15 EHS 实证):先例与
|
|
1220
|
+
// 预检提示同挂点追加——标准命令不可用但已有验证过的等效口径时,指引优先配进
|
|
1221
|
+
// commands.test 实测对账而非 skip 放行。
|
|
1222
|
+
const _precHint = renderVerifyPrecedentHint(parseVerifyPrecedents(eaYamlText))
|
|
1223
|
+
if (_precHint) eaInjected = eaInjected ? `${eaInjected}\n\n${_precHint}` : _precHint
|
|
1042
1224
|
} catch (e) {
|
|
1043
1225
|
eaInjected = `(evidence-auto 推荐注入失败:${e.message}——请读 .sillyspec/local.yaml 确认 test_strategy;--done 时 CLI 仍会按 resolveTestStrategy 实测对账,推荐不可用时可显式设 test_strategy: full/module)`
|
|
1044
1226
|
}
|
|
@@ -1108,6 +1290,29 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1108
1290
|
}
|
|
1109
1291
|
}
|
|
1110
1292
|
|
|
1293
|
+
// ── 机械知识注入(quick step1,2026-09-14-knowledge-loop-close task-04,FR-04)──
|
|
1294
|
+
// 判定口径与上方模块上下文注入同锚(quickFirstStep):查询串用 guard.taskDescription 现成
|
|
1295
|
+
// 读取(X-008——changeName 是 quick-<hash> 无语义);命中追加「📚 命中知识」段到 prompt 末尾
|
|
1296
|
+
// (top-3 同规则),并 appendKnowledgeHit 落 hits.jsonl;未命中零字节(prompt 与现状字节一致,
|
|
1297
|
+
// 零命中静默)。全链 fail-soft:注入失败不阻断 quick 启动。
|
|
1298
|
+
if (quickFirstStep) {
|
|
1299
|
+
try {
|
|
1300
|
+
const kiSpecBase = resolvePromptSpecBase(platformOpts, cwd)
|
|
1301
|
+
const kiQuery = String(readQuickGuardField(changeName, kiSpecBase, 'taskDescription') || '')
|
|
1302
|
+
if (kiQuery.trim() !== '') {
|
|
1303
|
+
const ki = buildKnowledgeInjection({
|
|
1304
|
+
knowledgeDir: join(kiSpecBase, 'knowledge'),
|
|
1305
|
+
runtimeDir: join(kiSpecBase, '.runtime'),
|
|
1306
|
+
change: changeName || '',
|
|
1307
|
+
query: kiQuery,
|
|
1308
|
+
})
|
|
1309
|
+
if (ki.section) {
|
|
1310
|
+
promptText = promptText + '\n\n' + ki.section
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
} catch { /* fail-soft:知识注入异常不阻断 quick step1 prompt 输出 */ }
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1111
1316
|
// ── 语义护栏进场注入(task-05,FR-03,D-001@v1 模块四)──
|
|
1112
1317
|
// 判定口径独立:与上方模块上下文注入同锚(quickFirstStep——quick step1「理解任务」),不嵌进
|
|
1113
1318
|
// {QUICK_CONTEXT_DIGEST} 占位符守卫内——模板改版去占位符时护栏静默失效(plan 审查 N2)。
|
|
@@ -1223,10 +1428,15 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1223
1428
|
// archive-batch-31-tool-notes ②:通用 --done 模板不带该 flag,agent 照抄执行撞
|
|
1224
1429
|
// 「请添加 --confirm」确认门,误以为参数没带。
|
|
1225
1430
|
const confirmFlag = step.requiresConfirm === true ? ' --confirm' : ''
|
|
1226
|
-
// auto driver(2026-09-08-auto-driver D-002/P1
|
|
1431
|
+
// auto driver(2026-09-08-auto-driver D-002/P1-Ⅲ):auto 模式下命令一律 run auto 形态——
|
|
1227
1432
|
// 与 SS-META 的 doneCommand 同源(同一 cmdStage 变量),消灭「正文 run <stage> / 元数据 run auto」双命令
|
|
1228
1433
|
const cmdStage = autoMeta ? 'auto' : stageName
|
|
1229
|
-
|
|
1434
|
+
// 意图断言(坑 execute-concurrent-done-skips-next-wave 终解):execute 的 Wave 步骤是动态
|
|
1435
|
+
// 步骤表,并发 --done 会落到错误 Wave 且用旧摘要静默完成——完成命令带 --step "<本步名>"
|
|
1436
|
+
//(CLI 校验一致才放行),照抄命令即得并发防护。仅 execute 注入(quick/静态阶段测试断言
|
|
1437
|
+
// 提示格式,且静态步骤名稳定无漂移风险)。
|
|
1438
|
+
const stepAssertFlag = (stageName === 'execute' && step?.name) ? ` --step "${step.name}"` : ''
|
|
1439
|
+
const doneCommand = `sillyspec run ${cmdStage} --done${confirmFlag}${stepAssertFlag}${changeFlag} --output "你的摘要"`
|
|
1230
1440
|
if (requiresWait) {
|
|
1231
1441
|
console.log(`本步骤必须等待用户输入,不能直接 --done:`)
|
|
1232
1442
|
console.log(`sillyspec run ${cmdStage} --wait --reason "${step.waitReason || '等待用户输入'}" --options "${(step.waitOptions || ['确认']).join(',')}"${changeFlag} --output "你的问题/方案摘要"`)
|