sillyspec 3.15.0 → 3.15.2
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 +16 -1
- package/src/worktree-apply.js +105 -6
- package/src/worktree.js +8 -6
package/package.json
CHANGED
package/src/run.js
CHANGED
|
@@ -10,6 +10,7 @@ import { ProgressManager } from './progress.js'
|
|
|
10
10
|
import { stageRegistry, auxiliaryStages } from './stages/index.js'
|
|
11
11
|
import { buildExecuteSteps } from './stages/execute.js'
|
|
12
12
|
import { buildPlanSteps } from './stages/plan.js'
|
|
13
|
+
import { formatExecuteSummary } from './worktree-apply.js'
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* 同步触发辅助函数:_write 后 best-effort 同步到平台
|
|
@@ -745,7 +746,21 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
|
|
|
745
746
|
console.log(`✅ ${stageName} 阶段已完成(${total}/${total} 步)`)
|
|
746
747
|
|
|
747
748
|
if (stageName === 'execute') {
|
|
748
|
-
|
|
749
|
+
// execute run summary:展示真实可得的结构化信息
|
|
750
|
+
try {
|
|
751
|
+
const lastOutput = steps[steps.length - 1]?.output || ''
|
|
752
|
+
const summary = formatExecuteSummary({
|
|
753
|
+
changeName,
|
|
754
|
+
stepsCompleted: total,
|
|
755
|
+
stepsTotal: total,
|
|
756
|
+
agentSummary: lastOutput,
|
|
757
|
+
cwd,
|
|
758
|
+
})
|
|
759
|
+
console.log(`\n${summary}`)
|
|
760
|
+
} catch (e) {
|
|
761
|
+
// summary 失败不影响主流程
|
|
762
|
+
console.log('\n👉 下一步:sillyspec run verify(验证通过后才能归档)')
|
|
763
|
+
}
|
|
749
764
|
} else if (stageName === 'verify') {
|
|
750
765
|
console.log('\n👉 下一步:sillyspec run archive(验证通过,可以归档了)')
|
|
751
766
|
} else if (stageName === 'archive') {
|
package/src/worktree-apply.js
CHANGED
|
@@ -92,9 +92,17 @@ export function applyWorktree(changeName, { cwd, checkOnly = false } = {}) {
|
|
|
92
92
|
// 同时检测 untracked 新文件(git diff 不包含 untracked)
|
|
93
93
|
let changedFiles;
|
|
94
94
|
try {
|
|
95
|
-
//
|
|
96
|
-
const
|
|
97
|
-
const
|
|
95
|
+
// 用 --name-status 捕获 rename/delete(--name-only 会丢失 rename 源文件)
|
|
96
|
+
const statusRaw = git(worktreePath, `diff --name-status ${diffBase}`);
|
|
97
|
+
const statusFiles = new Set();
|
|
98
|
+
if (statusRaw) {
|
|
99
|
+
for (const line of statusRaw.split('\n').filter(Boolean)) {
|
|
100
|
+
const parts = line.split('\t');
|
|
101
|
+
// R100 old.txt new.txt → 提取两个文件
|
|
102
|
+
if (parts.length >= 2) statusFiles.add(parts[parts.length - 1]);
|
|
103
|
+
if (parts.length >= 3) statusFiles.add(parts[parts.length - 2]);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
98
106
|
|
|
99
107
|
// untracked 新文件(diffBase 中不存在的文件)
|
|
100
108
|
const untrackedRaw = gitQuiet(worktreePath, `ls-files --others --exclude-standard`);
|
|
@@ -102,7 +110,7 @@ export function applyWorktree(changeName, { cwd, checkOnly = false } = {}) {
|
|
|
102
110
|
? untrackedRaw.split('\n').filter(Boolean).filter(f => !f.startsWith('.sillyspec/') && f !== 'meta.json')
|
|
103
111
|
: [];
|
|
104
112
|
|
|
105
|
-
changedFiles = [...new Set([...
|
|
113
|
+
changedFiles = [...new Set([...statusFiles, ...untrackedFiles])];
|
|
106
114
|
} catch (e) {
|
|
107
115
|
result.errors.push(`获取变更文件列表失败: ${e.message}`);
|
|
108
116
|
return result;
|
|
@@ -216,8 +224,11 @@ export function applyWorktree(changeName, { cwd, checkOnly = false } = {}) {
|
|
|
216
224
|
|
|
217
225
|
// 分 tracked 变更和 untracked 新文件生成 patch
|
|
218
226
|
const trackedFiles = patchFiles.filter(f => {
|
|
219
|
-
//
|
|
220
|
-
|
|
227
|
+
// 文件在 diffBase 的 tree 中存在 → tracked(包括 rename 目标可能的情况)
|
|
228
|
+
if (gitQuiet(worktreePath, `cat-file -e ${diffBase}:${f}`) !== null) return true;
|
|
229
|
+
// 文件在工作区 index 中已存在(比如被 git mv 处理过)→ 也视为 tracked
|
|
230
|
+
if (gitQuiet(worktreePath, `ls-files --error-unmatch ${f}`) !== null) return true;
|
|
231
|
+
return false;
|
|
221
232
|
});
|
|
222
233
|
const untrackedPatchFiles = patchFiles.filter(f => !trackedFiles.includes(f));
|
|
223
234
|
|
|
@@ -290,3 +301,91 @@ export function applyWorktree(changeName, { cwd, checkOnly = false } = {}) {
|
|
|
290
301
|
|
|
291
302
|
return result;
|
|
292
303
|
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* 格式化 execute run summary(人类可读)
|
|
307
|
+
*
|
|
308
|
+
* 只展示 CLI 真实掌握的信息,不声称知道 per-task 状态。
|
|
309
|
+
* @param {object} opts
|
|
310
|
+
* @param {string} opts.changeName - 变更名
|
|
311
|
+
* @param {number} opts.stepsCompleted - 已完成步骤数
|
|
312
|
+
* @param {number} opts.stepsTotal - 总步骤数
|
|
313
|
+
* @param {string} opts.agentSummary - Agent 最终输出摘要
|
|
314
|
+
* @param {string} [opts.cwd] - 项目根目录(默认 process.cwd())
|
|
315
|
+
* @returns {string} 格式化的 summary 文本
|
|
316
|
+
*/
|
|
317
|
+
export function formatExecuteSummary({ changeName, stepsCompleted, stepsTotal, agentSummary, cwd }) {
|
|
318
|
+
const wm = new WorktreeManager({ cwd });
|
|
319
|
+
const meta = wm.getMeta(changeName);
|
|
320
|
+
const lines = [];
|
|
321
|
+
|
|
322
|
+
const SEPARATOR = '─'.repeat(32);
|
|
323
|
+
|
|
324
|
+
// --- Header ---
|
|
325
|
+
lines.push(`Execute Summary`);
|
|
326
|
+
lines.push(SEPARATOR);
|
|
327
|
+
|
|
328
|
+
// --- Status ---
|
|
329
|
+
if (!meta) {
|
|
330
|
+
// worktree 不存在(可能已 cleanup 或没有用过 worktree)
|
|
331
|
+
lines.push(`Status: COMPLETED`);
|
|
332
|
+
lines.push(`Steps: ${stepsCompleted} / ${stepsTotal}`);
|
|
333
|
+
lines.push(`Apply: N/A`);
|
|
334
|
+
} else {
|
|
335
|
+
const hasBaseline = meta.baselineCommit != null;
|
|
336
|
+
const wtExists = existsSync(meta.worktreePath);
|
|
337
|
+
|
|
338
|
+
const applyStatus = wtExists ? 'pending' : 'applied';
|
|
339
|
+
const baselineCount = meta.baselineFiles?.length || 0;
|
|
340
|
+
const baselineStatus = hasBaseline
|
|
341
|
+
? `dirty (${baselineCount} baseline file${baselineCount === 1 ? '' : 's'} protected)`
|
|
342
|
+
: 'clean';
|
|
343
|
+
|
|
344
|
+
lines.push(`Status: COMPLETED`);
|
|
345
|
+
lines.push(`Steps: ${stepsCompleted} / ${stepsTotal}`);
|
|
346
|
+
lines.push(`Baseline: ${baselineStatus}`);
|
|
347
|
+
lines.push(`Apply: ${applyStatus}`);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// --- Changed files ---
|
|
351
|
+
// 从主工作区 diff 获取(worktree 已 apply)或从 worktree diff 获取
|
|
352
|
+
if (meta && existsSync(meta.worktreePath)) {
|
|
353
|
+
// worktree 还在,用 baselineCommit 或 baseHash 做 diff
|
|
354
|
+
try {
|
|
355
|
+
const diffBase = meta.baselineCommit || meta.baseHash;
|
|
356
|
+
const { execSync: es } = require('child_process');
|
|
357
|
+
const filesRaw = es(`git -C ${meta.worktreePath} diff --name-only ${diffBase} 2>/dev/null`, { encoding: 'utf8' });
|
|
358
|
+
const files = filesRaw ? filesRaw.trim().split('\n').filter(Boolean) : [];
|
|
359
|
+
if (files.length > 0) {
|
|
360
|
+
lines.push(``);
|
|
361
|
+
const maxShow = 10;
|
|
362
|
+
const showFiles = files.slice(0, maxShow);
|
|
363
|
+
const remain = files.length - maxShow;
|
|
364
|
+
lines.push(`Changed Files (${files.length})`);
|
|
365
|
+
showFiles.forEach(f => lines.push(` ${f}`));
|
|
366
|
+
if (remain > 0) {
|
|
367
|
+
lines.push(` ... ${remain} more`);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
} catch {}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// --- Agent Summary ---
|
|
374
|
+
if (agentSummary) {
|
|
375
|
+
lines.push(``);
|
|
376
|
+
lines.push(`Agent Summary`);
|
|
377
|
+
// 缩进每行,截断过长内容
|
|
378
|
+
const maxLen = 200;
|
|
379
|
+
const summary = agentSummary.length > maxLen
|
|
380
|
+
? agentSummary.slice(0, maxLen) + '...'
|
|
381
|
+
: agentSummary;
|
|
382
|
+
summary.split('\n').forEach(l => lines.push(` ${l}`));
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// --- Next ---
|
|
386
|
+
lines.push(``);
|
|
387
|
+
lines.push(`Next`);
|
|
388
|
+
lines.push(` → sillyspec run verify`);
|
|
389
|
+
|
|
390
|
+
return lines.join('\n');
|
|
391
|
+
}
|
package/src/worktree.js
CHANGED
|
@@ -302,10 +302,11 @@ export class WorktreeManager {
|
|
|
302
302
|
const staged = gitQuiet(mainCwd, 'diff --cached --name-only') || '';
|
|
303
303
|
if (staged) {
|
|
304
304
|
try {
|
|
305
|
-
|
|
306
|
-
|
|
305
|
+
// 用 Buffer 模式读取,避免二进制 patch 被 UTF-8 解码损坏
|
|
306
|
+
const patchBuf = execSync(`git diff --cached --binary`, { cwd: mainCwd, stdio: ['pipe','pipe','pipe'] });
|
|
307
|
+
if (patchBuf && patchBuf.length > 0) {
|
|
307
308
|
const patchFile = join(worktreePath, '.sillyspec-baseline-staged.patch');
|
|
308
|
-
writeFileSync(patchFile,
|
|
309
|
+
writeFileSync(patchFile, patchBuf);
|
|
309
310
|
git(worktreePath, `apply --binary ${patchFile}`);
|
|
310
311
|
rmSync(patchFile, { force: true });
|
|
311
312
|
}
|
|
@@ -319,10 +320,11 @@ export class WorktreeManager {
|
|
|
319
320
|
const unstaged = gitQuiet(mainCwd, 'diff --name-only') || '';
|
|
320
321
|
if (unstaged) {
|
|
321
322
|
try {
|
|
322
|
-
|
|
323
|
-
|
|
323
|
+
// 用 Buffer 模式读取,避免二进制 patch 被 UTF-8 解码损坏
|
|
324
|
+
const patchBuf = execSync(`git diff --binary`, { cwd: mainCwd, stdio: ['pipe','pipe','pipe'] });
|
|
325
|
+
if (patchBuf && patchBuf.length > 0) {
|
|
324
326
|
const patchFile = join(worktreePath, '.sillyspec-baseline-unstaged.patch');
|
|
325
|
-
writeFileSync(patchFile,
|
|
327
|
+
writeFileSync(patchFile, patchBuf);
|
|
326
328
|
git(worktreePath, `apply --binary ${patchFile}`);
|
|
327
329
|
rmSync(patchFile, { force: true });
|
|
328
330
|
}
|