thincoder 0.12.49 → 0.12.50
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/CHANGELOG.md +12 -0
- package/package.json +1 -1
- package/src/agent/setup.mjs +2 -2
- package/src/prompts/eng-coder.md +1 -0
- package/src/prompts/engineering.md +20 -5
- package/src/prompts/methodology-template.md +8 -3
- package/src/session.mjs +8 -0
- package/src/tui/cmd-advisor.mjs +20 -2
- package/src/tui/cmd-eng.mjs +44 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
本文件记录 ThinCoder CLI 的发布历史。格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),版本遵循[语义化版本](https://semver.org/lang/zh-CN/)。
|
|
4
4
|
|
|
5
|
+
## [0.12.50] — 2026-08-29
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- **engineering 与 advisor.guard 改为会话级(跨端污染修复)**:旧设计里 engineering 只存 config.json 全局(`agent.engineering`),CLI `/eng` 与 VS Code 面板都写它 → 两端互相翻转对方的工程模式("VS Code 工程模式下模型仍委托 role='coder'")。现事实源是当前会话槽位文件(`engineering` 字段 + `advisor.guard`),config.json 降为 CLI 兼容/可见性镜像(双写保留:slot 先、config 后,slot 失败不阻断)。改动:`/eng` toggle 双写 slot(`cmd-eng.mjs` persistEngineering);`/advisor` guard 切换双写 slot(`cmd-advisor.mjs` persistGuard,model/thinking 仍 config-scoped);`saveSession`/`applySession` 往返 slot 值(无字段旧槽位回退 config,兼容锁定);启动恢复链 `bin/thincoder.mjs` applySession 时 slot 覆盖 config 播种值。权威文档:`docs/design/ENGINEERING-MODE.md` §5 重写
|
|
10
|
+
- **METHODOLOGY 三缺口修复**(核对工程模式提示词时发现):① 需求文档三层结构(总目标/功能用户故事/非功能标准)落地进 METHODOLOGY.md 与 methodology-template.md——engineering.md 的 "three layers per METHODOLOGY" 引用此前悬空;② engineering.md 交付评审补测试文档口径(METHODOLOGY 存在时每条用户故事须有测试用例覆盖正常/边界/异常,无覆盖=评审不通过)——与三文档硬流程对齐;③ METHODOLOGY.md 缺失警告点名后果(引用悬空+硬流程失效+恢复路径),不再静默降级
|
|
11
|
+
- **工程模式 UI/交互决策全链路落档**(用户报告"agent 无视讨论过的 UI 设计"):设计文档要素扩项——涉及界面时必须收录与用户达成的每条 UI/交互决策(布局/流程/控件行为/状态反馈),未定标 open 不静默发明;eng-coder 任务书必须复述这些决策(或指向设计文档具体章节)——子代理零上下文,留在聊天里的决策永远到不了它;`eng-coder.md` 执行侧闭合——缺失的界面决策停下报告,不自行发明。两端 prompts byte-identical;`ENGINEERING-MODE.md` §7 变更记录
|
|
12
|
+
|
|
13
|
+
### Tests
|
|
14
|
+
|
|
15
|
+
- 新增 `test/session-eng-advisor.test.mjs`:/eng 双写断言(slot+config)、applySession slot 恢复(true/false/显式 false 压过 config true)、旧槽位无字段回退 config 锁定、saveSession 每 turn 往返、/advisor guard 双写(model 等仍 config-scoped)
|
|
16
|
+
|
|
5
17
|
## [0.12.49] — 2026-08-29
|
|
6
18
|
|
|
7
19
|
### Added
|
package/package.json
CHANGED
package/src/agent/setup.mjs
CHANGED
|
@@ -252,12 +252,12 @@ export async function prepareRun(agent, input, callbacks, {
|
|
|
252
252
|
warnings.push(`Engineering template (${agent._role === "eng-coder" ? "engineering-sub.md" : "engineering.md"}) not found — using degraded constraints.`)
|
|
253
253
|
}
|
|
254
254
|
if (engResult.methodologyMissing) {
|
|
255
|
-
warnings.push("METHODOLOGY.md not found — project-
|
|
255
|
+
warnings.push("METHODOLOGY.md not found in the project root — no project methodology is loaded, so every 'per METHODOLOGY' reference in the engineering prompt is dangling and the three-document hard flow (requirements / design / test doc) is NOT enforced. Ask the user whether to create METHODOLOGY.md (scaffold available as src/prompts/methodology-template.md) before designing.")
|
|
256
256
|
}
|
|
257
257
|
if (warnings.length > 0) {
|
|
258
258
|
agent.history.push({
|
|
259
259
|
role: "user",
|
|
260
|
-
content: `[System reminder: ENGINEERING MODE is active but ${warnings.join(" ")}
|
|
260
|
+
content: `[System reminder: ENGINEERING MODE is active but ${warnings.join(" ")}]`,
|
|
261
261
|
})
|
|
262
262
|
}
|
|
263
263
|
}
|
package/src/prompts/eng-coder.md
CHANGED
|
@@ -14,6 +14,7 @@ The parent agent ran an independent design review (`advisor` with `type="design"
|
|
|
14
14
|
|
|
15
15
|
- Work independently. The parent only sees your final report.
|
|
16
16
|
- Follow the design document. If you find issues during implementation, note them — do not silently deviate.
|
|
17
|
+
- UI/interaction: implement exactly what the task brief and design doc state (layout, flows, control behavior, states, feedback). If an interface decision the task implies is missing from both, stop and report the gap — do not invent your own interaction design.
|
|
17
18
|
- Write code one file at a time, verify each before moving on: call `verify` after each logical group (it runs syntax checks + related tests), syntax check after each edit.
|
|
18
19
|
- Do not modify any file not listed in the design.
|
|
19
20
|
- If the task is ambiguous, note the ambiguity in your report; do not ask the user.
|
|
@@ -35,7 +35,11 @@ subagents only.
|
|
|
35
35
|
discipline.
|
|
36
36
|
2. **Design.** Write the design document in `docs/` (problem statement,
|
|
37
37
|
solution approach, full affected-file list, verifiable acceptance criteria).
|
|
38
|
-
|
|
38
|
+
When the task involves a user interface, the design document MUST also
|
|
39
|
+
capture every UI/interaction decision agreed with the user — layout, flows,
|
|
40
|
+
control behavior, states and feedback — exactly as discussed; parts not yet
|
|
41
|
+
decided are marked open, never silently invented. Do NOT open any code file
|
|
42
|
+
for editing before this document exists.
|
|
39
43
|
3. **Remind readiness — never self-initiate review.** Present the design
|
|
40
44
|
summary and say it is ready for review, then WAIT. You do NOT call the
|
|
41
45
|
advisor yourself — the initiation right belongs to the user: you prepare
|
|
@@ -59,15 +63,21 @@ subagents only.
|
|
|
59
63
|
6. **Implement via eng-coder.** Spawn a subagent with `role="eng-coder"`,
|
|
60
64
|
providing the METHODOLOGY task structure: the **Docs involved** list (design
|
|
61
65
|
doc + requirements + referenced docs), the file list, the acceptance
|
|
62
|
-
criteria.
|
|
63
|
-
|
|
64
|
-
|
|
66
|
+
criteria. When the task has UI, the task text MUST restate the agreed
|
|
67
|
+
UI/interaction decisions (or point to the exact design-doc sections that
|
|
68
|
+
hold them) — an eng-coder has NO conversation context, so a decision that
|
|
69
|
+
lives only in the chat never reaches it. Pass the designToken via the
|
|
70
|
+
`designToken` PARAMETER — never in the task text. The token is required —
|
|
71
|
+
eng-coder cannot modify files without it.
|
|
65
72
|
7. **Delivery review — automatic flow node.** After eng-coder returns, verify
|
|
66
73
|
the delivery against the acceptance criteria from the design (run the
|
|
67
74
|
tests it claims pass, read the changed files) AND run the code review with
|
|
68
75
|
the `advisor` tool (`type="code"`, `documents=[...]` = the task's Docs
|
|
69
76
|
involved list). This review happens automatically — no user initiation
|
|
70
|
-
needed (2026-08-24 decision).
|
|
77
|
+
needed (2026-08-24 decision). When METHODOLOGY.md is present, the
|
|
78
|
+
METHODOLOGY test document is part of the delivery too: each user story must
|
|
79
|
+
map to at least one test case (normal / edge / error) — a delivery without
|
|
80
|
+
its test coverage fails the review.
|
|
71
81
|
8. **Verify.** Run `verify` — it must pass before you claim the task complete.
|
|
72
82
|
|
|
73
83
|
## Work Loop (every user message)
|
|
@@ -136,6 +146,11 @@ cannot enumerate. When using the `question` tool:
|
|
|
136
146
|
constraint, or preference during design discussion or review, update the
|
|
137
147
|
relevant docs (design doc, METHODOLOGY.md, ENGINEERING-MODE.md) right away —
|
|
138
148
|
do not wait to be asked. A decision that isn't in a doc didn't land.
|
|
149
|
+
- **UI/interaction decisions ride the full chain**: every UI/interaction
|
|
150
|
+
decision agreed with the user MUST land in the design document AND be
|
|
151
|
+
restated in the eng-coder task (or pointer to its exact design-doc section).
|
|
152
|
+
"Discussed but not written down" is the most common reason an implementation
|
|
153
|
+
ignores what the user asked for — the subagent never saw the discussion.
|
|
139
154
|
- Review initiation split: the DESIGN review is called ONLY when the user
|
|
140
155
|
explicitly asks (e.g. "评审吧") — remind them when the design is ready,
|
|
141
156
|
never fire it yourself; each round of findings goes back to the user for
|
|
@@ -8,10 +8,15 @@
|
|
|
8
8
|
|
|
9
9
|
Every task follows four steps, no skipping:
|
|
10
10
|
|
|
11
|
-
1. **Requirements** — Discuss and document what's needed
|
|
12
|
-
|
|
11
|
+
1. **Requirements** — Discuss and document what's needed, then write the requirements doc organized in **three layers**:
|
|
12
|
+
- **Overall goal** — one sentence: what problem does this task solve, for whom;
|
|
13
|
+
- **Functional user stories** — individually acceptable, format: **As a [role], I want [feature], so that [goal]**. Describe who / what / why — never how;
|
|
14
|
+
- **Non-functional standards** — performance, security, compatibility, usability constraints, each with how it will be measured.
|
|
15
|
+
|
|
16
|
+
Requirements are DONE when all three layers are concrete enough to design against (the user confirms, or the answers stop changing the requirement). After confirming, create a checklist entry for each story. No checklist entry means the requirement hasn't landed yet.
|
|
17
|
+
2. **Design** — Write a design document: problem statement, approach and rationale, full affected-file list, and verifiable acceptance criteria (each criterion traces back to a user story). Design is approved before coding starts.
|
|
13
18
|
3. **Implementation** — Write the code.
|
|
14
|
-
4. **Testing** — Verify
|
|
19
|
+
4. **Testing** — Verify with a test document: each user story maps to at least one test case covering normal path, edge cases, and error conditions. Describe what to test, what input to give, and what output to expect.
|
|
15
20
|
|
|
16
21
|
These four steps are not "best practice" — they are hard process. Three documents required: **requirements doc**, **design doc**, **test doc**. Skipping to step 3 and writing code first is wrong nine times out of ten.
|
|
17
22
|
|
package/src/session.mjs
CHANGED
|
@@ -44,6 +44,7 @@ export function sessionPath(cwd) {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
function slotPath(cwd, n) { return sessionPath(cwd) + "." + n }
|
|
47
|
+
export { slotPath }
|
|
47
48
|
function manifestPath(cwd) { return sessionPath(cwd) + ".manifest" }
|
|
48
49
|
|
|
49
50
|
/** Path to the active slot's file */
|
|
@@ -417,6 +418,13 @@ export function applySession(agent, data) {
|
|
|
417
418
|
agent._pendingReminders = data.pendingReminders ?? []
|
|
418
419
|
agent._sessionStart = data.sessionStart ?? null
|
|
419
420
|
agent._engDesignToken = data.engDesignToken ?? null
|
|
421
|
+
// engineering is session-level (2026-08-29): the slot value is the CLI session's authority
|
|
422
|
+
// — config.json is only the initial default / cross-end mirror. A legacy slot without the
|
|
423
|
+
// field keeps whatever config.json seeded (unchanged behavior).
|
|
424
|
+
if (data.engineering !== undefined) {
|
|
425
|
+
agent.config.agent ??= {}
|
|
426
|
+
agent.config.agent.engineering = data.engineering === true
|
|
427
|
+
}
|
|
420
428
|
if (data.advisor) {
|
|
421
429
|
agent.config.advisor = { ...data.advisor }
|
|
422
430
|
}
|
package/src/tui/cmd-advisor.mjs
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
/** /advisor command: configure review model/thinking and toggle the review guard.
|
|
2
2
|
* Interactive loop UX — stays in menu after each action, Esc to exit.
|
|
3
3
|
* ctx: { agent, showPicker, pushLine, pushLabel, persistRaw } */
|
|
4
|
+
import { readFileSync } from "node:fs"
|
|
4
5
|
import { ansi, C } from "./ansi.mjs"
|
|
6
|
+
import { activeSlot, slotPath } from "../session.mjs"
|
|
7
|
+
import { writeSessionFile } from "./cmd-eng.mjs"
|
|
5
8
|
|
|
6
9
|
export async function handleAdvisorCommand(ctx) {
|
|
7
10
|
const { agent, showPicker, pushLine, pushLabel } = ctx
|
|
@@ -19,6 +22,21 @@ export async function handleAdvisorCommand(ctx) {
|
|
|
19
22
|
}
|
|
20
23
|
}
|
|
21
24
|
|
|
25
|
+
// Guard-only dual write (2026-08-29 — advisor.guard is session-level): the guard goes into
|
|
26
|
+
// the CURRENT session slot first (shared with VS Code), the config.json mirror follows.
|
|
27
|
+
// Other advisor keys (model/thinking/effort/timeout) stay config-scoped — persist() only.
|
|
28
|
+
const persistGuard = async () => {
|
|
29
|
+
try {
|
|
30
|
+
const p = slotPath(agent.cwd, activeSlot(agent.cwd))
|
|
31
|
+
const data = JSON.parse(readFileSync(p, "utf8"))
|
|
32
|
+
if (data && typeof data === "object" && Array.isArray(data.history)) {
|
|
33
|
+
data.advisor = { ...(typeof data.advisor === "object" && data.advisor !== null ? data.advisor : {}), guard: cfg.guard === true }
|
|
34
|
+
writeSessionFile(p, data)
|
|
35
|
+
}
|
|
36
|
+
} catch { /* slot missing/unreadable — config mirror still written */ }
|
|
37
|
+
await persist()
|
|
38
|
+
}
|
|
39
|
+
|
|
22
40
|
// Lazy model cache — fetched once per /advisor session
|
|
23
41
|
let modelCache = null
|
|
24
42
|
|
|
@@ -133,9 +151,9 @@ export async function handleAdvisorCommand(ctx) {
|
|
|
133
151
|
|
|
134
152
|
if (choice.action === "guard") {
|
|
135
153
|
cfg.guard = !(cfg.guard === true)
|
|
136
|
-
await
|
|
154
|
+
await persistGuard().catch(err => pushLine(`[error] ${err.message}`, C.error))
|
|
137
155
|
pushLabel("❯ Advisor", ansi.bold + C.tool)
|
|
138
|
-
pushLine(`Advisor: ${cfg.guard === true ? "on" : "off"}`, C.tool)
|
|
156
|
+
pushLine(`Advisor: ${cfg.guard === true ? "on" : "off"} (session)`, C.tool)
|
|
139
157
|
continue
|
|
140
158
|
}
|
|
141
159
|
|
package/src/tui/cmd-eng.mjs
CHANGED
|
@@ -1,14 +1,29 @@
|
|
|
1
1
|
/** /eng command: toggle engineering mode.
|
|
2
2
|
* Requires METHODOLOGY.md in project root. Offers to create one if missing.
|
|
3
3
|
* ctx: { agent, pushLine, pushLabel, persistRaw, showPicker } */
|
|
4
|
-
import { existsSync, copyFileSync } from "node:fs"
|
|
5
|
-
import { join } from "node:path"
|
|
4
|
+
import { existsSync, copyFileSync, readFileSync, writeFileSync, mkdirSync, renameSync, unlinkSync } from "node:fs"
|
|
5
|
+
import { join, dirname } from "node:path"
|
|
6
6
|
import { fileURLToPath } from "node:url"
|
|
7
7
|
import { ansi, C } from "./ansi.mjs"
|
|
8
|
+
import { activeSlot, slotPath } from "../session.mjs"
|
|
8
9
|
|
|
9
10
|
const templateDir = join(fileURLToPath(import.meta.url), "..", "..", "prompts")
|
|
10
11
|
import { ENG_OFF_REMINDER } from "../agent.mjs"
|
|
11
12
|
|
|
13
|
+
/** Atomic slot write (same shape as session.mjs writeSessionFile — kept local to avoid a
|
|
14
|
+
* private-import; cmd-advisor's guard toggle shares this helper). */
|
|
15
|
+
export function writeSessionFile(p, data) {
|
|
16
|
+
mkdirSync(dirname(p), { recursive: true })
|
|
17
|
+
const tmp = `${p}.tmp`
|
|
18
|
+
writeFileSync(tmp, JSON.stringify(data), "utf8")
|
|
19
|
+
try {
|
|
20
|
+
renameSync(tmp, p)
|
|
21
|
+
} catch {
|
|
22
|
+
try { unlinkSync(p) } catch {}
|
|
23
|
+
try { renameSync(tmp, p) } catch { writeFileSync(p, readFileSync(tmp, "utf8"), "utf8") }
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
12
27
|
export async function handleEngCommand(ctx) {
|
|
13
28
|
const { agent, pushLine, pushLabel, persistRaw, showPicker } = ctx
|
|
14
29
|
agent.config.agent ??= {}
|
|
@@ -40,13 +55,35 @@ export async function handleEngCommand(ctx) {
|
|
|
40
55
|
agent._pendingReminders = agent._pendingReminders ?? []
|
|
41
56
|
agent._pendingReminders.push(ENG_OFF_REMINDER)
|
|
42
57
|
}
|
|
43
|
-
await
|
|
44
|
-
raw.agent ??= {}
|
|
45
|
-
raw.agent.engineering = agent.config.agent.engineering
|
|
46
|
-
})
|
|
58
|
+
await persistEngineering(ctx, agent)
|
|
47
59
|
pushLabel("❯ Eng", ansi.bold + C.tool)
|
|
48
|
-
pushLine(`Engineering mode: ${agent.config.agent.engineering ? "ON" : "OFF"}`, C.tool)
|
|
60
|
+
pushLine(`Engineering mode: ${agent.config.agent.engineering ? "ON" : "OFF"} (session)`, C.tool)
|
|
49
61
|
if (agent.config.agent.engineering) {
|
|
50
62
|
pushLine(` → strictly following ${methodologyPath}`, C.dim)
|
|
51
63
|
}
|
|
52
64
|
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Dual persistence (2026-08-29 — engineering is session-level): write the flipped flag into
|
|
68
|
+
* the CURRENT session slot first (slot authority — shared with VS Code, per-session), then
|
|
69
|
+
* the config.json mirror (CLI visibility/compat; no longer the cross-session source of truth).
|
|
70
|
+
* The in-memory agent.config.agent.engineering (already flipped) stays the live authority for
|
|
71
|
+
* this process; saveSession also round-trips it on every turn-end write.
|
|
72
|
+
*/
|
|
73
|
+
async function persistEngineering(ctx, agent) {
|
|
74
|
+
const slot = activeSlot(agent.cwd)
|
|
75
|
+
try {
|
|
76
|
+
const p = slotPath(agent.cwd, slot)
|
|
77
|
+
const data = JSON.parse(readFileSync(p, "utf8"))
|
|
78
|
+
if (data && typeof data === "object" && Array.isArray(data.history)) {
|
|
79
|
+
data.engineering = agent.config.agent.engineering
|
|
80
|
+
writeSessionFile(p, data)
|
|
81
|
+
}
|
|
82
|
+
} catch { /* slot missing/unreadable — config mirror still written */ }
|
|
83
|
+
if (ctx.persistRaw) {
|
|
84
|
+
await ctx.persistRaw((raw) => {
|
|
85
|
+
raw.agent ??= {}
|
|
86
|
+
raw.agent.engineering = agent.config.agent.engineering
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
}
|