sillyspec 3.23.1 → 3.23.3

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.1",
3
+ "version": "3.23.3",
4
4
  "description": "SillySpec CLI — 流程状态机,让 AI 严格按步骤来",
5
5
  "icon": "logo.jpg",
6
6
  "homepage": "https://sillyspec.ppdmq.top/",
package/src/run.js CHANGED
@@ -315,7 +315,7 @@ function parseModuleMapSimple(content) {
315
315
  /**
316
316
  * quick 完成审计:对比 baseline 与实际变更
317
317
  */
318
- async function auditQuickCompletion(cwd, guard, options = {}) {
318
+ export async function auditQuickCompletion(cwd, guard, options = {}) {
319
319
  const { baselineFiles, allowedFiles = [], allowNew = false, forceBaseline = false } = guard
320
320
  const { isConfirm } = options
321
321
  const result = { status: 'safe', reasons: [], changedFiles: [], newFiles: [], deletedFiles: [], baselineHit: [] }
@@ -326,6 +326,10 @@ async function auditQuickCompletion(cwd, guard, options = {}) {
326
326
  const currentEntries = gitStatus.trim().split('\n').filter(Boolean)
327
327
 
328
328
  const normalizeGitPath = (p) => p.replace(/\\/g, '/')
329
+ // step1 启动时记录的全量脏文件 = 预存改动(非本次 quick 产生)。审计必须排除它们,
330
+ // 否则脏工作区下预存文件持续留在 git status → 命中 baselineFiles → 误判「覆盖 baseline」
331
+ // → 永远 blocked(--force-baseline 也救不回来,因为 status 判定看 baselineHit 数组)。
332
+ const baselineFilesSet = new Set((baselineFiles || []).map(f => normalizeGitPath(f)))
329
333
  const isQuickMetadata = (p) => {
330
334
  const file = normalizeGitPath(p)
331
335
  return file.startsWith('.sillyspec/quicklog/')
@@ -355,6 +359,9 @@ async function auditQuickCompletion(cwd, guard, options = {}) {
355
359
  const file = normalizeGitPath(entry.slice(3).trim())
356
360
  if (!file || file.startsWith('??. ')) continue
357
361
 
362
+ // 预存脏文件:step1 baseline 已记录,非本次 quick 产生,跳过审计
363
+ if (baselineFilesSet.has(file)) continue
364
+
358
365
  result.changedFiles.push(file)
359
366
  if (status === 'D' || status === ' D') result.deletedFiles.push(file)
360
367
  if (status === '??') result.newFiles.push(file)
@@ -404,10 +411,11 @@ async function auditQuickCompletion(cwd, guard, options = {}) {
404
411
  }
405
412
  }
406
413
 
407
- // 判定结果
408
- if (result.baselineHit.length > 0 || result.deletedFiles.length > 0 || result.reasons.some(r => r.startsWith('危险') || r.startsWith('删除'))) {
414
+ // 判定结果(force-baseline 降级 baselineHit → 非 blocked;allow-new 降级新增文件 → 非 warning。
415
+ // reasons 文案本就受这两个 flag 控制,但原判定直接看数组长度,致 flag status 失效。)
416
+ if ((!forceBaseline && result.baselineHit.length > 0) || result.deletedFiles.length > 0 || result.reasons.some(r => r.startsWith('危险') || r.startsWith('删除'))) {
409
417
  result.status = 'blocked'
410
- } else if (result.newFiles.length > 0 || (allowedFiles.length > 0 && result.reasons.some(r => r.startsWith('超出')))) {
418
+ } else if ((!allowNew && result.newFiles.length > 0) || (allowedFiles.length > 0 && result.reasons.some(r => r.startsWith('超出')))) {
411
419
  result.status = 'warning'
412
420
  }
413
421
 
@@ -80,6 +80,10 @@ function parseAllowedPaths(content) {
80
80
  * @returns {boolean}
81
81
  */
82
82
  function hasAcceptanceCriteria(content) {
83
+ // 新模板把 goal/implementation/acceptance/verify/constraints 放在 frontmatter
84
+ // (acceptance: 作为 fm 字段),老格式或人工写的卡片可能用 body 章节。
85
+ // 两种都认,避免「模板格式」与「postcheck 判定」不一致导致卡片过不了校验。
86
+ if (/^acceptance:/m.test(content)) return true
83
87
  return /##\s*验收标准/.test(content) || /##\s*Acceptance/.test(content)
84
88
  }
85
89
 
@@ -89,6 +93,8 @@ function hasAcceptanceCriteria(content) {
89
93
  * @returns {boolean}
90
94
  */
91
95
  function hasTddOrVerify(content) {
96
+ // 同 hasAcceptanceCriteria:新模板用 frontmatter 的 verify: 字段,老格式用 body 章节。
97
+ if (/^verify:/m.test(content)) return true
92
98
  return /##\s*TDD/.test(content) || /##\s*验证/.test(content) || /##\s*Verify/.test(content)
93
99
  }
94
100
 
@@ -230,13 +236,13 @@ export function validateBlueprintConsistency(changeDir) {
230
236
  taskInfo.set(taskId, { dependsOn, allowedPaths, hasAcceptance, hasTdd, file })
231
237
 
232
238
  if (allowedPaths.length === 0) {
233
- errors.push(`${taskId} (${file}): 缺少 allowed_paths`)
239
+ errors.push(`${taskId} (${file}): frontmatter 缺少 allowed_paths(需非空数组,列出本 task 真实改动的源文件;回归类 task 无源码改动时填被验证的关键入口文件)`)
234
240
  }
235
241
  if (!hasAcceptance) {
236
- errors.push(`${taskId} (${file}): 缺少「验收标准」章节`)
242
+ errors.push(`${taskId} (${file}): 缺少验收标准——frontmatter 需有 acceptance: 列表字段,或 body 需有「## 验收标准」/「## Acceptance」章节`)
237
243
  }
238
244
  if (!hasTdd) {
239
- warnings.push(`${taskId} (${file}): 缺少 TDD/验证步骤章节`)
245
+ warnings.push(`${taskId} (${file}): 缺少验证步骤——frontmatter 需有 verify: 字段,或 body 需有「## TDD」/「## 验证」/「## Verify」章节`)
240
246
  }
241
247
 
242
248
  for (const p of allowedPaths) {
@@ -447,6 +447,10 @@ TaskCard 格式规则(必须严格遵守):
447
447
  - 填写后 plan-postcheck 会做硬对账:consumer 的每个 expects_from[provider].needs 字段必须在对应 provider 的 provides.fields 里,否则 plan 阶段阻断(不进入 execute)
448
448
  - 不要把内部实现字段塞进 provides;只暴露给其他 task 的对外契约形状
449
449
  - 如果存在 decisions.md,无法覆盖的 D-xxx@vN 在 constraints 中标注
450
+ - **保存前格式自检**(plan-postcheck 会硬校验,不通过到 Step 4 会批量报错,先在这里逐条自查):
451
+ - frontmatter 字段齐全:id、title、title_zh、author、created_at、priority、depends_on、blocks、allowed_paths、goal、implementation、acceptance、verify、constraints
452
+ - allowed_paths 非空(至少一个真实源文件路径;回归类 task 无源码改动时填被验证的关键入口文件)
453
+ - acceptance 与 verify 都在 frontmatter 内(漏写会被 plan-postcheck 报「缺少验收标准」/「缺少验证步骤」)
450
454
  - 写完后用 Write tool 保存到文件
451
455
  \`\`\``
452
456
  }).join('\n\n')
@@ -478,8 +482,7 @@ ${subagentPrompts}
478
482
 
479
483
  ## 验收(生成后自查,不另开步骤)
480
484
  - 每个 task-N.md 文件存在且非空
481
- - frontmatter 包含:id、title、author、created_at、priority、depends_on、blocks、allowed_paths
482
- - body 包含:goal、implementation、acceptance、verify、constraints
485
+ - frontmatter 包含:id、title、author、created_at、priority、depends_on、blocks、allowed_paths、goal、implementation、acceptance、verify、constraints
483
486
  - 每个 task 总长度 20~40 行
484
487
  - **一致性自查**:
485
488
  - allowed_paths 有无冲突
package/src/worktree.js CHANGED
@@ -1078,8 +1078,12 @@ export class WorktreeManager {
1078
1078
  if (!status) {
1079
1079
  return gitQuiet(worktreePath, 'rev-parse HEAD');
1080
1080
  }
1081
+ // --no-verify:baseline 是锚点不是交付物,只是把主仓库 dirty 文件快照到 worktree
1082
+ // 分支上以便区分「前置 baseline」与「子代理新增改动」。它不该触发项目 pre-commit
1083
+ // hook(如 ruff format),否则主仓库 dirty 文件中任一不达标的会被 hook reformat
1084
+ // 致 commit 失败 → worktree 创建失败 → execute 无法启动。
1081
1085
  execSync(
1082
- `git commit -m "sillyspec: baseline checkpoint for ${changeName}"`,
1086
+ `git commit --no-verify -m "sillyspec: baseline checkpoint for ${changeName}"`,
1083
1087
  { cwd: worktreePath, encoding: 'utf8', stdio: ['pipe','pipe','pipe'], env }
1084
1088
  );
1085
1089
  const hash = git(worktreePath, 'rev-parse HEAD');