sillyspec 3.30.0 → 3.32.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 +54 -54
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +2 -2
- package/.claude/skills/sillyspec-commit/SKILL.md +1 -1
- package/.claude/skills/sillyspec-flow/SKILL.md +10 -6
- package/.claude/skills/sillyspec-quick/SKILL.md +116 -18
- package/.zcodeignore +66 -0
- package/AGENTS.md +15 -15
- package/assets/command-cards/flow.md +9 -3
- package/assets/command-cards/run-quick.md +4 -1
- package/package.json +2 -2
- package/src/agent-session-log.js +27 -5
- package/src/config-schema.js +21 -0
- package/src/datetime.js +51 -0
- package/src/db.js +10 -3
- package/src/decision-distill.js +21 -12
- package/src/design-facts.js +46 -6
- package/src/flow-draft.js +468 -95
- package/src/flow-parity.js +62 -4
- package/src/flow-review.js +136 -6
- package/src/flow.js +729 -125
- package/src/foreign-declared.js +71 -3
- package/src/fr-index.js +148 -162
- package/src/git-helper.js +16 -3
- package/src/hooks/worktree-guard.js +16 -5
- package/src/hunk-attribution.js +28 -5
- package/src/index.js +184 -17
- package/src/knowledge-classify.js +3 -1
- package/src/knowledge-digest.js +55 -26
- package/src/knowledge-match.js +135 -7
- package/src/knowledge-stats.js +66 -4
- package/src/knowledge-vector.js +160 -0
- package/src/machine-interface.js +90 -2
- package/src/module-impact.js +22 -1
- package/src/modules.js +9 -0
- package/src/progress/change-registry.js +20 -0
- package/src/progress/stage-machine.js +4 -7
- package/src/progress.js +8 -1
- package/src/quicklog.js +20 -0
- package/src/redomain.js +23 -7
- package/src/route-hindsight.js +276 -0
- package/src/run/archive-distill.js +3 -1
- package/src/run/command.js +65 -12
- package/src/run/complete-handlers.js +219 -5
- package/src/run/complete.js +77 -11
- package/src/run/gate-snapshot.js +15 -0
- package/src/run/gates.js +60 -4
- package/src/run/prompt.js +159 -31
- package/src/run/quick-audit.js +66 -7
- package/src/run/shared.js +6 -11
- package/src/run/stage.js +5 -1
- package/src/run/verify-quality-scan.js +84 -0
- package/src/scan-facts.js +2 -1
- package/src/scope-audit.js +9 -2
- package/src/sentinel-assertions.js +67 -16
- package/src/stage-contract-engine.js +4 -1
- package/src/stage-contract-spec.js +103 -4
- package/src/stage-contract.js +55 -7
- package/src/stages/brainstorm.js +27 -14
- package/src/stages/knowledge.js +4 -3
- package/src/stages/plan-postcheck.js +98 -3
- package/src/stages/plan.js +22 -2
- package/src/stages/verify.js +2 -1
- package/src/task-review.js +1 -1
- package/src/task-tick.js +121 -0
- package/src/taskcard-frontmatter.js +31 -0
- package/src/ui-visual.js +43 -8
- package/src/verify-postcheck.js +509 -37
- package/src/verify-probes.js +216 -5
- package/src/watcher.js +1067 -1031
- package/src/worktree-apply.js +9 -1
- package/src/worktree.js +21 -6
- package/templates/agents-instruction.md +15 -15
- package/templates/prompts/taskcard-rules.md +1 -0
package/src/stages/plan.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync } from 'fs'
|
|
2
2
|
import path from 'path'
|
|
3
|
+
import jsYaml from 'js-yaml'
|
|
3
4
|
import { getRule } from '../stage-contract-spec.js'
|
|
4
5
|
import { REVIEW_CHECKLISTS } from '../stage-review-checklist.js'
|
|
5
6
|
|
|
@@ -414,6 +415,20 @@ execute/verify 阶段会按实际代码变更更新此文档;archive 阶段会
|
|
|
414
415
|
*/
|
|
415
416
|
export function buildCoordinatorStep(changeDir, taskNames) {
|
|
416
417
|
const changeName = path.basename(changeDir)
|
|
418
|
+
// 填卡 batch 阈值项目化(2026-10-06-verify-docs-prefill task-03):「task 总数 ≤N 主 agent 直填」
|
|
419
|
+
// 原硬编码 8——430 万 token 填卡实证后按项目体系可调(local.yaml plan.fill_batch_min_tasks,
|
|
420
|
+
// integer ≥1,非法/缺省回退 8)。specBase 自 changeDir 推导(changes/<名> 的父目录)。
|
|
421
|
+
const fillBatchMin = (() => {
|
|
422
|
+
try {
|
|
423
|
+
const specBase = String(changeDir || '').replace(/[\\/]+changes[\\/]+[^\\/]+$/, '')
|
|
424
|
+
if (!specBase || specBase === changeDir) return 8
|
|
425
|
+
const p = path.join(specBase, 'local.yaml')
|
|
426
|
+
if (!existsSync(p)) return 8
|
|
427
|
+
const doc = jsYaml.load(readFileSync(p, 'utf8'))
|
|
428
|
+
const v = doc && doc.plan ? Number(doc.plan.fill_batch_min_tasks) : NaN
|
|
429
|
+
return Number.isInteger(v) && v >= 1 ? v : 8
|
|
430
|
+
} catch { return 8 }
|
|
431
|
+
})()
|
|
417
432
|
const taskList = taskNames.map(t => {
|
|
418
433
|
const num = t.num
|
|
419
434
|
return `- task-${num}: ${t.name}`
|
|
@@ -496,8 +511,8 @@ ${taskList}
|
|
|
496
511
|
幂等,已存在的卡跳过不覆盖;骨架带 LF 行尾 + 闭合 frontmatter + 硬校验 9 字段 + depends_on 反填。**只有主 agent 跑这一次,子代理一律不再运行 taskcard CLI。**
|
|
497
512
|
1. 确认 \`${changeDir}/tasks/\` 目录存在(上一步预生成会自动创建)
|
|
498
513
|
2. **默认主 agent 自己填卡(P0-2,2026-09-20 对撞实验驱动——6 个填卡子代理 6.7M token 做的是主 agent 可自完成的誊写,独立性价值为零):**
|
|
499
|
-
- task 总数
|
|
500
|
-
- 仅当 task 总数
|
|
514
|
+
- task 总数 ≤${fillBatchMin} 或变更单仓单模块 → 主 agent 逐卡 Edit 填充(骨架已预生成,誊写近乎免费),**不派子代理**
|
|
515
|
+
- 仅当 task 总数 >${fillBatchMin} **且**跨多模块/跨仓、或主会话上下文已明显吃紧时,才按 batch 分派子代理(此时并行省墙钟有真实收益)
|
|
501
516
|
3. **(分派形态时)按 batch 分派子代理(减少总子代理数量,而不是一个 task 一个子代理):**
|
|
502
517
|
- 把任务按「同一 Wave + 同一模块/相近能力 + 无跨 batch 强依赖」原则分组
|
|
503
518
|
- **每个 batch 包含 2~4 个 task**;Wave 内任务数 ≤4 时整个 Wave 可作为一个 batch
|
|
@@ -508,6 +523,11 @@ ${taskList}
|
|
|
508
523
|
5. 每个子代理使用下方「批量 TaskCard 子代理 prompt」,一次生成该 batch 的全部 task-N.md
|
|
509
524
|
6. 等待所有 batch 子代理完成
|
|
510
525
|
7. 验证每个 task-N.md 文件已生成且非空
|
|
526
|
+
8. **全部卡片填充完成后,主 agent 跑一次零成本自检(plan 门禁同源规则前置暴露,免 Step 4 门禁批量报错返工)**:
|
|
527
|
+
\`\`\`
|
|
528
|
+
sillyspec taskcard ${changeName} --validate
|
|
529
|
+
\`\`\`
|
|
530
|
+
报错即门禁报错(frontmatter YAML/占位符/必要字段/target_files 形态,YAML 错误带中文分诊动作);修复后重跑至通过再进下一步。
|
|
511
531
|
|
|
512
532
|
> 设计意图:plan.md 里 task 数可以较多(能力拆分完整),但 TaskCard 生成阶段的**默认形态是主 agent 直填**——卡是 design/plan 的誊写不是创作,主 agent 刚写完 plan 上下文最热;子代理只在规模/上下文压力真实存在时才有净收益(每个子代理都要重读 design+plan+源码,6 个子代理 6.7M token 的实证教训)。分派时合并 batch,避免子代理数量随 task 数线性爆炸。
|
|
513
533
|
|
package/src/stages/verify.js
CHANGED
|
@@ -164,7 +164,8 @@ export const definition = {
|
|
|
164
164
|
2. 如果存在:
|
|
165
165
|
- 逐个读取 tasks/task-NN.md,对照 frontmatter 的 \`acceptance:\` 列表逐条核验(TaskCard 协议的验收标准在 frontmatter YAML,正文无 checkbox)
|
|
166
166
|
- 每条 acceptance 对照实际实现/测试结果判定满足与否,未满足的项列为不通过
|
|
167
|
-
- 覆盖对账走探针 7 矩阵:verify-result.md「探针 7:验收×测试覆盖矩阵」段已由 CLI 预填归属测试文件与关键词提示(骨架未生成时先跑 \`sillyspec verify-probes --change <change-name> --init\`,幂等不覆盖已有正文);逐行填判定(covered/covered-service/partial/uncovered/non-testable 五选一——covered-service 适用:端点行为由 service 层等非端点层测试锁定,证据附测试锚点)与证据——covered/covered-service/partial 附测试锚点(file:line / \`.test.\` 测试文件名 / 反引号包裹的路径或测试名,行号可省),non-testable 写一句理由;判定/证据未填会被 verify \`--done\` 门禁阻断(fail-closed
|
|
167
|
+
- 覆盖对账走探针 7 矩阵:verify-result.md「探针 7:验收×测试覆盖矩阵」段已由 CLI 预填归属测试文件与关键词提示(骨架未生成时先跑 \`sillyspec verify-probes --change <change-name> --init\`,幂等不覆盖已有正文);逐行填判定(covered/covered-service/partial/uncovered/non-testable 五选一——covered-service 适用:端点行为由 service 层等非端点层测试锁定,证据附测试锚点)与证据——covered/covered-service/partial 附测试锚点(file:line / \`.test.\` 测试文件名 / 反引号包裹的路径或测试名,行号可省),non-testable 写一句理由;判定/证据未填会被 verify \`--done\` 门禁阻断(fail-closed);矩阵里带「既有用例候选(FR 关联回归面)」注记的测试是本变更未改动的既有回归用例——复核其覆盖性后如实填判定,命中≠结论
|
|
168
|
+
- **收口前预检(免逐轮撞门)**:verify-result.md 各节填完后、跑 \`--done\` 前,先跑 \`sillyspec gate verify --change <change-name> --docs-only\`——秒级只读预检纯文档契约缺项(visual-evidence/结论槽/移交项结构化等一次列清,不执行测试);缺项清零后再走 \`--done\` 的完整实测门
|
|
168
169
|
3. 如果不存在:跳过此步骤
|
|
169
170
|
|
|
170
171
|
### 输出
|
package/src/task-review.js
CHANGED
|
@@ -1015,7 +1015,7 @@ export function stampExecuteRunChange(runtimeRoot, runId, changeName) {
|
|
|
1015
1015
|
* 读 run 目录的 change 归属戳。
|
|
1016
1016
|
* @returns {string|null} 戳内容(trim 后非空),无戳/读失败返回 null
|
|
1017
1017
|
*/
|
|
1018
|
-
function readExecuteRunChangeStamp(runtimeRoot, runId) {
|
|
1018
|
+
export function readExecuteRunChangeStamp(runtimeRoot, runId) {
|
|
1019
1019
|
try {
|
|
1020
1020
|
const c = readFileSync(join(runtimeRoot, 'execute-runs', runId, 'change'), 'utf8').trim()
|
|
1021
1021
|
return c || null
|
package/src/task-tick.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* task-tick.js — `sillyspec task tick` 轻量勾选动词(2026-10-03-voluntary-task-tick)。
|
|
3
|
+
*
|
|
4
|
+
* 定位:让「边干边勾」成为最顺手路径——做完一个任务单元,一条命令翻格并立即拿到机器
|
|
5
|
+
* 确认(进度 N/M + 下一任务指针)。翻格产生的 tasks.md 变更由 watcher 既有 task-done
|
|
6
|
+
* 事件链实时上平台——闭环确认是 OpenSpec apply 循环「勾后重询」的本地对等物(2026-10-03
|
|
7
|
+
* local-usage-caliber-fix 0/12 事故的自愿路径修复:正反馈替代验证强制)。
|
|
8
|
+
*
|
|
9
|
+
* 事件直写(2026-10-07-thin-tasks-v3):翻格成功后向本变更 watcher 事件流追加分级精确
|
|
10
|
+
* task-done 事件(source:'task-tick',checked N→M 逐格口径)——watcher 3s 轮询对快速连续
|
|
11
|
+
* tick 只能采样出合并跳,收口节奏门以 CLI 精确事件为权威去重(detectBatchCheckCadence)。
|
|
12
|
+
* best-effort:事件写失败不阻断翻格(节奏门退回采样判,fail 方向安全)。
|
|
13
|
+
*
|
|
14
|
+
* 边界:
|
|
15
|
+
* - 纯簿记动词:无 review/verdict/ownership 仪式(与 task done 四合一的分界——那是厚档
|
|
16
|
+
* 任务卡的收尾件;本动词对 thin 工作分解面与覆写面同样适用,自愿使用不违 D-007)。
|
|
17
|
+
* - 幂等:已勾再勾走 already 分支(exit 0,零事件直写)——并发/重放零副作用;写盘走 writeAtomicSync。
|
|
18
|
+
* - 行内替换只动 checkbox 态字符,其余字节(含 CRLF)逐字保留。
|
|
19
|
+
* - 归档件只读:变更已归档时拒收(exit 2)。
|
|
20
|
+
*/
|
|
21
|
+
import { existsSync, readFileSync, appendFileSync } from 'node:fs'
|
|
22
|
+
import { join } from 'node:path'
|
|
23
|
+
|
|
24
|
+
/** 任务行(宽容缩进/星号有序标记,与哨兵判集行锚同口径 + 缩进容差)。 */
|
|
25
|
+
const TASK_ROW_RE = /^(\s*[-*] \[)( |x|X)(\] task-(\d+):)(.*)$/
|
|
26
|
+
|
|
27
|
+
/** 归一 task id:task-1 → task-01(与 governance-autopilot 勾选 token 归一同口径)。 */
|
|
28
|
+
export function normalizeTaskId(id) {
|
|
29
|
+
const m = /^task-(\d+)$/.exec(String(id || '').trim())
|
|
30
|
+
return m ? `task-${String(Number(m[1])).padStart(2, '0')}` : null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** 任务行清单(行序即文档序):[{id, checked, desc}]。 */
|
|
34
|
+
function collectRows(md) {
|
|
35
|
+
const rows = []
|
|
36
|
+
for (const line of String(md || '').split(/\r?\n/)) {
|
|
37
|
+
const m = line.match(TASK_ROW_RE)
|
|
38
|
+
if (m) rows.push({ id: `task-${String(Number(m[4])).padStart(2, '0')}`, checked: m[2].toLowerCase() === 'x', desc: (m[5] || '').trim() })
|
|
39
|
+
}
|
|
40
|
+
return rows
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 纯函数:翻格 + 进度回显材料。
|
|
45
|
+
* @param {{tasksMd:string, taskId:string}} opts
|
|
46
|
+
* @returns {{kind:'ticked'|'already', tasksMd:string, checked:number, total:number, next:{id:string,desc:string}|null}
|
|
47
|
+
* |{kind:'unknown', available:string[]}}
|
|
48
|
+
*/
|
|
49
|
+
export function tickTasksMd({ tasksMd, taskId }) {
|
|
50
|
+
const id = normalizeTaskId(taskId)
|
|
51
|
+
if (!id) return { kind: 'unknown', available: [] }
|
|
52
|
+
const rows = collectRows(tasksMd)
|
|
53
|
+
const target = rows.find((r) => r.id === id)
|
|
54
|
+
if (!target) return { kind: 'unknown', available: rows.map((r) => r.id) }
|
|
55
|
+
let out = String(tasksMd || '')
|
|
56
|
+
if (!target.checked) {
|
|
57
|
+
const re = new RegExp(`^(\\s*[-*] \\[) (\\] ${id}:)`, 'm')
|
|
58
|
+
out = out.replace(re, '$1x$2')
|
|
59
|
+
}
|
|
60
|
+
const after = collectRows(out)
|
|
61
|
+
const checked = after.filter((r) => r.checked).length
|
|
62
|
+
const pending = after.find((r) => !r.checked)
|
|
63
|
+
return {
|
|
64
|
+
kind: target.checked ? 'already' : 'ticked',
|
|
65
|
+
tasksMd: out,
|
|
66
|
+
checked,
|
|
67
|
+
total: after.length,
|
|
68
|
+
next: pending ? { id: pending.id, desc: pending.desc } : null,
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* IO 编排(index.js 分派入口):定位变更目录 → tickTasksMd → 原子写盘 → 回显。
|
|
74
|
+
* exit 0=翻格/幂等;exit 2=未知 id / 缺 tasks.md / 已归档。
|
|
75
|
+
*/
|
|
76
|
+
export async function runTaskTick({ changeName, cwd, taskId, specBase }) {
|
|
77
|
+
const changesRoot = join(specBase, 'changes')
|
|
78
|
+
const tasksPath = join(changesRoot, changeName, 'tasks.md')
|
|
79
|
+
if (!existsSync(tasksPath)) {
|
|
80
|
+
if (existsSync(join(changesRoot, 'archive', changeName))) {
|
|
81
|
+
console.error(`❌ 变更 ${changeName} 已归档——归档件只读,不勾选`)
|
|
82
|
+
} else {
|
|
83
|
+
console.error(`❌ 未找到 ${tasksPath}——task tick 面向在途变更的任务注册表(确认 --change)`)
|
|
84
|
+
}
|
|
85
|
+
process.exit(2)
|
|
86
|
+
}
|
|
87
|
+
const r = tickTasksMd({ tasksMd: readFileSync(tasksPath, 'utf8'), taskId })
|
|
88
|
+
if (r.kind === 'unknown') {
|
|
89
|
+
const ids = (r.available || []).join('、') || '(tasks.md 无任务行)'
|
|
90
|
+
console.error(`❌ tasks.md 中无 ${taskId}——可选任务:${ids}`)
|
|
91
|
+
process.exit(2)
|
|
92
|
+
}
|
|
93
|
+
if (r.kind === 'ticked') {
|
|
94
|
+
const { writeAtomicSync } = await import('./fs-atomic.js')
|
|
95
|
+
writeAtomicSync(tasksPath, r.tasksMd)
|
|
96
|
+
// 事件直写(2026-10-07-thin-tasks-v3):精确单格跳进事件流,节奏门按 source 去重采样合并跳。
|
|
97
|
+
try {
|
|
98
|
+
const { resolveRuntimeRoot } = await import('./run/shared.js')
|
|
99
|
+
const runtimeRoot = resolveRuntimeRoot({}, specBase)
|
|
100
|
+
const eventsPath = join(runtimeRoot, `watcher-events-${changeName}.jsonl`)
|
|
101
|
+
const from = Math.max(0, r.checked - 1)
|
|
102
|
+
appendFileSync(eventsPath, JSON.stringify({
|
|
103
|
+
ts: Date.now(), kind: 'task-done', stage: 'tasks',
|
|
104
|
+
detail: `checked ${from}→${r.checked}`, provisional: true, source: 'task-tick',
|
|
105
|
+
}) + '\n', 'utf8')
|
|
106
|
+
} catch { /* 事件 best-effort:写失败翻格不受影响(节奏门退回采样判) */ }
|
|
107
|
+
// 工件即时重推(2026-10-07-allticked-gate-docs-resync):翻格即任务面变更——顺带触发
|
|
108
|
+
// spec-sync,平台不再停留 start 时点快照(真实会话实证:改写后平台恒显初稿)。best-effort。
|
|
109
|
+
try {
|
|
110
|
+
const { triggerSync } = await import('./run/shared.js')
|
|
111
|
+
await triggerSync(cwd, changeName, { specRoot: specBase })
|
|
112
|
+
} catch { /* 重推 best-effort:失败不影响翻格主流程 */ }
|
|
113
|
+
}
|
|
114
|
+
const nextTip = r.next
|
|
115
|
+
? `|下一任务:${r.next.id} ${r.next.desc.slice(0, 40)}${r.next.desc.length > 40 ? '…' : ''}`
|
|
116
|
+
: `|🎉 ${r.total}/${r.total} 全勾——可收口:sillyspec flow done --change ${changeName}`
|
|
117
|
+
console.log(`${r.kind === 'ticked' ? '✅ 已勾' : 'ℹ️ 已是勾选态(幂等跳过写盘)'}(${r.checked}/${r.total})${nextTip}`)
|
|
118
|
+
return r
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export default { tickTasksMd, runTaskTick, normalizeTaskId }
|
|
@@ -55,3 +55,34 @@ export function parseTaskFrontmatter(content) {
|
|
|
55
55
|
}
|
|
56
56
|
return { ok: true, hasFrontmatter: true, fm, error: null }
|
|
57
57
|
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* js-yaml 报错分诊(2026-10-06-verify-friction-fix task-03):消息 + 出错行内容双信号映射中文
|
|
61
|
+
* 修复动作。背景:分诊知识长期躺在 templates/prompts/taskcard-rules.md L19-23 未接进门禁报错,
|
|
62
|
+
* 厚流程实证 agent 连撞 6 轮 YAML 门禁;且 js-yaml v4 真实消息与文档措辞有漂移(实证
|
|
63
|
+
* `title: A: B` 报 bad indentation 而非 mapping values),只匹配消息文本不可靠——先看出错行
|
|
64
|
+
* 的首字符是否保留指示符,再看行内是否「冒号+空格」,消息文本作家族归类兜底。
|
|
65
|
+
* @param {string} message js-yaml 异常 message(单行化后)
|
|
66
|
+
* @param {string} [errorLineText] 出错行原文(文件 1 基行内容;缺省只按消息分诊)
|
|
67
|
+
* @returns {string} 中文修复动作(非空)
|
|
68
|
+
*/
|
|
69
|
+
export function diagnoseTaskYamlError(message, errorLineText = '') {
|
|
70
|
+
const msg = String(message || '')
|
|
71
|
+
const line = String(errorLineText || '').replace(/\r$/, '')
|
|
72
|
+
// 剥列表项/缩进前缀后看首个 token 字符:反引号/@/% 是 YAML 保留指示符,js-yaml 对其报的
|
|
73
|
+
// 消息家族不稳定(实测反引号列表项报 bad indentation of a sequence entry),行内容才是可靠信号
|
|
74
|
+
const itemBody = line.replace(/^\s*(?:-\s*)?/, '')
|
|
75
|
+
if (/^[`@%]/.test(itemBody)) {
|
|
76
|
+
return '列表项/值以 YAML 保留指示符开头(反引号 `、@、%)——行首加普通文字前缀(如「执行 」)或给整行值加单引号包裹'
|
|
77
|
+
}
|
|
78
|
+
if (/quoted scalar/i.test(msg)) return '引号不成对——补齐成对引号,或改用中文引号/去掉引号'
|
|
79
|
+
if (/duplicated mapping key/i.test(msg)) return '同一字段写了两次——保留正确一处删除其余(骨架已反填的键勿重复手填)'
|
|
80
|
+
if (/expected ',' or ']'/i.test(msg)) return '方括号流序列内有裸特殊字符——改块式列表(键名换行后每项一行「 - 路径」);空 [] 占位不受影响'
|
|
81
|
+
if (/expected ',' or '}'/i.test(msg)) return '花括号且内部含冒号或引号(JS 对象/模板字面量)——改用不含这些字符的中文描述'
|
|
82
|
+
if (/mapping values are not allowed|bad indentation/i.test(msg)) {
|
|
83
|
+
if (/: /.test(line)) return '值含「冒号+空格」(如 A: B、url: http://x)被当成嵌套映射键——去掉冒号后的空格(写 A:B)或改写为不含「: 」的中文描述'
|
|
84
|
+
return '缩进或映射结构错——块式列表项保持两空格缩进、与上级键名对齐;值含「: 」时改写描述'
|
|
85
|
+
}
|
|
86
|
+
if (/cannot start any token/i.test(msg)) return '行内含 YAML 无法起始 token 的字符(如反引号/@ 开头)——加文字前缀或用引号包裹该值'
|
|
87
|
+
return '值含特殊字符——给值加单引号、去掉「: 」冒号空格,或改块式列表(规则全表见 templates/prompts/taskcard-rules.md)'
|
|
88
|
+
}
|
package/src/ui-visual.js
CHANGED
|
@@ -23,10 +23,13 @@ export const UI_EVIDENCE_FILENAME = 'visual-evidence.md'
|
|
|
23
23
|
|
|
24
24
|
/* ── 检测启发式(正反例单测锁定;误判面=默认档一行警告,误漏面由文件面扩展名兜底)── */
|
|
25
25
|
|
|
26
|
+
// 词表不含后端高频通用词「渲染/组件/样式」:CLI「渲染输出」「组件」「输出样式」是常规后端
|
|
27
|
+
// 用语,2026-10-05-knowledge-stats-freshness 实测误触整段 UI 须知+探针 warn(描述该问题的
|
|
28
|
+
// input 自己也复触一次);真 UI 信号由页面/前端/UI/视觉/界面/布局等词与扩展名兜底承接。
|
|
26
29
|
const UI_TOUCH_PATTERNS = [
|
|
27
|
-
/页面/, /前端/, /\bUI\b/i, /视觉/,
|
|
30
|
+
/页面/, /前端/, /\bUI\b/i, /视觉/, /界面/, /布局/, /主题色/, /配色/,
|
|
28
31
|
/\.tsx?\b/, /\.jsx\b/, /\.vue\b/, /\.svelte\b/, /\.css\b/, /\.scss\b/, /\.html\b/,
|
|
29
|
-
|
|
32
|
+
/原型/, /截图/,
|
|
30
33
|
]
|
|
31
34
|
|
|
32
35
|
/** input/规约文本是否触及前端页面或 UI(关键词启发式,≥1 命中即算)。 */
|
|
@@ -47,12 +50,16 @@ export function detectUiTouchInPaths(paths) {
|
|
|
47
50
|
|
|
48
51
|
export function buildUiGuidanceLines() {
|
|
49
52
|
return [
|
|
50
|
-
`🎨 UI 变更执行须知(本变更触及前端页面或 UI
|
|
51
|
-
` 1.
|
|
52
|
-
`
|
|
53
|
-
`
|
|
53
|
+
`🎨 UI 变更执行须知(本变更触及前端页面或 UI——advisory 引导非门禁):`,
|
|
54
|
+
` 1. 定稿原型必须是可复跑的真码产物(与实现同方言、能被本项目工具链构建)——手绘单文件`,
|
|
55
|
+
` 仅限一次性粗选对比,不作为交付原型(2026-09-28 frontend-apple-style 实证:手绘定稿后每条意见都要反向翻译回真实代码);`,
|
|
56
|
+
` 2. 开工先就近发现本项目原型管线:查本次变更触达项目自己的脚本、任务与工具链(多项目仓`,
|
|
57
|
+
` 按触达路径就近,不假设全仓共享一套);已有则复用,没有则现场搭最小管线并把入口留在`,
|
|
58
|
+
` 项目惯例位置——下一个会话能自己发现;`,
|
|
59
|
+
` 3. 边改边渲染对照:每完成一个页面任务单元就渲染实页与基准并排比对,差异清零再勾任务;`,
|
|
60
|
+
` 对照截图与结论随手落变更目录 ${UI_EVIDENCE_FILENAME}(干活时顺手写,收口只验在场);`,
|
|
54
61
|
` 4. 视觉降级须用户裁决留痕:执行中因约束对不齐基准而降级(结构收敛、范围缩小、样式统一级)`,
|
|
55
|
-
`
|
|
62
|
+
` 必须停下找用户签字,裁决记录写进 ${UI_EVIDENCE_FILENAME}(含「用户裁决」字样段)——`,
|
|
56
63
|
` 静默降级会在收口被「UI 视觉证据」探针硬拦(error 门)。`,
|
|
57
64
|
]
|
|
58
65
|
}
|
|
@@ -70,7 +77,13 @@ export function readUiVisualGate(specBase) {
|
|
|
70
77
|
}
|
|
71
78
|
}
|
|
72
79
|
|
|
73
|
-
|
|
80
|
+
// 降级声明判定词=绑定窗口而非裸词「降级」:裸词在后端/机制讨论语境高频(探针档位、性能
|
|
81
|
+
// 降级),与远处视觉词同行共现时分属两个讨论对象——2026-10-05-uivisual-word-narrow 实测
|
|
82
|
+
// 「error 档(降级无裁决)……渲染/组件/样式」同行共现被误判、error 档阻断收口(自举陷阱)。
|
|
83
|
+
// 窗口语义:降级与视觉词 ≤6 字内同短语(两侧顺序皆认,经既有正例「视觉收敛降级」标定);
|
|
84
|
+
// 绑定词表与 VISUAL_LINE 同集(含页面——评审 P2:缺页面时「六个页面降级为三个」漏判);
|
|
85
|
+
// 样式统一级独立短语与 \bpartial\b 词边界保留。纯结构判定,D-003 封闭面不变。
|
|
86
|
+
const DOWNGRADE_LINE = /((视觉|样式|界面|页面|UI)[^\n]{0,6}降级|降级[^\n]{0,6}(视觉|样式|界面|页面|UI)|样式统一级|\bpartial\b)/i
|
|
74
87
|
const VISUAL_LINE = /(视觉|UI|页面|样式|界面)/
|
|
75
88
|
|
|
76
89
|
/** design/requirements 是否声明「视觉降级」(同行共现口径——行级命中防跨行误配;runUiVisualProbe 内部消费)。 */
|
|
@@ -138,6 +151,28 @@ export function runUiVisualProbe({ changeDir, gate = 'warn' }) {
|
|
|
138
151
|
}
|
|
139
152
|
}
|
|
140
153
|
|
|
154
|
+
/**
|
|
155
|
+
* execute 收口的前置 advisory(2026-10-07-flow-friction-batch3 task-05):UI 触达变更的
|
|
156
|
+
* visual-evidence.md 应在执行期随手落盘,但执法只在 verify 收口(error 档)——postmortem
|
|
157
|
+
* (provider-model-list)实证 agent 到 verify 末步才发现缺证据。本函数供 execute --done 的
|
|
158
|
+
* gates 调用(warn 级提示,不阻断 execute;verify 的 error 档语义不动):UI 触达且证据
|
|
159
|
+
* 缺失时返回提示串,否则 null(非 UI / 证据在场 / gate off 零打扰)。
|
|
160
|
+
* @param {{ changeDir: string, specBase?: string }} opts
|
|
161
|
+
* @returns {string|null}
|
|
162
|
+
*/
|
|
163
|
+
export function uiEvidenceExecuteAdvisory({ changeDir, specBase = null }) {
|
|
164
|
+
try {
|
|
165
|
+
const gate = readUiVisualGate(specBase || join(changeDir, '..', '..', '..'))
|
|
166
|
+
const probe = runUiVisualProbe({ changeDir, gate })
|
|
167
|
+
if (!probe.applicable) return null
|
|
168
|
+
if (probe.evidencePresent) return null
|
|
169
|
+
const hard = gate === 'error'
|
|
170
|
+
return `UI 触达变更但变更目录尚无 ${UI_EVIDENCE_FILENAME}——渲染对照证据应在执行期随手落盘(截图/对照结论,收口只验在场不产新证据)${hard ? ';本仓 ui_visual_gate=error,verify 收口会硬拦' : ';verify 收口会警告'}。落盘路径:${join(changeDir, UI_EVIDENCE_FILENAME)}`
|
|
171
|
+
} catch {
|
|
172
|
+
return null // fail-soft:探测异常零打扰(verify 侧同款口径兜底)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
141
176
|
/** design 文件变更清单行的路径粗提(反引号/裸路径行——探针侧仅作 UI 扩展名兜底命中,不追求全解)。 */
|
|
142
177
|
function extractQuotedPaths(designText) {
|
|
143
178
|
if (!designText) return []
|