@menghuan1918/dsh-apollo 0.0.0-stage → 0.3.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.
Files changed (47) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +232 -2
  3. package/cordis.patch.yml +78 -0
  4. package/docs/DEVELOPMENT.md +629 -0
  5. package/package.json +75 -4
  6. package/presets/eng/NOTICE +2 -0
  7. package/presets/eng/plugins/exit-guard/index.js +111 -0
  8. package/presets/eng/plugins/exit-guard/package.json +7 -0
  9. package/presets/eng/plugins/mode-instructions/index.js +48 -0
  10. package/presets/eng/plugins/mode-instructions/package.json +7 -0
  11. package/presets/eng/plugins/wait-subagent/index.js +228 -0
  12. package/presets/eng/plugins/wait-subagent/package.json +7 -0
  13. package/presets/eng-noask/NOTICE +2 -0
  14. package/presets/eng-noask/plugins/exit-guard/index.js +111 -0
  15. package/presets/eng-noask/plugins/exit-guard/package.json +7 -0
  16. package/presets/eng-noask/plugins/mode-instructions/index.js +48 -0
  17. package/presets/eng-noask/plugins/mode-instructions/package.json +7 -0
  18. package/presets/eng-noask/plugins/wait-subagent/index.js +228 -0
  19. package/presets/eng-noask/plugins/wait-subagent/package.json +7 -0
  20. package/presets/eng-noask.patch.yml +341 -0
  21. package/presets/eng.patch.yml +341 -0
  22. package/skills/brainstorming/SKILL.md +90 -0
  23. package/skills/brainstorming/spec-document-reviewer-prompt.md +50 -0
  24. package/skills/code-review/SKILL.md +90 -0
  25. package/skills/dev/SKILL.md +43 -0
  26. package/skills/diagnosing-bugs/SKILL.md +132 -0
  27. package/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +55 -0
  28. package/skills/exec/SKILL.md +91 -0
  29. package/skills/handoff/SKILL.md +23 -0
  30. package/skills/main-agent-driven-development/SKILL.md +64 -0
  31. package/skills/tdd/SKILL.md +34 -0
  32. package/skills/tdd/mocking.md +59 -0
  33. package/skills/tdd/tests.md +77 -0
  34. package/src/cli/claude.js +166 -0
  35. package/src/cli/codex.js +164 -0
  36. package/src/cli/jsonl.js +216 -0
  37. package/src/cli-adapter.js +198 -0
  38. package/src/cli-sessions.js +69 -0
  39. package/src/client.js +815 -0
  40. package/src/config.js +301 -0
  41. package/src/dsh-home.js +38 -0
  42. package/src/index.js +181 -0
  43. package/src/panel.js +452 -0
  44. package/src/settings.js +191 -0
  45. package/src/subagents.js +199 -0
  46. package/third-party/LICENSE-MIT-dsh-standard-preset +21 -0
  47. package/third-party/NOTICE-mattpocock-skills +21 -0
