sillyspec 3.25.5 → 3.25.7
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-brainstorm/SKILL.md +1 -1
- package/.claude/skills/sillyspec-scan/SKILL.md +12 -7
- package/.claude/skills/sillyspec-verify/SKILL.md +2 -2
- package/package.json +1 -1
- package/src/change-list.js +100 -12
- package/src/constants.js +11 -0
- package/src/index.js +1 -1
- package/src/init.js +69 -0
- package/src/quicklog.js +41 -2
- package/src/run/command.js +57 -2
- package/src/run/complete-handlers.js +5 -1
- package/src/run/complete.js +103 -3
- package/src/run/gates.js +37 -0
- package/src/run/quick-audit.js +8 -1
- package/src/run/scan-profile.js +29 -10
- package/src/run/shared.js +175 -5
- package/src/run/stage.js +12 -2
- package/src/scan-postcheck.js +25 -11
- package/src/stage-contract.js +6 -1
- package/src/stage-review.js +11 -5
- package/src/stages/brainstorm.js +9 -14
- package/src/stages/plan-postcheck.js +1 -1
- package/src/stages/plan.js +6 -0
- package/src/stages/quick.js +31 -31
- package/src/stages/scan.js +2 -1
- package/src/stages/verify.js +6 -3
- package/src/task-review.js +159 -0
- package/src/verify-postcheck.js +148 -3
- package/src/worktree-apply.js +34 -29
- package/templates/claude-instruction.md +23 -0
- package/templates/prompts/verify-probes.md +13 -2
- package/CLAUDE.md +0 -40
package/src/verify-postcheck.js
CHANGED
|
@@ -19,6 +19,7 @@ import { execSync } from 'child_process'
|
|
|
19
19
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'
|
|
20
20
|
import { join } from 'path'
|
|
21
21
|
import { verifyApiParity } from './contract-matrix.js'
|
|
22
|
+
import { parseFileChangeListDetailed, pathMatches } from './change-list.js'
|
|
22
23
|
|
|
23
24
|
// 测试命令最长执行时间;超时视为失败(防止 CLI 被挂起的测试卡死)
|
|
24
25
|
const TEST_TIMEOUT_MS = Number(process.env.SILLYSPEC_TEST_TIMEOUT_MS) || 10 * 60 * 1000
|
|
@@ -289,9 +290,20 @@ export function judgeWithKnownFailures(exitCode, output, baseReason, knownFailur
|
|
|
289
290
|
exemptedCount: exempted.length,
|
|
290
291
|
}
|
|
291
292
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
293
|
+
// 剩余未豁免失败行:列出具体行 + 预存债指引(坑 verify-known-failures-stale-list)。
|
|
294
|
+
// known_failures 清单常滞后于新预存测试 → 同源预存债漏入清单,agent 跑完才发现要手动补。
|
|
295
|
+
// 这里点出具体未命中行,让 agent 一眼分辨"补清单 vs 修代码",不替它判断(确定性:列已算出的 remaining)。
|
|
296
|
+
let detail
|
|
297
|
+
if (remaining.length > 0) {
|
|
298
|
+
const sample = remaining.slice(0, 5).map(l => {
|
|
299
|
+
const t = String(l).trim()
|
|
300
|
+
return t.length > 120 ? t.slice(0, 120) + '…' : t
|
|
301
|
+
})
|
|
302
|
+
const more = remaining.length > 5 ? `\n …(其余 ${remaining.length - 5} 行见上方测试输出)` : ''
|
|
303
|
+
detail = `${remaining.length} 个失败行未命中 known_failures 清单:\n - ${sample.join('\n - ')}${more}\n → 若是预存债(变更前就失败),加入 local.yaml 的 known_failures 清单;若是本次变更引入的真实失败,请修复`
|
|
304
|
+
} else {
|
|
305
|
+
detail = '失败输出未检测到可豁免的失败行(保守判 fail)'
|
|
306
|
+
}
|
|
295
307
|
return {
|
|
296
308
|
status: 'failed',
|
|
297
309
|
reason: baseReason ? `${baseReason}(${detail})` : detail,
|
|
@@ -373,6 +385,24 @@ function runGitDiffNameOnly(cwd, refSpec) {
|
|
|
373
385
|
}
|
|
374
386
|
}
|
|
375
387
|
|
|
388
|
+
/**
|
|
389
|
+
* 在指定 cwd 跑 `git diff --name-status <refSpec>`,返回原始文本(调用方按行解析,
|
|
390
|
+
* 保留状态字母 D/R/C 供删除探针识别删除/重命名)。git 不可用 / 非仓库 / ref 无效 → 返回 null。
|
|
391
|
+
*/
|
|
392
|
+
function runGitDiffNameStatus(cwd, refSpec) {
|
|
393
|
+
try {
|
|
394
|
+
const cmd = refSpec ? `git diff --name-status ${refSpec}` : 'git diff --name-status'
|
|
395
|
+
return execSync(cmd, {
|
|
396
|
+
cwd,
|
|
397
|
+
encoding: 'utf8',
|
|
398
|
+
timeout: 30 * 1000,
|
|
399
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
400
|
+
})
|
|
401
|
+
} catch {
|
|
402
|
+
return null
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
376
406
|
/**
|
|
377
407
|
* 取主工作区 git 变更文件列表(unstaged + staged,相对仓库根)。
|
|
378
408
|
* `git diff --name-only HEAD` 同时覆盖已暂存与未暂存改动(相对 HEAD),
|
|
@@ -715,3 +745,118 @@ export function printVerifyParityCheck(result) {
|
|
|
715
745
|
if (result.missingBackend.length > 20) console.warn(` …还有 ${result.missingBackend.length - 20} 个`)
|
|
716
746
|
console.warn(' 提示:检查是否后端漏实现,或前端调用了尚未实现的端点。确认无误可在 design.md 标注豁免。')
|
|
717
747
|
}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* 删除探针(advisory):用 git 事实客观对账本次变更删除的文件 vs design.md 声明的操作。
|
|
751
|
+
*
|
|
752
|
+
* 切斯特顿栅栏护栏——verify 对「agent 静默删除代码」本是盲区:agent 删一段它看不懂的
|
|
753
|
+
* 旧代码,只要路径合规、不碰风险关键词,5 探针 + 风险分级 + 测试对账全都不会响。本探针
|
|
754
|
+
* 不信任 agent 自报告,用 `git diff --name-status HEAD` 客观提取删除文件,对账 design
|
|
755
|
+
* 清单声明的操作(声明「新增/修改」却整文件删除 = 高风险)。warning 不阻断 verify 完成
|
|
756
|
+
* (advisory 起步)——「检测到删除」是确定性事实(做),「该不该删」是意图(只报不拦)。
|
|
757
|
+
*
|
|
758
|
+
* 信号源:apply(git apply --3way)不 commit,verify 时主仓 HEAD 仍是变更前 commit,
|
|
759
|
+
* 删除的文件在工作树消失但仍在 HEAD → `git diff --name-status HEAD` 显示 D。
|
|
760
|
+
*
|
|
761
|
+
* @param {object} opts
|
|
762
|
+
* @param {string} opts.cwd - 项目根(主仓,代码在此)
|
|
763
|
+
* @param {string} opts.specBase - .sillyspec(或平台 specRoot)
|
|
764
|
+
* @param {string|null} [opts.changeName]
|
|
765
|
+
* @returns {{
|
|
766
|
+
* status: 'skipped'|'warning'|'passed',
|
|
767
|
+
* highRisk: Array<{path:string, declaredOp:string, reason:string}>,
|
|
768
|
+
* mediumRisk: Array<{path:string, reason:string}>,
|
|
769
|
+
* compliant: Array<{path:string, declaredOp:string}>,
|
|
770
|
+
* summary: string,
|
|
771
|
+
* reason: string|null,
|
|
772
|
+
* }}
|
|
773
|
+
*/
|
|
774
|
+
export function runVerifyDeletionCheck({ cwd, specBase, changeName = null }) {
|
|
775
|
+
const statusRaw = runGitDiffNameStatus(cwd, 'HEAD')
|
|
776
|
+
if (statusRaw === null) {
|
|
777
|
+
return { status: 'skipped', highRisk: [], mediumRisk: [], compliant: [],
|
|
778
|
+
summary: '', reason: 'git 不可用或非仓库,删除对账跳过' }
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// 解析 D(删除);R/C 的旧路径等价删除(防御 committed rename;标准 apply 流程
|
|
782
|
+
// rename 表现为纯 D,故本探针不加 -M,D 统一处理)。
|
|
783
|
+
const deletions = []
|
|
784
|
+
for (const line of statusRaw.split('\n').filter(Boolean)) {
|
|
785
|
+
const parts = line.split('\t')
|
|
786
|
+
const st = parts[0]
|
|
787
|
+
if (!st) continue
|
|
788
|
+
if (st.startsWith('D')) {
|
|
789
|
+
deletions.push({ path: parts[1], kind: 'D' })
|
|
790
|
+
} else if ((st.startsWith('R') || st.startsWith('C')) && parts.length >= 3) {
|
|
791
|
+
// rename/copy 旧路径(parts[1])等价删除,新路径 parts[2]
|
|
792
|
+
deletions.push({ path: parts[1], kind: 'R-old', renamedTo: parts[2] })
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// 排除交付物外文件(.sillyspec/ 文档 churn + meta.json),避免污染删除信号。
|
|
797
|
+
// 内联 filterDeliverableFiles 逻辑(一行,不跨模块耦合)。
|
|
798
|
+
const deliverable = deletions.filter(d =>
|
|
799
|
+
!d.path.startsWith('.sillyspec/') && d.path !== 'meta.json')
|
|
800
|
+
|
|
801
|
+
if (deliverable.length === 0) {
|
|
802
|
+
return { status: 'skipped', highRisk: [], mediumRisk: [], compliant: [],
|
|
803
|
+
summary: '', reason: '本次变更无文件删除(或改动已被 commit,主仓 HEAD 已推进,删除对账无锚点)' }
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// 读 design 声明(operation)。无清单章节 → [],所有删除归「未声明」。
|
|
807
|
+
const designPath = changeName
|
|
808
|
+
? join(specBase, 'changes', changeName, 'design.md') : null
|
|
809
|
+
const designEntries = designPath && existsSync(designPath)
|
|
810
|
+
? parseFileChangeListDetailed(designPath) : []
|
|
811
|
+
|
|
812
|
+
const highRisk = []
|
|
813
|
+
const mediumRisk = []
|
|
814
|
+
const compliant = []
|
|
815
|
+
for (const d of deliverable) {
|
|
816
|
+
const hit = designEntries.find(e => pathMatches(d.path, e.path))
|
|
817
|
+
if (!hit) {
|
|
818
|
+
mediumRisk.push({ path: d.path, reason: d.kind === 'R-old'
|
|
819
|
+
? `重命名源文件未在 design 清单(→ ${d.renamedTo})`
|
|
820
|
+
: 'design 清单未列出该删除文件' })
|
|
821
|
+
continue
|
|
822
|
+
}
|
|
823
|
+
if (hit.operation === '新增' || hit.operation === '修改') {
|
|
824
|
+
highRisk.push({ path: d.path, declaredOp: hit.operation,
|
|
825
|
+
reason: `design 声明「${hit.operation}」但 git 显示整文件删除` })
|
|
826
|
+
} else if (hit.operation === '删除') {
|
|
827
|
+
compliant.push({ path: d.path, declaredOp: '删除' })
|
|
828
|
+
} else {
|
|
829
|
+
// operation=null(未声明操作)或 重命名:删除发生但声明不明确
|
|
830
|
+
mediumRisk.push({ path: d.path,
|
|
831
|
+
reason: `design 列出但操作为「${hit.operation || '未声明'}」,与删除不一致` })
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
if (highRisk.length > 0 || mediumRisk.length > 0) {
|
|
836
|
+
return { status: 'warning', highRisk, mediumRisk, compliant,
|
|
837
|
+
summary: `${highRisk.length} 个高风险删除 + ${mediumRisk.length} 个未声明删除`, reason: null }
|
|
838
|
+
}
|
|
839
|
+
return { status: 'passed', highRisk: [], mediumRisk: [], compliant,
|
|
840
|
+
summary: `所有 ${compliant.length} 个删除均在 design 声明为「删除」`, reason: null }
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
/** 打印删除对账结果(advisory,不阻断 verify 完成) */
|
|
844
|
+
export function printVerifyDeletionCheck(result) {
|
|
845
|
+
if (result.status === 'skipped') return // 静默:无删除 / git 不可用 / 改动已 commit
|
|
846
|
+
if (result.status === 'passed') {
|
|
847
|
+
console.log(`\n✅ 删除对账通过:${result.summary}`)
|
|
848
|
+
return
|
|
849
|
+
}
|
|
850
|
+
// warning
|
|
851
|
+
if (result.highRisk.length > 0) {
|
|
852
|
+
console.warn(`\n⚠️ 删除对账发现 ${result.highRisk.length} 个高风险删除(design 声明新增/修改却被整文件删除,advisory 不阻断):`)
|
|
853
|
+
for (const m of result.highRisk.slice(0, 20)) console.warn(` - ${m.path} (${m.reason})`)
|
|
854
|
+
if (result.highRisk.length > 20) console.warn(` …还有 ${result.highRisk.length - 20} 个`)
|
|
855
|
+
}
|
|
856
|
+
if (result.mediumRisk.length > 0) {
|
|
857
|
+
console.warn(`⚠️ ${result.mediumRisk.length} 个未声明删除(design 清单未列出 / 操作不一致):`)
|
|
858
|
+
for (const m of result.mediumRisk.slice(0, 20)) console.warn(` - ${m.path} (${m.reason})`)
|
|
859
|
+
if (result.mediumRisk.length > 20) console.warn(` …还有 ${result.mediumRisk.length - 20} 个`)
|
|
860
|
+
}
|
|
861
|
+
console.warn(' 提示:确认删除是否预期。预期删除请在 design.md 清单用「删除」操作显式声明;误删请恢复。')
|
|
862
|
+
}
|
package/src/worktree-apply.js
CHANGED
|
@@ -18,7 +18,8 @@ import { join, resolve } from 'path';
|
|
|
18
18
|
import { tmpdir } from 'os';
|
|
19
19
|
import { createHash } from 'crypto';
|
|
20
20
|
import { WorktreeManager } from './worktree.js';
|
|
21
|
-
import { parseFileChangeList, pathMatches } from './change-list.js';
|
|
21
|
+
import { parseFileChangeList, parseFileChangeListDetailed, pathMatches } from './change-list.js';
|
|
22
|
+
import { parseAllowedPaths } from './stages/plan-postcheck.js';
|
|
22
23
|
|
|
23
24
|
const CHANGES_REL = '.sillyspec/changes';
|
|
24
25
|
|
|
@@ -190,7 +191,9 @@ export function applyWorktree(changeName, { cwd, checkOnly = false, merge = fals
|
|
|
190
191
|
result.errors.push(
|
|
191
192
|
`文件清单校验失败:以下变更文件不在 design.md 清单中:\n ${violations.join('\n ')}`
|
|
192
193
|
);
|
|
193
|
-
|
|
194
|
+
// checkOnly(assess)模式不短路:继续跑 Gate3,收集所有道供一次报全(坑 worktree-execute-apply-friction 坑4)。
|
|
195
|
+
// 真实 apply(checkOnly=false)仍短路,保安全。
|
|
196
|
+
if (!checkOnly) return result;
|
|
194
197
|
}
|
|
195
198
|
}
|
|
196
199
|
|
|
@@ -228,7 +231,7 @@ export function applyWorktree(changeName, { cwd, checkOnly = false, merge = fals
|
|
|
228
231
|
`execute 前 baseline: ${meta.baselineHash}\n` +
|
|
229
232
|
`当前 baseline: ${currentHash}`
|
|
230
233
|
);
|
|
231
|
-
return result;
|
|
234
|
+
if (!checkOnly) return result; // checkOnly 收集不短路(一次报全);真实 apply 短路
|
|
232
235
|
}
|
|
233
236
|
}
|
|
234
237
|
|
|
@@ -243,7 +246,7 @@ export function applyWorktree(changeName, { cwd, checkOnly = false, merge = fals
|
|
|
243
246
|
result.errors.push(
|
|
244
247
|
`主工作区有以下未 commit 的变更,会影响 apply:\n ${conflictDirty.join('\n ')}\n请先 commit 或 stash 这些变更。`
|
|
245
248
|
);
|
|
246
|
-
return result;
|
|
249
|
+
if (!checkOnly) return result; // checkOnly 收集不短路(一次报全);真实 apply 短路
|
|
247
250
|
}
|
|
248
251
|
}
|
|
249
252
|
|
|
@@ -518,19 +521,18 @@ export function assessApplyRisk(changeName, { cwd } = {}) {
|
|
|
518
521
|
// 先跑 --check-only 模式的 applyWorktree 获取变更文件列表
|
|
519
522
|
const checkResult = applyWorktree(changeName, { cwd: projectRoot, checkOnly: true });
|
|
520
523
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
reasons: checkResult.errors,
|
|
526
|
-
warnings: [],
|
|
527
|
-
stats: { additions: 0, deletions: 0 }
|
|
528
|
-
};
|
|
529
|
-
}
|
|
524
|
+
// applyWorktree(checkOnly) 已收集所有道(Gate1/3 不短路)——其 errors 纳入 reasons,
|
|
525
|
+
// 继续跑 Gate2/4/6 一次报全(坑 worktree-execute-apply-friction 坑4:原此处提前 return 致逐道挤牙膏)。
|
|
526
|
+
reasons.push(...checkResult.errors);
|
|
527
|
+
if (checkResult.warnings?.length) warnings.push(...checkResult.warnings);
|
|
530
528
|
|
|
531
529
|
const changedFiles = checkResult.changedFiles;
|
|
532
530
|
|
|
533
531
|
if (changedFiles.length === 0) {
|
|
532
|
+
// 无变更:若仍有 Gate 错误(如主区 dirty)则 BLOCKED,否则 SAFE
|
|
533
|
+
if (reasons.length > 0) {
|
|
534
|
+
return { decision: 'BLOCKED', changedFiles: [], reasons, warnings, stats: { additions: 0, deletions: 0 } };
|
|
535
|
+
}
|
|
534
536
|
return {
|
|
535
537
|
decision: 'SAFE',
|
|
536
538
|
changedFiles: [],
|
|
@@ -540,36 +542,39 @@ export function assessApplyRisk(changeName, { cwd } = {}) {
|
|
|
540
542
|
};
|
|
541
543
|
}
|
|
542
544
|
|
|
543
|
-
// 解析 TaskCard allowed_paths
|
|
545
|
+
// 解析 TaskCard allowed_paths(复用 plan-postcheck.parseAllowedPaths,消除内联重复实现漂移)
|
|
544
546
|
const wm = new WorktreeManager({ cwd: projectRoot });
|
|
545
547
|
const meta = wm.getMeta(changeName);
|
|
546
548
|
const tasksDir = join(projectRoot, CHANGES_REL, changeName, 'tasks');
|
|
547
549
|
const allowedPaths = new Set();
|
|
548
550
|
if (existsSync(tasksDir)) {
|
|
549
551
|
for (const tf of readdirSync(tasksDir).filter(f => /^task-\d+\.md$/.test(f))) {
|
|
550
|
-
const
|
|
551
|
-
|
|
552
|
-
if (!fmMatch) continue;
|
|
553
|
-
const fm = fmMatch[1];
|
|
554
|
-
const inline = fm.match(/allowed_paths:\s*\[([^\]]*)\]/);
|
|
555
|
-
if (inline) {
|
|
556
|
-
inline[1].split(',').forEach(s => { const v = s.trim().replace(/['"]/g, ''); if (v) allowedPaths.add(v); });
|
|
557
|
-
}
|
|
558
|
-
const block = fm.match(/allowed_paths:\s*\n((?:\s+-\s+.+\n?)+)/);
|
|
559
|
-
if (block) {
|
|
560
|
-
block[1].match(/-\s+(.+)/g)?.forEach(s => { const v = s.replace(/^-\s+/, '').trim().replace(/['"]/g, ''); if (v) allowedPaths.add(v); });
|
|
552
|
+
for (const p of parseAllowedPaths(readFileSync(join(tasksDir, tf), 'utf8'))) {
|
|
553
|
+
if (p) allowedPaths.add(p);
|
|
561
554
|
}
|
|
562
555
|
}
|
|
563
556
|
}
|
|
564
557
|
|
|
565
|
-
//
|
|
558
|
+
// design §6 标记为「顺带修复」的文件(坑 worktree-execute-apply-friction 坑1):合规修预存债,
|
|
559
|
+
// 不属任何 task 边界,assess 豁免 allowed_paths 严格校验(降级 warning),避免被迫 cherry-pick 绕过。
|
|
560
|
+
const designPath = join(projectRoot, CHANGES_REL, changeName, 'design.md');
|
|
561
|
+
const incidentalSet = new Set(
|
|
562
|
+
parseFileChangeListDetailed(designPath).filter(e => e.incidental).map(e => e.path)
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
// 检查 2: 变更在 allowed_paths 内(仅在 TaskCard 存在时);顺带修复文件豁免。
|
|
566
|
+
// 匹配换 pathMatches(与 Gate1/plan-postcheck 同语义容差),消除原字面前缀弱匹配漂移。
|
|
566
567
|
if (allowedPaths.size > 0) {
|
|
567
|
-
const
|
|
568
|
-
|
|
569
|
-
);
|
|
568
|
+
const isIncidental = f => [...incidentalSet].some(ap => pathMatches(f, ap));
|
|
569
|
+
const outsideAll = changedFiles.filter(f => ![...allowedPaths].some(allowed => pathMatches(f, allowed)));
|
|
570
|
+
const outsidePaths = outsideAll.filter(f => !isIncidental(f));
|
|
571
|
+
const exempted = outsideAll.filter(f => isIncidental(f));
|
|
570
572
|
if (outsidePaths.length > 0) {
|
|
571
573
|
reasons.push(`变更文件超出 allowed_paths:\n ${outsidePaths.join('\n ')}`);
|
|
572
574
|
}
|
|
575
|
+
if (exempted.length > 0) {
|
|
576
|
+
warnings.push(`顺带修复文件(已豁免 allowed_paths,来源 design §6 标记):${exempted.join(', ')}`);
|
|
577
|
+
}
|
|
573
578
|
}
|
|
574
579
|
|
|
575
580
|
// 检查 4+5: 高风险文件模式
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Claude Code 指引
|
|
2
|
+
|
|
3
|
+
## 项目说明
|
|
4
|
+
本项目使用 **SillySpec** 管理变更,采用文档驱动开发。SillySpec 是给 Agent 调用的 CLI 流程控制器,不是给人类直接使用的产品,也不是处理业务逻辑的工具——你(Agent)通过 CLI 告诉它"我在哪",它告诉你"下一步做什么";你执行步骤,它校验产出、推进状态,人类只在关键决策点介入审批。要考虑多 agent 同时操作代码,代码随时可能变化。所有变更以稳定、可用、可维护为目标,按生产级标准处理。
|
|
5
|
+
|
|
6
|
+
## 核心规则
|
|
7
|
+
1. **禁止绕过本文件规则和 SillySpec 流程**。所有变更走 sillyspec 流程,不裸改裸提交。
|
|
8
|
+
2. **改代码前必须先说明依据**——依据的文档路径(design.md / 模块文档 / file-lifecycle.md)或现有代码依据,无依据不改。
|
|
9
|
+
3. **新功能 / 大改动走完整流程**:`brainstorm → plan → execute → verify → archive`。
|
|
10
|
+
4. **小修复 / 小调整走 quick**:`sillyspec run quick`。
|
|
11
|
+
5. **执行顺序**:文档 → 读代码 → 写测试 → 写实现 → 跑测试 → 验收 → 更新文档。
|
|
12
|
+
6. **判规模选档**:≤3 文件、范围明确走 `quick`;多阶段 / 架构级走完整流程。
|
|
13
|
+
7. **代码先行不补流程(倒推 B 模式)**:代码若已先写好,**不回头补 brainstorm/plan 装样子**——用 `quick --done` 收尾 + 补 quicklog 条目,把已落盘改动如实登记进进度库。
|
|
14
|
+
8. **实证核验再 `--done`**:`quick --done` 前先 `npm test` + `npm run lint`,以落盘文件与测试结果为准,不信口头"已完成"。
|
|
15
|
+
9. **中途停下用 `sillyspec status` / `sillyspec resume` 存进度**,不直接 commit 半成品。
|
|
16
|
+
10. **实现完成后对照文档验收**(design.md / 模块文档),并检查是否影响已有测试。
|
|
17
|
+
11. **非测试逻辑本身有误时,禁止改测试来"通过"**——修逻辑,不修测试。
|
|
18
|
+
12. **hook 拦截提交时禁止跳过**(`.husky/pre-push`),修复问题后再提交。
|
|
19
|
+
13. **代码必须兼容 Windows / Linux / macOS**(路径 / 换行 / 并发都要顾)。
|
|
20
|
+
14. **任务记录隔离**:永不重置 / reset / 清零已存在的 change;多个活跃 change 各自 `--change <名>` 隔离不重叠;quick 同一 QUICKLOG 按 ql-ID 条目追加,不冲突。
|
|
21
|
+
15. **quicklog 手动精修**:CLI 只写骨架,`--done` 后手动补语义化标题 / 文件多行带括注 / 结果四段。
|
|
22
|
+
16. **代码可能随时在修改**(多 agent 并行),Edit 前重跑 + 查最新态;破坏性 git op 前先备份。
|
|
23
|
+
17. **不奉承用户**,禁止"你说得对"类话术,直接给结论、依据、方案。
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
### 自动探针(必须先执行)
|
|
2
|
-
|
|
2
|
+
在检查前,依次运行以下六个探针,将结果作为验证输入:
|
|
3
3
|
|
|
4
4
|
**探针 1:未实现标记扫描(仅变更文件)**
|
|
5
5
|
只扫描本次变更涉及的文件,不要全项目扫描——历史 TODO 与本次变更无关,徒增噪音与 token。变更文件 = design.md「文件变更清单」列出的源码文件(你已在「加载规范」步骤读取 design.md,glob 路径需展开为具体文件):
|
|
@@ -18,7 +18,7 @@ grep -rl "<关键词>" <源码目录>/ --include="*.java" --include="*.js" --inc
|
|
|
18
18
|
|
|
19
19
|
**探针 3:验收标准测试覆盖探针**
|
|
20
20
|
1. 读取变更目录下的 tasks.md,提取所有 checkbox 任务
|
|
21
|
-
2. 对每个 task
|
|
21
|
+
2. 对每个 task,**递归**检查对应模块目录及其**所有子目录**下是否存在测试文件(*test*、*spec*、*Test*、*Spec*)——包括模块内 co-located 的 tests/ 目录(如 `backend/app/modules/*/tests/`),**不要只看顶层 `tests/`**,否则会把测试就在模块内部的项目误判为“无测试”
|
|
22
22
|
3. 没有测试文件的 task 标记为 ⚠️ 缺少测试
|
|
23
23
|
|
|
24
24
|
**探针 4:决策追踪覆盖探针(如存在 decisions.md)**
|
|
@@ -51,3 +51,14 @@ grep -rl "<关键词>" <源码目录>/ --include="*.java" --include="*.js" --inc
|
|
|
51
51
|
| ❌ missing | GET /api/ppm/project-plan/{param}/plan-nodes | — | frontend/src/lib/ppm/plan.ts |
|
|
52
52
|
|
|
53
53
|
如果发现 Missing backend endpoint,必须在验证报告中标记为 ❌ contract gap。
|
|
54
|
+
|
|
55
|
+
**探针 6:代码删除对账(切斯特顿栅栏护栏)**
|
|
56
|
+
静默删除代码是 verify 的盲区——agent 删一段它看不懂的旧代码,只要路径合规、不碰风险关键词,其他探针都不会响。用 git 事实客观对账,不要凭记忆:
|
|
57
|
+
1. 运行 `git diff --name-status HEAD`(本次变更已 apply 到主仓、未 commit,删除的文件在工作树消失但仍在 HEAD,显示 `D`)
|
|
58
|
+
2. 筛出状态以 `D` 开头的行(删除);`R`/`C` 开头的旧路径等价删除
|
|
59
|
+
3. 对每个删除文件,核对 design.md「文件变更清单」声明的操作:
|
|
60
|
+
- design 声明「删除」→ ✅ 合规(预期删除)
|
|
61
|
+
- design 声明「新增/修改」却整文件删除 → ❌ 高风险(声明与事实矛盾)
|
|
62
|
+
- design 清单未列出 → ⚠️ 未声明删除
|
|
63
|
+
4. 排除 `.sillyspec/` 与 `meta.json`(文档 churn 不算删除信号)
|
|
64
|
+
5. verify --done 时 CLI 会用同一 git 事实独立复核,谎报无效——务必如实记录
|
package/CLAUDE.md
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# Claude Code 指引
|
|
2
|
-
|
|
3
|
-
# SillySpec工具定位
|
|
4
|
-
1. 是给Agent调用的CLI工具,非人类直接使用
|
|
5
|
-
2. 管理Agent工作流的CLI工具,非业务工具
|
|
6
|
-
|
|
7
|
-
## 文件生命周期文档同步
|
|
8
|
-
每次修改 `src/stages/` 下的阶段定义(prompt、步骤、输出文件名等)或 `src/run.js`、`src/progress.js` 等影响文件生命周期的代码后,**必须同步更新** `docs/sillyspec/file-lifecycle.md`,确保文档与代码一致。
|
|
9
|
-
|
|
10
|
-
### 触发更新的典型改动
|
|
11
|
-
- 新增/删除/重命名阶段步骤
|
|
12
|
-
- 修改步骤 prompt 中的输出文件名(如 verify-result.md)
|
|
13
|
-
- 修改阶段间的流转逻辑(如 archive 归档方式)
|
|
14
|
-
- 新增/删除运行时文件类型(如 gate-status.json)
|
|
15
|
-
- 修改 ProgressManager 的数据存储方式(如 SQLite 表结构变更)
|
|
16
|
-
|
|
17
|
-
### 更新检查清单
|
|
18
|
-
- [ ] 文件名引用一致(prompt 输出的文件名 == validateFileLocations 期望的文件名)
|
|
19
|
-
- [ ] 阶段步骤描述与 `src/stages/*.js` 一致
|
|
20
|
-
- [ ] 归档/清理流程描述与实际代码逻辑一致
|
|
21
|
-
- [ ] 数据库 Schema 描述与 `src/db.js` 一致
|
|
22
|
-
- [ ] 更新文档头部 `updated_at` 时间戳
|
|
23
|
-
|
|
24
|
-
## 提示词文档同步(docs/prompt/)
|
|
25
|
-
`docs/prompt/` 收录各阶段 CLI→Agent 的**逐步提示词原文**(人类可读 md 参考)。详见 `docs/prompt/README.md`。
|
|
26
|
-
|
|
27
|
-
### 数据源关系(重要 — 避免双写漂移)
|
|
28
|
-
- **唯一数据源 = `src/stages/*.js`**(`definition.steps[].prompt`,CLI 运行时读这里)+ `src/run/prompt.js`(注入框架:persona / 铁律 / 占位符替换 / 完成后执行命令模板)。
|
|
29
|
-
- `docs/prompt/*.md` 是源码的**机械提取人类可读镜像**,由 `node docs/prompt/_extract.mjs` 一键再生(源码 → `_extracted.json` → md)。提取脚本对静态阶段直接读 `definition.steps`,对动态阶段(plan/execute)用示例输入跑 `buildPlanSteps`/`buildExecuteSteps` 得到真实 prompt。
|
|
30
|
-
- **禁止手改 md 里的 prompt 原文**(下次提取会被覆盖,且与源码不一致)。要改提示词,改源码后重跑脚本。
|
|
31
|
-
|
|
32
|
-
### 触发同步的改动
|
|
33
|
-
- 修改 `src/stages/*.js`:`definition.steps[].prompt`、步骤名、步骤增删/重排、wait 配置、`_globalGuardrails`、title/description
|
|
34
|
-
- 修改 `src/run/prompt.js`:persona / 铁律 / 占位符替换 / 完成后执行命令模板
|
|
35
|
-
- 修改 `stage-review.js` / `review-tier.js` / `knowledge-match.js`:影响 `{REVIEW_JSON_CONTRACT}` / `{REVIEW_TIER}` / `{KNOWLEDGE_HIT_REPORT}` 等动态块
|
|
36
|
-
|
|
37
|
-
### 同步步骤
|
|
38
|
-
1. 改源码
|
|
39
|
-
2. 运行 `node docs/prompt/_extract.mjs`(刷新 `docs/prompt/_extracted.json`)
|
|
40
|
-
3. 按变动同步对应 `docs/prompt/<stage>.md`(prompt 正文以 `_extracted.json` 为准逐字替换)+ 必要时更新 `docs/prompt/README.md`(占位符总表 / persona 表 / CLI 注入框架)
|