sillyspec 3.26.14 → 3.27.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/.claude/skills/sillyspec-archive/SKILL.md +2 -0
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +1 -1
- package/.claude/skills/sillyspec-commit/SKILL.md +18 -60
- package/.claude/skills/sillyspec-continue/SKILL.md +2 -0
- package/.claude/skills/sillyspec-execute/SKILL.md +10 -4
- package/.claude/skills/sillyspec-explore/SKILL.md +10 -3
- package/.claude/skills/sillyspec-plan/SKILL.md +10 -2
- package/.claude/skills/sillyspec-quick/SKILL.md +2 -2
- package/.claude/skills/sillyspec-resume/SKILL.md +2 -0
- package/.claude/skills/sillyspec-verify/SKILL.md +4 -0
- package/.claude/skills/sillyspec-workspace/SKILL.md +7 -1
- package/README.md +1 -1
- package/package.json +1 -1
- package/packages/dashboard/server/index.js +22 -6
- package/src/change-risk-profile.js +21 -6
- package/src/commit-suggest.js +202 -0
- package/src/config-cat.js +88 -0
- package/src/db.js +14 -3
- package/src/docs-check.js +598 -534
- package/src/doctor-diagnostics.js +102 -1
- package/src/endpoint-extractor.js +21 -2
- package/src/fs-atomic.js +4 -2
- package/src/git-helper.js +73 -5
- package/src/hooks/claude-pre-tool-use.cjs +10 -5
- package/src/hooks/worktree-guard.js +42 -4
- package/src/index.js +557 -12
- package/src/init.js +1 -1
- package/src/local-register.js +98 -0
- package/src/module-impact.js +166 -0
- package/src/module-resolve.js +211 -0
- package/src/modules.js +82 -2
- package/src/progress/change-registry.js +43 -4
- package/src/progress/stage-machine.js +10 -5
- package/src/progress/step-store.js +12 -8
- package/src/progress.js +19 -3
- package/src/quicklog.js +106 -1
- package/src/run/command.js +95 -1
- package/src/run/complete-handlers.js +73 -5
- package/src/run/complete.js +47 -14
- package/src/run/gates.js +144 -17
- package/src/run/multi-repo-context.js +3 -1
- package/src/run/next.js +128 -0
- package/src/run/prompt.js +36 -4
- package/src/run/quick-audit.js +5 -2
- package/src/run/shared.js +48 -20
- package/src/run/stage.js +37 -3
- package/src/scan-postcheck.js +142 -2
- package/src/stage-contract-spec.js +1 -1
- package/src/stage-contract.js +14 -1
- package/src/stage-review.js +15 -6
- package/src/stages/archive.js +4 -2
- package/src/stages/brainstorm.js +1 -0
- package/src/stages/execute.js +275 -44
- package/src/stages/explore.js +11 -2
- package/src/stages/knowledge.js +3 -2
- package/src/stages/quick.js +18 -9
- package/src/stages/scan.js +1 -0
- package/src/stages/verify.js +16 -51
- package/src/sync.js +44 -3
- package/src/task-review.js +273 -11
- package/src/taskcard.js +52 -22
- package/src/verify-postcheck.js +223 -6
- package/src/verify-probes.js +264 -0
- package/src/workspace.js +125 -0
- package/src/worktree-apply.js +43 -11
- package/src/worktree-deps.js +59 -5
- package/src/worktree.js +128 -14
- package/templates/claude-instruction.md +1 -1
- package/templates/prompts/taskcard-rules.md +1 -1
- package/templates/prompts/verify-probes.md +13 -47
package/src/worktree.js
CHANGED
|
@@ -907,11 +907,32 @@ export class WorktreeManager {
|
|
|
907
907
|
|
|
908
908
|
// 4. 删除分支(fail-closed:task review 引用可达性校验。force 也不绕过——force 语义 =
|
|
909
909
|
// 丢弃内容,不含丢弃审计链;确要删用 git branch -D 手动执行)
|
|
910
|
+
// 升级(坑 cleanup-branch-review-anchor-tag,2026-08-21 实证):分支被 review.json 引用
|
|
911
|
+
// 时不再只能手动保留——打 `sillyspec-audit/<branch>` 轻量 tag 锚定分支 tip(ref 前缀独立
|
|
912
|
+
// 于 sillyspec/* 分支族,doctor 孤儿分支扫描不误伤),commit 经 tag 保持可达(gc 安全),
|
|
913
|
+
// 随后正常删分支。tag 创建失败 → 回退保留(宁保留勿丢审计链)。
|
|
910
914
|
try {
|
|
911
915
|
const reviewRefs = this._branchReviewReferences(branch);
|
|
912
916
|
if (reviewRefs.length > 0) {
|
|
913
|
-
|
|
914
|
-
|
|
917
|
+
const tagName = 'sillyspec-audit/' + branch;
|
|
918
|
+
let tagOk = false;
|
|
919
|
+
try {
|
|
920
|
+
if (!gitQuiet(this.cwd, ['rev-parse', '--verify', '--quiet', `refs/tags/${tagName}`])) {
|
|
921
|
+
git(this.cwd, ['tag', tagName, branch]);
|
|
922
|
+
}
|
|
923
|
+
tagOk = true;
|
|
924
|
+
} catch (e) {
|
|
925
|
+
details.push(`audit tag creation failed: ${e.message}`);
|
|
926
|
+
}
|
|
927
|
+
if (tagOk) {
|
|
928
|
+
gitQuiet(this.cwd, ['branch', '-D', branch]);
|
|
929
|
+
details.push(`branch deleted after anchoring tip to tag ${tagName} (audit 链可达)`);
|
|
930
|
+
console.log(`🔗 审计锚定:${branch} 被 ${reviewRefs.length} 个 task review.json 引用其 commit——已打 tag ${tagName} 锚定(commit 保持可达,gc 安全)并删除分支 ref。`);
|
|
931
|
+
console.log(` 确认不再需要该审计链后可删 tag:git tag -d ${tagName}`);
|
|
932
|
+
} else {
|
|
933
|
+
details.push(`branch kept: ${reviewRefs.length} 个 task review.json 引用分支上的 commit(base/head 审计保护)`);
|
|
934
|
+
console.log(`🔗 分支保留:${branch} 上有 ${reviewRefs.length} 个 task review.json 引用其 commit(apply 只复制文件内容,ref 删除后 base/head 悬空无法复核)。确要删除:git branch -D ${branch}`);
|
|
935
|
+
}
|
|
915
936
|
} else {
|
|
916
937
|
gitQuiet(this.cwd, ['branch', '-D', branch]);
|
|
917
938
|
details.push('branch deleted');
|
|
@@ -982,7 +1003,7 @@ export class WorktreeManager {
|
|
|
982
1003
|
* @param {string} wtPath - worktree 根目录(meta.worktreePath)
|
|
983
1004
|
* @returns {{ ok: boolean, msg: string }}
|
|
984
1005
|
*/
|
|
985
|
-
_doctorReprovision(name, wtPath) {
|
|
1006
|
+
_doctorReprovision(name, wtPath, opts = {}) {
|
|
986
1007
|
try {
|
|
987
1008
|
const meta = this.getMeta(name) || {};
|
|
988
1009
|
const isInPlace = meta.mode === 'in-place-fallback';
|
|
@@ -1012,9 +1033,14 @@ export class WorktreeManager {
|
|
|
1012
1033
|
}
|
|
1013
1034
|
}
|
|
1014
1035
|
}
|
|
1036
|
+
// 触发分流(坑 doctor-reprovision-junction-missing,2026-08-22 实证:junction 未建的
|
|
1037
|
+
// missing/failed 场景被 force install 强制重装——install 静默失败面大且慢;lockfile 一致时
|
|
1038
|
+
// 重建 junction 才是对症动作。opts.relinkFirst(missing/failed 触发)→ 非 force 跑:解链后
|
|
1039
|
+
// node_modules 不存在,tryLink 直接重建;lockfile 漂移自动落 install。stale/main-drift 维持
|
|
1040
|
+
// force 重装(依赖确实变了)。
|
|
1015
1041
|
const deps = provisionDeps(wtPath, this.cwd, {
|
|
1016
1042
|
specBase: join(this.cwd, '.sillyspec'),
|
|
1017
|
-
force:
|
|
1043
|
+
force: !opts.relinkFirst,
|
|
1018
1044
|
}) || {};
|
|
1019
1045
|
const metaPath = join(this.getWorktreePath(name), META_FILE);
|
|
1020
1046
|
// 成功重供时清掉旧 depsError(provisionDeps 成功结果不含该键,{...meta,...deps} 会残留旧值)
|
|
@@ -1132,7 +1158,29 @@ export class WorktreeManager {
|
|
|
1132
1158
|
// 放宽原 909 in-place-fallback 守卫:in-place 也跑(main-drift/stale/failed 对它同样有意义,
|
|
1133
1159
|
// 真 in-place 的 wtPath===主仓→wtHash===mainHash→不会误报 main-drift,仅 failed/stale 有意义)。
|
|
1134
1160
|
// fix 时 _doctorReprovision 自行判断是否解链(in-place 不解链,仅 install)。
|
|
1161
|
+
// 归档态前置闸(坑 doctor-reprovision-archived-change,2026-08-21 实证:已归档变更的
|
|
1162
|
+
// worktree 被 doctor 当活跃任务 re-provision——给死目录装依赖)。归档判定(changes/archive/
|
|
1163
|
+
// 实体 或 进度库 status='archived')命中 → 跳过供给,fix 时走 cleanup(其内建
|
|
1164
|
+
// hasUnappliedChanges 护栏仍在,未落仓内容照样拒删)。
|
|
1135
1165
|
if (meta && meta.worktreePath && existsSync(meta.worktreePath)) {
|
|
1166
|
+
const isArchivedChange = existsSync(join(this.cwd, '.sillyspec', 'changes', 'archive', name))
|
|
1167
|
+
|| this._isChangeArchivedInDb(name);
|
|
1168
|
+
if (isArchivedChange) {
|
|
1169
|
+
issues.push({ type: 'worktree-archived-change', name, fixable: meta.mode !== 'native-worktree',
|
|
1170
|
+
detail: `变更 ${name} 已归档但 worktree 残留——不供给依赖(死目录),建议清理(cleanup 内建未落仓护栏)` });
|
|
1171
|
+
if (fix && meta.mode !== 'native-worktree') {
|
|
1172
|
+
try {
|
|
1173
|
+
const r = this.cleanup(name);
|
|
1174
|
+
if (r.result === 'cleaned' || r.result === 'force-cleaned') {
|
|
1175
|
+
fixed.push(`cleaned archived-change worktree: ${name}`);
|
|
1176
|
+
} else if (r.result === 'blocked') {
|
|
1177
|
+
unfixable.push(`cleanup blocked (archived change 有未落仓内容): ${name} — 先 apply/commit 再清`);
|
|
1178
|
+
} else {
|
|
1179
|
+
unfixable.push(`cleanup skipped (archived change): ${name}`);
|
|
1180
|
+
}
|
|
1181
|
+
} catch { unfixable.push(`cleanup failed (archived change): ${name}`); }
|
|
1182
|
+
}
|
|
1183
|
+
} else {
|
|
1136
1184
|
const wtPath = meta.worktreePath;
|
|
1137
1185
|
const fresh = checkDepsFreshness(meta, wtPath, this.cwd);
|
|
1138
1186
|
const issueTypeByStatus = {
|
|
@@ -1145,10 +1193,13 @@ export class WorktreeManager {
|
|
|
1145
1193
|
if (issueType) {
|
|
1146
1194
|
issues.push({ type: issueType, name, detail: fresh.detail, fixable: true });
|
|
1147
1195
|
if (fix) {
|
|
1148
|
-
|
|
1196
|
+
// relinkFirst:missing/failed 多为 junction 丢失/上次供给失败——link 优先重建;
|
|
1197
|
+
// stale/main-drift 是依赖真变了——force 重装
|
|
1198
|
+
const r = this._doctorReprovision(name, wtPath, { relinkFirst: issueType === 'deps-missing' || issueType === 'deps-failed' });
|
|
1149
1199
|
(r.ok ? fixed : unfixable).push(r.msg);
|
|
1150
1200
|
}
|
|
1151
1201
|
}
|
|
1202
|
+
}
|
|
1152
1203
|
}
|
|
1153
1204
|
|
|
1154
1205
|
// meta 存在但 worktree 目录不存在
|
|
@@ -1290,10 +1341,24 @@ export class WorktreeManager {
|
|
|
1290
1341
|
|
|
1291
1342
|
// 2) 候选集中尚未落到主工作区 HEAD 的子集
|
|
1292
1343
|
const pending = this._changesAlreadyOnMain(worktreePath, tracked, untracked);
|
|
1293
|
-
|
|
1294
|
-
|
|
1344
|
+
// 3) main 工作区副本降噪层(坑 unapplied-false-positive-workspace-copy,2026-08-21 实证:
|
|
1345
|
+
// apply 后 main 工作区有逐字节一致副本但未 commit → HEAD-only 判定报「未落仓」虚警拦
|
|
1346
|
+
// doctor)。原注释「不查 main 工作区未提交副本(防误删)」的保护意图是内容未落地——
|
|
1347
|
+
// 逐字节一致时内容确已落地(删 worktree 无损),仅逐字节不同的才保守保留。
|
|
1348
|
+
const pendingAfterWs = pending.filter(f => {
|
|
1349
|
+
try {
|
|
1350
|
+
const mainCopy = join(this.cwd, f);
|
|
1351
|
+
if (!existsSync(mainCopy)) return true; // main 工作区无副本 → 仍算未落仓
|
|
1352
|
+
const wtCopy = join(worktreePath, f);
|
|
1353
|
+
if (!existsSync(wtCopy)) return true; // worktree 侧是删除态、main 侧却在 → 内容分叉,保留
|
|
1354
|
+
const a = readFileSync(wtCopy); const b = readFileSync(mainCopy);
|
|
1355
|
+
return !(a.length === b.length && a.equals(b)); // 逐字节一致 → 已落地(剔除)
|
|
1356
|
+
} catch { return true }
|
|
1357
|
+
})
|
|
1358
|
+
if (pendingAfterWs.length === 0) {
|
|
1359
|
+
return { hasChanges: false, changedFiles: [], reason: 'all changes already on main (HEAD or identical workspace copy)' };
|
|
1295
1360
|
}
|
|
1296
|
-
return { hasChanges: true, changedFiles:
|
|
1361
|
+
return { hasChanges: true, changedFiles: pendingAfterWs };
|
|
1297
1362
|
} catch (e) {
|
|
1298
1363
|
// 检测失败时保守处理:视为有变更,保留 worktree
|
|
1299
1364
|
return { hasChanges: true, changedFiles: [], reason: `check failed: ${e.message}` };
|
|
@@ -1364,6 +1429,26 @@ export class WorktreeManager {
|
|
|
1364
1429
|
return map;
|
|
1365
1430
|
}
|
|
1366
1431
|
|
|
1432
|
+
/**
|
|
1433
|
+
* DB 归档态探测(坑 doctor-reprovision-archived-change):changes 表 status='archived'。
|
|
1434
|
+
* 只读直查(node:sqlite)——doctor 是同步方法,惰性动态 import 不可用;DB 不存在/无行/
|
|
1435
|
+
* 读失败 → false(保守退回普通 deps 供给路径)。
|
|
1436
|
+
* @param {string} name
|
|
1437
|
+
* @returns {boolean}
|
|
1438
|
+
*/
|
|
1439
|
+
_isChangeArchivedInDb(name) {
|
|
1440
|
+
try {
|
|
1441
|
+
const dbPath = join(this.cwd, '.sillyspec', '.runtime', 'sillyspec.db');
|
|
1442
|
+
if (!existsSync(dbPath)) return false;
|
|
1443
|
+
const { openDatabase } = require('./db-engine.js');
|
|
1444
|
+
const db = openDatabase(dbPath, { readOnly: true });
|
|
1445
|
+
try {
|
|
1446
|
+
const row = db.prepare('SELECT status FROM changes WHERE name = ?').get(name);
|
|
1447
|
+
return row?.status === 'archived';
|
|
1448
|
+
} finally { try { db.close() } catch {} }
|
|
1449
|
+
} catch { return false }
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1367
1452
|
/**
|
|
1368
1453
|
* 从 worktree 路径反推 changeName
|
|
1369
1454
|
* @private
|
|
@@ -1388,6 +1473,13 @@ export class WorktreeManager {
|
|
|
1388
1473
|
* 将主工作区未提交变更同步到 worktree(dirty baseline overlay)
|
|
1389
1474
|
* 覆盖 staged + unstaged 的文件变更,以及 untracked 文件。
|
|
1390
1475
|
* 使用 git diff + git apply 确保正确处理删除/rename/binary。
|
|
1476
|
+
*
|
|
1477
|
+
* .sillyspec/ 隔离(坑 baseline-overlay-cross-change-contamination,2026-08-21 实证):
|
|
1478
|
+
* overlay 原样全量吸收主仓未提交改动——多变更并行时其他变更的 spec 文档
|
|
1479
|
+
* (changes/<他变更>/、ROADMAP.md、quicklog 等 39 个文件实测)被 checkpoint 进本变更
|
|
1480
|
+
* baseline,apply 回 main 即随本变更交付,需人工隔离。spec 文档不参与 worktree 内
|
|
1481
|
+
* 构建/测试(execute 的 spec 读写经 specDriftAnchor 锚回主仓),排除零功能损失;
|
|
1482
|
+
* 排除清单显式打印保持可见(哪些跨变更文件被隔离、去哪了)。
|
|
1391
1483
|
* @param {string} mainCwd - 主工作区路径
|
|
1392
1484
|
* @param {string} worktreePath - worktree 路径
|
|
1393
1485
|
* @returns {Array<string>} overlay 的文件列表
|
|
@@ -1395,15 +1487,26 @@ export class WorktreeManager {
|
|
|
1395
1487
|
_overlayBaseline(mainCwd, worktreePath) {
|
|
1396
1488
|
const files = [];
|
|
1397
1489
|
const errors = [];
|
|
1490
|
+
// git pathspec 排除(execFileSync 数组传参不经 shell,字面安全):.sillyspec/ 整目录
|
|
1491
|
+
const EXCLUDE_PATHSPEC = ':(exclude).sillyspec';
|
|
1492
|
+
const collectExcluded = (raw, excluded) => {
|
|
1493
|
+
for (const f of raw.split('\n').filter(Boolean)) {
|
|
1494
|
+
const norm = f.replace(/\\/g, '/')
|
|
1495
|
+
if (norm === '.sillyspec' || norm.startsWith('.sillyspec/')) excluded.push(f)
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1398
1498
|
|
|
1399
1499
|
try {
|
|
1400
|
-
|
|
1401
|
-
|
|
1500
|
+
const excludedSpecFiles = [];
|
|
1501
|
+
// staged 变更(pathspec 排除 .sillyspec/——跨变更 spec 文档不进 baseline)
|
|
1502
|
+
const staged = gitQuiet(mainCwd, ['diff', '--cached', '--name-only', '--', '.', EXCLUDE_PATHSPEC], { timeout: 30000 }) || ''
|
|
1503
|
+
const stagedAll = gitQuiet(mainCwd, ['diff', '--cached', '--name-only'], { timeout: 30000 }) || ''
|
|
1504
|
+
collectExcluded(stagedAll, excludedSpecFiles)
|
|
1402
1505
|
if (staged) {
|
|
1403
1506
|
try {
|
|
1404
1507
|
// 用 Buffer 模式读取,避免二进制 patch 被 UTF-8 解码损坏
|
|
1405
1508
|
// QUAL-01 收口:git-helper 新增 encoding:'buffer' 支持(二进制输出专用,跳过 trim)
|
|
1406
|
-
const patchBuf = git(mainCwd, ['diff', '--cached', '--binary'], { encoding: 'buffer', timeout: 30000 });
|
|
1509
|
+
const patchBuf = git(mainCwd, ['diff', '--cached', '--binary', '--', '.', EXCLUDE_PATHSPEC], { encoding: 'buffer', timeout: 30000 });
|
|
1407
1510
|
if (patchBuf && patchBuf.length > 0) {
|
|
1408
1511
|
const patchFile = join(worktreePath, '.sillyspec-baseline-staged.patch');
|
|
1409
1512
|
try {
|
|
@@ -1421,12 +1524,14 @@ export class WorktreeManager {
|
|
|
1421
1524
|
files.push(...staged.split('\n').filter(Boolean));
|
|
1422
1525
|
}
|
|
1423
1526
|
|
|
1424
|
-
// unstaged
|
|
1425
|
-
const unstaged = gitQuiet(mainCwd, ['diff', '--name-only'], { timeout: 30000 }) || ''
|
|
1527
|
+
// unstaged 变更(同上 pathspec 排除)
|
|
1528
|
+
const unstaged = gitQuiet(mainCwd, ['diff', '--name-only', '--', '.', EXCLUDE_PATHSPEC], { timeout: 30000 }) || ''
|
|
1529
|
+
const unstagedAll = gitQuiet(mainCwd, ['diff', '--name-only'], { timeout: 30000 }) || ''
|
|
1530
|
+
collectExcluded(unstagedAll, excludedSpecFiles)
|
|
1426
1531
|
if (unstaged) {
|
|
1427
1532
|
try {
|
|
1428
1533
|
// 用 Buffer 模式读取,避免二进制 patch 被 UTF-8 解码损坏(QUAL-01 收口,同上)
|
|
1429
|
-
const patchBuf = git(mainCwd, ['diff', '--binary'], { encoding: 'buffer', timeout: 30000 });
|
|
1534
|
+
const patchBuf = git(mainCwd, ['diff', '--binary', '--', '.', EXCLUDE_PATHSPEC], { encoding: 'buffer', timeout: 30000 });
|
|
1430
1535
|
if (patchBuf && patchBuf.length > 0) {
|
|
1431
1536
|
const patchFile = join(worktreePath, '.sillyspec-baseline-unstaged.patch');
|
|
1432
1537
|
try {
|
|
@@ -1448,6 +1553,8 @@ export class WorktreeManager {
|
|
|
1448
1553
|
const skippedDirs = [];
|
|
1449
1554
|
if (untracked) {
|
|
1450
1555
|
for (const f of untracked.split('\n').filter(Boolean)) {
|
|
1556
|
+
const norm = f.replace(/\\/g, '/')
|
|
1557
|
+
if (norm === '.sillyspec' || norm.startsWith('.sillyspec/')) { excludedSpecFiles.push(f); continue }
|
|
1451
1558
|
const r = copyUntrackedEntry(join(mainCwd, f), join(worktreePath, f));
|
|
1452
1559
|
if (r.status === 'copied') files.push(f);
|
|
1453
1560
|
else if (r.status === 'skipped-dir') skippedDirs.push(f);
|
|
@@ -1457,6 +1564,13 @@ export class WorktreeManager {
|
|
|
1457
1564
|
if (skippedDirs.length > 0) {
|
|
1458
1565
|
console.log(`ℹ️ baseline overlay 跳过 ${skippedDirs.length} 个 untracked 目录(不读目录避免 EISDIR,坑 execute-worktree-overlay-untracked-dir-eisdir)`);
|
|
1459
1566
|
}
|
|
1567
|
+
// 跨变更 spec 文档隔离可见性:列出被排除的 .sillyspec/ 文件(去重,截断展示),
|
|
1568
|
+
// 让「ROADMAP 被谁改动、为何不在本 baseline」在 create 时刻可查,不需事后考古
|
|
1569
|
+
const excludedUnique = [...new Set(excludedSpecFiles)]
|
|
1570
|
+
if (excludedUnique.length > 0) {
|
|
1571
|
+
const preview = excludedUnique.slice(0, 8).join(', ') + (excludedUnique.length > 8 ? ` …(共 ${excludedUnique.length} 个)` : '')
|
|
1572
|
+
console.log(`🧹 baseline overlay 已隔离 ${excludedUnique.length} 个 .sillyspec/ 未提交文件(跨变更 spec 文档不进本变更 baseline,留在主仓各自归属):${preview}`)
|
|
1573
|
+
}
|
|
1460
1574
|
|
|
1461
1575
|
if (files.length > 0) {
|
|
1462
1576
|
console.log(`📁 baseline overlay: ${files.length} 个未提交文件已同步到 worktree`);
|
|
@@ -18,6 +18,6 @@
|
|
|
18
18
|
12. **hook 拦截提交时禁止跳过**(`.husky/pre-push`),修复问题后再提交。
|
|
19
19
|
13. **代码必须兼容 Windows / Linux / macOS**(路径 / 换行 / 并发都要顾)。
|
|
20
20
|
14. **任务记录隔离**:永不重置 / reset / 清零已存在的 change;多个活跃 change 各自 `--change <名>` 隔离不重叠;quick 同一 QUICKLOG 按 ql-ID 条目追加,不冲突。
|
|
21
|
-
15. **quicklog
|
|
21
|
+
15. **quicklog 结构化落盘**:末步 `--done` 用四参数 `--req/--cause/--solution/--result`(CLI 合成结构化 output 并自动提取标题/四段分行);文件括注用 `--file-notes "path::注 || path2"`。骨架由 CLI 接管,`--done` 后按需核对即可,勿手拼模板。
|
|
22
22
|
16. **代码可能随时在修改**(多 agent 并行),Edit 前重跑 + 查最新态;破坏性 git op 前先备份。
|
|
23
23
|
17. **不奉承用户**,禁止"你说得对"类话术,直接给结论、依据、方案。
|
|
@@ -25,4 +25,4 @@ TaskCard 格式规则(必须严格遵守):
|
|
|
25
25
|
- **规范约定字段**(应填但不硬校验,缺失不阻断完成、只影响规范性):author、created_at、priority、depends_on、blocks
|
|
26
26
|
- allowed_paths 非空(至少一个真实源文件路径;回归类 task 无源码改动时填被验证的关键入口文件)
|
|
27
27
|
- acceptance 与 verify 都在 frontmatter 内(漏写会被 plan-postcheck 报「缺少验收标准」/「缺少验证步骤」)
|
|
28
|
-
-
|
|
28
|
+
- **生成方式**:用 `sillyspec taskcard <change-name> --task task-NN [--all]` 生成安全骨架(LF 行尾 + frontmatter 必闭合 + 九个硬校验字段齐全 + author/created_at 自动填),再往骨架里填语义内容——**勿用 Write tool 整文件手写**(CRLF / 漏闭合 `---` / 漏字段的根源,postcheck 报错大多由此来);已存在的卡命令会跳过不覆盖,局部修改用 Edit 定点改
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
### 自动探针(必须先执行)
|
|
2
|
-
在检查前,依次运行以下六个探针,将结果作为验证输入:
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
```
|
|
9
|
-
记录每个匹配的文件、行号和内容。
|
|
3
|
+
> 🔧 **先跑 CLI 机械探针(一条命令)**:`sillyspec verify-probes --change <change-name> [--init]`
|
|
4
|
+
> 输出已包含四个纯机械探针的结果(可直接进验证报告):**探针 1** 未实现标记扫描(design 清单文件逐行 TODO/FIXME 命中 + 行号)、**探针 3** 测试文件递归查找(含 co-located tests/,逐 task 覆盖)、**探针 5** API 契约对账表(endpoints.json × 前端调用 diff)、**探针 6** 删除对账三态(git 事实 × design 声明)。`--init` 顺带生成 verify-result.md 九章节骨架(探针结果已预填,`<!--TODO-->` 占位替换即可)。
|
|
5
|
+
>
|
|
6
|
+
> 下列条目中,只有**半语义部分**需要你执行:探针 2(关键词提取)、探针 3.4 集成盲区标注、探针 3.5 断言有效性抽查、探针 4(决策追踪闭环判定)。CLI 已跑的部分勿重复手跑——对 CLI 输出的 warning 项做语义复核即可。
|
|
10
7
|
|
|
11
|
-
**探针 2
|
|
8
|
+
**探针 2:设计关键词覆盖探针(你执行——关键词提取是语义)**
|
|
12
9
|
1. 读取 design.md,从中提取所有能力关键词(如"登录"、"导出"、"批量"、"删除"、"搜索"等动作词)
|
|
13
10
|
2. 对每个关键词,在源码目录中 grep 确认是否有对应的实现代码:
|
|
14
11
|
```bash
|
|
@@ -16,14 +13,11 @@ grep -rl "<关键词>" <源码目录>/ --include="*.java" --include="*.js" --inc
|
|
|
16
13
|
```
|
|
17
14
|
3. 如果某个关键词在源码中完全没有匹配,标记为 ⚠️ 可能未实现
|
|
18
15
|
|
|
19
|
-
**探针 3
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
3. 没有测试文件的 task 标记为 ⚠️ 缺少测试
|
|
23
|
-
4. **集成盲区提示**:测试文件存在 ≠ 集成正确——路由/layout 守卫重定向、跨模块装配这类集成 bug 组件单测覆盖不到,只有集成/冒烟/E2E 才暴露。对路由/layout/跨进程装配敏感的 task,额外检查是否有集成冒烟覆盖;无则标 ⚠️ 集成层未验证(需人工或部署确认)
|
|
24
|
-
5. **断言有效性抽查(与 execute「测试用例设计」闭环)**:测试文件存在 ≠ 测试有效。对最能代表本次变更行为的 2-3 个核心测试做抽查:① 断言验证真实输出/副作用,不是只"不抛错"的空断言、只测 getter/setter;② 覆盖边界/异常分支,不只业务正例;③ 走公开 API 测行为,不测实现细节(重构后功能不变测试不应失败)。不达标标 ⚠️ 断言薄弱或无效(advisory:CLI 不阻断,是否 FAIL 由你诚实判定;execute 已按「测试用例设计」写的,这里只抽查核验,不重审全量)
|
|
16
|
+
**探针 3 补充(CLI 已查存在性,你补语义)**
|
|
17
|
+
- **集成盲区提示**:测试文件存在 ≠ 集成正确——路由/layout 守卫重定向、跨模块装配这类集成 bug 组件单测覆盖不到,只有集成/冒烟/E2E 才暴露。对路由/layout/跨进程装配敏感的 task,额外检查是否有集成冒烟覆盖;无则标 ⚠️ 集成层未验证(需人工或部署确认)
|
|
18
|
+
- **断言有效性抽查(与 execute「测试用例设计」闭环)**:测试文件存在 ≠ 测试有效。对最能代表本次变更行为的 2-3 个核心测试做抽查:① 断言验证真实输出/副作用,不是只"不抛错"的空断言、只测 getter/setter;② 覆盖边界/异常分支,不只业务正例;③ 走公开 API 测行为,不测实现细节(重构后功能不变测试不应失败)。不达标标 ⚠️ 断言薄弱或无效(advisory:CLI 不阻断,是否 FAIL 由你诚实判定;execute 已按「测试用例设计」写的,这里只抽查核验,不重审全量)
|
|
25
19
|
|
|
26
|
-
**探针 4:决策追踪覆盖探针(如存在 decisions.md
|
|
20
|
+
**探针 4:决策追踪覆盖探针(如存在 decisions.md——闭环图机械,证据真伪是语义)**
|
|
27
21
|
1. 从 decisions.md 提取所有当前版本 D-xxx@vN
|
|
28
22
|
2. 检查 requirements.md 是否引用每个 D-xxx@vN,并映射到 FR-xxx
|
|
29
23
|
3. 检查 plan.md 或 tasks/task-NN.md 是否引用每个 FR-xxx/D-xxx@vN
|
|
@@ -31,36 +25,8 @@ grep -rl "<关键词>" <源码目录>/ --include="*.java" --include="*.js" --inc
|
|
|
31
25
|
5. 任意 D-xxx@vN 无下游覆盖时标记为 ⚠️ 决策未闭环
|
|
32
26
|
6. 任意 P0/P1 unresolved/blocking 决策标记为 FAIL blocker
|
|
33
27
|
|
|
34
|
-
**探针 5
|
|
35
|
-
|
|
36
|
-
- 存在 \.sillyspec/.runtime/contract-artifacts/ 目录(说明 execute 阶段生成了 endpoint artifact)
|
|
37
|
-
- 或者项目同时有 backend/ 和 frontend/ 目录
|
|
38
|
-
|
|
39
|
-
执行步骤:
|
|
40
|
-
1. 收集所有 provider endpoint artifacts:
|
|
41
|
-
- 读取 .sillyspec/.runtime/contract-artifacts/*/endpoints.json
|
|
42
|
-
- 汇总为 backend 端点清单
|
|
43
|
-
2. 扫描前端 API 调用:
|
|
44
|
-
- 在 frontend/ 目录中搜索 apiFetch/request/axios/fetch 调用
|
|
45
|
-
- 提取所有 API 路径(归一化动态参数为 {param})
|
|
46
|
-
3. Diff 对账:
|
|
47
|
-
- 前端调用路径在 backend 端点清单中找不到 → **❌ Missing backend endpoint**(advisory:CLI 复核后仅 warn,不硬阻断归档;但 contract gap 是真实集成缺陷,应诚实标 FAIL 并回 execute 补端点)
|
|
48
|
-
- backend 端点在前端无调用 → ⚠️ Unused backend endpoint(warning,不阻断)
|
|
49
|
-
4. 输出对账结果表格(示例用通用占位路径,实际填本仓真实文件):
|
|
50
|
-
|
|
51
|
-
| 状态 | 前端调用 | 后端端点 | 文件 |
|
|
52
|
-
|---|---|---|---|
|
|
53
|
-
| ❌ missing | GET /api/<域>/<资源>/<param> | — | <前端调用所在文件,如 src/lib/<模块>.ts> |
|
|
54
|
-
|
|
55
|
-
如果发现 Missing backend endpoint,在验证报告中标记为 ❌ contract gap(CLI 仅 advisory 提示、不硬阻断归档;但缺口真实存在——诚实判 FAIL 并回 execute 补齐端点,勿因 CLI 不拦而放行)。
|
|
28
|
+
**探针 5 补充(CLI 已出对账表,你做诚实判定)**
|
|
29
|
+
CLI 的 missing backend endpoint 是 advisory(不硬阻断归档)——但 contract gap 是真实集成缺陷,应诚实标 FAIL 并回 execute 补端点,勿因 CLI 不拦而放行。后端 router 的端点清单可用 `sillyspec endpoints extract --change <变更名> --task <task-NN>` 重新静态提取(勿手扫装饰器)。
|
|
56
30
|
|
|
57
|
-
**探针 6
|
|
58
|
-
|
|
59
|
-
1. 运行 `git diff --name-status HEAD`(本次变更已 apply 到主仓、未 commit,删除的文件在工作树消失但仍在 HEAD,显示 `D`)
|
|
60
|
-
2. 筛出状态以 `D` 开头的行(删除);`R`/`C` 开头的旧路径等价删除
|
|
61
|
-
3. 对每个删除文件,核对 design.md「文件变更清单」声明的操作:
|
|
62
|
-
- design 声明「删除」→ ✅ 合规(预期删除)
|
|
63
|
-
- design 声明「新增/修改」却整文件删除 → ❌ 高风险(声明与事实矛盾)
|
|
64
|
-
- design 清单未列出 → ⚠️ 未声明删除
|
|
65
|
-
4. 排除 `.sillyspec/` 与 `meta.json`(文档 churn 不算删除信号)
|
|
66
|
-
5. verify --done 时 CLI 会用同一 git 事实 advisory 复核(打印警告,不硬阻断归档)——是否 FAIL blocker 由你诚实判定,务必如实记录
|
|
31
|
+
**探针 6 补充(CLI 已出三态判定,你做终审)**
|
|
32
|
+
以 git 事实为准(真实 > 声明);CLI 的三态(合规/高风险/未声明)是机械判定,是否构成 FAIL blocker 由你诚实判定,务必如实记录——静默删除代码是 verify 的最大盲区。
|