sillyspec 3.28.2 → 3.28.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/.claude/skills/sillyspec-auto/SKILL.md +54 -93
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -114
- package/.claude/skills/sillyspec-continue/SKILL.md +17 -47
- package/.claude/skills/sillyspec-execute/SKILL.md +222 -214
- package/.claude/skills/sillyspec-knowledge/SKILL.md +40 -270
- package/.claude/skills/sillyspec-plan/SKILL.md +156 -156
- package/.claude/skills/sillyspec-propose/SKILL.md +16 -87
- package/.claude/skills/sillyspec-resume/SKILL.md +21 -70
- package/.claude/skills/sillyspec-state/SKILL.md +25 -64
- package/.claude/skills/sillyspec-workspace/SKILL.md +33 -175
- package/package.json +1 -1
- package/src/archive-delta.js +45 -2
- package/src/constants.js +6 -0
- package/src/design-facts.js +64 -0
- package/src/dispatch/backends/sillyhub-mcp.js +17 -5
- package/src/docs-check.js +119 -27
- package/src/docs-migrate.js +124 -0
- package/src/index.js +172 -22
- package/src/progress/change-registry.js +19 -0
- package/src/progress.js +2 -0
- package/src/run/command.js +129 -4
- package/src/run/complete-handlers.js +10 -3
- package/src/run/complete.js +59 -5
- package/src/run/gates.js +22 -2
- package/src/run/progress-confirm.js +66 -0
- package/src/run/prompt.js +139 -31
- package/src/run/quick-audit.js +12 -5
- package/src/run/scan-profile.js +20 -2
- package/src/run/shared.js +10 -5
- package/src/run/stage.js +7 -0
- package/src/scan-postcheck.js +5 -1
- package/src/stages/brainstorm-auto.js +2 -1
- package/src/stages/brainstorm.js +9 -17
- package/src/stages/execute.js +10 -14
- package/src/stages/plan.js +2 -1
- package/src/stages/quick.js +4 -2
- package/src/stages/verify.js +13 -15
- package/src/task-review.js +200 -0
- package/src/verify-postcheck.js +164 -30
- package/templates/agents-instruction.md +1 -1
- package/.claude/skills/verify-per-user/SKILL.md +0 -6
package/src/index.js
CHANGED
|
@@ -93,6 +93,9 @@ SillySpec CLI — 规范驱动开发工具包
|
|
|
93
93
|
sillyspec gate <stage> --change <name> [--json] 机器门控:阶段能否标记完成(只读)
|
|
94
94
|
sillyspec derive <facet> --change <name> [--json] 单项事实核验(facet: execute-evidence|verify-test|task-reviews|artifacts)
|
|
95
95
|
sillyspec backfill-reviews --change <name> [--adopt] [--json] 缺 review.json 的 task 生成草稿;--adopt 重算代填已有 review 的 base/head 等机械字段(verdict 保留)
|
|
96
|
+
sillyspec review write --change <name> --task task-NN --spec <verdict> --quality <verdict>
|
|
97
|
+
[--notes ...] [--evidence ...] [--changed-files a,b] [--force]
|
|
98
|
+
单 task review.json 命令式写入:verdict/notes 你给,base/head/changedFiles/diffPaths CLI 代算
|
|
96
99
|
sillyspec symbol-impact --change <name> 生成 symbol-impact.md 逐 task <!--TODO--> 骨架(gate 拒绝未替换占位,防骨架直接过门)
|
|
97
100
|
sillyspec design-init --change <name> [--force] 从 decisions.md 生成 design.md 十三章节骨架(决策追踪表预填;已存在不覆盖)
|
|
98
101
|
sillyspec delta --change <name> [--json] 生成变更 delta.md(Before/Delta/After 三段式聚合;幂等覆盖重跑即刷新)
|
|
@@ -565,6 +568,72 @@ async function main() {
|
|
|
565
568
|
process.exitCode = exitCode;
|
|
566
569
|
break;
|
|
567
570
|
}
|
|
571
|
+
case 'review': {
|
|
572
|
+
// 单 task review.json 命令式写入(2026-09-07 noAI 主流程批次 T2-4):agent 只给判断字段
|
|
573
|
+
// (verdict/notes/evidence),mechanics(executeRunId/base/head/changedFiles/diffPaths)全由
|
|
574
|
+
// CLI 代算——消灭「手拼 JSON→gate 拒→重写」循环与手算 hash 错误面。
|
|
575
|
+
// sillyspec review write --change <名> --task task-NN --spec pass --quality pass
|
|
576
|
+
// [--notes "..."] [--evidence "..."](cannot_verify 必填)
|
|
577
|
+
// [--changed-files a,b](归属切片为空或纯验证任务时显式给出;""=空覆盖)
|
|
578
|
+
// [--base <rev> --head <rev>](无 worktree meta 时显式锡点)[--force]
|
|
579
|
+
const rwSub = filteredArgs[1];
|
|
580
|
+
if (rwSub !== 'write') {
|
|
581
|
+
console.error('用法: sillyspec review write --change <名> --task task-NN --spec <pass|fail|cannot_verify> --quality <verdict>\n [--notes "评审备注"] [--evidence "cannot_verify 证据说明"] [--changed-files a,b] [--base <rev> --head <rev>] [--force] [--json]\n 单 task review.json 命令式写入:verdict/notes 由你给,executeRunId/base/head/changedFiles/diffPaths 由 CLI 从 git + task 卡代算;已存在默认拒覆盖(--force 越过)');
|
|
582
|
+
process.exit(2);
|
|
583
|
+
}
|
|
584
|
+
const rwVal = (flag) => {
|
|
585
|
+
const i = filteredArgs.indexOf(flag);
|
|
586
|
+
return i >= 0 && filteredArgs[i + 1] ? filteredArgs[i + 1] : null;
|
|
587
|
+
};
|
|
588
|
+
const rwChange = rwVal('--change');
|
|
589
|
+
const rwTask = rwVal('--task');
|
|
590
|
+
const rwSpec = rwVal('--spec');
|
|
591
|
+
const rwQuality = rwVal('--quality');
|
|
592
|
+
const rwNotes = rwVal('--notes');
|
|
593
|
+
const rwEvidence = rwVal('--evidence');
|
|
594
|
+
const rwBase = rwVal('--base');
|
|
595
|
+
const rwHead = rwVal('--head');
|
|
596
|
+
const rwFilesIdx = filteredArgs.indexOf('--changed-files');
|
|
597
|
+
// --changed-files 存在即视为显式覆盖(含空串=纯验证任务零 diff 声明);不存在传 null 走归属切片
|
|
598
|
+
const rwFilesOverride = rwFilesIdx >= 0
|
|
599
|
+
? (filteredArgs[rwFilesIdx + 1] || '').split(',').map(s => s.trim()).filter(Boolean)
|
|
600
|
+
: null;
|
|
601
|
+
if (!rwChange || !rwTask || !rwSpec || !rwQuality) {
|
|
602
|
+
console.error('用法: sillyspec review write --change <名> --task task-NN --spec <verdict> --quality <verdict> [--notes ...] [--evidence ...] [--changed-files a,b] [--base <rev> --head <rev>] [--force]');
|
|
603
|
+
process.exit(2);
|
|
604
|
+
}
|
|
605
|
+
assertSafeChangeName(rwChange, '--change 变更名');
|
|
606
|
+
const { writeTaskReview } = await import('./task-review.js');
|
|
607
|
+
const rwPlatformOpts = {};
|
|
608
|
+
const rwResolved = resolvePlatformOpts(dir, specDir);
|
|
609
|
+
if (rwResolved) {
|
|
610
|
+
rwPlatformOpts.specRoot = rwResolved.specRoot;
|
|
611
|
+
if (rwResolved.runtimeRoot) rwPlatformOpts.runtimeRoot = rwResolved.runtimeRoot;
|
|
612
|
+
}
|
|
613
|
+
const rwResult = await writeTaskReview({
|
|
614
|
+
changeName: rwChange, cwd: dir, taskId: rwTask,
|
|
615
|
+
specVerdict: rwSpec, qualityVerdict: rwQuality,
|
|
616
|
+
reviewerNotes: rwNotes || '',
|
|
617
|
+
requiredEvidence: rwEvidence ? [rwEvidence] : [],
|
|
618
|
+
baseOverride: rwBase, headOverride: rwHead,
|
|
619
|
+
changedFilesOverride: rwFilesOverride,
|
|
620
|
+
force: filteredArgs.includes('--force'),
|
|
621
|
+
platformOpts: rwPlatformOpts,
|
|
622
|
+
});
|
|
623
|
+
if (json) {
|
|
624
|
+
console.log(JSON.stringify({ ok: rwResult.ok, ...rwResult }, null, 2));
|
|
625
|
+
} else if (rwResult.ok) {
|
|
626
|
+
console.log(`✅ review.json 已写入:${rwResult.reviewPath}`);
|
|
627
|
+
console.log(` executeRunId: ${rwResult.executeRunId}`);
|
|
628
|
+
for (const w of rwResult.warnings) console.log(`⚠️ ${w}`);
|
|
629
|
+
} else {
|
|
630
|
+
console.error('❌ review write 失败:');
|
|
631
|
+
for (const e of rwResult.errors) console.error(` - ${e}`);
|
|
632
|
+
for (const w of rwResult.warnings) console.error(`⚠️ ${w}`);
|
|
633
|
+
process.exit(1);
|
|
634
|
+
}
|
|
635
|
+
break;
|
|
636
|
+
}
|
|
568
637
|
case 'backfill-reviews': {
|
|
569
638
|
// 手动补 task(reopen execute / 直接实现)缺 review.json 的官方入口(坑 verify-archive-flow-pitfalls 坑2)。
|
|
570
639
|
// 复用 generateTaskReviewDrafts(execute --done 同源兜底,幂等、fail-open):据 git diff base..head + working-tree
|
|
@@ -1328,16 +1397,29 @@ async function main() {
|
|
|
1328
1397
|
const { resolveRuntimeRoot } = await import('./run/shared.js');
|
|
1329
1398
|
const dlRuntimeRoot = resolveRuntimeRoot(dlPlatformOpts, dlSpecBase);
|
|
1330
1399
|
const { buildDeltaReport } = await import('./archive-delta.js');
|
|
1331
|
-
//
|
|
1332
|
-
//
|
|
1333
|
-
|
|
1400
|
+
// project 同口径修复(change: 2026-09-07-ir-hardening D-005@v1):此前硬编码 null →
|
|
1401
|
+
// 手动补跑恒走 loadModuleMap 降级(报告恒「无 module-map」注记)。改从 progress.project
|
|
1402
|
+
// 取值(与归档确认步自动生成同源),null 兜底降级不变。
|
|
1403
|
+
let dlProject = null;
|
|
1404
|
+
try {
|
|
1405
|
+
const { ProgressManager } = await import('./progress.js');
|
|
1406
|
+
const dlPm = new ProgressManager({ specDir: dlSpecBase });
|
|
1407
|
+
const dlProgress = dlPm.read(cwd, dlChange);
|
|
1408
|
+
dlProject = dlProgress?.project || null;
|
|
1409
|
+
} catch { /* 读 progress 失败 → null 兜底降级(fail-soft) */ }
|
|
1410
|
+
const dlResult = buildDeltaReport({
|
|
1334
1411
|
changeDir: dlChangeDir,
|
|
1335
1412
|
specRoot: dlSpecBase,
|
|
1336
|
-
project:
|
|
1413
|
+
project: dlProject,
|
|
1337
1414
|
runtimeRoot: dlRuntimeRoot,
|
|
1415
|
+
withSummary: true,
|
|
1338
1416
|
});
|
|
1417
|
+
const dlMarkdown = dlResult.markdown;
|
|
1339
1418
|
const dlPath = join(dlChangeDir, 'delta.md');
|
|
1340
1419
|
writeFileSync(dlPath, dlMarkdown);
|
|
1420
|
+
// 回灌 sidecar(2026-09-07-ir-hardening D-006):fail-soft,写失败不阻断 delta 输出
|
|
1421
|
+
const { writeLastDeltaSidecar } = await import('./archive-delta.js');
|
|
1422
|
+
writeLastDeltaSidecar(dlRuntimeRoot, dlResult);
|
|
1341
1423
|
if (json) {
|
|
1342
1424
|
console.log(JSON.stringify({ command: 'delta', change: dlChange, ok: true, path: dlPath, written: true }, null, 2));
|
|
1343
1425
|
break;
|
|
@@ -1419,8 +1501,40 @@ async function main() {
|
|
|
1419
1501
|
case 'docs': {
|
|
1420
1502
|
const docsSubCmd = filteredArgs[1];
|
|
1421
1503
|
if (docsSubCmd === 'migrate') {
|
|
1422
|
-
|
|
1423
|
-
|
|
1504
|
+
// docs migrate 双形态(2026-09-08-docs-fix-capability / D-002+D-004):
|
|
1505
|
+
// 有 --from/--to → 新语义(路径引用前缀迁移,薄模块 docs-migrate.js)
|
|
1506
|
+
// 无 flag → 旧语义(.sillyspec 结构一次性迁移 migrateDocs,兼容保留)
|
|
1507
|
+
const rawMigrateArgs = filteredArgs.slice(2);
|
|
1508
|
+
let mFrom = null, mTo = null, mApply = false;
|
|
1509
|
+
for (let i = 0; i < rawMigrateArgs.length; i++) {
|
|
1510
|
+
const a = rawMigrateArgs[i];
|
|
1511
|
+
if (a === '--from' && rawMigrateArgs[i + 1] !== undefined) { mFrom = rawMigrateArgs[i + 1]; i++; }
|
|
1512
|
+
else if (a === '--to' && rawMigrateArgs[i + 1] !== undefined) { mTo = rawMigrateArgs[i + 1]; i++; }
|
|
1513
|
+
else if (a === '--apply') { mApply = true; }
|
|
1514
|
+
else if (a === '--from' || a === '--to') {
|
|
1515
|
+
console.error(`❌ docs migrate: ${a} 缺值(如 --from "modules/" --to "backend/app/modules/")`);
|
|
1516
|
+
process.exit(2);
|
|
1517
|
+
} else if (a.startsWith('--')) {
|
|
1518
|
+
console.error(`❌ docs migrate: 未知 flag「${a}」。已知 flag:--from --to --apply`);
|
|
1519
|
+
process.exit(2);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
if (mFrom !== null || mTo !== null) {
|
|
1523
|
+
if (mFrom === null || mTo === null) {
|
|
1524
|
+
console.error('❌ docs migrate: --from 与 --to 必须成对提供');
|
|
1525
|
+
process.exit(2);
|
|
1526
|
+
}
|
|
1527
|
+
const { runDocsMigrate } = await import('./docs-migrate.js');
|
|
1528
|
+
const result = runDocsMigrate({ projectRoot: dir, from: mFrom, to: mTo, apply: mApply });
|
|
1529
|
+
// exit code 契约:0=dry-run 零计划或 --apply 后 postCheck 全绿;1=--apply 后仍失效或
|
|
1530
|
+
// unverified>0;dry-run 有计划恒 0(预览不判成败)
|
|
1531
|
+
if (!mApply) { process.exit(0); }
|
|
1532
|
+
const fail = result.unverified > 0 || (result.postCheck && result.postCheck.invalid > 0);
|
|
1533
|
+
process.exit(fail ? 1 : 0);
|
|
1534
|
+
} else {
|
|
1535
|
+
const { migrateDocs } = await import('./migrate.js');
|
|
1536
|
+
migrateDocs(dir);
|
|
1537
|
+
}
|
|
1424
1538
|
} else if (docsSubCmd === 'check') {
|
|
1425
1539
|
// docs check(2026-08-15 docs-check-productize D-6):文档 file:line 引用校验。
|
|
1426
1540
|
// exit code 三档(D-003):0 全绿 / 1 存在无效引用 / 2 配置/IO 错误(DocsCheckConfigError)。
|
|
@@ -1433,14 +1547,14 @@ async function main() {
|
|
|
1433
1547
|
// 写回);--dry-run 预览不写盘,单独传(无 --fix)也走预览路径——design §5.2 行为矩阵
|
|
1434
1548
|
// --dry-run 列独立存在(「报告修复预览 + exit 1」),即 dryRun 置位 = fix 语义自动生效
|
|
1435
1549
|
// 但零写盘(本口径记录于此,防歧义)。
|
|
1436
|
-
const BARE_FLAGS = ['--
|
|
1550
|
+
const BARE_FLAGS = ['--fix', '--dry-run', '--no-exempt'];
|
|
1437
1551
|
const PAIRED_FLAGS = ['--paths'];
|
|
1438
1552
|
const rawDocsArgs = filteredArgs.slice(2);
|
|
1439
1553
|
const docsCheckFlags = [];
|
|
1440
1554
|
let cliPaths = null;
|
|
1441
|
-
let suggest = false;
|
|
1442
1555
|
let fix = false;
|
|
1443
1556
|
let dryRun = false;
|
|
1557
|
+
let noExempt = false;
|
|
1444
1558
|
for (let i = 0; i < rawDocsArgs.length; i++) {
|
|
1445
1559
|
const a = rawDocsArgs[i];
|
|
1446
1560
|
if (a === '--paths' && rawDocsArgs[i + 1] !== undefined) {
|
|
@@ -1450,9 +1564,9 @@ async function main() {
|
|
|
1450
1564
|
console.error('❌ docs check: --paths 缺值(逗号分隔 glob,如 --paths "docs/**/*.md")');
|
|
1451
1565
|
process.exit(2);
|
|
1452
1566
|
} else if (BARE_FLAGS.includes(a)) {
|
|
1453
|
-
if (a === '--
|
|
1454
|
-
else if (a === '--fix') fix = true;
|
|
1567
|
+
if (a === '--fix') fix = true;
|
|
1455
1568
|
else if (a === '--dry-run') dryRun = true;
|
|
1569
|
+
else if (a === '--no-exempt') noExempt = true;
|
|
1456
1570
|
} else if (a.startsWith('--')) {
|
|
1457
1571
|
console.error(`❌ docs check: 未知 flag「${a}」。已知 flag:${[...BARE_FLAGS, ...PAIRED_FLAGS].join(' ')}(--json 为全局 flag)`);
|
|
1458
1572
|
process.exit(2);
|
|
@@ -1471,6 +1585,7 @@ async function main() {
|
|
|
1471
1585
|
skip: cfg.skip,
|
|
1472
1586
|
keywordAssert: cfg.keywordAssert,
|
|
1473
1587
|
crossRepoRoots: cfg.crossRepoRoots,
|
|
1588
|
+
exempt: !noExempt,
|
|
1474
1589
|
});
|
|
1475
1590
|
// task-03 修复链路(FR-04,platform-map-auto-anchors):fixActive 才构造 fixes——
|
|
1476
1591
|
// 仅 fix.fixable===true 且 newLine 为整数的条目可重锚;newRef = ref 的行号部分替换为
|
|
@@ -1491,6 +1606,31 @@ async function main() {
|
|
|
1491
1606
|
}
|
|
1492
1607
|
}
|
|
1493
1608
|
const fixResult = fixActive ? applyFixes(dir, fixes, dryRun ? { dryRun: true } : {}) : null;
|
|
1609
|
+
// 修复回执(change: 2026-09-07-ir-hardening D-007@v1,FR-04):--fix 落盘后用与首查
|
|
1610
|
+
// 完全相同的口径重跑一次失效收集,得 after 计数——「前 N → 重锚 M → 后 K(消除 N-K)」
|
|
1611
|
+
// 即修复建议被机器证明消除了多少诊断(acceptsFix 最小件)。仅 --fix(写盘)路径;
|
|
1612
|
+
// --dry-run 不写盘重跑无意义,receipt 为 null;非 fix 路径零变化。
|
|
1613
|
+
let fixReceipt = null;
|
|
1614
|
+
if (fix && !dryRun && fixResult && fixResult.applied > 0) {
|
|
1615
|
+
try {
|
|
1616
|
+
const after = runDocsCheck({
|
|
1617
|
+
projectRoot: dir,
|
|
1618
|
+
docs: docsCheckFlags.length > 0 ? docsCheckFlags : null,
|
|
1619
|
+
paths: cliPaths || cfg.paths,
|
|
1620
|
+
skip: cfg.skip,
|
|
1621
|
+
keywordAssert: cfg.keywordAssert,
|
|
1622
|
+
crossRepoRoots: cfg.crossRepoRoots,
|
|
1623
|
+
});
|
|
1624
|
+
fixReceipt = {
|
|
1625
|
+
invalidBefore: result.invalid.length,
|
|
1626
|
+
reAnchored: fixResult.applied,
|
|
1627
|
+
invalidAfter: after.invalid.length,
|
|
1628
|
+
eliminated: result.invalid.length - after.invalid.length,
|
|
1629
|
+
};
|
|
1630
|
+
} catch (e) {
|
|
1631
|
+
console.warn(`⚠️ 修复回执重算失败(不影响修复本身):${e && e.message ? e.message : e}`);
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1494
1634
|
// 变更名提名 advisory(2026-08-31 变更关联审计 P2):铁律同决策规则族——只 warn,
|
|
1495
1635
|
// 不进 invalid、不影响下方 exit code 与 docs gate。降级容错:advisory 自身异常
|
|
1496
1636
|
// 不拖垮主检查(try-catch 吞并提示)。
|
|
@@ -1503,7 +1643,7 @@ async function main() {
|
|
|
1503
1643
|
if (json) {
|
|
1504
1644
|
// --fix 与 --json 组合保持 stdout 纯 JSON 可解析(constraints):修复统计作 result.fixReport
|
|
1505
1645
|
// 附加后整体 stringify,主体仍是 runDocsCheck 返回(invalid[].fix 是 task-01 设计内增量)
|
|
1506
|
-
if (fixResult) result.fixReport = { applied: fixResult.applied, skipped: fixResult.skipped.length, dryRun };
|
|
1646
|
+
if (fixResult) result.fixReport = { applied: fixResult.applied, skipped: fixResult.skipped.length, dryRun, ...(fixReceipt ? { receipt: fixReceipt } : {}) };
|
|
1507
1647
|
if (changeNameReport) result.changeNameReport = changeNameReport;
|
|
1508
1648
|
console.log(JSON.stringify(result, null, 2));
|
|
1509
1649
|
} else {
|
|
@@ -1514,7 +1654,10 @@ async function main() {
|
|
|
1514
1654
|
if (result.ok) {
|
|
1515
1655
|
console.log(`✅ docs check: ${result.total} 处引用全通过(其中 ${result.kwChecked} 处带关键词断言)`);
|
|
1516
1656
|
} else {
|
|
1517
|
-
|
|
1657
|
+
// FR-5(D-005,2026-09-08-docs-fix-capability):报告内容统一 stdout(脚本 capture_output
|
|
1658
|
+
// 只读 stdout 可捕获);stderr 仅留 ⚠️ 诊断(console.warn)与配置错误(exit 2 路径)。
|
|
1659
|
+
// 文本逐字不变,只改通道。
|
|
1660
|
+
console.log(`\n❌ docs check: ${result.invalid.length}/${result.total} 处引用失效:`);
|
|
1518
1661
|
let appliedLeft = fixResult ? fixResult.applied : 0;
|
|
1519
1662
|
const skippedQueue = fixResult ? [...fixResult.skipped] : [];
|
|
1520
1663
|
for (const inv of result.invalid) {
|
|
@@ -1522,26 +1665,31 @@ async function main() {
|
|
|
1522
1665
|
if (newRef !== undefined && appliedLeft > 0) {
|
|
1523
1666
|
// 已应用(dry-run 为将应用):归因按构造顺序消耗 applied 计数——写回 skipped 仅
|
|
1524
1667
|
// 防御路径(文档消失/行内失配)触发,CLI 正常流 fixable 必然落位
|
|
1525
|
-
console.
|
|
1668
|
+
console.log(` ✅ [${inv.doc}:L${inv.docLine}] ${inv.ref} → ${newRef}${dryRun ? '(dry-run 未写盘)' : ''}`);
|
|
1526
1669
|
appliedLeft--;
|
|
1527
1670
|
} else if (newRef !== undefined) {
|
|
1528
1671
|
const s = skippedQueue.shift();
|
|
1529
|
-
console.
|
|
1672
|
+
console.log(` ⚠️ [${inv.doc}:L${inv.docLine}] ${inv.ref} → 写回跳过:${s ? s.reason : '未知原因'}`);
|
|
1530
1673
|
} else if (fixResult) {
|
|
1531
1674
|
// needs-manual:多命中歧义/零命中/无 token——fix.reason 含候选行号,交人工
|
|
1532
|
-
console.
|
|
1675
|
+
console.log(` ❌ [${inv.doc}:L${inv.docLine}] ${inv.ref} → ${(inv.fix && inv.fix.reason) || inv.reason}(待人工)`);
|
|
1533
1676
|
} else {
|
|
1534
|
-
console.
|
|
1677
|
+
console.log(` ❌ [${inv.doc}:L${inv.docLine}] ${inv.ref} → ${inv.reason}`);
|
|
1535
1678
|
}
|
|
1536
|
-
if (
|
|
1537
|
-
|
|
1679
|
+
if (inv.suggest && inv.suggest.length > 0) {
|
|
1680
|
+
// 2026-09-07-ir-hardening task-08:原 --suggest 旗标删除(no-op 首选路径退役),
|
|
1681
|
+
// 候选行号提示改为 needs-manual 默认输出——旗标唯一用途,默认开更有信息量
|
|
1682
|
+
console.log(` 💡 候选行号: ${inv.suggest.join(', ')}(token 命中行,人工确认后更新文档锚)`);
|
|
1538
1683
|
}
|
|
1539
1684
|
}
|
|
1540
1685
|
if (fixResult) {
|
|
1541
|
-
console.
|
|
1686
|
+
console.log(`\n🔧 重锚报告:${fixResult.applied} 处已${dryRun ? '预览' : '改写'}${dryRun ? '(dry-run 未写盘)' : ''}、${result.invalid.length - fixes.length} 处待人工${fixResult.skipped.length > 0 ? `、${fixResult.skipped.length} 处写回跳过` : ''}。`);
|
|
1687
|
+
if (fixReceipt) {
|
|
1688
|
+
console.log(`🔁 修复回执(机器复跑证明):修复前 ${fixReceipt.invalidBefore} 处失效 → 重锚 ${fixReceipt.reAnchored} 处 → 修复后 ${fixReceipt.invalidAfter} 处(消除 ${fixReceipt.eliminated} 处)。`);
|
|
1689
|
+
}
|
|
1542
1690
|
}
|
|
1543
|
-
console.
|
|
1544
|
-
console.
|
|
1691
|
+
console.log(`\n修复指引:行号漂移 → 更新文档行号到当前源码;文件删改名 → 更新引用路径;`);
|
|
1692
|
+
console.log(`关键词缺失但行号正确 → 确认符号是否改名,改文档 token 或行号。`);
|
|
1545
1693
|
}
|
|
1546
1694
|
// 变更名提名 advisory 段(主结果之后、exit 之前;不参与 ok/invalid 与 exit code)。
|
|
1547
1695
|
// 零输出原则(docs-check-fix 契约:无新 flag 时 stdout 逐字节一致;决策规则族同款
|
|
@@ -1622,7 +1770,9 @@ async function main() {
|
|
|
1622
1770
|
}
|
|
1623
1771
|
process.exit(g.exitCode);
|
|
1624
1772
|
} else {
|
|
1625
|
-
console.log('用法: sillyspec docs migrate | sillyspec docs check [--paths <glob,...>] [--json] [--
|
|
1773
|
+
console.log('用法: sillyspec docs migrate [--from <旧前缀> --to <新前缀> [--apply]] | sillyspec docs check [--paths <glob,...>] [--json] [--fix] [--dry-run] [--no-exempt] | sillyspec docs gate [--init-baseline] [--json]');
|
|
1774
|
+
console.log(' docs migrate:--from/--to 路径前缀迁移(默认 dry-run,--apply 写盘+自动复核);无 flag = 旧 .sillyspec 结构迁移');
|
|
1775
|
+
console.log(' docs check --no-exempt:archive/finished 与 doc_type: snapshot 文档照常校验(缺省豁免)');
|
|
1626
1776
|
process.exit(2);
|
|
1627
1777
|
}
|
|
1628
1778
|
break;
|
|
@@ -19,6 +19,25 @@ export class ChangeRegistry {
|
|
|
19
19
|
return rows.map(r => r.name);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* 读变更创建时间(IR 严格模式闸门判别源,change: 2026-09-07-ir-hardening D-001@v1)。
|
|
24
|
+
* 只读不抛(与 listChanges 同款 DB 访问):变更不存在 / db 异常 → null——调用方
|
|
25
|
+
* (isStrictChange)按 fail-open 落存量豁免,不误伤。
|
|
26
|
+
* @param {string} cwd
|
|
27
|
+
* @param {string} changeName
|
|
28
|
+
* @returns {string|null} ISO 时间字符串或 null
|
|
29
|
+
*/
|
|
30
|
+
getChangeCreatedAt(cwd, changeName) {
|
|
31
|
+
if (!changeName || typeof changeName !== 'string') return null;
|
|
32
|
+
try {
|
|
33
|
+
const db = this.pm._ensureDB(cwd);
|
|
34
|
+
const row = db.getDb().prepare('SELECT created_at FROM changes WHERE name = ?').get(changeName);
|
|
35
|
+
return row && typeof row.created_at === 'string' ? row.created_at : null;
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
22
41
|
/**
|
|
23
42
|
* 查询单个变更的流程阶段与状态(quick 轻量归档阶段闸用)。
|
|
24
43
|
* 与 readChangeIsolation 不同(展示读,catch → null 宽容):本方法服务权限判定,读失败
|
package/src/progress.js
CHANGED
|
@@ -833,6 +833,8 @@ export class ProgressManager {
|
|
|
833
833
|
// ── 变更管理 ──
|
|
834
834
|
|
|
835
835
|
listChanges(cwd) { return this._changeRegistry.listChanges(cwd); }
|
|
836
|
+
/** 读变更创建时间(IR 严格模式闸门判别源,2026-09-07-ir-hardening D-001)——只读不抛,缺行/异常 → null */
|
|
837
|
+
getChangeCreatedAt(cwd, changeName) { return this._changeRegistry.getChangeCreatedAt(cwd, changeName); }
|
|
836
838
|
|
|
837
839
|
// 单变更阶段查询(quick 轻量归档阶段闸):无行 null,读失败抛(调用方 fail-closed)
|
|
838
840
|
getChangeStage(cwd, changeName) { return this._changeRegistry.getChangeStage(cwd, changeName); }
|
package/src/run/command.js
CHANGED
|
@@ -1087,6 +1087,22 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
|
|
|
1087
1087
|
// brainstorm 自动创建变更也写 title(--input 需求描述优先 / 自动名兜底),proposal 落盘后刷新。
|
|
1088
1088
|
progress = pm.initChange(cwd, autoName, { title: inputText ? sanitizeDesc(inputText) : autoName })
|
|
1089
1089
|
changeName = autoName
|
|
1090
|
+
} else if (stageName === 'auto') {
|
|
1091
|
+
// auto 零活跃建变更(2026-09-08-auto-driver D-003@v1,Grill P1-②):SKILL 启动命令即
|
|
1092
|
+
// run auto --input——此前此处 exit 2 是断头路。复用 brainstorm 命名(date-new-change-hex)。
|
|
1093
|
+
// pm.read 对零/多活跃都返 null——先分流:多活跃报错列候选(不隐式选择),零活跃才建。
|
|
1094
|
+
const activeCount = pm.listChanges(cwd)
|
|
1095
|
+
if (activeCount.length > 1) {
|
|
1096
|
+
console.error(`❌ 已存在 ${activeCount.length} 个活跃变更,run auto 无法自动定位目标。`)
|
|
1097
|
+
console.error(' 活跃变更:' + activeCount.join('、'))
|
|
1098
|
+
console.error(' 请用 --change <变更名> 显式指定,或先归档/收尾现有变更。')
|
|
1099
|
+
process.exit(2)
|
|
1100
|
+
}
|
|
1101
|
+
const date = new Date().toISOString().slice(0, 10)
|
|
1102
|
+
const autoName = `${date}-new-change-${randomBytes(4).toString('hex')}`
|
|
1103
|
+
console.log(`🔄 auto 模式自动创建变更:${autoName}(可用 --change 指定名称,或事后 change-rename)`)
|
|
1104
|
+
progress = pm.initChange(cwd, autoName, { title: inputText ? sanitizeDesc(inputText) : autoName })
|
|
1105
|
+
changeName = autoName
|
|
1090
1106
|
} else {
|
|
1091
1107
|
console.error('❌ 未找到进度数据,请先运行 sillyspec init 或指定 --change <变更名>')
|
|
1092
1108
|
// 自愈引导(坑 suggestion-command-missing-change 同族):多活跃变更仓不带 --change 时
|
|
@@ -1546,8 +1562,15 @@ async function runAutoMode(pm, progress, cwd, flags, changeName, platformOpts =
|
|
|
1546
1562
|
const inputText = autoFlagValue('--input')
|
|
1547
1563
|
const skipApproval = flags.includes('--skip-approval')
|
|
1548
1564
|
const explicitMode = autoFlagValue('--mode')
|
|
1565
|
+
// --wait-interactive(2026-09-08-auto-driver D-004@v1,FR-03):TTY 下 requiresUser 步 readline 直收
|
|
1566
|
+
const waitInteractive = autoFlagValue('--wait-interactive') === 'true'
|
|
1567
|
+
globalThis.__ssWaitInteractive = waitInteractive // maybeWaitInteractive 消费(同进程直通)
|
|
1549
1568
|
const specBase = platformOpts?.specRoot || join(cwd, '.sillyspec')
|
|
1550
1569
|
|
|
1570
|
+
// 单活跃自动选中为既有行为(入口 pm.read(cwd,null))——此处仅回显(D-003@v1;
|
|
1571
|
+
// 零活跃建变更已上移入口守卫层,多活跃 exit 2 由入口守卫处理)。
|
|
1572
|
+
if (changeName) console.log(`🎯 auto 目标变更(单活跃自动选中或显式指定):${changeName}`)
|
|
1573
|
+
|
|
1551
1574
|
// Helper: 在 auto 模式下获取步骤定义
|
|
1552
1575
|
const getAutoSteps = async (stage) => {
|
|
1553
1576
|
if (stage === 'brainstorm') {
|
|
@@ -1568,6 +1591,10 @@ async function runAutoMode(pm, progress, cwd, flags, changeName, platformOpts =
|
|
|
1568
1591
|
if (stage === 'brainstorm') {
|
|
1569
1592
|
const existingSteps = progress.stages?.brainstorm?.steps
|
|
1570
1593
|
const isAutoModeSteps = existingSteps?.length === 4 && existingSteps?.[0]?.name === '进度确认与上下文加载'
|
|
1594
|
+
// auto 表在位即早退(2026-09-08 E2E 实证 bug:此前仅 !isAutoModeSteps 重种后 return,
|
|
1595
|
+
// auto 表存在时掉进下方通用 ensureStageSteps——主模式 8 步定义判 4 步 auto 表「漂移」
|
|
1596
|
+
// 重种主模式表,与 getAutoSteps 渲染双轨互踩,进度永远回 step1)
|
|
1597
|
+
if (isAutoModeSteps) return progress
|
|
1571
1598
|
if (!isAutoModeSteps) {
|
|
1572
1599
|
if (!progress.stages) progress.stages = {}
|
|
1573
1600
|
progress.stages.brainstorm = {
|
|
@@ -1613,6 +1640,8 @@ async function runAutoMode(pm, progress, cwd, flags, changeName, platformOpts =
|
|
|
1613
1640
|
currentStage = firstOpenStage()
|
|
1614
1641
|
}
|
|
1615
1642
|
if (!currentStage) {
|
|
1643
|
+
// 重进入且已全完成:复用收尾总结 helper(FR-04)
|
|
1644
|
+
if (changeName) printAutoCompletionSummary(pm, cwd, changeName, flowStages, specBase)
|
|
1616
1645
|
console.log('All auto flow stages are complete.')
|
|
1617
1646
|
return
|
|
1618
1647
|
}
|
|
@@ -1656,7 +1685,11 @@ async function runAutoMode(pm, progress, cwd, flags, changeName, platformOpts =
|
|
|
1656
1685
|
}
|
|
1657
1686
|
const next = nextInFlow(currentStage)
|
|
1658
1687
|
if (next) console.log(`${currentStage} is complete. Run: sillyspec run auto --done --output "${currentStage} complete"`)
|
|
1659
|
-
else
|
|
1688
|
+
else {
|
|
1689
|
+
console.log('All auto flow stages are complete.')
|
|
1690
|
+
// 展示路径全完成(阶段已关但用户重进)——复用收尾总结(FR-04)
|
|
1691
|
+
printAutoCompletionSummary(pm, cwd, changeName, flowStages, specBase)
|
|
1692
|
+
}
|
|
1660
1693
|
return
|
|
1661
1694
|
}
|
|
1662
1695
|
// execute 阶段启动前检查审批
|
|
@@ -1677,7 +1710,9 @@ async function runAutoMode(pm, progress, cwd, flags, changeName, platformOpts =
|
|
|
1677
1710
|
}
|
|
1678
1711
|
}
|
|
1679
1712
|
}
|
|
1680
|
-
await outputStep(currentStage, pendingIdx, defSteps, cwd, changeName, progress.project || null, platformOpts, null, collectStageWaitHistory(progress, currentStage))
|
|
1713
|
+
await outputStep(currentStage, pendingIdx, defSteps, cwd, changeName, progress.project || null, platformOpts, null, collectStageWaitHistory(progress, currentStage), { changeName })
|
|
1714
|
+
// wait 直通(FR-03):requiresUser 且 TTY 且旗标开 → readline 直收,等价 --continue --answer
|
|
1715
|
+
await maybeWaitInteractive(defSteps[pendingIdx], changeName, currentStage)
|
|
1681
1716
|
return
|
|
1682
1717
|
}
|
|
1683
1718
|
|
|
@@ -1717,13 +1752,15 @@ async function runAutoMode(pm, progress, cwd, flags, changeName, platformOpts =
|
|
|
1717
1752
|
}
|
|
1718
1753
|
}
|
|
1719
1754
|
}
|
|
1720
|
-
await outputStep(currentStage, nextPendingIdx, defSteps, cwd, changeName, progress.project || null, platformOpts, null, collectStageWaitHistory(progress, currentStage))
|
|
1755
|
+
await outputStep(currentStage, nextPendingIdx, defSteps, cwd, changeName, progress.project || null, platformOpts, null, collectStageWaitHistory(progress, currentStage), { changeName })
|
|
1721
1756
|
return
|
|
1722
1757
|
}
|
|
1723
1758
|
|
|
1724
1759
|
const next = nextInFlow(currentStage)
|
|
1725
1760
|
if (!next) {
|
|
1726
1761
|
console.log('\nAll auto flow stages are complete.')
|
|
1762
|
+
// 自然收尾点(--done 后 next==null,FR-04):打印收尾总结
|
|
1763
|
+
printAutoCompletionSummary(pm, cwd, changeName, flowStages, specBase)
|
|
1727
1764
|
return
|
|
1728
1765
|
}
|
|
1729
1766
|
|
|
@@ -1795,7 +1832,95 @@ async function runAutoMode(pm, progress, cwd, flags, changeName, platformOpts =
|
|
|
1795
1832
|
}
|
|
1796
1833
|
}
|
|
1797
1834
|
}
|
|
1798
|
-
await outputStep(next, firstPending, nextSteps, cwd, changeName, progress.project || null, platformOpts, null, collectStageWaitHistory(progress, next))
|
|
1835
|
+
await outputStep(next, firstPending, nextSteps, cwd, changeName, progress.project || null, platformOpts, null, collectStageWaitHistory(progress, next), { changeName })
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
|
|
1840
|
+
/**
|
|
1841
|
+
* maybeWaitInteractive —— auto 模式 wait 用户输入直通(change: 2026-09-08-auto-driver,D-004@v1,FR-03)。
|
|
1842
|
+
*
|
|
1843
|
+
* 时序:outputStep 渲染返回后调用。条件全满足(旗标开 + requiresUser 四源 + stdin TTY)→
|
|
1844
|
+
* readline 直收一行,等价自动 `--continue --answer <输入>`(复用 completeStep wait→continue 状态机,
|
|
1845
|
+
* 无新状态);任一不满足/异常 → 静默或提示回三段式(fail-open,Grill P2-①时序)。
|
|
1846
|
+
* 仅 runAutoMode 的首渲染点消费(--done 推进路径不重收)。
|
|
1847
|
+
*/
|
|
1848
|
+
export async function maybeWaitInteractive(stepDef, changeName, stageName, { rlFactory = null } = {}) {
|
|
1849
|
+
try {
|
|
1850
|
+
if (!globalThis.__ssWaitInteractive) return
|
|
1851
|
+
if (!stepDef) return
|
|
1852
|
+
const { requiresUser } = await import('./prompt.js')
|
|
1853
|
+
// prompt 正文在此不可得——四源三键优先(WAIT_MARKER_RE 源由 SS-META 渲染时已并入,直通用三键近似:
|
|
1854
|
+
// 真需 wait 的步必有标记,纯正文标记步三段式照旧)
|
|
1855
|
+
const needsUser = stepDef.requiresWait === true || stepDef.conditionalWait === true || stepDef.requiresConfirm === true
|
|
1856
|
+
if (!needsUser) return
|
|
1857
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
1858
|
+
console.log('ℹ️ --wait-interactive 需要 TTY(当前非交互环境)——按 prompt 的 wait 指令走三段式')
|
|
1859
|
+
return
|
|
1860
|
+
}
|
|
1861
|
+
// rlFactory 为测试缝(注入伪 readline);生产路径用 node:readline/promises 真实 TTY
|
|
1862
|
+
const rl = rlFactory
|
|
1863
|
+
? rlFactory()
|
|
1864
|
+
: (await import('node:readline/promises')).createInterface({ input: process.stdin, output: process.stdout })
|
|
1865
|
+
console.log('')
|
|
1866
|
+
const answer = await rl.question('⌨️ 本步需要你的输入(直通模式,回答后将自动 --continue --answer):')
|
|
1867
|
+
rl.close()
|
|
1868
|
+
if (answer && answer.trim()) {
|
|
1869
|
+
console.log(`📩 已接收,自动续行:sillyspec run auto --continue --answer "${answer.trim().slice(0, 60)}${answer.trim().length > 60 ? '…' : ''}" ...`)
|
|
1870
|
+
// 复用既有 continue 路径:与 runCommand 的 --continue 同源(子进程自调避免递归锁)
|
|
1871
|
+
const { execFileSync } = await import('node:child_process')
|
|
1872
|
+
const binSelf = new URL('../bin/sillyspec.js', import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, '$1')
|
|
1873
|
+
try {
|
|
1874
|
+
execFileSync(process.execPath, [binSelf, 'run', 'auto', '--continue', '--answer', answer.trim(), '--change', changeName], { stdio: 'inherit', cwd: process.cwd() })
|
|
1875
|
+
} catch { /* continue 失败(非 waiting 态等)静默——回三段式由 agent 走 */ }
|
|
1876
|
+
}
|
|
1877
|
+
} catch (e) {
|
|
1878
|
+
console.log(`ℹ️ wait 直通异常回退三段式:${e && e.message ? e.message : e}`)
|
|
1799
1879
|
}
|
|
1800
1880
|
}
|
|
1801
1881
|
|
|
1882
|
+
|
|
1883
|
+
/**
|
|
1884
|
+
* printAutoCompletionSummary —— auto 流程收尾总结(change: 2026-09-08-auto-driver,D-005@v1,FR-04)。
|
|
1885
|
+
* 挂点:--done 后 next==null 的自然收尾(:1726 区)+ :1616 重进入分支复用。纯 console 无新文件:
|
|
1886
|
+
* 变更名/各阶段状态/tasks 勾选(readPlanCheckboxStatus)/产物存在清单/last-delta 模块清单(缺失跳过)。
|
|
1887
|
+
*/
|
|
1888
|
+
function printAutoCompletionSummary(pm, cwd, changeName, flowStages, specBase) {
|
|
1889
|
+
try {
|
|
1890
|
+
if (!changeName) return
|
|
1891
|
+
const progress = pm.read(cwd, changeName)
|
|
1892
|
+
console.log('')
|
|
1893
|
+
console.log('═'.repeat(56))
|
|
1894
|
+
console.log(`🏁 auto 流程收尾总结 — ${changeName}`)
|
|
1895
|
+
for (const s of flowStages) {
|
|
1896
|
+
const st = progress.stages?.[s]?.status || '未开始'
|
|
1897
|
+
const icon = st === 'completed' ? '✅' : st === 'in-progress' ? '🔄' : '⬜'
|
|
1898
|
+
console.log(` ${icon} ${s}: ${st}`)
|
|
1899
|
+
}
|
|
1900
|
+
// tasks 勾选
|
|
1901
|
+
try {
|
|
1902
|
+
const changeDirCk = join((specBase || join(cwd, '.sillyspec')), 'changes', changeName)
|
|
1903
|
+
const plan = pm.readPlanCheckboxStatus(changeDirCk)
|
|
1904
|
+
if (plan && plan.total > 0) console.log(` 📋 任务勾选:${plan.checked}/${plan.total}`)
|
|
1905
|
+
} catch { /* 勾选读取 fail-soft */ }
|
|
1906
|
+
// 产物存在清单
|
|
1907
|
+
const changeDirArt = join((specBase || join(cwd, '.sillyspec')), 'changes', changeName)
|
|
1908
|
+
const artifacts = ['proposal.md', 'design.md', 'tasks.md', 'plan.md', 'verify-result.md', 'delta.md']
|
|
1909
|
+
const found = artifacts.filter(a => existsSync(join(changeDirArt, a)))
|
|
1910
|
+
if (found.length > 0) console.log(` 📁 产物:${found.join('、')}`)
|
|
1911
|
+
// last-delta 模块清单(sidecar 缺失跳过)
|
|
1912
|
+
try {
|
|
1913
|
+
const runtimeRoot = join((specBase || join(cwd, '.sillyspec')), '.runtime')
|
|
1914
|
+
const sidecarPath = join(runtimeRoot, 'last-delta.json')
|
|
1915
|
+
if (existsSync(sidecarPath)) {
|
|
1916
|
+
const sidecar = JSON.parse(readFileSync(sidecarPath, 'utf8'))
|
|
1917
|
+
if (Array.isArray(sidecar.affectedModules) && sidecar.affectedModules.length > 0) {
|
|
1918
|
+
console.log(` 🔁 涉及模块(最近 delta):${sidecar.affectedModules.join('、')}`)
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
} catch { /* sidecar 读取 fail-soft */ }
|
|
1922
|
+
console.log('═'.repeat(56))
|
|
1923
|
+
} catch (e) {
|
|
1924
|
+
console.log(`ℹ️ 收尾总结渲染降级:${e && e.message ? e.message : e}`)
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
@@ -441,16 +441,19 @@ export async function handleArchiveConfirmStep({ stageName, steps, currentIdx, c
|
|
|
441
441
|
const deltaChangeName = progress.currentChange || changeName
|
|
442
442
|
if (deltaChangeName) {
|
|
443
443
|
try {
|
|
444
|
-
const { buildDeltaReport } = await import('../archive-delta.js')
|
|
444
|
+
const { buildDeltaReport, writeLastDeltaSidecar } = await import('../archive-delta.js')
|
|
445
445
|
const deltaChangeDir = join(specBase, 'changes', deltaChangeName)
|
|
446
|
-
const
|
|
446
|
+
const deltaResult = buildDeltaReport({
|
|
447
447
|
changeDir: deltaChangeDir,
|
|
448
448
|
specRoot: specBase,
|
|
449
449
|
// project 缺参可 null(loadModuleMap null 降级路径已支持,报告内逐段降级注记)
|
|
450
450
|
project: progress?.project || null,
|
|
451
451
|
runtimeRoot: resolveRuntimeRoot(platformOpts, specBase),
|
|
452
|
+
// IR 回灌 sidecar(2026-09-07-ir-hardening D-006):与手动补跑同源数据
|
|
453
|
+
withSummary: true,
|
|
452
454
|
})
|
|
453
|
-
writeFileSync(join(deltaChangeDir, 'delta.md'),
|
|
455
|
+
writeFileSync(join(deltaChangeDir, 'delta.md'), deltaResult.markdown)
|
|
456
|
+
writeLastDeltaSidecar(resolveRuntimeRoot(platformOpts, specBase), deltaResult)
|
|
454
457
|
console.log(`📊 归档前已生成 delta.md: ${join(deltaChangeDir, 'delta.md')}`)
|
|
455
458
|
} catch (e) {
|
|
456
459
|
console.error(`⚠️ 归档前 delta.md 自动生成失败(不阻断归档): ${e.message}`)
|
|
@@ -932,10 +935,14 @@ export async function handleQuickStageCompletion({ stageName, steps, currentIdx,
|
|
|
932
935
|
// P0-2(2026-09-02 跨 agent 工单):触及 src/test 的 quick --done 内置 test+lint
|
|
933
936
|
// 实测门禁——把 CLAUDE.md 规则 8 从 agent 自律下沉为 CLI 卡点(fail → step 回
|
|
934
937
|
// pending + exit 1,重跑不丢进度;纯 doc/配置与未配置命令自动跳过不阻断)。
|
|
938
|
+
// declaredFiles 兜底(2026-09-07):倒推 B 模式(代码先行 --done 收尾)的文件全部
|
|
939
|
+
// 早于会话启动被基线吸收 → 审计 changedFiles 空 → 此前静默 skip;声明边界触及
|
|
940
|
+
// src/test 时实测仍应跑(quick-f9138c2f 实证)。
|
|
935
941
|
const gate = await runQuickTestLintGate({
|
|
936
942
|
cwd,
|
|
937
943
|
specBase,
|
|
938
944
|
changedFiles: review.changedFiles,
|
|
945
|
+
declaredFiles: Array.isArray(mergedGuard?.allowedFiles) ? mergedGuard.allowedFiles : [],
|
|
939
946
|
changeName,
|
|
940
947
|
})
|
|
941
948
|
printQuickTestLintGate(gate)
|