sillyspec 3.26.8 → 3.26.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/skills/sillyspec-archive/SKILL.md +1 -2
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +1 -2
- package/.claude/skills/sillyspec-execute/SKILL.md +11 -3
- package/.claude/skills/sillyspec-explore/SKILL.md +0 -1
- package/.claude/skills/sillyspec-plan/SKILL.md +1 -2
- package/.claude/skills/sillyspec-quick/SKILL.md +4 -5
- package/.claude/skills/sillyspec-scan/SKILL.md +2 -2
- package/.claude/skills/sillyspec-status/SKILL.md +0 -1
- package/.claude/skills/sillyspec-verify/SKILL.md +4 -3
- package/README.md +5 -9
- package/package.json +2 -2
- package/packages/dashboard/server/index.js +14 -8
- package/src/change-risk-profile.js +5 -207
- package/src/config-schema.js +6 -1
- package/src/constants.js +97 -106
- package/src/db-engine.js +1 -1
- package/src/docs-check.js +34 -5
- package/src/git-helper.js +2 -2
- package/src/hooks/worktree-guard.js +1 -1
- package/src/index.js +96 -13
- package/src/init.js +14 -3
- package/src/machine-interface.js +7 -4
- package/src/progress/stage-machine.js +3 -0
- package/src/quicklog.js +203 -4
- package/src/run/command.js +101 -3
- package/src/run/complete-handlers.js +1399 -1254
- package/src/run/complete.js +9 -1
- package/src/run/concurrent-detect.js +22 -0
- package/src/run/gates.js +67 -1
- package/src/run/prompt.js +761 -713
- package/src/run/quick-audit.js +146 -137
- package/src/run/shared.js +1215 -1109
- package/src/run/stage.js +17 -1
- package/src/scan-diff.js +388 -0
- package/src/scan-staleness.js +18 -13
- package/src/spec-sync.js +319 -0
- package/src/stage-contract-spec.js +1 -1
- package/src/stage-review.js +1 -1
- package/src/stages/archive.js +2 -1
- package/src/stages/brainstorm.js +2 -2
- package/src/stages/execute.js +1003 -985
- package/src/stages/plan-postcheck.js +22 -1
- package/src/stages/plan.js +74 -40
- package/src/stages/quick.js +13 -5
- package/src/stages/scan.js +4 -3
- package/src/stages/verify.js +14 -12
- package/src/sync.js +22 -0
- package/src/task-review.js +12 -4
- package/src/workflow.js +8 -185
- package/src/worktree-apply.js +122 -0
- package/src/worktree.js +50 -6
- package/templates/claude-instruction.md +1 -1
- package/templates/prompts/taskcard-rules.md +1 -0
- package/templates/prompts/verify-probes.md +2 -2
- package/templates/skills/sillyspec-onboard/SKILL.md +0 -92
package/src/run/complete.js
CHANGED
|
@@ -24,7 +24,7 @@ import { triggerSync, WAIT_MARKER_RE, getStageSteps, formatWaitOptions, resolveR
|
|
|
24
24
|
import { executeScanPreflight, executeScanPostcheck, computeScanProfile } from './scan-profile.js'
|
|
25
25
|
import { executePlanPostcheck as runPlanPostcheckLib } from '../stages/plan-postcheck.js'
|
|
26
26
|
import { outputStep } from './prompt.js'
|
|
27
|
-
import { enforceDepsGate, enforceReviewJsonGate, completeStageGates, readDesignScale } from './gates.js'
|
|
27
|
+
import { enforceDepsGate, enforceReviewJsonGate, enforceSymbolImpactGate, completeStageGates, readDesignScale } from './gates.js'
|
|
28
28
|
import { handleArchiveConfirmStep, handlePlanGeneratePlanStep, handleScanProjectListStep, handleWorkflowPostCheck, handleQuickStageCompletion, handleExecuteWaveArtifact } from './complete-handlers.js'
|
|
29
29
|
import { formatExecuteSummary } from '../worktree-apply.js'
|
|
30
30
|
import { isEndToEndTaskText } from '../change-risk-profile.js'
|
|
@@ -160,6 +160,9 @@ export async function completeStep(pm, progress, stageName, cwd, outputText, inp
|
|
|
160
160
|
// review.json 字段硬门(坑 review-json-field-gap):已勾 [x] task 的 review.json 必须 schema 完整,
|
|
161
161
|
// 提前到每次 --done 校验(而非等 Task Review Gate 在整阶段收尾才暴露,迫使用户事后批量补)。
|
|
162
162
|
await enforceReviewJsonGate(stageName, cwd, changeName, steps[currentIdx], steps, currentIdx, specBase, platformOpts)
|
|
163
|
+
// 符号影响面报告硬门(ql-20260816-005-3d7f):execute「加载上下文」步产出落盘核验——
|
|
164
|
+
// symbol-impact.md 存在 + plan 每 task 有结论行;防前缀步被一句「上下文在会话内」盖章跳过。
|
|
165
|
+
await enforceSymbolImpactGate(stageName, changeName, steps[currentIdx]?.name, specBase)
|
|
163
166
|
|
|
164
167
|
// ── noAI 步骤硬门(坑 noai-done-bypass):noAI 步骤的确定性校验不可被 --done 绕过 ──
|
|
165
168
|
// 正常路径 agent 跑 `run <stage>` 推进到 noAI step 时,runStage 自动执行 _cliAction
|
|
@@ -323,6 +326,9 @@ export async function completeStep(pm, progress, stageName, cwd, outputText, inp
|
|
|
323
326
|
console.warn('⚠️ 阶段完成 ctx 构造失败,降级单仓 gate(' + (e && e.message ? e.message : e) + ')')
|
|
324
327
|
}
|
|
325
328
|
const _stageGatesResult = await completeStageGates({ stageName, cwd, changeName, platformOpts, specBase, progress, pm, stageData, steps, currentIdx, outputText, ctx: _completeCtx })
|
|
329
|
+
// task-04 / A5:gate 失败(stageCompleted===false)设进程退出码 1,对齐 quick 审计 blocked→exit 1 惯例。
|
|
330
|
+
// 用 process.exitCode 而非 process.exit(1):让回滚 pm._write + triggerSync 落盘完成后自然退出,保留失败现场。
|
|
331
|
+
if (_stageGatesResult?.stageCompleted === false) process.exitCode = 1
|
|
326
332
|
if (_stageGatesResult) return _stageGatesResult
|
|
327
333
|
|
|
328
334
|
// 完整流程 change title 刷新:proposal/design 落盘后(brainstorm/plan 完成),从 proposal 首个 #
|
|
@@ -805,6 +811,8 @@ export async function continueStep(pm, progress, stageName, cwd, answer, options
|
|
|
805
811
|
console.warn('⚠️ 阶段完成 ctx 构造失败,降级单仓 gate(' + (e && e.message ? e.message : e) + ')')
|
|
806
812
|
}
|
|
807
813
|
const _stageGatesResult = await completeStageGates({ stageName, cwd, changeName, platformOpts, specBase, progress, pm, stageData, steps: stageData.steps, currentIdx, outputText: null, ctx: _contCtx })
|
|
814
|
+
// task-04 / A5:gate 失败(stageCompleted===false)设进程退出码 1(与 completeStep 完成分支同语义)。
|
|
815
|
+
if (_stageGatesResult?.stageCompleted === false) process.exitCode = 1
|
|
808
816
|
if (_stageGatesResult) return _stageGatesResult
|
|
809
817
|
// 完整流程 change title 刷新(同 completeStep 完成分支,wait 解除后阶段完成也刷新)。
|
|
810
818
|
if (changeName && !/^quick-[0-9a-f]{8}$/.test(changeName)) {
|
|
@@ -30,6 +30,28 @@ function normalizeGitPath(p) {
|
|
|
30
30
|
return String(p).replace(/\\/g, '/')
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* 并发预检 ownFiles 锚点(2026-08-18 误归属修复)。
|
|
35
|
+
*
|
|
36
|
+
* 未声明会话(allowedFiles 空)维持旧口径 changedFiles ∪ baselineFiles——无声明即无归属信息,
|
|
37
|
+
* 收窄锚点会把本会话每次未声明的自身改动全误报他者。声明会话锚点 = baselineFiles ∪ allowedFiles
|
|
38
|
+
* (声明即归属):窗口 diff(changedFiles)在多 agent 并发仓库可能含他者污染(ql-20260818-003
|
|
39
|
+
* 实证:并行会话窗口内改的文件被算进本会话 changedFiles 再被 ownFiles 自吞),超出声明集的
|
|
40
|
+
* 窗口新脏文件才作为 foreign 信号报出(= 他者或漏声明,歧义由 advisory 文案兜住)。
|
|
41
|
+
*
|
|
42
|
+
* 纯函数:不查盘不调 git,调用点(complete-handlers task-02 钩子)传入三个列表。
|
|
43
|
+
* @param {{ changedFiles?: string[], baselineFiles?: string[], allowedFiles?: string[] }} opts
|
|
44
|
+
* @returns {string[]} ownFiles 锚点集合(去重)
|
|
45
|
+
*/
|
|
46
|
+
export function resolveConcurrentAnchor({ changedFiles = [], baselineFiles = [], allowedFiles = [] } = {}) {
|
|
47
|
+
const arr = (x) => (Array.isArray(x) ? x : [])
|
|
48
|
+
const declared = arr(allowedFiles).map(normalizeGitPath).filter(Boolean)
|
|
49
|
+
if (declared.length === 0) {
|
|
50
|
+
return [...arr(changedFiles).map(normalizeGitPath), ...arr(baselineFiles).map(normalizeGitPath)]
|
|
51
|
+
}
|
|
52
|
+
return [...new Set([...arr(baselineFiles).map(normalizeGitPath), ...declared])]
|
|
53
|
+
}
|
|
54
|
+
|
|
33
55
|
/**
|
|
34
56
|
* 检测工作树里的并发他者改动(非阻塞 advisory 用)。
|
|
35
57
|
*
|
package/src/run/gates.js
CHANGED
|
@@ -27,6 +27,68 @@ import { handleScanStageCompleted, handleExecuteWorktreeCleanup } from './comple
|
|
|
27
27
|
import { detectConcurrentChanges, formatConcurrentWarning } from './concurrent-detect.js'
|
|
28
28
|
import { stageRegistry } from '../stages/index.js'
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* 从 plan.md 提取全部 task id(task-XX)——符号影响面覆盖度校验用。
|
|
32
|
+
* 只认 checkbox 行 `- [ ] task-XX:` / `- [x] task-XX:`(与 execute.js parseWavesFromPlan 的
|
|
33
|
+
* 收容口径一致:### 标题式 task / 非 task 区 checkbox 不算),light plan 的 `## Tasks` 段同样收。
|
|
34
|
+
*/
|
|
35
|
+
function extractTaskIdsFromPlan(planContent) {
|
|
36
|
+
if (!planContent) return []
|
|
37
|
+
return [...String(planContent).matchAll(/^[-*]\s*\[[ x]\]\s*(?:.*\b)?task-(\d+)\b/gim)]
|
|
38
|
+
.map(m => `task-${m[1].padStart(2, '0')}`)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 符号影响面报告结构校验(纯函数):execute「加载上下文」步的产出落盘核验。
|
|
43
|
+
*
|
|
44
|
+
* 治 persuasion-only 失效现场(ql-20260816-005-3d7f):execute 前缀 4 步无任何 gate,agent 可
|
|
45
|
+
* <1s 连发 4 次 --done 盖章跳过,「符号影响面扫描」(execute.js 操作 11)被一句「上下文在会话内」
|
|
46
|
+
* 带过(输出契约只写「上下文摘要」,操作与输出脱节)。修法 = 报告落盘
|
|
47
|
+
* {SPEC_ROOT}/changes/<change>/symbol-impact.md + 本函数校验:plan.md 每个 task-XX 在报告中出现
|
|
48
|
+
* (逐 task 结论,「无签名级变更」也要显式写,治整体一句话带过)。语义质量(调用点找没找全)
|
|
49
|
+
* 仍归 agent,CLI 只核结构覆盖度——符合债单「persuasion-only → 补最小硬门」原则。
|
|
50
|
+
*
|
|
51
|
+
* @param {{reportContent: string|null, planContent: string|null}} params
|
|
52
|
+
* @returns {{ok: boolean, errors: string[]}}
|
|
53
|
+
*/
|
|
54
|
+
export function validateSymbolImpactReport({ reportContent, planContent }) {
|
|
55
|
+
const errors = []
|
|
56
|
+
if (!reportContent || !String(reportContent).trim()) {
|
|
57
|
+
return { ok: false, errors: ['symbol-impact.md 不存在或内容为空——「加载上下文」步的符号影响面报告必须落盘(逐 task 一行结论,「无签名级变更」也要显式写)'] }
|
|
58
|
+
}
|
|
59
|
+
const taskIds = extractTaskIdsFromPlan(planContent)
|
|
60
|
+
if (taskIds.length === 0) return { ok: true, errors } // plan 无 task 可列(默认兜底形态)→ 无可校验对象
|
|
61
|
+
const missing = taskIds.filter(id => !String(reportContent).includes(id))
|
|
62
|
+
for (const id of missing) {
|
|
63
|
+
errors.push(`${id} 未在 symbol-impact.md 中出现——每个 task 都要有一行结论(含「无签名级变更」的显式声明)`)
|
|
64
|
+
}
|
|
65
|
+
return { ok: errors.length === 0, errors }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 符号影响面报告硬门:execute「加载上下文」步 --done 时校验 symbol-impact.md 落盘 + task 覆盖度。
|
|
70
|
+
* fail-closed:不通过 → 当前步标 blocked + exit 1(进度不推进)。非目标步骤/阶段直接放行。
|
|
71
|
+
*/
|
|
72
|
+
export async function enforceSymbolImpactGate(stageName, changeName, currentStepName, specBase) {
|
|
73
|
+
if (stageName !== 'execute') return
|
|
74
|
+
if (!currentStepName || !currentStepName.includes('加载上下文')) return
|
|
75
|
+
const planPath = join(specBase, 'changes', changeName || '', 'plan.md')
|
|
76
|
+
const reportPath = join(specBase, 'changes', changeName || '', 'symbol-impact.md')
|
|
77
|
+
if (!existsSync(planPath)) return // plan.md 缺失场景已有 plan 阶段 gate 把关,此处不重复拦
|
|
78
|
+
let planContent = ''
|
|
79
|
+
try { planContent = readFileSync(planPath, 'utf8') } catch { return }
|
|
80
|
+
let reportContent = null
|
|
81
|
+
try { reportContent = readFileSync(reportPath, 'utf8') } catch { /* 缺失由 validate 报 */ }
|
|
82
|
+
const result = validateSymbolImpactReport({ reportContent, planContent })
|
|
83
|
+
if (result.ok) return
|
|
84
|
+
console.error('❌ ── 符号影响面报告校验阻断(本次 --done 未完成,进度未推进)──')
|
|
85
|
+
console.error(` 「加载上下文」步的符号影响面检查报告未落盘或不完整:`)
|
|
86
|
+
console.error(` 期望路径:${reportPath}`)
|
|
87
|
+
for (const e of result.errors) console.error(` • ${e}`)
|
|
88
|
+
console.error(' 修复:按「加载上下文」步操作 11 完成符号影响面扫描,逐 task 写结论(无签名级变更也要显式写「无」),落盘后重跑 execute --done。')
|
|
89
|
+
process.exit(1)
|
|
90
|
+
}
|
|
91
|
+
|
|
30
92
|
/**
|
|
31
93
|
* 判断当前 execute step 所在 wave 是否全部 task 都声明 no_deps_verify: true(D-006@v2)。
|
|
32
94
|
* 仅 wave 执行步骤(名如 "Wave N 执行")可 opt-out;非 wave 步骤恒返回 false(保守过门)。
|
|
@@ -379,7 +441,11 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
|
|
|
379
441
|
if (!executeRunId) {
|
|
380
442
|
executeRunId = generateExecuteRunId()
|
|
381
443
|
// 落盘(marker 缺失时 fallback 生成后写盘,保证后续 checkbox/gate 读到同一 ID)
|
|
382
|
-
|
|
444
|
+
// D-001#1 fallback 写入点:mkdir execute-runs/<runId>/tasks 先于 marker(不变量:
|
|
445
|
+
// marker 在则目录在)。不 try/catch——异常直穿外层 catch 走 fail-closed 阻断
|
|
446
|
+
//(gate 自身写 run 目录失败不能静默放行完成)。
|
|
447
|
+
mkdirSync(join(runtimeRoot, 'execute-runs', executeRunId, 'tasks'), { recursive: true })
|
|
448
|
+
writeFileSync(runIdFile, executeRunId + '\n')
|
|
383
449
|
}
|
|
384
450
|
}
|
|
385
451
|
|