sillyspec 3.25.2 → 3.25.4
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-quick/SKILL.md +2 -1
- package/package.json +1 -1
- package/src/quicklog.js +11 -3
- package/src/run/complete-handlers.js +11 -2
- package/src/run/gates.js +1 -1
- package/src/run/prompt.js +16 -2
- package/src/run/shared.js +35 -23
- package/src/run.js +2 -2
- package/src/stage-review.js +44 -7
- package/src/stages/plan-postcheck.js +64 -5
- package/src/stages/quick.js +18 -4
- package/src/sync.js +10 -3
- package/src/worktree-apply.js +8 -3
- package/src/worktree.js +8 -3
- package/.playwright-mcp/console-2026-07-27T15-45-50-621Z.log +0 -1
- package/.playwright-mcp/page-2026-07-27T15-45-51-459Z.yml +0 -144
- package/.playwright-mcp/prompt-final.png +0 -0
- package/.playwright-mcp/prompt-view.png +0 -0
|
@@ -87,7 +87,8 @@ sillyspec run quick --files src/phone.ts,src/phone.test.ts
|
|
|
87
87
|
- **必须用 exec 工具(shell)执行 CLI,不要自己编造流程**
|
|
88
88
|
- quick 直接在主工作区改代码(不创建 worktree),范围必须小且明确
|
|
89
89
|
- 完成后立即 `--done`,不跳过
|
|
90
|
-
- QUICKLOG
|
|
90
|
+
- QUICKLOG 记录的**骨架由 CLI 接管**:启动时 CLI 自动分配 ql-ID 并在 `.sillyspec/quicklog/QUICKLOG-<user>.md` 写「进行中」条目(含关联变更 tasks.md),`--done` 时 CLI 自动翻「已完成」+ 勾选 task + 回填文件路径。ql-ID 分配/状态/task 你无需手写,只需用注入的 `<quicklog-id>` 在模块文档变更索引引用
|
|
91
|
+
- **QUICKLOG 正文精修(`--done` 后必做,不可跳过)**:CLI 落盘的是简版骨架(标题可能是 `(quick 任务)`、文件是单行逗号无括注),**不合格**。`--done` 成功后必须立即编辑本次 `<quicklog-id>` 条目,手动精修成丰富格式:① 标题改真实需求摘要(禁 `(quick 任务)`);② 文件改多行 bullet、每行带关键改动括注(禁 `(见实际改动)` 或无括注纯路径);③ 正文 `需求:`/`根因:`/`方案:`/`结果:` 四段逐项充实(禁只留一段「结果:」)。参照同文件早期丰富条目。一条 quick = 一条独立 ql,不追加到旧条目
|
|
91
92
|
- **最后一步 `--done --output` 必须按结构化结果模板给全四字段**(逐项一句话):`需求:… 根因:… 方案:… 结果:…`。这是 QUICKLOG「结果:」归档的唯一来源;CLI 校验缺字段会拒绝 `--done`(exit 1),补全后重跑即可。前两个 step 的 `--output` 是中间摘要,不用此模板
|
|
92
93
|
- **禁止**在没有运行 CLI 的情况下自行决定流程
|
|
93
94
|
|
package/package.json
CHANGED
package/src/quicklog.js
CHANGED
|
@@ -215,7 +215,7 @@ async function checkTaskCheckbox(specBase, change, qlId) {
|
|
|
215
215
|
// 可能是 CRLF,split('\n') 后每行带 \r。原代码 `lines[i] === '状态:进行中'` 精确匹配
|
|
216
216
|
// 恒失败 → 走 splice「兜底插入」→ 条目内同时出现「状态:已完成」+「状态:进行中」。
|
|
217
217
|
// 状态行匹配与「已完成」判断统一用行首前缀匹配,容忍行尾 \r;写入保持 CRLF 不扩大改动。
|
|
218
|
-
function flipEntryInContent(content, qlId, result) {
|
|
218
|
+
function flipEntryInContent(content, qlId, result, changedFiles = []) {
|
|
219
219
|
const lines = content.split('\n')
|
|
220
220
|
const startIdx = lines.findIndex(l => l.startsWith(`## ${qlId} |`))
|
|
221
221
|
if (startIdx === -1) return null
|
|
@@ -228,6 +228,12 @@ function flipEntryInContent(content, qlId, result) {
|
|
|
228
228
|
for (let i = startIdx + 1; i < endIdx; i++) {
|
|
229
229
|
if (/^状态:进行中\r?$/.test(lines[i])) { lines[i] = '状态:已完成'; flipped = true }
|
|
230
230
|
if (lines[i].startsWith('结果:')) hasResult = true
|
|
231
|
+
// 回填实际改动文件(调用方已用 isQuickMetadata 过滤 quick 自身元数据,只留业务文件)。
|
|
232
|
+
// 原地替换 lines[i],不改数组长度 → 与下方状态/结果 splice 的索引完全解耦。
|
|
233
|
+
// changedFiles 空(brownfield 无 guard / 调用方未传)→ 不动文件行,保持「(见实际改动)」。
|
|
234
|
+
if (changedFiles.length > 0 && lines[i].startsWith('文件:')) {
|
|
235
|
+
lines[i] = `文件:${changedFiles.join(', ')}`
|
|
236
|
+
}
|
|
231
237
|
}
|
|
232
238
|
if (!flipped && !lines.slice(startIdx, endIdx).some(l => /^状态:已完成\r?$/.test(l))) {
|
|
233
239
|
// 无状态行(异常),插一条已完成
|
|
@@ -297,11 +303,13 @@ export async function allocateQuicklogEntry(specBase, gitUser, { description, li
|
|
|
297
303
|
/**
|
|
298
304
|
* 翻某 qlId 条目为「已完成」+ 追加结果 + 勾选关联 tasks.md。持锁。
|
|
299
305
|
*/
|
|
300
|
-
export async function completeQuicklogEntry(specBase, gitUser, qlId, { resultText = '', linkedChanges = [] } = {}) {
|
|
306
|
+
export async function completeQuicklogEntry(specBase, gitUser, qlId, { resultText = '', linkedChanges = [], changedFiles = [] } = {}) {
|
|
301
307
|
const quicklogDir = join(specBase, 'quicklog')
|
|
302
308
|
const lockPath = join(quicklogDir, `.QUICKLOG-${(gitUser || 'unknown')}.md.lock`)
|
|
303
309
|
const result = sanitizeResult(resultText)
|
|
304
310
|
const linked = Array.isArray(linkedChanges) ? linkedChanges : []
|
|
311
|
+
// 实际改动文件(调用方 complete-handlers 已用 isQuickMetadata 过滤 quick 自身元数据)。空 → 不动文件行。
|
|
312
|
+
const realFiles = Array.isArray(changedFiles) ? changedFiles.filter(Boolean) : []
|
|
305
313
|
|
|
306
314
|
await withFileLock(lockPath, async () => {
|
|
307
315
|
// 条目可能在主文件或轮转归档中
|
|
@@ -309,7 +317,7 @@ export async function completeQuicklogEntry(specBase, gitUser, qlId, { resultTex
|
|
|
309
317
|
const filePath = join(quicklogDir, f)
|
|
310
318
|
let content = ''
|
|
311
319
|
try { content = readFileSync(filePath, 'utf8') } catch { continue }
|
|
312
|
-
const updated = flipEntryInContent(content, qlId, result)
|
|
320
|
+
const updated = flipEntryInContent(content, qlId, result, realFiles)
|
|
313
321
|
if (updated !== null) {
|
|
314
322
|
await writeAtomic(filePath, updated) // 命中处原子落盘(只改含目标条目的那一个文件)
|
|
315
323
|
break
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
import { basename, join, resolve, relative, isAbsolute } from 'node:path'
|
|
24
24
|
import { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync, unlinkSync, rmSync } from 'node:fs'
|
|
25
25
|
import { renameSyncRetry, writeAtomicSync } from '../fs-atomic.js'
|
|
26
|
-
import { resolveChangeDir, safeGit, auditQuickCompletion, triggerSync } from './shared.js'
|
|
26
|
+
import { resolveChangeDir, safeGit, auditQuickCompletion, triggerSync, isQuickMetadata } from './shared.js'
|
|
27
27
|
import { stageRegistry } from '../stages/index.js'
|
|
28
28
|
import { SCAN_STATUS, POINTER_STATUS } from '../constants.js'
|
|
29
29
|
import { printQuickAuditReview } from './quick-audit.js'
|
|
@@ -559,6 +559,9 @@ export async function handleQuickStageCompletion({ stageName, steps, currentIdx,
|
|
|
559
559
|
const gitUser = safeGit(cwd, ['config', 'user.name']).value || 'unknown'
|
|
560
560
|
let qlId = guard?.quicklogId || null
|
|
561
561
|
const linkedChanges = Array.isArray(guard?.linkedChanges) ? guard.linkedChanges : []
|
|
562
|
+
// 审计结果(仅 guard 存在时填充)。提到 if(guard) 外声明,供下方回填 QUICKLOG 文件行复用
|
|
563
|
+
// review.changedFiles;brownfield 无 guard 时保持 null → 文件行不回填(降级,不报错)。
|
|
564
|
+
let review = null
|
|
562
565
|
|
|
563
566
|
// 审计:仅在有 guard 时跑(brownfield 无 guard 跳过,兼容 D-003 brownfield 行为)。
|
|
564
567
|
if (guard) {
|
|
@@ -570,7 +573,7 @@ export async function handleQuickStageCompletion({ stageName, steps, currentIdx,
|
|
|
570
573
|
forceBaseline: guard.forceBaseline || isForceBaseline,
|
|
571
574
|
allowNew: guard.allowNew || isAllowNew,
|
|
572
575
|
}
|
|
573
|
-
|
|
576
|
+
review = await auditQuickCompletion(cwd, mergedGuard, { isConfirm: confirm })
|
|
574
577
|
printQuickAuditReview(review)
|
|
575
578
|
if (review.status === 'blocked') {
|
|
576
579
|
steps[currentIdx].status = 'pending'
|
|
@@ -632,9 +635,15 @@ export async function handleQuickStageCompletion({ stageName, steps, currentIdx,
|
|
|
632
635
|
// 翻状态进行中→已完成 + 追加结果 + 勾选关联 tasks.md
|
|
633
636
|
// resultText 不再截断:结构化结果块(需求/根因/方案/结果)完整落盘,多行写成字段化块。
|
|
634
637
|
try {
|
|
638
|
+
// 回填实际改动文件:review.changedFiles 含 quick 自身元数据(quicklog/.runtime/modules 等),
|
|
639
|
+
// 用 isQuickMetadata 过滤掉,只留真实业务文件。brownfield 无 review → 空数组,文件行不动。
|
|
640
|
+
const realFiles = Array.isArray(review?.changedFiles)
|
|
641
|
+
? review.changedFiles.filter(f => !isQuickMetadata(f, linkedChanges))
|
|
642
|
+
: []
|
|
635
643
|
await completeQuicklogEntry(specBase, gitUser, qlId, {
|
|
636
644
|
resultText: outputText || '',
|
|
637
645
|
linkedChanges,
|
|
646
|
+
changedFiles: realFiles,
|
|
638
647
|
})
|
|
639
648
|
console.log(`📝 QUICKLOG 条目 ${qlId} 已标记完成`)
|
|
640
649
|
} catch (e) {
|
package/src/run/gates.js
CHANGED
|
@@ -234,7 +234,7 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
|
|
|
234
234
|
if (tier.tier === 'self') {
|
|
235
235
|
console.log('\nℹ️ Stage Review: ' + stageName + ' tier=self(' + tier.reason + '),已降级为当前 agent 自审,不强制独立子代理。')
|
|
236
236
|
} else {
|
|
237
|
-
const reviewRunId = getLatestStageReviewRunId(runtimeRoot, stageName)
|
|
237
|
+
const reviewRunId = getLatestStageReviewRunId(runtimeRoot, stageName, changeName)
|
|
238
238
|
const reviewType = stageName === 'brainstorm' ? 'design'
|
|
239
239
|
: stageName === 'plan' ? 'plan'
|
|
240
240
|
: stageName === 'propose' ? 'proposal'
|
package/src/run/prompt.js
CHANGED
|
@@ -472,7 +472,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
472
472
|
if (['brainstorm', 'plan', 'propose', 'execute'].includes(stageName) && promptText.includes('{REVIEW_TIER}')) {
|
|
473
473
|
try {
|
|
474
474
|
const { classifyReviewTier } = await import('../review-tier.js')
|
|
475
|
-
const { generateStageReviewRunId, renderReviewJsonContract } = await import('../stage-review.js')
|
|
475
|
+
const { generateStageReviewRunId, renderReviewJsonContract, stageReviewMarkerPath } = await import('../stage-review.js')
|
|
476
476
|
const tierSpecBase = platformOpts?.specRoot || join(cwd, '.sillyspec')
|
|
477
477
|
const tierChangeDir = changeName ? join(tierSpecBase, 'changes', changeName) : null
|
|
478
478
|
const designPath = tierChangeDir ? join(tierChangeDir, 'design.md') : null
|
|
@@ -485,7 +485,21 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
485
485
|
}
|
|
486
486
|
}
|
|
487
487
|
const tier = classifyReviewTier({ planLevel, designPath })
|
|
488
|
-
|
|
488
|
+
// reviewRunId:优先读 marker 复用(保证多次渲染 prompt 注入同一 ID == gate 读取的 ID,
|
|
489
|
+
// 修复「prompt 多次渲染 / 多次 review 时 gate 取错 ID 读错 review.json」),marker 缺失才
|
|
490
|
+
// generate + 落盘。对齐 execute {EXECUTE_RUN_ID} 段(prompt.js:449-467)。
|
|
491
|
+
const tierRuntimeRoot = platformOpts?.runtimeRoot || join(tierSpecBase, '.runtime')
|
|
492
|
+
const reviewRunIdMarker = stageReviewMarkerPath(tierRuntimeRoot, stageName, changeName)
|
|
493
|
+
let reviewRunId = ''
|
|
494
|
+
try {
|
|
495
|
+
if (existsSync(reviewRunIdMarker)) {
|
|
496
|
+
reviewRunId = readFileSync(reviewRunIdMarker, 'utf8').trim()
|
|
497
|
+
}
|
|
498
|
+
} catch {}
|
|
499
|
+
if (!reviewRunId) {
|
|
500
|
+
reviewRunId = generateStageReviewRunId()
|
|
501
|
+
try { mkdirSync(tierRuntimeRoot, { recursive: true }); writeFileSync(reviewRunIdMarker, reviewRunId + '\n') } catch {}
|
|
502
|
+
}
|
|
489
503
|
// review.json 产物契约(schema + 示例 + docHash 算法 + 重算提示)事前注入,与 validateStageReviewSchema 同源
|
|
490
504
|
const reviewContractMd = renderReviewJsonContract({ stage: stageName, changeDir: tierChangeDir, reviewRunId, tier: tier.tier })
|
|
491
505
|
promptText = promptText
|
package/src/run/shared.js
CHANGED
|
@@ -188,6 +188,38 @@ export function parsePorcelainPath(line) {
|
|
|
188
188
|
return path.replace(/\\/g, '/')
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
+
/**
|
|
192
|
+
* quick 自身写入的 .sillyspec/ 元数据判定(auditQuickCompletion 审计 与 QUICKLOG「文件:」行回填
|
|
193
|
+
* 单源)。回填文件行时复用:review.changedFiles 含这些元数据时过滤掉,只留真实业务文件。
|
|
194
|
+
*
|
|
195
|
+
* 归类口径:
|
|
196
|
+
* - quicklog/.runtime/modules/_module-map/knowledge-uncategorized 等 quick 自身产物 → 元数据
|
|
197
|
+
* - .sillyspec/changes/:quick 自己没有该目录,其下文件要么是关联变更(reverse-sync),
|
|
198
|
+
* 要么是并发他者会话的工作。非关联变更目录整体视为元数据放行;关联变更文件不算(属本 quick 真实改动)
|
|
199
|
+
*
|
|
200
|
+
* 「并发工作 vs 偷建变更」的意图软判定留给 sillyhub,确定性校验只做路径归类。
|
|
201
|
+
*
|
|
202
|
+
* @param {string} p 文件路径(容错 \\ / 混用)
|
|
203
|
+
* @param {string[]} [linkedChanges] 关联变更名列表(影响 changes/ 归类)
|
|
204
|
+
* @returns {boolean} true=quick 元数据,应从业务文件列表过滤掉
|
|
205
|
+
*/
|
|
206
|
+
export function isQuickMetadata(p, linkedChanges = []) {
|
|
207
|
+
const normalizeGitPath = (x) => String(x).replace(/\\/g, '/')
|
|
208
|
+
const file = normalizeGitPath(p)
|
|
209
|
+
if (file.startsWith('.sillyspec/quicklog/')
|
|
210
|
+
|| file.startsWith('.sillyspec/.runtime/')
|
|
211
|
+
|| file === '.sillyspec/knowledge/uncategorized.md'
|
|
212
|
+
|| (/^\.sillyspec\/docs\/[^/]+\/modules\/[^/]+\.md$/.test(file))
|
|
213
|
+
|| (/^\.sillyspec\/docs\/[^/]+\/modules\/_module-map\.yaml$/.test(file))) return true
|
|
214
|
+
if (file.startsWith('.sillyspec/changes/')) {
|
|
215
|
+
const linkedChangeNames = new Set((Array.isArray(linkedChanges) ? linkedChanges : []).map(c => normalizeGitPath(c)))
|
|
216
|
+
const m = file.match(/^\.sillyspec\/changes\/([^/]+)(\/|$)/)
|
|
217
|
+
// bare 折叠 token(git 把全新未跟踪 changes/ 折叠成 `changes/`)或具体但非关联的变更 → 放行
|
|
218
|
+
if (!m || !linkedChangeNames.has(m[1])) return true
|
|
219
|
+
}
|
|
220
|
+
return false
|
|
221
|
+
}
|
|
222
|
+
|
|
191
223
|
/**
|
|
192
224
|
* quick 完成审计:对比 baseline 与实际变更。
|
|
193
225
|
* @returns {{ status: 'safe'|'warning'|'blocked', reasons: string[], changedFiles: string[], newFiles: string[], deletedFiles: string[], baselineHit: string[] }}
|
|
@@ -215,26 +247,6 @@ export async function auditQuickCompletion(cwd, guard, options = {}) {
|
|
|
215
247
|
const f = normalizeGitPath(p)
|
|
216
248
|
return baselineExact.has(f) || baselineDirs.some(d => f.startsWith(d))
|
|
217
249
|
}
|
|
218
|
-
// quick 自己没有 .sillyspec/changes/ 目录——该路径下任何文件要么是关联变更(reverse-sync),
|
|
219
|
-
// 要么是并发其他会话的变更工作。多会话并行时别人的 changes/ 不应被本 quick 审计拦截
|
|
220
|
-
// (确定性校验无法区分「并发工作」与「本 quick 偷建变更」,后者这类意图软判定留给 sillyhub)。
|
|
221
|
-
// 故:非关联变更目录整体放行;关联变更的文件仍走正常审计(reverse-sync 可见,需 --force-baseline)。
|
|
222
|
-
const linkedChangeNames = new Set((Array.isArray(guard.linkedChanges) ? guard.linkedChanges : []).map(c => normalizeGitPath(c)))
|
|
223
|
-
const isQuickMetadata = (p) => {
|
|
224
|
-
const file = normalizeGitPath(p)
|
|
225
|
-
if (file.startsWith('.sillyspec/quicklog/')
|
|
226
|
-
|| file.startsWith('.sillyspec/.runtime/')
|
|
227
|
-
|| file === '.sillyspec/knowledge/uncategorized.md'
|
|
228
|
-
|| (/^\.sillyspec\/docs\/[^/]+\/modules\/[^/]+\.md$/.test(file))
|
|
229
|
-
|| (/^\.sillyspec\/docs\/[^/]+\/modules\/_module-map\.yaml$/.test(file))) return true
|
|
230
|
-
// .sillyspec/changes/ 下:bare 折叠 token(git 把全新未跟踪 changes/ 折叠成 `changes/`)
|
|
231
|
-
// 或具体但【非关联】的变更 → 都属并发其他会话工作,放行;关联变更文件仍走审计。
|
|
232
|
-
if (file.startsWith('.sillyspec/changes/')) {
|
|
233
|
-
const m = file.match(/^\.sillyspec\/changes\/([^/]+)(\/|$)/)
|
|
234
|
-
if (!m || !linkedChangeNames.has(m[1])) return true
|
|
235
|
-
}
|
|
236
|
-
return false
|
|
237
|
-
}
|
|
238
250
|
const DANGEROUS_PATTERNS = [
|
|
239
251
|
'package.json',
|
|
240
252
|
'package-lock.json',
|
|
@@ -269,7 +281,7 @@ export async function auditQuickCompletion(cwd, guard, options = {}) {
|
|
|
269
281
|
}
|
|
270
282
|
|
|
271
283
|
// 检查危险文件(除非 force-baseline)
|
|
272
|
-
if (file.startsWith('.sillyspec/') && !isQuickMetadata(file) && !forceBaseline) {
|
|
284
|
+
if (file.startsWith('.sillyspec/') && !isQuickMetadata(file, guard.linkedChanges) && !forceBaseline) {
|
|
273
285
|
result.reasons.push(`危险文件变更: ${file}`)
|
|
274
286
|
}
|
|
275
287
|
|
|
@@ -293,7 +305,7 @@ export async function auditQuickCompletion(cwd, guard, options = {}) {
|
|
|
293
305
|
// 检查 new files(除非 allow-new)
|
|
294
306
|
if (!allowNew) {
|
|
295
307
|
for (const f of result.newFiles) {
|
|
296
|
-
if (!isQuickMetadata(f)) {
|
|
308
|
+
if (!isQuickMetadata(f, guard.linkedChanges)) {
|
|
297
309
|
result.reasons.push(`新增文件(需 --allow-new): ${f}`)
|
|
298
310
|
}
|
|
299
311
|
}
|
|
@@ -302,7 +314,7 @@ export async function auditQuickCompletion(cwd, guard, options = {}) {
|
|
|
302
314
|
// 检查 allowedFiles 范围
|
|
303
315
|
if (allowedFiles.length > 0) {
|
|
304
316
|
for (const f of result.changedFiles) {
|
|
305
|
-
if (!allowedFiles.includes(f) && !isQuickMetadata(f)) {
|
|
317
|
+
if (!allowedFiles.includes(f) && !isQuickMetadata(f, guard.linkedChanges)) {
|
|
306
318
|
result.reasons.push(`超出 allowedFiles: ${f}`)
|
|
307
319
|
}
|
|
308
320
|
}
|
package/src/run.js
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
*/
|
|
13
13
|
// W6 Step8c: 主命令分发(runCommand)+ auto 模式 + ensureStageSteps 搬至 ./run/command.js
|
|
14
14
|
export { runCommand, ensureStageSteps } from './run/command.js'
|
|
15
|
-
// barrel re-export: parsePorcelainPath + auditQuickCompletion 被 test 直接 import(契约保留)
|
|
16
|
-
export { parsePorcelainPath, auditQuickCompletion } from './run/shared.js'
|
|
15
|
+
// barrel re-export: parsePorcelainPath + auditQuickCompletion + isQuickMetadata 被 test 直接 import(契约保留)
|
|
16
|
+
export { parsePorcelainPath, auditQuickCompletion, isQuickMetadata } from './run/shared.js'
|
|
17
17
|
// barrel re-export: applyRootPlaceholders 被 test/prompt-placeholders.test.mjs 直接 import(契约保留)
|
|
18
18
|
export { applyRootPlaceholders } from './run/prompt.js'
|
|
19
19
|
// barrel re-export: sanitizeProjectName + validateParsedProjects 被 test 直接 import(随 handleScan 搬走,契约保留)
|
package/src/stage-review.js
CHANGED
|
@@ -175,8 +175,9 @@ export function validateStageReviewSchema(review) {
|
|
|
175
175
|
* 对应 task-review.verifyReviewGitEvidence 的"防伪造"职责——agent 不能凭空编一份
|
|
176
176
|
* review.json 贴个假 hash。约定 docHash 对应 reviewedFiles[0](主审查对象)。
|
|
177
177
|
*
|
|
178
|
-
* searchDirs 依次尝试解析 reviewedFiles 的相对路径(
|
|
179
|
-
*
|
|
178
|
+
* searchDirs 依次尝试解析 reviewedFiles[0] 的相对路径(effectiveSpecBase / changeDir / cwd)。
|
|
179
|
+
* 主文档在所有基准下都找不到时 fail-closed 阻断——连主审查文档都定位不到,要么 reviewedFiles[0]
|
|
180
|
+
* 是伪造路径(防 agent 填假路径跳过校验),要么路径基准严重错位,两者都不该静默放行。
|
|
180
181
|
*
|
|
181
182
|
* @param {object} review - 已通过 schema 校验
|
|
182
183
|
* @param {string[]} searchDirs - 解析相对路径的候选基准目录(绝对路径)
|
|
@@ -204,9 +205,13 @@ export function verifyStageReviewDocHash(review, searchDirs) {
|
|
|
204
205
|
return { ok: true, errors, warnings }
|
|
205
206
|
}
|
|
206
207
|
|
|
207
|
-
//
|
|
208
|
-
|
|
209
|
-
|
|
208
|
+
// 所有基准都找不到主文档 → fail-closed。连主审查文档都定位不到,说明 reviewedFiles[0] 要么
|
|
209
|
+
// 是伪造路径(恶意 agent 填假路径跳过 docHash 校验),要么路径基准严重错位——两者都该阻断
|
|
210
|
+
// 而非静默放行(历史降级 warning 是可被利用的伪造通道)。searchDirs =
|
|
211
|
+
// [effectiveSpecBase(.sillyspec), reviewChangeDir(changes/<change>), cwd],契约规定的
|
|
212
|
+
// reviewedFiles[0](changes/<change>/<mainDoc> 或 <mainDoc>)在这三个基准下必命中其一。
|
|
213
|
+
errors.push('主审查文档 ' + primaryRel + ' 在 ' + (searchDirs || []).length + ' 个候选基准目录下均不存在 — 无法做 docHash 校验(reviewedFiles[0] 路径伪造或基准错位)')
|
|
214
|
+
return { ok: false, errors, warnings }
|
|
210
215
|
}
|
|
211
216
|
|
|
212
217
|
/**
|
|
@@ -220,13 +225,45 @@ export function generateStageReviewRunId() {
|
|
|
220
225
|
}
|
|
221
226
|
|
|
222
227
|
/**
|
|
223
|
-
*
|
|
228
|
+
* stage review marker 文件路径(prompt 渲染时写 / getLatestStageReviewRunId 读,同源)。
|
|
229
|
+
*
|
|
230
|
+
* 对齐 execute 的 current-execute-run-id-<change> 语义:含 change 防多 change 串台
|
|
231
|
+
* (同一 stage 不同变更的 review 各自独立 marker);changeName 缺失时退化为按 stage 分。
|
|
232
|
+
*
|
|
233
|
+
* @param {string} runtimeRoot - .runtime 绝对路径
|
|
234
|
+
* @param {string} stage - brainstorm|plan|propose|execute
|
|
235
|
+
* @param {string} [changeName] - 变更名(防多 change 串台)
|
|
236
|
+
* @returns {string} marker 文件绝对路径
|
|
237
|
+
*/
|
|
238
|
+
export function stageReviewMarkerPath(runtimeRoot, stage, changeName) {
|
|
239
|
+
const suffix = changeName ? `${stage}-${changeName}` : stage
|
|
240
|
+
return join(runtimeRoot, `current-stage-review-run-id-${suffix}`)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* 获取最新的 stage review run id
|
|
245
|
+
*
|
|
246
|
+
* 优先读 marker 文件(prompt 渲染 {REVIEW_TIER} 时写入,保证 gate 取的 ID == 注入给 agent
|
|
247
|
+
* 的 ID —— 修复「prompt 多次渲染 / 多次 review 时 gate 取错 ID 读错 review.json」),
|
|
248
|
+
* 对齐 getLatestExecuteRunId(task-review.js:420-428)。marker 缺失时 fallback 扫描
|
|
249
|
+
* stage-reviews/<stage>-review-* 目录取字典序最新(向后兼容无 marker 旧数据)。
|
|
224
250
|
*
|
|
225
251
|
* @param {string} runtimeRoot - .runtime 绝对路径
|
|
226
252
|
* @param {string} stage - brainstorm|plan|propose|execute
|
|
253
|
+
* @param {string} [changeName] - 变更名(读对应 marker,防多 change 串台)
|
|
227
254
|
* @returns {string|null} run id(如 'review-2026-07-16-143000'),无则 null
|
|
228
255
|
*/
|
|
229
|
-
export function getLatestStageReviewRunId(runtimeRoot, stage) {
|
|
256
|
+
export function getLatestStageReviewRunId(runtimeRoot, stage, changeName) {
|
|
257
|
+
// 优先读 marker(prompt 写 / gate 读同源,对齐 execute current-execute-run-id 机制)
|
|
258
|
+
try {
|
|
259
|
+
const markerPath = stageReviewMarkerPath(runtimeRoot, stage, changeName)
|
|
260
|
+
if (existsSync(markerPath)) {
|
|
261
|
+
const content = readFileSync(markerPath, 'utf8').trim()
|
|
262
|
+
if (content) return content
|
|
263
|
+
}
|
|
264
|
+
} catch {}
|
|
265
|
+
|
|
266
|
+
// fallback:扫描 stage-reviews/<stage>-review-* 目录取字典序最新(向后兼容无 marker 旧数据)
|
|
230
267
|
const dir = join(runtimeRoot, 'stage-reviews')
|
|
231
268
|
if (!existsSync(dir)) return null
|
|
232
269
|
try {
|
|
@@ -202,6 +202,34 @@ export function topoSortWaves(depMap) {
|
|
|
202
202
|
return { waves, error: null }
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
+
/**
|
|
206
|
+
* 解析 plan.md 显式 Wave 分组 → Map<taskId, waveIndex>。
|
|
207
|
+
*
|
|
208
|
+
* Wave 口径与 execute.js parseWavesFromPlan 一致(execute 的真实并行单元,非 topoSort 建议值):
|
|
209
|
+
* - `## Wave N` 标题下的 task-XX checkbox 归入 Wave N;
|
|
210
|
+
* - 非 Wave 标题行(## 自检 等)退出当前 Wave 段,避免其 checkbox 混入。
|
|
211
|
+
* 返回 null = plan.md 不存在或无显式 Wave 标题 → execute 会把所有 task 放进单个隐式 Wave
|
|
212
|
+
* 全并行(execute.js:402-408),调用方据此判「同文件即冲突」。
|
|
213
|
+
*
|
|
214
|
+
* @param {string} planPath - plan.md 绝对路径
|
|
215
|
+
* @returns {Map<string, number>|null} taskId → waveIndex;null = 无显式 Wave
|
|
216
|
+
*/
|
|
217
|
+
function parseTaskWavesFromPlan(planPath) {
|
|
218
|
+
if (!existsSync(planPath)) return null
|
|
219
|
+
const content = readFileSync(planPath, 'utf8')
|
|
220
|
+
if (!/^#+\s*Wave\s+\d+/im.test(content)) return null
|
|
221
|
+
const map = new Map()
|
|
222
|
+
let currentWave = null
|
|
223
|
+
for (const line of content.split('\n')) {
|
|
224
|
+
const wm = line.match(/^#+\s*Wave\s+(\d+)/i)
|
|
225
|
+
if (wm) { currentWave = parseInt(wm[1], 10); continue }
|
|
226
|
+
if (/^#+\s/.test(line)) { currentWave = null; continue } // 非 Wave 标题退出当前 Wave 段
|
|
227
|
+
const tm = line.match(/^[-*]\s*\[[ x]\]\s*(task-\d+)/i)
|
|
228
|
+
if (tm && currentWave !== null) map.set(tm[1], currentWave)
|
|
229
|
+
}
|
|
230
|
+
return map
|
|
231
|
+
}
|
|
232
|
+
|
|
205
233
|
/**
|
|
206
234
|
* 本地一致性校验器
|
|
207
235
|
* @param {string} changeDir - 变更目录
|
|
@@ -258,10 +286,40 @@ export function validateBlueprintConsistency(changeDir) {
|
|
|
258
286
|
}
|
|
259
287
|
}
|
|
260
288
|
|
|
261
|
-
//
|
|
289
|
+
// 路径冲突(Wave 感知):同 Wave 内 >1 task 共享 allowed_path → execute 强制并行(execute.js:603)
|
|
290
|
+
// 子代理会互相覆盖该文件 → error。跨 Wave 同文件 → 串行执行安全 → warning。
|
|
291
|
+
// Wave 口径 = plan.md 显式 `## Wave N`(parseTaskWavesFromPlan,与 execute 同源,非 topoSort 建议值);
|
|
292
|
+
// plan.md 无显式 Wave → execute 全并行(隐式单 Wave,execute.js:402-408)→ 同文件即冲突。
|
|
293
|
+
const waveOfTask = parseTaskWavesFromPlan(pJoin(changeDir, 'plan.md'))
|
|
262
294
|
for (const [p, owners] of pathOwners) {
|
|
263
|
-
if (owners.length
|
|
264
|
-
|
|
295
|
+
if (owners.length < 2) continue
|
|
296
|
+
if (waveOfTask === null) {
|
|
297
|
+
errors.push(
|
|
298
|
+
`路径 ${p} 被 ${owners.length} 个 task 修改: ${owners.join(', ')} — plan.md 无显式 Wave 划分,` +
|
|
299
|
+
`execute 会把它们放进同一隐式 Wave 全并行(execute.js:402-408),子代理互相覆盖。` +
|
|
300
|
+
`解法:拆到不同 Wave(串行),或合并为单个 task。`
|
|
301
|
+
)
|
|
302
|
+
continue
|
|
303
|
+
}
|
|
304
|
+
// 显式 Wave:按 wave 分组 owners,找同 Wave 冲突
|
|
305
|
+
const byWave = new Map()
|
|
306
|
+
for (const t of owners) {
|
|
307
|
+
const w = waveOfTask.get(t)
|
|
308
|
+
if (w === undefined) continue // 未列入任何 Wave → execute 不执行,不参与冲突判定
|
|
309
|
+
if (!byWave.has(w)) byWave.set(w, [])
|
|
310
|
+
byWave.get(w).push(t)
|
|
311
|
+
}
|
|
312
|
+
for (const [w, ts] of byWave) {
|
|
313
|
+
if (ts.length > 1) {
|
|
314
|
+
errors.push(
|
|
315
|
+
`路径 ${p} 被 Wave ${w} 内 ${ts.length} 个 task 修改: ${ts.join(', ')} — 同 Wave 任务 execute 强制并行(execute.js:603),` +
|
|
316
|
+
`子代理会互相覆盖该文件。解法:把它们拆到不同 Wave(串行)。`
|
|
317
|
+
)
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
// 跨 Wave 共享:串行安全,仅提示
|
|
321
|
+
if (byWave.size > 1) {
|
|
322
|
+
warnings.push(`路径 ${p} 跨 Wave 被修改: ${owners.join(', ')}(不同 Wave 串行执行,安全;确认是否有意共享)`)
|
|
265
323
|
}
|
|
266
324
|
}
|
|
267
325
|
|
|
@@ -468,8 +526,9 @@ export function validatePlanFeasibility(changeDir, projectRoot = null) {
|
|
|
468
526
|
// 1. 必要字段检查
|
|
469
527
|
const taskId = (fm.match(/^id:\s*(.+)/m)?.[1] || '').trim()
|
|
470
528
|
const title = (fm.match(/^title:\s*(.+)/m)?.[1] || '').trim()
|
|
471
|
-
|
|
472
|
-
|
|
529
|
+
// 复用 parseAllowedPaths(认 inline [x] + 块式两种写法),与 blueprint consistency / design coverage
|
|
530
|
+
// 三处统一,消除「feasibility 只认块式 → inline 写法被误报 allowed_paths 为空」的漂移(transcript 卡 3 轮根因)。
|
|
531
|
+
const allowedPaths = parseAllowedPaths(content)
|
|
473
532
|
|
|
474
533
|
if (!taskId) errors.push(fsRule.data.messageId.replaceAll('${id}', file))
|
|
475
534
|
if (!title) errors.push(fsRule.data.messageTitle.replaceAll('${id}', file))
|
package/src/stages/quick.js
CHANGED
|
@@ -31,7 +31,9 @@ export const definition = {
|
|
|
31
31
|
- 在 \`{SPEC_ROOT}/quicklog/QUICKLOG-<git-user>.md\` 写入「进行中」条目(含关联变更与预估文件)
|
|
32
32
|
- 对每个关联变更 \`<c>\`:在 \`{SPEC_ROOT}/changes/<c>/tasks.md\` 追加未勾选 task \`- [ ] <quicklog-id> <任务描述>\`
|
|
33
33
|
|
|
34
|
-
**你不要创建或修改任何 QUICKLOG / tasks.md
|
|
34
|
+
**你不要创建或修改任何 QUICKLOG 的条目骨架 / tasks.md 记录**——ql-ID 分配、「进行中」条目、状态翻转、task 勾选、轮转全由 CLI 接管,你无需手写。<quicklog-id> 可用于 design.md / plan.md / archive / 模块变更索引引用。
|
|
35
|
+
|
|
36
|
+
> ⚠️ 例外:step 3 --done 之后,CLI 落盘的 QUICKLOG 只是简版骨架,**你必须手动精修正文成丰富格式**(标题语义化 + 文件多行带括注 + 四段充实),详见 step 3「QUICKLOG 正文精修」。这是 quick 交付质量的一部分,不可跳过。
|
|
35
37
|
|
|
36
38
|
### 输出
|
|
37
39
|
任务理解 + 上下文摘要`,
|
|
@@ -85,7 +87,7 @@ export const definition = {
|
|
|
85
87
|
2. 使用 \`git add -- <file...>\` 暂存本次 quick 实际修改的文件(不要 commit,由用户通过统一提交工具处理)
|
|
86
88
|
- 禁止使用 \`git add -A\`
|
|
87
89
|
- 不要暂存 quick 开始前就已存在的无关改动
|
|
88
|
-
3. QUICKLOG / tasks.md 记录由 CLI 在本 step 完成时自动收尾(翻「已完成」+ 勾选 task +
|
|
90
|
+
3. QUICKLOG / tasks.md 记录由 CLI 在本 step 完成时自动收尾(翻「已完成」+ 勾选 task + 轮转),你无需手动建/翻骨架——但**正文精修必做**(见下方「QUICKLOG 正文精修」)
|
|
89
91
|
4. 如果发现项目特有的坑,追加到 \`{SPEC_ROOT}/knowledge/uncategorized.md\`
|
|
90
92
|
5. 任务比预期复杂 → 建议用完整流程
|
|
91
93
|
|
|
@@ -100,8 +102,20 @@ export const definition = {
|
|
|
100
102
|
- 使用 \`git add -- <module-doc>\` 暂存更新的模块文件
|
|
101
103
|
9. 未命中任何模块 → 跳过,不做额外操作
|
|
102
104
|
|
|
103
|
-
### 输出
|
|
104
|
-
暂存确认 + 记录路径 +
|
|
105
|
+
### 输出
|
|
106
|
+
暂存确认 + 记录路径 + 模块文档同步结果(如有)
|
|
107
|
+
|
|
108
|
+
### 📝 QUICKLOG 正文精修(--done 之后必做,不可跳过)
|
|
109
|
+
sillyspec run quick --done 成功后,CLI 已在 {SPEC_ROOT}/quicklog/QUICKLOG-<git-user>.md 落盘本次 <quicklog-id> 条目的简版骨架:状态翻「已完成」、「文件:」是单行逗号路径、正文是 --output 的四字段。骨架是机械产物不达标,你必须立即编辑该条目手动精修成丰富格式,三项必改:
|
|
110
|
+
|
|
111
|
+
1. 标题:把 ## <quicklog-id> | <时间> | <标题> 中的 <标题> 改为真实需求摘要(如「登录加 IP 限流(5 次/分)+ 失败 3 次滑块验证」)。禁止保留 CLI 兜底的 (quick 任务) 或一句话笼统描述。
|
|
112
|
+
2. 文件:把 CLI 写的单行「文件:a.js, b.js」改写为多行 bullet,每个文件带关键改动括注;禁止「(见实际改动)」或无括注纯路径单行。示例:
|
|
113
|
+
文件:
|
|
114
|
+
- backend/app/modules/auth/router.py(登录端点串 check_rate_limit→assert_captcha_if_needed)
|
|
115
|
+
- backend/app/modules/auth/captcha_service.py(新建:限流 INCR + 失败计数 + Pillow 滑块生成/校验)
|
|
116
|
+
3. 正文:核对 需求: 根因: 方案: 结果: 四段逐项充实(--output 已写、CLI 原样插入,按需扩写),不允许只留一段「结果:」。
|
|
117
|
+
|
|
118
|
+
一条 quick = 一条独立 ql 条目;精修只动本次 <quicklog-id> 条目,不追加到旧条目。QUICKLOG 在 .sillyspec/(gitignore),精修不影响 --done 已通过的边界审计。`,
|
|
105
119
|
outputHint: '暂存和记录确认',
|
|
106
120
|
optional: false
|
|
107
121
|
}
|
package/src/sync.js
CHANGED
|
@@ -26,6 +26,13 @@ const LOCAL_YAML = '.sillyspec/local.yaml';
|
|
|
26
26
|
const CHANGES_DIR = '.sillyspec/changes';
|
|
27
27
|
const REQUEST_TIMEOUT_MS = 10_000;
|
|
28
28
|
|
|
29
|
+
// 未连接平台是本地独立用户的合法默认状态。sync / syncDocuments / checkApproval 由 run 流程
|
|
30
|
+
// 在后台 best-effort 触发(每步完成、execute 阶段启动),未连接时默认静默跳过——不每步催连
|
|
31
|
+
// 平台制造噪音(本地用户根本不需要平台)。需要排查同步行为时设 SILLYSPEC_DEBUG_SYNC=1。
|
|
32
|
+
function debugLog(msg) {
|
|
33
|
+
if (process.env.SILLYSPEC_DEBUG_SYNC) console.warn(msg)
|
|
34
|
+
}
|
|
35
|
+
|
|
29
36
|
/** 四件套文档文件名 */
|
|
30
37
|
const DOCUMENT_FILES = ['proposal.md', 'design.md', 'requirements.md', 'tasks.md'];
|
|
31
38
|
|
|
@@ -193,7 +200,7 @@ export class SyncManager {
|
|
|
193
200
|
async sync(changeName) {
|
|
194
201
|
const platform = this._getPlatform();
|
|
195
202
|
if (!platform) {
|
|
196
|
-
|
|
203
|
+
debugLog('[sync] 未连接平台(本地合法状态);如需平台同步:sillyspec platform connect');
|
|
197
204
|
return { synced: 0, errors: ['未连接平台'] };
|
|
198
205
|
}
|
|
199
206
|
|
|
@@ -255,7 +262,7 @@ export class SyncManager {
|
|
|
255
262
|
async syncDocuments(changeName) {
|
|
256
263
|
const platform = this._getPlatform();
|
|
257
264
|
if (!platform) {
|
|
258
|
-
|
|
265
|
+
debugLog('[sync] 未连接平台(本地合法状态);如需平台同步:sillyspec platform connect');
|
|
259
266
|
return { synced: 0, errors: ['未连接平台'] };
|
|
260
267
|
}
|
|
261
268
|
|
|
@@ -317,7 +324,7 @@ export class SyncManager {
|
|
|
317
324
|
async checkApproval(changeName) {
|
|
318
325
|
const platform = this._getPlatform();
|
|
319
326
|
if (!platform) {
|
|
320
|
-
|
|
327
|
+
debugLog('[sync] 未连接平台(本地合法状态);如需平台同步:sillyspec platform connect');
|
|
321
328
|
return { status: 'pending', reason: '未连接平台' };
|
|
322
329
|
}
|
|
323
330
|
|
package/src/worktree-apply.js
CHANGED
|
@@ -185,11 +185,16 @@ export function applyWorktree(changeName, { cwd, checkOnly = false, merge = fals
|
|
|
185
185
|
|
|
186
186
|
// --- 4.5 校验:主工作区 baseline 是否变化(防 execute 期间主工作区被修改)---
|
|
187
187
|
// 注意:必须和 computeBaselineHash (worktree.js) 使用相同的排除规则。
|
|
188
|
-
//
|
|
188
|
+
// 排除非交付物的元数据/文档 churn(execute 自身改的 + 多操作者常改的 agent 指引/文档),
|
|
189
|
+
// 否则别人改 CLAUDE.md/docs/.claude → 整树 hash 变 → apply 误阻断(多操作者仓库高频踩坑)。
|
|
190
|
+
// 安全:放过它们不丢真冲突——下方 step5a(未提交∩changedFiles)+step5b(HEAD blob) 对交付文件
|
|
191
|
+
// 做精确 per-file 校验,不读这里的 exclude。
|
|
189
192
|
// - .sillyspec/:plan/design 蓝图 + runtime 产物
|
|
190
|
-
// -
|
|
193
|
+
// - .claude/:agent 配置/skills/CLAUDE.md
|
|
194
|
+
// - docs/:文档(deliverable 文档冲突由 step5a/5b 兜底)
|
|
195
|
+
// - CLAUDE.md:根 agent 指引
|
|
191
196
|
if (meta.baselineHash) {
|
|
192
|
-
const exclude = '-- . ":(exclude).sillyspec/" ":(exclude)docs/
|
|
197
|
+
const exclude = '-- . ":(exclude).sillyspec/" ":(exclude).claude/" ":(exclude)docs/" ":(exclude)CLAUDE.md"';
|
|
193
198
|
const staged = gitQuiet(projectRoot, `diff --cached ${exclude}`) || '';
|
|
194
199
|
const unstaged = gitQuiet(projectRoot, `diff ${exclude}`) || '';
|
|
195
200
|
const untracked = gitQuiet(projectRoot, `ls-files --others --exclude-standard ${exclude}`) || '';
|
package/src/worktree.js
CHANGED
|
@@ -168,11 +168,16 @@ function sleepMs(ms) {
|
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
export function computeBaselineHash(cwd) {
|
|
171
|
-
//
|
|
171
|
+
// 排除非交付物的元数据/文档 churn,避免多操作者仓库里别人改这些 → 整树 hash 变 → apply 误阻断
|
|
172
|
+
// (execute 自身也会改 .sillyspec/docs.sillyspec,否则每个 execute 收尾都因自身改动 BLOCKED)。
|
|
173
|
+
// 安全:coarse hash 放过这些不丢真冲突——applyWorktree step5a(未提交∩changedFiles)+step5b(HEAD blob)
|
|
174
|
+
// 对交付文件做精确 per-file 校验,不读这里的 exclude。
|
|
172
175
|
// - .sillyspec/:brainstorm/plan 蓝图 + runtime 产物
|
|
173
|
-
// -
|
|
176
|
+
// - .claude/:agent 配置/skills/CLAUDE.md(多操作者 agent 指引 churn)
|
|
177
|
+
// - docs/:文档(非代码交付物;deliverable 文档冲突由 step5a/5b 兜底)
|
|
178
|
+
// - CLAUDE.md:根 agent 指引(多操作者常改)
|
|
174
179
|
// 必须和 applyWorktree step 4.5 (worktree-apply.js) 使用相同的排除规则。
|
|
175
|
-
const exclude = '-- . ":(exclude).sillyspec/" ":(exclude)docs/
|
|
180
|
+
const exclude = '-- . ":(exclude).sillyspec/" ":(exclude).claude/" ":(exclude)docs/" ":(exclude)CLAUDE.md"';
|
|
176
181
|
const staged = gitQuiet(cwd, `diff --cached ${exclude}`) || '';
|
|
177
182
|
const unstaged = gitQuiet(cwd, `diff ${exclude}`) || '';
|
|
178
183
|
const untracked = gitQuiet(cwd, `ls-files --others --exclude-standard ${exclude}`) || '';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
[ 809ms] [ERROR] Failed to load resource: the server responded with a status of 404 (File not found) @ http://localhost:8848/favicon.ico:0
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
- generic [active] [ref=e1]:
|
|
2
|
-
- banner [ref=e2]:
|
|
3
|
-
- generic [ref=e3]:
|
|
4
|
-
- generic [ref=e4]:
|
|
5
|
-
- text: Silly
|
|
6
|
-
- emphasis [ref=e5]: Spec
|
|
7
|
-
- generic [ref=e6]: Prompt Dossier
|
|
8
|
-
- generic [ref=e7]:
|
|
9
|
-
- text: ⌕
|
|
10
|
-
- textbox "搜索步骤名 / prompt 内容 / 占位符…" [ref=e8]
|
|
11
|
-
- generic [ref=e9]:
|
|
12
|
-
- generic [ref=e10]: 12 stages · 71 steps · 87k
|
|
13
|
-
- button "◐" [ref=e11] [cursor=pointer]
|
|
14
|
-
- generic [ref=e87]:
|
|
15
|
-
- navigation [ref=e88]:
|
|
16
|
-
- generic [ref=e89]:
|
|
17
|
-
- generic [ref=e90]:
|
|
18
|
-
- text: 主流程
|
|
19
|
-
- generic [ref=e91]: "4"
|
|
20
|
-
- generic [ref=e92] [cursor=pointer]:
|
|
21
|
-
- generic [ref=e93]: "01"
|
|
22
|
-
- generic [ref=e94]: 头脑风暴
|
|
23
|
-
- generic [ref=e95]: "8"
|
|
24
|
-
- generic [ref=e96] [cursor=pointer]:
|
|
25
|
-
- generic [ref=e97]: "02"
|
|
26
|
-
- generic [ref=e98]: 实现计划
|
|
27
|
-
- generic [ref=e99]: "5"
|
|
28
|
-
- generic [ref=e100] [cursor=pointer]:
|
|
29
|
-
- generic [ref=e101]: "03"
|
|
30
|
-
- generic [ref=e102]: 波次执行
|
|
31
|
-
- generic [ref=e103]: "12"
|
|
32
|
-
- generic [ref=e104] [cursor=pointer]:
|
|
33
|
-
- generic [ref=e105]: "04"
|
|
34
|
-
- generic [ref=e106]: 验证
|
|
35
|
-
- generic [ref=e107]: "7"
|
|
36
|
-
- generic [ref=e108]:
|
|
37
|
-
- generic [ref=e109]:
|
|
38
|
-
- text: 辅助
|
|
39
|
-
- generic [ref=e110]: "6"
|
|
40
|
-
- generic [ref=e111] [cursor=pointer]:
|
|
41
|
-
- generic [ref=e112]: "05"
|
|
42
|
-
- generic [ref=e113]: 项目扫描
|
|
43
|
-
- generic [ref=e114]: "11"
|
|
44
|
-
- generic [ref=e115] [cursor=pointer]:
|
|
45
|
-
- generic [ref=e116]: "06"
|
|
46
|
-
- generic [ref=e117]: 快速任务
|
|
47
|
-
- generic [ref=e118]: "3"
|
|
48
|
-
- generic [ref=e119] [cursor=pointer]:
|
|
49
|
-
- generic [ref=e120]: "07"
|
|
50
|
-
- generic [ref=e121]: 自由探索
|
|
51
|
-
- generic [ref=e122]: "1"
|
|
52
|
-
- generic [ref=e123] [cursor=pointer]:
|
|
53
|
-
- generic [ref=e124]: "08"
|
|
54
|
-
- generic [ref=e125]: 归档
|
|
55
|
-
- generic [ref=e126]: "5"
|
|
56
|
-
- generic [ref=e127] [cursor=pointer]:
|
|
57
|
-
- generic [ref=e128]: "09"
|
|
58
|
-
- generic [ref=e129]: 状态
|
|
59
|
-
- generic [ref=e130]: "3"
|
|
60
|
-
- generic [ref=e131] [cursor=pointer]:
|
|
61
|
-
- generic [ref=e132]: "10"
|
|
62
|
-
- generic [ref=e133]: 自检
|
|
63
|
-
- generic [ref=e134]: "5"
|
|
64
|
-
- generic [ref=e135]:
|
|
65
|
-
- generic [ref=e136]:
|
|
66
|
-
- text: 变体
|
|
67
|
-
- generic [ref=e137]: "1"
|
|
68
|
-
- generic [ref=e138] [cursor=pointer]:
|
|
69
|
-
- generic [ref=e139]: "11"
|
|
70
|
-
- generic [ref=e140]: 自动模式头脑风暴
|
|
71
|
-
- generic [ref=e141]: "4"
|
|
72
|
-
- generic [ref=e142]:
|
|
73
|
-
- generic [ref=e143]:
|
|
74
|
-
- text: 已废弃
|
|
75
|
-
- generic [ref=e144]: "1"
|
|
76
|
-
- generic [ref=e145] [cursor=pointer]:
|
|
77
|
-
- generic [ref=e146]: "12"
|
|
78
|
-
- generic [ref=e147]: propose · 已废弃
|
|
79
|
-
- generic [ref=e148]: "7"
|
|
80
|
-
- main [ref=e149]:
|
|
81
|
-
- generic [ref=e150]: 主流程 · STAGE 01
|
|
82
|
-
- heading "头脑风暴" [level=1] [ref=e151]
|
|
83
|
-
- paragraph [ref=e152]: 探索需求、分析技术方案、识别风险
|
|
84
|
-
- generic [ref=e153]:
|
|
85
|
-
- generic [ref=e154]: sourcesrc/stages/brainstorm.js
|
|
86
|
-
- generic [ref=e155]: persona资深架构师
|
|
87
|
-
- generic [ref=e156]: steps8
|
|
88
|
-
- generic [ref=e157]:
|
|
89
|
-
- button "全部展开" [ref=e158] [cursor=pointer]
|
|
90
|
-
- button "全部折叠" [ref=e159] [cursor=pointer]
|
|
91
|
-
- generic [ref=e160]: 8 STEPS
|
|
92
|
-
- generic [ref=e161]:
|
|
93
|
-
- article [ref=e162]:
|
|
94
|
-
- generic [ref=e163] [cursor=pointer]:
|
|
95
|
-
- generic [ref=e164]: 01 / 08
|
|
96
|
-
- generic [ref=e165]: 状态检查
|
|
97
|
-
- generic [ref=e166]: ▸
|
|
98
|
-
- article [ref=e167]:
|
|
99
|
-
- generic [ref=e168] [cursor=pointer]:
|
|
100
|
-
- generic [ref=e169]: 02 / 08
|
|
101
|
-
- generic [ref=e170]: 加载项目上下文
|
|
102
|
-
- generic [ref=e171]: ▸
|
|
103
|
-
- article [ref=e172]:
|
|
104
|
-
- generic [ref=e173] [cursor=pointer]:
|
|
105
|
-
- generic [ref=e174]: 03 / 08
|
|
106
|
-
- generic [ref=e175]: 对话式探索与需求澄清
|
|
107
|
-
- generic [ref=e176]:
|
|
108
|
-
- generic [ref=e177]: conditionalWait
|
|
109
|
-
- generic [ref=e178]: repeatable×8
|
|
110
|
-
- generic [ref=e179]: ▸
|
|
111
|
-
- article [ref=e180]:
|
|
112
|
-
- generic [ref=e181] [cursor=pointer]:
|
|
113
|
-
- generic [ref=e182]: 04 / 08
|
|
114
|
-
- generic [ref=e183]: 提出 2-3 种方案
|
|
115
|
-
- generic [ref=e184]: requiresWait
|
|
116
|
-
- generic [ref=e186]: ▸
|
|
117
|
-
- article [ref=e187]:
|
|
118
|
-
- generic [ref=e188] [cursor=pointer]:
|
|
119
|
-
- generic [ref=e189]: 05 / 08
|
|
120
|
-
- generic [ref=e190]: 分段展示设计
|
|
121
|
-
- generic [ref=e191]: requiresWait
|
|
122
|
-
- generic [ref=e193]: ▸
|
|
123
|
-
- article [ref=e194]:
|
|
124
|
-
- generic [ref=e195] [cursor=pointer]:
|
|
125
|
-
- generic [ref=e196]: 06 / 08
|
|
126
|
-
- generic [ref=e197]: 写设计文档并自审
|
|
127
|
-
- generic [ref=e198]: ▸
|
|
128
|
-
- article [ref=e199]:
|
|
129
|
-
- generic [ref=e200] [cursor=pointer]:
|
|
130
|
-
- generic [ref=e201]: 07 / 08
|
|
131
|
-
- generic [ref=e202]: Design Grill 交叉审查
|
|
132
|
-
- generic [ref=e203]: conditionalWait
|
|
133
|
-
- generic [ref=e205]: ▸
|
|
134
|
-
- article [ref=e206]:
|
|
135
|
-
- generic [ref=e207] [cursor=pointer]:
|
|
136
|
-
- generic [ref=e208]: 08 / 08
|
|
137
|
-
- generic [ref=e209]: 用户确认并生成规范文件
|
|
138
|
-
- generic [ref=e210]: requiresWait
|
|
139
|
-
- generic [ref=e212]: ▸
|
|
140
|
-
- contentinfo [ref=e213]:
|
|
141
|
-
- text: 数据机械提取自
|
|
142
|
-
- code [ref=e214]: src/stages/*.js
|
|
143
|
-
- text: · 单文件自包含 · file:// 可直接打开 · 生成命令:
|
|
144
|
-
- code [ref=e215]: node docs/prompt/_build-site.mjs
|
|
Binary file
|
|
Binary file
|