sillyspec 3.23.3 → 3.23.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sillyspec",
3
- "version": "3.23.3",
3
+ "version": "3.23.5",
4
4
  "description": "SillySpec CLI — 流程状态机,让 AI 严格按步骤来",
5
5
  "icon": "logo.jpg",
6
6
  "homepage": "https://sillyspec.ppdmq.top/",
@@ -70,8 +70,10 @@ export function pathMatches(a, b) {
70
70
  /**
71
71
  * 「文件变更清单」章节标题同义词(与 src/stage-contract.js 的识别集对齐,
72
72
  * 避免两个校验器对「有没有清单」给出矛盾结论)。
73
+ * 容忍可选编号前缀(`## 6. 文件变更清单` / `## 6) 文件变更清单`)——
74
+ * brainstorm Step11 模板鼓励 design 章节带编号,编号前缀不应让 plan-postcheck 解析失败。
73
75
  */
74
- const FILE_LIST_SECTION_RE = /^#{2,3}\s*(文件变更清单|变更文件清单|文件清单|File Changes|Files to Change)/im
76
+ const FILE_LIST_SECTION_RE = /^#{2,3}\s*(?:\d+[.)]\s*)?(文件变更清单|变更文件清单|文件清单|File Changes|Files to Change)/im
75
77
 
76
78
  /**
77
79
  * exclude 子标题词集:「不修改/暂缓/保留」类 —— 其下路径从清单移除,不强制 task 覆盖。
package/src/run.js CHANGED
@@ -454,8 +454,10 @@ export async function auditQuickCompletion(cwd, guard, options = {}) {
454
454
  console.log(` - ${r}`)
455
455
  }
456
456
  }
457
- console.log(`\n 如确认接受这些变更,重新运行:sillyspec run quick --done --confirm --output "..."`)
458
- console.log(` 或使用 --force-baseline 允许覆盖 baseline 文件,--allow-new 允许新增文件`)
457
+ console.log(`\n 如确认接受这些变更,重新运行 --done 时带上对应 flag 即可解锁:`)
458
+ console.log(` sillyspec run quick --done --force-baseline --allow-new --change <id> --output "..."`)
459
+ console.log(` (--force-baseline 覆盖受保护/危险文件如 src/run.js;--allow-new 允许新增文件)`)
460
+ console.log(` 或在首个 sillyspec run quick 启动(step 1)时就声明这些 flag,持久化进 guard。`)
459
461
  }
460
462
  } catch (e) {
461
463
  result.reasons.push(`审计失败: ${e.message}`)
@@ -1645,11 +1647,11 @@ export async function runCommand(args, cwd, specDir = null) {
1645
1647
  // 这里复用同一值写 current-quick-run-id,保证两处一致。
1646
1648
  // 旧 quick-YYYYMMDD-HHMMSS 时间戳已摒弃(D-003@v1:同秒并发撞)。
1647
1649
  if (stageName === 'quick' && !isDone && !isStatus && !isSkip && !isReset && !isReopen) {
1648
- const qStage = progress.stages?.quick
1649
- if (qStage?.steps?.length) {
1650
- qStage.steps = qStage.steps.map(s => ({ ...s, status: 'pending', completedAt: null }))
1651
- qStage.status = 'in-progress'
1652
- }
1650
+ // 不再无条件 reset steps:原逻辑把任何 quick 非 --done 启动都重置为 pending,致 in-progress
1651
+ // quick 中途查 prompt(sillyspec run quick)丢已 done 的 step 进度(报告
1652
+ // quick-state-reset-platform-mode)。现保留进度——completed 重跑由 runStage
1653
+ // currentIdx === -1 自动重置(~1944 行);in-progress 输出当前 step;全新由
1654
+ // ensureStageSteps 初始化;显式从头用 --reset(已由 !isReset 排除出本分支)。
1653
1655
  try {
1654
1656
  const runtimeRoot = platformOpts.runtimeRoot || join(specRoot, '.runtime')
1655
1657
  mkdirSync(runtimeRoot, { recursive: true })
@@ -1692,7 +1694,7 @@ export async function runCommand(args, cwd, specDir = null) {
1692
1694
  // --done
1693
1695
  if (isDone) {
1694
1696
  const doneAnswer = getFlagValue('--answer')
1695
- return await completeStep(pm, progress, stageName, cwd, outputText, inputText, { confirm: isConfirm, changeName: effectiveChange, nonInteractive: isNonInteractive && !isInteractive, platformOpts, confirmMode, doneAnswer })
1697
+ return await completeStep(pm, progress, stageName, cwd, outputText, inputText, { confirm: isConfirm, changeName: effectiveChange, nonInteractive: isNonInteractive && !isInteractive, platformOpts, confirmMode, doneAnswer, isForceBaseline, isAllowNew })
1696
1698
  }
1697
1699
 
1698
1700
  // 默认:输出当前步骤
@@ -1961,10 +1963,13 @@ async function runStage(pm, progress, stageName, cwd, changeName, skipApproval =
1961
1963
  try {
1962
1964
  const { execSync } = await import('child_process')
1963
1965
  const gitStatus = execSync('git status --porcelain', { cwd, encoding: 'utf8', timeout: 10000 })
1966
+ // 记录全部预存脏文件(含 untracked + .sillyspec/ 路径)。quick 会话期间自身写入的元数据
1967
+ // (quicklog/.runtime/modules/_module-map 等)由 auditQuickCompletion 的 isQuickMetadata 精确豁免,
1968
+ // 不需要这里粗放过滤 .sillyspec/——旧过滤致预存 untracked .sillyspec/changes/ 不进 baseline,
1969
+ // 却在 audit 被当「危险(.sillyspec/)+新增」误判永久 blocked(ql-20260713-002-7628 修复)。
1964
1970
  const baselineFiles = gitStatus
1965
1971
  .trim().split('\n').filter(Boolean)
1966
1972
  .map(line => line.slice(3).trim())
1967
- .filter(f => !f.startsWith('.sillyspec/'))
1968
1973
  const allowedFiles = quickOpts?.quickFiles || []
1969
1974
  const allowNew = quickOpts?.isAllowNew || false
1970
1975
  const forceBaseline = quickOpts?.isForceBaseline || false
@@ -2614,7 +2619,7 @@ function rollbackStageCompletion(stageData, steps, currentIdx) {
2614
2619
  }
2615
2620
 
2616
2621
  async function completeStep(pm, progress, stageName, cwd, outputText, inputText = null, options = {}) {
2617
- const { printNext = true, confirm = false, changeName, platformOpts = {}, nonInteractive = false, confirmMode = null } = options
2622
+ const { printNext = true, confirm = false, changeName, platformOpts = {}, nonInteractive = false, confirmMode = null, isForceBaseline = false, isAllowNew = false } = options
2618
2623
  const specBase = platformOpts.specRoot || join(cwd, '.sillyspec')
2619
2624
  const stageData = progress.stages[stageName]
2620
2625
  const scanProfile = stageData?.scanProfile || null
@@ -2990,7 +2995,15 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
2990
2995
  : (existsSync(legacyGuardFile) ? JSON.parse(readFileSync(legacyGuardFile, 'utf8')) : null)
2991
2996
  } catch {}
2992
2997
  if (guard) {
2993
- const review = await auditQuickCompletion(cwd, guard, { isConfirm: confirm })
2998
+ // --done --force-baseline/--allow-new 并入 guard(与 step1 持久化值取或)。
2999
+ // 修复 ql-20260713-002-7628:旧代码解析了这两个 flag 但只传 {isConfirm} 给审计,
3000
+ // 致 --done --force-baseline 静默无效、用户被误导「重跑 --confirm」也无法解锁。
3001
+ const mergedGuard = {
3002
+ ...guard,
3003
+ forceBaseline: guard.forceBaseline || isForceBaseline,
3004
+ allowNew: guard.allowNew || isAllowNew,
3005
+ }
3006
+ const review = await auditQuickCompletion(cwd, mergedGuard, { isConfirm: confirm })
2994
3007
  printQuickAuditReview(review)
2995
3008
  if (review.status === 'blocked') {
2996
3009
  steps[currentIdx].status = 'pending'
@@ -258,8 +258,21 @@ function validateBrainstormOutputs(cwd, changeName, context = {}) {
258
258
  const hasLifecycleKeyword = /\b(session|lease|agent[._-]?run|daemon|lifecycle|state[._-]?transition|claim|heartbeat)\b/i.test(content)
259
259
  if (hasLifecycleKeyword) {
260
260
  // 显式声明本变更不涉及生命周期契约(覆盖字段名/错误码/否定声明场景):
261
- // 历史教训:design 提到 daemon_id 字段名或 daemon_not_owned 错误码就触发,被迫加空表(B3a
262
- const declaresNotApplicable = /(生命周期|lifecycle)[^\n]{0,40}(n\/?a|不涉及|无|none|not[ _-]?applicable)|(不涉及|无需|没有)[^\n]{0,40}(生命周期|lifecycle)/i.test(content)
261
+ // 历史教训:design 提到 daemon_id 字段名或 daemon_not_owned 错误码就触发,被迫加空表(B3a)。
262
+ //
263
+ // 收紧原则(修:正常 design 不应被误判「已豁免」):
264
+ // - 否定词必须是明确多字短语(不涉及/不适用/未涉及/不包含/没有/n\/a/not applicable/none),
265
+ // 杜绝裸单字「无」与裸「na」在 40 字符宽窗口内任意命中——
266
+ // 「lifecycle 状态无变化」「本变更无需 lifecycle 事件」「lifecycle canal 不涉及」等不再误判;
267
+ // - 否定词必须与「生命周期(契约)/lifecycle(contract)」紧邻(仅允许少量空白/分隔符/「任何」),
268
+ // 不再用 40 字符宽松窗口;工具错误信息本身就指引写「不涉及生命周期契约」这个规范短语。
269
+ const declaresNotApplicable =
270
+ // 否定在前:「不涉及生命周期(契约)」「不适用 lifecycle contract」
271
+ /(?:不涉及|不适用|未涉及|不包含|没有(?:任何)?)\s?(?:任何\s?)?(?:生命周期(?:契约)?|lifecycle(?:[ _=-]?contract)?)/i.test(content) ||
272
+ // 主题在前(表格/列表单元「生命周期契约:不涉及 / N/A / 无」——分隔符强制,杜绝宽窗口)
273
+ /(?:生命周期(?:契约)?|lifecycle(?:[ _=-]?contract)?)\s?[::=]\s?(?:不涉及|不适用|未涉及|无|n\/?a\b|not[ _=-]?applicable|none\b)/i.test(content) ||
274
+ // 英文谓语句:「does not involve / not applicable ... lifecycle」
275
+ /(?:does[ _-]?not[ _-]?involve|not[ _-]?applicable)[^\n]{0,15}lifecycle/i.test(content)
263
276
  if (declaresNotApplicable) {
264
277
  warnings.push('design.md 显式声明不涉及生命周期契约 — 已豁免「生命周期契约表」要求')
265
278
  } else {