sillyspec 3.32.10 → 3.33.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-brainstorm/SKILL.md +114 -114
- package/.claude/skills/sillyspec-execute/SKILL.md +222 -222
- package/.claude/skills/sillyspec-explore/SKILL.md +107 -107
- package/.claude/skills/sillyspec-flow/SKILL.md +1 -1
- package/.claude/skills/sillyspec-plan/SKILL.md +156 -156
- package/.github/workflows/ci.yml +66 -0
- package/.husky/pre-push +7 -6
- package/AGENTS.md +1 -1
- package/assets/command-cards/flow.md +1 -1
- package/eval/eval.config.json +3 -3
- package/eval/lib/config.mjs +6 -3
- package/eval/lib/util.mjs +16 -3
- package/eval/lib/workdir.mjs +4 -1
- package/eval/results/eval.db +0 -0
- package/eval/results/logs/2026-10-15T12-37-22-986Z-004-phase-stats-armA/prompt.txt +10 -0
- package/eval/results/logs/2026-10-15T12-37-22-986Z-004-phase-stats-armA/stderr.txt +0 -0
- package/eval/results/logs/2026-10-15T12-37-22-986Z-004-phase-stats-armA/stdout.txt +1 -0
- package/eval/results/logs/2026-10-15T12-45-37-124Z-004-phase-stats-armB/prompt.txt +10 -0
- package/eval/results/logs/2026-10-15T12-45-37-124Z-004-phase-stats-armB/stderr.txt +0 -0
- package/eval/results/logs/2026-10-15T12-45-37-124Z-004-phase-stats-armB/stdout.txt +1 -0
- package/eval/results/logs/2026-10-15T12-56-19-259Z-004-phase-stats-armA/prompt.txt +10 -0
- package/eval/results/logs/2026-10-15T12-56-19-259Z-004-phase-stats-armA/stderr.txt +0 -0
- package/eval/results/logs/2026-10-15T12-56-19-259Z-004-phase-stats-armA/stdout.txt +1 -0
- package/eval/results/logs/2026-10-15T13-00-05-625Z-004-phase-stats-armB/prompt.txt +10 -0
- package/eval/results/logs/2026-10-15T13-04-51-003Z-004-phase-stats-armA/prompt.txt +10 -0
- package/eval/results/logs/2026-10-15T13-04-51-003Z-004-phase-stats-armA/stderr.txt +0 -0
- package/eval/results/logs/2026-10-15T13-04-51-003Z-004-phase-stats-armA/stdout.txt +1 -0
- package/eval/results/logs/2026-10-15T13-06-25-271Z-004-phase-stats-armB/prompt.txt +10 -0
- package/eval/results/logs/2026-10-15T13-06-25-271Z-004-phase-stats-armB/stderr.txt +0 -0
- package/eval/results/logs/2026-10-15T13-06-25-271Z-004-phase-stats-armB/stdout.txt +1 -0
- package/eval/results/logs/2026-10-15T13-24-38-308Z-004-phase-stats-armB/prompt.txt +10 -0
- package/eval/results/logs/2026-10-15T13-24-38-308Z-004-phase-stats-armB/stderr.txt +0 -0
- package/eval/results/logs/2026-10-15T13-24-38-308Z-004-phase-stats-armB/stdout.txt +1 -0
- package/eval/results/logs/2026-10-15T14-50-01-950Z-005-orders-export-armA/prompt.txt +8 -0
- package/eval/results/logs/2026-10-15T14-50-01-950Z-005-orders-export-armA/stderr.txt +0 -0
- package/eval/results/logs/2026-10-15T14-50-01-950Z-005-orders-export-armA/stdout.txt +1 -0
- package/eval/results/logs/2026-10-15T14-51-56-316Z-005-orders-export-armB/prompt.txt +8 -0
- package/eval/results/logs/2026-10-15T14-51-56-316Z-005-orders-export-armB/stderr.txt +0 -0
- package/eval/results/logs/2026-10-15T14-51-56-316Z-005-orders-export-armB/stdout.txt +1 -0
- package/eval/results/workdirs/001-slugify__armA/package.json +6 -0
- package/eval/results/workdirs/001-slugify__armA/src/slugify.mjs +26 -0
- package/eval/results/workdirs/001-slugify__armB/package.json +6 -0
- package/eval/results/workdirs/001-slugify__armB/src/slugify.mjs +26 -0
- package/eval/results/workdirs/002-each-day__armA/package.json +6 -0
- package/eval/results/workdirs/002-each-day__armA/src/dates.mjs +30 -0
- package/eval/results/workdirs/002-each-day__armB/DRY_RUN_WRONG_ANSWER.mjs +1 -0
- package/eval/results/workdirs/002-each-day__armB/package.json +6 -0
- package/eval/results/workdirs/002-each-day__armB/src/dates.mjs +4 -0
- package/eval/results/workdirs/003-word-report__armA/bin/report.mjs +36 -0
- package/eval/results/workdirs/003-word-report__armA/data/notes.txt +5 -0
- package/eval/results/workdirs/003-word-report__armA/package.json +6 -0
- package/eval/results/workdirs/003-word-report__armB/bin/report.mjs +36 -0
- package/eval/results/workdirs/003-word-report__armB/data/notes.txt +5 -0
- package/eval/results/workdirs/003-word-report__armB/package.json +6 -0
- package/eval/results/workdirs/004-phase-stats__armA/data/events.log +16 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/flow.md +44 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/run-archive.md +27 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/run-brainstorm.md +34 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/run-execute.md +30 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/run-plan.md +28 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/run-verify.md +30 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/status.md +28 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-archive/SKILL.md +87 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-auto/SKILL.md +54 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-commit/SKILL.md +67 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-continue/SKILL.md +17 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-doctor/SKILL.md +76 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-execute/SKILL.md +222 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-explore/SKILL.md +107 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-export/SKILL.md +57 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-flow/SKILL.md +62 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-init/SKILL.md +177 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-knowledge/SKILL.md +40 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-plan/SKILL.md +156 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-propose/SKILL.md +16 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-quick/SKILL.md +116 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-resume/SKILL.md +21 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-scan/SKILL.md +79 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-state/SKILL.md +25 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-status/SKILL.md +54 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-verify/SKILL.md +87 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-workspace/SKILL.md +33 -0
- package/eval/results/workdirs/004-phase-stats__armB/AGENTS.md +45 -0
- package/eval/results/workdirs/004-phase-stats__armB/CLAUDE.md +3 -0
- package/eval/results/workdirs/004-phase-stats__armB/data/events.log +16 -0
- package/eval/results/workdirs/run-001-slugify__armA/package.json +6 -0
- package/eval/results/workdirs/run-001-slugify__armA/src/slugify.mjs +26 -0
- package/eval/results/workdirs/run-001-slugify__armB/package.json +6 -0
- package/eval/results/workdirs/run-001-slugify__armB/src/slugify.mjs +26 -0
- package/eval/results/workdirs/run-002-each-day__armA/package.json +6 -0
- package/eval/results/workdirs/run-002-each-day__armA/src/dates.mjs +30 -0
- package/eval/results/workdirs/run-002-each-day__armB/DRY_RUN_WRONG_ANSWER.mjs +1 -0
- package/eval/results/workdirs/run-002-each-day__armB/package.json +6 -0
- package/eval/results/workdirs/run-002-each-day__armB/src/dates.mjs +4 -0
- package/eval/results/workdirs/run-003-word-report__armA/bin/report.mjs +36 -0
- package/eval/results/workdirs/run-003-word-report__armA/data/notes.txt +5 -0
- package/eval/results/workdirs/run-003-word-report__armA/package.json +6 -0
- package/eval/results/workdirs/run-003-word-report__armB/bin/report.mjs +36 -0
- package/eval/results/workdirs/run-003-word-report__armB/data/notes.txt +5 -0
- package/eval/results/workdirs/run-003-word-report__armB/package.json +6 -0
- package/eval/results/workdirs/run-004-phase-stats__armA/bin/phase-stats.mjs +37 -0
- package/eval/results/workdirs/run-004-phase-stats__armA/data/events.log +16 -0
- package/eval/results/workdirs/run-004-phase-stats__armB/DRY_RUN_WRONG_ANSWER.mjs +1 -0
- package/eval/results/workdirs/run-004-phase-stats__armB/data/events.log +16 -0
- package/eval/results/workdirs/run-005-orders-export__armA/bin/export-orders.mjs +57 -0
- package/eval/results/workdirs/run-005-orders-export__armA/data/.gitattributes +1 -0
- package/eval/results/workdirs/run-005-orders-export__armA/data/orders.csv +9 -0
- package/eval/results/workdirs/run-005-orders-export__armA/out/summary.json +1 -0
- package/eval/results/workdirs/run-005-orders-export__armB/bin/export-orders.mjs +57 -0
- package/eval/results/workdirs/run-005-orders-export__armB/data/.gitattributes +1 -0
- package/eval/results/workdirs/run-005-orders-export__armB/data/orders.csv +9 -0
- package/eval/results/workdirs/run-005-orders-export__armB/out/summary.json +1 -0
- package/eval/tasks/004-phase-stats/files/data/events.log +16 -0
- package/eval/tasks/004-phase-stats/solution/bin/phase-stats.mjs +37 -0
- package/eval/tasks/004-phase-stats/task.json +6 -0
- package/eval/tasks/004-phase-stats/verify.mjs +38 -0
- package/eval/tasks/005-orders-export/files/data/.gitattributes +1 -0
- package/eval/tasks/005-orders-export/files/data/orders.csv +9 -0
- package/eval/tasks/005-orders-export/solution/bin/export-orders.mjs +57 -0
- package/eval/tasks/005-orders-export/task.json +6 -0
- package/eval/tasks/005-orders-export/verify.mjs +28 -0
- package/package.json +1 -1
- package/quicklog/tmp-fix-review.mjs +21 -0
- package/src/archive-delta.js +4 -3
- package/src/ceremony-tier.js +2 -1
- package/src/change-delete.js +6 -5
- package/src/contract-matrix.js +2 -1
- package/src/cross-placement.js +5 -1
- package/src/db.js +8 -2
- package/src/decision-distill.js +29 -3
- package/src/decision-redomain.js +148 -0
- package/src/deleted-ledger.js +2 -1
- package/src/design-facts.js +3 -3
- package/src/docs-debt.js +3 -3
- package/src/doctor-diagnostics.js +8 -3
- package/src/flow-draft.js +2 -1
- package/src/flow-parity.js +10 -2
- package/src/flow.js +112 -63
- package/src/forensics.js +236 -0
- package/src/fr-index.js +2 -1
- package/src/friction-ledger.js +2 -1
- package/src/friction-tally.js +8 -4
- package/src/handoff.js +2 -1
- package/src/hooks/worktree-guard.js +5 -5
- package/src/index.js +75 -8
- package/src/knowledge-graph.js +3 -4
- package/src/knowledge-hits.js +2 -1
- package/src/knowledge-vector.js +2 -1
- package/src/module-impact.js +2 -5
- package/src/modules.js +31 -0
- package/src/progress/consistency-doctor.js +5 -2
- package/src/progress/stage-machine.js +6 -2
- package/src/progress.js +19 -8
- package/src/quicklog.js +194 -9
- package/src/review-dispatch.js +4 -3
- package/src/review-tier.js +2 -1
- package/src/route-hindsight.js +4 -3
- package/src/run/bg-sync.js +10 -3
- package/src/run/command.js +13 -11
- package/src/run/complete-handlers.js +40 -37
- package/src/run/complete.js +12 -8
- package/src/run/gate-snapshot-ledger.js +2 -1
- package/src/run/gate-snapshot.js +57 -11
- package/src/run/gates.js +38 -21
- package/src/run/lint-ownership.js +37 -0
- package/src/run/prompt.js +46 -30
- package/src/run/scan-profile.js +2 -1
- package/src/run/shared.js +109 -5
- package/src/run/stage.js +5 -4
- package/src/run/test-ledger.js +2 -1
- package/src/run/verify-quality-scan.js +21 -6
- package/src/runtime-paths.js +189 -0
- package/src/scan-refresh.js +4 -3
- package/src/scope-audit.js +7 -6
- package/src/sentinel-assertions.js +37 -0
- package/src/spec-sync.js +118 -23
- package/src/stage-contract.js +24 -2
- package/src/stage-review.js +5 -3
- package/src/stages/brainstorm.js +72 -21
- package/src/stages/plan-postcheck.js +13 -2
- package/src/stages/plan.js +19 -0
- package/src/sync-noise.js +3 -2
- package/src/sync.js +71 -15
- package/src/task-done.js +2 -1
- package/src/task-review.js +7 -5
- package/src/task-tick.js +23 -1
- package/src/test-bindings.js +32 -3
- package/src/ui-visual.js +119 -4
- package/src/validate-artifacts.js +2 -1
- package/src/verify-draft.js +2 -1
- package/src/verify-postcheck.js +128 -27
- package/src/verify-probes.js +157 -52
- package/src/watcher.js +15 -9
- package/src/worktree-apply.js +28 -3
- package/src/write-audit.js +2 -1
- package/templates/agents-instruction.md +1 -1
package/src/friction-tally.js
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
* 不打印任何输出,提示何时打印由调用方决定。
|
|
25
25
|
*/
|
|
26
26
|
import { existsSync, mkdirSync, readFileSync, rmSync } from 'fs'
|
|
27
|
+
import { runtimeStatePath } from './runtime-paths.js'
|
|
27
28
|
import { dirname, join } from 'path'
|
|
28
29
|
import { writeAtomicSync } from './fs-atomic.js'
|
|
29
30
|
import { withFileLock } from './quicklog.js'
|
|
@@ -76,7 +77,7 @@ function frictionTallyPath({ cwd, platformOpts, changeName }) {
|
|
|
76
77
|
if (QUICK_SESSION_ID_RE.test(changeName)) {
|
|
77
78
|
return join(resolveQuickSessionsDir(platformOpts, specBase), changeName, 'friction-tally.json')
|
|
78
79
|
}
|
|
79
|
-
return
|
|
80
|
+
return runtimeStatePath(resolveRuntimeRoot(platformOpts, specBase), `friction-tally-${changeName}.json`)
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
/** YAML 布尔值宽松解析:仅显式 false(含引号形态)为关,其余值一律默认开(R-05) */
|
|
@@ -174,10 +175,12 @@ export function renderFrictionHintLine(counts) {
|
|
|
174
175
|
export async function recordFrictionEvent({ cwd, platformOpts, changeName, type, detail }) {
|
|
175
176
|
try {
|
|
176
177
|
if (!FRICTION_TYPES.has(type)) return null
|
|
178
|
+
// enabled=false 直通:record/consume 双 no-op,.runtime 零写入(D-003 一键全关)。
|
|
179
|
+
// 必须先于 frictionTallyPath(2026-10-18 起路径助手带 mkdir-p 目录保证,关闭态
|
|
180
|
+
// 构造路径即建 state/ 目录=破坏零写入契约)
|
|
181
|
+
if (!readFrictionHintEnabled(deriveSpecBase(cwd, platformOpts))) return null
|
|
177
182
|
const tallyPath = frictionTallyPath({ cwd, platformOpts, changeName })
|
|
178
183
|
if (!tallyPath) return null
|
|
179
|
-
// enabled=false 直通:record/consume 双 no-op,.runtime 零写入(D-003 一键全关)
|
|
180
|
-
if (!readFrictionHintEnabled(deriveSpecBase(cwd, platformOpts))) return null
|
|
181
184
|
return await withFileLock(tallyPath + ".lock", async () => {
|
|
182
185
|
const tally = readFrictionTally(tallyPath)
|
|
183
186
|
const at = new Date().toISOString()
|
|
@@ -207,9 +210,10 @@ export async function recordFrictionEvent({ cwd, platformOpts, changeName, type,
|
|
|
207
210
|
*/
|
|
208
211
|
export async function consumeFrictionHint({ cwd, platformOpts, changeName }) {
|
|
209
212
|
try {
|
|
213
|
+
// enabled 检查先于路径构造(同 record:关闭态零写入,路径助手 mkdir-p 不触发)
|
|
214
|
+
if (!readFrictionHintEnabled(deriveSpecBase(cwd, platformOpts))) return { hint: null, counts: {} }
|
|
210
215
|
const tallyPath = frictionTallyPath({ cwd, platformOpts, changeName })
|
|
211
216
|
if (!tallyPath || !existsSync(tallyPath)) return { hint: null, counts: {} }
|
|
212
|
-
if (!readFrictionHintEnabled(deriveSpecBase(cwd, platformOpts))) return { hint: null, counts: {} }
|
|
213
217
|
return await withFileLock(tallyPath + ".lock", async () => {
|
|
214
218
|
const tally = readFrictionTally(tallyPath)
|
|
215
219
|
const counts = {}
|
package/src/handoff.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* 整块 80 行帽(HANDOFF_LINE_CAP),超限从上下文段尾截,粘贴块与警示行永不动。
|
|
16
16
|
*/
|
|
17
17
|
import { ProgressManager, resolveSpecDir } from './progress.js';
|
|
18
|
+
import { runtimeStatePath } from './runtime-paths.js'
|
|
18
19
|
import { MAIN_FLOW_ORDER, STAGE_LABELS } from './progress/shared.js';
|
|
19
20
|
import { readFileSync, existsSync } from 'node:fs';
|
|
20
21
|
import { join } from 'node:path';
|
|
@@ -199,7 +200,7 @@ export async function buildHandoff({ cwd, specBase, changeName } = {}) {
|
|
|
199
200
|
// 保持不变——SESSION_ID 相同不能当硬续证据)。本命令执行即切割:best-effort 给
|
|
200
201
|
// stage-session ledger 盖 handoffAt 章,墙据此放行新会话的 execute/verify 首入。
|
|
201
202
|
try {
|
|
202
|
-
const ledgerPath = join(specRoot, '.runtime', `stage-session-ledger-${target}.json`);
|
|
203
|
+
const ledgerPath = runtimeStatePath(join(specRoot, '.runtime'), `stage-session-ledger-${target}.json`);
|
|
203
204
|
let ledger = null;
|
|
204
205
|
try { ledger = JSON.parse(readFileSync(ledgerPath, 'utf8')); } catch { /* 无账本(阶段未收口)→ 无墙可放行,零动作 */ }
|
|
205
206
|
if (ledger && typeof ledger === 'object') {
|
|
@@ -61,7 +61,7 @@ const STAGE_HINTS = {
|
|
|
61
61
|
'你需要先启动一个任务流程才能修改源码(调用对应的 sillyspec skill):',
|
|
62
62
|
'',
|
|
63
63
|
' 轻量变更/BUG修复(默认快道,skill sillyspec-flow)——--input 引号内换行合法,下例可照抄:',
|
|
64
|
-
' sillyspec flow start --change <YYYY-MM-DD-名> --input "
|
|
64
|
+
' sillyspec flow start --change <YYYY-MM-DD-名> --input "<一句 10-30 字中文概括(首行即变更标题,硬区间 10-30 必须、建议 ~20 字);随后动机与背景',
|
|
65
65
|
'',
|
|
66
66
|
' 成功标准:',
|
|
67
67
|
' - <可验证标准>"',
|
|
@@ -80,7 +80,7 @@ const STAGE_HINTS = {
|
|
|
80
80
|
'verify': [
|
|
81
81
|
'当前在 verify(验证)阶段,只做代码审查和测试验证,不修改源码。',
|
|
82
82
|
'如需修改,请先回到 execute 阶段,或收口后开轻量变更(--input 引号内换行合法,可照抄):',
|
|
83
|
-
' sillyspec flow start --change <YYYY-MM-DD-名> --input "
|
|
83
|
+
' sillyspec flow start --change <YYYY-MM-DD-名> --input "<一句 10-30 字中文概括(首行即变更标题,硬区间 10-30 必须、建议 ~20 字);随后动机与背景',
|
|
84
84
|
'',
|
|
85
85
|
' 成功标准:',
|
|
86
86
|
' - <可验证标准>"',
|
|
@@ -88,7 +88,7 @@ const STAGE_HINTS = {
|
|
|
88
88
|
'archive': [
|
|
89
89
|
'当前在 archive(归档)阶段,不修改源码。',
|
|
90
90
|
'如需修改,请开启新变更(--input 引号内换行合法,可照抄):',
|
|
91
|
-
' sillyspec flow start --change <YYYY-MM-DD-名> --input "
|
|
91
|
+
' sillyspec flow start --change <YYYY-MM-DD-名> --input "<一句 10-30 字中文概括(首行即变更标题,硬区间 10-30 必须、建议 ~20 字);随后动机与背景',
|
|
92
92
|
'',
|
|
93
93
|
' 成功标准:',
|
|
94
94
|
' - <可验证标准>"',
|
|
@@ -190,8 +190,8 @@ function getScanDocInfo(filePath) {
|
|
|
190
190
|
|
|
191
191
|
function readScanGuard(scanDocInfo, projectRoot) {
|
|
192
192
|
const candidates = [
|
|
193
|
-
path.join(scanDocInfo.specRoot, '.runtime', 'scan-guard.json'),
|
|
194
|
-
path.join(projectRoot, '.sillyspec', '.runtime', 'scan-guard.json'),
|
|
193
|
+
path.join(scanDocInfo.specRoot, '.runtime', 'state', 'scan-guard.json'),
|
|
194
|
+
path.join(projectRoot, '.sillyspec', '.runtime', 'state', 'scan-guard.json'),
|
|
195
195
|
]
|
|
196
196
|
for (const p of candidates) {
|
|
197
197
|
if (!existsSync(p)) continue
|
package/src/index.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* 状态管理通过 sillyspec.db(SQLite)完成,使用 `sillyspec progress` 命令。
|
|
8
8
|
*/
|
|
9
9
|
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, writeFileSync, unlinkSync } from 'fs';
|
|
10
|
+
import { runtimeStatePath } from './runtime-paths.js'
|
|
10
11
|
import { writeAtomicSync } from './fs-atomic.js';
|
|
11
12
|
import { basename, dirname, extname, join, resolve, isAbsolute, sep, relative } from 'path';
|
|
12
13
|
import { safeGit, git, gitQuiet } from './git-helper.js';
|
|
@@ -100,6 +101,8 @@ SillySpec CLI — 规范驱动开发工具包
|
|
|
100
101
|
变更合成时间线(事件流 × tasks.md × git 提交锚三源真实留痕渲染;活跃/归档变更均可)
|
|
101
102
|
sillyspec dispatch <probe | hint> SillyHub 派发能力探测 + 策略生成(agent 调用桥,仅渲染不执行 tool)
|
|
102
103
|
sillyspec agent-log [--detect] [--json] 本地 agent 会话日志查询/现场探测(run 命令自动上报平台 POST /api/agent-logs 并本地留底)
|
|
104
|
+
sillyspec diagnose --change <名> [--detect] [--json]
|
|
105
|
+
会话取证:只读装配证据包(flow 态/会话日志/摩擦账本/verify-runs/评审轮次)+ 取证任务书(path:line 铁律;报告由执行任务书的 agent 产出)
|
|
103
106
|
|
|
104
107
|
sillyspec workflow check <name> [--project <p>] [--change <c>] [--json] [--save]
|
|
105
108
|
全局 workflow 校验(--save 归档到 .sillyspec/.runtime/workflow-runs/)
|
|
@@ -171,7 +174,7 @@ SillySpec CLI — 规范驱动开发工具包
|
|
|
171
174
|
示例:
|
|
172
175
|
sillyspec init
|
|
173
176
|
sillyspec run brainstorm --change 2026-07-03-add-login
|
|
174
|
-
sillyspec flow start --change 2026-07-03-my-fix --input "
|
|
177
|
+
sillyspec flow start --change 2026-07-03-my-fix --input "<一句 10-30 字中文概括(首行即变更标题,硬区间 10-30 必须、建议 ~20 字)+动机与背景详述+独立一行『成功标准:』+每行一条『- 可验证标准』>" # 轻量变更(默认快道,2 次调用;变更名须 YYYY-MM-DD-<简短描述> 日期前缀)
|
|
175
178
|
sillyspec run verify --done --output "验证通过,测试全绿"
|
|
176
179
|
sillyspec run archive --done --confirm --output "归档完成"
|
|
177
180
|
sillyspec run plan --reopen --from-step 2 # 修订 plan,从第 2 步重做
|
|
@@ -226,7 +229,7 @@ function refuseRetiredQuickFreshStart(stageArgs) {
|
|
|
226
229
|
console.error('❌ quick 通道已退役,不再接受新会话(v3.30.0 起)。')
|
|
227
230
|
// 可照抄实例(2026-10-05-input-teach-copyable):引号内换行合法,行级 trim 容忍缩进
|
|
228
231
|
console.error(' 新工作请走轻量变更(--input 引号内换行合法,下例可照抄):')
|
|
229
|
-
console.error(' sillyspec flow start --change <YYYY-MM-DD-名> --input "
|
|
232
|
+
console.error(' sillyspec flow start --change <YYYY-MM-DD-名> --input "<一句 10-30 字中文概括(首行即变更标题,硬区间 10-30 必须、建议 ~20 字);随后动机与背景')
|
|
230
233
|
console.error('')
|
|
231
234
|
console.error(' 成功标准:')
|
|
232
235
|
console.error(' - <可验证标准>"')
|
|
@@ -321,6 +324,26 @@ async function main() {
|
|
|
321
324
|
process.exit(2);
|
|
322
325
|
}
|
|
323
326
|
|
|
327
|
+
// ── 旧布局迁移(2026-10-18 runtime-relayout):per-change 散文件入 state/、可删产物入 temp/。
|
|
328
|
+
// 每次入口跑一遍(幂等:已收敛后 readdir 一轮零写入);fail-open 绝不阻断命令。
|
|
329
|
+
// init 除外——目标仓尚无 .sillyspec 可迁。resolveEffectiveDir 是函数声明(提升后可调)。
|
|
330
|
+
if (filteredArgs[0] !== 'init') {
|
|
331
|
+
try {
|
|
332
|
+
const { migrateRuntimeLayout } = await import('./runtime-paths.js')
|
|
333
|
+
const roots = new Set()
|
|
334
|
+
const direct = join(specDir || join(targetDir, '.sillyspec'), '.runtime')
|
|
335
|
+
roots.add(direct)
|
|
336
|
+
if (!existsSync(direct)) {
|
|
337
|
+
try { roots.add(join(resolveEffectiveDir(targetDir), '.sillyspec', '.runtime')) } catch { /* cwd 无仓 = 无可迁 */ }
|
|
338
|
+
}
|
|
339
|
+
try {
|
|
340
|
+
const po = resolvePlatformOpts(targetDir)
|
|
341
|
+
if (po && po.runtimeRoot) roots.add(po.runtimeRoot)
|
|
342
|
+
} catch { /* 平台指针不可达 = 无平台侧可迁 */ }
|
|
343
|
+
for (const rt of roots) migrateRuntimeLayout(rt)
|
|
344
|
+
} catch { /* 迁移失败不阻断任何命令 */ }
|
|
345
|
+
}
|
|
346
|
+
|
|
324
347
|
const command = filteredArgs[0];
|
|
325
348
|
// 支持 sillyspec init /path/to/project 语法:如果第二个参数看起来像路径,当作 targetDir
|
|
326
349
|
if (command === 'init' && filteredArgs[1] && !filteredArgs[1].startsWith('-')) {
|
|
@@ -3390,11 +3413,34 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
3390
3413
|
// --by-change(坑 fr-domain-suggest-typo-and-no-split-migration 缺陷2):unmapped 等
|
|
3391
3414
|
// 混合池按「变更:<名>」字段分批迁移——整域迁移会错置大部分条目,来源变更才是分批键。
|
|
3392
3415
|
const byChangeD = flag('--by-change')
|
|
3393
|
-
if (!fromD || !toD) { fail('用法: sillyspec tests --redomain --from <源域> --to <目标域> [--anchor <FR-id>] [--by-change <变更名>] [--write]\n
|
|
3416
|
+
if (!fromD || !toD) { fail('用法: sillyspec tests --redomain --from <源域> --to <目标域> [--anchor <FR-id>] [--by-change <变更名>] [--decisions] [--write]\n --decisions 切 decisions/ 面(默认 fr/ 面);域名可带前缀与 .md 后缀(fr/auto-backend ≍ auto-backend.md ≍ auto-backend)'); break }
|
|
3394
3417
|
// 多 --anchor 收集
|
|
3395
3418
|
const anchorList = []
|
|
3396
3419
|
for (let i = 0; i < filteredArgs.length - 1; i++) if (filteredArgs[i] === '--anchor') anchorList.push(filteredArgs[i + 1])
|
|
3420
|
+
// --decisions(2026-10-15-decision-redomain):切 decisions/ 面的孪生迁移实现——
|
|
3421
|
+
// 同 flag 族(--from/--to/--anchor/--by-change/--write),段字节整体搬运、迁域不换号、
|
|
3422
|
+
// 跨变更同号不冲突(双键防);平台仓 unmapped 442 条存量清偿的机器通道。
|
|
3423
|
+
const useDecisions = has('--decisions')
|
|
3397
3424
|
try {
|
|
3425
|
+
if (useDecisions) {
|
|
3426
|
+
const { planDecisionRedomain, redomainDecisionEntries } = await import('./decision-redomain.js')
|
|
3427
|
+
if (!has('--write')) {
|
|
3428
|
+
const plan = planDecisionRedomain({ knowledgeRoot, from: fromD, to: toD, anchors: anchorList.length ? anchorList : null, byChange: byChangeD || null })
|
|
3429
|
+
console.log(`🔧 决策域迁移预览(--write 落盘):decisions/${fromD}.md → decisions/${toD}.md,${plan.entries.length} 条${byChangeD ? `(仅「变更:${byChangeD}」)` : ''}`)
|
|
3430
|
+
for (const e of plan.entries.slice(0, 10)) console.log(` ${e.id} ${e.title}`)
|
|
3431
|
+
if (plan.entries.length > 10) console.log(` … 共 ${plan.entries.length} 条`)
|
|
3432
|
+
console.log(` 目标域文件${plan.targetExists ? '在场' : '将新建'};源域${plan.sourceWillDelete ? '将删空壳' : '保留余条'};ID 不变(身份保持)`)
|
|
3433
|
+
break
|
|
3434
|
+
}
|
|
3435
|
+
const r = redomainDecisionEntries({ knowledgeRoot, from: fromD, to: toD, anchors: anchorList.length ? anchorList : null, byChange: byChangeD || null })
|
|
3436
|
+
console.log(`✅ 决策域迁移完成:${r.moved.length} 条 decisions/${fromD}.md → decisions/${toD}.md(ID 不变${byChangeD ? `,仅「变更:${byChangeD}」` : ''})`)
|
|
3437
|
+
for (const m of r.moved.slice(0, 10)) console.log(` ${m.id} ${m.title}`)
|
|
3438
|
+
if (r.skippedConflicts.length > 0) console.log(` ⚠️ 目标域同 id 同变更段冲突跳过 ${r.skippedConflicts.length} 条(${r.skippedConflicts.slice(0, 5).join('、')})`)
|
|
3439
|
+
if (r.sourceDeleted) console.log(` 源域空壳已删:decisions/${fromD}.md`)
|
|
3440
|
+
if (r.targetCreated) console.log(` 目标域文件新建:decisions/${toD}.md`)
|
|
3441
|
+
if (r.indexLineAdded) console.log(` INDEX 路由行已补:${toD}`)
|
|
3442
|
+
break
|
|
3443
|
+
}
|
|
3398
3444
|
if (!has('--write')) {
|
|
3399
3445
|
// 预览与落盘同口径透传 byChange(坑 redomain-plan-preview-by-change-ignored,
|
|
3400
3446
|
// 2026-10-02 实证:76338b4d 收编在途工作时此行漏透传——预览列整域、落盘只迁
|
|
@@ -4107,6 +4153,14 @@ SillySpec worktree — git worktree 隔离管理
|
|
|
4107
4153
|
await cmdAgentLog(filteredArgs.slice(1), { json, cwd: dir, specDir });
|
|
4108
4154
|
break;
|
|
4109
4155
|
}
|
|
4156
|
+
case 'diagnose': {
|
|
4157
|
+
// 会话取证(2026-10-15-session-forensics):只读装配证据包六面(flow 态/会话日志/摩擦账本/
|
|
4158
|
+
// verify-runs/评审轮次/任务书)+ 取证任务书(path:line 铁律,唯一可写产物 diagnosis-report.md
|
|
4159
|
+
// 由执行任务书的 agent 产出)。零状态写入,不进 run 状态机——诊断是随时可调用的查询动作。
|
|
4160
|
+
const { cmdForensics } = await import('./forensics.js');
|
|
4161
|
+
await cmdForensics(filteredArgs.slice(1), { json, cwd: dir, specDir });
|
|
4162
|
+
break;
|
|
4163
|
+
}
|
|
4110
4164
|
case 'patch-reanchor': {
|
|
4111
4165
|
// 收口留痕锚点回写(2026-10-12-close-trace-freeze-integrity FR-03):worktree 内收口的
|
|
4112
4166
|
// change-patch.json 记 worktree 本地 SHA,apply 重放回主仓后悬空——按提交主题归属推导
|
|
@@ -5338,10 +5392,10 @@ SillySpec config — local.yaml 配置键速查
|
|
|
5338
5392
|
'sillyspec.db': '进度库 SQLite(stages/steps/changes 表)',
|
|
5339
5393
|
'sillyspec.db.bak': '进度库备份(sql.js 时代写前快照遗留,node:sqlite 不再写入,仅 _openWithFallback 向后兼容恢复用)',
|
|
5340
5394
|
'sillyspec.db.schema-version': '进度库 schema 版本标记',
|
|
5341
|
-
'doctor-diagnosis.json': 'doctor --json
|
|
5395
|
+
'doctor-diagnosis.json': 'doctor --json 结构化诊断快照(可再生,落 temp/ 子目录)',
|
|
5342
5396
|
'doctor-dumps': 'doctor --dump-db 取证输出目录',
|
|
5343
5397
|
'workflow-runs': 'workflow check --save 归档目录',
|
|
5344
|
-
'scan-guard.json': 'scan
|
|
5398
|
+
'scan-guard.json': 'scan 覆盖保护记录(2026-10-18 起落 state/ 子目录)',
|
|
5345
5399
|
'quick-sessions': 'quick 会话记录目录',
|
|
5346
5400
|
'quick-guard.json': 'quick baseline 守卫(旧位置)',
|
|
5347
5401
|
'worktrees': 'worktree meta(hooks 侧)',
|
|
@@ -5355,6 +5409,8 @@ SillySpec config — local.yaml 配置键速查
|
|
|
5355
5409
|
'history': '历史记录目录(init 预建)',
|
|
5356
5410
|
'logs': '日志目录(init 预建)',
|
|
5357
5411
|
'templates': '工作流模板目录(init 预建)',
|
|
5412
|
+
'state': '按 change 命名的派生状态目录(watcher 事件流/账本/冲突标记等,归档时随变更精确回收)',
|
|
5413
|
+
'temp': '可随时整体删除的临时产物(日志/db 备份/锁残留 tmp——删后自动重建,无状态语义)',
|
|
5358
5414
|
};
|
|
5359
5415
|
let entries;
|
|
5360
5416
|
try {
|
|
@@ -5396,7 +5452,7 @@ SillySpec config — local.yaml 配置键速查
|
|
|
5396
5452
|
SillySpec pull — 拉取服务器 spec 快照到本地(X2 / FR-07)
|
|
5397
5453
|
|
|
5398
5454
|
用法:
|
|
5399
|
-
sillyspec pull --spec [--force]
|
|
5455
|
+
sillyspec pull --spec [--force] [--restore-deleted]
|
|
5400
5456
|
|
|
5401
5457
|
说明:
|
|
5402
5458
|
从 SillyHub 平台拉取当前工作区的 .sillyspec 整树快照(tar 解压到 specDir,
|
|
@@ -5411,6 +5467,10 @@ SillySpec pull — 拉取服务器 spec 快照到本地(X2 / FR-07)
|
|
|
5411
5467
|
本命令不自动同步、不在会话中自动刷新;拉取后本地的改动仍需正常推送。
|
|
5412
5468
|
daemon 模式下任务/会话开始时会按 spec_version 自动取新,无需手动跑本命令。
|
|
5413
5469
|
|
|
5470
|
+
防复活抑制(默认开启):本地已删除、但删除尚未同步到服务器的文件(基线快照有、
|
|
5471
|
+
盘面无),不会从快照写回本地(防「同步失败窗口内删除的任务卡被服务器旧文件复活」);
|
|
5472
|
+
确要从快照恢复这类文件用 --restore-deleted(纯快照回放语义)。
|
|
5473
|
+
|
|
5414
5474
|
拉取进度六表(多机进度同步)请用:sillyspec platform pull [--change <name>]
|
|
5415
5475
|
`);
|
|
5416
5476
|
if (pullWantsHelp || !pullWantsSpec) {
|
|
@@ -5423,6 +5483,10 @@ SillySpec pull — 拉取服务器 spec 快照到本地(X2 / FR-07)
|
|
|
5423
5483
|
}
|
|
5424
5484
|
|
|
5425
5485
|
const pullSpecForce = pullSpecArgs.includes('--force');
|
|
5486
|
+
// 防复活抑制(坑 spec-tree-file-resurrect)的显式恢复通道:默认把「基线有、盘面无」的
|
|
5487
|
+
// 本地已删文件挡在快照回放外(删除 op 未上行成功时不被服务器旧文件复活);
|
|
5488
|
+
// --restore-deleted = 用户显式意志优先,恢复纯快照回放语义。
|
|
5489
|
+
const pullSpecRestoreDeleted = pullSpecArgs.includes('--restore-deleted');
|
|
5426
5490
|
const syncModule = await import('./sync.js');
|
|
5427
5491
|
// 未连接明确提示不崩(对齐 platform pull :2289 先例)
|
|
5428
5492
|
const smForSpecPull = new syncModule.SyncManager(dir);
|
|
@@ -5430,10 +5494,13 @@ SillySpec pull — 拉取服务器 spec 快照到本地(X2 / FR-07)
|
|
|
5430
5494
|
console.error('❌ 未连接平台,请先 sillyspec platform connect');
|
|
5431
5495
|
process.exit(1);
|
|
5432
5496
|
}
|
|
5433
|
-
const specR = await syncModule.pullSpecBundle(dir, { force: pullSpecForce, specDir });
|
|
5497
|
+
const specR = await syncModule.pullSpecBundle(dir, { force: pullSpecForce, specDir, restoreDeleted: pullSpecRestoreDeleted });
|
|
5434
5498
|
if (specR.ok && specR.pulled) {
|
|
5435
5499
|
console.log(`✅ 已拉取 spec 快照 → ${specR.specDir}${specR.specVersion ? `(平台 spec_version: ${specR.specVersion})` : ''}`);
|
|
5436
5500
|
console.log(' 快照语义:打包时刻快照非实时,无自动同步;本地后续改动仍需正常推送。');
|
|
5501
|
+
if (Array.isArray(specR.resurrectSkipped) && specR.resurrectSkipped.length > 0) {
|
|
5502
|
+
console.log(' 防复活抑制已生效:本地已删除文件未从快照恢复(基线有、盘面无=本地删除尚未上行成功,抑制防被服务器旧文件复活);确要从快照恢复用 --restore-deleted。');
|
|
5503
|
+
}
|
|
5437
5504
|
} else if (/非空/.test(specR.reason || '')) {
|
|
5438
5505
|
console.error(`❌ ${specR.reason}`);
|
|
5439
5506
|
console.error(' 确认整树替换请加 --force(先删除本地 .sillyspec 整树再解包,进度 DB 等本地文件一并丢失;连接凭据 local.yaml 保留,无需重新 connect)');
|
|
@@ -5496,7 +5563,7 @@ SillySpec pull — 拉取服务器 spec 快照到本地(X2 / FR-07)
|
|
|
5496
5563
|
}
|
|
5497
5564
|
if (!tRes.exists) {
|
|
5498
5565
|
console.error(`📭 ${tChange} 无事件流(未跑过 watcher),无从合成时间线`);
|
|
5499
|
-
console.error(` 事件流路径:${
|
|
5566
|
+
console.error(` 事件流路径:${runtimeStatePath(tRuntimeRoot, `watcher-events-${tChange}.jsonl`)};活跃变更跑任意 flow/run 命令会拉起 watcher`);
|
|
5500
5567
|
process.exit(2);
|
|
5501
5568
|
}
|
|
5502
5569
|
const { changeDir, tasksMd, tickLog } = loadChangeTasks(tSpecBase, tChange);
|
package/src/knowledge-graph.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
import { existsSync, readFileSync, readdirSync } from 'fs'
|
|
22
22
|
import { join } from 'path'
|
|
23
23
|
import { parseKnowledgeIndex, parseDecisionEntries, anchorFilePaths } from './knowledge-match.js'
|
|
24
|
-
import { parseModuleMapSimple } from './modules.js'
|
|
24
|
+
import { modulePathMatches, parseModuleMapSimple } from './modules.js'
|
|
25
25
|
|
|
26
26
|
// ── 本体常量(design.md 总体方案 §1 的代码化) ──
|
|
27
27
|
|
|
@@ -273,9 +273,8 @@ export function buildKnowledgeGraph(specRoot) {
|
|
|
273
273
|
const moduleOf = (p) => {
|
|
274
274
|
const v = posix(p)
|
|
275
275
|
for (const [mid, mp] of modulePathIndex) {
|
|
276
|
-
// 模块 paths
|
|
277
|
-
|
|
278
|
-
if (v === stem || v.startsWith(stem + '/')) return mid
|
|
276
|
+
// 模块 paths 目录/glob 语义——modulePathMatches 统一入口(**/*.tsx 等扫描卡形态可命中)
|
|
277
|
+
if (modulePathMatches(v, mp)) return mid
|
|
279
278
|
}
|
|
280
279
|
return null
|
|
281
280
|
}
|
package/src/knowledge-hits.js
CHANGED
|
@@ -12,12 +12,13 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'fs'
|
|
15
|
+
import { runtimeStatePath } from './runtime-paths.js'
|
|
15
16
|
import { join } from 'path'
|
|
16
17
|
|
|
17
18
|
const HITS_FILENAME = 'knowledge-hits.jsonl'
|
|
18
19
|
|
|
19
20
|
function hitsFilePath(runtimeDir) {
|
|
20
|
-
return
|
|
21
|
+
return runtimeStatePath(runtimeDir, HITS_FILENAME)
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
/**
|
package/src/knowledge-vector.js
CHANGED
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
* 404 另有进程级死信短路(端点死信不再空转探测)。
|
|
36
36
|
*/
|
|
37
37
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'
|
|
38
|
+
import { runtimeStatePath } from './runtime-paths.js'
|
|
38
39
|
import { join, dirname } from 'path'
|
|
39
40
|
import { readPlatformConfig } from './sync.js'
|
|
40
41
|
import { matchByRouting, fallbackByQueryShingles, parseDecisionEntries } from './knowledge-match.js'
|
|
@@ -49,7 +50,7 @@ const CAPABILITY_RETRY_MS = 60 * 60 * 1000
|
|
|
49
50
|
// 机器态的标准落点(knowledge-hits 同款)。写盘全 fail-soft;按平台 URL 匹配(换平台即失效)。
|
|
50
51
|
|
|
51
52
|
function capabilityCachePath(cwd) {
|
|
52
|
-
return join(cwd, '.sillyspec', '.runtime', 'vector-capability.json')
|
|
53
|
+
return runtimeStatePath(join(cwd, '.sillyspec', '.runtime'), 'vector-capability.json')
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
function readCapabilityCache(cwd) {
|
package/src/module-impact.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* 归属分类同一套 module-map 前缀匹配,仅输入来源不同。
|
|
15
15
|
*/
|
|
16
16
|
import { existsSync, readFileSync, readdirSync, writeFileSync } from 'fs'
|
|
17
|
+
import { modulePathMatches } from './modules.js'
|
|
17
18
|
import { join } from 'path'
|
|
18
19
|
import { resolveVerifyChangedFiles } from './verify-postcheck.js'
|
|
19
20
|
import { resolveSpecDir } from './run/shared.js'
|
|
@@ -87,11 +88,7 @@ function classifyFile(posixPath, modulePaths) {
|
|
|
87
88
|
for (const [moduleId, paths] of modulePaths) {
|
|
88
89
|
for (const p of paths) {
|
|
89
90
|
// 目录条目以 / 结尾 → 前缀匹配;文件条目 → 精确或子路径(与模块上下文索引读侧口径一致)
|
|
90
|
-
if (p
|
|
91
|
-
if (posixPath.startsWith(p)) return moduleId
|
|
92
|
-
} else if (posixPath === p || posixPath.startsWith(p + '/')) {
|
|
93
|
-
return moduleId
|
|
94
|
-
}
|
|
91
|
+
if (modulePathMatches(posixPath, p)) return moduleId
|
|
95
92
|
}
|
|
96
93
|
}
|
|
97
94
|
return null
|
package/src/modules.js
CHANGED
|
@@ -2,6 +2,37 @@ import { existsSync, readdirSync, readFileSync, writeFileSync } from 'fs';
|
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { gitQuiet } from './git-helper.js';
|
|
4
4
|
import { nowWallClock } from './datetime.js';
|
|
5
|
+
import { globMatch } from './change-list.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 模块 paths 条目 × 文件路径 的统一归属判定(2026-10-15-module-path-glob-match)。
|
|
9
|
+
*
|
|
10
|
+
* 背景:sillyspec-scan 生成的 module-map(消费仓)大量使用 glob 形态 paths——
|
|
11
|
+
* `app/modules/daemon/**`、`frontend/src/components/daemon/*.tsx`、多段通配与文件通配的
|
|
12
|
+
* 组合(`**` 与 `*.tsx` 同串)、括号路径等(平台仓实测 146 条)——而历史各消费点只做朴素
|
|
13
|
+
* `file === p || startsWith(p + '/')`,glob 条目恒不命中(恰好只有精确文件路径能命中),
|
|
14
|
+
* 导致消费仓全链路域路由失效:交付文件匹配不上模块 → 三级兜底全灭 → FR 铸伪域
|
|
15
|
+
* (auto-backend 等)、决策落 unmapped。
|
|
16
|
+
*
|
|
17
|
+
* 语义(本仓无 glob map 时与旧各点逐字等价——零回归):
|
|
18
|
+
* - 尾部 `/` 的目录条目:startsWith(旧 module-impact 目录分支语义)
|
|
19
|
+
* - 其余条目:相等 或 `p/` 前缀(旧 fr-index/ceremony-tier 等语义)
|
|
20
|
+
* - 含 `*`/`?` 的 glob 条目:追加 globMatch(双星跨零或多段、单星段内、单问号单字符;
|
|
21
|
+
* change-list.js 既有完备实现单一源复用)
|
|
22
|
+
* @param {string} filePath 仓根相对文件路径(含反斜杠容忍)
|
|
23
|
+
* @param {string} rawPattern module-map 的 paths/core_files 条目
|
|
24
|
+
* @returns {boolean}
|
|
25
|
+
*/
|
|
26
|
+
export function modulePathMatches(filePath, rawPattern) {
|
|
27
|
+
const f = String(filePath || '').replace(/\\/g, '/');
|
|
28
|
+
const raw = String(rawPattern || '').replace(/\\/g, '/');
|
|
29
|
+
if (!f || !raw) return false;
|
|
30
|
+
if (raw.endsWith('/')) return f.startsWith(raw);
|
|
31
|
+
const p = raw.replace(/\/+$/, '');
|
|
32
|
+
if (!p) return false;
|
|
33
|
+
if (f === p || f.startsWith(p + '/')) return true;
|
|
34
|
+
return globMatch(f, p);
|
|
35
|
+
}
|
|
5
36
|
|
|
6
37
|
|
|
7
38
|
/**
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// W6 Step9a: Revision v1 状态一致性检查 + 修复 + --force 审计日志。
|
|
2
2
|
// 从 src/progress.js 单体 ProgressManager 抽出。持有 pm 引用(构造注入),调 pm.read / pm._write /
|
|
3
3
|
// pm._ensureRuntimeDir / pm._runtimePath(persistence-core 留 facade 本体)。
|
|
4
|
-
import { appendFileSync, existsSync, readFileSync, readdirSync } from 'fs';
|
|
4
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync } from 'fs';
|
|
5
|
+
import { dirname } from 'path';
|
|
6
|
+
import { RUNTIME_TEMP_DIRNAME } from '../runtime-paths.js'
|
|
5
7
|
import { join, resolve } from 'path';
|
|
6
8
|
import { DB } from '../db.js';
|
|
7
9
|
import { openDatabase } from '../db-engine.js';
|
|
@@ -122,8 +124,9 @@ export class ConsistencyDoctor {
|
|
|
122
124
|
_appendAuditLog(cwd, entry) {
|
|
123
125
|
try {
|
|
124
126
|
this.pm._ensureRuntimeDir(cwd);
|
|
125
|
-
const auditPath = this.pm._runtimePath(cwd, 'audit.log');
|
|
127
|
+
const auditPath = this.pm._runtimePath(cwd, RUNTIME_TEMP_DIRNAME, 'audit.log');
|
|
126
128
|
const line = JSON.stringify({ at: new Date().toISOString(), ...entry });
|
|
129
|
+
try { mkdirSync(dirname(auditPath), { recursive: true }); } catch { /* fail-open */ }
|
|
127
130
|
appendFileSync(auditPath, line + '\n');
|
|
128
131
|
} catch (e) {
|
|
129
132
|
console.warn(`⚠️ 审计日志写入失败: ${e.message}`);
|
|
@@ -361,8 +361,12 @@ export class StageMachine {
|
|
|
361
361
|
/** 未决同步冲突列表(overview 数据源;与 sync.js listConflictFiles 同前缀同字段,fs-only) */
|
|
362
362
|
_listPendingConflicts(specDir) {
|
|
363
363
|
const runtimeDir = join(specDir, '.runtime');
|
|
364
|
+
// 2026-10-18 布局分层:冲突文件落 state/;根目录扫描保留旧布局兜底
|
|
364
365
|
let files = [];
|
|
365
|
-
|
|
366
|
+
for (const dir of [join(runtimeDir, 'state'), runtimeDir]) {
|
|
367
|
+
try { files.push(...readdirSync(dir)); } catch { /* 可选层缺失跳过 */ }
|
|
368
|
+
}
|
|
369
|
+
const fileDir = (f) => (existsSync(join(runtimeDir, 'state', f)) ? join(runtimeDir, 'state') : runtimeDir)
|
|
366
370
|
const conflicts = [];
|
|
367
371
|
for (const f of files) {
|
|
368
372
|
const isProgress = f.startsWith('sync-conflict-');
|
|
@@ -371,7 +375,7 @@ export class StageMachine {
|
|
|
371
375
|
let createdAt = null;
|
|
372
376
|
let kind = null;
|
|
373
377
|
try {
|
|
374
|
-
const cf = JSON.parse(readFileSync(join(
|
|
378
|
+
const cf = JSON.parse(readFileSync(join(fileDir(f), f), 'utf8'));
|
|
375
379
|
change = cf.change || change;
|
|
376
380
|
createdAt = cf.created_at || null;
|
|
377
381
|
kind = typeof cf.kind === 'string' ? cf.kind : null;
|
package/src/progress.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync, unlinkSync, appendFileSync, copyFileSync, readdirSync, statSync, realpathSync } from 'fs';
|
|
14
|
+
import { RUNTIME_TEMP_DIRNAME, runtimeTempPath } from './runtime-paths.js'
|
|
14
15
|
import { join, basename, dirname, resolve, sep } from 'path';
|
|
15
16
|
import { tmpdir, hostname } from 'os';
|
|
16
17
|
import { writeAtomicSync } from './fs-atomic.js';
|
|
@@ -122,7 +123,7 @@ export function resolveSessionIdentity({ flagSession = null, quickChangeName = n
|
|
|
122
123
|
try {
|
|
123
124
|
const markerDir = join(cwd, SPEC_DIR_NAME, RUNTIME_SUBDIR);
|
|
124
125
|
mkdirSync(markerDir, { recursive: true });
|
|
125
|
-
const markerPath =
|
|
126
|
+
const markerPath = runtimeTempPath(markerDir, ANON_SESSION_WARN_MARKER);
|
|
126
127
|
const now = Date.now();
|
|
127
128
|
try {
|
|
128
129
|
const m = JSON.parse(readFileSync(markerPath, 'utf8'));
|
|
@@ -348,7 +349,7 @@ export class ProgressManager {
|
|
|
348
349
|
const runtimeDir = this._runtimePath(cwd);
|
|
349
350
|
if (!existsSync(runtimeDir)) {
|
|
350
351
|
mkdirSync(runtimeDir, { recursive: true });
|
|
351
|
-
for (const d of ['artifacts', 'history', 'logs', 'templates']) {
|
|
352
|
+
for (const d of ['artifacts', 'history', 'logs', 'templates', 'state', 'temp']) {
|
|
352
353
|
mkdirSync(join(runtimeDir, d), { recursive: true });
|
|
353
354
|
}
|
|
354
355
|
}
|
|
@@ -746,7 +747,8 @@ export class ProgressManager {
|
|
|
746
747
|
throw new Error(`本地进度库不存在,无法 import:${dbPath}`);
|
|
747
748
|
}
|
|
748
749
|
const bakTs = new Date().toISOString().replace(/[:.]/g, '-');
|
|
749
|
-
const bakPath = this._runtimePath(cwd, `sillyspec.db.pre-import-${bakTs}.bak`);
|
|
750
|
+
const bakPath = this._runtimePath(cwd, RUNTIME_TEMP_DIRNAME, `sillyspec.db.pre-import-${bakTs}.bak`);
|
|
751
|
+
try { mkdirSync(dirname(bakPath), { recursive: true }); } catch { /* import 主流程 fail-open */ }
|
|
750
752
|
// 体检 BUG-18:WAL 模式下最近已提交事务可能仍在 -wal 侧车,直接 copy 主库文件会拿到
|
|
751
753
|
// 缺尾部提交的快照(恢复时静默回退进度)。先尽力 checkpoint(TRUNCATE) 把 WAL 合并回主库;
|
|
752
754
|
// 他进程持连接致 checkpoint 不完全时,把 -wal 侧车一并备份,恢复侧才有完整提交
|
|
@@ -880,17 +882,25 @@ export class ProgressManager {
|
|
|
880
882
|
let keep = Number(process.env.SILLYSPEC_PREIMPORT_BAK_KEEP);
|
|
881
883
|
if (!Number.isInteger(keep) || keep < 1) keep = PRE_IMPORT_BAK_KEEP_DEFAULT;
|
|
882
884
|
|
|
883
|
-
|
|
884
|
-
|
|
885
|
+
// 快照 2026-10-18 起落 temp/;根目录扫描保留旧布局兜底(迁移幂等收敛后清零)。
|
|
886
|
+
// 契约:根 runtime 目录缺失仍如实抛 ENOENT(fail-loud 不静默假成功);temp/ 是可选
|
|
887
|
+
// 层(迁移前不存在),缺失按空集处理。
|
|
888
|
+
const rootDir = this._runtimePath(cwd)
|
|
889
|
+
const scanDirs = [this._runtimePath(cwd, RUNTIME_TEMP_DIRNAME), rootDir]
|
|
890
|
+
const baks = [...new Set([
|
|
891
|
+
...(() => { try { return readdirSync(scanDirs[0]) } catch { return [] } })(),
|
|
892
|
+
...readdirSync(rootDir),
|
|
893
|
+
])].filter(f => PRE_IMPORT_BAK_RE.test(f)).sort();
|
|
885
894
|
const keepName = keepPath ? basename(keepPath) : null;
|
|
886
895
|
const doomed = baks.slice(0, Math.max(0, baks.length - keep)).filter(f => f !== keepName);
|
|
887
896
|
|
|
888
897
|
let removed = 0;
|
|
889
898
|
for (const f of doomed) {
|
|
890
899
|
// 单份删除失败(他进程持句柄 / 权限)不连坐其余份:跳过即可,下次 import 再收。
|
|
891
|
-
try {
|
|
900
|
+
const fDir = scanDirs.find(d => { try { return existsSync(join(d, f)) } catch { return false } }) || scanDirs[0]
|
|
901
|
+
try { unlinkSync(join(fDir, f)); } catch { continue; }
|
|
892
902
|
removed++;
|
|
893
|
-
const wal = join(
|
|
903
|
+
const wal = join(fDir, `${f}-wal`);
|
|
894
904
|
if (existsSync(wal)) {
|
|
895
905
|
try { unlinkSync(wal); } catch { /* 侧车残留无害:不参与恢复链,下次裁剪再收 */ }
|
|
896
906
|
}
|
|
@@ -937,7 +947,8 @@ export class ProgressManager {
|
|
|
937
947
|
_maybeSnapshotBak(cwd) {
|
|
938
948
|
try {
|
|
939
949
|
const dbPath = this._runtimePath(cwd, 'sillyspec.db');
|
|
940
|
-
|
|
950
|
+
// 2026-10-18 布局分层:可删备份归 temp/(db.js _openWithFallback 双路径回读)
|
|
951
|
+
const bakPath = runtimeTempPath(dirname(dbPath), 'sillyspec.db.bak');
|
|
941
952
|
const st = existsSync(bakPath) ? statSync(bakPath) : null;
|
|
942
953
|
if (st && Date.now() - st.mtimeMs < ProgressManager.SNAPSHOT_INTERVAL_MS) return;
|
|
943
954
|
const tmpPath = bakPath + '.tmp';
|