@@ -0,0 +1,132 @@
1
+ ---
2
+ name: diagnosing-bugs
3
+ description: Diagnosis loop for hard bugs and performance regressions. Use when the user says "diagnose"/"debug this", or reports something broken/throwing/failing/slow.
4
+ ---
5
+
6
+ # Diagnosing Bugs
7
+
8
+ A discipline for hard bugs. Skip phases only when explicitly justified.
9
+
10
+ ## Phase 1 — Build a feedback loop
11
+
12
+ **This is the skill.** Everything else is mechanical. If you have a **tight** pass/fail signal for the bug — one that goes red on _this_ bug — you will find the cause; bisection, hypothesis-testing, and instrumentation all just consume it. If you don't have one, no amount of staring at code will save you.
13
+
14
+ Spend disproportionate effort here. **Be aggressive. Be creative. Refuse to give up.**
15
+
16
+ ### Ways to construct one — try them in roughly this order
17
+
18
+ 1. **Failing test** at whatever seam reaches the bug — unit, integration, e2e.
19
+ 2. **Curl / HTTP script** against a running dev server.
20
+ 3. **CLI invocation** with a fixture input, diffing stdout against a known-good snapshot.
21
+ 4. **Headless browser script** (Playwright / Puppeteer) — drives the UI, asserts on DOM/console/network.
22
+ 5. **Replay a captured trace.** Save a real network request / payload / event log to disk; replay it through the code path in isolation.
23
+ 6. **Throwaway harness.** Spin up a minimal subset of the system (one service, mocked deps) that exercises the bug code path with a single function call.
24
+ 7. **Property / fuzz loop.** If the bug is "sometimes wrong output", run 1000 random inputs and look for the failure mode.
25
+ 8. **Bisection harness.** If the bug appeared between two known states (commit, dataset, version), automate "boot at state X, check, repeat" so you can `git bisect run` it.
26
+ 9. **Differential loop.** Run the same input through old-version vs new-version (or two configs) and diff outputs.
27
+ 10. **HITL bash script.** Last resort. If a human must click, drive _them_ with `scripts/hitl-loop.template.sh` so the loop is still structured. Captured output feeds back to you.
28
+
29
+ Build the right feedback loop, and the bug is 90% fixed.
30
+
31
+ ### Tighten the loop
32
+
33
+ Treat the loop as a product. Once you have _a_ loop, **tighten** it:
34
+
35
+ - Can I make it faster? (Cache setup, skip unrelated init, narrow the test scope.)
36
+ - Can I make the signal sharper? (Assert on the specific symptom, not "didn't crash".)
37
+ - Can I make it more deterministic? (Pin time, seed RNG, isolate filesystem, freeze network.)
38
+
39
+ A 30-second flaky loop is barely better than no loop; a 2-second deterministic one is tight — a debugging superpower.
40
+
41
+ ### Non-deterministic bugs
42
+
43
+ The goal is not a clean repro but a **higher reproduction rate**. Loop the trigger 100×, parallelise, add stress, narrow timing windows, inject sleeps. A 50%-flake bug is debuggable; 1% is not — keep raising the rate until it's debuggable.
44
+
45
+ ### When you genuinely cannot build a loop
46
+
47
+ Stop and say so explicitly. List what you tried. Ask the user for: (a) access to whatever environment reproduces it, (b) a captured artifact (HAR file, log dump, core dump, screen recording with timestamps), or (c) permission to add temporary production instrumentation. Do **not** proceed to hypothesise without a loop.
48
+
49
+ ### Completion criterion — a tight loop that goes red
50
+
51
+ Phase 1 is done when the loop is **tight** and **red-capable**: you can name **one command** — a script path, a test invocation, a curl — that you have **already run at least once** (paste the invocation and its output), and that is:
52
+
53
+ - [ ] **Red-capable** — it drives the actual bug code path and asserts the **user's exact symptom**, so it can go red on this bug and green once fixed. Not "runs without erroring" — it must be able to _catch this specific bug_.
54
+ - [ ] **Deterministic** — same verdict every run (flaky bugs: a pinned, high reproduction rate, per above).
55
+ - [ ] **Fast** — seconds, not minutes.
56
+ - [ ] **Agent-runnable** — you can run it unattended; a human in the loop only via `scripts/hitl-loop.template.sh`.
57
+
58
+ If you catch yourself reading code to build a theory before this command exists, **stop — jumping straight to a hypothesis is the exact failure this skill prevents.** No red-capable command, no Phase 2.
59
+
60
+ ## Phase 2 — Reproduce + minimise
61
+
62
+ Run the loop. Watch it go red — the bug appears.
63
+
64
+ Confirm:
65
+
66
+ - [ ] The loop produces the failure mode the **user** described — not a different failure that happens to be nearby. Wrong bug = wrong fix.
67
+ - [ ] The failure is reproducible across multiple runs (or, for non-deterministic bugs, reproducible at a high enough rate to debug against).
68
+ - [ ] You have captured the exact symptom (error message, wrong output, slow timing) so later phases can verify the fix actually addresses it.
69
+
70
+ ### Minimise
71
+
72
+ Once it's red, shrink the repro to the **smallest scenario that still goes red**. Cut inputs, callers, config, data, and steps **one at a time**, re-running the loop after each cut — keep only what is load-bearing for the failure.
73
+
74
+ Why bother: a minimal repro shrinks the hypothesis space in Phase 3 (fewer moving parts left to suspect) and becomes the clean regression test in Phase 5.
75
+
76
+ Done when **every remaining element is load-bearing** — removing any one of them makes the loop go green.
77
+
78
+ Do not proceed until you have reproduced **and** minimised.
79
+
80
+ ## Phase 3 — Hypothesise
81
+
82
+ Generate **3–5 ranked hypotheses** before testing any of them. Single-hypothesis generation anchors on the first plausible idea.
83
+
84
+ Each hypothesis must be **falsifiable**: state the prediction it makes.
85
+
86
+ > Format: "If <X> is the cause, then <changing Y> will make the bug disappear / <changing Z> will make it worse."
87
+
88
+ If you cannot state the prediction, the hypothesis is a vibe — discard or sharpen it.
89
+
90
+ **Show the ranked list to the user before testing.** They often have domain knowledge that re-ranks instantly ("we just deployed a change to #3"), or know hypotheses they've already ruled out. Cheap checkpoint, big time saver. Don't block on it — proceed with your ranking if the user is AFK.
91
+
92
+ ## Phase 4 — Instrument
93
+
94
+ Each probe must map to a specific prediction from Phase 3. **Change one variable at a time.**
95
+
96
+ Tool preference:
97
+
98
+ 1. **Debugger / REPL inspection** if the env supports it. One breakpoint beats ten logs.
99
+ 2. **Targeted logs** at the boundaries that distinguish hypotheses.
100
+ 3. Never "log everything and grep".
101
+
102
+ **Tag every debug log** with a unique prefix, e.g. `[DEBUG-a4f2]`. Cleanup at the end becomes a single grep. Untagged logs survive; tagged logs die.
103
+
104
+ **Perf branch.** For performance regressions, logs are usually wrong. Instead: establish a baseline measurement (timing harness, `performance.now()`, profiler, query plan), then bisect. Measure first, fix second.
105
+
106
+ ## Phase 5 — Fix + regression test
107
+
108
+ Write the regression test **before the fix** — but only if there is a **correct seam** for it.
109
+
110
+ A correct seam is one where the test exercises the **real bug pattern** as it occurs at the call site. If the only available seam is too shallow (single-caller test when the bug needs multiple callers, unit test that can't replicate the chain that triggered the bug), a regression test there gives false confidence.
111
+
112
+ **If no correct seam exists, that itself is the finding.** Note it. The codebase architecture is preventing the bug from being locked down. Flag this in the post-mortem.
113
+
114
+ If a correct seam exists:
115
+
116
+ 1. Turn the minimised repro into a failing test at that seam.
117
+ 2. Watch it fail.
118
+ 3. Apply the fix.
119
+ 4. Watch it pass.
120
+ 5. Re-run the Phase 1 feedback loop against the original (un-minimised) scenario.
121
+
122
+ ## Phase 6 — Cleanup + post-mortem
123
+
124
+ Required before declaring done:
125
+
126
+ - [ ] Original repro no longer reproduces (re-run the Phase 1 loop)
127
+ - [ ] Regression test passes (or absence of seam is documented)
128
+ - [ ] All `[DEBUG-...]` instrumentation removed (`grep` the prefix)
129
+ - [ ] Throwaway prototypes deleted (or moved to a clearly-marked debug location)
130
+ - [ ] The hypothesis that turned out correct is stated in the commit / PR message — so the next debugger learns
131
+
132
+ **Then ask: what would have prevented this bug?** If the answer involves architectural change (no good test seam, tangled callers, hidden coupling), flag it for the user as an architecture improvement opportunity. Make the recommendation **after** the fix is in, not before — you have more information now than when you started.
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env bash
2
+ # HITL (Human-In-The-Loop) debugging script template.
3
+ # Used when a human must interact to reproduce a bug.
4
+ # Drives the human through structured steps and captures output.
5
+
6
+ set -euo pipefail
7
+
8
+ LOG_DIR="${DEBUG_LOG_DIR:-/tmp/debug-hitl}"
9
+ mkdir -p "$LOG_DIR"
10
+
11
+ TIMESTAMP=$(date +%Y%m%d-%H%M%S)
12
+ LOG_FILE="$LOG_DIR/hitl-$TIMESTAMP.log"
13
+
14
+ echo "[HITL] Starting debug session. Output: $LOG_FILE"
15
+ echo "[HITL] Timestamp: $TIMESTAMP"
16
+ echo ""
17
+
18
+ step() {
19
+ local n=$1; shift
20
+ echo "" | tee -a "$LOG_FILE"
21
+ echo "=== Step $n: $* ===" | tee -a "$LOG_FILE"
22
+ echo "[HITL] Press Enter when ready..." | tee -a "$LOG_FILE"
23
+ read -r
24
+ }
25
+
26
+ capture() {
27
+ local label=$1; shift
28
+ echo "[HITL] Capturing: $label" | tee -a "$LOG_FILE"
29
+ echo "--- $label ---" >> "$LOG_FILE"
30
+ "$@" 2>&1 | tee -a "$LOG_FILE"
31
+ echo "--- end $label ---" >> "$LOG_FILE"
32
+ }
33
+
34
+ # --- Steps go here ---
35
+
36
+ step 1 "Reproduce the issue"
37
+ echo "Follow these steps to reproduce:" | tee -a "$LOG_FILE"
38
+ echo " 1. <describe step>" | tee -a "$LOG_FILE"
39
+ echo " 2. <describe step>" | tee -a "$LOG_FILE"
40
+ echo " 3. Observe: <expected vs actual>" | tee -a "$LOG_FILE"
41
+ echo ""
42
+ echo "[HITL] Paste the output or error message, then press Enter:" | tee -a "$LOG_FILE"
43
+ read -r user_output
44
+ echo "User observed: $user_output" >> "$LOG_FILE"
45
+
46
+ step 2 "Collect diagnostics"
47
+ capture "git-status" git status --short
48
+ capture "git-diff" git diff --stat
49
+ capture "recent-logs" tail -50 /var/log/app.log 2>/dev/null || echo "(no log file)"
50
+
51
+ step 3 "Check environment"
52
+ capture "env" env | grep -v -i -E 'secret|password|token|key' || true
53
+
54
+ echo "" | tee -a "$LOG_FILE"
55
+ echo "[HITL] Session complete. Log: $LOG_FILE" | tee -a "$LOG_FILE"
@@ -0,0 +1,91 @@
1
+ ---
2
+ name: exec
3
+ description: "当用户调用 /exec 并提供已批准的主 Spec 时使用。负责编排逐 Spec worktree、波次 workflow 执行、合并、验证和最终代码审查。"
4
+ ---
5
+
6
+ # 执行多 Spec 任务
7
+
8
+ 在目标仓库的 `.worktrees/` 隔离 worktree 中执行已批准的子 Spec。主工作区始终是合入目标;编排状态只保存在当前会话上下文中。
9
+
10
+ ## 前置条件
11
+
12
+ - 主 Spec 索引至少两个子 Spec。
13
+ - 确认唯一目标仓库;主工作区位于命名分支且工作树干净。
14
+ - `.worktrees/` 已被目标仓库 `.gitignore` 覆盖;没有就先提示用户加入。
15
+
16
+ ## 执行
17
+
18
+ 1. 读取主 Spec 和全部子 Spec,确认共享契约、依赖 DAG、验证命令。
19
+ 2. 按依赖波次创建任务列表。
20
+ 3. 每波:从主工作区 HEAD 建分支,在 `<repo>/.worktrees/exec-<任务>/<子任务>` 建 worktree,再派遣整波节点。前置波全部合入后才创建下一波 worktree。
21
+
22
+ ## 波次执行:workflow 驱动
23
+
24
+ 每波恰好一次 `workflow` 工具调用:脚本只做波内扇出与 CR 循环——worktree 创建、合并、清理全在主线程(workflow 引擎无文件系统,脚本碰不了 git)。节点契约(prompt 要素、schema、失败语义)见 `dev` skill。本波 specs 经 workflow 的 `args` 传入,每个元素带具体值:`{ id, specPath, worktree, contracts, verify }`(`worktree` 为绝对路径)。
25
+
26
+ ```js
27
+ const specs = args.specs
28
+ // 质量档 = 本会话可用的最强模型。留空即不覆盖路由:子代理继承父会话的
29
+ // provider/model,任何环境都能跑。有固定质量档时只改这一处,例如:
30
+ // const quality = { provider: '<provider>', model: '<model>' }
31
+ const quality = {}
32
+ const implSchema = { type: 'object', required: ['status', 'commit', 'verification', 'summary'], properties: { status: { type: 'string', enum: ['complete', 'blocked'] }, commit: { type: 'string' }, verification: { type: 'string' }, summary: { type: 'string' }, blocker: { type: 'string' } }, additionalProperties: false }
33
+ const verdictSchema = { type: 'object', required: ['status', 'findings'], properties: { status: { type: 'string', enum: ['APPROVED', 'APPROVED_WITH_NOTES', 'CHANGES_REQUIRED', 'BLOCKED'] }, findings: { type: 'array', items: { type: 'string' } } }, additionalProperties: false }
34
+ const implPrompt = (s) => `你在隔离 worktree ${s.worktree} 中工作:bash 一律 workdir=${s.worktree},文件工具用绝对路径。以预建 worktree 模式遵循 main-agent-driven-development skill(跳过其内部 CR 步骤,CR 由本编排循环承担,节点不自行派 reviewer)。只实现 ${s.specPath} 的子 Spec,遵守共享契约:${s.contracts}。运行验证:${s.verify}。提交后按 schema 报告。不得合入或删除 worktree;阻塞时 status=blocked 并填 blocker。`
35
+ const reviewPrompt = (s, impl) => `只读审查 worktree ${s.worktree} 相对基线的完整 diff(bash 一律 workdir=${s.worktree})。Spec:${s.specPath}。实现摘要:${impl.summary}。已跑验证:${impl.verification}。按严重度列出问题,每条附文件与行号;按 schema 四态判定。不得编辑、提交、合并。`
36
+ const fixPrompt = (s, impl, verdict) => `你在隔离 worktree ${s.worktree} 中继续前人的实现(bash 一律 workdir=${s.worktree},文件工具用绝对路径)。此前摘要:${impl.summary}(commit ${impl.commit})。reviewer 发现:${verdict.findings.join(';')}。逐条修复真实问题,对误报在 summary 中给出理由;重跑验证:${s.verify};提交后按 schema 报告。不得合入。`
37
+ log(`wave: dispatching ${specs.length} spec(s)`)
38
+ return await parallel(specs.map((s) => async () => {
39
+ let impl = await agent(implPrompt(s), { label: `${s.id}-impl`, schema: implSchema, ...quality })
40
+ if (!impl || impl.status !== 'complete') return { spec: s.id, status: 'node-failed', impl }
41
+ let verdict = await agent(reviewPrompt(s, impl), { label: `${s.id}-cr-1`, schema: verdictSchema, ...quality })
42
+ let round = 1
43
+ while (verdict && verdict.status === 'CHANGES_REQUIRED' && round < 5) {
44
+ impl = await agent(fixPrompt(s, impl, verdict), { label: `${s.id}-fix-${round}`, schema: implSchema, ...quality })
45
+ if (!impl || impl.status !== 'complete') return { spec: s.id, status: 'fix-failed', round }
46
+ round += 1
47
+ verdict = await agent(reviewPrompt(s, impl), { label: `${s.id}-cr-${round}`, schema: verdictSchema, ...quality })
48
+ }
49
+ return { spec: s.id, status: verdict ? verdict.status : 'review-failed', rounds: round, impl, verdict }
50
+ }))
51
+ ```
52
+
53
+ 要点:
54
+
55
+ - CR 循环是代码不是纪律:每轮全新 reviewer(`agent()` 一次一代理)、schema 校验判定、5 轮熔断。
56
+ - 子代理失败 resolve `null` → 该 Spec 记 `node-failed`,`parallel` 等整波,单个失败不拖垮其他 Spec。
57
+ - 质量档(本会话最强模型)在脚本顶部一处定义,impl / review / fix 节点全部使用;留空则由子代理继承父会话路由。
58
+ - 调用前台阻塞至整波结束,波内无人工交互;节点的疑问一律按 blocked 上报,由主线程处置。
59
+ - 返回值是结构化波次报告:只有 `status` 为 `APPROVED` / `APPROVED_WITH_NOTES` 的 Spec 进入合并。
60
+
61
+ ## 合并
62
+
63
+ 对每个完成节点:确认已提交且验证通过 → 检查变更文件排除越界 → 合入主工作区 → 合并后验证 → 删除 worktree 和子分支。
64
+
65
+ 小而明确的冲突由主代理直接解决。复杂冲突用 `.worktrees/` 下新的修复 worktree 和开发节点。不得合并失败或阻塞的节点。
66
+
67
+ ## 最终审查
68
+
69
+ 所有子 Spec 合入后,运行完整集成验证,然后派遣一个全新只读 reviewer 子代理(质量档 = 本会话最强模型)审查全量集成结果:
70
+
71
+ ```
72
+ subagent(
73
+ prompt="只读审查 <workspace> 的 <base>..HEAD diff(bash 一律 workdir=<workspace>)。
74
+ 主 Spec: <path> | 全部子 Spec | 共享契约 | 变更文件 | 实现摘要 | 验证结果。
75
+ 按严重度列出问题,每条附文件与行号。
76
+ 返回 APPROVED / APPROVED_WITH_NOTES / CHANGES_REQUIRED / BLOCKED。不得编辑、提交、合并。"
77
+ )
78
+ ```
79
+
80
+ 用 `wait_subagent` 等待。`CHANGES_REQUIRED` 时不需要询问用户——主代理直接分类并修复所有真实 bug,对误报和既有行为在上下文中简述理由;修复后重新验证,再派全新 reviewer 复审。软熔断 3 轮后仍存在严重或阻塞问题则停止修复循环,如实向用户报告剩余问题。
81
+
82
+ ## 收尾
83
+
84
+ 运行完整验证,核对 Spec,报告分支、提交、验证结果和遗留问题,询问用户下一步。未经用户选择不得创建 PR 或删除分支。
85
+
86
+ ## KISS
87
+
88
+ - 一个子 Spec、一个 worktree、一个开发节点。
89
+ - 主线程只持仓库拓扑(分支/worktree/合并/清理);波内扇出交给一次 workflow 调用。
90
+ - 状态只保存在会话上下文;不增加集成分支、调度脚本或状态文件。
91
+ - 等待靠完成通知自动送达,需要结果才继续时再等待。不得用 `sleep` 轮询。
@@ -0,0 +1,23 @@
1
+ ---
2
+ name: handoff
3
+ description: 把当前对话压缩成一份交接文档,供下一个全新 agent 接续。
4
+ argument-hint: "下一个会话将用来做什么?"
5
+ disable-model-invocation: true
6
+ ---
7
+
8
+ 把当前对话压缩成一份交接文档,让全新 agent 无需重读父会话即可接续工作。
9
+
10
+ ## 存放与复用
11
+
12
+ - **已有则复写**:若会话上下文中已存在交接文档(本会话早前写过,或会话开启时注入了一份),整篇重写该文件,不新建。
13
+ - **否则新建**:工作区有 `.workspace-docs/` 时写 `.workspace-docs/notes/<仓库或主题>/`,交接本质上就是一篇 note;否则写目标仓库现有的文档目录。文件名带日期前缀,沿用目录内现有命名风格。
14
+ - 写入 `.workspace-docs/` 时新建文件带 OKF 头(`type: Note` 及 `title`/`description`/`tags`/`status`/`repos`),不登记 `index.md`;提交/推送遵循该仓库既有纪律。目标仓库无此约定时用其现有文档格式。
15
+ - 两个位置都不存在时,回退写 OS 临时目录。
16
+
17
+ ## 内容要求
18
+
19
+ - 含「建议技能」一节,提示接手 agent 应调用的技能。
20
+ - 不复述已落盘物(spec、plan、ADR、issue、commit、diff)的内容,只给路径或 URL。
21
+ - 只写客观事实与验证证据,不写自我免责叙事。
22
+ - 脱敏 API key、密码、个人身份信息等敏感内容。
23
+ - 用户带参数调用时,参数即下一会话焦点,据此裁剪文档。
@@ -0,0 +1,64 @@
1
+ ---
2
+ name: main-agent-driven-development
3
+ description: "仅当用户或编排技能显式调用 /main-agent-driven-development 来完成一个边界明确的实现任务时使用。"
4
+ ---
5
+
6
+ # 主代理驱动开发
7
+
8
+ 由当前 Agent 会话直接实现一个边界明确的 Spec。外层 `/exec` 编排器负责 worktree 和合入生命周期。
9
+
10
+ ## Worktree 模式
11
+
12
+ 只选一种:
13
+
14
+ - **预建模式** — 调用方已提供隔离 worktree 并声明负责生命周期时,直接使用;不得创建、合并或删除 worktree。
15
+ - **独立模式** — 否则识别目标仓库,要求基线位于命名分支,记录起始 SHA,在仓库 `.worktrees/` 下创建 worktree。
16
+
17
+ 所有操作必须在选定 worktree 内完成。隔离后不得编辑原始 checkout。
18
+
19
+ ## 流程
20
+
21
+ 1. 读取分配的 Spec 及相关仓库指令。
22
+ 2. 确认任务边界、依赖、验收标准和必要验证。只有缺失决策真正阻塞实现时才提问。
23
+ 3. 根据 Spec 初始化任务列表。
24
+ 4. 项目初始化 + 基线验证。基线失败时先报告;仅当失败明确无关且已记录时才继续。
25
+ 5. 直接实现,限定在 Spec 范围。遵循 `tdd` skill(本包已捆绑):先写失败测试,再写最小实现。
26
+ 6. 每个逻辑步骤后运行聚焦验证、自审 diff,内容完整时提交。验证标准:本轮运行命令并确认输出,不得以"应该通过"代替实际运行。
27
+ 7. 实现完成后运行全部必要验证。
28
+ 8. 验证失败或 CR 发现 bug 时,遵循 `diagnosing-bugs` skill(本包已捆绑):先建反馈循环找根因,再修复。
29
+ 9. 执行内部 CR 循环(见下文)。例外:作为 `/exec` dev 后端 workflow 波次中的实现节点时,**跳过本步**——CR 循环由编排脚本承担,节点只实现、验证、提交、报告。
30
+ 10. 提交全部已批准修复,向调用方报告。
31
+
32
+ 保持 KISS。优先复用既有模式,小而完整的修改,只做 Spec 必需的针对性重构。
33
+
34
+ ## 内部 CR 循环
35
+
36
+ 完整验证后,派遣恰好一个全新只读 reviewer 子代理(质量档 = 本会话最强模型):
37
+
38
+ ```
39
+ subagent(
40
+ prompt="Review <worktree-abs-path> from <base_sha> to HEAD.
41
+ Spec: <path>
42
+ Implementation summary: <brief>
43
+ Changed files: <list>
44
+ Verification already run: <commands and results>"
45
+ )
46
+ ```
47
+
48
+ 要求附文件和行号,返回一种状态:`APPROVED` / `APPROVED_WITH_NOTES` / `CHANGES_REQUIRED` / `BLOCKED`。
49
+
50
+ 用 `wait_subagent` 等待审查完成。收到结果后直接修复所有必要的正确性错误、Spec 缺口、安全问题、集成风险和验证缺口——不需要询问用户。验证并提交修复后,再用 `subagent` 派遣全新 reviewer。重复直到 `APPROVED` 或仅含非阻塞建议。
51
+
52
+ 不得自我批准,不得复用旧 reviewer。
53
+
54
+ ## 完成契约
55
+
56
+ 报告完成前必须满足:
57
+
58
+ - 所有分配的验收标准已实现
59
+ - 必要验证已在本轮运行并通过(有命令输出为证)
60
+ - 没有未提交的实现修改
61
+ - 内部 CR 已通过且无必要修复项
62
+ - 可提供最终 commit SHA
63
+
64
+ 如实报告阻塞。不得自行合入;调用方负责集成和清理。
@@ -0,0 +1,34 @@
1
+ ---
2
+ name: tdd
3
+ description: Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests.
4
+ ---
5
+
6
+ # Test-Driven Development
7
+
8
+ TDD is the red → green loop. This skill is the reference that makes that loop produce tests worth keeping: what a good test is, where tests go, the anti-patterns, and the rules of the loop. Every section applies on every cycle — consult them before and during the loop, not after.
9
+
10
+ ## What a good test is
11
+
12
+ Tests verify behavior through public interfaces, not implementation details. Code can change entirely; tests shouldn't. A good test reads like a specification — "user can checkout with valid cart" tells you exactly what capability exists — and survives refactors because it doesn't care about internal structure.
13
+
14
+ See [tests.md](tests.md) for examples and [mocking.md](mocking.md) for mocking guidelines.
15
+
16
+ ## Seams — where tests go
17
+
18
+ A **seam** is the public boundary you test at: the interface where you observe behavior without reaching inside. Tests live at seams, never against internals.
19
+
20
+ **Test only at pre-agreed seams.** Before writing any test, write down the seams under test and confirm them with the user. No test is written at an unconfirmed seam. You can't test everything — agreeing the seams up front is how testing effort lands on the critical paths and complex logic instead of every edge case.
21
+
22
+ Ask: "What's the public interface, and which seams should we test?"
23
+
24
+ ## Anti-patterns
25
+
26
+ - **Implementation-coupled** — mocks internal collaborators, tests private methods, or verifies through a side channel (querying the database instead of using the interface). The tell: the test breaks when you refactor but behavior hasn't changed.
27
+ - **Tautological** — the assertion recomputes the expected value the way the code does (`expect(add(a, b)).toBe(a + b)`, a snapshot derived by hand the same way, a constant asserted equal to itself), so it passes by construction and can never disagree with the code. Expected values must come from an independent source of truth — a known-good literal, a worked example, the spec.
28
+ - **Horizontal slicing** — writing all tests first, then all implementation. Bulk tests verify _imagined_ behavior: you test the _shape_ of things rather than user-facing behavior, the tests go insensitive to real changes, and you commit to test structure before understanding the implementation. Work in **vertical slices** instead — one test → one implementation → repeat, each test a **tracer bullet** that responds to what the last cycle taught you.
29
+
30
+ ## Rules of the loop
31
+
32
+ - **Red before green.** Write the failing test first, then only enough code to pass it. Don't anticipate future tests or add speculative features.
33
+ - **One slice at a time.** One seam, one test, one minimal implementation per cycle.
34
+ - **Refactoring is not part of the loop.** It belongs to the review stage (see the `code-review` skill), not the red → green implementation cycle.
@@ -0,0 +1,59 @@
1
+ # When to Mock
2
+
3
+ Mock at **system boundaries** only:
4
+
5
+ - External APIs (payment, email, etc.)
6
+ - Databases (sometimes - prefer test DB)
7
+ - Time/randomness
8
+ - File system (sometimes)
9
+
10
+ Don't mock:
11
+
12
+ - Your own classes/modules
13
+ - Internal collaborators
14
+ - Anything you control
15
+
16
+ ## Designing for Mockability
17
+
18
+ At system boundaries, design interfaces that are easy to mock:
19
+
20
+ **1. Use dependency injection**
21
+
22
+ Pass external dependencies in rather than creating them internally:
23
+
24
+ ```typescript
25
+ // Easy to mock
26
+ function processPayment(order, paymentClient) {
27
+ return paymentClient.charge(order.total);
28
+ }
29
+
30
+ // Hard to mock
31
+ function processPayment(order) {
32
+ const client = new StripeClient(process.env.STRIPE_KEY);
33
+ return client.charge(order.total);
34
+ }
35
+ ```
36
+
37
+ **2. Prefer SDK-style interfaces over generic fetchers**
38
+
39
+ Create specific functions for each external operation instead of one generic function with conditional logic:
40
+
41
+ ```typescript
42
+ // GOOD: Each function is independently mockable
43
+ const api = {
44
+ getUser: (id) => fetch(`/users/${id}`),
45
+ getOrders: (userId) => fetch(`/users/${userId}/orders`),
46
+ createOrder: (data) => fetch('/orders', { method: 'POST', body: data }),
47
+ };
48
+
49
+ // BAD: Mocking requires conditional logic inside the mock
50
+ const api = {
51
+ fetch: (endpoint, options) => fetch(endpoint, options),
52
+ };
53
+ ```
54
+
55
+ The SDK approach means:
56
+ - Each mock returns one specific shape
57
+ - No conditional logic in test setup
58
+ - Easier to see which endpoints a test exercises
59
+ - Type safety per endpoint
@@ -0,0 +1,77 @@
1
+ # Good and Bad Tests
2
+
3
+ ## Good Tests
4
+
5
+ **Integration-style**: Test through real interfaces, not mocks of internal parts.
6
+
7
+ ```typescript
8
+ // GOOD: Tests observable behavior
9
+ test("user can checkout with valid cart", async () => {
10
+ const cart = createCart();
11
+ cart.add(product);
12
+ const result = await checkout(cart, paymentMethod);
13
+ expect(result.status).toBe("confirmed");
14
+ });
15
+ ```
16
+
17
+ Characteristics:
18
+
19
+ - Tests behavior users/callers care about
20
+ - Uses public API only
21
+ - Survives internal refactors
22
+ - Describes WHAT, not HOW
23
+ - One logical assertion per test
24
+
25
+ ## Bad Tests
26
+
27
+ **Implementation-detail tests**: Coupled to internal structure.
28
+
29
+ ```typescript
30
+ // BAD: Tests implementation details
31
+ test("checkout calls paymentService.process", async () => {
32
+ const mockPayment = jest.mock(paymentService);
33
+ await checkout(cart, payment);
34
+ expect(mockPayment.process).toHaveBeenCalledWith(cart.total);
35
+ });
36
+ ```
37
+
38
+ Red flags:
39
+
40
+ - Mocking internal collaborators
41
+ - Testing private methods
42
+ - Asserting on call counts/order
43
+ - Test breaks when refactoring without behavior change
44
+ - Test name describes HOW not WHAT
45
+ - Verifying through external means instead of interface
46
+
47
+ ```typescript
48
+ // BAD: Bypasses interface to verify
49
+ test("createUser saves to database", async () => {
50
+ await createUser({ name: "Alice" });
51
+ const row = await db.query("SELECT * FROM users WHERE name = ?", ["Alice"]);
52
+ expect(row).toBeDefined();
53
+ });
54
+
55
+ // GOOD: Verifies through interface
56
+ test("createUser makes user retrievable", async () => {
57
+ const user = await createUser({ name: "Alice" });
58
+ const retrieved = await getUser(user.id);
59
+ expect(retrieved.name).toBe("Alice");
60
+ });
61
+ ```
62
+
63
+ **Tautological tests**: Expected value restates the implementation, so the test passes by construction.
64
+
65
+ ```typescript
66
+ // BAD: Expected value is recomputed the way the code computes it
67
+ test("calculateTotal sums line items", () => {
68
+ const items = [{ price: 10 }, { price: 5 }];
69
+ const expected = items.reduce((sum, i) => sum + i.price, 0);
70
+ expect(calculateTotal(items)).toBe(expected);
71
+ });
72
+
73
+ // GOOD: Expected value is an independent, known literal
74
+ test("calculateTotal sums line items", () => {
75
+ expect(calculateTotal([{ price: 10 }, { price: 5 }])).toBe(15);
76
+ });
77
+ ```