sillyspec 3.23.0 → 3.23.1
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/index.js +6 -2
- package/src/run.js +2 -2
- package/src/stages/execute.js +2 -2
- package/src/task-review.js +6 -3
- package/src/worktree-apply.js +54 -2
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -632,12 +632,13 @@ SillySpec worktree — git worktree 隔离管理
|
|
|
632
632
|
}
|
|
633
633
|
case 'apply': {
|
|
634
634
|
if (!wtName) {
|
|
635
|
-
console.error('❌ 用法: sillyspec worktree apply <change-name> [--check-only]');
|
|
635
|
+
console.error('❌ 用法: sillyspec worktree apply <change-name> [--check-only] [--merge]');
|
|
636
636
|
process.exit(1);
|
|
637
637
|
}
|
|
638
638
|
const checkOnly = args.includes('--check-only');
|
|
639
|
+
const merge = args.includes('--merge');
|
|
639
640
|
const { applyWorktree } = await import('./worktree-apply.js');
|
|
640
|
-
const result = applyWorktree(wtName, { cwd: dir, checkOnly });
|
|
641
|
+
const result = applyWorktree(wtName, { cwd: dir, checkOnly, merge });
|
|
641
642
|
|
|
642
643
|
if (result.errors.length > 0) {
|
|
643
644
|
console.error(`❌ 校验失败:`);
|
|
@@ -657,6 +658,8 @@ SillySpec worktree — git worktree 隔离管理
|
|
|
657
658
|
for (const f of result.changedFiles) {
|
|
658
659
|
console.log(` ${f}`);
|
|
659
660
|
}
|
|
661
|
+
} else if (result.merged) {
|
|
662
|
+
console.log(`✅ 已通过 git merge 应用变更(baseline 漂移降级,引入合并提交)${result.mergeSummary ? ':' + result.mergeSummary : ''}`);
|
|
660
663
|
} else {
|
|
661
664
|
console.log(`✅ 已应用 ${result.changedFiles.length} 个文件变更`);
|
|
662
665
|
}
|
|
@@ -710,6 +713,7 @@ SillySpec worktree — git worktree 隔离管理
|
|
|
710
713
|
console.log('Action: blocked');
|
|
711
714
|
console.log(' → 检查变更: sillyspec worktree diff ' + wtName);
|
|
712
715
|
console.log(' → 丢弃变更: sillyspec worktree cleanup ' + wtName);
|
|
716
|
+
console.log(' → baseline 漂移降级: sillyspec worktree apply ' + wtName + ' --merge');
|
|
713
717
|
}
|
|
714
718
|
break;
|
|
715
719
|
}
|
package/src/run.js
CHANGED
|
@@ -3321,7 +3321,7 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
|
|
|
3321
3321
|
} catch {}
|
|
3322
3322
|
|
|
3323
3323
|
const reviewResult = validateTaskReviews({ planContent, runtimeRoot, executeRunId, changeDir: planFile, gitDir: reviewGitDir })
|
|
3324
|
-
printReviewResult(reviewResult)
|
|
3324
|
+
printReviewResult(reviewResult, { runtimeRoot, executeRunId })
|
|
3325
3325
|
|
|
3326
3326
|
if (!reviewResult.ok) {
|
|
3327
3327
|
// Task review 校验失败,阻断 execute 完成
|
|
@@ -3329,7 +3329,7 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
|
|
|
3329
3329
|
const uncheckedTasks = reviewResult.errors.filter(e => e.includes('缺少 review.json'))
|
|
3330
3330
|
if (uncheckedTasks.length > 0) {
|
|
3331
3331
|
console.error('\n⚠️ 部分任务已在 plan.md 中勾选,但 review.json 不存在。')
|
|
3332
|
-
console.error(
|
|
3332
|
+
console.error(` 请取消勾选这些任务的 checkbox,或补充对应的 review.json(execute run ID: ${executeRunId})。`)
|
|
3333
3333
|
}
|
|
3334
3334
|
rollbackStageCompletion(stageData, steps, currentIdx)
|
|
3335
3335
|
progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
|
package/src/stages/execute.js
CHANGED
|
@@ -620,7 +620,7 @@ ${taskList}
|
|
|
620
620
|
|
|
621
621
|
**review.json 路径:**
|
|
622
622
|
|
|
623
|
-
task-XX
|
|
623
|
+
task-XX 对应:{SPEC_ROOT}/.runtime/execute-runs/{EXECUTE_RUN_ID}/tasks/task-XX/review.json
|
|
624
624
|
|
|
625
625
|
本 execute run 的固定 ID 是:{EXECUTE_RUN_ID}
|
|
626
626
|
**所有 task 的 review.json 必须使用这个 ID,不要自行创建新目录。**
|
|
@@ -641,7 +641,7 @@ task-XX 对应:.sillyspec/.runtime/execute-runs/{EXECUTE_RUN_ID}/tasks/task-XX
|
|
|
641
641
|
### 完成后
|
|
642
642
|
1. 为每个后端 router task,扫描变更文件提取 API 端点 artifact:
|
|
643
643
|
- 在变更文件中搜索所有 router 注册路径(@router.get/post/put/delete)
|
|
644
|
-
- 将端点清单写入
|
|
644
|
+
- 将端点清单写入 {SPEC_ROOT}/.runtime/contract-artifacts/<task-name>/endpoints.json
|
|
645
645
|
- 格式: { "task": "task-XX", "type": "backend_endpoints", "endpoints": [{ "method": "GET", "path": "/api/ppm/xxx" }] }
|
|
646
646
|
2. 运行 sillyspec run execute --done --input "用户原始反馈" --output "Wave ${waveIndex} 结果摘要"`
|
|
647
647
|
}
|
package/src/task-review.js
CHANGED
|
@@ -179,7 +179,7 @@ export function validateTaskReviews(opts) {
|
|
|
179
179
|
if (isTaskLowRisk(changeDir, taskId)) {
|
|
180
180
|
warnings.push(`${taskId}: 缺少 review.json — task 声明 low_risk: true,已豁免评审(type-only/机械迁移)`)
|
|
181
181
|
} else {
|
|
182
|
-
errors.push(`${taskId}: 缺少 review.json — task
|
|
182
|
+
errors.push(`${taskId}: 缺少 review.json — task 未经过评审(期望路径:${reviewPath},execute run ID: ${executeRunId})`)
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
185
|
continue
|
|
@@ -447,7 +447,7 @@ export function ensureTaskReviewDir(runtimeRoot, executeRunId, taskId) {
|
|
|
447
447
|
* 打印校验结果
|
|
448
448
|
* @param {{ ok: boolean, errors: string[], warnings: string[], requiredEvidence: Array }} result
|
|
449
449
|
*/
|
|
450
|
-
export function printReviewResult(result) {
|
|
450
|
+
export function printReviewResult(result, context = {}) {
|
|
451
451
|
if (result.ok && result.warnings.length === 0) {
|
|
452
452
|
console.log('\n✅ Task Review Gate — 所有任务评审通过')
|
|
453
453
|
return
|
|
@@ -458,7 +458,10 @@ export function printReviewResult(result) {
|
|
|
458
458
|
for (const err of result.errors) {
|
|
459
459
|
console.error(` - ${err}`)
|
|
460
460
|
}
|
|
461
|
-
|
|
461
|
+
const hint = context.runtimeRoot && context.executeRunId
|
|
462
|
+
? `期望路径:${context.runtimeRoot}/execute-runs/${context.executeRunId}/tasks/<task-XX>/review.json`
|
|
463
|
+
: '为缺失/失败的任务补充 review.json'
|
|
464
|
+
console.error(`\n 提示:${hint},然后重新 --done`)
|
|
462
465
|
}
|
|
463
466
|
|
|
464
467
|
if (result.warnings.length > 0) {
|
package/src/worktree-apply.js
CHANGED
|
@@ -73,7 +73,7 @@ function getFileBlobHash(cwd, treeish, filePath) {
|
|
|
73
73
|
* errors: string[]
|
|
74
74
|
* }}
|
|
75
75
|
*/
|
|
76
|
-
export function applyWorktree(changeName, { cwd, checkOnly = false } = {}) {
|
|
76
|
+
export function applyWorktree(changeName, { cwd, checkOnly = false, merge = false } = {}) {
|
|
77
77
|
const projectRoot = cwd || process.cwd();
|
|
78
78
|
const wm = new WorktreeManager({ cwd: projectRoot });
|
|
79
79
|
const meta = wm.getMeta(changeName);
|
|
@@ -84,6 +84,7 @@ export function applyWorktree(changeName, { cwd, checkOnly = false } = {}) {
|
|
|
84
84
|
hashMismatchFiles: [],
|
|
85
85
|
patchPath: null,
|
|
86
86
|
errors: [],
|
|
87
|
+
merged: false,
|
|
87
88
|
};
|
|
88
89
|
|
|
89
90
|
// --- 1. 校验 worktree 存在 + meta.json 有效 ---
|
|
@@ -172,9 +173,13 @@ export function applyWorktree(changeName, { cwd, checkOnly = false } = {}) {
|
|
|
172
173
|
const raw = `staged:${staged}\nunstaged:${unstaged}\nuntracked:${untracked}`;
|
|
173
174
|
const currentHash = createHash('sha256').update(raw).digest('hex').slice(0, 16);
|
|
174
175
|
if (currentHash !== meta.baselineHash) {
|
|
176
|
+
if (merge && !checkOnly) {
|
|
177
|
+
// baseline 漂移 + --merge 降级(D-001):跳过步骤 5-7(patch 路径),走 git merge
|
|
178
|
+
return applyByMerge(result, changeName, projectRoot, wm);
|
|
179
|
+
}
|
|
175
180
|
result.errors.push(
|
|
176
181
|
`主工作区 baseline 已变化(execute 前后不一致),不能直接 apply task.patch。\n` +
|
|
177
|
-
`建议:重新创建 worktree
|
|
182
|
+
`建议:重新创建 worktree,或用 --merge 降级(git merge sillyspec/${changeName} 替代 patch,会引入合并提交)。\n` +
|
|
178
183
|
`execute 前 baseline: ${meta.baselineHash}\n` +
|
|
179
184
|
`当前 baseline: ${currentHash}`
|
|
180
185
|
);
|
|
@@ -320,6 +325,53 @@ export function applyWorktree(changeName, { cwd, checkOnly = false } = {}) {
|
|
|
320
325
|
return result;
|
|
321
326
|
}
|
|
322
327
|
|
|
328
|
+
/**
|
|
329
|
+
* baseline 漂移时的 merge 降级路径(D-001)。
|
|
330
|
+
*
|
|
331
|
+
* 当主工作区 baseline 在 execute 期间漂移、patch 无法干净应用时,用
|
|
332
|
+
* `git merge sillyspec/<change>` 替代 patch apply(BRANCH_PREFIX='sillyspec/',worktree.js:18)。
|
|
333
|
+
* git merge 比 patch 鲁棒,能处理 baseline 漂移 + 潜在冲突。会引入合并提交
|
|
334
|
+
* (D-002:与 worktree.md:84「patch 而非 merge 保持线性历史」架构决策的张力——
|
|
335
|
+
* 仅作 --merge 显式 opt-in,不改变默认 patch 行为)。
|
|
336
|
+
*
|
|
337
|
+
* merge 冲突时不自动解决:git merge --abort 回滚到合并前状态 + 报冲突文件列表。
|
|
338
|
+
*
|
|
339
|
+
* @param {object} result - applyWorktree 的 result 对象(mutate 后返回)
|
|
340
|
+
* @param {string} changeName
|
|
341
|
+
* @param {string} projectRoot - 主仓库根
|
|
342
|
+
* @param {object} wm - WorktreeManager 实例
|
|
343
|
+
* @returns {object} result(merged=true 表示走了 merge 降级)
|
|
344
|
+
*/
|
|
345
|
+
function applyByMerge(result, changeName, projectRoot, wm) {
|
|
346
|
+
try {
|
|
347
|
+
git(projectRoot, `merge --no-ff sillyspec/${changeName}`);
|
|
348
|
+
result.merged = true;
|
|
349
|
+
result.ok = true;
|
|
350
|
+
try { result.mergeSummary = git(projectRoot, `log --oneline -1`); } catch {}
|
|
351
|
+
// 成功后自动 cleanup(与 patch 路径步骤 8 一致;分支已合并,删除安全)
|
|
352
|
+
try {
|
|
353
|
+
wm.cleanup(changeName);
|
|
354
|
+
} catch (cleanupErr) {
|
|
355
|
+
result.warnings = result.warnings || [];
|
|
356
|
+
result.warnings.push(`cleanup 失败(不影响 merge 结果): ${cleanupErr.message}`);
|
|
357
|
+
}
|
|
358
|
+
} catch (e) {
|
|
359
|
+
// merge 冲突:先取冲突文件列表,再 abort 回滚(避免半成品合并)
|
|
360
|
+
let conflictFiles = [];
|
|
361
|
+
try {
|
|
362
|
+
const cf = gitQuiet(projectRoot, `diff --name-only --diff-filter=U`);
|
|
363
|
+
conflictFiles = cf ? cf.split('\n').filter(Boolean) : [];
|
|
364
|
+
} catch {}
|
|
365
|
+
try { gitQuiet(projectRoot, `merge --abort`); } catch {}
|
|
366
|
+
result.errors.push(
|
|
367
|
+
`git merge sillyspec/${changeName} 冲突,请手动解决。冲突文件:\n` +
|
|
368
|
+
(conflictFiles.length ? ` ${conflictFiles.join('\n ')}\n` : ` (未能获取冲突文件列表)\n`) +
|
|
369
|
+
`已执行 git merge --abort 回滚到合并前状态。`
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
return result;
|
|
373
|
+
}
|
|
374
|
+
|
|
323
375
|
/**
|
|
324
376
|
* 风险审计:评估 worktree 变更是否可以安全自动 apply
|
|
325
377
|
*
|