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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thincoder",
3
- "version": "0.12.49",
3
+ "version": "0.12.50",
4
4
  "description": "Thin coding agent - zero dependencies, no build step, Node.js native. Sharp code, zero bloat.",
5
5
  "keywords": [
6
6
  "ai",
@@ -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-specific rules are absent.")
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(" ")} Create METHODOLOGY.md and ensure prompt templates exist for full enforcement, or disable engineering mode (/eng).]`,
260
+ content: `[System reminder: ENGINEERING MODE is active but ${warnings.join(" ")}]`,
261
261
  })
262
262
  }
263
263
  }
@@ -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
- Do NOT open any code file for editing before this document exists.
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. Pass the designToken via the `designToken` PARAMETER never in
63
- the task text. The token is required — eng-coder cannot modify files
64
- without it.
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. Use user stories: **As a [role], I want [feature], so that [goal]**. Describe who / what / why — never how. After confirming requirements, create a checklist entry for each one. No checklist entry means the requirement hasn't landed yet.
12
- 2. **Design** — Write a design document covering approach, architecture, and implementation plan. Design is approved before coding starts.
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. 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.
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
  }
@@ -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 persist().catch(err => pushLine(`[error] ${err.message}`, C.error))
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
 
@@ -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 persistRaw((raw) => {
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
+ }