sillyspec 3.25.9 → 3.26.0
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 +8 -13
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +8 -6
- package/.claude/skills/sillyspec-continue/SKILL.md +3 -3
- package/.claude/skills/sillyspec-doctor/SKILL.md +1 -1
- package/.claude/skills/sillyspec-execute/SKILL.md +10 -4
- package/.claude/skills/sillyspec-plan/SKILL.md +1 -1
- package/.claude/skills/sillyspec-quick/SKILL.md +3 -1
- package/.claude/skills/sillyspec-verify/SKILL.md +1 -1
- package/.sillyspec-platform-cleaned +1 -0
- package/.sillyspec-platform.json +7 -0
- package/README.md +9 -0
- package/package.json +2 -2
- package/src/change-list.js +11 -8
- package/src/change-risk-profile.js +22 -0
- package/src/db.js +170 -161
- package/src/dispatch/backends/local-agent.js +152 -0
- package/src/dispatch/backends/sillyhub-mcp.js +242 -0
- package/src/dispatch/probe.js +210 -0
- package/src/dispatch/strategy.js +121 -0
- package/src/doctor-diagnostics.js +19 -25
- package/src/fs-atomic.js +69 -69
- package/src/git-helper.js +66 -0
- package/src/hooks/worktree-guard.js +64 -55
- package/src/index.js +326 -36
- package/src/init.js +7 -14
- package/src/local-detect.js +120 -110
- package/src/machine-interface.js +54 -19
- package/src/migrate.js +14 -4
- package/src/modules.js +16 -5
- package/src/progress/change-registry.js +85 -70
- package/src/progress/consistency-doctor.js +71 -14
- package/src/progress/shared.js +8 -5
- package/src/progress/stage-machine.js +67 -65
- package/src/progress/step-store.js +72 -67
- package/src/progress.js +448 -236
- package/src/quicklog.js +59 -12
- package/src/run/command.js +78 -39
- package/src/run/complete-handlers.js +1028 -889
- package/src/run/complete.js +132 -212
- package/src/run/concurrent-detect.js +132 -0
- package/src/run/gates.js +302 -19
- package/src/run/prompt.js +22 -68
- package/src/run/quick-audit.js +10 -9
- package/src/run/shared.js +722 -600
- package/src/run/stage.js +43 -24
- package/src/scan-postcheck.js +9 -31
- package/src/sillyhub-mcp/client.js +394 -0
- package/src/sillyhub-mcp/config.js +62 -0
- package/src/spec-dir-typo.js +71 -0
- package/src/stage-contract-engine.js +22 -1
- package/src/stage-contract-spec.js +11 -4
- package/src/stage-contract.js +26 -3
- package/src/stage-review.js +118 -10
- package/src/stages/archive.js +4 -6
- package/src/stages/brainstorm-auto.js +26 -20
- package/src/stages/brainstorm.js +29 -5
- package/src/stages/cmd-existence.js +145 -0
- package/src/stages/doctor.js +10 -7
- package/src/stages/execute.js +64 -22
- package/src/stages/plan-postcheck.js +152 -0
- package/src/stages/plan.js +16 -31
- package/src/stages/quick.js +25 -9
- package/src/stages/scan.js +53 -8
- package/src/stages/status.js +0 -1
- package/src/stages/verify.js +14 -9
- package/src/sync.js +433 -16
- package/src/task-review.js +59 -1
- package/src/verify-postcheck.js +938 -862
- package/src/version.js +19 -0
- package/src/worktree-apply.js +218 -93
- package/src/worktree-deps.js +89 -5
- package/src/worktree.js +213 -111
- package/templates/prompts/taskcard-rules.md +27 -0
- package/templates/prompts/verify-probes.md +3 -3
- package/.claude/skills/sillyspec-propose/SKILL.md +0 -87
- package/src/stages/propose.js +0 -181
package/src/run/prompt.js
CHANGED
|
@@ -20,8 +20,9 @@
|
|
|
20
20
|
import { basename, join } from 'node:path'
|
|
21
21
|
import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs'
|
|
22
22
|
import { stageRegistry } from '../stages/index.js'
|
|
23
|
-
import { resolvePromptIncludes, safeGit, WAIT_MARKER_RE } from './shared.js'
|
|
23
|
+
import { resolvePromptIncludes, resolveRuntimeRoot, safeGit, WAIT_MARKER_RE } from './shared.js'
|
|
24
24
|
import { renderStageContract } from '../stage-contract-spec.js'
|
|
25
|
+
import { parseModuleMapSimple } from '../modules.js'
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
28
|
* 从 _module-map.yaml 读取模块上下文索引
|
|
@@ -36,6 +37,14 @@ function loadModuleContextIndex(specBase, projectName) {
|
|
|
36
37
|
const mapPath = join(specBase, 'docs', projectName, 'modules', '_module-map.yaml')
|
|
37
38
|
if (!existsSync(mapPath)) return null
|
|
38
39
|
const content = readFileSync(mapPath, 'utf8')
|
|
40
|
+
// schema_version 校验(advisory):modules.js 写 schema_version: 2;缺失或非 2 → warn,
|
|
41
|
+
// 提示 v1/v2 格式混用风险(旧格式字段解析会静默错位)。正常生成的文件不触发。
|
|
42
|
+
const sv = content.match(/^schema_version:\s*(\d+)/m)
|
|
43
|
+
if (!sv) {
|
|
44
|
+
console.warn(`⚠️ _module-map.yaml 缺少 schema_version 声明(期望 2),模块解析可能错位:${mapPath}`)
|
|
45
|
+
} else if (sv[1] !== '2') {
|
|
46
|
+
console.warn(`⚠️ _module-map.yaml schema_version=${sv[1]}(期望 2),可能为旧格式,模块解析可能错位:${mapPath}`)
|
|
47
|
+
}
|
|
39
48
|
return parseModuleMapSimple(content)
|
|
40
49
|
} catch {
|
|
41
50
|
return null
|
|
@@ -104,63 +113,8 @@ function buildModuleContextInjection(taskDescription, moduleIndex, specBase, pro
|
|
|
104
113
|
return injection
|
|
105
114
|
}
|
|
106
115
|
|
|
107
|
-
// 复用 modules.js
|
|
108
|
-
|
|
109
|
-
const modules = {}
|
|
110
|
-
let currentModule = null
|
|
111
|
-
let currentKey = null
|
|
112
|
-
let currentArray = null
|
|
113
|
-
|
|
114
|
-
for (const line of content.split('\n')) {
|
|
115
|
-
const moduleMatch = line.match(/^ ([a-zA-Z0-9_-]+):$/)
|
|
116
|
-
if (moduleMatch) {
|
|
117
|
-
if (currentArray && currentModule && currentKey) {
|
|
118
|
-
modules[currentModule][currentKey] = currentArray
|
|
119
|
-
}
|
|
120
|
-
currentModule = moduleMatch[1]
|
|
121
|
-
modules[currentModule] = {}
|
|
122
|
-
currentKey = null
|
|
123
|
-
currentArray = null
|
|
124
|
-
continue
|
|
125
|
-
}
|
|
126
|
-
if (!currentModule) continue
|
|
127
|
-
|
|
128
|
-
const arrayFieldMatch = line.match(/^ (depends_on|used_by|paths|tags|aliases|entrypoints|main_symbols|review_reasons|core_files|test_files|related_docs|verify_commands):$/)
|
|
129
|
-
if (arrayFieldMatch) {
|
|
130
|
-
if (currentArray && currentKey) modules[currentModule][currentKey] = currentArray
|
|
131
|
-
currentKey = arrayFieldMatch[1]
|
|
132
|
-
currentArray = []
|
|
133
|
-
continue
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const inlineArrayMatch = line.match(/^ (depends_on|used_by|paths|tags|aliases|entrypoints|main_symbols|review_reasons|core_files|test_files|related_docs|verify_commands): \[(.*)\]$/)
|
|
137
|
-
if (inlineArrayMatch) {
|
|
138
|
-
if (currentArray && currentKey) modules[currentModule][currentKey] = currentArray
|
|
139
|
-
const vals = inlineArrayMatch[2].split(',').map(v => v.trim()).filter(Boolean)
|
|
140
|
-
modules[currentModule][inlineArrayMatch[1]] = vals
|
|
141
|
-
currentKey = null
|
|
142
|
-
currentArray = null
|
|
143
|
-
continue
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const scalarMatch = line.match(/^ (status|doc|needs_review|role|risk_level): (.+)$/)
|
|
147
|
-
if (scalarMatch) {
|
|
148
|
-
if (currentArray && currentKey) { modules[currentModule][currentKey] = currentArray; currentArray = null; currentKey = null }
|
|
149
|
-
modules[currentModule][scalarMatch[1]] = scalarMatch[2]
|
|
150
|
-
continue
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const itemMatch = line.match(/^ - (.+)$/)
|
|
154
|
-
if (itemMatch && currentArray !== null) {
|
|
155
|
-
currentArray.push(itemMatch[1].trim())
|
|
156
|
-
continue
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
if (currentArray && currentModule && currentKey) {
|
|
160
|
-
modules[currentModule][currentKey] = currentArray
|
|
161
|
-
}
|
|
162
|
-
return modules
|
|
163
|
-
}
|
|
116
|
+
// parseModuleMapSimple 复用 modules.js 的 canonical 实现(合并历史 copy-paste 副本,2026-08-07;
|
|
117
|
+
// 无循环依赖:modules.js 仅 import fs/path/db.js,prompt.js → modules.js 单向)。
|
|
164
118
|
/**
|
|
165
119
|
* 输出当前步骤的 prompt
|
|
166
120
|
*/
|
|
@@ -450,7 +404,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
450
404
|
if (stageName === 'execute' && promptText.includes('{EXECUTE_RUN_ID}')) {
|
|
451
405
|
let runId = ''
|
|
452
406
|
const execSpecBase = platformOpts?.specRoot || join(cwd, '.sillyspec')
|
|
453
|
-
const runtimeRoot = platformOpts
|
|
407
|
+
const runtimeRoot = resolveRuntimeRoot(platformOpts, execSpecBase)
|
|
454
408
|
const runIdFile = join(runtimeRoot, `current-execute-run-id-${changeName}`)
|
|
455
409
|
try {
|
|
456
410
|
if (existsSync(runIdFile)) {
|
|
@@ -466,10 +420,10 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
466
420
|
promptText = promptText.replace(/\{EXECUTE_RUN_ID\}/g, runId)
|
|
467
421
|
}
|
|
468
422
|
|
|
469
|
-
// Stage Review Tier:brainstorm/plan/
|
|
423
|
+
// Stage Review Tier:brainstorm/plan/execute 阶段注入审查分级占位符
|
|
470
424
|
// (scanProfile 只在 scan 生效、change-risk-profile 只管 apply/verify,都不约束这些阶段的审查方式,
|
|
471
425
|
// 故按 plan_level / 变更文件数分级:self 当前 agent 自审,independent 强制独立子代理 + review.json)
|
|
472
|
-
if (['brainstorm', 'plan', '
|
|
426
|
+
if (['brainstorm', 'plan', 'execute'].includes(stageName) && promptText.includes('{REVIEW_TIER}')) {
|
|
473
427
|
try {
|
|
474
428
|
const { classifyReviewTier } = await import('../review-tier.js')
|
|
475
429
|
const { generateStageReviewRunId, renderReviewJsonContract, stageReviewMarkerPath } = await import('../stage-review.js')
|
|
@@ -488,7 +442,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
488
442
|
// reviewRunId:优先读 marker 复用(保证多次渲染 prompt 注入同一 ID == gate 读取的 ID,
|
|
489
443
|
// 修复「prompt 多次渲染 / 多次 review 时 gate 取错 ID 读错 review.json」),marker 缺失才
|
|
490
444
|
// generate + 落盘。对齐 execute {EXECUTE_RUN_ID} 段(prompt.js:449-467)。
|
|
491
|
-
const tierRuntimeRoot = platformOpts
|
|
445
|
+
const tierRuntimeRoot = resolveRuntimeRoot(platformOpts, tierSpecBase)
|
|
492
446
|
const reviewRunIdMarker = stageReviewMarkerPath(tierRuntimeRoot, stageName, changeName)
|
|
493
447
|
let reviewRunId = ''
|
|
494
448
|
try {
|
|
@@ -526,7 +480,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
526
480
|
try {
|
|
527
481
|
const { summarizeTaskCompletion } = await import('../task-review.js')
|
|
528
482
|
const tcrSpecBase = platformOpts?.specRoot || join(cwd, '.sillyspec')
|
|
529
|
-
const tcrRuntimeRoot = platformOpts
|
|
483
|
+
const tcrRuntimeRoot = resolveRuntimeRoot(platformOpts, tcrSpecBase)
|
|
530
484
|
const tcrChangeDir = changeName ? join(tcrSpecBase, 'changes', changeName) : null
|
|
531
485
|
const summary = tcrChangeDir
|
|
532
486
|
? summarizeTaskCompletion({ changeDir: tcrChangeDir, runtimeRoot: tcrRuntimeRoot, changeName })
|
|
@@ -585,11 +539,11 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
585
539
|
if (stepIndex === 0) {
|
|
586
540
|
console.log(`\n### ⚠️ 铁律`)
|
|
587
541
|
console.log('- 文档优先:代码产出必须先有对应的设计/规范文档支撑。')
|
|
588
|
-
console.log('-
|
|
589
|
-
console.log('-
|
|
590
|
-
console.log('-
|
|
591
|
-
console.log('-
|
|
592
|
-
console.log('-
|
|
542
|
+
console.log('- 聚焦本步骤:只执行本步骤描述的操作并完整做完;自行扩展或跳步会破坏状态机推进,后续步骤再做后续事。')
|
|
543
|
+
console.log('- 已完成步骤视为只读:需修订用 `sillyspec run <stage> --reopen --from-step N` 回退重做,直接回头改会让进度记录与产物脱节。')
|
|
544
|
+
console.log('- CLI 子命令以本 prompt 或上一条 --done 输出的字面为准;不确定时停下问用户,猜测命令会让状态机推进到错误位置。')
|
|
545
|
+
console.log('- 本步骤产物落盘后立即执行 prompt 末尾的 --done:CLI 据此校验产出并推进状态机;不跑则进度永远停在本步。')
|
|
546
|
+
console.log('- 变更目录改名用 `sillyspec change-rename <旧名> <新名>`:mv/rename 会漏改进度库引用,导致变更失联。')
|
|
593
547
|
console.log('- 文档类型文件(.md/.yaml/.json 等)头部必须包含 author(git 用户名)和 created_at(精确到秒)')
|
|
594
548
|
console.log('- 执行构建/测试前必须先读 local.yaml,优先使用其中配置的命令、路径和环境变量;未配置时才使用默认值')
|
|
595
549
|
}
|
package/src/run/quick-audit.js
CHANGED
|
@@ -6,12 +6,11 @@
|
|
|
6
6
|
*
|
|
7
7
|
* 路径修正(相对 src/run/):
|
|
8
8
|
* - resolveQuickLinkedChanges 的 quick-recommend.js 动态 import './quick-recommend.js' → '../quick-recommend.js'
|
|
9
|
-
* -
|
|
9
|
+
* - 脏文件信号改用 safeGit(带 safe.directory,Q3);不再 import execSync
|
|
10
10
|
* - parsePorcelainPath 从 ./shared.js 重 import;checkbox 顶部 import
|
|
11
11
|
*/
|
|
12
|
-
import { execSync } from 'node:child_process'
|
|
13
12
|
import { checkbox } from '@inquirer/prompts'
|
|
14
|
-
import { parsePorcelainPath } from './shared.js'
|
|
13
|
+
import { parsePorcelainPath, safeGit } from './shared.js'
|
|
15
14
|
|
|
16
15
|
/**
|
|
17
16
|
* 打印 quick 完成审计结论(按 review.status 输出 SAFE/WARNING/BLOCKED)。
|
|
@@ -55,7 +54,7 @@ export function printQuickAuditReview(review) {
|
|
|
55
54
|
export async function resolveQuickLinkedChanges({ pm, cwd, specDir, quickFiles, taskDescription, nonInteractive }) {
|
|
56
55
|
let activeChanges = []
|
|
57
56
|
try {
|
|
58
|
-
activeChanges =
|
|
57
|
+
activeChanges = pm.listChanges(cwd)
|
|
59
58
|
} catch {
|
|
60
59
|
activeChanges = []
|
|
61
60
|
}
|
|
@@ -67,15 +66,17 @@ export async function resolveQuickLinkedChanges({ pm, cwd, specDir, quickFiles,
|
|
|
67
66
|
return []
|
|
68
67
|
}
|
|
69
68
|
|
|
70
|
-
//
|
|
69
|
+
// 脏文件(推荐信号之一)。safeGit 带 safe.directory,避免 linked worktree/容器/挂载点下裸
|
|
70
|
+
// `git status` 抛错致推荐信号静默丢失(multi-agent-review Q3 同类)。推荐非关键路径,失败回退 []。
|
|
71
|
+
// trim:false:porcelain 首行前导空格是状态码,trim 会削掉致 parsePorcelainPath 丢首字符。
|
|
71
72
|
let baselineFiles = []
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
baselineFiles =
|
|
73
|
+
const statusResult = safeGit(cwd, ['status', '--porcelain'], { trim: false })
|
|
74
|
+
if (!statusResult.error) {
|
|
75
|
+
baselineFiles = (statusResult.value || '').split('\n').filter(Boolean)
|
|
75
76
|
.map(l => parsePorcelainPath(l))
|
|
76
77
|
.filter(Boolean)
|
|
77
78
|
.filter(f => !f.startsWith('.sillyspec/'))
|
|
78
|
-
}
|
|
79
|
+
}
|
|
79
80
|
|
|
80
81
|
// 推荐打分(脏文件 + 任务描述双信号)
|
|
81
82
|
let recommendations = []
|