sillyspec 3.33.9 → 3.34.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/AGENTS.md +3 -0
- package/eval/knowledge/baseline.json +29 -476
- package/package.json +1 -1
- package/src/archive-baseline-replay.js +94 -0
- package/src/decision-distill.js +148 -4
- package/src/doctor-diagnostics.js +56 -1
- package/src/index.js +12 -2
- package/src/knowledge-match.js +44 -3
- package/src/md-fence.js +64 -0
- package/src/progress.js +34 -2
- package/src/spec-sync.js +16 -2
- package/src/stages/brainstorm.js +1 -1
- package/src/stages/knowledge.js +17 -2
- package/src/task-tick.js +42 -9
- package/templates/agents-instruction.md +3 -0
package/src/task-tick.js
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import { existsSync, readFileSync, appendFileSync } from 'node:fs'
|
|
22
22
|
import { runtimeStatePath } from './runtime-paths.js'
|
|
23
|
+
import { fenceScan } from './md-fence.js'
|
|
23
24
|
import { join } from 'node:path'
|
|
24
25
|
|
|
25
26
|
/** 任务行(宽容缩进/星号有序标记,与哨兵判集行锚同口径 + 缩进容差)。 */
|
|
@@ -31,11 +32,23 @@ export function normalizeTaskId(id) {
|
|
|
31
32
|
return m ? `task-${String(Number(m[1])).padStart(2, '0')}` : null
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* 任务行清单(行序即文档序):[{id, checked, desc}]。
|
|
37
|
+
* 围栏感知(2026-11-04-fence-aware-task-rows):代码围栏内的示例任务行不计——
|
|
38
|
+
* 文档围栏常含 `- [ ] task-N:` 形态示例,逐行正则会把它们当真实任务(幻影行污染
|
|
39
|
+
* 进度 N/M 与下一任务指针)。按 fenceScan 行标记过滤,围栏语法口径见 md-fence.js。
|
|
40
|
+
*/
|
|
35
41
|
function collectRows(md) {
|
|
42
|
+
const text = String(md || '')
|
|
43
|
+
const { inFence } = fenceScan(text)
|
|
36
44
|
const rows = []
|
|
37
|
-
|
|
38
|
-
|
|
45
|
+
// 行切分口径:split(/\r?\n/) 与 fenceScan 的 split('\n') 行数恒等(\r 只附着前行段),
|
|
46
|
+
// inFence 行索引直接对齐;行内容去 \r 是硬需求——JS 正则的 . 不匹配 \r,TASK_ROW_RE
|
|
47
|
+
// 的 (.*)$ 咬不到带 \r 的行尾(CRLF 全漏检)。
|
|
48
|
+
const lines = text.split(/\r?\n/)
|
|
49
|
+
for (let i = 0; i < lines.length; i++) {
|
|
50
|
+
if (inFence[i]) continue
|
|
51
|
+
const m = lines[i].match(TASK_ROW_RE)
|
|
39
52
|
if (m) rows.push({ id: `task-${String(Number(m[4])).padStart(2, '0')}`, checked: m[2].toLowerCase() === 'x', desc: (m[5] || '').trim() })
|
|
40
53
|
}
|
|
41
54
|
return rows
|
|
@@ -44,19 +57,34 @@ function collectRows(md) {
|
|
|
44
57
|
/**
|
|
45
58
|
* 纯函数:翻格 + 进度回显材料。
|
|
46
59
|
* @param {{tasksMd:string, taskId:string}} opts
|
|
47
|
-
* @returns {{kind:'ticked'|'already', tasksMd:string, checked:number, total:number, next:{id:string,desc:string}|null}
|
|
48
|
-
* |{kind:'unknown', available:string[]}}
|
|
60
|
+
* @returns {{kind:'ticked'|'already', tasksMd:string, checked:number, total:number, next:{id:string,desc:string}|null, unclosedFence:boolean, unclosedFenceLine:number|null}
|
|
61
|
+
* |{kind:'unknown', available:string[], unclosedFence:boolean, unclosedFenceLine:number|null}}
|
|
62
|
+
* unclosedFence/unclosedFenceLine(2026-11-04-fence-aware-task-rows):tasks.md 存在未闭合
|
|
63
|
+
* 代码围栏及其 1 基起始行号——围栏内任务行按开栏即入栏不计,advisory 面(不阻断翻格)。
|
|
49
64
|
*/
|
|
50
65
|
export function tickTasksMd({ tasksMd, taskId }) {
|
|
51
66
|
const id = normalizeTaskId(taskId)
|
|
52
|
-
if (!id) return { kind: 'unknown', available: [] }
|
|
67
|
+
if (!id) return { kind: 'unknown', available: [], unclosedFence: false, unclosedFenceLine: null }
|
|
68
|
+
const scanInfo = fenceScan(String(tasksMd || ''))
|
|
53
69
|
const rows = collectRows(tasksMd)
|
|
54
70
|
const target = rows.find((r) => r.id === id)
|
|
55
|
-
if (!target)
|
|
71
|
+
if (!target) {
|
|
72
|
+
return { kind: 'unknown', available: rows.map((r) => r.id), unclosedFence: scanInfo.unclosed, unclosedFenceLine: scanInfo.fenceStartLine }
|
|
73
|
+
}
|
|
56
74
|
let out = String(tasksMd || '')
|
|
57
75
|
if (!target.checked) {
|
|
58
|
-
|
|
59
|
-
|
|
76
|
+
// 行内替换(围栏感知):只动目标行 checkbox 态字符,其余字节逐字保留(\r 留在行内,
|
|
77
|
+
// 按 \n split 重 join 对 LF/CRLF 透明);围栏内同 id 示例行不命中(inFence 行跳过)。
|
|
78
|
+
const re = new RegExp(`^(\\s*[-*] \\[) (\\] ${id}:)`)
|
|
79
|
+
const lines = out.split('\n')
|
|
80
|
+
for (let i = 0; i < lines.length; i++) {
|
|
81
|
+
if (scanInfo.inFence[i]) continue
|
|
82
|
+
if (re.test(lines[i])) {
|
|
83
|
+
lines[i] = lines[i].replace(re, '$1x$2')
|
|
84
|
+
break
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
out = lines.join('\n')
|
|
60
88
|
}
|
|
61
89
|
const after = collectRows(out)
|
|
62
90
|
const checked = after.filter((r) => r.checked).length
|
|
@@ -67,6 +95,8 @@ export function tickTasksMd({ tasksMd, taskId }) {
|
|
|
67
95
|
checked,
|
|
68
96
|
total: after.length,
|
|
69
97
|
next: pending ? { id: pending.id, desc: pending.desc } : null,
|
|
98
|
+
unclosedFence: scanInfo.unclosed,
|
|
99
|
+
unclosedFenceLine: scanInfo.fenceStartLine,
|
|
70
100
|
}
|
|
71
101
|
}
|
|
72
102
|
|
|
@@ -111,6 +141,9 @@ export async function runTaskTick({ changeName, cwd, taskId, specBase, evidence
|
|
|
111
141
|
process.exit(2)
|
|
112
142
|
}
|
|
113
143
|
const r = tickTasksMd({ tasksMd: readFileSync(tasksPath, 'utf8'), taskId })
|
|
144
|
+
if (r.unclosedFence) {
|
|
145
|
+
console.warn(`⚠️ tasks.md 存在未闭合代码围栏(第 ${r.unclosedFenceLine} 行起)——围栏内任务行不计入任务集(检查围栏开合)`)
|
|
146
|
+
}
|
|
114
147
|
if (r.kind === 'unknown') {
|
|
115
148
|
const ids = (r.available || []).join('、') || '(tasks.md 无任务行)'
|
|
116
149
|
console.error(`❌ tasks.md 中无 ${taskId}——可选任务:${ids}`)
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
## 项目说明
|
|
4
4
|
本项目使用 **SillySpec** 管理变更,采用文档驱动开发。SillySpec 是给 Agent 调用的 CLI 流程控制器:你通过 CLI 告诉它"我在哪",它告诉你"下一步做什么";你执行步骤,它校验产出、推进状态,人类只在关键决策点介入审批。要考虑多 agent 同时操作代码,代码随时可能变化。所有变更以稳定、可用、可维护为目标,按生产级标准处理。
|
|
5
5
|
|
|
6
|
+
## 子代理豁免
|
|
7
|
+
被派发/派生的子代理(subagent)不执行本流程:不运行 sillyspec 流程命令(立项/批准/收口/归档/推送等),只完成派单任务并回传结果——变更状态所有权归派单的主会话。子代理发现流程前提不满足时在回传中报告,由主会话处理。
|
|
8
|
+
|
|
6
9
|
## 选道(动手前先对号入座)
|
|
7
10
|
| 需求形态 | 走法 |
|
|
8
11
|
|---|---|
|