sillyspec 3.29.5 → 3.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/sillyspec-auto/SKILL.md +54 -54
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +2 -2
- package/.claude/skills/sillyspec-commit/SKILL.md +1 -1
- package/.claude/skills/sillyspec-flow/SKILL.md +57 -0
- package/.claude/skills/sillyspec-quick/SKILL.md +18 -116
- package/AGENTS.md +38 -20
- package/SKILL.md +9 -3
- package/assets/command-cards/flow.md +32 -0
- package/assets/command-cards/run-brainstorm.md +1 -1
- package/assets/command-cards/run-quick.md +8 -25
- package/package.json +2 -1
- package/packages/dashboard/server/executor.js +2 -1
- package/packages/dashboard/server/parser.js +7 -7
- package/r7-test-env/README-R7-TEST.md +93 -0
- package/r7-test-env/demo-project/lib/calc.js +6 -0
- package/r7-test-env/demo-project/package.json +1 -0
- package/r7-test-env/demo-project/test.js +10 -0
- package/r7-test-env/prompt-R7-SMOKE.md +41 -0
- package/r7-test-env/sillyspec-r7.cmd +3 -0
- package/round4/account.mjs +18 -5
- package/round4/b1-analyze-task08.mjs +40 -0
- package/round4/b1-b1-target-validation.md +32 -0
- package/round4/b3a-prompt-audit-result.md +66 -0
- package/round4/b3a-prompt-audit.mjs +89 -0
- package/round4/optimization-plan.md +165 -0
- package/round4/r4-final-report.html +347 -0
- package/round4/r5-scenario-matrix.md +46 -0
- package/src/change-list.js +9 -0
- package/src/classify-change.js +6 -0
- package/src/command-cards.js +2 -1
- package/src/commit-guard.js +2 -0
- package/src/config-schema.js +64 -12
- package/src/db.js +35 -4
- package/src/decision-distill.js +9 -3
- package/src/docs-check.js +1 -1
- package/src/doctor-diagnostics.js +44 -2
- package/src/flow-draft.js +748 -0
- package/src/flow-parity.js +236 -0
- package/src/flow-review.js +184 -0
- package/src/flow.js +1492 -0
- package/src/foreign-declared.js +17 -1
- package/src/fr-index.js +396 -22
- package/src/fs-atomic.js +5 -1
- package/src/git-helper.js +2 -2
- package/src/greenfield-bootstrap.js +98 -0
- package/src/handoff.js +120 -7
- package/src/hunk-attribution.js +191 -0
- package/src/index.js +570 -16
- package/src/knowledge-classify.js +47 -1
- package/src/knowledge-digest.js +162 -0
- package/src/knowledge-quicklog.js +246 -0
- package/src/machine-draft.js +128 -0
- package/src/machine-interface.js +107 -5
- package/src/mcp-server.js +1 -0
- package/src/module-resolve.js +3 -1
- package/src/preview-progress.js +160 -0
- package/src/progress/change-registry.js +11 -3
- package/src/progress/shared.js +3 -2
- package/src/progress.js +56 -13
- package/src/quicklog.js +44 -6
- package/src/redomain.js +147 -0
- package/src/review-material-pack.js +204 -1
- package/src/run/archive-distill.js +4 -2
- package/src/run/bg-sync.js +67 -0
- package/src/run/command.js +123 -10
- package/src/run/complete-handlers.js +327 -72
- package/src/run/complete.js +350 -71
- package/src/run/gate-snapshot-ledger.js +213 -0
- package/src/run/gate-snapshot.js +250 -34
- package/src/run/gates.js +370 -233
- package/src/run/green-cache.js +123 -0
- package/src/run/junction-rm.js +70 -0
- package/src/run/next.js +14 -0
- package/src/run/prompt.js +185 -55
- package/src/run/quick-audit.js +185 -12
- package/src/run/shared.js +120 -3
- package/src/run/stage.js +182 -216
- package/src/run/test-ledger.js +264 -0
- package/src/run/verify-quality-scan.js +84 -7
- package/src/scope-audit.js +6 -1
- package/src/sentinel-assertions.js +114 -0
- package/src/setup.js +3 -3
- package/src/spec-sync.js +41 -1
- package/src/stage-contract.js +26 -8
- package/src/stage-review-checklist.js +2 -0
- package/src/stage-review.js +7 -2
- package/src/stage-templates.js +157 -0
- package/src/stages/archive.js +2 -0
- package/src/stages/brainstorm.js +24 -106
- package/src/stages/execute.js +221 -84
- package/src/stages/knowledge.js +74 -1
- package/src/stages/plan-postcheck.js +352 -1
- package/src/stages/plan.js +23 -1
- package/src/stages/quick.js +10 -2
- package/src/stages/verify.js +4 -2
- package/src/sync.js +81 -23
- package/src/task-done.js +181 -0
- package/src/task-review.js +8 -55
- package/src/test-bindings.js +549 -0
- package/src/timeline.js +218 -0
- package/src/ui-visual.js +175 -0
- package/src/verify-draft.js +214 -0
- package/src/verify-postcheck.js +860 -158
- package/src/verify-probes.js +59 -3
- package/src/watcher.js +1031 -0
- package/src/worktree-apply.js +44 -4
- package/src/worktree-deps.js +22 -8
- package/src/worktree-policy.js +42 -0
- package/src/worktree.js +102 -15
- package/templates/agents-instruction.md +37 -18
package/src/run/prompt.js
CHANGED
|
@@ -18,14 +18,39 @@
|
|
|
18
18
|
* - 删除 outputStep 内死代码 `const { execSync } = await import('child_process')`(execSync 解构未用,实际走 safeGit)
|
|
19
19
|
* - loadModuleContextIndex/buildModuleContextInjection 内 require('fs'/'path') 改顶部静态 import
|
|
20
20
|
*/
|
|
21
|
-
import { basename, join } from 'node:path'
|
|
22
|
-
import { existsSync, readFileSync, mkdirSync, readdirSync } from 'node:fs'
|
|
21
|
+
import { basename, dirname, join } from 'node:path'
|
|
22
|
+
import { existsSync, readFileSync, mkdirSync, readdirSync, writeFileSync } from 'node:fs'
|
|
23
23
|
import { createHash } from 'node:crypto'
|
|
24
24
|
import jsYaml from 'js-yaml'
|
|
25
25
|
import { writeAtomicSync } from '../fs-atomic.js'
|
|
26
26
|
import { stageRegistry } from '../stages/index.js'
|
|
27
27
|
import { resolvePromptIncludes, resolveRuntimeRoot, safeGit, parsePorcelainPath, WAIT_MARKER_RE, QUICK_SID_RE, triggerStepStartSync } from './shared.js'
|
|
28
|
+
import { materializeStageTemplates } from '../stage-templates.js'
|
|
28
29
|
import { renderSemanticGuardBlock, readSemanticGuardEnabled } from '../semantic-guard.js'
|
|
30
|
+
|
|
31
|
+
// ── M1 步骤指引静态段指纹(2026-09-21-r5-efficiency-batch2 task-01 / D-001@v1)──
|
|
32
|
+
// 易变占位符全集(值因运行而异):指纹掩码 + 复入附录照常渲染。新增易变占位符时同步此表
|
|
33
|
+
// (漏登会令指纹误变——退化后果只是多一次全量重印,不产生错误指引;多登同理只多印)。
|
|
34
|
+
const VOLATILE_PLACEHOLDER_RES = [
|
|
35
|
+
/<now-datetime>/g, /<now-timestamp>/g, /<now-date>/g, /<now-iso-datetime>/g, /<git-head-short>/g, /<quicklog-id>/g,
|
|
36
|
+
/{SCAN_STALENESS}/g, /{SCAN_FACTS}/g, /{QUICK_CONTEXT_DIGEST}/g, /{DECISION_HITS}/g,
|
|
37
|
+
/{KNOWLEDGE_HIT_REPORT}/g, /{DOCS_DEBT}/g, /{MODULE_RESOLVE_TABLE}/g, /{REVIEW_MATERIALS}/g,
|
|
38
|
+
/{EXECUTE_RUN_ID}/g, /{STAGE_REVIEW_RUN_ID}/g, /{PROGRESS_SNAPSHOT}/g, /{TASK_COMPLETION_REPORT}/g,
|
|
39
|
+
/{WORKTREE_META}/g, /{WORKTREE_BASELINE_INFO}/g, /{TASKS_CHECKBOX}/g, /{GIT_DIRTY}/g,
|
|
40
|
+
/{SCOPE_AUDIT_TABLE}/g, /{FR_INDEX_DIGEST}/g, /{HANDOVER_SUMMARY}/g, /{PREFLIGHT_FAILURES}/g,
|
|
41
|
+
/{PRIOR_REVIEW_FACTS}/g, /{ARCHIVE_IMPACT_AUDIT}/g, /{EVIDENCE_AUTO_RECOMMENDATION}/g,
|
|
42
|
+
]
|
|
43
|
+
const VOLATILE_MASK = '⟦dyn⟧'
|
|
44
|
+
function maskVolatileForGuide(text) {
|
|
45
|
+
let out = text
|
|
46
|
+
for (const re of VOLATILE_PLACEHOLDER_RES) out = out.replace(re, VOLATILE_MASK)
|
|
47
|
+
return out
|
|
48
|
+
}
|
|
49
|
+
/** M1:步骤指引静态段指纹——模板原文(include 解析后、占位符替换前)掩码易变占位符后的 sha256。
|
|
50
|
+
* 确定性:同模板同指纹;模板文本/静态占位符变更即变;易变占位符的值不参与(值在替换后,不在此面)。 */
|
|
51
|
+
export function computeStepGuideFingerprint(template) {
|
|
52
|
+
return createHash('sha256').update(maskVolatileForGuide(String(template))).digest('hex')
|
|
53
|
+
}
|
|
29
54
|
import { renderStageContract } from '../stage-contract-spec.js'
|
|
30
55
|
import { nowWallClock } from '../datetime.js'
|
|
31
56
|
import { parseModuleMapSimple } from '../modules.js'
|
|
@@ -35,6 +60,7 @@ import { REVIEW_SCHEMA_VERSION, isValidExecuteRunId } from '../task-review.js'
|
|
|
35
60
|
// 静态 import 不引入环(stages/* 反向引用本文件会撞 index.js 顶层 stageRegistry TDZ——见
|
|
36
61
|
// buildKnowledgeInjection docstring)。
|
|
37
62
|
import { matchKnowledge } from '../knowledge-match.js'
|
|
63
|
+
import { matchQuicklogContext } from '../knowledge-quicklog.js'
|
|
38
64
|
import { appendKnowledgeHit } from '../knowledge-hits.js'
|
|
39
65
|
import { readActiveFrDigest } from '../fr-index.js'
|
|
40
66
|
// 前置失败清单 validator 面(2026-09-18-preflight-slimming task-01):design-facts /
|
|
@@ -163,9 +189,10 @@ export function loadModuleContextIndex(specBase, projectName) {
|
|
|
163
189
|
* @param {object} moduleIndex - loadModuleContextIndex 返回值
|
|
164
190
|
* @param {string} specBase - 规范目录
|
|
165
191
|
* @param {string} projectName - 项目名
|
|
192
|
+
* @param {object} [opts] - { change }:变更名 / quick sessionId(docs-inject 遥测 change 字段;缺省 '')
|
|
166
193
|
* @returns {{text: string, frModules: Array<{id: string, count: number}>}} 上下文注入文本(空=无匹配模块)+ 有活需求的模块清单(供遥测)
|
|
167
194
|
*/
|
|
168
|
-
export function buildModuleContextInjection(taskDescription, moduleIndex, specBase, projectName) { if (!moduleIndex || !taskDescription) return { text: '', frModules: [] }
|
|
195
|
+
export function buildModuleContextInjection(taskDescription, moduleIndex, specBase, projectName, opts = {}) { if (!moduleIndex || !taskDescription) return { text: '', frModules: [] }
|
|
169
196
|
|
|
170
197
|
const taskLower = taskDescription.toLowerCase()
|
|
171
198
|
const matched = []
|
|
@@ -237,6 +264,21 @@ export function buildModuleContextInjection(taskDescription, moduleIndex, specBa
|
|
|
237
264
|
injection += '\n'
|
|
238
265
|
}
|
|
239
266
|
|
|
267
|
+
// docs-inject 遥测(2026-09-21-scan-docs-ops-panel task-04,FR-06 / D-003@v1 Wave 0):模块
|
|
268
|
+
// 上下文注入命中(≥1 模块、注入段已渲染)经 task-01 底座 appendKnowledgeHit 落一行
|
|
269
|
+
// type:docs-inject,供平台 scan-docs 注入频次聚合消费(knowledge-stats 未知 type 前向兼容
|
|
270
|
+
// 静默跳过,不污染知识 stats 口径)。matchedFiles 取命中模块 doc 字段原值(modules/<x>.md,
|
|
271
|
+
// _module-map.yaml 实际形态)。未命中零行;fail-soft:遥测写失败不影响注入本体(对齐上方
|
|
272
|
+
// buildKnowledgeInjection 遥测分离先例)。
|
|
273
|
+
try {
|
|
274
|
+
appendKnowledgeHit(join(specBase, '.runtime'), {
|
|
275
|
+
type: 'docs-inject',
|
|
276
|
+
change: String((opts && opts.change) || ''),
|
|
277
|
+
query: String(taskDescription || ''),
|
|
278
|
+
matchedFiles: matched.map(m => m.data && m.data.doc).filter(Boolean),
|
|
279
|
+
})
|
|
280
|
+
} catch { /* 遥测 fail-soft(R-04):hits 落盘失败不影响注入本体 */ }
|
|
281
|
+
|
|
240
282
|
return { text: injection, frModules }
|
|
241
283
|
}
|
|
242
284
|
|
|
@@ -409,19 +451,28 @@ function renderKnowledgeInjectSection(knowledgeResult, { knowledgeDir, maxFiles,
|
|
|
409
451
|
*/
|
|
410
452
|
export function buildKnowledgeInjection({ knowledgeDir, runtimeDir, change, query, maxFiles = KNOWLEDGE_INJECT_MAX_FILES, maxLines = KNOWLEDGE_INJECT_MAX_LINES } = {}) {
|
|
411
453
|
const knowledgeResult = matchKnowledge(knowledgeDir, String(query || ''))
|
|
412
|
-
|
|
454
|
+
// quicklog 检索面(知识可见性导线):与 INDEX 知识面独立——INDEX 未命中时 quicklog 照常
|
|
455
|
+
// 注入(两 retrieval 面互为补充);fail-open(无 quicklog/异常 → 空 hits 零注入)。
|
|
456
|
+
// specBase 由 knowledgeDir 上溯(两个注入点传的都是 <specBase>/knowledge)。
|
|
457
|
+
const quick = knowledgeDir
|
|
458
|
+
? matchQuicklogContext(dirname(knowledgeDir), String(query || ''))
|
|
459
|
+
: { hits: [], report: '' }
|
|
460
|
+
if (!knowledgeResult.matched && quick.hits.length === 0) {
|
|
413
461
|
return { matched: false, section: '', report: knowledgeResult.report, json: knowledgeResult.json }
|
|
414
462
|
}
|
|
415
|
-
const
|
|
463
|
+
const kSection = knowledgeResult.matched ? renderKnowledgeInjectSection(knowledgeResult, { knowledgeDir, maxFiles, maxLines }) : ''
|
|
464
|
+
const qSection = quick.report || ''
|
|
465
|
+
const section = [kSection, qSection].filter(Boolean).join('\n\n')
|
|
416
466
|
try {
|
|
417
467
|
appendKnowledgeHit(runtimeDir, {
|
|
418
468
|
type: 'inject',
|
|
419
469
|
change: change || '',
|
|
420
470
|
query: String(query || ''),
|
|
421
471
|
matchedFiles: knowledgeResult.entries.map(e => (e.anchor ? `${e.file}#${e.anchor}` : e.file)),
|
|
472
|
+
quicklogIds: quick.hits.map(h => h.qlId),
|
|
422
473
|
})
|
|
423
474
|
} catch { /* 遥测 fail-soft(R-04):hits 落盘失败不阻断注入本体 */ }
|
|
424
|
-
return { matched: true, section, report: knowledgeResult.report, json: knowledgeResult.json }
|
|
475
|
+
return { matched: true, section, report: knowledgeResult.report, json: knowledgeResult.json, quickHits: quick.hits }
|
|
425
476
|
}
|
|
426
477
|
|
|
427
478
|
// parseModuleMapSimple 复用 modules.js 的 canonical 实现(合并历史 copy-paste 副本,2026-08-07;
|
|
@@ -775,6 +826,13 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
775
826
|
// POST 落地后进程才退出。
|
|
776
827
|
triggerStepStartSync(cwd, changeName, platformOpts)
|
|
777
828
|
|
|
829
|
+
// ── 阶段产物模板落盘(R16 减负批次 2026-09-24):模板出上下文——prompt 只留指针,模板
|
|
830
|
+
// 幂等落盘 <specBase>/.runtime/templates/ 供 agent 写产物前 Read 一次。best-effort:
|
|
831
|
+
// 落盘失败零输出不阻断渲染(模板文件缺失时 --done 门禁会兜底点名)。──
|
|
832
|
+
try {
|
|
833
|
+
materializeStageTemplates(platformOpts?.specRoot || platformOpts?.specDriftAnchor || join(cwd, '.sillyspec'))
|
|
834
|
+
} catch { /* fail-soft */ }
|
|
835
|
+
|
|
778
836
|
// ── 首个 agent 可见步索引(「首步一次建立」类注入的锚点)──
|
|
779
837
|
// step0 为 noAI(brainstorm/execute/verify 的 step1「进度确认」,2026-09-07)时不渲染 prompt,
|
|
780
838
|
// persona/护栏/完成契约/铁律/平台路径约束等 step0 专属注入须顺延到首个渲染步,否则整段丢失
|
|
@@ -852,17 +910,36 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
852
910
|
// 先解析 {{include: name}}(把外部模板片段拉进 prompt),再做下方占位符替换,
|
|
853
911
|
// 保证模板内容里的 {SPEC_ROOT}/<change-name> 等也能被替换
|
|
854
912
|
let promptText = resolvePromptIncludes(step.prompt)
|
|
913
|
+
// ── M1 步骤指引静态段指纹增量(2026-09-21-r5-efficiency-batch2 task-01 / FR-01 / D-001@v1)──
|
|
914
|
+
// P8 实证(round5):73 次 CLI 调用注入 252KB、同步骤复入全量重印。契约:静态段按渲染指纹落盘
|
|
915
|
+
// .runtime/step-guides/,同指纹复入静态部分 ≤10 行(指纹+路径+提示),动态注入段(值因运行而异)
|
|
916
|
+
// 每次渲染永不缓存(附录照常输出)。--json(console.log 劫持形态,src/index.js withJsonOutput)与
|
|
917
|
+
// SILLYSPEC_STEP_GUIDE=0 走全量——机器消费方与逃生门不读盘。
|
|
918
|
+
const guideTemplate = promptText // 替换管线入口的模板原文(指纹基面)
|
|
919
|
+
const dynSegments = [] // 动态注入段值收集(复入附录素材,顺序即渲染顺序)
|
|
920
|
+
const substitute = (re, value, isStaticToken = false) => {
|
|
921
|
+
const v = String(value)
|
|
922
|
+
if (!isStaticToken) dynSegments.push(v) // 非白名单占位符默认按动态收集(错收只多不少,漏收才是坑)
|
|
923
|
+
promptText = promptText.replace(re, v)
|
|
924
|
+
return promptText
|
|
925
|
+
}
|
|
926
|
+
const substituteSplitJoin = (token, value) => {
|
|
927
|
+
const v = String(value)
|
|
928
|
+
dynSegments.push(v)
|
|
929
|
+
promptText = promptText.split(token).join(v)
|
|
930
|
+
return promptText
|
|
931
|
+
}
|
|
855
932
|
// 替换 prompt 中的占位符
|
|
856
933
|
if (projectName && promptText.includes('<project>')) {
|
|
857
|
-
|
|
934
|
+
substitute(/<project>/g, projectName, true)
|
|
858
935
|
}
|
|
859
936
|
// 替换 <git-user> 占位符
|
|
860
937
|
if (promptText.includes('<git-user>')) {
|
|
861
938
|
try {
|
|
862
939
|
const gitUser = safeGit(cwd, ['config', 'user.name']).value || 'unknown'
|
|
863
|
-
|
|
940
|
+
substitute(/<git-user>/g, gitUser, true)
|
|
864
941
|
} catch {
|
|
865
|
-
|
|
942
|
+
substitute(/<git-user>/g, 'unknown', true)
|
|
866
943
|
}
|
|
867
944
|
}
|
|
868
945
|
// 替换时间戳占位符(本地墙钟统一走 nowWallClock——坑 taskcard-created-at-utc:人读字段用
|
|
@@ -871,33 +948,33 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
871
948
|
const nowDatetime = nowWallClock(now)
|
|
872
949
|
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')
|
|
873
950
|
const nowDate = now.getFullYear() + '-' + String(now.getMonth()+1).padStart(2,'0') + '-' + String(now.getDate()).padStart(2,'0')
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
951
|
+
substitute(/<now-datetime>/g, nowDatetime)
|
|
952
|
+
substitute(/<now-timestamp>/g, nowTimestamp)
|
|
953
|
+
substitute(/<now-date>/g, nowDate)
|
|
877
954
|
// <now-iso-datetime>(scan frontmatter updated_at 用;与 scan-fix-headers 同款秒级格式)。
|
|
878
955
|
// 占位符名带 iso 是历史遗留——值已统一为本地墙钟(同 <now-datetime>),改名会破坏 stages 引用。
|
|
879
|
-
|
|
956
|
+
substitute(/<now-iso-datetime>/g, nowWallClock(now))
|
|
880
957
|
// <git-head-short>(scan frontmatter source_commit 用;CLI 代查,agent 勿自跑 rev-parse)
|
|
881
958
|
if (promptText.includes('<git-head-short>')) {
|
|
882
959
|
let headShort = 'unknown'
|
|
883
960
|
try {
|
|
884
961
|
headShort = safeGit(cwd, ['rev-parse', '--short', 'HEAD']).value || 'unknown'
|
|
885
962
|
} catch { /* git 不可用 → unknown,scan-postcheck 失配时再核 */ }
|
|
886
|
-
|
|
963
|
+
substitute(/<git-head-short>/g, headShort)
|
|
887
964
|
}
|
|
888
965
|
// 替换 {REVIEW_SCHEMA_VERSION} 占位符(task review 示例模板用,值=CLI 当前 REVIEW_SCHEMA_VERSION 常量,
|
|
889
966
|
// 避免 agent 照抄 design 目标版本与 CLI 写侧常量漂移;与 stage review 契约 renderReviewJsonContract 动态注入同源)
|
|
890
967
|
if (promptText.includes('{REVIEW_SCHEMA_VERSION}')) {
|
|
891
|
-
|
|
968
|
+
substitute(/\{REVIEW_SCHEMA_VERSION\}/g, String(REVIEW_SCHEMA_VERSION))
|
|
892
969
|
}
|
|
893
970
|
// 替换 <change-name> 占位符
|
|
894
971
|
if (changeName && promptText.includes('<change-name>')) {
|
|
895
|
-
|
|
972
|
+
substitute(/<change-name>/g, changeName, true)
|
|
896
973
|
}
|
|
897
974
|
// 替换 <quick-session-id> 占位符(quick 阶段专用:sessionId == changeName == quick-<uuid8>,
|
|
898
975
|
// 见 runStage 参数解析 quickSessionId 生成。告知 agent 本会话 id + --done 需带 --change)
|
|
899
976
|
if (changeName && promptText.includes('<quick-session-id>')) {
|
|
900
|
-
|
|
977
|
+
substitute(/<quick-session-id>/g, changeName, true)
|
|
901
978
|
}
|
|
902
979
|
// 替换 <quicklog-id> 占位符(quick 阶段:从 session guard.json 读 CLI 分配的 ql-ID,
|
|
903
980
|
// 供 agent 在模块文档变更索引等处引用)
|
|
@@ -912,7 +989,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
912
989
|
} catch (err) {
|
|
913
990
|
console.warn(`⚠️ quicklog-id 占位符读取 guard.json 失败: ${err && err.message ? err.message : err}`);
|
|
914
991
|
}
|
|
915
|
-
|
|
992
|
+
substitute(/<quicklog-id>/g, qlIdVal || '(未分配)')
|
|
916
993
|
}
|
|
917
994
|
// 替换 <linked-changes> 占位符(quick 阶段:从 .runtime/quick-sessions/<sessionId>/guard.json 读关联变更)
|
|
918
995
|
if (promptText.includes('<linked-changes>')) {
|
|
@@ -928,7 +1005,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
928
1005
|
if (guard) linkedChanges = Array.isArray(guard.linkedChanges) ? guard.linkedChanges : []
|
|
929
1006
|
} catch {}
|
|
930
1007
|
const display = linkedChanges.length > 0 ? linkedChanges.join(', ') : '(无)'
|
|
931
|
-
|
|
1008
|
+
substitute(/<linked-changes>/g, display, true)
|
|
932
1009
|
}
|
|
933
1010
|
// 平台模式:注入路径覆盖指令
|
|
934
1011
|
if (platformOpts?.specRoot || platformOpts?.runtimeRoot) {
|
|
@@ -1058,9 +1135,9 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1058
1135
|
const injected = fact
|
|
1059
1136
|
? `[docs-debt] scan 基线漂移:${fact.message}`
|
|
1060
1137
|
: '[docs-debt] scan 基线漂移:无 scan 文档(绿地项目),跳过判定'
|
|
1061
|
-
|
|
1138
|
+
substitute(/\{SCAN_STALENESS\}/g, injected)
|
|
1062
1139
|
} catch (e) {
|
|
1063
|
-
|
|
1140
|
+
substitute(/\{SCAN_STALENESS\}/g, `[docs-debt] scan 漂移检测异常(${e.message}),跳过判定`)
|
|
1064
1141
|
}
|
|
1065
1142
|
}
|
|
1066
1143
|
|
|
@@ -1090,7 +1167,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1090
1167
|
'\n\n⛔ 红线(同 scan 子代理先例):禁止重新 grep 发现底稿覆盖的机械事实(端点/依赖/规模),冲突以底稿为准。\n'
|
|
1091
1168
|
}
|
|
1092
1169
|
} catch { /* fail-soft:读取失败空注入,不阻断 prompt 输出 */ }
|
|
1093
|
-
|
|
1170
|
+
substitute(/\{SCAN_FACTS\}/g, factsInjected)
|
|
1094
1171
|
}
|
|
1095
1172
|
|
|
1096
1173
|
// ── 机械事实注入三件(2026-09-07 注入缺口批次):{LOCAL_COMMANDS} / {GIT_DIRTY} / {TASKS_CHECKBOX} ──
|
|
@@ -1125,7 +1202,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1125
1202
|
} catch (e) {
|
|
1126
1203
|
localBlock = `(构建命令注入失败:${e.message}——可自行读 local.yaml)`
|
|
1127
1204
|
}
|
|
1128
|
-
|
|
1205
|
+
substitute(/\{LOCAL_COMMANDS\}/g, localBlock)
|
|
1129
1206
|
}
|
|
1130
1207
|
|
|
1131
1208
|
// ①b {QUICK_CONTEXT_DIGEST}(刀①,2026-09-08):quick step1 的项目/约定上下文 CLI 代读注入
|
|
@@ -1133,7 +1210,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1133
1210
|
// fail-soft 同三件套;无 quick 占位符自然零输出。
|
|
1134
1211
|
if (stageName === 'quick' && promptText.includes('{QUICK_CONTEXT_DIGEST}')) {
|
|
1135
1212
|
const digestSpecBase = resolvePromptSpecBase(platformOpts, cwd)
|
|
1136
|
-
|
|
1213
|
+
substitute(/\{QUICK_CONTEXT_DIGEST}/g, buildQuickContextDigest(digestSpecBase, projectName))
|
|
1137
1214
|
}
|
|
1138
1215
|
|
|
1139
1216
|
// ①c {FR_INDEX_DIGEST}(2026-09-18-fr-index-l1 L1,D-004):brainstorm step8 的触达域现行 FR 注入
|
|
@@ -1149,12 +1226,12 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1149
1226
|
const domains = resolveTouchedDomains(frChangeDir, discoverModuleIndex(knowledgeRoot))
|
|
1150
1227
|
const entries = readActiveFrDigest(knowledgeRoot, domains)
|
|
1151
1228
|
if (entries.length === 0) {
|
|
1152
|
-
|
|
1229
|
+
substitute(/\{FR_INDEX_DIGEST\}/g, '(触达域暂无 active FR 索引条目——本变更大概率是这些域的首批需求,照常写作)')
|
|
1153
1230
|
} else {
|
|
1154
1231
|
const lines = entries.map((e) => `- ${e.id} ${e.title}(来源 ${e.change}${e.scenarios.length ? ';场景:' + e.scenarios.slice(0, 3).join(',') : ''}${(e.decisions || []).length ? ';依据:' + e.decisions.slice(0, 3).join('、') : ''})${e.needsReview ? ` ⚠️ 待复核(${e.needsReview})——该 FR 覆盖的代码近期被 quick 触达,行为可能已变;本变更若触及同域先核对现状再决定承接/新写` : ''}`)
|
|
1155
1232
|
lines.push('')
|
|
1156
1233
|
lines.push('> 域解析自本变更 design.md 文件清单(漏域先核对清单)。改写/取代已有行为 → 对应 FR 块加承接行;新行为 → 新 FR 块。superseded 条目默认不列(历史回溯自行读 knowledge/fr/)。依据决策(L2)= 当年取舍锚——翻案须先读 knowledge/decisions/<域>.md 的否决理由,满足复潮条件走 D-xxx@vN+1,不得静默改行为。承接行可带退役理由:`承接: FR-<域>-NNN(退役理由:一句话)`——归档时写进被取代条目(理由内禁逗号);条目正文是截断摘要,全文锚(全文:<归档路径>#FR-NN)由 CLI 自动落。')
|
|
1157
|
-
|
|
1234
|
+
substitute(/\{FR_INDEX_DIGEST\}/g, lines.join('\n'))
|
|
1158
1235
|
}
|
|
1159
1236
|
try {
|
|
1160
1237
|
const { appendKnowledgeHit } = await import('../knowledge-hits.js')
|
|
@@ -1163,7 +1240,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1163
1240
|
})
|
|
1164
1241
|
} catch { /* 遥测 fail-soft */ }
|
|
1165
1242
|
} catch (e) {
|
|
1166
|
-
|
|
1243
|
+
substitute(/\{FR_INDEX_DIGEST\}/g, `(FR 索引注入失败:${e && e.message ? e.message : e}——可自行读 {SPEC_ROOT}/knowledge/fr/)`)
|
|
1167
1244
|
}
|
|
1168
1245
|
}
|
|
1169
1246
|
|
|
@@ -1180,7 +1257,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1180
1257
|
} catch (e) {
|
|
1181
1258
|
dirtyBlock = `(脏文件注入失败:${e.message}——可自行跑 git status --porcelain)`
|
|
1182
1259
|
}
|
|
1183
|
-
|
|
1260
|
+
substitute(/\{GIT_DIRTY\}/g, dirtyBlock)
|
|
1184
1261
|
}
|
|
1185
1262
|
|
|
1186
1263
|
// ③ {TASKS_CHECKBOX}:tasks.md 任务勾选状态投影(verify 逐项检查步消费——省 agent 手数)
|
|
@@ -1203,7 +1280,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1203
1280
|
} catch (e) {
|
|
1204
1281
|
tasksBlock = `(勾选状态注入失败:${e.message}——可自行读 tasks.md)`
|
|
1205
1282
|
}
|
|
1206
|
-
|
|
1283
|
+
substitute(/\{TASKS_CHECKBOX\}/g, tasksBlock)
|
|
1207
1284
|
}
|
|
1208
1285
|
|
|
1209
1286
|
// 决策防复潮注入(W1.1 第 3 点,task-04,FR-05):brainstorm「加载项目上下文」Step2 的
|
|
@@ -1232,13 +1309,13 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1232
1309
|
}
|
|
1233
1310
|
decInjected = '\n\n' + lines.join('\n')
|
|
1234
1311
|
}
|
|
1235
|
-
|
|
1312
|
+
substitute(/\{DECISION_HITS\}/g, decInjected)
|
|
1236
1313
|
// 注入结果同步落 runtime JSON(与既有 KNOWLEDGE_HIT_REPORT 落盘口径一致)
|
|
1237
1314
|
const decRuntimeDir = join(decSpecBase, '.runtime')
|
|
1238
1315
|
mkdirSync(decRuntimeDir, { recursive: true })
|
|
1239
1316
|
writeAtomicSync(join(decRuntimeDir, 'decision-hits.json'), JSON.stringify({ matched: decResult.matched, decisionHits: decResult.decisionHits || [] }, null, 2) + '\n')
|
|
1240
1317
|
} catch (e) {
|
|
1241
|
-
|
|
1318
|
+
substitute(/\{DECISION_HITS\}/g, `[decisions] 决策命中检测异常(${e.message}),跳过`)
|
|
1242
1319
|
}
|
|
1243
1320
|
}
|
|
1244
1321
|
|
|
@@ -1274,7 +1351,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1274
1351
|
change: changeName || '',
|
|
1275
1352
|
query: taskContext,
|
|
1276
1353
|
})
|
|
1277
|
-
|
|
1354
|
+
substitute(
|
|
1278
1355
|
/\{KNOWLEDGE_HIT_REPORT\}/g,
|
|
1279
1356
|
knowledgeInjection.matched ? knowledgeInjection.section : knowledgeInjection.report
|
|
1280
1357
|
)
|
|
@@ -1282,7 +1359,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1282
1359
|
mkdirSync(runtimeDir, { recursive: true })
|
|
1283
1360
|
writeAtomicSync(join(runtimeDir, 'knowledge-hit-report.json'), JSON.stringify(knowledgeInjection.json, null, 2) + '\n')
|
|
1284
1361
|
} catch (e) {
|
|
1285
|
-
|
|
1362
|
+
substitute(/\{KNOWLEDGE_HIT_REPORT\}/g, 'Status: no matches (error: ' + e.message + ')')
|
|
1286
1363
|
}
|
|
1287
1364
|
}
|
|
1288
1365
|
|
|
@@ -1311,9 +1388,9 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1311
1388
|
try {
|
|
1312
1389
|
debtTouchFacts = renderDecisionTouchFacts(computeDecisionTouches(changedFiles, join(debtSpecBase, 'knowledge')).touches)
|
|
1313
1390
|
} catch { /* 触碰计算降级为空——不影响欠账 facts 注入 */ }
|
|
1314
|
-
|
|
1391
|
+
substitute(/\{DOCS_DEBT\}/g, [result.facts, debtTouchFacts].filter(Boolean).join('\n'))
|
|
1315
1392
|
} catch (e) {
|
|
1316
|
-
|
|
1393
|
+
substitute(/\{DOCS_DEBT\}/g, `[docs-debt] 欠账计算异常(${e.message}),跳过`)
|
|
1317
1394
|
}
|
|
1318
1395
|
}
|
|
1319
1396
|
|
|
@@ -1326,9 +1403,9 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1326
1403
|
const { renderModuleResolveTable } = await import('../module-resolve.js')
|
|
1327
1404
|
const mrSpecBase = resolvePromptSpecBase(platformOpts, cwd)
|
|
1328
1405
|
const table = renderModuleResolveTable({ cwd, specBase: mrSpecBase, changeName })
|
|
1329
|
-
|
|
1406
|
+
substitute(/\{MODULE_RESOLVE_TABLE\}/g, table)
|
|
1330
1407
|
} catch (e) {
|
|
1331
|
-
|
|
1408
|
+
substitute(/\{MODULE_RESOLVE_TABLE\}/g, `[modules-resolve] 模块卡分级解析异常(${e.message}),跳过——可手跑 sillyspec modules resolve --change <变更名>`)
|
|
1332
1409
|
}
|
|
1333
1410
|
}
|
|
1334
1411
|
|
|
@@ -1366,7 +1443,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1366
1443
|
console.error(`[sillyspec] execute run marker/目录写入失败(降级继续,ID ${runId} 仍注入 prompt): ${e.message}`)
|
|
1367
1444
|
}
|
|
1368
1445
|
}
|
|
1369
|
-
|
|
1446
|
+
substitute(/\{EXECUTE_RUN_ID\}/g, runId)
|
|
1370
1447
|
}
|
|
1371
1448
|
|
|
1372
1449
|
// Stage Review Tier:brainstorm/plan/execute 阶段注入审查分级占位符
|
|
@@ -1743,7 +1820,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1743
1820
|
if (stageName === 'quick') {
|
|
1744
1821
|
taskDesc = readQuickGuardField(changeName, effectiveSpecBase, 'taskDescription') || taskDesc
|
|
1745
1822
|
}
|
|
1746
|
-
const injRes = buildModuleContextInjection(taskDesc, moduleIndex, effectiveSpecBase, projectName) || { text: '', frModules: [] }
|
|
1823
|
+
const injRes = buildModuleContextInjection(taskDesc, moduleIndex, effectiveSpecBase, projectName, { change: changeName || '' }) || { text: '', frModules: [] }
|
|
1747
1824
|
const injection = injRes.text
|
|
1748
1825
|
if (injection) {
|
|
1749
1826
|
promptText = injection + '\n' + promptText
|
|
@@ -1875,12 +1952,58 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1875
1952
|
})
|
|
1876
1953
|
} catch { preflightFailuresMd = '' }
|
|
1877
1954
|
if (promptText.includes('{PREFLIGHT_FAILURES}')) {
|
|
1878
|
-
promptText =
|
|
1955
|
+
promptText = substituteSplitJoin('{PREFLIGHT_FAILURES}', preflightFailuresMd)
|
|
1879
1956
|
} else if (preflightFailuresMd) {
|
|
1880
1957
|
promptText = `${promptText}\n\n${preflightFailuresMd}`
|
|
1881
1958
|
}
|
|
1882
1959
|
|
|
1883
|
-
|
|
1960
|
+
// ── M1 打印分流:同指纹复入短输出 + 动态附录;首见全量 + guide 落盘 ──
|
|
1961
|
+
const jsonHijacked = String(console.log).includes('stderr.write') // --json 劫持形态探测(withJsonOutput 同款函数体)
|
|
1962
|
+
// P4 缺省开(D-004@batch3,2026-09-21-r5-efficiency-batch3 task-02):未设=开(短输出+落盘),
|
|
1963
|
+
// '=0' 显式关(逃生门保留,stdout 确定性测试族 fixture 用它锁定全量形态)。v1 默认关的动因
|
|
1964
|
+
// (测试族冲突)已由 task-02 迁移消解——逐例迁移注记见各测试文件。
|
|
1965
|
+
const guideEnabled = process.env.SILLYSPEC_STEP_GUIDE !== '0'
|
|
1966
|
+
const fingerprint = computeStepGuideFingerprint(guideTemplate)
|
|
1967
|
+
const runtimeRoot = platformOpts?.specRoot || join(cwd, '.sillyspec')
|
|
1968
|
+
const guideRoot = join(runtimeRoot, '.runtime', 'step-guides')
|
|
1969
|
+
const guideFile = join(guideRoot, `${stageName}-step${stepIndex}-${fingerprint.slice(0, 8)}.md`)
|
|
1970
|
+
// 复入状态=变更级(change+stage+step → 指纹):「复入」锚定同一变更同一步的重渲染(跨 CLI 进程
|
|
1971
|
+
// 持续生效——实验里每次 run 都是独立进程,进程内缓存无意义);他变更渲染同模板不误短路
|
|
1972
|
+
// (knowledge-inject 两路径字节一致测试的机制依赖此界——模板级寻址会短路其第二次全量渲染)。
|
|
1973
|
+
const stateRoot = join(runtimeRoot, '.runtime', 'step-guide-state')
|
|
1974
|
+
const stateFile = join(stateRoot, `${stageName}-step${stepIndex}-${String(changeName || '_nochange').replace(/[^A-Za-z0-9._-]/g, '_')}.json`)
|
|
1975
|
+
let prevState = null
|
|
1976
|
+
try { prevState = JSON.parse(readFileSync(stateFile, 'utf8')) } catch { /* 无状态=首见 */ }
|
|
1977
|
+
const reentryHit = guideEnabled && !jsonHijacked && prevState != null && prevState.fingerprint === fingerprint && existsSync(prevState.guidePath)
|
|
1978
|
+
if (reentryHit) {
|
|
1979
|
+
// 复入:静态部分 ≤10 行;动态段照常渲染为附录
|
|
1980
|
+
console.log(`📄 步骤指引未变(fingerprint=${fingerprint.slice(0, 8)})——静态全文不再重印。需要全文时 Read:`)
|
|
1981
|
+
console.log(` ${guideFile}`)
|
|
1982
|
+
console.log(` (首见渲染已在上下文中;上下文被压缩丢失时才需要 Read)`)
|
|
1983
|
+
console.log(`⛔ 本步骤安全铁律仍在生效(破坏性操作禁令见首见渲染/guide 全文)`)
|
|
1984
|
+
if (dynSegments.filter(x => x.trim() !== '').length > 0) {
|
|
1985
|
+
console.log(`## 动态注入段(本次运行——永不缓存,每次照常渲染)`)
|
|
1986
|
+
for (const seg of dynSegments) if (seg.trim() !== '') console.log(seg)
|
|
1987
|
+
}
|
|
1988
|
+
} else {
|
|
1989
|
+
console.log(promptText)
|
|
1990
|
+
try {
|
|
1991
|
+
// 特性开启且有 spec 根才落盘:默认关时不写(stdout 确定性);无 spec 根不凭空造目录
|
|
1992
|
+
//(spec-dir 向上命中类测试会被新建的 test/.sillyspec 污染——本批实证)。
|
|
1993
|
+
if (guideEnabled && (platformOpts?.specRoot || existsSync(join(cwd, '.sillyspec')))) {
|
|
1994
|
+
mkdirSync(guideRoot, { recursive: true })
|
|
1995
|
+
writeFileSync(guideFile, `<!--fp=${fingerprint}-->
|
|
1996
|
+
# ${stageName} step ${stepIndex + 1} 指引静态全文(${step.name})
|
|
1997
|
+
|
|
1998
|
+
> 动态占位符(时间/材料包/知识命中/欠账等)以当次渲染为准,本文件只存静态模板面。
|
|
1999
|
+
|
|
2000
|
+
${maskVolatileForGuide(guideTemplate)}
|
|
2001
|
+
`, 'utf8')
|
|
2002
|
+
mkdirSync(stateRoot, { recursive: true })
|
|
2003
|
+
writeFileSync(stateFile, JSON.stringify({ fingerprint, guidePath: guideFile, changeName: changeName || null, stage: stageName, stepIndex, at: new Date().toISOString() }, null, 1) + '\n', 'utf8')
|
|
2004
|
+
}
|
|
2005
|
+
} catch { /* guide/state 落盘 best-effort:失败仅退化回全量重印,不阻断步骤输出 */ }
|
|
2006
|
+
}
|
|
1884
2007
|
// 铁律拆分(W3 token 效率):通用流程纪律(文档优先/不跳步/不编造命令)只在首个 agent 可见步注入——
|
|
1885
2008
|
// 每步重复 ~800B 纯耗 context。但【平台写入规则 + 路径规则】是安全关键(防写错目录/绕过 Write),
|
|
1886
2009
|
// 且依赖 changeName/platformOpts,必须【每步注入】(context 压缩丢失会让 agent 越界写源码)。
|
|
@@ -1894,6 +2017,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1894
2017
|
console.log('- 变更目录改名用 `sillyspec change-rename <旧名> <新名>`:mv/rename 会漏改进度库引用,导致变更失联。')
|
|
1895
2018
|
console.log('- 变更产物文档一律用 CLI 骨架命令生成(fourpiece-init / design-init / taskcard / module-impact / verify-probes --init)——骨架已预填 author/created_at/generated_by 元数据,勿删勿手拼 frontmatter;仅从零手写的补充文档才需手填 author(git 用户名)与 created_at(精确到秒)')
|
|
1896
2019
|
console.log('- 执行构建/测试前必须先读 local.yaml,优先使用其中配置的命令、路径和环境变量;未配置时才使用默认值')
|
|
2020
|
+
console.log('- **输出纪律(上下文卫生;R12 对撞实测均轮 134K vs 其他轮 181-234K)**:预计输出超过 ~50 行的命令(pytest / vitest / npm test / uv sync / 全量 JSON 等)一律重定向落盘 `.sillyspec/.runtime/logs/<名>.log 2>&1`,只回看 `tail -n 50` 判断结果;需要细节时 grep 该日志。禁止把命令全量输出读进上下文——长输出逐轮回放是 token 膨胀主源(CLI noAI 步的输出截断同理,勿整段复述)。')
|
|
1897
2021
|
}
|
|
1898
2022
|
// 平台模式 + 路径规则(安全关键,每步注入;step1+ 起带精简标题,不复述通用铁律)
|
|
1899
2023
|
if (platformOpts?.specRoot || platformOpts?.runtimeRoot || changeName) {
|
|
@@ -1948,21 +2072,27 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1948
2072
|
// 提示格式,且静态步骤名稳定无漂移风险)。
|
|
1949
2073
|
const stepAssertFlag = (stageName === 'execute' && step?.name) ? ` --step "${step.name}"` : ''
|
|
1950
2074
|
const doneCommand = `sillyspec run ${cmdStage} --done${confirmFlag}${stepAssertFlag}${changeFlag} --output "你的摘要"`
|
|
2075
|
+
// 合并式完成命令(R16 减负批次 2026-09-24):--done --answer 一步吞 wait+done——
|
|
2076
|
+
// 未挂起时自动补全 waitAnswer(complete.js requiresWait 门),已挂起时 resolveWaitingStepWithAnswer
|
|
2077
|
+
// 拉回收口。R15-SF 实证 brainstorm 8 次 CLI 调用中 4 次是三段式(wait/continue/done)补轮——
|
|
2078
|
+
// 合并式主路径把每个 wait 步省 2 次调用;--wait 降级为「用户暂不可达需挂起」的备选。
|
|
2079
|
+
const answerDoneCommand = `sillyspec run ${cmdStage} --done --answer "用户回答"${confirmFlag}${stepAssertFlag}${changeFlag} --output "你的摘要"`
|
|
1951
2080
|
if (requiresWait) {
|
|
1952
|
-
console.log(
|
|
1953
|
-
console.log(
|
|
2081
|
+
console.log(`本步骤需要用户决策——主路径一步合并完成(--answer 自动补全 wait 状态,省 2 次 CLI 往返):`)
|
|
2082
|
+
console.log(answerDoneCommand + (autoMeta ? '' : ' --input "用户原始需求/反馈"'))
|
|
1954
2083
|
console.log(``)
|
|
1955
|
-
console.log(
|
|
1956
|
-
console.log(`sillyspec run ${cmdStage} --
|
|
1957
|
-
console.log(
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
2084
|
+
console.log(`若用户暂不可达需先挂起:`)
|
|
2085
|
+
console.log(`sillyspec run ${cmdStage} --wait --reason "${step.waitReason || '等待用户输入'}" --options "${(step.waitOptions || ['确认']).join(',')}"${changeFlag} --output "你的问题/方案摘要"`)
|
|
2086
|
+
console.log(`(挂起后拿到回答,仍用上方一步合并命令收口——无需 --continue 单独解等待)`)
|
|
2087
|
+
} else {
|
|
2088
|
+
if (mayNeedWait) {
|
|
2089
|
+
console.log(`如果需要用户决策(选择方案/确认设计等),拿到回答后一步完成(--answer 自动补全 wait 状态):`)
|
|
2090
|
+
console.log(answerDoneCommand + (autoMeta ? '' : ' --input "用户原始需求/反馈"'))
|
|
2091
|
+
console.log(``)
|
|
2092
|
+
console.log(`如果不需要用户决策,正常完成:`)
|
|
2093
|
+
}
|
|
2094
|
+
console.log(doneCommand + (autoMeta ? '' : ' --input "用户原始需求/反馈"'))
|
|
1964
2095
|
}
|
|
1965
|
-
console.log(doneCommand + (autoMeta ? '' : ' --input "用户原始需求/反馈"'))
|
|
1966
2096
|
// P0-2(noai-ir-roadmap §3):--output 可省略提示(auto 模式除外——auto driver 的
|
|
1967
2097
|
// SS-META doneCommand 仍带 --output,照抄路径零变化;省略路径由 command.js 同源合成)。
|
|
1968
2098
|
if (!autoMeta) {
|
|
@@ -1982,9 +2112,9 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1982
2112
|
stepName: step.name,
|
|
1983
2113
|
change: changeName || null,
|
|
1984
2114
|
requiresUser: requiresWait || mayNeedWait || step.requiresConfirm === true,
|
|
1985
|
-
doneCommand,
|
|
2115
|
+
doneCommand: requiresWait ? answerDoneCommand : doneCommand,
|
|
1986
2116
|
waitHint: (requiresWait || mayNeedWait)
|
|
1987
|
-
? 'requiresUser=true
|
|
2117
|
+
? 'requiresUser=true:与用户交互拿到回答后,逐字跑 doneCommand(--done --answer 一步合并,自动补全 wait 状态);用户暂不可达才先 --wait 挂起'
|
|
1988
2118
|
: '直接执行任务后逐字跑 doneCommand',
|
|
1989
2119
|
}
|
|
1990
2120
|
console.log('<!--SS-META:' + JSON.stringify(meta) + '-->')
|