sillyspec 3.29.3 → 3.29.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/asset-audit-2026-09-20/README.md +42 -0
- package/asset-audit-2026-09-20/asset-audit.cjs +253 -0
- package/asset-audit-2026-09-20/extract-candidates.cjs +37 -0
- package/asset-audit-2026-09-20/platform-archive.csv +294 -0
- package/asset-audit-2026-09-20/platform-fr.csv +12 -0
- package/asset-audit-2026-09-20/platform-gotchas.csv +6 -0
- package/asset-audit-2026-09-20/platform-knownissues.csv +23 -0
- package/asset-audit-2026-09-20/platform-modules.csv +222 -0
- package/asset-audit-2026-09-20/platform-quicklog.csv +1076 -0
- package/asset-audit-2026-09-20/platform-troubleshooting-candidates.csv +88 -0
- package/asset-audit-2026-09-20/platform-troubleshooting.csv +1 -0
- package/asset-audit-2026-09-20/sillyspec-archive.csv +108 -0
- package/asset-audit-2026-09-20/sillyspec-fr.csv +51 -0
- package/asset-audit-2026-09-20/sillyspec-gotchas.csv +4 -0
- package/asset-audit-2026-09-20/sillyspec-knownissues.csv +34 -0
- package/asset-audit-2026-09-20/sillyspec-modules.csv +30 -0
- package/asset-audit-2026-09-20/sillyspec-quicklog.csv +460 -0
- package/asset-audit-2026-09-20/sillyspec-troubleshooting.csv +69 -0
- package/asset-audit-2026-09-20/summary.json +241 -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 +34 -0
- package/assets/command-cards/run-verify.md +29 -0
- package/assets/command-cards/status.md +27 -0
- package/package.json +1 -1
- package/round4/account.mjs +85 -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/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-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/result-S.md +171 -0
- package/round4/result-final.md +46 -0
- package/round4/result-quality.md +63 -0
- package/src/command-cards.js +73 -0
- package/src/contract-matrix.js +26 -5
- package/src/cross-repo-reconcile.js +232 -46
- package/src/diagnostic-codes.js +2 -1
- package/src/fr-index.js +58 -2
- package/src/index.js +35 -8
- package/src/init.js +25 -8
- package/src/machine-interface.js +88 -3
- package/src/module-impact.js +51 -22
- package/src/run/bg-sync.js +231 -0
- package/src/run/command.js +4 -0
- package/src/run/complete-handlers.js +244 -6
- package/src/run/complete.js +6 -3
- package/src/run/gate-snapshot.js +10 -4
- package/src/run/gates.js +124 -42
- package/src/run/prompt.js +1 -1
- package/src/run/quick-audit.js +28 -2
- package/src/run/shared.js +35 -1
- package/src/scope-audit.js +303 -20
- package/src/stages/execute.js +2 -2
- package/src/stages/plan-postcheck.js +33 -19
- package/src/stages/quick.js +2 -0
- package/src/sync.js +12 -0
- package/src/taskcard-frontmatter.js +57 -0
- package/src/verify-postcheck.js +68 -6
- package/src/verify-probes.js +45 -23
|
@@ -30,11 +30,11 @@
|
|
|
30
30
|
* (非被外层吞);process.exit 不可被 try 捕获 → 搬迁行为完全等价。
|
|
31
31
|
*/
|
|
32
32
|
import { basename, dirname, join, resolve, relative, isAbsolute } from 'node:path'
|
|
33
|
-
import { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync, unlinkSync, rmSync } from 'node:fs'
|
|
33
|
+
import { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync, unlinkSync, rmSync, renameSync } from 'node:fs'
|
|
34
34
|
import { createHash } from 'node:crypto'
|
|
35
35
|
import { renameSyncRetry, writeAtomicSync } from '../fs-atomic.js'
|
|
36
36
|
import { gitQuiet } from '../git-helper.js'
|
|
37
|
-
import { resolveChangeDir, resolveQuickSessionsDir, safeGit, auditQuickCompletion, triggerSync, isQuickMetadata, isQuicklogFileLineNoise, resolveRuntimeRoot, collectOtherQuickSessionDeclarations, mergeQuickBoundaryFiles } from './shared.js'
|
|
37
|
+
import { resolveChangeDir, resolveQuickSessionsDir, safeGit, auditQuickCompletion, triggerSync, isQuickMetadata, isQuicklogFileLineNoise, resolveRuntimeRoot, collectOtherQuickSessionDeclarations, mergeQuickBoundaryFiles, QUICK_SID_RE } from './shared.js'
|
|
38
38
|
import { detectConcurrentChanges, formatConcurrentWarning, resolveConcurrentAnchor } from './concurrent-detect.js'
|
|
39
39
|
import { stageRegistry } from '../stages/index.js'
|
|
40
40
|
import { SCAN_STATUS, POINTER_STATUS } from '../constants.js'
|
|
@@ -1265,6 +1265,25 @@ export async function handleWorkflowPostCheck({ stageName, steps, currentIdx, cw
|
|
|
1265
1265
|
}
|
|
1266
1266
|
return null
|
|
1267
1267
|
}
|
|
1268
|
+
/**
|
|
1269
|
+
* guard.linkedChanges/linkedChangesAuto 并入 --done 显式声明(R4-S-Q 缺陷 B 纯函数核):
|
|
1270
|
+
* 持久化值 ∪ 显式值(去重保序,persisted 在前);显式 ['none'] 语义为清空 manual 面(auto 不动);
|
|
1271
|
+
* 无变化返回原对象引用(调用点以此判「是否需要回写」)。guard 非对象原样返回。
|
|
1272
|
+
*/
|
|
1273
|
+
export function mergeGuardLinkedChanges(guard, explicitLinked, explicitAuto) {
|
|
1274
|
+
if (!guard || typeof guard !== 'object') return guard
|
|
1275
|
+
const cur = Array.isArray(guard.linkedChanges) ? guard.linkedChanges : []
|
|
1276
|
+
const curAuto = Array.isArray(guard.linkedChangesAuto) ? guard.linkedChangesAuto : []
|
|
1277
|
+
const ex = (Array.isArray(explicitLinked) ? explicitLinked : []).filter(Boolean)
|
|
1278
|
+
const exAuto = (Array.isArray(explicitAuto) ? explicitAuto : []).filter(Boolean)
|
|
1279
|
+
const next = ex.length === 1 && ex[0] === 'none' ? [] : [...new Set([...cur, ...ex])]
|
|
1280
|
+
const nextAuto = exAuto.length === 0 ? curAuto : [...new Set([...curAuto, ...exAuto])]
|
|
1281
|
+
const unchanged = next.length === cur.length && next.every((v, i) => v === cur[i])
|
|
1282
|
+
&& nextAuto.length === curAuto.length && nextAuto.every((v, i) => v === curAuto[i])
|
|
1283
|
+
if (unchanged) return guard
|
|
1284
|
+
return { ...guard, linkedChanges: next, linkedChangesAuto: nextAuto }
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1268
1287
|
/**
|
|
1269
1288
|
* quick 阶段完成收尾(W6 Step6b 从 completeStep 内联块抽出):
|
|
1270
1289
|
* 强校验 QUICKLOG 条目 + 审计(auditQuickCompletion)+ 结果摘要结构校验 + 翻状态/勾 tasks.md
|
|
@@ -1274,7 +1293,7 @@ export async function handleWorkflowPostCheck({ stageName, steps, currentIdx, cw
|
|
|
1274
1293
|
* isForceBaseline/isAllowNew/platformOpts。辅助函数直接 import(safeGit/auditQuickCompletion ← shared,
|
|
1275
1294
|
* printQuickAuditReview ← quick-audit,4 个 quicklog fns ← quicklog,unlinkSync/rmSync ← fs 静态)。
|
|
1276
1295
|
*/
|
|
1277
|
-
export async function handleQuickStageCompletion({ stageName, steps, currentIdx, cwd, progress, changeName, specBase, outputText, confirm, isForceBaseline, isAllowNew, isAllowDelete, isNoDocs, sessionFlag = null, platformOpts, pm, quickFiles = [] }) {
|
|
1296
|
+
export async function handleQuickStageCompletion({ stageName, steps, currentIdx, cwd, progress, changeName, specBase, outputText, confirm, isForceBaseline, isAllowNew, isAllowDelete, isNoDocs, sessionFlag = null, platformOpts, pm, quickFiles = [], linkedChanges = [], linkedChangesAuto = [] }) {
|
|
1278
1297
|
// quick 收尾:强校验 QUICKLOG 条目 + 翻状态 + 勾 tasks.md(CLI 接管)
|
|
1279
1298
|
if (stageName === 'quick') {
|
|
1280
1299
|
// §4.6 从 session guard.json 读 guard(不依赖 progress.quickGuard)。
|
|
@@ -1293,6 +1312,22 @@ export async function handleQuickStageCompletion({ stageName, steps, currentIdx,
|
|
|
1293
1312
|
: (existsSync(legacyGuardFile) ? JSON.parse(readFileSync(legacyGuardFile, 'utf8')) : null)
|
|
1294
1313
|
} catch {}
|
|
1295
1314
|
|
|
1315
|
+
// R4-S-Q 缺陷 B:--done 时显式 --linked-changes 并入 guard(启动持久化值 ∪ 本次显式值,
|
|
1316
|
+
// 'none' 清空)再回写 sessionGuardFile。下游关账 closeQuickLinkedChanges / 资产尾蒸馏 /
|
|
1317
|
+
// quicklog「关联变更」渲染全读 guard——不并入则 --done 时声明的关联被静默丢弃(R4-S-Q
|
|
1318
|
+
// 实测:--linked-changes 两次都在命令行上,资产尾零触发 + 条目渲染「(无)」)。
|
|
1319
|
+
// guard 缺失(brownfield)不造对象——保持 D-3 无 guard 降级语义不变。
|
|
1320
|
+
try {
|
|
1321
|
+
if (guard && (linkedChanges.length > 0 || linkedChangesAuto.length > 0)) {
|
|
1322
|
+
const merged = mergeGuardLinkedChanges(guard, linkedChanges, linkedChangesAuto)
|
|
1323
|
+
if (merged !== guard) {
|
|
1324
|
+
guard = merged
|
|
1325
|
+
writeAtomicSync(sessionGuardFile, JSON.stringify(guard, null, 2))
|
|
1326
|
+
console.log(`🔗 --done 显式关联变更已并入 guard: ${(guard.linkedChanges ?? []).join(', ') || '(none 清空)'}`)
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
} catch { /* 并入/回写失败不拦完成,guard 内存原值兜底 */ }
|
|
1330
|
+
|
|
1296
1331
|
// 强校验 / 收尾:本会话必须有一条真实 QUICKLOG 条目(治「报 SAFE 但漏写」bug)。
|
|
1297
1332
|
// guard 缺失(brownfield:新代码前启动的会话)不阻断——兜底补写一条记录,保住「完成必有记录」不变量。
|
|
1298
1333
|
const gitUser = safeGit(cwd, ['config', 'user.name']).value || 'unknown'
|
|
@@ -1399,6 +1434,70 @@ export async function handleQuickStageCompletion({ stageName, steps, currentIdx,
|
|
|
1399
1434
|
progress.lastQuickReview = review
|
|
1400
1435
|
}
|
|
1401
1436
|
|
|
1437
|
+
// ── quick 资产尾(2026-09-20-quick-asset-tail,D-001/D-002/D-005):门禁过后三件机械
|
|
1438
|
+
// 事,agent 零新增命令/零写作义务,fail-open 全链(任何异常 warn 不拦 quick 完成)。
|
|
1439
|
+
// ① 薄通道蒸馏尾:linked 真变更(非 quick-<hex>)→ decision-distill + fr-index(幂等)
|
|
1440
|
+
// + lite 归档(治「薄通道零资产 + 僵尸 brainstorm 态」——autocompact 对撞实证)。
|
|
1441
|
+
// 挂点在门禁后(D-002 时序:防未实现设计进索引)。
|
|
1442
|
+
// ② 模块 changelog 机械追加:changedFiles×moduleIndex join → 边车存在才 append 一行
|
|
1443
|
+
// (纯 quick 的检索面——QUICKLOG 是时间流水账,模块边车是空间索引)。
|
|
1444
|
+
// ③ 根因 classify 提示:--cause 原文 × INDEX 关键词命中 → 打一行确切命令(非空提示)。
|
|
1445
|
+
try {
|
|
1446
|
+
// linkedChangesAuto 排除(S2 执行审查缺陷③):auto-link 命中的他者中途/僵尸变更不走蒸馏
|
|
1447
|
+
// 尾(quick-single-change-auto-link §3 契约——即便 requirements.md 在场也不蒸馏不归档,
|
|
1448
|
+
// 防未实现设计进索引 + 误归档他者 brainstorm 进行中变更)
|
|
1449
|
+
const linkedChanges = Array.isArray(mergedGuard?.linkedChanges) ? mergedGuard.linkedChanges : []
|
|
1450
|
+
const autoLinked = new Set(Array.isArray(mergedGuard?.linkedChangesAuto) ? mergedGuard.linkedChangesAuto : [])
|
|
1451
|
+
const manualLinked = linkedChanges.filter((c) => c && !QUICK_SID_RE.test(c) && !autoLinked.has(c))
|
|
1452
|
+
const { distillLinkedChangeAssets } = await import('./complete-handlers.js')
|
|
1453
|
+
const tail = await distillLinkedChangeAssets({ pm, cwd, specBase, changeName, linkedChanges: manualLinked, platformOpts })
|
|
1454
|
+
for (const w of tail.warnings) console.warn(`⚠️ [资产尾] ${w}`)
|
|
1455
|
+
} catch (e) {
|
|
1456
|
+
console.warn(`⚠️ [资产尾] 蒸馏尾异常(fail-open 不拦 quick 完成):${e && e.message ? e.message : e}`)
|
|
1457
|
+
}
|
|
1458
|
+
try {
|
|
1459
|
+
// ② changelog 边车追加——projectName 同源修复(S2 执行审查缺陷①a):progress.project
|
|
1460
|
+
// (与 outputStep 占位符渲染同源,平台模式真实项目名)三级回退,不再硬编码 null。
|
|
1461
|
+
const projName = progress.project
|
|
1462
|
+
|| steps[currentIdx].project
|
|
1463
|
+
|| (steps[currentIdx].name.match(/\[([^\]]+)\]\s*$/) || [])[1]
|
|
1464
|
+
|| null
|
|
1465
|
+
const { loadQuickModuleIndex } = await import('./shared.js')
|
|
1466
|
+
const moduleIndex2 = await loadQuickModuleIndex(specBase, projName)
|
|
1467
|
+
const changed2 = (progress.lastQuickReview?.changedFiles ?? []).map(f => String(f).replace(/\\/g, '/'))
|
|
1468
|
+
if (changed2.length > 0 && moduleIndex2 && projName) {
|
|
1469
|
+
const touchedMods = new Map()
|
|
1470
|
+
for (const [modId, mod] of Object.entries(moduleIndex2)) {
|
|
1471
|
+
const prefixes = [...(mod && Array.isArray(mod.paths) ? mod.paths : []), ...(mod && Array.isArray(mod.core_files) ? mod.core_files : [])]
|
|
1472
|
+
for (const raw of prefixes) {
|
|
1473
|
+
const p = String(raw).replace(/\\/g, '/').replace(/\/+$/, '')
|
|
1474
|
+
if (p && changed2.some(f => f === p || f.startsWith(p + '/'))) { touchedMods.set(modId, mod); break }
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
// 修复①b:边车路径补 <project> 段——map 的 doc 字段相对 docs/<project>/(如 modules/x.md)
|
|
1478
|
+
const modulesDir = join(specBase, 'docs', projName)
|
|
1479
|
+
for (const [modId, mod] of touchedMods) {
|
|
1480
|
+
try {
|
|
1481
|
+
const docRel = mod && mod.doc
|
|
1482
|
+
if (!docRel) continue
|
|
1483
|
+
const docNorm = String(docRel).replace(/\\/g, '/')
|
|
1484
|
+
const sidecarRel = docNorm.replace(/\.md$/, '.changelog.md')
|
|
1485
|
+
const target = join(modulesDir, sidecarRel)
|
|
1486
|
+
if (!existsSync(target)) continue // 边车不存在静默跳过(建卡是 scan 职责)
|
|
1487
|
+
const line = `- ${changeName} | quick 机械留痕(${changed2.length} 文件触达 ${modId};详账见 QUICKLOG)`
|
|
1488
|
+
appendFileSync(target, line + '\n')
|
|
1489
|
+
console.log(`🗂️ 模块留痕:${modId} changelog 边车 +1 行(${changeName})`)
|
|
1490
|
+
} catch { /* 单模块失败不连坐 */ }
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
} catch (e) {
|
|
1494
|
+
console.warn(`⚠️ [资产尾] changelog 追加异常(fail-open):${e && e.message ? e.message : e}`)
|
|
1495
|
+
}
|
|
1496
|
+
// ③ 根因 classify 提示——S2 复审缺陷②终裁:**删除本块**。既有「待归类提议」块
|
|
1497
|
+
// (下方 extractQuickCauseField → matchKnowledge → 确切 classify 命令)已完全承担此职责
|
|
1498
|
+
// 且传参/qlId/文件路径全正确(1836-1844);本块首版传对象给字符串参数(TypeError 被吞)
|
|
1499
|
+
// 二版仍是重复建设——机械件③的 classify 职责由既有块唯一承担,本变更不再另造。
|
|
1500
|
+
|
|
1402
1501
|
// task-02 并发预检(FR-05/FR-07,纯副作用 advisory):auditQuickCompletion 返回后、推进前
|
|
1403
1502
|
// 扫工作树,识别他者未提交改动 / 他者脏变更目录,有则 console.warn。不改 status/gate、
|
|
1404
1503
|
// 不 exit、不 return early(FR-07 铁律)。ownFiles 必含 baselineFiles(D-001:脏工作树 quick
|
|
@@ -1897,6 +1996,15 @@ async function closeSingleQuickLinkedChange({ pm, cwd, specBase, changeName, pla
|
|
|
1897
1996
|
* 缺陷①):current_stage=brainstorm 但该阶段 status=completed(brainstorm 收尾到 plan
|
|
1898
1997
|
* 开始之间的空窗)≠ 僵尸变更——此时关联 quick --done 会把即将进 plan 的进行中变更误
|
|
1899
1998
|
* 轻量归档。stage_status=completed 一律走原流程收尾。
|
|
1999
|
+
*
|
|
2000
|
+
* 转轨放行(2026-09-20 僵尸状态实证缺口):缺陷①②两闸把 d192f89 的原始目标场景——
|
|
2001
|
+
* brainstorm 精判 scale:small、设计裁定实现路径即 `quick --linked-changes <本变更>` 的
|
|
2002
|
+
* 刻意转轨变更——也拦成了永真(brainstorm --done 必然置 completed、转轨间隔天然分钟级
|
|
2003
|
+
* 必中时近性闸),linked quick --done 后变更永留 active/brainstorm 成僵尸。识别信号用
|
|
2004
|
+
* brainstorm 末步已落盘的机器可读产物:current_stage=brainstorm + stage_status=completed
|
|
2005
|
+
* + design.md frontmatter scale:small(readDesignScale,gates.js)三条件齐 = 转轨变更,
|
|
2006
|
+
* 放行两闸且 tasks 判定按 quick 语义(无 tasks.md=无待办);scale=large/未写 scale 的
|
|
2007
|
+
* 在途变更两闸原样生效(缺陷①②防护面不变)。
|
|
1900
2008
|
*/
|
|
1901
2009
|
const QUICK_CLOSE_ALLOWED_STAGES = new Set(['', 'scan', 'brainstorm'])
|
|
1902
2010
|
|
|
@@ -1913,6 +2021,10 @@ const QUICK_CLOSE_ACTIVITY_WINDOW_MS = 60 * 60 * 1000
|
|
|
1913
2021
|
* scan/brainstorm 才继续 tasks 判定;plan/execute/verify/archive 一律 skip 走原流程收尾。
|
|
1914
2022
|
* pm 缺 getChangeStage 接口或查询抛错同样 skip,不静默放行。
|
|
1915
2023
|
*
|
|
2024
|
+
* 转轨放行:current_stage=brainstorm + stage_status=completed + design.md scale:small =
|
|
2025
|
+
* brainstorm 精判裁定走 quick 的刻意转轨变更(见 QUICK_CLOSE_ALLOWED_STAGES 上注释),
|
|
2026
|
+
* 豁免「阶段完成态」「时近性」两闸与「无 tasks.md」判定,linked quick --done 即收尾。
|
|
2027
|
+
*
|
|
1916
2028
|
* @param {Object} opts
|
|
1917
2029
|
* @param {ProgressManager} opts.pm
|
|
1918
2030
|
* @param {string} opts.cwd
|
|
@@ -1952,13 +2064,25 @@ export async function closeQuickLinkedChanges({ pm, cwd, specBase, linkedChanges
|
|
|
1952
2064
|
})
|
|
1953
2065
|
continue
|
|
1954
2066
|
}
|
|
2067
|
+
// 刻意转轨判定(见 QUICK_CLOSE_ALLOWED_STAGES 上注释「转轨放行」段):scale:small 是
|
|
2068
|
+
// brainstorm 末步写进 design.md frontmatter 的精判产物(stages/brainstorm.js「生成规范
|
|
2069
|
+
// 文件」步),其设计实现路径就是本 quick 会话——linked quick --done 即本变更流程收尾点。
|
|
2070
|
+
// readDesignScale 动态导入破环(gates.js 静态导入本模块,其 :1175 反向动态 import 先例);
|
|
2071
|
+
// 导入/执行异常 fail-safe 按「非转轨」走原闸,不废既有护栏。
|
|
2072
|
+
let quickHandoff = false
|
|
2073
|
+
if (stageInfo !== null && stageInfo.current_stage === 'brainstorm' && stageInfo.stage_status === 'completed') {
|
|
2074
|
+
try {
|
|
2075
|
+
const { readDesignScale } = await import('./gates.js')
|
|
2076
|
+
quickHandoff = readDesignScale(specBase, changeName) === 'small'
|
|
2077
|
+
} catch { /* fail-safe:识别不了按非转轨走原闸 */ }
|
|
2078
|
+
}
|
|
1955
2079
|
// ql-20260819-010(quick-done-autoarchive-misfire 缺陷①):当前阶段已完成 ≠
|
|
1956
2080
|
// 从未进入完整流程的僵尸。brainstorm 收尾到 plan 开始之间的空窗里 current_stage
|
|
1957
2081
|
// 仍读 brainstorm,只看阶段名会把即将进 plan 的变更误轻量归档(propose 骨架
|
|
1958
2082
|
// tasks.md 除 quick 追加的 ql 行外没有任务行,「无未勾选框=全勾」恒真)。
|
|
1959
2083
|
// stage_status=completed 一律原流程收尾;null(无阶段行/brownfield)与旧调用方
|
|
1960
2084
|
// mock 缺字段(undefined)均按未完成放行,僵尸逃生通道行为不变。
|
|
1961
|
-
if (stageInfo !== null && stageInfo.stage_status === 'completed') {
|
|
2085
|
+
if (!quickHandoff && stageInfo !== null && stageInfo.stage_status === 'completed') {
|
|
1962
2086
|
skipped.push({
|
|
1963
2087
|
name: changeName,
|
|
1964
2088
|
reason: `变更当前阶段「${stageInfo.current_stage || '(空)'}」已完成(推进/收尾中,非僵尸变更),不自动归档——请走原流程收尾(sillyspec progress show 查看进度)`,
|
|
@@ -1974,7 +2098,10 @@ export async function closeQuickLinkedChanges({ pm, cwd, specBase, linkedChanges
|
|
|
1974
2098
|
//(既有「真·僵尸 in_progress→closed」行为保留)。pm 无此方法(旧 mock/旧进度库)
|
|
1975
2099
|
// 按无近期活动放行——与 getChangeStage 缺失 skip 的 fail-safe 不同向:本闸误放行的
|
|
1976
2100
|
// 最坏后果是回到缺陷②现状(有 D-002 防护兜底),误拦截则僵尸永不清——权衡取放行。
|
|
1977
|
-
|
|
2101
|
+
// (转轨变更例外:brainstorm --done → quick 启动/--done 间隔天然分钟级,时近性对它
|
|
2102
|
+
// 是永真拦截信号——quickHandoff 已用 scale:small 坐实「等 quick 落地」,不在「在途」
|
|
2103
|
+
// 语义面内,放行。)
|
|
2104
|
+
if (!quickHandoff && typeof pm.getLatestActivityAt === 'function') {
|
|
1978
2105
|
const latest = pm.getLatestActivityAt(cwd, changeName)
|
|
1979
2106
|
if (latest && Date.now() - new Date(latest).getTime() < QUICK_CLOSE_ACTIVITY_WINDOW_MS) {
|
|
1980
2107
|
skipped.push({
|
|
@@ -1984,7 +2111,13 @@ export async function closeQuickLinkedChanges({ pm, cwd, specBase, linkedChanges
|
|
|
1984
2111
|
continue
|
|
1985
2112
|
}
|
|
1986
2113
|
}
|
|
1987
|
-
|
|
2114
|
+
// tasks 判定(转轨例外):scale=small 按 brainstorm 末步约定不生成 tasks.md
|
|
2115
|
+
//(「proposal/requirements/tasks 对 quick 无用」)——转轨变更无 tasks.md = 无待办
|
|
2116
|
+
// 放行;文件存在(propose 骨架等)仍按全勾判定,未勾任务行照拦。
|
|
2117
|
+
const tasksComplete = quickHandoff && !existsSync(join(specBase, 'changes', changeName, 'tasks.md'))
|
|
2118
|
+
? true
|
|
2119
|
+
: isChangeTasksComplete(specBase, changeName)
|
|
2120
|
+
if (!tasksComplete) {
|
|
1988
2121
|
skipped.push({ name: changeName, reason: 'tasks.md 未全勾选或不存在' })
|
|
1989
2122
|
continue
|
|
1990
2123
|
}
|
|
@@ -2733,3 +2866,108 @@ async function runArchiveCeremonyDualRunExit({ cwd, specBase, changeName, platfo
|
|
|
2733
2866
|
}
|
|
2734
2867
|
}
|
|
2735
2868
|
|
|
2869
|
+
|
|
2870
|
+
// ── quick 资产尾(2026-09-20-quick-asset-tail,D-001/D-002/D-004)─────────────────
|
|
2871
|
+
|
|
2872
|
+
/**
|
|
2873
|
+
* lite 归档(D-004):薄通道(brainstorm→linked quick)变更的轻量收口——所有权 assert →
|
|
2874
|
+
* 命名 → rename → unregisterChange。不复用 archiveChangeDirectory 的 9 处重门(未 apply
|
|
2875
|
+
* 交付面等 worktree 导向,linked quick 变更无 worktree 语义会被误拦);重件
|
|
2876
|
+
* (module-impact/ROADMAP/delta)明确豁免。自愈:源目录缺失但已在 archive/ 时补
|
|
2877
|
+
* unregister 不留永久 skipped(与重路径 586-604 同款语义)。
|
|
2878
|
+
* @returns {Promise<{archivedTo: string}|{skipped: string}>}
|
|
2879
|
+
*/
|
|
2880
|
+
export async function liteArchiveChange({ pm, cwd, specBase, changeName, platformOpts = {} }) {
|
|
2881
|
+
const changesDir = join(specBase, 'changes')
|
|
2882
|
+
const archiveDir = join(changesDir, 'archive')
|
|
2883
|
+
const srcDir = join(changesDir, changeName)
|
|
2884
|
+
// 所有权(同重路径语义:他人活跃拒;self/stale/forced 放行并接管)
|
|
2885
|
+
const { session } = resolveSessionIdentity({ flagSession: platformOpts.sessionFlag || null, cwd })
|
|
2886
|
+
const check = pm.assertChangeOwnership(cwd, changeName, { selfSession: session, nowMs: Date.now() })
|
|
2887
|
+
if (!check.allowed) return { skipped: `所有权拒(owner=${check.owner},活跃窗内)` }
|
|
2888
|
+
if (check.action !== 'self') pm.setChangeOwner(cwd, changeName, session)
|
|
2889
|
+
// 自愈:目录已被手动/前次部分流程移到 archive/ → 只补 unregister(不留永久 desync)
|
|
2890
|
+
if (!existsSync(srcDir)) {
|
|
2891
|
+
// lite 变更目录常无 plan.md(findAlreadyArchivedDir 精确命中的硬条件)——本场景放宽:
|
|
2892
|
+
// archive/ 下有以变更名为末段的目录且含 requirements.md 或 decisions.md 即认自愈
|
|
2893
|
+
const already = (() => {
|
|
2894
|
+
try {
|
|
2895
|
+
const entries = readdirSync(archiveDir, { withFileTypes: true }).filter(e => e.isDirectory()).map(e => e.name)
|
|
2896
|
+
const hit = entries.find(e => (e === changeName || e.endsWith('-' + changeName)) &&
|
|
2897
|
+
(existsSync(join(archiveDir, e, 'requirements.md')) || existsSync(join(archiveDir, e, 'decisions.md'))))
|
|
2898
|
+
return hit ? join(archiveDir, hit) : null
|
|
2899
|
+
} catch { return null }
|
|
2900
|
+
})()
|
|
2901
|
+
if (already) {
|
|
2902
|
+
pm.unregisterChange(cwd, changeName, { archiveStepNames: typeof pm.archiveStepNamesForArchive === 'function' ? pm.archiveStepNamesForArchive() : null })
|
|
2903
|
+
console.log(`📦 lite 自愈归档:${changeName} 已在 archive/(${basename(already)}),补注销 DB 行`)
|
|
2904
|
+
return { archivedTo: already }
|
|
2905
|
+
}
|
|
2906
|
+
return { skipped: `源目录不存在且 archive/ 无此变更(requirements/decisions 均缺的空关联)` }
|
|
2907
|
+
}
|
|
2908
|
+
const destName = archiveDestDirName(new Date().toISOString().slice(0, 10), changeName)
|
|
2909
|
+
const destDir = join(archiveDir, destName)
|
|
2910
|
+
mkdirSync(archiveDir, { recursive: true })
|
|
2911
|
+
renameSync(srcDir, destDir) // 先 rename 后 unregister:失败时 change 留 active 可重试(D-004 顺序保证)
|
|
2912
|
+
pm.unregisterChange(cwd, changeName, { archiveStepNames: typeof pm.archiveStepNamesForArchive === 'function' ? pm.archiveStepNamesForArchive() : null })
|
|
2913
|
+
console.log(`📦 lite 归档:${changeName} → archive/${destName}/(薄通道轻收口:蒸馏已先行,module-impact/ROADMAP 豁免)`)
|
|
2914
|
+
return { archivedTo: destDir }
|
|
2915
|
+
}
|
|
2916
|
+
|
|
2917
|
+
/**
|
|
2918
|
+
* 薄通道蒸馏尾(D-001/D-002):quick --done 门禁过后对 linked 真变更(非 quick-<hex>)跑
|
|
2919
|
+
* decision-distill + fr-index(均幂等)→ lite 归档。挂点在质量闸后(D-002:防未实现设计进
|
|
2920
|
+
* 索引——quick 中途废弃永不 --done 永不蒸馏);两文件均缺失零打扰跳过;fail-open 全链
|
|
2921
|
+
* (任何异常 warn 留痕不拦 quick 完成)。
|
|
2922
|
+
* @returns {Promise<{distilled: boolean, frCount: number, decisionCount: number, archived: boolean, warnings: string[]}>}
|
|
2923
|
+
*/
|
|
2924
|
+
export async function distillLinkedChangeAssets({ pm, cwd, specBase, changeName, linkedChanges = [], platformOpts = {} }) {
|
|
2925
|
+
const out = { distilled: false, frCount: 0, decisionCount: 0, archived: false, warnings: [] }
|
|
2926
|
+
const realChanges = (linkedChanges || []).filter((c) => c && !QUICK_SID_RE.test(c))
|
|
2927
|
+
if (realChanges.length === 0) return out // 纯 quick:无关联真变更,零打扰
|
|
2928
|
+
try {
|
|
2929
|
+
const knowledgeRoot = join(specBase, 'knowledge')
|
|
2930
|
+
let anyDistilled = false
|
|
2931
|
+
for (const linked of realChanges) {
|
|
2932
|
+
const changeDir = join(specBase, 'changes', linked)
|
|
2933
|
+
if (!existsSync(changeDir)) { out.warnings.push(`linked 变更目录缺失:${linked}`); continue }
|
|
2934
|
+
const hasReqs = existsSync(join(changeDir, 'requirements.md'))
|
|
2935
|
+
const hasDecisions = existsSync(join(changeDir, 'decisions.md'))
|
|
2936
|
+
if (!hasReqs && !hasDecisions) continue // quick/scale:small 无索引义务(fr-index 既有语义对齐)
|
|
2937
|
+
// 决策蒸馏(decisions.md → knowledge/decisions/,幂等;无文件内部自跳过)
|
|
2938
|
+
if (hasDecisions) {
|
|
2939
|
+
try {
|
|
2940
|
+
const { distillIntoKnowledge } = await import('../decision-distill.js')
|
|
2941
|
+
const r = distillIntoKnowledge(changeDir, knowledgeRoot, '')
|
|
2942
|
+
const writtenCount = r && Array.isArray(r.written) ? r.written.length : 0
|
|
2943
|
+
if (writtenCount > 0) { out.decisionCount += writtenCount; anyDistilled = true }
|
|
2944
|
+
} catch (e) { out.warnings.push(`decision-distill ${linked} 异常跳过:${e && e.message ? e.message : e}`) }
|
|
2945
|
+
}
|
|
2946
|
+
// FR 索引(requirements.md → knowledge/fr/,幂等:同变更名 no-op)
|
|
2947
|
+
if (hasReqs) {
|
|
2948
|
+
try {
|
|
2949
|
+
const { indexRequirements } = await import('../fr-index.js')
|
|
2950
|
+
const r = indexRequirements({ changeDir, knowledgeRoot, headHash: '' })
|
|
2951
|
+
if (r && Array.isArray(r.written) && r.written.length > 0) { out.frCount += r.written.length; anyDistilled = true }
|
|
2952
|
+
} catch (e) { out.warnings.push(`fr-index ${linked} 异常跳过:${e && e.message ? e.message : e}`) }
|
|
2953
|
+
}
|
|
2954
|
+
}
|
|
2955
|
+
out.distilled = anyDistilled
|
|
2956
|
+
// lite 归档(蒸馏后;仅四件套容器变更——requirements.md 在场=brainstorm 承接的薄通道形态;
|
|
2957
|
+
// 纯信号自动关联的 proposal-only 变更不归档〔quick-single-change-auto-link §3 契约钉死〕)
|
|
2958
|
+
for (const linked of realChanges) {
|
|
2959
|
+
if (!existsSync(join(specBase, 'changes', linked, 'requirements.md'))) continue
|
|
2960
|
+
try {
|
|
2961
|
+
const r = await liteArchiveChange({ pm, cwd, specBase, changeName: linked, platformOpts })
|
|
2962
|
+
if (r.archivedTo) out.archived = true
|
|
2963
|
+
else if (r.skipped) out.warnings.push(`lite 归档 ${linked}:${r.skipped}`)
|
|
2964
|
+
} catch (e) { out.warnings.push(`lite 归档 ${linked} 异常跳过:${e && e.message ? e.message : e}`) }
|
|
2965
|
+
}
|
|
2966
|
+
if (anyDistilled || out.archived) {
|
|
2967
|
+
console.log(`📚 quick 资产尾:${out.frCount} 条 FR 入索引、${out.decisionCount} 条决策入 knowledge${out.archived ? '、linked 变更 lite 归档' : ''}(门禁后自动,agent 零新增命令)`)
|
|
2968
|
+
}
|
|
2969
|
+
} catch (e) {
|
|
2970
|
+
out.warnings.push(`蒸馏尾异常(fail-open 不拦 quick 完成):${e && e.message ? e.message : e}`)
|
|
2971
|
+
}
|
|
2972
|
+
return out
|
|
2973
|
+
}
|
package/src/run/complete.js
CHANGED
|
@@ -120,7 +120,7 @@ export function synthesizeStepOutput({ stageName, stepName, cwd }) {
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
export async function completeStep(pm, progress, stageName, cwd, outputText, inputText = null, options = {}) {
|
|
123
|
-
const { printNext = true, confirm = false, changeName, platformOpts = {}, nonInteractive = false, isForceBaseline = false, isAllowNew = false, isAllowDelete = false, isNoDocs = false, isSkipApply = false, sessionFlag = null, quickFiles = [] } = options
|
|
123
|
+
const { printNext = true, confirm = false, changeName, platformOpts = {}, nonInteractive = false, isForceBaseline = false, isAllowNew = false, isAllowDelete = false, isNoDocs = false, isSkipApply = false, sessionFlag = null, quickFiles = [], linkedChanges = [], linkedChangesAuto = [] } = options
|
|
124
124
|
// specRoot(平台) > specDriftAnchor(worktree 漂移锚定主仓) > cwd/.sillyspec(本地)——
|
|
125
125
|
// 与 prompt.js resolvePromptSpecBase 同序:--done 收尾的 user-inputs.md/超长 artifact 落盘
|
|
126
126
|
// 必须与 prompt 渲染同一根,否则写进 worktree 副本目录、随 cleanup 整目录删除。
|
|
@@ -619,8 +619,11 @@ export async function completeStep(pm, progress, stageName, cwd, outputText, inp
|
|
|
619
619
|
}
|
|
620
620
|
// quick 收尾(W6 Step6b 抽至 complete-handlers.js handleQuickStageCompletion);
|
|
621
621
|
// isNoDocs(task-02)--no-docs 豁免随链透传进 auditQuickCompletion options;
|
|
622
|
-
// sessionFlag(task-03)--session 随链透传进 quick
|
|
623
|
-
|
|
622
|
+
// sessionFlag(task-03)--session 随链透传进 quick 轻量归档链所有权校验;
|
|
623
|
+
// linkedChanges/linkedChangesAuto(R4-S-Q 缺陷 B):--done 时显式 --linked-changes 随链透传——
|
|
624
|
+
// 完成侧 guard 从落盘文件读,不接 opts 会静默丢弃 --done 时声明的关联(蒸馏尾/quicklog
|
|
625
|
+
// 关联变更行/closeQuickLinkedChanges 三处消费同断)
|
|
626
|
+
await handleQuickStageCompletion({ stageName, steps, currentIdx, cwd, progress, changeName, specBase, outputText, confirm, isForceBaseline, isAllowNew, isAllowDelete, isNoDocs, sessionFlag, platformOpts, pm, quickFiles, linkedChanges, linkedChangesAuto })
|
|
624
627
|
|
|
625
628
|
// ── reopen --done 回填(坑 brainstorm-reopen-step-state-desync)──
|
|
626
629
|
// nextPendingIdx === -1 且无 waiting,说明要进阶段完成分支。此时若存在 stale 步骤
|
package/src/run/gate-snapshot.js
CHANGED
|
@@ -389,15 +389,21 @@ export function createGateSnapshot({ cwd, files, sourceRoot = null, skipImportSm
|
|
|
389
389
|
// 两份都变于祖先且互不相等(真双写分叉)→ 显式警告 + 取 cwd(主仓直写=本会话最新编辑位约定)。
|
|
390
390
|
// 行尾归一(坑 autocrlf-byte-misjudge):本机 autocrlf=true 时快照 checkout 是 CRLF、
|
|
391
391
|
// 工作区文件常是 LF——字节直比会误判「两边都改」。比较一律 CRLF→LF 归一后做。
|
|
392
|
+
// 对称 trim(坑 gate-snapshot-ancestor-trim,2026-09-20 taskcard-yaml-hardgate verify 实证):
|
|
393
|
+
// git() 助手对全部输出 .trim()(rev-parse 场景需要),git show 的祖先内容因此被剥掉尾换行——
|
|
394
|
+
// 与磁盘读取(仅 CRLF 归一)不对称,已存在文件恒 anc!==cwd 误判「双写分叉」取主仓旧版,
|
|
395
|
+
// worktree 交付的修改进不了 verify 门快照→门假红(新文件因主仓不存在幸免)。比较三侧
|
|
396
|
+
// 统一 normalizeEol+trim 同口径——首尾空白差异不参与分叉判定(语义修改不会只差首尾空白)。
|
|
392
397
|
const normalizeEol = (buf) => String(buf).replace(/\r\n/g, '\n')
|
|
398
|
+
const cmpText = (t) => normalizeEol(t).trim()
|
|
393
399
|
const headFileContent = (rel) => {
|
|
394
400
|
if (mergeBase) {
|
|
395
401
|
try {
|
|
396
402
|
const anc = git(cwd, ['show', `${mergeBase}:${rel}`])
|
|
397
|
-
if (typeof anc === 'string') return
|
|
403
|
+
if (typeof anc === 'string') return cmpText(anc)
|
|
398
404
|
} catch { /* 祖先无此文件(新增文件)→ 落到快照 HEAD 口径 */ }
|
|
399
405
|
}
|
|
400
|
-
try { return
|
|
406
|
+
try { return cmpText(readFileSync(join(snapshotRoot, rel))) } catch { return null }
|
|
401
407
|
}
|
|
402
408
|
for (const f of files) {
|
|
403
409
|
const isSillyspecRuntime = typeof f === 'string' && (f.startsWith('.sillyspec/.runtime/') || f.startsWith('.sillyspec/quicklog/') || f === '.sillyspec/local.yaml' || f === '.sillyspec/.sillyspec-platform.json')
|
|
@@ -407,8 +413,8 @@ export function createGateSnapshot({ cwd, files, sourceRoot = null, skipImportSm
|
|
|
407
413
|
const wtPath = join(sourceRoot, f), cwdPath = join(cwd, f)
|
|
408
414
|
if (existsSync(wtPath) && existsSync(cwdPath)) {
|
|
409
415
|
let wtText = null, cwdText = null
|
|
410
|
-
try { wtText =
|
|
411
|
-
try { cwdText =
|
|
416
|
+
try { wtText = cmpText(readFileSync(wtPath)) } catch { /* 读失败不介入 */ }
|
|
417
|
+
try { cwdText = cmpText(readFileSync(cwdPath)) } catch { /* 读失败不介入 */ }
|
|
412
418
|
if (wtText !== null && cwdText !== null && wtText !== cwdText) {
|
|
413
419
|
const base = headFileContent(f)
|
|
414
420
|
const wtDiff = base === null || wtText !== base
|