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 +1 -1
- package/src/run.js +12 -4
- package/src/stages/plan-postcheck.js +9 -3
- package/src/stages/plan.js +5 -2
- package/src/worktree.js +5 -1
package/package.json
CHANGED
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
|
-
|
|
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}):
|
|
245
|
+
warnings.push(`${taskId} (${file}): 缺少验证步骤——frontmatter 需有 verify: 字段,或 body 需有「## TDD」/「## 验证」/「## Verify」章节`)
|
|
240
246
|
}
|
|
241
247
|
|
|
242
248
|
for (const p of allowedPaths) {
|
package/src/stages/plan.js
CHANGED
|
@@ -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');
|