sillyspec 3.25.1 → 3.25.2
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-doctor/SKILL.md +9 -0
- package/.playwright-mcp/console-2026-07-27T15-45-50-621Z.log +1 -0
- package/.playwright-mcp/page-2026-07-27T15-45-51-459Z.yml +144 -0
- package/.playwright-mcp/prompt-final.png +0 -0
- package/.playwright-mcp/prompt-view.png +0 -0
- package/CLAUDE.md +18 -0
- package/package.json +1 -1
- package/src/change-risk-profile.js +60 -12
- package/src/check-primitives.js +65 -0
- package/src/index.js +135 -6
- package/src/progress/change-registry.js +217 -0
- package/src/progress/consistency-doctor.js +259 -0
- package/src/progress/shared.js +33 -0
- package/src/progress/stage-machine.js +535 -0
- package/src/progress/step-store.js +256 -0
- package/src/progress.js +59 -1191
- package/src/quicklog.js +3 -1
- package/src/run/command.js +127 -50
- package/src/run/complete-handlers.js +2 -1
- package/src/run/complete.js +725 -695
- package/src/run/gates.js +326 -321
- package/src/run/prompt.js +619 -604
- package/src/run/quick-audit.js +107 -102
- package/src/run/shared.js +43 -0
- package/src/stage-contract-engine.js +150 -0
- package/src/stage-contract-spec.js +517 -0
- package/src/stage-contract.js +83 -148
- package/src/stage-review.js +63 -0
- package/src/stages/brainstorm.js +3 -1
- package/src/stages/execute.js +5 -2
- package/src/stages/plan-postcheck.js +38 -27
- package/src/stages/plan.js +12 -49
- package/src/stages/propose.js +2 -1
- package/src/stages/scan.js +3 -1
- package/src/workflow.js +9 -17
- package/src/worktree-apply.js +5 -2
- package/src/worktree-deps.js +114 -32
- package/src/worktree.js +6 -3
- package/src/brainstorm-postcheck.js +0 -158
package/src/quicklog.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* 无新 npm 依赖(仅 fs/path/crypto)——匹配项目零 FS 工具依赖的风格。
|
|
12
12
|
*/
|
|
13
13
|
import { join, dirname, basename } from 'path'
|
|
14
|
+
import { getRule } from './stage-contract-spec.js'
|
|
14
15
|
import {
|
|
15
16
|
openSync, closeSync, unlinkSync, statSync, mkdirSync, existsSync,
|
|
16
17
|
readFileSync, writeFileSync, readdirSync, renameSync, appendFileSync,
|
|
@@ -87,7 +88,8 @@ function sanitizeResult(resultText) {
|
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
// 结果块必填字段(quick step3 --done --output 的结构化结果模板,见 src/stages/quick.js)。
|
|
90
|
-
|
|
91
|
+
// 4 个必填字段标签从 manifest 同源(stage-contract-spec.js quick.result-labels),prompt 事前契约与本校验单源。
|
|
92
|
+
const RESULT_REQUIRED_LABELS = getRule('quick.result-labels').data.literals
|
|
91
93
|
|
|
92
94
|
/**
|
|
93
95
|
* quick step3 结果摘要结构校验(确定性:只查「必填字段是否都在」,不判内容质量——
|
package/src/run/command.js
CHANGED
|
@@ -1,38 +1,48 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* run/command.js(W6 Step8c 从 run.js 抽出)。
|
|
3
|
-
*
|
|
4
|
-
* 命令分发层:runCommand(主入口,被 src/index.js + 6 个 test import)+ auto 模式 +
|
|
5
|
-
* ensureStageSteps(被 step-migration test import)+ 4 个 runCommand 私有 helper
|
|
6
|
-
* (resolveChangeName / resolveChangeNameAuto / showStatus / resetStage)。
|
|
7
|
-
*
|
|
8
|
-
* 调用图(单向,无环):runCommand → { runStage(stage.js), runAutoMode, resetStage, showStatus,
|
|
9
|
-
* ensureStageSteps, completeStep/skip/wait/continue(complete.js) };runAutoMode → { outputStep,
|
|
10
|
-
* completeStep, ensureStageSteps, checkApproval(shared) },不调 runStage(内联 outputStep)。
|
|
11
|
-
*
|
|
12
|
-
* 安全锚:run.js 始终 barrel。runCommand + ensureStageSteps 被 test 直接 import → run.js barrel
|
|
13
|
-
* re-export(契约保留);其余 5 函数 runCommand 私有,无 test 直接 import。
|
|
14
|
-
*
|
|
15
|
-
* 路径修正(相对 src/run/):
|
|
16
|
-
* - 动态 import './init.js' / './worktree.js' / './classify-change.js' → '../'(src/ 下退一层)
|
|
17
|
-
* - 'fs'(runCommand 内 await import('fs'))裸模块名不变
|
|
18
|
-
* - 静态 import './progress.js' / './stage-contract.js' / './stages/*' / './fs-atomic.js' → '../'
|
|
19
|
-
* './run/shared.js' / './run/quick-audit.js' / './run/prompt.js' / './run/complete.js' /
|
|
20
|
-
* './run/stage.js' → './shared.js' 等同级
|
|
21
|
-
*/
|
|
22
|
-
import { join, resolve, dirname } from 'node:path'
|
|
23
|
-
import { existsSync, readdirSync, mkdirSync, writeFileSync, readFileSync, rmSync } from 'node:fs'
|
|
24
|
-
import { randomBytes, randomUUID } from 'node:crypto'
|
|
25
|
-
import { writeAtomicSync } from '../fs-atomic.js'
|
|
26
|
-
import { resolveSpecDir, resolveChangeDir, triggerSync, getStageSteps, formatWaitOptions, checkApproval } from './shared.js'
|
|
27
|
-
import { resolveQuickLinkedChanges } from './quick-audit.js'
|
|
28
|
-
import { outputStep } from './prompt.js'
|
|
29
|
-
import { completeStep, skipStep, waitStep, continueStep } from './complete.js'
|
|
30
|
-
import { runStage } from './stage.js'
|
|
31
|
-
import { ProgressManager } from '../progress.js'
|
|
32
|
-
import { validateChangeExists } from '../stage-contract.js'
|
|
33
|
-
import { stageRegistry, auxiliaryStages } from '../stages/index.js'
|
|
34
|
-
import { definition as brainstormAutoDef } from '../stages/brainstorm-auto.js'
|
|
35
|
-
|
|
1
|
+
/**
|
|
2
|
+
* run/command.js(W6 Step8c 从 run.js 抽出)。
|
|
3
|
+
*
|
|
4
|
+
* 命令分发层:runCommand(主入口,被 src/index.js + 6 个 test import)+ auto 模式 +
|
|
5
|
+
* ensureStageSteps(被 step-migration test import)+ 4 个 runCommand 私有 helper
|
|
6
|
+
* (resolveChangeName / resolveChangeNameAuto / showStatus / resetStage)。
|
|
7
|
+
*
|
|
8
|
+
* 调用图(单向,无环):runCommand → { runStage(stage.js), runAutoMode, resetStage, showStatus,
|
|
9
|
+
* ensureStageSteps, completeStep/skip/wait/continue(complete.js) };runAutoMode → { outputStep,
|
|
10
|
+
* completeStep, ensureStageSteps, checkApproval(shared) },不调 runStage(内联 outputStep)。
|
|
11
|
+
*
|
|
12
|
+
* 安全锚:run.js 始终 barrel。runCommand + ensureStageSteps 被 test 直接 import → run.js barrel
|
|
13
|
+
* re-export(契约保留);其余 5 函数 runCommand 私有,无 test 直接 import。
|
|
14
|
+
*
|
|
15
|
+
* 路径修正(相对 src/run/):
|
|
16
|
+
* - 动态 import './init.js' / './worktree.js' / './classify-change.js' → '../'(src/ 下退一层)
|
|
17
|
+
* - 'fs'(runCommand 内 await import('fs'))裸模块名不变
|
|
18
|
+
* - 静态 import './progress.js' / './stage-contract.js' / './stages/*' / './fs-atomic.js' → '../'
|
|
19
|
+
* './run/shared.js' / './run/quick-audit.js' / './run/prompt.js' / './run/complete.js' /
|
|
20
|
+
* './run/stage.js' → './shared.js' 等同级
|
|
21
|
+
*/
|
|
22
|
+
import { join, resolve, dirname } from 'node:path'
|
|
23
|
+
import { existsSync, readdirSync, mkdirSync, writeFileSync, readFileSync, rmSync } from 'node:fs'
|
|
24
|
+
import { randomBytes, randomUUID } from 'node:crypto'
|
|
25
|
+
import { writeAtomicSync } from '../fs-atomic.js'
|
|
26
|
+
import { resolveSpecDir, resolveChangeDir, triggerSync, getStageSteps, formatWaitOptions, checkApproval, didYouMean, assertSafeChangeName } from './shared.js'
|
|
27
|
+
import { resolveQuickLinkedChanges } from './quick-audit.js'
|
|
28
|
+
import { outputStep } from './prompt.js'
|
|
29
|
+
import { completeStep, skipStep, waitStep, continueStep } from './complete.js'
|
|
30
|
+
import { runStage } from './stage.js'
|
|
31
|
+
import { ProgressManager } from '../progress.js'
|
|
32
|
+
import { validateChangeExists } from '../stage-contract.js'
|
|
33
|
+
import { stageRegistry, auxiliaryStages } from '../stages/index.js'
|
|
34
|
+
import { definition as brainstormAutoDef } from '../stages/brainstorm-auto.js'
|
|
35
|
+
|
|
36
|
+
// F2/F4: 哪些 flag 吃下一个 token 作「值」(其余 --flag 都是布尔,不吞值)。
|
|
37
|
+
// 校验循环只对 VALUE_FLAGS 跳下一个 token(否则 --done 后跟 typo 会被当 --done 的值吞掉)。
|
|
38
|
+
// getFlagValue 也据此拒收「下一个 token 是另一个 flag 名」当值。
|
|
39
|
+
const VALUE_FLAGS = new Set([
|
|
40
|
+
'--reason', '--options', '--answer', '--confirm-mode',
|
|
41
|
+
'--output', '--input', '--change', '--linked-changes',
|
|
42
|
+
'--spec-dir', '--spec-root', '--runtime-root', '--workspace-id', '--scan-run-id',
|
|
43
|
+
'--files', '--from-step', '--mode', '--dir', '--confirm-mode',
|
|
44
|
+
])
|
|
45
|
+
|
|
36
46
|
/**
|
|
37
47
|
* 从 progress 或变更目录推导变更名
|
|
38
48
|
*/
|
|
@@ -45,7 +55,7 @@ function resolveChangeName(cwd, progress, specDir = null) {
|
|
|
45
55
|
if (entries.length === 1) return entries[0].name
|
|
46
56
|
return null
|
|
47
57
|
}
|
|
48
|
-
|
|
58
|
+
|
|
49
59
|
/**
|
|
50
60
|
* 确保阶段的 steps 已初始化到 progress
|
|
51
61
|
*/
|
|
@@ -86,7 +96,7 @@ export async function ensureStageSteps(progress, stageName, cwd, specDir = null)
|
|
|
86
96
|
|
|
87
97
|
return false
|
|
88
98
|
}
|
|
89
|
-
|
|
99
|
+
|
|
90
100
|
/**
|
|
91
101
|
* sillyspec run <stage> 主命令
|
|
92
102
|
*/
|
|
@@ -101,7 +111,19 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
|
|
|
101
111
|
}
|
|
102
112
|
// 解析参数
|
|
103
113
|
const stageName = args[0]
|
|
104
|
-
|
|
114
|
+
let flags = args.slice(1) // let:下方规范化 --k=v 会改写
|
|
115
|
+
|
|
116
|
+
// F7: 规范化 --key=value → ['--key', 'value']。许多 CLI 通用等号语法此前被整 token 当未知参数拒绝。
|
|
117
|
+
// 只对值类 flag 展开(见 VALUE_FLAGS);布尔 flag 不吃值,--skip-approval=x 这种无意义,原样进校验报错。
|
|
118
|
+
flags = flags.flatMap(tok => {
|
|
119
|
+
if (typeof tok === 'string' && tok.startsWith('--') && tok.includes('=')) {
|
|
120
|
+
const eq = tok.indexOf('=')
|
|
121
|
+
const key = tok.slice(0, eq)
|
|
122
|
+
const val = tok.slice(eq + 1)
|
|
123
|
+
if (VALUE_FLAGS.has(key)) return [key, val]
|
|
124
|
+
}
|
|
125
|
+
return [tok]
|
|
126
|
+
})
|
|
105
127
|
|
|
106
128
|
if (!stageName) {
|
|
107
129
|
console.error('❌ 请指定阶段,例如: sillyspec run brainstorm')
|
|
@@ -135,7 +157,12 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
|
|
|
135
157
|
// --spec-dir 是统一参数名,--spec-root 保留为向后兼容别名
|
|
136
158
|
const getFlagValue = (name) => {
|
|
137
159
|
const idx = flags.indexOf(name)
|
|
138
|
-
|
|
160
|
+
if (idx === -1) return null
|
|
161
|
+
const next = flags[idx + 1]
|
|
162
|
+
// F4: 下一个 token 若是另一个 flag 名(--xxx),说明本 flag 漏值,
|
|
163
|
+
// 不能把 flag 名当值返回(否则 --change --done → changeName="--done" 一路污染)。
|
|
164
|
+
if (!next || (typeof next === 'string' && next.startsWith('--'))) return null
|
|
165
|
+
return next
|
|
139
166
|
}
|
|
140
167
|
const isDone = flags.includes('--done')
|
|
141
168
|
const isSkip = flags.includes('--skip')
|
|
@@ -149,6 +176,27 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
|
|
|
149
176
|
const isContinue = flags.includes('--continue')
|
|
150
177
|
const isNonInteractive = flags.includes('--non-interactive')
|
|
151
178
|
const isInteractive = flags.includes('--interactive')
|
|
179
|
+
// F3 冲突 flag 检测:步骤/阶段动作 flag 互斥——各自定义「对当前步骤做什么」,同时给 2+ 个
|
|
180
|
+
// 语义矛盾。旧实现按代码里 if 顺序静默取先命中的、其余被忽略,agent 以为生效的其实没生效
|
|
181
|
+
// (如 --done --reset 只 reset 不 done,agent 却以为完成了步骤)。显式报冲突让 agent 选一个。
|
|
182
|
+
const STEP_ACTIONS = [
|
|
183
|
+
['--done', isDone, '完成当前步骤'],
|
|
184
|
+
['--skip', isSkip, '跳过当前步骤'],
|
|
185
|
+
['--reset', isReset, '重置整个阶段(从头开始)'],
|
|
186
|
+
['--reopen', isReopen, '重新打开已完成阶段进入修订(配 --from-step)'],
|
|
187
|
+
['--wait', isWait, '暂停等用户决策'],
|
|
188
|
+
['--continue', isContinue, '恢复等待中的步骤'],
|
|
189
|
+
['--status', isStatus, '查看进度(只读)'],
|
|
190
|
+
]
|
|
191
|
+
const activeActions = STEP_ACTIONS.filter(([, v]) => v)
|
|
192
|
+
if (activeActions.length >= 2) {
|
|
193
|
+
console.error('❌ 步骤动作参数冲突(同时只能指定一个):')
|
|
194
|
+
for (const [name, , desc] of activeActions) console.error(` ${name} — ${desc}`)
|
|
195
|
+
console.error(' 这些动作互斥,请只保留其中一个再重试。')
|
|
196
|
+
process.exit(2)
|
|
197
|
+
}
|
|
198
|
+
// 注:--non-interactive 与 --interactive 的冲突在 index.js 检测(--interactive 被 index.js
|
|
199
|
+
// 作为全局 flag 吞掉、不透传到此处,故 isInteractive 在此恒为 false)。
|
|
152
200
|
const waitReason = getFlagValue('--reason')
|
|
153
201
|
const waitOptions = getFlagValue('--options')
|
|
154
202
|
const continueAnswer = getFlagValue('--answer')
|
|
@@ -307,6 +355,19 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
|
|
|
307
355
|
if (explicitLinked !== null) {
|
|
308
356
|
linkedChanges = explicitLinked
|
|
309
357
|
}
|
|
358
|
+
// F6 路径穿越消毒:change 名会被 join 进 .sillyspec/changes/<name>/ 当目录名,
|
|
359
|
+
// 含 ../ 或路径分隔符会逃出 changes/ 写到任意位置。quick 的 --change 复用为 sessionId
|
|
360
|
+
// (quick-<8hex>,正则已约束) 或 linkedChanges,故 quick 的 changeName 不在此校验。
|
|
361
|
+
try {
|
|
362
|
+
if (stageName !== 'quick' && changeName) assertSafeChangeName(changeName)
|
|
363
|
+
for (const lc of linkedChanges) {
|
|
364
|
+
if (lc && lc !== 'none') assertSafeChangeName(lc, '关联变更名(--linked-changes)')
|
|
365
|
+
}
|
|
366
|
+
} catch (e) {
|
|
367
|
+
console.error(`❌ ${e.message}`)
|
|
368
|
+
console.error(` 合法变更名示例:2026-07-27-add-login(仅字母/数字/._-,不含 / \\ ..)`)
|
|
369
|
+
process.exit(2)
|
|
370
|
+
}
|
|
310
371
|
// quick 会话隔离(D-001@v1 + D-003@v1 + §4.4 跨进程传递):每会话用 sessionId 作 changeName,
|
|
311
372
|
// DB 分行 progress.quick-<uuid8>,避免并行 quick 会话共享单行 progress.default.quick 互相覆盖。
|
|
312
373
|
// sessionId = crypto.randomUUID 前 8 hex(摒弃旧 quick-YYYYMMDD-HHMMSS 时间戳,同秒并发撞)。
|
|
@@ -376,17 +437,22 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
|
|
|
376
437
|
'--files', '--allow-new', '--force-baseline', '--force-rescan',
|
|
377
438
|
'--json', '--dir', '--help',
|
|
378
439
|
'--reopen', '--from-step', '--mode',
|
|
440
|
+
'--deep', // F1: scan-profile.js 从 argv 读 --deep,文档化(index.js help)但此前 knownFlags 漏列→被拒
|
|
379
441
|
])
|
|
380
442
|
for (let i = 0; i < flags.length; i++) {
|
|
381
443
|
const f = flags[i]
|
|
382
444
|
if (f.startsWith('--')) {
|
|
383
445
|
if (!knownFlags.has(f)) {
|
|
446
|
+
// F10: flag 级 did-you-mean(此前只命令级有)
|
|
447
|
+
const suggestion = didYouMean(f, [...knownFlags])
|
|
384
448
|
console.error(`❌ 未知参数: ${f}`)
|
|
385
|
-
console.error(
|
|
449
|
+
if (suggestion) console.error(` 你是想输入「${suggestion}」吗?`)
|
|
450
|
+
else console.error(`已知参数: ${[...knownFlags].sort().join(', ')}`)
|
|
386
451
|
process.exit(2) // 用法错 → exit 2
|
|
387
452
|
}
|
|
388
|
-
//
|
|
389
|
-
|
|
453
|
+
// F2: 只有吃值的 flag 才跳下一个 token。布尔 flag(--done 等)不能 i++——
|
|
454
|
+
// 否则会把紧跟的 typo flag 当成 --done 的「值」吞掉,既不校验也不生效(静默忽略)。
|
|
455
|
+
if (VALUE_FLAGS.has(f)) i++
|
|
390
456
|
}
|
|
391
457
|
}
|
|
392
458
|
|
|
@@ -626,7 +692,11 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
|
|
|
626
692
|
|
|
627
693
|
// --status
|
|
628
694
|
if (isStatus) {
|
|
629
|
-
|
|
695
|
+
// D9:状态展示末尾附「下一步该跑什么」。pm 持有状态机,按 progress 实际进度算出精确命令;
|
|
696
|
+
// 不传则 agent 只看到一堆 step 进度条,却不知道完成后/卡住时下一条命令是什么。
|
|
697
|
+
let nextSuggestion = null
|
|
698
|
+
try { nextSuggestion = pm._getNextSuggestion(progress) } catch { /* 读建议失败不阻断状态展示 */ }
|
|
699
|
+
return showStatus(progress, stageName, nextSuggestion)
|
|
630
700
|
}
|
|
631
701
|
|
|
632
702
|
// --skip
|
|
@@ -641,7 +711,7 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
|
|
|
641
711
|
|
|
642
712
|
// --continue: 从 waiting 恢复
|
|
643
713
|
if (isContinue) {
|
|
644
|
-
return await continueStep(pm, progress, stageName, cwd, continueAnswer, { changeName: effectiveChange, nonInteractive: isNonInteractive && !isInteractive, platformOpts })
|
|
714
|
+
return await continueStep(pm, progress, stageName, cwd, continueAnswer, { changeName: effectiveChange, nonInteractive: isNonInteractive && !isInteractive, platformOpts, fromStep: fromStepValue })
|
|
645
715
|
}
|
|
646
716
|
|
|
647
717
|
// --done
|
|
@@ -653,7 +723,7 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
|
|
|
653
723
|
// 默认:输出当前步骤
|
|
654
724
|
return await runStage(pm, progress, stageName, cwd, effectiveChange, isSkipApproval, platformOpts, { quickFiles, isAllowNew, isForceBaseline, isForceRescan, linkedChanges, taskDescription: inputText })
|
|
655
725
|
}
|
|
656
|
-
|
|
726
|
+
|
|
657
727
|
/**
|
|
658
728
|
* 自动推导变更名(不依赖 progress)
|
|
659
729
|
*/
|
|
@@ -665,8 +735,8 @@ function resolveChangeNameAuto(cwd, specDir = null) {
|
|
|
665
735
|
if (entries.length === 1) return entries[0].name
|
|
666
736
|
return null
|
|
667
737
|
}
|
|
668
|
-
|
|
669
|
-
function showStatus(progress, stageName) {
|
|
738
|
+
|
|
739
|
+
function showStatus(progress, stageName, nextSuggestion = null) {
|
|
670
740
|
const stageData = progress.stages[stageName]
|
|
671
741
|
const stageDef = stageRegistry[stageName]
|
|
672
742
|
|
|
@@ -733,8 +803,15 @@ function showStatus(progress, stageName) {
|
|
|
733
803
|
if (step.waitedAt) console.log(` 等待时间:${step.waitedAt}`)
|
|
734
804
|
}
|
|
735
805
|
})
|
|
806
|
+
|
|
807
|
+
// D9:本阶段进度之后,给出全局「下一步该跑什么」——
|
|
808
|
+
// 否则 agent 看完进度条仍要自己推状态机(尤其阶段已完成 / 失效 / 修订中这类非线性状态)。
|
|
809
|
+
if (nextSuggestion && nextSuggestion.command) {
|
|
810
|
+
console.log(`\n👉 ${nextSuggestion.text}`)
|
|
811
|
+
console.log(` 下一步命令:${nextSuggestion.command}`)
|
|
812
|
+
}
|
|
736
813
|
}
|
|
737
|
-
|
|
814
|
+
|
|
738
815
|
async function resetStage(pm, progress, stageName, cwd, changeName, platformOpts = {}) {
|
|
739
816
|
// execute 阶段 reset 时清理自建 worktree,否则下次 run execute 会因 existingMeta 存在
|
|
740
817
|
// 直接复用带脏状态的旧 worktree(启动逻辑:meta 存在即复用,不查健康状态)
|
|
@@ -770,7 +847,7 @@ async function resetStage(pm, progress, stageName, cwd, changeName, platformOpts
|
|
|
770
847
|
triggerSync(cwd, changeName, platformOpts)
|
|
771
848
|
console.log(`🔄 ${stageName} 阶段已重置`)
|
|
772
849
|
}
|
|
773
|
-
|
|
850
|
+
|
|
774
851
|
/**
|
|
775
852
|
* auto 模式:自动推进 brainstorm → plan → execute → verify
|
|
776
853
|
*/
|
|
@@ -1008,4 +1085,4 @@ async function runAutoMode(pm, progress, cwd, flags, changeName, platformOpts =
|
|
|
1008
1085
|
await outputStep(next, firstPending, nextSteps, cwd, changeName, progress.project || null, platformOpts)
|
|
1009
1086
|
}
|
|
1010
1087
|
}
|
|
1011
|
-
|
|
1088
|
+
|
|
@@ -28,6 +28,7 @@ import { stageRegistry } from '../stages/index.js'
|
|
|
28
28
|
import { SCAN_STATUS, POINTER_STATUS } from '../constants.js'
|
|
29
29
|
import { printQuickAuditReview } from './quick-audit.js'
|
|
30
30
|
import { validateQuickResult, allocateQuicklogEntry, findQuicklogEntry, completeQuicklogEntry } from '../quicklog.js'
|
|
31
|
+
import { getRule } from '../stage-contract-spec.js'
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
34
|
* 清洗项目名:只保留 ASCII 字母/数字/横线/下划线/点,过滤中文和特殊字符。
|
|
@@ -588,7 +589,7 @@ export async function handleQuickStageCompletion({ stageName, steps, currentIdx,
|
|
|
588
589
|
if (outputText) {
|
|
589
590
|
const resultCheck = validateQuickResult(outputText)
|
|
590
591
|
if (!resultCheck.ok) {
|
|
591
|
-
console.error(
|
|
592
|
+
console.error('\n' + getRule('quick.result-labels').failMessage.replaceAll('${missing}', resultCheck.missing.join('、')))
|
|
592
593
|
console.error(` --output 是 QUICKLOG「结果:」归档的唯一来源,四个标签必须放在 --output 里(不是 --input)。`)
|
|
593
594
|
console.error(` 补全后重跑 --done(不丢进度),直接照抄此模板:`)
|
|
594
595
|
console.error(` sillyspec run quick --done --change <changeName> --output "需求:用户/任务要什么`)
|