sillyspec 3.27.1 → 3.27.5
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/skills/sillyspec-execute/SKILL.md +8 -0
- package/.claude/skills/sillyspec-quick/SKILL.md +2 -1
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-archive/SKILL.md +87 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-auto/SKILL.md +93 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-commit/SKILL.md +67 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-continue/SKILL.md +47 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-doctor/SKILL.md +76 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-execute/SKILL.md +214 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-explore/SKILL.md +102 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-export/SKILL.md +57 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-init/SKILL.md +177 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-knowledge/SKILL.md +270 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-plan/SKILL.md +156 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-quick/SKILL.md +116 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-resume/SKILL.md +70 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-scan/SKILL.md +79 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-state/SKILL.md +64 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-status/SKILL.md +54 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-verify/SKILL.md +87 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-workspace/SKILL.md +175 -0
- package/.claude/worktrees/sillyspec-data-model-research/.gitattributes +2 -0
- package/.claude/worktrees/sillyspec-data-model-research/.husky/pre-push +20 -0
- package/.claude/worktrees/sillyspec-data-model-research/README.md +203 -0
- package/.claude/worktrees/sillyspec-data-model-research/SKILL.md +91 -0
- package/.claude/worktrees/sillyspec-data-model-research/bin/sillyspec.js +2 -0
- package/.claude/worktrees/sillyspec-data-model-research/logo.jpg +0 -0
- package/.claude/worktrees/sillyspec-data-model-research/package-lock.json +977 -0
- package/.claude/worktrees/sillyspec-data-model-research/package.json +39 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/assets/index-Bq_Z2hne.js +7446 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/favicon.jpg +0 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/index.html +16 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/logo.jpg +0 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/prototype-dashboard.html +836 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/prototype-overview.html +256 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/package.json +25 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/executor.js +95 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/index.js +679 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/parser.js +526 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/watcher.js +357 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/agent-session-log.js +803 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/change-list.js +290 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/change-risk-profile.js +270 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/check-primitives.js +65 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/classify-change.js +109 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/commit-suggest.js +202 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/config-cat.js +88 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/config-schema.js +328 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/constants.js +97 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/contract-matrix.js +480 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/datetime.js +20 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/db-engine.js +55 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/db.js +381 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/decision-distill.js +517 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/backends/local-agent.js +155 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/backends/sillyhub-mcp.js +245 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/probe.js +246 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/strategy.js +121 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/docs-check.js +825 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/docs-debt.js +393 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/docs-gate.js +107 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/doctor-diagnostics.js +1099 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/endpoint-extractor.js +420 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/foreign-declared.js +78 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/fs-atomic.js +73 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/git-helper.js +150 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/hooks/claude-pre-tool-use.cjs +130 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/hooks/worktree-guard.js +892 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/index.js +2798 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/init.js +626 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/knowledge-match.js +231 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/local-detect.js +128 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/local-register.js +104 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/machine-interface.js +543 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/migrate.js +127 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/module-impact.js +166 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/module-resolve.js +222 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/modules.js +580 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/progress/change-registry.js +336 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/progress/consistency-doctor.js +355 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/progress/shared.js +36 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/progress/stage-machine.js +594 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/progress/step-store.js +265 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/progress.js +1304 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/quick-recommend.js +115 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/quicklog.js +852 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/review-tier.js +77 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/command.js +1595 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/complete-handlers.js +1552 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/complete.js +1259 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/concurrent-detect.js +156 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/gates.js +1084 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/multi-repo-context.js +208 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/next.js +128 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/prompt.js +843 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/quick-audit.js +186 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/scan-profile.js +269 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/shared.js +1562 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/stage.js +584 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run.js +23 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/scan-diff.js +388 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/scan-postcheck.js +512 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/scan-staleness.js +95 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/setup.js +414 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/sillyhub-mcp/client.js +597 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/sillyhub-mcp/config.js +62 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/spec-dir-typo.js +50 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/spec-sync.js +358 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stage-contract-engine.js +171 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stage-contract-spec.js +550 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stage-contract.js +889 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stage-review.js +597 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/archive.js +199 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/brainstorm-auto.js +236 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/brainstorm.js +617 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/cmd-existence.js +145 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/doctor.js +540 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/execute.js +1275 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/explore.js +43 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/index.js +29 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/knowledge.js +514 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/plan-postcheck.js +1344 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/plan.js +650 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/quick.js +167 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/scan.js +608 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/status.js +64 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/verify.js +246 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/sync.js +1470 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/task-review.js +1498 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/taskcard.js +210 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/verify-postcheck.js +1718 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/verify-probes.js +277 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/version.js +19 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/workflow.js +600 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/workspace.js +125 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/worktree-apply.js +1446 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/worktree-deps.js +576 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/worktree.js +1715 -0
- package/.claude/worktrees/sillyspec-data-model-research/templates/claude-instruction.md +23 -0
- package/.claude/worktrees/sillyspec-data-model-research/templates/prompts/taskcard-rules.md +28 -0
- package/.claude/worktrees/sillyspec-data-model-research/templates/prompts/testcase-design.md +12 -0
- package/.claude/worktrees/sillyspec-data-model-research/templates/prompts/verify-probes.md +32 -0
- package/.claude/worktrees/sillyspec-data-model-research/templates/workflows/archive-impact.yaml +79 -0
- package/.claude/worktrees/sillyspec-data-model-research/templates/workflows/scan-docs.yaml +132 -0
- package/README.md +1 -1
- package/eval/README.md +86 -0
- package/eval/eval.config.json +28 -0
- package/eval/lib/adapters/cli.mjs +63 -0
- package/eval/lib/adapters/dryrun.mjs +27 -0
- package/eval/lib/config.mjs +82 -0
- package/eval/lib/db.mjs +76 -0
- package/eval/lib/report.mjs +120 -0
- package/eval/lib/runner.mjs +157 -0
- package/eval/lib/tasks.mjs +73 -0
- package/eval/lib/util.mjs +107 -0
- package/eval/lib/workdir.mjs +30 -0
- package/eval/run.mjs +83 -0
- package/eval/tasks/001-slugify/files/package.json +6 -0
- package/eval/tasks/001-slugify/solution/src/slugify.mjs +26 -0
- package/eval/tasks/001-slugify/task.json +6 -0
- package/eval/tasks/001-slugify/verify.mjs +28 -0
- package/eval/tasks/002-each-day/files/package.json +6 -0
- package/eval/tasks/002-each-day/files/src/dates.mjs +4 -0
- package/eval/tasks/002-each-day/solution/src/dates.mjs +30 -0
- package/eval/tasks/002-each-day/task.json +6 -0
- package/eval/tasks/002-each-day/verify.mjs +31 -0
- package/eval/tasks/003-word-report/files/data/notes.txt +5 -0
- package/eval/tasks/003-word-report/files/package.json +6 -0
- package/eval/tasks/003-word-report/solution/bin/report.mjs +36 -0
- package/eval/tasks/003-word-report/task.json +6 -0
- package/eval/tasks/003-word-report/verify.mjs +38 -0
- package/package.json +4 -2
- package/src/agent-session-log.js +803 -0
- package/src/change-list.js +22 -2
- package/src/change-risk-profile.js +83 -14
- package/src/config-schema.js +18 -3
- package/src/contract-matrix.js +153 -8
- package/src/datetime.js +20 -0
- package/src/decision-distill.js +517 -0
- package/src/dispatch/backends/local-agent.js +4 -4
- package/src/docs-check.js +227 -0
- package/src/docs-debt.js +187 -24
- package/src/doctor-diagnostics.js +102 -2
- package/src/foreign-declared.js +78 -0
- package/src/index.js +99 -10
- package/src/init.js +9 -7
- package/src/knowledge-match.js +107 -6
- package/src/local-register.js +7 -1
- package/src/module-resolve.js +12 -1
- package/src/modules.js +2 -1
- package/src/plan-adopt-waves.js +126 -0
- package/src/quicklog.js +61 -7
- package/src/run/command.js +139 -20
- package/src/run/complete-handlers.js +48 -19
- package/src/run/complete.js +43 -3
- package/src/run/gates.js +87 -22
- package/src/run/prompt.js +84 -42
- package/src/run/quick-audit.js +28 -0
- package/src/run/shared.js +176 -11
- package/src/run/stage.js +23 -0
- package/src/run.js +2 -2
- package/src/scan-postcheck.js +30 -17
- package/src/stage-contract-spec.js +2 -1
- package/src/stage-contract.js +68 -4
- package/src/stages/archive.js +53 -12
- package/src/stages/brainstorm-auto.js +7 -0
- package/src/stages/brainstorm.js +11 -0
- package/src/stages/doctor.js +157 -1
- package/src/stages/execute.js +33 -6
- package/src/stages/plan-postcheck.js +79 -18
- package/src/stages/plan.js +4 -0
- package/src/stages/quick.js +6 -1
- package/src/stages/verify.js +31 -14
- package/src/sync.js +94 -2
- package/src/task-review.js +2 -2
- package/src/taskcard-placeholders.js +21 -0
- package/src/taskcard.js +10 -1
- package/src/verify-postcheck.js +315 -13
- package/src/verify-probes.js +46 -4
- package/src/worktree-apply.js +184 -7
- package/src/worktree-deps.js +69 -0
- package/src/worktree.js +201 -58
- package/templates/prompts/taskcard-rules.md +1 -0
package/src/run/complete.js
CHANGED
|
@@ -167,6 +167,38 @@ export async function completeStep(pm, progress, stageName, cwd, outputText, inp
|
|
|
167
167
|
steps[_staleIdx].status = 'pending'
|
|
168
168
|
currentIdx = _staleIdx
|
|
169
169
|
console.log(`⚠️ --confirm 收尾:把 stale 步骤 "${steps[_staleIdx].name}" 拉回当前步骤走完成管线,其余 stale 由回填门控一并确认。`)
|
|
170
|
+
} else if (stageData?.status !== 'completed' && steps.length > 0) {
|
|
171
|
+
// ── 矛盾态自愈(坑 execute-status-machine-contradiction,2026-08-23 实证:--done 报
|
|
172
|
+
// 「没有待完成的步骤」而 status 显示 15 步未完成——并发进程写入竞争后 DB 步骤行停在
|
|
173
|
+
// completed 但阶段 status 未推进的矛盾态,用户只能靠 verify 启动碰运气自愈)。
|
|
174
|
+
// 自愈 = 重播种:ensureStageSteps 按 def 步骤名重建步骤行(保留同名完成态),矛盾行
|
|
175
|
+
// (DB 有但 def 无的幽灵行/半写行)被清掉;重读后再走正常管线。自愈失败再报错给精确指引。
|
|
176
|
+
console.warn(`⚠️ 检测到状态机矛盾:阶段 ${stageName} 无 pending/in-progress 步骤但 status=${stageData.status}(并发写入竞争的半写态)。自动重播种修复…`)
|
|
177
|
+
try {
|
|
178
|
+
const { ensureStageSteps } = await import('./command.js')
|
|
179
|
+
const reseeded = await ensureStageSteps(progress, stageName, cwd, platformOpts?.specRoot || null)
|
|
180
|
+
if (reseeded && effectiveChange) {
|
|
181
|
+
pm._write(cwd, progress, effectiveChange)
|
|
182
|
+
progress = pm.read(cwd, effectiveChange) || progress
|
|
183
|
+
}
|
|
184
|
+
const freshSteps = progress?.stages?.[stageName]?.steps || []
|
|
185
|
+
const freshPending = freshSteps.findIndex(s => s.status === 'pending' || s.status === 'in-progress')
|
|
186
|
+
if (freshPending !== -1) {
|
|
187
|
+
console.log(`✅ 重播种成功:${freshSteps.filter(s => s.status === 'pending').length} 个待完成步骤恢复,继续完成管线`)
|
|
188
|
+
currentIdx = freshPending
|
|
189
|
+
steps.length = 0
|
|
190
|
+
steps.push(...freshSteps)
|
|
191
|
+
stageData.steps = freshSteps
|
|
192
|
+
} else {
|
|
193
|
+
console.error(`重播种后仍无待完成步骤(阶段 status=${progress?.stages?.[stageName]?.status ?? '未知'})。`)
|
|
194
|
+
console.error(` 手动修复:sillyspec run ${stageName} --reset${changeName ? ` --change ${changeName}` : ''} 从头重跑,或 sillyspec doctor 诊断。`)
|
|
195
|
+
process.exit(1)
|
|
196
|
+
}
|
|
197
|
+
} catch (e) {
|
|
198
|
+
console.error(`❌ 矛盾态自愈失败: ${e.message}`)
|
|
199
|
+
console.error(` 手动修复:sillyspec run ${stageName} --reset${changeName ? ` --change ${changeName}` : ''},或 sillyspec doctor`)
|
|
200
|
+
process.exit(1)
|
|
201
|
+
}
|
|
170
202
|
} else {
|
|
171
203
|
console.error(`没有待完成的步骤(阶段 ${stageName} 已无 pending/in-progress 步骤)。当前阶段状态:${stageData?.status ?? '未知'}。用 \`sillyspec run ${stageName} --status\` 查看进度,或 \`sillyspec progress show\` 看全局下一步。`)
|
|
172
204
|
process.exit(1)
|
|
@@ -679,7 +711,12 @@ async function autoCheckPlanFromReviews({ stageName, changeName, cwd, platformOp
|
|
|
679
711
|
return { autoChecked: false, checkedCount: 0, skippedCount: 0 }
|
|
680
712
|
}
|
|
681
713
|
try {
|
|
682
|
-
|
|
714
|
+
// 勾选写入目标漂移锚定(坑 autocheck-worktree-tasks-lost,2026-08-23 实证:agent 在
|
|
715
|
+
// worktree 内跑 execute --done 且 drift 守卫未拦(非平台模式+无 specDriftAnchor 透传的
|
|
716
|
+
// 直调路径)时,tasks.md 勾选写进 worktree 的 .sillyspec/changes/ 副本——worktree 被
|
|
717
|
+
// cleanup 整目录删除后勾选丢失,主仓 tasks.md 停在未勾态需手动补。runtimeRoot 已走
|
|
718
|
+
// resolveRuntimeRoot(drift-aware),changeDir 同步对齐:specDriftAnchor > specRoot > cwd。
|
|
719
|
+
const specBaseLc = platformOpts?.specDriftAnchor || platformOpts?.specRoot || join(cwd, '.sillyspec')
|
|
683
720
|
const changeDir = join(specBaseLc, 'changes', changeName)
|
|
684
721
|
// 2026-08-20-task-truth-unify D-001@v1:勾选写入目标从 plan.md 迁 tasks.md(任务唯一真相;
|
|
685
722
|
// plan.md Wave 引用行无 checkbox,无处可勾)。tasks.md 存在性作前置。
|
|
@@ -959,8 +996,11 @@ export async function waitStep(pm, progress, stageName, cwd, outputText, waitRea
|
|
|
959
996
|
// requiresWait 语义前置(坑 archive-step3-wait-answer-hint-late):--continue --answer 只是中继回答,
|
|
960
997
|
// 本步会回到待执行(回答后还需执行动作再 --done 收尾);不想两段式可用 --done --answer 一步完成。
|
|
961
998
|
// 提示落在标记 --wait 的此刻,而非等 agent 撞 --done 报错才知道。
|
|
962
|
-
|
|
963
|
-
|
|
999
|
+
// conditionalWait(坑 archive-subconfirm-redundant 后 archive step3 降级软门)+ repeatableWait 的
|
|
1000
|
+
// --wait 同样回 pending(continueStep shouldReturnToCurrentStep),提示同样适用。
|
|
1001
|
+
if (stepDef.requiresWait === true || currentStep.requiresWait === true
|
|
1002
|
+
|| stepDef.conditionalWait === true || currentStep.conditionalWait === true) {
|
|
1003
|
+
console.log(` 注:本步为需回执的等待步骤(requiresWait/conditionalWait)——--continue --answer 后本步回到待执行,完成动作后需再 --done 收尾`)
|
|
964
1004
|
console.log(` 或一步完成:sillyspec run ${stageName} --done --answer "你的选择"${changeName ? ` --change ${changeName}` : ''} --output "你的摘要"`)
|
|
965
1005
|
}
|
|
966
1006
|
}
|
package/src/run/gates.js
CHANGED
|
@@ -463,6 +463,16 @@ function rollbackCompletionAndReturn(pm, progress, stageData, steps, currentIdx,
|
|
|
463
463
|
*/
|
|
464
464
|
export async function runStageCompletionGates({ stageName, cwd, changeName, platformOpts, specBase, progress, pm, stageData, steps, currentIdx, ctx = null }) {
|
|
465
465
|
const projectName = progress.project || basename(cwd)
|
|
466
|
+
// decisions.md header 自动补齐(坑 decisions-header-late-warning,2026-08-24 用户反馈二期):
|
|
467
|
+
// brainstorm step8 旧模板自带无 frontmatter 的 decisions 样例,存量变更照抄必缺 author/created_at,
|
|
468
|
+
// 拖到平台同步/后续环节才提示。gate 前幂等补机械字段(先例:autoCheckPlanFromReviews 同层写
|
|
469
|
+
// tasks.md),validator 读到的即已补齐版——补的是 CLI 可自证的机械字段,不替 agent 产出语义内容。
|
|
470
|
+
if (['brainstorm', 'plan', 'verify'].includes(stageName) && changeName) {
|
|
471
|
+
try {
|
|
472
|
+
const { ensureDecisionDocHeader } = await import('../stage-contract.js')
|
|
473
|
+
ensureDecisionDocHeader(resolveChangeDir(cwd, changeName, platformOpts?.specRoot))
|
|
474
|
+
} catch { /* 补齐失败不阻断 gate */ }
|
|
475
|
+
}
|
|
466
476
|
const contractResult = runValidators(stageName, cwd, changeName, { projectName, specRoot: platformOpts?.specRoot })
|
|
467
477
|
if (contractResult.errors.length > 0) {
|
|
468
478
|
console.error(`\n❌ 阶段 ${stageName} 校验失败:`)
|
|
@@ -498,6 +508,37 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
|
|
|
498
508
|
printVerifyTestCheck(testCheck)
|
|
499
509
|
if (testCheck.status === 'failed') {
|
|
500
510
|
console.error('\n❌ verify 阶段被阻断:verify-result.md 自报告通过,但 CLI 实测测试失败。')
|
|
511
|
+
// 并行 WIP 归因鉴别(坑 verify-reconcile-foreign-wip):实测跑在主仓共享工作区,并行会话
|
|
512
|
+
// 在途 WIP 物理在场——他者文件命中声明集时提示复验归因(EADDRINUSE 鉴别同款风格),不静默背锅。
|
|
513
|
+
try {
|
|
514
|
+
const { collectForeignDeclaredFiles } = await import('../verify-postcheck.js')
|
|
515
|
+
const { gitQuiet } = await import('../git-helper.js')
|
|
516
|
+
const foreignMap = collectForeignDeclaredFiles(cwd, changeName)
|
|
517
|
+
if (foreignMap.size > 0) {
|
|
518
|
+
const dirty = (gitQuiet(cwd, ['diff', '--name-only', 'HEAD']) || '')
|
|
519
|
+
.split('\n').filter(Boolean).map(f => f.replace(/\\/g, '/'))
|
|
520
|
+
const hitForeign = dirty.filter(f => foreignMap.has(f))
|
|
521
|
+
if (hitForeign.length > 0) {
|
|
522
|
+
console.error(` ℹ️ 归因提示:主仓检出 ${hitForeign.length} 个并行会话声明的在途文件(${hitForeign.slice(0, 5).join(', ')}${hitForeign.length > 5 ? ' 等' : ''})——实测失败可能混入他者 WIP 而非本变更问题。待其提交/收尾后复验,仍失败才是本变更的。`)
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
// 第二判据(坑 derived-artifact-stale-baseline):本变更 apply 的文件(apply-pathspec 落盘)
|
|
526
|
+
// 与主仓近期提交重叠——本变更旧基线的生成产物可能覆盖了已合入内容,先在新基线重跑生成命令再复验
|
|
527
|
+
try {
|
|
528
|
+
const pathspecFile = join(resolveRuntimeRoot(platformOpts, specBase), `apply-pathspec-${changeName}.txt`)
|
|
529
|
+
if (changeName && existsSync(pathspecFile)) {
|
|
530
|
+
const ownFiles = new Set(readFileSync(pathspecFile, 'utf8').split('\n').map(l => l.trim()).filter(Boolean).map(f => f.replace(/\\/g, '/')))
|
|
531
|
+
if (ownFiles.size > 0) {
|
|
532
|
+
const recent = (gitQuiet(cwd, ['log', '-n', '10', '--name-only', '--pretty=format:']) || '')
|
|
533
|
+
.split('\n').map(l => l.trim().replace(/\\/g, '/')).filter(Boolean)
|
|
534
|
+
const overlap = [...new Set(recent)].filter(f => ownFiles.has(f))
|
|
535
|
+
if (overlap.length > 0) {
|
|
536
|
+
console.error(` ℹ️ 归因提示②:本变更有 ${overlap.length} 个文件与主仓最近 10 条提交重叠(${overlap.slice(0, 5).join(', ')}${overlap.length > 5 ? ' 等' : ''})——若为生成产物(api-types 等),本变更可能在旧基线生成并覆盖了已合入内容;先在新基线重跑生成命令(如 gen:types)再复验。`)
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
} catch { /* 判据②失败不影响主流程 */ }
|
|
541
|
+
} catch { /* 归因提示失败不影响阻断语义 */ }
|
|
501
542
|
console.error(' 请修复失败的测试并更新 verify-result.md 后重新完成此步骤。')
|
|
502
543
|
return await rollbackCompletionAndReturn(pm, progress, stageData, steps, currentIdx, cwd, changeName, platformOpts)
|
|
503
544
|
}
|
|
@@ -543,29 +584,10 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
|
|
|
543
584
|
}
|
|
544
585
|
}
|
|
545
586
|
}
|
|
546
|
-
// ── verify 服务进程回收(坑 verify-service-process-leak
|
|
547
|
-
//
|
|
548
|
-
// 此处收尾逐个 kill + 清文件。best-effort:ESRCH(已退出)静默,kill 失败 warn 不阻断 verify。
|
|
587
|
+
// ── verify 服务进程回收(坑 verify-service-process-leak;坑 verify-pids-cross-session-kill
|
|
588
|
+
// 见 reapVerifyServices 注释)──
|
|
549
589
|
try {
|
|
550
|
-
|
|
551
|
-
if (existsSync(servicesPath)) {
|
|
552
|
-
const pids = readFileSync(servicesPath, 'utf8').split('\n').map(l => l.trim()).filter(l => /^\d+$/.test(l))
|
|
553
|
-
const reaped = []; const failed = []
|
|
554
|
-
for (const pid of pids) {
|
|
555
|
-
try { process.kill(parseInt(pid, 10), 'SIGTERM'); reaped.push(pid) }
|
|
556
|
-
catch (e) { if (e.code !== 'ESRCH') failed.push(`${pid}(${e.code || e.message})`) }
|
|
557
|
-
}
|
|
558
|
-
try { unlinkSync(servicesPath) } catch {}
|
|
559
|
-
if (reaped.length > 0) console.log(`🧹 verify 服务进程已回收 ${reaped.length} 个(PID: ${reaped.join(', ')})`)
|
|
560
|
-
// CLI 回执(坑 verify-literal-evidence-mismatch):回收即真实启动的结构化证据——
|
|
561
|
-
// checkIntegrationEvidence(stage-contract)读回执注入匹配,agent 措辞差异不再误拦
|
|
562
|
-
try {
|
|
563
|
-
writeFileSync(join(dirname(servicesPath), 'verify-services.receipt.json'), JSON.stringify({
|
|
564
|
-
change: changeName || null, reapedPidCount: reaped.length, reapedAt: new Date().toISOString(),
|
|
565
|
-
}, null, 2) + '\n')
|
|
566
|
-
} catch {}
|
|
567
|
-
if (failed.length > 0) console.warn(`⚠️ 服务进程回收失败 ${failed.length} 个:${failed.join(', ')} — 手动处理:taskkill /PID <pid> /F(Windows)/ kill -9 <pid>`)
|
|
568
|
-
}
|
|
590
|
+
reapVerifyServices(platformOpts, specBase, changeName)
|
|
569
591
|
} catch (e) { console.warn(`⚠️ verify 服务进程回收异常(不阻断): ${e.message}`) }
|
|
570
592
|
|
|
571
593
|
console.log('\n✅ 验证通过,下一步:sillyspec run archive')
|
|
@@ -1027,3 +1049,46 @@ export async function completeStageGates({ stageName, cwd, changeName, platformO
|
|
|
1027
1049
|
return null
|
|
1028
1050
|
}
|
|
1029
1051
|
|
|
1052
|
+
|
|
1053
|
+
/**
|
|
1054
|
+
* verify 服务进程回收(verify --done 收尾调用)。
|
|
1055
|
+
*
|
|
1056
|
+
* 坑 verify-service-process-leak(2026-08-21):真实启动验证起的长驻服务(uvicorn 等)漏挂
|
|
1057
|
+
* 一天多——verify prompt 要求 PID 按变更分片登记 .runtime/verify-services-<change>.pids。
|
|
1058
|
+
* 坑 verify-pids-cross-session-kill(2026-08-23):原单文件无归属,A 会话 --done 会把 B 正在
|
|
1059
|
+
* 收集 Runtime Evidence 的服务一并杀掉——分片后各回收各的;旧单文件兼容回收(升级过渡期)。
|
|
1060
|
+
* 回执同样按变更分片(原单份被并行会话后写覆盖),checkIntegrationEvidence(stage-contract)
|
|
1061
|
+
* 读分片名注入证据匹配。best-effort:ESRCH(已退出)静默,kill 失败 warn 不阻断 verify。
|
|
1062
|
+
*
|
|
1063
|
+
* @returns {{ reaped: number, receiptPath: string|null }}
|
|
1064
|
+
*/
|
|
1065
|
+
export function reapVerifyServices(platformOpts, specBase, changeName) {
|
|
1066
|
+
const runtimeDir = resolveRuntimeRoot(platformOpts, specBase)
|
|
1067
|
+
const reapFile = (path, label) => {
|
|
1068
|
+
if (!existsSync(path)) return null
|
|
1069
|
+
const pids = readFileSync(path, 'utf8').split('\n').map(l => l.trim()).filter(l => /^\d+$/.test(l))
|
|
1070
|
+
const reaped = []; const failed = []
|
|
1071
|
+
for (const pid of pids) {
|
|
1072
|
+
try { process.kill(parseInt(pid, 10), 'SIGTERM'); reaped.push(pid) }
|
|
1073
|
+
catch (e) { if (e.code !== 'ESRCH') failed.push(`${pid}(${e.code || e.message})`) }
|
|
1074
|
+
}
|
|
1075
|
+
try { unlinkSync(path) } catch {}
|
|
1076
|
+
if (reaped.length > 0) console.log(`🧹 verify 服务进程已回收 ${reaped.length} 个${label}(PID: ${reaped.join(', ')})`)
|
|
1077
|
+
return { reaped: reaped.length, failed }
|
|
1078
|
+
}
|
|
1079
|
+
const own = reapFile(join(runtimeDir, `verify-services-${changeName}.pids`), '')
|
|
1080
|
+
const legacy = reapFile(join(runtimeDir, 'verify-services.pids'), '(旧格式单文件,兼容回收)')
|
|
1081
|
+
const reapedTotal = (own?.reaped || 0) + (legacy?.reaped || 0)
|
|
1082
|
+
const failedAll = [...(own?.failed || []), ...(legacy?.failed || [])]
|
|
1083
|
+
let receiptPath = null
|
|
1084
|
+
if (reapedTotal > 0) {
|
|
1085
|
+
try {
|
|
1086
|
+
receiptPath = join(runtimeDir, `verify-services-${changeName}.receipt.json`)
|
|
1087
|
+
writeFileSync(receiptPath, JSON.stringify({
|
|
1088
|
+
change: changeName || null, reapedPidCount: reapedTotal, reapedAt: new Date().toISOString(),
|
|
1089
|
+
}, null, 2) + '\n')
|
|
1090
|
+
} catch { receiptPath = null }
|
|
1091
|
+
}
|
|
1092
|
+
if (failedAll.length > 0) console.warn(`⚠️ 服务进程回收失败 ${failedAll.length} 个:${failedAll.join(', ')} — 手动处理:taskkill /PID <pid> /F(Windows)/ kill -9 <pid>`)
|
|
1093
|
+
return { reaped: reapedTotal, receiptPath }
|
|
1094
|
+
}
|
package/src/run/prompt.js
CHANGED
|
@@ -23,6 +23,7 @@ import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs'
|
|
|
23
23
|
import { stageRegistry } from '../stages/index.js'
|
|
24
24
|
import { resolvePromptIncludes, resolveRuntimeRoot, safeGit, WAIT_MARKER_RE, QUICK_SID_RE } from './shared.js'
|
|
25
25
|
import { renderStageContract } from '../stage-contract-spec.js'
|
|
26
|
+
import { nowWallClock } from '../datetime.js'
|
|
26
27
|
import { parseModuleMapSimple } from '../modules.js'
|
|
27
28
|
import { REVIEW_SCHEMA_VERSION, isValidExecuteRunId } from '../task-review.js'
|
|
28
29
|
|
|
@@ -228,16 +229,18 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
228
229
|
promptText = promptText.replace(/<git-user>/g, 'unknown')
|
|
229
230
|
}
|
|
230
231
|
}
|
|
231
|
-
//
|
|
232
|
+
// 替换时间戳占位符(本地墙钟统一走 nowWallClock——坑 taskcard-created-at-utc:人读字段用
|
|
233
|
+
// UTC 会偏一个时区,子代理手工改)
|
|
232
234
|
const now = new Date()
|
|
233
|
-
const nowDatetime =
|
|
235
|
+
const nowDatetime = nowWallClock(now)
|
|
234
236
|
const nowTimestamp = now.getFullYear() + String(now.getMonth()+1).padStart(2,'0') + String(now.getDate()).padStart(2,'0') + '-' + String(now.getHours()).padStart(2,'0') + String(now.getMinutes()).padStart(2,'0') + String(now.getSeconds()).padStart(2,'0')
|
|
235
237
|
const nowDate = now.getFullYear() + '-' + String(now.getMonth()+1).padStart(2,'0') + '-' + String(now.getDate()).padStart(2,'0')
|
|
236
238
|
promptText = promptText.replace(/<now-datetime>/g, nowDatetime)
|
|
237
239
|
promptText = promptText.replace(/<now-timestamp>/g, nowTimestamp)
|
|
238
240
|
promptText = promptText.replace(/<now-date>/g, nowDate)
|
|
239
|
-
// <now-iso-datetime>(scan frontmatter updated_at 用;与 scan-fix-headers
|
|
240
|
-
|
|
241
|
+
// <now-iso-datetime>(scan frontmatter updated_at 用;与 scan-fix-headers 同款秒级格式)。
|
|
242
|
+
// 占位符名带 iso 是历史遗留——值已统一为本地墙钟(同 <now-datetime>),改名会破坏 stages 引用。
|
|
243
|
+
promptText = promptText.replace(/<now-iso-datetime>/g, nowWallClock(now))
|
|
241
244
|
// <git-head-short>(scan frontmatter source_commit 用;CLI 代查,agent 勿自跑 rev-parse)
|
|
242
245
|
if (promptText.includes('<git-head-short>')) {
|
|
243
246
|
let headShort = 'unknown'
|
|
@@ -423,6 +426,42 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
423
426
|
}
|
|
424
427
|
}
|
|
425
428
|
|
|
429
|
+
// 决策防复潮注入(W1.1 第 3 点,task-04,FR-05):brainstorm「加载项目上下文」Step2 的
|
|
430
|
+
// {DECISION_HITS} → matchKnowledge decisionHits——命中 rejected 条目时渲染「否决决策提示」段
|
|
431
|
+
// (ID/标题/否决理由/复潮条件),无命中替换为空串零输出(不留残留占位符);注入结果同步落
|
|
432
|
+
// runtime JSON(与 KNOWLEDGE_HIT_REPORT 的 json 落盘同口径)。advisory 展示,不改变步骤流程;
|
|
433
|
+
// 全降级不抛(异常 → 单行说明)。
|
|
434
|
+
if (stageName === 'brainstorm' && promptText.includes('{DECISION_HITS}')) {
|
|
435
|
+
try {
|
|
436
|
+
const { matchKnowledge } = await import('../knowledge-match.js')
|
|
437
|
+
const decSpecBase = resolvePromptSpecBase(platformOpts, cwd)
|
|
438
|
+
const decKnowledgeDir = join(decSpecBase, 'knowledge')
|
|
439
|
+
// taskContext:changeName(brainstorm Step2 时 tasks.md 尚未生成,变更名是唯一稳定任务
|
|
440
|
+
// 语料;与 execute 分支的 changeName 基底同口径)
|
|
441
|
+
const decResult = matchKnowledge(decKnowledgeDir, changeName || '')
|
|
442
|
+
const rejectedHits = (decResult.decisionHits || []).filter(h => h.status === 'rejected')
|
|
443
|
+
let decInjected = ''
|
|
444
|
+
if (rejectedHits.length > 0) {
|
|
445
|
+
const lines = [
|
|
446
|
+
'⚠️ 否决决策提示(历史已否决,防复潮)——下列决策此前已被否决。看到否决理由后,除非复潮条件明确满足,不要在本次方案中重新提出;若认为复潮条件已满足,须在本变更 decisions.md 记录新版本条目(D-xxx@vN+1)说明依据:'
|
|
447
|
+
]
|
|
448
|
+
for (const h of rejectedHits) {
|
|
449
|
+
lines.push(`- ${h.id} ${h.title}(${h.file})`)
|
|
450
|
+
lines.push(` - 否决理由:${h.reason || '(未记录)'}`)
|
|
451
|
+
lines.push(` - 复潮条件:${h.revisitWhen || '(未记录)'}`)
|
|
452
|
+
}
|
|
453
|
+
decInjected = '\n\n' + lines.join('\n')
|
|
454
|
+
}
|
|
455
|
+
promptText = promptText.replace(/\{DECISION_HITS\}/g, decInjected)
|
|
456
|
+
// 注入结果同步落 runtime JSON(与既有 KNOWLEDGE_HIT_REPORT 落盘口径一致)
|
|
457
|
+
const decRuntimeDir = join(decSpecBase, '.runtime')
|
|
458
|
+
mkdirSync(decRuntimeDir, { recursive: true })
|
|
459
|
+
writeFileSync(join(decRuntimeDir, 'decision-hits.json'), JSON.stringify({ matched: decResult.matched, decisionHits: decResult.decisionHits || [] }, null, 2) + '\n')
|
|
460
|
+
} catch (e) {
|
|
461
|
+
promptText = promptText.replace(/\{DECISION_HITS\}/g, `[decisions] 决策命中检测异常(${e.message}),跳过`)
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
426
465
|
// Knowledge hit report: execute 阶段注入匹配结果
|
|
427
466
|
if (stageName === 'execute' && promptText.includes('{KNOWLEDGE_HIT_REPORT}')) {
|
|
428
467
|
try {
|
|
@@ -458,53 +497,29 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
458
497
|
// docs-debt 模块文档欠账事实注入(2026-08-15 docs-debt-inject D-006,债单第六节"CLI 算事实"):
|
|
459
498
|
// Wave prompt {DOCS_DEBT} → computeDocsDebt(git diff × module-map)facts;空 facts 替换为空串
|
|
460
499
|
// (无债零输出,无残留占位符)。changedFiles 口径(D-001/FR-007):worktree 根
|
|
461
|
-
// (meta.json 所在)git status --porcelain + diff baselineCommit..HEAD
|
|
462
|
-
// (
|
|
500
|
+
// (meta.json 所在)git status --porcelain + diff baselineCommit..HEAD 并集——公共实现已抽至
|
|
501
|
+
// docs-debt.collectExecuteChangedFiles(execute.js Wave 步渲染点同源复用,勿双写)。
|
|
502
|
+
// 全降级不抛(异常 → 单行说明)。
|
|
463
503
|
if (stageName === 'execute' && promptText.includes('{DOCS_DEBT}')) {
|
|
464
504
|
try {
|
|
465
|
-
const { computeDocsDebt } = await import('../docs-debt.js')
|
|
466
505
|
const debtSpecBase = resolvePromptSpecBase(platformOpts, cwd)
|
|
467
506
|
const debtProjectName = dbProjectName || basename(cwd)
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
const wtRoot = join(debtSpecBase, '.runtime', 'worktrees', changeName || '')
|
|
471
|
-
if (changeName && existsSync(wtRoot)) {
|
|
472
|
-
debtProjectRoot = wtRoot
|
|
473
|
-
}
|
|
474
|
-
// changedFiles:已提交(baseline..HEAD)∪ 未提交(status porcelain,非未跟踪)
|
|
475
|
-
const changed = new Set()
|
|
476
|
-
const { safeGit } = await import('../git-helper.js')
|
|
477
|
-
const statusRes = safeGit(debtProjectRoot, ['status', '--porcelain'], { trim: false })
|
|
478
|
-
if (!statusRes.error && statusRes.value) {
|
|
479
|
-
for (const line of statusRes.value.split('\n')) {
|
|
480
|
-
if (!line.trim()) continue
|
|
481
|
-
// "XY path" / rename "R old -> new" 取 new
|
|
482
|
-
const pathPart = line.slice(3).split('->').pop().trim().replace(/"/g, '')
|
|
483
|
-
if (pathPart && !pathPart.startsWith('.sillyspec/')) changed.add(pathPart.replace(/\\/g, '/'))
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
const { readFileSync: rf } = await import('node:fs')
|
|
487
|
-
const metaPath = join(wtRoot, 'meta.json')
|
|
488
|
-
if (changeName && existsSync(metaPath)) {
|
|
489
|
-
try {
|
|
490
|
-
const meta = JSON.parse(rf(metaPath, 'utf8'))
|
|
491
|
-
if (meta.baselineCommit) {
|
|
492
|
-
const diffRes = safeGit(debtProjectRoot, ['diff', '--name-only', `${meta.baselineCommit}..HEAD`], { trim: false })
|
|
493
|
-
if (!diffRes.error && diffRes.value) {
|
|
494
|
-
for (const f of diffRes.value.split('\n')) {
|
|
495
|
-
if (f.trim() && !f.trim().startsWith('.sillyspec/')) changed.add(f.trim().replace(/\\/g, '/'))
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
} catch { /* meta 读失败只用未提交集 */ }
|
|
500
|
-
}
|
|
507
|
+
const { collectExecuteChangedFiles, computeDocsDebt, computeDecisionTouches, renderDecisionTouchFacts } = await import('../docs-debt.js')
|
|
508
|
+
const { root: debtProjectRoot, changedFiles } = collectExecuteChangedFiles({ specBase: debtSpecBase, changeName, cwd })
|
|
501
509
|
const result = computeDocsDebt({
|
|
502
510
|
projectRoot: debtProjectRoot,
|
|
503
511
|
specBase: debtSpecBase,
|
|
504
512
|
projectName: debtProjectName,
|
|
505
|
-
changedFiles
|
|
513
|
+
changedFiles,
|
|
506
514
|
})
|
|
507
|
-
|
|
515
|
+
// 决策锚点触碰事实(2026-08-24-decision-touch-cli-drift task-01,W-A 次渲染点):重入/reset
|
|
516
|
+
// 场景 changedFiles 非空时呈现。advisory 追加渲染分支——既有 docs-debt facts 逐字不变,
|
|
517
|
+
// 无触碰 render 返回 ''(零输出);主渲染点在 execute.js buildWavePrompt(同源 facts 计算)。
|
|
518
|
+
let debtTouchFacts = ''
|
|
519
|
+
try {
|
|
520
|
+
debtTouchFacts = renderDecisionTouchFacts(computeDecisionTouches(changedFiles, join(debtSpecBase, 'knowledge')).touches)
|
|
521
|
+
} catch { /* 触碰计算降级为空——不影响欠账 facts 注入 */ }
|
|
522
|
+
promptText = promptText.replace(/\{DOCS_DEBT\}/g, [result.facts, debtTouchFacts].filter(Boolean).join('\n'))
|
|
508
523
|
} catch (e) {
|
|
509
524
|
promptText = promptText.replace(/\{DOCS_DEBT\}/g, `[docs-debt] 欠账计算异常(${e.message}),跳过`)
|
|
510
525
|
}
|
|
@@ -677,6 +692,33 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
677
692
|
promptText = promptText.split('{WORKTREE_BASELINE_INFO}').join(baselineInfo)
|
|
678
693
|
}
|
|
679
694
|
|
|
695
|
+
// verify「运行测试和质量扫描」evidence-auto 推荐注入(task-12,FR-11 / D-005@v2):
|
|
696
|
+
// verify-postcheck 在 --done 事后解析 evidence-auto,agent 写 verify-result.md 时看不到
|
|
697
|
+
// 推荐组合、无从否决——故在 prompt 时点经 {EVIDENCE_AUTO_RECOMMENDATION} 把 task-11
|
|
698
|
+
// resolveTestStrategy 的 evidence_auto_recommendation.summary 渲染进 step prompt(含推荐
|
|
699
|
+
// 理由、降级注记与「可在 verify-result.md 否决并改跑全量」路径),供用户否决(FR-11)。
|
|
700
|
+
// 仅 test_strategy=evidence-auto 且 recommendation 非空时渲染;full/module/skip/未配置
|
|
701
|
+
// (recommendation=null)替换为空串零输出。fail-soft:读取/解析异常降级单行说明不抛,
|
|
702
|
+
// 绝不阻断 verify prompt 输出(对齐上方 {WORKTREE_BASELINE_INFO} 注入先例)。
|
|
703
|
+
if (stageName === 'verify' && promptText.includes('{EVIDENCE_AUTO_RECOMMENDATION}')) {
|
|
704
|
+
let eaInjected = ''
|
|
705
|
+
try {
|
|
706
|
+
const { resolveTestStrategy } = await import('../verify-postcheck.js')
|
|
707
|
+
const eaSpecBase = resolvePromptSpecBase(platformOpts, cwd)
|
|
708
|
+
// 信任边界:local.yaml 只读 specBase(平台 specRoot / 漂移锚定主仓 / cwd 本地),与
|
|
709
|
+
// runVerifyTestCheck 的 specBase 来源同口径,不读 agent 可写的 worktree 副本。
|
|
710
|
+
const eaYamlPath = join(eaSpecBase, 'local.yaml')
|
|
711
|
+
const eaYamlText = existsSync(eaYamlPath) ? readFileSync(eaYamlPath, 'utf8') : null
|
|
712
|
+
const eaChangeDir = changeName ? join(eaSpecBase, 'changes', changeName) : null
|
|
713
|
+
const resolution = resolveTestStrategy({ yamlText: eaYamlText, changeDir: eaChangeDir })
|
|
714
|
+
const rec = resolution.evidence_auto_recommendation
|
|
715
|
+
if (rec && rec.summary) eaInjected = rec.summary
|
|
716
|
+
} catch (e) {
|
|
717
|
+
eaInjected = `(evidence-auto 推荐注入失败:${e.message}——请读 .sillyspec/local.yaml 确认 test_strategy;--done 时 CLI 仍会按 resolveTestStrategy 实测对账,推荐不可用时可显式设 test_strategy: full/module)`
|
|
718
|
+
}
|
|
719
|
+
promptText = promptText.split('{EVIDENCE_AUTO_RECOMMENDATION}').join(eaInjected)
|
|
720
|
+
}
|
|
721
|
+
|
|
680
722
|
// 注入模块上下文(brainstorm/plan/execute 阶段,基于 Module Context Index)
|
|
681
723
|
if (['brainstorm', 'plan', 'execute'].includes(stageName) && projectName) {
|
|
682
724
|
const effectiveSpecBase = resolvePromptSpecBase(platformOpts, cwd)
|
package/src/run/quick-audit.js
CHANGED
|
@@ -35,6 +35,13 @@ export function printQuickAuditReview(review) {
|
|
|
35
35
|
console.error(` 如确认接受这些变更,重新运行 --done 时带上对应 flag 即可解锁:`)
|
|
36
36
|
console.error(` sillyspec run quick --done --force-baseline --allow-new --change <id> --output "..."`)
|
|
37
37
|
console.error(` (--force-baseline 覆盖受保护/危险文件如 src/run.js;--allow-new 允许新增文件;--allow-delete 允许删除文件)`)
|
|
38
|
+
// 两套开关明示(坑 files-flag-not-unlock-protected,2026-08-22 实证:模块文档被判危险
|
|
39
|
+
// 文件,追加 --files 边界不解锁仍被拦——「追加边界」与「解锁拦截」是两套开关,交互上易误解
|
|
40
|
+
// 前者能解决后者)。命中危险文件 reason 时点名:--files 只改归属口径(哪些文件计入本
|
|
41
|
+
// 会话),不改变危险判定;受保护/危险文件的放行开关唯 --force-baseline。
|
|
42
|
+
if (review.reasons.some(r => r.startsWith('危险文件变更'))) {
|
|
43
|
+
console.error(` ⛔ 注意:追加 --files 边界不会解锁受保护/危险文件的拦截(两套开关——--files 只声明哪些文件计入本会话,不改变危险判定)。改这类文件必须 --force-baseline。`)
|
|
44
|
+
}
|
|
38
45
|
}
|
|
39
46
|
} else if (review.status === 'warning') {
|
|
40
47
|
console.warn(`\n⚠️ quick 变更边界审计 — WARNING:`)
|
|
@@ -51,6 +58,27 @@ export function printQuickAuditReview(review) {
|
|
|
51
58
|
console.log(`\n✅ quick 变更边界审计 — SAFE (本轮新增变更 ${review.changedFiles.length} 个文件)`)
|
|
52
59
|
}
|
|
53
60
|
}
|
|
61
|
+
// 关联变更遗留放行提示(坑 linked-change-leftover-false-block,独立于三态都打——放行可见
|
|
62
|
+
// 可审计,不静默):这些文件属关联变更目录但不在本会话归属(启动后出现的他者遗留),
|
|
63
|
+
// 已剔除出本会话文件行,由关联变更自己的流程收尾。
|
|
64
|
+
if (Array.isArray(review.linkedChangeLeftovers) && review.linkedChangeLeftovers.length > 0) {
|
|
65
|
+
console.warn(`
|
|
66
|
+
🧹 关联变更目录检测到 ${review.linkedChangeLeftovers.length} 个遗留脏文件(他者/上个 session 残留,非本会话改动)——已放行不计入本会话:`)
|
|
67
|
+
for (const f of review.linkedChangeLeftovers) console.warn(` - ${f}`)
|
|
68
|
+
console.warn(` 它们归关联变更自己的流程管;若确系本会话需要改,用 --files 显式声明归属。`)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 他者会话声明豁免提示(坑 foreign-session-declared-false-block,独立于三态都打——放行可见
|
|
72
|
+
// 可审计,不静默,与上块同哲学):多 agent 并发时并行 quick 会话 --files 已声明的文件在本会话
|
|
73
|
+
// 窗口内出现——不属本会话,已退栈归该会话审计,本会话无需 --force-baseline。
|
|
74
|
+
if (Array.isArray(review.foreignSessionDeclared) && review.foreignSessionDeclared.length > 0) {
|
|
75
|
+
console.warn(`
|
|
76
|
+
🔗 ${review.foreignSessionDeclared.length} 个文件已由其他 active quick 会话声明——已剔除出本会话审计,归该会话边界管:`)
|
|
77
|
+
for (const x of review.foreignSessionDeclared) console.warn(` - ${x.file} ← ${(x.sessions || []).join(', ')}`)
|
|
78
|
+
console.warn(` 并发下无需 --force-baseline:这些文件不属本会话。若确系本会话改动,恢复会话时 --files 追加声明归属(sillyspec run quick --files <file> --change <本会话ID>);`)
|
|
79
|
+
console.warn(` 他者会话若为崩溃残留(超 7 天未收尾),sillyspec run quick --cancel --change <他者会话ID> 清理后声明即失效。`)
|
|
80
|
+
}
|
|
81
|
+
|
|
54
82
|
// D-8 文档欠账显性化(advisory,独立于 status 三态都打):改了源码没动文档 → 一行欠账标记。
|
|
55
83
|
// 不阻断、不解锁、纯显性化——累积欠账可事后审计 QUICKLOG reasons 追溯。
|
|
56
84
|
if (review.docSyncHint && review.docSyncHint.touchedSource > 0 && review.docSyncHint.docFiles.length === 0) {
|