sillyspec 3.29.4 → 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-archive.md +26 -0
- package/assets/command-cards/run-brainstorm.md +33 -0
- package/assets/command-cards/run-execute.md +29 -0
- package/assets/command-cards/run-plan.md +27 -0
- package/assets/command-cards/run-quick.md +17 -0
- package/assets/command-cards/run-verify.md +29 -0
- package/assets/command-cards/status.md +27 -0
- 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 +98 -0
- 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/brief-autocompact.md +34 -0
- package/round4/brief-session-replay.md +50 -0
- package/round4/deep-read-synthesis.md +38 -0
- package/round4/extract-full.mjs +38 -0
- package/round4/extract-timeline.mjs +43 -0
- package/round4/find-sessions.mjs +13 -0
- package/round4/full-r4-l.md +4707 -0
- package/round4/full-r4-o-l.md +4300 -0
- package/round4/full-r4-os.md +1681 -0
- package/round4/full-r4-sf.md +4255 -0
- package/round4/full-r4-sq.md +1734 -0
- package/round4/gate-audit-relay-receipt.md +30 -0
- package/round4/gate-value-audit.md +52 -0
- package/round4/optimization-plan.md +165 -0
- package/round4/prereg.md +67 -0
- package/round4/prompt-R4-L.md +63 -0
- package/round4/prompt-R4-O-L.md +59 -0
- package/round4/prompt-R4-O-S.md +56 -0
- package/round4/prompt-R4-S-F.md +60 -0
- package/round4/prompt-R4-S-Q.md +66 -0
- package/round4/r4-final-report.html +347 -0
- package/round4/r4-os-timeline.txt +297 -0
- package/round4/r4-sf-timeline.txt +738 -0
- package/round4/r4-sq-timeline.txt +322 -0
- package/round4/r4l-timeline.txt +793 -0
- package/round4/r4ol-timeline.txt +729 -0
- package/round4/r4sf-timeline.txt +852 -0
- package/round4/r5-scenario-matrix.md +46 -0
- package/round4/result-S.md +171 -0
- package/round4/result-final.md +46 -0
- package/round4/result-quality.md +63 -0
- package/src/change-list.js +9 -0
- package/src/classify-change.js +6 -0
- package/src/command-cards.js +74 -0
- package/src/commit-guard.js +2 -0
- package/src/config-schema.js +64 -12
- package/src/contract-matrix.js +26 -5
- package/src/db.js +35 -4
- package/src/decision-distill.js +9 -3
- package/src/diagnostic-codes.js +2 -1
- 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 +602 -21
- package/src/init.js +25 -8
- 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 +194 -7
- package/src/mcp-server.js +1 -0
- package/src/module-impact.js +51 -22
- 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 +127 -10
- package/src/run/complete-handlers.js +363 -73
- package/src/run/complete.js +356 -74
- package/src/run/gate-snapshot-ledger.js +213 -0
- package/src/run/gate-snapshot.js +260 -38
- package/src/run/gates.js +482 -268
- 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 +213 -14
- package/src/run/shared.js +123 -4
- 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 +222 -85
- package/src/stages/knowledge.js +74 -1
- package/src/stages/plan-postcheck.js +385 -20
- 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/taskcard-frontmatter.js +57 -0
- 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 +919 -159
- package/src/verify-probes.js +103 -25
- 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
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* 是 prompt.js tier 注入链的组包入口——机械抽素材半边(designDigest/fileList/硬约束/diff/热区/
|
|
24
24
|
* checklist),主代理点名半边(五交叉点/plan 差量)留位不预填(decisions.md D-002)。
|
|
25
25
|
*/
|
|
26
|
-
import { readFileSync } from 'fs'
|
|
26
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'fs'
|
|
27
27
|
import { join } from 'path'
|
|
28
28
|
import { REVIEW_CHECKLISTS } from './stage-review-checklist.js'
|
|
29
29
|
|
|
@@ -132,9 +132,15 @@ export function buildReviewMaterialPack(stage, inputs = {}) {
|
|
|
132
132
|
const delta = (inputs.planDelta || []).map(d => `- [${d.id}] ${d.status}${d.note ? '——' + d.note : ''}`).join('\n')
|
|
133
133
|
body = ['### design 硬约束', hc || '(无)', '### plan 差量(逐约束判定:一致/偏离/未覆盖)', delta || '(无——主代理未提供差量,评审者按 cannot_verify 列缺件)'].join('\n\n')
|
|
134
134
|
} else if (stage === 'execute-qa') {
|
|
135
|
+
// 测试清单(R16 减负批次 2026-09-24):diff 命中的测试文件点名——QA 对「组装行为」条目
|
|
136
|
+
// 作答时不必全仓找测试(R14 对撞失控勘察子代理 2.33M 的反面教材后,包内点名收窄查证面)。
|
|
137
|
+
const testFiles = (inputs.diffSummary && inputs.diffSummary.files || [])
|
|
138
|
+
.filter(f => /(^|[\\/])(tests?|__tests__)([\\/])|\.(test|spec)\.[cm]?[jt]sx?$|_test\.(go|py)$|(^|[\\/])test_[^\\/]+\.py$/i.test(f))
|
|
139
|
+
.slice(0, 30)
|
|
135
140
|
body = [
|
|
136
141
|
'### diff 摘要', (inputs.diffSummary && inputs.diffSummary.files || []).map(f => '- ' + f).join('\n') || '(无)',
|
|
137
142
|
inputs.diffSummary && inputs.diffSummary.stat ? '```diff\n' + inputs.diffSummary.stat + '\n```' : '',
|
|
143
|
+
'### 测试清单(diff 命中的测试文件)', testFiles.map(f => '- ' + f).join('\n') || '(diff 无测试文件——「组装行为」条目按包不足处理:cannot_verify 列缺件或定向查证并列明)',
|
|
138
144
|
'### design 热区(CLI 抽取)', extractDesignHotZone(inputs.designContent || '') || '(无命中节)',
|
|
139
145
|
'### 验收清单', (inputs.checklist || []).map(c => '- [ ] ' + c).join('\n') || '(无)',
|
|
140
146
|
].filter(Boolean).join('\n\n')
|
|
@@ -263,3 +269,200 @@ export async function assembleStageReviewMaterials({ stage, cwd, changeName, spe
|
|
|
263
269
|
} catch { /* 装配 best-effort:失败零注入(与占位符缺失同态) */ }
|
|
264
270
|
return ''
|
|
265
271
|
}
|
|
272
|
+
|
|
273
|
+
// ══ execute 任务材料包(2026-09-21-r5-efficiency-batch1 task-02,B-④ / D-003)════
|
|
274
|
+
// 两段式装配:稳定段(design.md「接口定义」「文件变更清单」固定节名机械选取原文 + 签名锚点)
|
|
275
|
+
// 在前、专属段(task 卡 title/goal/implementation/acceptance + allowed_paths)在后——稳定前缀
|
|
276
|
+
// 跨任务共享,兑现统一前缀缓存收益(压 task-08 型子代理轮均 148K vs 同侪 45-92K 差额)。
|
|
277
|
+
// 只摘不译(D-003 错键正典红线):一切内容为源文件原文逐字摘录/字段机械抽取,禁转写缩写。
|
|
278
|
+
|
|
279
|
+
/** 材料包字节上限(专属段尾部优先截;稳定段超限按节优先级截断——见 assembleExecuteTaskMaterials) */
|
|
280
|
+
const EXEC_MATERIALS_MAX_BYTES = 24576
|
|
281
|
+
/** 被截节保底形态里「首个代码块/表格」的块级上限(单块病态体量防穿顶) */
|
|
282
|
+
const EXEC_STUB_BLOCK_MAX_BYTES = 3600
|
|
283
|
+
|
|
284
|
+
function bytesOf(s) { return Buffer.byteLength(String(s || ''), 'utf8') }
|
|
285
|
+
|
|
286
|
+
/** 字节安全截断(按 UTF-8 字节预算切,不切断多字节字符) */
|
|
287
|
+
function byteSafeSlice(text, maxBytes) {
|
|
288
|
+
const s = String(text || '')
|
|
289
|
+
if (bytesOf(s) <= maxBytes) return s
|
|
290
|
+
let out = ''
|
|
291
|
+
let used = 0
|
|
292
|
+
for (const ch of s) {
|
|
293
|
+
const b = Buffer.byteLength(ch, 'utf8')
|
|
294
|
+
if (used + b > maxBytes) break
|
|
295
|
+
out += ch
|
|
296
|
+
used += b
|
|
297
|
+
}
|
|
298
|
+
return out
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/** 节内首个完整代码块(``` 围栏)或表格(连续 | 行)——被截节的保底保留形态 */
|
|
302
|
+
function firstBlockOrTable(body) {
|
|
303
|
+
const fence = body.match(/```[^\n]*\n[\s\S]*?```/)
|
|
304
|
+
if (fence) return fence[0]
|
|
305
|
+
const lines = body.split('\n')
|
|
306
|
+
const start = lines.findIndex(l => l.trim().startsWith('|'))
|
|
307
|
+
if (start >= 0) {
|
|
308
|
+
let end = start
|
|
309
|
+
while (end < lines.length && lines[end].trim().startsWith('|')) end++
|
|
310
|
+
return lines.slice(start, end).join('\n')
|
|
311
|
+
}
|
|
312
|
+
return null
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** task 卡 frontmatter 块级解析(机械逐字):顶格 key 行归属,其后缩进行归当前 key */
|
|
316
|
+
function splitTaskCardFields(cardText) {
|
|
317
|
+
const m = String(cardText || '').match(/^---\r?\n([\s\S]*?)\r?\n---/)
|
|
318
|
+
if (!m) return {}
|
|
319
|
+
const fields = {}
|
|
320
|
+
let cur = null
|
|
321
|
+
for (const line of m[1].split(/\r?\n/)) {
|
|
322
|
+
if (/^[A-Za-z_][\w-]*:/.test(line)) {
|
|
323
|
+
const at = line.indexOf(':')
|
|
324
|
+
cur = { key: line.slice(0, at), raw: line.slice(at + 1).trim(), lines: [] }
|
|
325
|
+
fields[cur.key] = cur
|
|
326
|
+
} else if (cur) {
|
|
327
|
+
cur.lines.push(line)
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return fields
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function unquoteScalar(v) {
|
|
334
|
+
const s = String(v || '')
|
|
335
|
+
return s.replace(/^'(.*)'$/s, '$1').replace(/^"(.*)"$/s, '$1')
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* task 卡字段取值(只摘不译——原文保持,无 YAML 折叠语义改写):
|
|
340
|
+
* 块标量(>/|)→ 原始行去公共缩进后逐行拼接;列表 → `- ` 后原文数组;标量 → 去包裹引号。
|
|
341
|
+
* @returns {string|string[]}
|
|
342
|
+
*/
|
|
343
|
+
function taskCardFieldValue(field) {
|
|
344
|
+
if (!field) return ''
|
|
345
|
+
if (/^[>|][+-]?$/.test(field.raw)) {
|
|
346
|
+
const indents = field.lines.filter(l => l.trim()).map(l => l.match(/^[ \t]*/)[0].length)
|
|
347
|
+
const cut = indents.length ? Math.min(...indents) : 0
|
|
348
|
+
return field.lines.map(l => (l.trim() ? l.slice(cut) : '')).join('\n').trim()
|
|
349
|
+
}
|
|
350
|
+
if (field.raw) return unquoteScalar(field.raw)
|
|
351
|
+
const items = []
|
|
352
|
+
for (const l of field.lines) {
|
|
353
|
+
const im = l.match(/^\s*-\s?(.*)$/)
|
|
354
|
+
if (im && im[1].trim()) items.push(im[1].trim())
|
|
355
|
+
}
|
|
356
|
+
return items
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* 组装 execute 任务材料包(两段式,只摘不译——契约见 change design.md「接口定义」)。
|
|
361
|
+
* @param {object} opts
|
|
362
|
+
* @param {string} opts.changeDir - 变更目录(读 design.md / tasks/task-NN.md)
|
|
363
|
+
* @param {string} opts.taskId - 'task-NN'
|
|
364
|
+
* @param {string} [opts.materialsDir] - 落盘目录;缺省 → 不落盘不抛错返回 null(additive)
|
|
365
|
+
* @param {Array<{symbol: string, file: string, line: number}>} [opts.signatureAnchors]
|
|
366
|
+
* - 接口签名锚点(可选,调用方供给;缺省空)
|
|
367
|
+
* @returns {Promise<{path: string, bytes: number, truncated: boolean}|null>}
|
|
368
|
+
* 截尾序:①专属段尾部优先截(稳定段全量保留);②稳定段自身超限按节优先级(接口定义 >
|
|
369
|
+
* 文件变更清单——高优先节后截)逐节降为「节头 + 首个代码块/表格 + 回源指引行」;锚点永不丢。
|
|
370
|
+
*/
|
|
371
|
+
export async function assembleExecuteTaskMaterials({ changeDir, taskId, materialsDir, signatureAnchors }) {
|
|
372
|
+
if (!changeDir || !taskId || !materialsDir) return null
|
|
373
|
+
const designPath = join(changeDir, 'design.md')
|
|
374
|
+
const taskCardPath = join(changeDir, 'tasks', `${taskId}.md`)
|
|
375
|
+
let designContent = ''
|
|
376
|
+
let cardContent = ''
|
|
377
|
+
try { designContent = readFileSync(designPath, 'utf8') } catch { /* 缺 design → 稳定段只剩锚点 */ }
|
|
378
|
+
try { cardContent = readFileSync(taskCardPath, 'utf8') } catch { /* 缺卡 → 专属段仅回源指引 */ }
|
|
379
|
+
|
|
380
|
+
// ── 稳定段素材:固定节名机械选取(缺节跳过)+ 签名锚点行 ──
|
|
381
|
+
const anchors = (Array.isArray(signatureAnchors) ? signatureAnchors : [])
|
|
382
|
+
.filter(a => a && a.symbol)
|
|
383
|
+
.map(a => `- \`${String(a.symbol)}\` → ${a.file || '?'}:${a.line || '?'}`)
|
|
384
|
+
const anchorsBlock = anchors.length ? `### 接口签名锚点\n\n${anchors.join('\n')}` : ''
|
|
385
|
+
|
|
386
|
+
const SECTION_SPECS = [{ name: '接口定义' }, { name: '文件变更清单' }] // 序 = 保留优先级序
|
|
387
|
+
const secBlocks = SECTION_SPECS
|
|
388
|
+
.map(s => ({ ...s, body: sectionBody(designContent, s.name) }))
|
|
389
|
+
.filter(s => s.body)
|
|
390
|
+
.map(s => ({ ...s, full: `### ${s.name}(design.md 原文摘录)\n\n${s.body}` }))
|
|
391
|
+
|
|
392
|
+
/** 被截节残体:节头 + 首个代码块/表格(块级 clamp)+ 一行回源指引 */
|
|
393
|
+
const stubOf = s => {
|
|
394
|
+
const first = firstBlockOrTable(s.body)
|
|
395
|
+
const kept = first
|
|
396
|
+
? (bytesOf(first) > EXEC_STUB_BLOCK_MAX_BYTES ? byteSafeSlice(first, EXEC_STUB_BLOCK_MAX_BYTES) + '\n…(首块超限截断)' : first)
|
|
397
|
+
: byteSafeSlice(s.body, 600)
|
|
398
|
+
return `### ${s.name}(design.md 原文摘录)\n\n${kept}\n\n完整内容回源:${designPath}#${s.name}`
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// ── 专属段素材:task 卡要点(机械摘录)+ allowed_paths + 回源指引 ──
|
|
402
|
+
const fields = splitTaskCardFields(cardContent)
|
|
403
|
+
const val = k => taskCardFieldValue(fields[k])
|
|
404
|
+
const bulletList = v => (Array.isArray(v) ? v.map(i => ` - ${i}`).join('\n') : ` - ${v}`)
|
|
405
|
+
const title = val('title')
|
|
406
|
+
const goal = val('goal')
|
|
407
|
+
const impl = val('implementation')
|
|
408
|
+
const acc = val('acceptance')
|
|
409
|
+
const paths = val('allowed_paths')
|
|
410
|
+
const cardLines = [
|
|
411
|
+
title ? `- title:${title}` : '',
|
|
412
|
+
goal ? `- goal:${goal}` : '',
|
|
413
|
+
impl ? `- implementation:\n${bulletList(impl)}` : '',
|
|
414
|
+
acc ? `- acceptance:\n${bulletList(acc)}` : '',
|
|
415
|
+
].filter(Boolean).join('\n')
|
|
416
|
+
const dedicatedFull = [
|
|
417
|
+
cardLines ? `### 任务卡要点(tasks/${taskId}.md 摘录)\n\n${cardLines}` : '',
|
|
418
|
+
Array.isArray(paths) && paths.length ? `### allowed_paths\n\n${paths.map(p => `- ${p}`).join('\n')}` : '',
|
|
419
|
+
`### 回源指引\n\n- design.md:${designPath}\n- 任务卡:${taskCardPath}`,
|
|
420
|
+
].filter(Boolean).join('\n\n')
|
|
421
|
+
|
|
422
|
+
// ── 装配(稳定段在前、专属段在后)+ 字节预算 ──
|
|
423
|
+
const packTitle = `# execute 任务材料包:${taskId}`
|
|
424
|
+
const packNote = '> 两段式:稳定段(design 契约节原文摘录)在前、专属段(task 卡摘录)在后。\n> 只摘不译——正文均为源文件原文逐字摘录;与源文件冲突时以源文件为准,按锚点回源核对。'
|
|
425
|
+
const stableTitle = '## 稳定段(design 契约节原文摘录——跨任务稳定)'
|
|
426
|
+
const dedicatedTitle = `## 专属段(${taskId} 专属)`
|
|
427
|
+
const tailMarker = `…(专属段超限截尾——完整内容回源:${taskCardPath})`
|
|
428
|
+
const joinParts = parts => parts.filter(Boolean).join('\n\n')
|
|
429
|
+
const assemble = (secs, dedicatedText) =>
|
|
430
|
+
joinParts([packTitle, packNote, stableTitle, ...secs, anchorsBlock, dedicatedTitle, dedicatedText])
|
|
431
|
+
|
|
432
|
+
let truncated = false
|
|
433
|
+
const sections = secBlocks.map(s => s.full)
|
|
434
|
+
let content = assemble(sections, dedicatedFull)
|
|
435
|
+
|
|
436
|
+
if (bytesOf(content) > EXEC_MATERIALS_MAX_BYTES) {
|
|
437
|
+
truncated = true
|
|
438
|
+
// ① 专属段尾部优先截:稳定段全量保留,专属段按剩余预算截尾
|
|
439
|
+
const clampDedicated = () => {
|
|
440
|
+
const head = assemble(sections, '')
|
|
441
|
+
const room = EXEC_MATERIALS_MAX_BYTES - bytesOf(head) - bytesOf('\n\n') - bytesOf(tailMarker) - 1
|
|
442
|
+
if (room < 0) return false
|
|
443
|
+
const sliced = byteSafeSlice(dedicatedFull, room)
|
|
444
|
+
content = head + '\n\n' + (sliced ? sliced + '\n' + tailMarker : tailMarker)
|
|
445
|
+
return true
|
|
446
|
+
}
|
|
447
|
+
if (!clampDedicated()) {
|
|
448
|
+
// ② 稳定段自身超限:按节优先级(接口定义 > 文件变更清单)低优先节先降为残节,逐节重试
|
|
449
|
+
for (let i = secBlocks.length - 1; i >= 0; i--) {
|
|
450
|
+
sections[i] = stubOf(secBlocks[i])
|
|
451
|
+
if (clampDedicated()) break
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
// ③ 终极兜底(双节皆残仍超限——仅病态锚点面可达):字节安全截 + 锚点块强制保留
|
|
455
|
+
if (bytesOf(content) > EXEC_MATERIALS_MAX_BYTES && anchorsBlock) {
|
|
456
|
+
const cut = content.indexOf(anchorsBlock)
|
|
457
|
+
const before = (cut > 0 ? content.slice(0, cut) : content).replace(/\n+$/, '')
|
|
458
|
+
content = byteSafeSlice(before, EXEC_MATERIALS_MAX_BYTES - bytesOf('\n\n' + anchorsBlock)) + '\n\n' + anchorsBlock
|
|
459
|
+
} else if (bytesOf(content) > EXEC_MATERIALS_MAX_BYTES) {
|
|
460
|
+
content = byteSafeSlice(content, EXEC_MATERIALS_MAX_BYTES)
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
mkdirSync(materialsDir, { recursive: true })
|
|
465
|
+
const outPath = join(materialsDir, `${taskId}.md`)
|
|
466
|
+
writeFileSync(outPath, content, 'utf8')
|
|
467
|
+
return { path: outPath, bytes: bytesOf(content), truncated }
|
|
468
|
+
}
|
|
@@ -48,8 +48,10 @@ export async function executeArchiveDistill({ cwd, specBase, changeName }) {
|
|
|
48
48
|
|
|
49
49
|
// ── FR 索引提炼(2026-09-18-fr-index-l1 L1:稳定 id 发号+承接翻链;同 best-effort 降级语义)──
|
|
50
50
|
try {
|
|
51
|
-
const { indexRequirements } = await import('../fr-index.js')
|
|
52
|
-
|
|
51
|
+
const { indexRequirements, archiveDeliverableFiles } = await import('../fr-index.js')
|
|
52
|
+
// greenfield-bootstrap:供 deliverableFiles(design 表 ∪ apply-manifest)——此前缺供致域路由
|
|
53
|
+
// 退化为 design 清单单源,全泛化段时落 unmapped(R17 臂3 实证),与轻量道口径对齐。
|
|
54
|
+
const fr = indexRequirements({ changeDir, knowledgeRoot, headHash, deliverableFiles: archiveDeliverableFiles(changeDir) })
|
|
53
55
|
const frWritten = Array.isArray(fr && fr.written) ? fr.written : []
|
|
54
56
|
if (fr && fr.skipped) {
|
|
55
57
|
console.log(`\nℹ️ FR 索引零输出:${fr.skipped}`)
|
package/src/run/bg-sync.js
CHANGED
|
@@ -52,6 +52,11 @@ const LOCK_STALE_MS = 15 * 60 * 1000;
|
|
|
52
52
|
const LOG_TRUNCATE_THRESHOLD = 1_000_000;
|
|
53
53
|
const LOG_TRUNCATE_KEEP = 512 * 1024;
|
|
54
54
|
|
|
55
|
+
// 终态补推扫描窗口与上限(2026-09-23 quick ql-20260923-007):候选取 last_active 倒序
|
|
56
|
+
// 前 50 行(老终态缺位也收敛,但不全表扫描);一轮最多补 3 条(防多缺位风暴挤爆预算)。
|
|
57
|
+
const TERMINAL_SWEEP_CANDIDATES = 50;
|
|
58
|
+
export const TERMINAL_SWEEP_MAX = 3;
|
|
59
|
+
|
|
55
60
|
/** pid 活性探测(跨平台):signal 0 探测;EPERM = 进程存在但属他户,保守按活。 */
|
|
56
61
|
function isPidAlive(pid) {
|
|
57
62
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
@@ -178,6 +183,48 @@ export async function spawnBackgroundSync(cwd, changeName, platformOpts = {}, op
|
|
|
178
183
|
return { status: 'spawned', logPath };
|
|
179
184
|
}
|
|
180
185
|
|
|
186
|
+
/**
|
|
187
|
+
* 归档终态补推扫描:DB 里 status=archived/deleted 且本地终态脏于平台镜像
|
|
188
|
+
* (last_local_modified_ts > last_synced_platform_ts,或从未同步)的变更名,last_active
|
|
189
|
+
* 倒序取前 TERMINAL_SWEEP_MAX 条。
|
|
190
|
+
*
|
|
191
|
+
* 背景(2026-09-22 session-fork-continuation 实证):终态推送依赖归档后还有一轮
|
|
192
|
+
* triggerSync——bg 子进程 best-effort,spawn 失败/单飞锁竞态/会话在归档后戛然而止都会
|
|
193
|
+
* 吞掉最后一轮,本地已归档而平台镜像停在旧阶段(verify),此后再无命令碰该变更即永久
|
|
194
|
+
* 滞后。归档/删除收尾均 _touchLocalModified(change-registry 语义:本地状态推进),
|
|
195
|
+
* 谓词即「终态戳晚于最后一次成功推送」。补推幂等(sync() 对 archived/deleted 推终态+
|
|
196
|
+
* 墓碑,POST 幂等),任意后续命令触发的 bg 轮顺带收敛。
|
|
197
|
+
* 时钟口径:last_synced_platform_ts 是服务端时间,本地钟超前服务端时至多多补一轮幂等
|
|
198
|
+
* 推送(无副作用),且 synced 随每次成功推进,自愈。
|
|
199
|
+
* 库路径与 sync() 内 ProgressManager 同源(resolvePlatformSpecDir(cwd):平台指针→向上
|
|
200
|
+
* 发现→cwd/.sillyspec 默认;platformOpts.specRoot 不进库路径——与 triggerSync 对齐),
|
|
201
|
+
* 保证扫的库就是主轮推的库;解析抛错(指针失效等)→ 空集跳过本轮(best-effort)。
|
|
202
|
+
*/
|
|
203
|
+
export async function collectTerminalSyncPending(cwd) {
|
|
204
|
+
const { resolvePlatformSpecDir, ProgressManager } = await import('../progress.js');
|
|
205
|
+
let specDir = null;
|
|
206
|
+
try { specDir = resolvePlatformSpecDir(cwd); } catch { return []; }
|
|
207
|
+
if (!specDir) return [];
|
|
208
|
+
if (!existsSync(join(specDir, '.runtime', 'sillyspec.db'))) return [];
|
|
209
|
+
const pm = new ProgressManager({ specDir });
|
|
210
|
+
const rows = pm._ensureDB(cwd).getDb().prepare(
|
|
211
|
+
`SELECT name, last_active, last_synced_platform_ts, last_local_modified_ts
|
|
212
|
+
FROM changes WHERE status IN ('archived','deleted')
|
|
213
|
+
ORDER BY last_active DESC LIMIT ${TERMINAL_SWEEP_CANDIDATES}`
|
|
214
|
+
).all();
|
|
215
|
+
const pending = [];
|
|
216
|
+
for (const r of rows) {
|
|
217
|
+
if (!r.name) continue;
|
|
218
|
+
const synced = r.last_synced_platform_ts || '';
|
|
219
|
+
const local = r.last_local_modified_ts || '';
|
|
220
|
+
// 脏度戳必须在场(fail-closed):D-013 之前的陈年行两戳皆空,无从判滞后——补推只会
|
|
221
|
+
// 给平台凭空造新行;synced 空而 local 在场 = 从未成功推送过但终态有据 → 补。
|
|
222
|
+
if (local && (!synced || local > synced)) pending.push(r.name);
|
|
223
|
+
if (pending.length >= TERMINAL_SWEEP_MAX) break;
|
|
224
|
+
}
|
|
225
|
+
return pending;
|
|
226
|
+
}
|
|
227
|
+
|
|
181
228
|
/**
|
|
182
229
|
* 子侧入口(bootstrap 经 env 传参调起;env 注入默认值供测试覆盖)。
|
|
183
230
|
*
|
|
@@ -224,6 +271,26 @@ export async function runBgSyncFromEnv(env = process.env) {
|
|
|
224
271
|
break;
|
|
225
272
|
}
|
|
226
273
|
}
|
|
274
|
+
// 终态补推(2026-09-23 quick ql-20260923-007):主轮收尾后扫 DB,补推「本地已达终态但
|
|
275
|
+
// 平台镜像滞后」的变更(session-fork 实证缺口)。幂等;主变更刚推过跳过;预算耗尽留下一轮。
|
|
276
|
+
try {
|
|
277
|
+
const pending = await collectTerminalSyncPending(cwd);
|
|
278
|
+
for (const name of pending) {
|
|
279
|
+
if (name === changeName) continue;
|
|
280
|
+
if (Date.now() + roundTimeoutMs > deadline) {
|
|
281
|
+
console.warn('[spec-sync-bg] 总预算用尽,终态补推剩余条目由下一轮 bg 接管');
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
console.log(`[spec-sync-bg] 终态补推(本地终态未被平台镜像): ${name}`);
|
|
285
|
+
try {
|
|
286
|
+
await triggerSync(cwd, name, platformOpts, { inline: true, timeoutMs: roundTimeoutMs });
|
|
287
|
+
} catch (e) {
|
|
288
|
+
console.warn(`[spec-sync-bg] 终态补推异常(best-effort,下轮重试): ${(e && e.message) || e}`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
} catch (e) {
|
|
292
|
+
console.warn(`[spec-sync-bg] 终态补推扫描异常(best-effort): ${(e && e.message) || e}`);
|
|
293
|
+
}
|
|
227
294
|
console.log(`[spec-sync-bg] 后台同步完成 ${new Date().toISOString()}: ${changeName}`);
|
|
228
295
|
removeBgSyncLock(runtimeRoot);
|
|
229
296
|
}
|
package/src/run/command.js
CHANGED
|
@@ -20,14 +20,14 @@
|
|
|
20
20
|
* './run/stage.js' → './shared.js' 等同级
|
|
21
21
|
*/
|
|
22
22
|
import { basename, join, resolve, dirname } from 'node:path'
|
|
23
|
-
import { existsSync, readdirSync, mkdirSync, writeFileSync, readFileSync, rmSync, unlinkSync } from 'node:fs'
|
|
23
|
+
import { existsSync, readdirSync, mkdirSync, writeFileSync, readFileSync, rmSync, unlinkSync, statSync } from 'node:fs'
|
|
24
24
|
import { randomBytes, randomUUID } from 'node:crypto'
|
|
25
25
|
import { fileURLToPath } from 'node:url'
|
|
26
26
|
import { writeAtomicSync } from '../fs-atomic.js'
|
|
27
|
-
import { resolveSpecDir, countAncestorSpecDirs, ancestorSpecDirs, resolveAncestorCeiling, resolveChangeDir, triggerSync, getStageSteps, formatWaitOptions, checkApproval, warnApprovalUnknown, didYouMean, assertSafeChangeName, assertDatedChangeName, detectQuickSessionDrift, detectWorktreeSpecDrift, resolveRuntimeRoot, resolveQuickSessionsDir, writePlatformPointer, checkPlatformManaged, isSelfReferentialSpecRoot, isTempResidueSpecRoot, PLATFORM_MANAGED_FILENAME, warnSelfRefPointerOnce } from './shared.js'
|
|
27
|
+
import { resolveSpecDir, countAncestorSpecDirs, ancestorSpecDirs, resolveAncestorCeiling, resolveChangeDir, triggerSync, getStageSteps, formatWaitOptions, checkApproval, warnApprovalUnknown, didYouMean, assertSafeChangeName, assertDatedChangeName, detectQuickSessionDrift, detectWorktreeSpecDrift, resolveRuntimeRoot, resolveQuickSessionsDir, writePlatformPointer, checkPlatformManaged, isSelfReferentialSpecRoot, isTempResidueSpecRoot, PLATFORM_MANAGED_FILENAME, warnSelfRefPointerOnce, readStageBurst, STAGE_BURST_STAGES } from './shared.js'
|
|
28
28
|
import { resolveQuickLinkedChanges } from './quick-audit.js'
|
|
29
29
|
import { outputStep, collectStageWaitHistory } from './prompt.js'
|
|
30
|
-
import { completeStep, skipStep, waitStep, continueStep, synthesizeStepOutput } from './complete.js'
|
|
30
|
+
import { completeStep, completeStepBurst, skipStep, waitStep, continueStep, synthesizeStepOutput } from './complete.js'
|
|
31
31
|
import { runStage } from './stage.js'
|
|
32
32
|
import { sanitizeDesc } from '../quicklog.js'
|
|
33
33
|
import { ProgressManager, resolveSessionIdentity } from '../progress.js'
|
|
@@ -52,6 +52,7 @@ const VALUE_FLAGS = new Set([
|
|
|
52
52
|
'--base', // scan diff 基线 commit(吃值;只在 run scan --diff 转发路径消费)
|
|
53
53
|
'--session', // 显式会话标识(吃值;2026-09-14-change-ownership-guards task-02 所有权三级解析最高优先级层,启动 claim 消费)
|
|
54
54
|
'--inherit-from', // wait 继承盖章锚点 <D-xxx@vN>(吃值;2026-09-18-preflight-slimming task-03 / D-003@v2:仅 --wait 场景合法,--wait 分发点校验消费)
|
|
55
|
+
'--step', // --done --step <名|序号> 意图断言(吃值;:340 消费。漏登记死路同 --wait-interactive 前例:burst 说明书出示该形态却被未知参数 exit(2) 拦死,2026-09-23 执行会话实证)
|
|
55
56
|
])
|
|
56
57
|
|
|
57
58
|
/**
|
|
@@ -511,6 +512,10 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
|
|
|
511
512
|
console.error(`❌ 检测到祖先链有 ${ancestorSpecs.length} 个 .sillyspec 实例(monorepo 多实例),当前使用: ${specBase}`)
|
|
512
513
|
console.error(` 当前 cwd 不在 git 顶层仓库根下,静默绑定到子项目实例会导致进度/QUICKLOG 分裂。`)
|
|
513
514
|
console.error(` 修复:cd 回项目根目录,或用 --spec-dir <根>/.sillyspec 显式指定。`)
|
|
515
|
+
// 一键指定整行(R4-S-Q 实证:嵌套 worktree 场景下 agent 多次试错才拼对放行组合)——
|
|
516
|
+
// 意图实例即当前最近命中(specBase),直接给出可贴完整命令行。
|
|
517
|
+
console.error(` 一键指定(当前最近实例,整行照贴):`)
|
|
518
|
+
console.error(` sillyspec ${process.argv.slice(2).join(' ')} --spec-dir "${specBase}"`)
|
|
514
519
|
process.exit(2)
|
|
515
520
|
}
|
|
516
521
|
// 在 git 根下但有多实例(子项目也有 .sillyspec)→ 仅警告(正确实例已命中)
|
|
@@ -716,6 +721,10 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
|
|
|
716
721
|
'--takeover', // 所有权护栏显式强制接管(task-02 / FR-01:本层只注册透传,实际消费在 index.js apply/cleanup 子命令层——run 侧带上不报未知参数,语义出口在 worktree 命令)
|
|
717
722
|
'--skip-apply', // 归档收口跳过 apply 校验(task-02 只注册透传不改行为,消费归 task-03 archive 接线)
|
|
718
723
|
'--inherit-from', // wait 继承盖章 <D-xxx@vN>(2026-09-18-preflight-slimming task-03:仅 --wait 场景合法,hasDecisionId 校验+盖章在 --wait 分发点/complete 层)
|
|
724
|
+
'--step', // --done 意图断言 <名|序号>(吃值,VALUE_FLAGS 同步登记;:340 消费——漏登记则说明书出示的 --done --step 形态进命令即被未知参数拦死,2026-09-23 执行会话实证)
|
|
725
|
+
'--upgrade-thick', // 轻量→完整升档用户同意门(2026-09-25-thin-upgrade-consent:布尔 flag,混跑回退写侧 :1362 消费——无 flag 拒跑,带 flag 落 legacy_fallback+同意时点留痕)
|
|
726
|
+
'--same-session', // R17 实验实证死路清偿(cli-protocol-trust):STAGE_WALL 报错指引「加 --same-session 重跑」(stage.js 硬墙文案),消费在 runStage 逃生口——此前漏登记,照指引重跑即被本白名单 exit 2(指引指向死路的自我矛盾)
|
|
727
|
+
'--force', // 同族第二实例(静态扫描新发现):quick --cancel 强清消费点与报错指引(可用 --force)在场但未登记——同死路;一致性钉 test/flag-contract.test.mjs 守护整类
|
|
719
728
|
'-h',
|
|
720
729
|
])
|
|
721
730
|
for (let i = 0; i < flags.length; i++) {
|
|
@@ -791,6 +800,43 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
|
|
|
791
800
|
} catch {}
|
|
792
801
|
}
|
|
793
802
|
if (!quickSessionId) {
|
|
803
|
+
// 坑 quick-done-长静默与快照行尾假阳性 坑3(2026-09-21 实证):`quick --status` 不带
|
|
804
|
+
// --change 时缺省语义是「新建」而非「查看」——查进度忘带 --change 直接误开空壳会话
|
|
805
|
+
//(需 --cancel 清理)。只读意图(--status)拦截:读 current marker 与最近 guard 目录
|
|
806
|
+
// 展示最近会话状态 + 提示带 --change,零副作用退出(绝不新建)。
|
|
807
|
+
if (isStatus) {
|
|
808
|
+
const recentRoot = resolveRuntimeRoot(platformOpts, specRoot)
|
|
809
|
+
let recent = null
|
|
810
|
+
try {
|
|
811
|
+
const idFile = join(recentRoot, 'current-quick-run-id')
|
|
812
|
+
if (existsSync(idFile)) {
|
|
813
|
+
const v = readFileSync(idFile, 'utf8').trim()
|
|
814
|
+
if (QUICK_SID_RE.test(v)) recent = v
|
|
815
|
+
}
|
|
816
|
+
} catch { /* 读失败按无 marker 处理 */ }
|
|
817
|
+
if (!recent) {
|
|
818
|
+
try {
|
|
819
|
+
const sessRoot = join(recentRoot, 'quick-sessions')
|
|
820
|
+
if (existsSync(sessRoot)) {
|
|
821
|
+
const cands = readdirSync(sessRoot, { withFileTypes: true })
|
|
822
|
+
.filter(d => d.isDirectory() && QUICK_SID_RE.test(d.name))
|
|
823
|
+
.map(d => ({ name: d.name, m: statSync(join(sessRoot, d.name, 'guard.json')).mtimeMs }))
|
|
824
|
+
.filter(x => x.m > 0)
|
|
825
|
+
.sort((a, b) => b.m - a.m)
|
|
826
|
+
if (cands.length > 0) recent = cands[0].name
|
|
827
|
+
}
|
|
828
|
+
} catch { /* 枚举失败按无近期会话处理 */ }
|
|
829
|
+
}
|
|
830
|
+
console.log('ℹ️ `run quick --status` 不带 --change 查的是最近会话,不会新建(查进度请带 --change <sessionId>)')
|
|
831
|
+
if (recent) {
|
|
832
|
+
console.log(` 最近会话: ${recent}(本命令将展示其状态)`)
|
|
833
|
+
quickSessionId = recent
|
|
834
|
+
quickFallbackUsed = true
|
|
835
|
+
} else {
|
|
836
|
+
console.log(' 本仓无近期 quick 会话记录(current marker 与 quick-sessions 均空)——新工作请走轻量变更:sillyspec flow start --change <名> --input "<动机与背景;随后独立一行『成功标准:』;再每行一条『- <可验证标准>』>"')
|
|
837
|
+
process.exit(0)
|
|
838
|
+
}
|
|
839
|
+
} else {
|
|
794
840
|
// 坑 quick-no-input-placeholder-title(2026-08-24 二期①;2026-09-19 上移):全新 quick 会话
|
|
795
841
|
// (即将生成新 sessionId)缺 --input 且无可取标题的 --linked-changes → 拒绝启动(exit 2,
|
|
796
842
|
// 零沉没成本)。原位置在 flag 校验之后,但 sessionId 生成/「已建立」公告与 agent-log 平台
|
|
@@ -824,6 +870,7 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
|
|
|
824
870
|
// 若把命令拖过 exec 超时,调用方仍能从输出头部拿到 sessionId 续用(--status / guard.json
|
|
825
871
|
// 核对),不会误开空壳新会话。
|
|
826
872
|
console.log(`📌 quick 会话已建立: ${quickSessionId}(后续若超时/中断,用 --change ${quickSessionId} 续用)`)
|
|
873
|
+
}
|
|
827
874
|
}
|
|
828
875
|
changeName = quickSessionId
|
|
829
876
|
} else {
|
|
@@ -1301,6 +1348,45 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
|
|
|
1301
1348
|
// 注册变更到全局活跃列表(如果尚未注册)
|
|
1302
1349
|
if (effectiveChange) {
|
|
1303
1350
|
pm.registerChange(cwd, effectiveChange)
|
|
1351
|
+
// ── watcher 拉起(R7 切片一 / D-001:观测与协议解耦)──
|
|
1352
|
+
// effectiveChange 解析后无条件 spawn(不做「检测新建」——registerChange 是 INSERT OR
|
|
1353
|
+
// IGNORE 不返回新建信号),活锁在跑由单飞锁合并 coalesced。best-effort:失败只 warn
|
|
1354
|
+
// 绝不阻断主流程;未连接平台也 spawn(本地 jsonl 是事件唯一真相源);
|
|
1355
|
+
// SILLYSPEC_WATCHER=0 逃生阀。default 容器行不监听(非真实变更)。
|
|
1356
|
+
if (effectiveChange !== 'default') {
|
|
1357
|
+
// ── 混跑回退写侧(R7 切片二 / FR-06 / D-002:thin change 上跑 run <stage> = 该 change
|
|
1358
|
+
// 回 legacy 记账——flow done 读侧拒裁并指路厚档;两套记账不叠加。一次性置位留痕。)──
|
|
1359
|
+
// 升厚同意门(2026-09-25-thin-upgrade-consent,R16 实测驱动):轻量→完整是成本数倍的资源
|
|
1360
|
+
// 决策,归属用户——agent 不得凭 CLI 的 advisory 自行转道。混跑回退须显式 --upgrade-thick
|
|
1361
|
+
// (=「用户已同意」的落痕 flag,同 --force 留痕先例);无 flag 拒跑指路。
|
|
1362
|
+
try {
|
|
1363
|
+
const flowStatePath = join(specRoot, 'changes', effectiveChange, 'flow-state.yaml')
|
|
1364
|
+
if (existsSync(flowStatePath)) {
|
|
1365
|
+
const { readFlowState, writeFlowState } = await import('../flow.js')
|
|
1366
|
+
const changeDir = join(specRoot, 'changes', effectiveChange)
|
|
1367
|
+
const st = readFlowState(changeDir)
|
|
1368
|
+
if (st && !st.legacy_fallback) {
|
|
1369
|
+
if (!flags.includes('--upgrade-thick')) {
|
|
1370
|
+
console.error(`⛔ 本变更在轻量跑道上(flow-state 在场)——升厚走完整流程需用户同意,agent 不得自行转道:`)
|
|
1371
|
+
console.error(` · 征得用户同意后带 --upgrade-thick 重跑本命令(落痕留档,首次转厚即转)`)
|
|
1372
|
+
console.error(` · 用户未确认或选择轻量跑 → 继续轻量变更:直接干活,收口 sillyspec flow done --change ${effectiveChange}`)
|
|
1373
|
+
process.exit(2)
|
|
1374
|
+
}
|
|
1375
|
+
writeFlowState(changeDir, { legacy_fallback: true, upgraded_by_consent: new Date().toISOString() })
|
|
1376
|
+
console.warn(`⚠️ [flow] thin change「${effectiveChange}」经用户同意(--upgrade-thick 落痕)升厚——已混跑回退 legacy(flow done 将按厚档拒裁并指路;两套记账不叠加)`)
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
} catch { /* 探测失败不阻断既有 run 流程(process.exit 直出不经 catch) */ }
|
|
1380
|
+
try {
|
|
1381
|
+
const { spawnWatcher } = await import('../watcher.js')
|
|
1382
|
+
const r = await spawnWatcher(cwd, effectiveChange, { ...platformOpts, specBase })
|
|
1383
|
+
if (r.status === 'spawned') {
|
|
1384
|
+
console.log(`🔄 [watcher] 观测旁路已拉起(产物签名轮询,事件恒带 provisional:true): ${effectiveChange}`)
|
|
1385
|
+
}
|
|
1386
|
+
} catch (e) {
|
|
1387
|
+
console.warn(`⚠️ [watcher] 拉起失败(观测旁路 best-effort,不影响主流程): ${(e && e.message) || e}`)
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1304
1390
|
// ── 所有权首建 claim(2026-09-14-change-ownership-guards task-02 / FR-01 / D-001@v1)──
|
|
1305
1391
|
// run <stage> 启动即认领 owner(claimChangeOwner 守卫语义:已有值不覆盖——他人持有不抢
|
|
1306
1392
|
// 也不拒;拒绝语义只锁接管类操作,见 index.js apply/cleanup/assess 接线)。会话标识三级:
|
|
@@ -1652,11 +1738,16 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
|
|
|
1652
1738
|
const doneAnswer = getFlagValue('--answer')
|
|
1653
1739
|
// isSkipApply/sessionFlag(task-03):--skip-apply 归档收口跳过 + --session 所有权会话标识,
|
|
1654
1740
|
// 随 completeStep options 透传(isNoDocs 同款链路)——消费点 archive/quick 收尾 handler。
|
|
1741
|
+
// burst 分发门(2026-09-22-stage-burst-fold D-003@v2/FR-06):白名单主阶段 + readStageBurst
|
|
1742
|
+
// 开启 → completeStepBurst 循环收口(completeStep 本体零改动,守卫逐轮生效);options 原样透传。
|
|
1743
|
+
if (STAGE_BURST_STAGES.includes(stageName) && await readStageBurst(cwd)) {
|
|
1744
|
+
return await completeStepBurst(pm, progress, stageName, cwd, outputText, inputText, { confirm: isConfirm, changeName: effectiveChange, nonInteractive: isNonInteractive && !isInteractive, platformOpts, doneAnswer, isForceBaseline, isAllowNew, isAllowDelete, isNoDocs, isSkipApply, sessionFlag: getFlagValue('--session'), quickFiles, stepAssert: stepAssertValue })
|
|
1745
|
+
}
|
|
1655
1746
|
return await completeStep(pm, progress, stageName, cwd, outputText, inputText, { confirm: isConfirm, changeName: effectiveChange, nonInteractive: isNonInteractive && !isInteractive, platformOpts, doneAnswer, isForceBaseline, isAllowNew, isAllowDelete, isNoDocs, isSkipApply, sessionFlag: getFlagValue('--session'), quickFiles, stepAssert: stepAssertValue })
|
|
1656
1747
|
}
|
|
1657
1748
|
|
|
1658
1749
|
// 默认:输出当前步骤
|
|
1659
|
-
return await runStage(pm, progress, stageName, cwd, effectiveChange, isSkipApproval, platformOpts, { quickFiles, isAllowNew, isAllowDelete, isForceBaseline, isForceRescan, linkedChanges, linkedChangesAuto: linkedAuto, taskDescription: inputText, adoptBranch: stageName === 'execute' && flags.includes('--adopt-branch') })
|
|
1750
|
+
return await runStage(pm, progress, stageName, cwd, effectiveChange, isSkipApproval, platformOpts, { quickFiles, isAllowNew, isAllowDelete, isForceBaseline, isForceRescan, linkedChanges, linkedChangesAuto: linkedAuto, taskDescription: inputText, adoptBranch: stageName === 'execute' && flags.includes('--adopt-branch'), sameSession: flags.includes('--same-session') })
|
|
1660
1751
|
}
|
|
1661
1752
|
|
|
1662
1753
|
/**
|
|
@@ -1844,6 +1935,23 @@ async function runAutoMode(pm, progress, cwd, flags, changeName, platformOpts =
|
|
|
1844
1935
|
// 零活跃建变更已上移入口守卫层,多活跃 exit 2 由入口守卫处理)。
|
|
1845
1936
|
if (changeName) console.log(`🎯 auto 目标变更(单活跃自动选中或显式指定):${changeName}`)
|
|
1846
1937
|
|
|
1938
|
+
// ── watcher 拉起(2026-09-23-sentinel-rules task-05 / FR-09)──
|
|
1939
|
+
// run auto 在 runCommand 主干的 spawnWatcher 挂点(effectiveChange 解析后那处)之前
|
|
1940
|
+
// 早退进本函数——auto 路径是 run 族唯一观测缺口。语义同 flow start 接线:best-effort
|
|
1941
|
+
// 失败只 warn 不阻断,SILLYSPEC_WATCHER=0 逃生阀在 spawnWatcher 内短路,活锁合并
|
|
1942
|
+
// coalesced,未连接平台也 spawn(本地 jsonl 是事件唯一真相源)。
|
|
1943
|
+
if (changeName && changeName !== 'default') {
|
|
1944
|
+
try {
|
|
1945
|
+
const { spawnWatcher } = await import('../watcher.js')
|
|
1946
|
+
const r = await spawnWatcher(cwd, changeName, { ...platformOpts, specBase })
|
|
1947
|
+
if (r.status === 'spawned') {
|
|
1948
|
+
console.log(`🔄 [watcher] 观测旁路已拉起(auto 路径补挂,事件恒带 provisional:true): ${changeName}`)
|
|
1949
|
+
}
|
|
1950
|
+
} catch (e) {
|
|
1951
|
+
console.warn(`⚠️ [watcher] 拉起失败(观测旁路 best-effort,不影响主流程): ${(e && e.message) || e}`)
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1847
1955
|
// Helper: 在 auto 模式下获取步骤定义
|
|
1848
1956
|
const getAutoSteps = async (stage) => {
|
|
1849
1957
|
if (stage === 'brainstorm') {
|
|
@@ -1901,8 +2009,9 @@ async function runAutoMode(pm, progress, cwd, flags, changeName, platformOpts =
|
|
|
1901
2009
|
const localConfig = readAutoModeFromLocalYaml(cwd)
|
|
1902
2010
|
const classification = classifyChange({ description: inputText || '', explicitMode, localConfig })
|
|
1903
2011
|
if (classification.mode === 'quick') {
|
|
1904
|
-
console.log(`📊 auto 模式分类:${classification.
|
|
1905
|
-
console.log(`
|
|
2012
|
+
console.log(`📊 auto 模式分类:${classification.reason} 判为小变更`)
|
|
2013
|
+
console.log(` 小变更请走轻量变更(quick 通道已退役,v3.30.0 起拒绝新会话):`)
|
|
2014
|
+
console.log(` sillyspec flow start --change <名> --input "<动机与背景;随后独立一行『成功标准:』;再每行一条『- <可验证标准>』>"`)
|
|
1906
2015
|
return
|
|
1907
2016
|
}
|
|
1908
2017
|
console.log(`📊 auto 模式分类:${classification.mode}(${classification.reason})`)
|
|
@@ -1989,16 +2098,24 @@ async function runAutoMode(pm, progress, cwd, flags, changeName, platformOpts =
|
|
|
1989
2098
|
return
|
|
1990
2099
|
}
|
|
1991
2100
|
|
|
1992
|
-
|
|
2101
|
+
// burst 分发门(2026-09-22-stage-burst-fold D-003@v2/X-4,FR-06):auto 路径 burst 分支跳过
|
|
2102
|
+
// --output 预合成(预合成文本在 burst 下沦为横幅重复打印——burst 直接透传原始 outputText,
|
|
2103
|
+
// 横幅去重归 completeStepBurst 单点),循环收口后本路径既有推进逻辑(nextPendingIdx 渲染 /
|
|
2104
|
+
// nextInFlow 阶段推进)基于重读 progress 自然衔接。
|
|
2105
|
+
const _autoBurstOn = STAGE_BURST_STAGES.includes(currentStage) && await readStageBurst(cwd)
|
|
2106
|
+
|
|
2107
|
+
if (!outputText && !_autoBurstOn) {
|
|
1993
2108
|
// P0-2(noai-ir-roadmap §3):auto --done 的 --output 同样可省略——与 completeStep 同源
|
|
1994
2109
|
// 的事实性摘要合成(此前硬拒 exit 2 是用法守卫;SS-META 的 doneCommand 仍带 --output,
|
|
1995
2110
|
// 照抄路径零变化,省略路径不再罚一轮)。
|
|
1996
2111
|
const _autoStep = progress?.stages?.[currentStage]?.steps?.find((s) => s && ['pending', 'in-progress', 'blocked'].includes(s.status))
|
|
1997
|
-
outputText = synthesizeStepOutput({ stageName: currentStage, stepName: _autoStep?.name || currentStage, cwd })
|
|
2112
|
+
outputText = synthesizeStepOutput({ stageName: currentStage, stepName: _autoStep?.name || currentStage, cwd, platformOpts })
|
|
1998
2113
|
console.log(`🤖 auto --done 未带 --output——CLI 已按事实合成步骤摘要(语义说明下次带 --output 手写)。`)
|
|
1999
2114
|
}
|
|
2000
2115
|
|
|
2001
|
-
const result =
|
|
2116
|
+
const result = _autoBurstOn
|
|
2117
|
+
? await completeStepBurst(pm, progress, currentStage, cwd, outputText, inputText, { printNext: false, changeName, platformOpts })
|
|
2118
|
+
: await completeStep(pm, progress, currentStage, cwd, outputText, inputText, { printNext: false, changeName, platformOpts })
|
|
2002
2119
|
if (!result) return
|
|
2003
2120
|
progress = pm.read(cwd, changeName)
|
|
2004
2121
|
// change 行被并发归档/删除时 read 返回 null(体检 BUG-05):引导排查而非 TypeError 崩溃
|
|
@@ -2157,7 +2274,7 @@ export async function maybeWaitInteractive(stepDef, changeName, stageName, { rlF
|
|
|
2157
2274
|
const { execFileSync } = await import('node:child_process')
|
|
2158
2275
|
const binSelf = resolveBinSelfPath()
|
|
2159
2276
|
try {
|
|
2160
|
-
execFileSync(process.execPath, [binSelf, 'run', 'auto', '--continue', '--answer', answer.trim(), '--change', changeName], { stdio: 'inherit', cwd: process.cwd() })
|
|
2277
|
+
execFileSync(process.execPath, [binSelf, 'run', 'auto', '--continue', '--answer', answer.trim(), '--change', changeName], { stdio: 'inherit', cwd: process.cwd(), windowsHide: true })
|
|
2161
2278
|
} catch { /* continue 失败(非 waiting 态等)静默——回三段式由 agent 走 */ }
|
|
2162
2279
|
}
|
|
2163
2280
|
} catch (e) {
|