@zeyue0329/xiaoma-cli 1.15.0 → 1.16.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@zeyue0329/xiaoma-cli",
4
- "version": "1.15.0",
4
+ "version": "1.16.0",
5
5
  "description": "XiaoMa Universal AI Agent Framework",
6
6
  "keywords": [
7
7
  "agile",
@@ -110,7 +110,7 @@ For each iteration:
110
110
 
111
111
  ## Validation Blockers Checklist (阻断性问题检查清单)
112
112
 
113
- > **English summary (normative for non-Chinese readers):** The 8 blocker checks below are conditions that ALL must pass before step-06 is allowed to run. They cover (1) requirements-to-story traceability, (2) technical feasibility, (3) explicit business-value statement per epic, (4) SMART acceptance criteria, (5) identified cross-team dependencies, (6) quantified performance/scalability targets, (7) completed security review with no unmitigated CRITICAL items, and (8) compliance/legal requirements folded into stories. Any failed blocker halts the pipeline at step-05 and the auto-fix iterates. The "Validation Pass Criteria" section that follows defines the joint pass condition (all blockers + ≤3 unresolved MEDIUM + ≥85% doc-quality score + ≥5 stories for normal sprint). The "Beyond 3 Iterations Escalation" section defines the A/B/C/D handling matrix when the auto-fix loop exhausts `{max_validation_iterations}`. These checks are operative checklists — the validate-prd delegated workflow (`steps-v/step-v-*`) is responsible for performing the assertions; this file documents the gate semantics for the orchestrator and for auditors.
113
+ > **English summary (normative for non-Chinese readers):** The 8 blocker checks below are conditions that ALL must pass before step-06 is allowed to run. They cover (1) requirements-to-story traceability, (2) technical feasibility, (3) explicit business-value statement per epic, (4) SMART acceptance criteria, (5) identified cross-team dependencies, (6) quantified performance/scalability targets, (7) completed security review with no unmitigated CRITICAL items, and (8) compliance/legal requirements folded into stories. Any failed blocker halts the pipeline at step-05 and the auto-fix iterates. The "Validation Pass Criteria" section that follows defines the joint pass condition (all blockers + ≤3 unresolved MEDIUM + ≥85% doc-quality score + ≥5 stories for normal sprint). The "Beyond 3 Iterations Escalation" section defines the A/B/C/D handling matrix when the auto-fix loop exhausts `{max_validation_iterations}`. These checks are operative checklists — the delegated `xiaoma-prd` validate intent (its `references/validate.md` playbook walking `assets/prd-validation-checklist.md`) is responsible for performing the assertions; this file documents the gate semantics for the orchestrator and for auditors.
114
114
 
115
115
  以下条件**必须全部通过** — 任何一个失败都阻断进度到step-06:
116
116
 
@@ -134,6 +134,8 @@ PRD验证通过的充要条件:
134
134
  5. ✅ 文档质量评分 >= 85%
135
135
  6. ✅ Story计数 >= 5个 (如果目标是正常sprint)
136
136
 
137
+ > **管道(headless)模式语义注:** 在自动化管道运行中,第 4 条的 stakeholder 同意由管道调用方的授权代行(用户启动管道即视为授权 PM 自动决策;无人工会签环节);第 6 条仅适用于"正常 sprint"目标——对刻意收敛范围的项目(PRD/req 中显式声明 MVP 限定 N 个故事,N < 5),按其范围声明豁免,不构成验证失败。两条豁免均不需要额外记录 `{run_warnings}`。此外,部分 Blocker(跨团队依赖、合规)对单人/本地工具类项目可按"不适用(n/a)"判定通过——以 PRD 的项目类型与风险等级为准。
138
+
137
139
  ## 如果验证失败后超过3次迭代 (Beyond 3 Iterations Escalation)
138
140
 
139
141
  **步骤:**
@@ -129,3 +129,13 @@ skill = "xiaoma-auto-full-pipeline"
129
129
  code = "APS"
130
130
  description = "Auto PRD-to-Stories: from an existing prd.md, generate epics + per-story detailed files"
131
131
  skill = "xiaoma-auto-prd-to-stories"
132
+
133
+ [[agent.menu]]
134
+ code = "BR"
135
+ description = "Resolve a single bug end-to-end: intake, root-cause, fix, verify, close with a fix report"
136
+ skill = "xiaoma-bug-resolve"
137
+
138
+ [[agent.menu]]
139
+ code = "BB"
140
+ description = "Bug Resolve Batch: drain ALL pending bugs from the queue using Agent subprocess isolation"
141
+ skill = "xiaoma-bug-resolve-batch"
@@ -29,7 +29,7 @@ Main conversation (scheduler, minimal context)
29
29
 
30
30
  ### Core Rules (Strictly Follow)
31
31
 
32
- 1. **Each story processed by independent Agent** — Use Agent tool to launch general-purpose subprocess, pass complete story info and processing instructions to Agent, Agent independently completes the full story lifecycle (step-02 through step-08)
32
+ 1. **Each story processed by independent Agent** — Use the Agent tool to launch a **general-purpose subprocess with FULL tool-execution capability** (the complete tool set: file read/write/edit, shell/command execution, and test running — **NOT** a restricted or read-only subagent such as an explore/plan/search-only type). Pass complete story info and processing instructions to the Agent; the Agent independently completes the full story lifecycle (step-02 through step-08)
33
33
  2. **Main loop stays lightweight** — Main conversation only: query sprint-status → launch Agent → read Agent results → query remaining → continue or end
34
34
  3. **Serial processing** — Process one story at a time, wait for Agent completion before next, avoid concurrent modification conflicts
35
35
  4. **Fully automatic, no human intervention** — From start to finish, user needs no commands
@@ -171,7 +171,7 @@ Stories completed so far: {stories_completed}
171
171
 
172
172
  <critical>
173
173
 
174
- Use the Agent tool to launch a **general-purpose subprocess** to process this story.
174
+ Use the Agent tool to launch a **general-purpose subprocess with FULL tool-execution capability** to process this story. This subprocess **MUST** have the complete tool set enabled — file read/write/edit, shell/command execution, and test execution — because it runs the entire story lifecycle (create → validate → develop → review → test → fix → done). Do **NOT** delegate to a restricted or read-only subagent (e.g. an explore-, plan-, or search-only type): such agents cannot edit files or run commands, so the lifecycle would fail. When the host tool exposes a subagent-type selector, choose the type whose toolset is the full/unrestricted set (in Claude Code this is the `general-purpose` type).
175
175
 
176
176
  The Agent's prompt MUST contain the following complete information for independent work:
177
177
 
@@ -0,0 +1,6 @@
1
+ ---
2
+ name: xiaoma-bug-resolve
3
+ description: "Resolve a single bug end-to-end: intake from a defect platform, a bug-queue file, or the user's description; root-cause analysis; real-defect vs false-positive verdict; minimal-scope fix; verification with tests or browser automation; fix report plus status write-back. Use when the user says 'resolve bug', 'fix bug', 'fix test bug', 'BR', or '处理bug'."
4
+ ---
5
+
6
+ Follow the instructions in [workflow.md](workflow.md).
@@ -0,0 +1,67 @@
1
+ # DO NOT EDIT -- overwritten on every update.
2
+ #
3
+ # Workflow customization surface for xiaoma-bug-resolve. Mirrors the
4
+ # agent customization shape under the [workflow] namespace.
5
+ # Override in {project-root}/_xiaoma/custom/xiaoma-bug-resolve.toml
6
+ # (team, committed) or xiaoma-bug-resolve.user.toml (personal).
7
+
8
+ [workflow]
9
+
10
+ # --- Configurable below. Overrides merge per XiaoMa structural rules: ---
11
+ # scalars: override wins • arrays (persistent_facts, activation_steps_*): append
12
+ # arrays-of-tables with `code`/`id`: replace matching items, append new ones.
13
+
14
+ # Steps to run before the standard activation (config load, source resolution).
15
+ # Overrides append. Use for pre-flight loads, compliance checks, etc.
16
+
17
+ activation_steps_prepend = []
18
+
19
+ # Steps to run after activation but before the workflow begins.
20
+ # Overrides append.
21
+
22
+ activation_steps_append = []
23
+
24
+ # Persistent facts the workflow keeps in mind for the whole run: coding
25
+ # standards, architecture constraints, known fragile areas. Each entry is
26
+ # either a literal sentence or a `file:` reference (globs supported).
27
+ # Overrides append.
28
+
29
+ persistent_facts = [
30
+ "file:{project-root}/**/project-context.md",
31
+ ]
32
+
33
+ # Bug intake source. "auto" picks: platform when platform_spec is non-empty,
34
+ # else file when bug_list_file exists, else interactive.
35
+ # Explicit values: "interactive" | "file" | "platform"
36
+
37
+ bug_source = "auto"
38
+
39
+ # File mode: the bug-queue file (format: see workflow.md appendix).
40
+
41
+ bug_list_file = "{implementation_artifacts}/bug-queue.md"
42
+
43
+ # Platform mode: free-form instructions teaching the workflow how to operate
44
+ # your defect platform (queue query, detail fetch, status write-back, scope
45
+ # filter, data-verification tools). Contract and a complete MySQL-MCP example:
46
+ # workflow.md appendix "Platform Spec Contract".
47
+
48
+ platform_spec = ""
49
+
50
+ # Frontend base URL for browser-automation verification of interaction bugs.
51
+ # Can also be passed per-run as a `testUrl=...` arg. Empty = ask when needed.
52
+
53
+ test_url = ""
54
+
55
+ # Max fix-verify iterations before halting with a report.
56
+
57
+ max_fix_iterations = 3
58
+
59
+ # Subdirectory under {implementation_artifacts} where fix reports land.
60
+
61
+ fix_report_subdir = "bug-fixes"
62
+
63
+ # Executed after the closure summary. Override wins. Use for post-fix
64
+ # automation: notify a channel, link the report to a ticket, trigger CI.
65
+ # Leave empty for no custom post-completion behavior.
66
+
67
+ on_complete = ""
@@ -0,0 +1,265 @@
1
+ ---
2
+ name: bug-resolve
3
+ description: "Single-bug resolution workflow: intake from a defect platform, a bug-queue file, or the user's description; root-cause analysis; real-defect vs false-positive verdict; minimal-scope fix; verification; fix report and status write-back."
4
+ ---
5
+
6
+ # Bug Resolve Workflow
7
+
8
+ **Goal:** Take ONE bug from intake to closure — locate the root cause, judge whether it is a real defect or a false positive,
9
+ apply a minimal-scope fix, verify it, write a fix report, and update the bug's status at its source.
10
+
11
+ **Your Role:** You are a senior full-stack engineer handling defect resolution. You pinpoint root causes precisely, write
12
+ surgical fixes, and prove them with verification evidence. You never guess: every verdict cites `path:line` evidence.
13
+
14
+ - Communicate all responses in {communication_language} and generate all documents in {document_output_language}
15
+ - The user invoked this skill to get a bug FIXED. Present your analysis, then proceed to the fix without waiting for
16
+ ceremony — pause only at the decision points marked below
17
+
18
+ ---
19
+
20
+ ## INITIALIZATION
21
+
22
+ ### Resolve the Workflow Block
23
+
24
+ Run: `node {project-root}/_xiaoma/scripts/resolve_customization.js --skill {skill-root} --key workflow`
25
+
26
+ If the script fails, perform the merge manually: read `{skill-root}/customize.toml`, then overlay
27
+ `{project-root}/_xiaoma/custom/xiaoma-bug-resolve.toml` and `xiaoma-bug-resolve.user.toml` (scalars: override wins;
28
+ arrays: append).
29
+
30
+ Then:
31
+
32
+ 1. Execute each entry in `{workflow.activation_steps_prepend}` in order.
33
+ 2. Treat each entry in `{workflow.persistent_facts}` as foundational context. `file:` prefixes are paths or globs under
34
+ `{project-root}` (load contents); other entries are facts verbatim.
35
+ 3. Execute each entry in `{workflow.activation_steps_append}` in order.
36
+
37
+ ### Load Config
38
+
39
+ Load `{project-root}/_xiaoma/xmc/config.yaml` and resolve: `user_name`, `communication_language`,
40
+ `document_output_language`, `implementation_artifacts`, `project_knowledge`, and `date` as system-generated current
41
+ datetime. If `{implementation_artifacts}` is unresolved, fall back to `.` (the project root) and surface the fallback.
42
+
43
+ Set `{max_fix_iterations}` = `{workflow.max_fix_iterations}` (default 3) and `{fix_report_dir}` =
44
+ `{implementation_artifacts}/{workflow.fix_report_subdir}`. Initialize `fix_iteration` = 0.
45
+
46
+ ### Resolve the Bug Source Mode
47
+
48
+ Determine `{bug_source}` from `{workflow.bug_source}`:
49
+
50
+ - `"platform"` — an external defect platform operated per `{workflow.platform_spec}` (see the Platform Spec Contract
51
+ appendix). If the spec is empty, HALT: "bug_source is 'platform' but platform_spec is empty — configure it in
52
+ `_xiaoma/custom/xiaoma-bug-resolve.toml`."
53
+ - `"file"` — a bug-queue file at `{workflow.bug_list_file}` (see the Bug-Queue File Format appendix). If the file does
54
+ not exist, fall through to `interactive` and tell the user which path was checked.
55
+ - `"interactive"` — the bug comes from the user: their description, an error log, a stack trace, or an issue reference.
56
+ - `"auto"` (default) — pick the first that applies: `platform_spec` non-empty → `platform`; `{workflow.bug_list_file}`
57
+ exists → `file`; otherwise → `interactive`.
58
+
59
+ ### Parse Args
60
+
61
+ Optional args: a bug ID (platform/file mode — resolve that specific bug instead of the oldest pending one) or free text
62
+ (interactive mode — treat as the bug description). `testUrl=...` and `authToken=...` tokens override
63
+ `{workflow.test_url}` and supply credentials for browser verification.
64
+
65
+ ---
66
+
67
+ ## WORKFLOW
68
+
69
+ <workflow>
70
+
71
+ <step n="1" goal="Intake: acquire exactly one bug and its complete context">
72
+
73
+ **Platform mode:** Query the pending-bug queue per the platform spec (apply the spec's scope filter if it declares
74
+ one). Pick the bug matching the args bug ID, or the oldest pending bug. Fetch its full record. If the queue is empty,
75
+ tell the user there is nothing to process and stop.
76
+
77
+ **File mode:** Parse `{workflow.bug_list_file}`. Pick the entry matching the args bug ID, or the first entry with
78
+ status `pending`. If none, tell the user the queue is empty and stop.
79
+
80
+ **Interactive mode:** Collect from args and conversation: symptom description, reproduction steps, expected vs actual
81
+ behavior, error logs / stack traces / failing test names, affected page or module. Ask only for what is missing and
82
+ truly needed to start.
83
+
84
+ Output a bug summary: ID (or a short slug agreed with the user), title, severity (if known), type (frontend / backend /
85
+ data / infra), reproduction steps, error evidence.
86
+
87
+ Set `{slug}` = the bug ID when one exists, otherwise a short descriptive name, sanitized to lowercase alphanumeric with
88
+ hyphens.
89
+
90
+ </step>
91
+
92
+ <step n="2" goal="Root-cause analysis and verdict">
93
+
94
+ 1. Map the bug to code regions using every available signal: module / page URL → component or route; stack trace →
95
+ exact file and line; failing API → controller / service / data access chain; error text → grep target.
96
+ 2. Explore in parallel (use Explore subagents for broad sweeps; keep raw file dumps out of the main context): the
97
+ implicated frontend components, the backend call chain, recent `git log` for the touched area, and existing tests
98
+ covering it.
99
+ 3. Reproduce when feasible: a failing test, a script, or browser automation against `{test_url}` following the
100
+ reproduction steps. A confirmed reproduction upgrades the verdict's confidence; say so when reproduction is not
101
+ feasible.
102
+ 4. Reach ONE verdict:
103
+ - **Real defect** — the code is wrong; cite the defective `path:line` and explain the mechanism.
104
+ - **False positive** — the code is correct; the report stems from a wrong test expectation, bad test data, or an
105
+ environment issue. Cite the evidence proving correctness.
106
+ - **Need more info** — state exactly what evidence is missing and how to obtain it. In interactive use, ask the
107
+ user. When running as a delegated subprocess (batch mode), do NOT guess: report verdict `blocked` and stop.
108
+ 5. Present the verdict: root cause (`path:line`), mechanism, and — for real defects — the fix plan (files to change and
109
+ how). For false positives, skip to Step 5.
110
+
111
+ </step>
112
+
113
+ <step n="3" goal="Fix (real defects only)">
114
+
115
+ - Apply the fix with Edit, strictly scoped to the defect: no drive-by refactoring, no unrelated cleanup.
116
+ - Follow the project's conventions: CLAUDE.md, `project-context.md`, and `{workflow.persistent_facts}`.
117
+ - When the project has a test setup, add or extend a regression test that fails on the old code and passes on the fix.
118
+ - List every modified file with a one-line summary of the change.
119
+
120
+ </step>
121
+
122
+ <step n="4" goal="Verify the fix">
123
+
124
+ Run every verification lane that applies, in this order:
125
+
126
+ 1. **Tests** — discover the project's test command (package.json scripts, Makefile, CI config, project-context.md) and
127
+ run the suite scoped to the affected area, plus the new regression test.
128
+ 2. **Data layer** — if the fix changes a query or data logic and the platform spec declares a data-verification tool,
129
+ execute the corrected logic through it and compare against expectations.
130
+ 3. **Browser** — for bugs involving page interaction, when `{test_url}` is configured or provided: drive the page with
131
+ the available browser automation tooling (Playwright MCP or equivalent), follow the original reproduction steps, and
132
+ confirm the expected behavior. If the bug is interaction-related but no `{test_url}` is available, ask for it once
133
+ (interactive) or record the gap (batch); never block on it.
134
+ 4. If nothing can be executed locally (no test setup, no URL, no data tool), say so explicitly and downgrade the
135
+ closure claim to "fix applied, verification pending deployment".
136
+
137
+ If verification FAILS: return to Step 3 with the failure evidence. Increment `fix_iteration`; when it exceeds
138
+ `{max_fix_iterations}`, HALT — present the remaining failure, the attempts made, and hand back to the user (interactive)
139
+ or report verdict `failed` (batch).
140
+
141
+ Summarize the verification evidence: commands run, results, screenshots or response snippets where relevant.
142
+
143
+ </step>
144
+
145
+ <step n="5" goal="Close: fix report and status write-back">
146
+
147
+ 1. Write the fix report to `{fix_report_dir}/{slug}-fix.md` (create the directory if needed):
148
+
149
+ ```markdown
150
+ # Bug Fix Report: {slug}
151
+
152
+ | Field | Value |
153
+ | --- | --- |
154
+ | Bug ID / Title | ... |
155
+ | Source | platform / file / interactive |
156
+ | Verdict | real-defect / false-positive |
157
+ | Severity | ... |
158
+ | Date | {date} |
159
+
160
+ ## Root Cause
161
+
162
+ {mechanism, defective path:line citations}
163
+
164
+ ## Fix
165
+
166
+ {modified files with per-file change summary; regression test added}
167
+
168
+ ## Verification
169
+
170
+ {lanes run and their evidence; explicit gaps if any}
171
+
172
+ ## Residual Risk / Follow-ups
173
+
174
+ {anything the next engineer should know; empty if none}
175
+ ```
176
+
177
+ For false positives, the Fix section instead documents why the code is correct and what in the report was wrong
178
+ (test expectation, data, environment).
179
+
180
+ 2. Write the status back to the source:
181
+ - **Platform mode:** execute the spec's status write-back — `fixed` for verified real defects, `false-positive` for
182
+ misreports. Re-query to confirm the update took effect.
183
+ - **File mode:** update the bug's entry in `{workflow.bug_list_file}` — status to `fixed` or `false-positive`, plus
184
+ the report path.
185
+ - **Interactive mode:** nothing to write back; the report is the artifact.
186
+
187
+ 3. Output the closure summary: verdict, modified files, verification result, report path.
188
+
189
+ 4. **Platform / file mode:** report how many pending bugs remain. If more than one remains, mention that
190
+ `xiaoma-bug-resolve-batch` (menu code BB) can drain the whole queue automatically.
191
+
192
+ 5. Execute `{workflow.on_complete}` if non-empty.
193
+
194
+ </step>
195
+
196
+ </workflow>
197
+
198
+ ---
199
+
200
+ ## Appendix: Platform Spec Contract
201
+
202
+ `{workflow.platform_spec}` is a free-form instruction block (TOML multi-line string) that teaches this workflow how to
203
+ operate your defect platform through the tools available in the session (typically MCP tools). A usable spec answers:
204
+
205
+ 1. **Queue query** — how to list pending bugs, oldest first: exact tool name plus the query or call template.
206
+ 2. **Detail fetch** — how to fetch one bug's complete record by ID, and what the key fields mean (title, reproduction
207
+ steps, error logs, module, page URL, severity).
208
+ 3. **Status write-back** — how to mark a bug `fixed` and `false-positive` (and any other states the platform tracks).
209
+ 4. **Scope filter** *(optional)* — the field that isolates this project's bugs when the platform hosts several projects
210
+ (e.g. a JIRA project ID). If batch processing MUST be scope-filtered, the spec must say "scope is REQUIRED" — the
211
+ batch scheduler enforces it.
212
+ 5. **Data verification** *(optional)* — extra tools for verifying business data while reproducing or verifying (e.g. a
213
+ business-database query tool).
214
+
215
+ ### Example: an AI-test-platform defect library over a MySQL MCP tool
216
+
217
+ ```toml
218
+ platform_spec = """
219
+ Defect platform: AI test platform, MySQL defect library via the `mcp__dev-mysql__mysql_query` tool.
220
+ Table `t_defect_info`; status enum: 0=new, 1=verified, 2=fixed, 3=false-positive; soft delete flag `is_del`.
221
+ Scope is REQUIRED for batch: every SELECT/UPDATE/COUNT must carry AND jira_id = '<scope>' (JIRA project ID).
222
+
223
+ Queue query (oldest first):
224
+ SELECT defect_id, title, severity, defect_type, module_path, discover_time
225
+ FROM t_defect_info WHERE is_del = 0 AND defect_status = 0 [AND jira_id = '<scope>']
226
+ ORDER BY create_time ASC;
227
+
228
+ Detail fetch:
229
+ SELECT * FROM t_defect_info WHERE defect_id = '<id>' AND is_del = 0;
230
+ Key fields: steps (reproduction), error_front (frontend log), error_apis (API errors), url (page).
231
+
232
+ Status write-back (re-select afterwards to confirm):
233
+ fixed: UPDATE t_defect_info SET defect_status = 2 WHERE defect_id = '<id>' [AND jira_id = '<scope>'] AND is_del = 0;
234
+ false-positive: UPDATE t_defect_info SET defect_status = 3 WHERE defect_id = '<id>' [AND jira_id = '<scope>'] AND is_del = 0;
235
+
236
+ Data verification: business Oracle DB via `mcp__business-oracle__execute_query` for validating query logic and data.
237
+ """
238
+ ```
239
+
240
+ Adapt tool names, table, and fields to your environment; paste the result into
241
+ `{project-root}/_xiaoma/custom/xiaoma-bug-resolve.toml` under `[workflow]`.
242
+
243
+ ## Appendix: Bug-Queue File Format
244
+
245
+ `{workflow.bug_list_file}` is a markdown file owned by the team — any tracker export or hand-written list works. One
246
+ `##` section per bug:
247
+
248
+ ```markdown
249
+ # Bug Queue
250
+
251
+ ## BUG-001: Search returns no results for partial keywords
252
+
253
+ - status: pending <!-- pending | fixed | false-positive | blocked -->
254
+ - severity: high
255
+ - module: search
256
+ - steps: open /search, type "auth", press Enter
257
+ - expected: items whose names contain "auth"
258
+ - actual: empty list; works only on exact match
259
+ - evidence: logs/search-20260610.log
260
+ ```
261
+
262
+ The workflow reads the first `pending` entry (or the args-specified ID) and, at closure, rewrites that entry's
263
+ `status` line and appends `- report: {fix_report_dir}/{slug}-fix.md`. `blocked` is a human-set parking value: the
264
+ workflow itself only ever writes `fixed` or `false-positive` (a blocked verdict leaves the entry `pending`), and it
265
+ never picks entries whose status is anything other than `pending`.
@@ -0,0 +1,6 @@
1
+ ---
2
+ name: xiaoma-bug-resolve-batch
3
+ description: "Batch bug resolution with Agent subprocess isolation. Drains ALL pending bugs from the defect platform or bug-queue file, each resolved in an independent full-tool Agent context, until the queue is empty. Use when the user says 'batch resolve bugs', 'resolve all bugs', 'BB', '批量修复bug', or '循环处理bug'."
4
+ ---
5
+
6
+ Follow the instructions in [workflow.md](workflow.md).
@@ -0,0 +1,34 @@
1
+ # DO NOT EDIT -- overwritten on every update.
2
+ #
3
+ # Workflow customization surface for xiaoma-bug-resolve-batch (the scheduler).
4
+ # Bug-source settings (platform_spec, bug_list_file, test_url, ...) live in the
5
+ # sibling xiaoma-bug-resolve/customize.toml — configure them ONCE there; this
6
+ # scheduler resolves and inherits them. Override this file's keys in
7
+ # {project-root}/_xiaoma/custom/xiaoma-bug-resolve-batch.toml (team) or
8
+ # xiaoma-bug-resolve-batch.user.toml (personal).
9
+
10
+ [workflow]
11
+
12
+ # --- Configurable below. Overrides merge per XiaoMa structural rules: ---
13
+ # scalars: override wins • arrays (persistent_facts, activation_steps_*): append
14
+ # arrays-of-tables with `code`/`id`: replace matching items, append new ones.
15
+
16
+ # Steps to run before the scheduler initializes. Overrides append.
17
+
18
+ activation_steps_prepend = []
19
+
20
+ # Steps to run after initialization but before the batch loop. Overrides append.
21
+
22
+ activation_steps_append = []
23
+
24
+ # Persistent facts for the scheduler itself. Kept empty by default — the
25
+ # scheduler stays lightweight; project context is passed to each Agent
26
+ # subprocess instead. Overrides append.
27
+
28
+ persistent_facts = []
29
+
30
+ # Executed after the final batch report. Override wins. Use for post-batch
31
+ # automation: notify a channel, file a summary ticket, trigger CI.
32
+ # Leave empty for no custom post-completion behavior.
33
+
34
+ on_complete = ""
@@ -0,0 +1,277 @@
1
+ ---
2
+ name: bug-resolve-batch
3
+ description: "Batch bug resolution scheduler with Agent subprocess isolation. Drains all pending bugs from the defect platform or bug-queue file, each resolved in an independent full-tool Agent context."
4
+ ---
5
+
6
+ # Bug Resolve — Batch Mode (Agent Subprocess Isolation)
7
+
8
+ **Goal:** Process ALL pending bugs in the queue — each one resolved end-to-end (analyze → fix → verify → close) in an
9
+ **independent Agent subprocess** — fully automatic from start to finish, zero human intervention until the queue is
10
+ empty.
11
+
12
+ **Your Role:** Bug Batch Scheduler. You coordinate the loop and delegate each bug's full lifecycle to an independent
13
+ Agent subprocess. You stay lightweight — query queue, launch Agent, read results, continue or finalize.
14
+
15
+ - Communicate all responses in {communication_language} and generate all documents in {document_output_language}
16
+ - Absolutely DO NOT stop because of "milestones", "significant progress", or "session boundaries". Continue in a single
17
+ execution until ALL bugs are processed or a HALT condition is triggered
18
+ - **Fully automatic, no human intervention** — from start to finish, the user needs no commands
19
+
20
+ ---
21
+
22
+ ## Core Architecture: Agent-Isolated Processing
23
+
24
+ Each bug is delegated to an **independent Agent subprocess**. Agents naturally have isolated context spaces, releasing
25
+ upon completion without polluting the main conversation — no `/clear` needed. The main conversation only handles the
26
+ lightweight scheduling loop.
27
+
28
+ ```text
29
+ Main conversation (scheduler, minimal context)
30
+ ├── Agent #1 → Resolve Bug A (isolated context, released after completion)
31
+ ├── Agent #2 → Resolve Bug B (isolated context, released after completion)
32
+ ├── Agent #3 → Resolve Bug C (isolated context, released after completion)
33
+ └── ...until the queue is empty
34
+ ```
35
+
36
+ ### Core Rules (Strictly Follow)
37
+
38
+ 1. **Each bug processed by an independent Agent** — Use the Agent tool to launch a **general-purpose subprocess with
39
+ FULL tool-execution capability** (the complete tool set: file read/write/edit, shell/command execution, and test
40
+ running — **NOT** a restricted or read-only subagent such as an explore/plan/search-only type). Pass complete bug
41
+ info and processing instructions; the Agent independently completes analyze → fix → verify → close
42
+ 2. **Main loop stays lightweight** — the scheduler only: queries the queue → launches an Agent → reads results →
43
+ queries remaining → continues or ends
44
+ 3. **Serial processing** — one bug at a time; wait for Agent completion before the next, avoiding concurrent
45
+ modification conflicts
46
+ 4. **Scope is a hard isolation boundary** — when a scope filter is in effect, EVERY queue query, detail fetch, and
47
+ status write-back (scheduler-side and Agent-side) must carry it
48
+
49
+ ---
50
+
51
+ ## INITIALIZATION
52
+
53
+ ### Resolve Configuration
54
+
55
+ 1. **Single-bug workflow block** (the single source of truth for bug-source settings):
56
+ run `node {project-root}/_xiaoma/scripts/resolve_customization.js --skill {skill-root}/../xiaoma-bug-resolve --key workflow`
57
+ → yields `bug_source`, `bug_list_file`, `platform_spec`, `test_url`, `max_fix_iterations`, `fix_report_subdir`, and
58
+ the sibling's `persistent_facts` (project knowledge destined for every Agent subprocess — resolve `file:` entries
59
+ to their contents now, keep literal entries verbatim).
60
+ 2. **Own workflow block:** run the same script with `--skill {skill-root}` → yields this scheduler's
61
+ `activation_steps_*`, `persistent_facts`, `on_complete`. Execute prepend steps, load facts, execute append steps.
62
+ 3. Load `{project-root}/_xiaoma/xmc/config.yaml` and resolve: `user_name`, `communication_language`,
63
+ `document_output_language`, `implementation_artifacts`, `project_knowledge`, and `date` as system-generated current
64
+ datetime.
65
+
66
+ If the resolver script fails, perform the merges manually (customize.toml + `_xiaoma/custom/<skill-name>.toml` +
67
+ `<skill-name>.user.toml`; scalars override, arrays append).
68
+
69
+ ### Paths
70
+
71
+ - `single_bug_workflow` = `{skill-root}/../xiaoma-bug-resolve/workflow.md` (the sibling single-bug skill — each Agent
72
+ subprocess follows its Step 2–5 procedure)
73
+ - `batch_status_file` = `{implementation_artifacts}/bug-batch-status.json`
74
+
75
+ ### Parse Args
76
+
77
+ This scheduler accepts at most ONE positional token: the **scope filter** (e.g. a JIRA project ID isolating this
78
+ project's bugs on a shared platform). Rules:
79
+
80
+ - A token containing `=` is NOT a scope — treat scope as not provided and tell the user (key=value args such as
81
+ `testUrl=...` belong to the single-bug skill, not to batch mode). If args contain more than one token, HALT and
82
+ explain that batch mode takes a single scope token only.
83
+ - If `{platform_spec}` declares "scope is REQUIRED" and no scope token was provided: HALT —
84
+ "This platform requires a scope filter. Re-run as: `/xiaoma-bug-resolve-batch <scope>` (e.g. a JIRA project ID)."
85
+ - When a scope is in effect, it is a hard constraint inherited by every query and every Agent subprocess.
86
+ - File mode ignores scope (a bug-queue file is already project-local).
87
+
88
+ ---
89
+
90
+ ## WORKFLOW
91
+
92
+ <workflow>
93
+
94
+ <step n="1" goal="Validate the source is batchable and display the queue overview">
95
+
96
+ 1. Resolve the bug source exactly as the single-bug workflow does (`auto`: platform_spec non-empty → platform; else
97
+ bug_list_file exists → file). **Interactive is not batchable** — if neither platform nor file resolves, HALT:
98
+ "Batch mode needs a queue: configure platform_spec or create {bug_list_file}. For a single described bug, use
99
+ xiaoma-bug-resolve (BR)."
100
+ 2. Query queue statistics (scope-filtered when in effect): pending / fixed / false-positive counts.
101
+ 3. If pending == 0: go to Step 3 (final report).
102
+ 4. List the pending bugs (ID, title, severity, module) oldest first.
103
+ 5. Output the overview — include the scope line whenever a scope is in effect:
104
+
105
+ ```text
106
+ ===============================================
107
+ Bug Resolve — Batch Mode
108
+ (Agent Subprocess Isolation)
109
+ ===============================================
110
+ Scope: {scope or "—"}
111
+ Pending: {pending} | Fixed: {fixed} | False-positive: {false_positive}
112
+
113
+ Processing all {pending} bugs sequentially, each in an
114
+ independent Agent subprocess. Starting now...
115
+ -----------------------------------------------
116
+ ```
117
+
118
+ 6. Initialize tracking: `bugs_fixed` = 0, `bugs_false_positive` = 0, `bugs_failed` = 0, `failed_bug_keys` = [],
119
+ `bug_results` = [].
120
+
121
+ </step>
122
+
123
+ <step n="2" goal="Loop: resolve each bug in an independent Agent subprocess (fully automatic)">
124
+
125
+ ### 2.1 Find the Next Pending Bug
126
+
127
+ Query the queue fresh (scope-filtered when in effect), oldest first, skipping any key in `failed_bug_keys`. If no
128
+ processable bug remains, go to Step 3. Fetch the bug's complete record (platform detail fetch / full file entry).
129
+
130
+ Output: `Processing bug: {bug_key} — {title}`
131
+
132
+ ### 2.2 Launch the Independent Agent Subprocess
133
+
134
+ <critical>
135
+
136
+ Use the Agent tool to launch a **general-purpose subprocess with FULL tool-execution capability** to resolve this bug.
137
+ This subprocess **MUST** have the complete tool set enabled — file read/write/edit, shell/command execution, and test
138
+ execution — because it runs the entire bug lifecycle (analyze → fix → verify → close). Do **NOT** delegate to a
139
+ restricted or read-only subagent (e.g. an explore-, plan-, or search-only type): such agents cannot edit files or run
140
+ commands, so the lifecycle would fail. When the host tool exposes a subagent-type selector, choose the type whose
141
+ toolset is the full/unrestricted set (in Claude Code this is the `general-purpose` type).
142
+
143
+ The Agent's prompt MUST contain, so it can work fully independently:
144
+
145
+ **1. Bug record:** every field fetched in 2.1 (ID, title, reproduction steps, error logs, module, page URL, severity,
146
+ type) — pass actual content, not references. **Plus the scope value as a hard constraint when in effect: every
147
+ platform query and status write-back must carry it.**
148
+
149
+ **2. Configuration (inline all resolved values):** `communication_language`, `document_output_language`,
150
+ `implementation_artifacts`, `max_fix_iterations`, `fix_report_subdir`, `test_url`, the resolved bug-source mode, and —
151
+ verbatim — `platform_spec` (platform mode) or the `bug_list_file` path (file mode).
152
+
153
+ **3. Procedure:** read `{single_bug_workflow}` and execute its Step 2 (root-cause analysis and verdict), Step 3 (fix —
154
+ real defects only), Step 4 (verify), and Step 5 items 1–3 ONLY (fix report, status write-back, closure summary — skip
155
+ items 4–5: queue reporting and `on_complete` belong to this scheduler). Intake (Step 1) is already done — the record
156
+ above is authoritative. Operating constraints:
157
+
158
+ - You are running unattended: never wait for a user. Where the single-bug workflow says "ask the user", instead record
159
+ the gap; if the verdict cannot be reached without missing evidence, return verdict `blocked` — do NOT guess
160
+ - Fix strictly within the defect's scope; follow project conventions (CLAUDE.md, project-context.md)
161
+ - Track every file you modify. If you finish with verdict `blocked` or `failed`, restore the files you modified to
162
+ their pre-run state (leave files that already had unrelated local changes alone) and say so in `error` — the next
163
+ bug must start from a clean tree
164
+ - Write the fix report and execute the status write-back exactly as Step 5 specifies (status updates carry the scope
165
+ when in effect)
166
+
167
+ **4. Project context:** include the content of `project-context.md` if it exists (actual content, not just the path),
168
+ plus the sibling `persistent_facts` resolved during INITIALIZATION (file contents inlined, literal entries verbatim).
169
+
170
+ **5. Return format** — when done, return exactly:
171
+
172
+ ```text
173
+ BUG_RESULT:
174
+ - bug_key: {bug_key}
175
+ - verdict: fixed | false-positive | blocked | failed
176
+ - files_modified: [list of files, empty for false-positive/blocked]
177
+ - report_file: {path to the fix report, empty if not written}
178
+ - verification: {one-line verification outcome or gap}
179
+ - summary: {one-line description of what was done}
180
+ - error: {message if blocked/failed, empty otherwise}
181
+ ```
182
+
183
+ </critical>
184
+
185
+ ### 2.3 Read the Agent Result and Handle Errors
186
+
187
+ - `verdict: fixed` → increment `bugs_fixed`
188
+ - `verdict: false-positive` → increment `bugs_false_positive`
189
+ - `verdict: blocked`, `failed`, or no parseable BUG_RESULT → increment `bugs_failed`, append the bug key to
190
+ `failed_bug_keys` (prevents re-selecting it this run — its source status stays pending, so a later run can retry),
191
+ output `Bug {bug_key} blocked/failed — skipping and continuing.` — do NOT halt the batch
192
+ - Append the result to `bug_results`; output a one-line result for this bug
193
+
194
+ ### 2.4 Refresh and Continue
195
+
196
+ Re-query the queue statistics (fresh, scope-filtered when in effect) and output a checkpoint:
197
+
198
+ ```text
199
+ --- Batch checkpoint: fixed {bugs_fixed} | false-positive {bugs_false_positive} | failed {bugs_failed} | remaining {pending} ---
200
+ ```
201
+
202
+ If a processable bug remains (pending minus `failed_bug_keys`): return to 2.1. Otherwise go to Step 3.
203
+
204
+ </step>
205
+
206
+ <step n="3" goal="Final report and machine-readable batch status">
207
+
208
+ 1. Query the final queue state (scope-filtered when in effect).
209
+ 2. Write `{batch_status_file}` atomically (write `.tmp`, then rename):
210
+
211
+ ```json
212
+ {
213
+ "pipeline": "bug-resolve-batch",
214
+ "date": "{date}",
215
+ "scope": "{scope_or_empty}",
216
+ "source_mode": "platform|file",
217
+ "bugs_fixed": 0,
218
+ "bugs_false_positive": 0,
219
+ "bugs_failed": 0,
220
+ "failed_bug_keys": [],
221
+ "bug_results": [
222
+ {
223
+ "bug_key": "...",
224
+ "verdict": "fixed|false-positive|blocked|failed",
225
+ "files_modified": ["..."],
226
+ "report_file": "...",
227
+ "verification": "...",
228
+ "summary": "...",
229
+ "error": ""
230
+ }
231
+ ]
232
+ }
233
+ ```
234
+
235
+ If the batch is halted early, still write this file — the partial run is auditable.
236
+
237
+ 3. Output the final report:
238
+
239
+ ```text
240
+ ===============================================
241
+ Bug Resolve Batch — COMPLETE
242
+ ===============================================
243
+ Scope: {scope or "—"}
244
+
245
+ Fixed: {bugs_fixed}
246
+ False-positive: {bugs_false_positive}
247
+ Blocked/Failed (skipped, still pending at source): {bugs_failed}
248
+ Remaining pending: {pending}
249
+
250
+ Details:
251
+ | Bug | Verdict | Files | Report |
252
+ |-----|---------|-------|--------|
253
+ | ... | ... | ... | ... |
254
+
255
+ Batch status: {batch_status_file}
256
+ ===============================================
257
+ ```
258
+
259
+ 4. If `bugs_failed` > 0: list each failed/blocked bug with its `error` and what evidence or access would unblock it.
260
+ 5. Execute `{workflow.on_complete}` if non-empty. **HALT** — batch complete.
261
+
262
+ </step>
263
+
264
+ </workflow>
265
+
266
+ ---
267
+
268
+ ## Important Notes
269
+
270
+ 1. **Agent isolation is key** — each bug runs in an independent subprocess with isolated context; bugs never interfere
271
+ 2. **Serial safety** — one bug at a time avoids concurrent modifications to the same codebase
272
+ 3. **No infinite retries** — blocked/failed bugs go into `failed_bug_keys` and are skipped for the rest of THIS run;
273
+ their source status remains pending so the next run (or a human) can pick them up with fresh context
274
+ 4. **Scope discipline** — on shared defect platforms, running without the required scope would pull other projects'
275
+ bugs and mis-edit code and statuses; that is why the platform spec can make scope mandatory
276
+ 5. **Agent prompt quality** — each subprocess prompt must carry the complete bug record + resolved config + the
277
+ platform spec or file path + the return format, so it can work with zero callbacks
@@ -29,7 +29,7 @@ Main conversation (scheduler, minimal context)
29
29
 
30
30
  ### Core Rules (Strictly Follow)
31
31
 
32
- 1. **Each story processed by independent Agent** — Use the Agent tool to launch a general-purpose subprocess, pass complete story info + paths, have the Agent run `xiaoma-create-story` end-to-end, then return a structured summary
32
+ 1. **Each story processed by independent Agent** — Use the Agent tool to launch a **general-purpose subprocess with FULL tool-execution capability** (file read/write/edit plus codebase search — **NOT** a restricted or read-only subagent such as an explore/plan/search-only type, which cannot write the story file). Pass complete story info + paths, have the Agent run `xiaoma-create-story` end-to-end, then return a structured summary
33
33
  2. **Agent must STOP after xiaoma-create-story** — Do NOT chain into any downstream step. This is enforced in the Agent prompt with explicit hard-boundary instructions
34
34
  3. **Main loop stays lightweight** — Main scheduler only: query sprint-status → launch Agent → read result → query remaining → continue or end
35
35
  4. **Serial processing** — Process one story at a time, wait for Agent completion before next, avoid concurrent modification conflicts on `sprint-status.yaml`
@@ -104,7 +104,7 @@ Stories created so far: {stories_created} | Failed: {stories_failed}
104
104
 
105
105
  <critical>
106
106
 
107
- Use the **Agent tool** to launch a **general-purpose subprocess** to create this story.
107
+ Use the **Agent tool** to launch a **general-purpose subprocess with FULL tool-execution capability** to create this story — the subprocess MUST have the complete tool set enabled (file read/write/edit plus codebase search), because it has to write the story file and update `sprint-status.yaml`. Do **NOT** delegate to a restricted or read-only subagent (e.g. an explore-, plan-, or search-only type): such agents cannot write files, so story creation would fail. When the host tool exposes a subagent-type selector, choose the full/unrestricted toolset (in Claude Code this is the `general-purpose` type). *(This grants tool **capability**; the **HARD BOUNDARY** below then narrows the Agent's **behaviour** so it does not run tests or modify source files outside the planning/implementation artifacts — capability and behavioural scope are deliberately separate.)*
108
108
 
109
109
  The Agent's prompt MUST contain the following complete information for independent work:
110
110
 
@@ -234,6 +234,7 @@ Read the Agent's returned STORY_RESULT block.
234
234
 
235
235
  **IF the Agent failed (`final_status: failed`, no STORY_RESULT block, or file-missing case above):**
236
236
 
237
+ 0. **Transient-failure retry (at most once per story, BEFORE blacklisting):** distinguish an *environment-transient* failure from a *story-level* failure. The failure is transient when BOTH hold: (a) the Agent returned no STORY_RESULT at all (stream timeout, connection drop, runner crash — as opposed to an explicit `final_status: failed` verdict), AND (b) the disk shows no partial work (story file absent at `{implementation_artifacts}/{current_story_key}.md` AND sprint-status still has the story at `backlog`). In that case the story itself was never judged — re-launch the Agent subprocess once with the same prompt, tracking `{transient_retry_used}` per story key so each story gets at most ONE such retry. If the retry succeeds, take the success branch as normal; if it also fails, or the original failure was NOT transient (explicit `failed` verdict, or partial work found on disk), fall through to the blacklist steps below. *(Rationale: mirrors the bounded-retry pattern of auto-story-pipeline step-02 section 4. Without this, a single network blip permanently drops the story — and with it every FR that only this story covers — even though nothing about the story itself was wrong.)*
237
238
  1. Increment `{stories_failed}` by 1
238
239
  2. Add `{current_story_key}` to `{failed_story_keys}` (prevents infinite re-selection)
239
240
  3. Append to `{story_results}`: `{ story_key, final_status: "failed", error }`
@@ -29,4 +29,6 @@ XiaoMa Method,xiaoma-code-review,Code Review,CR,Story cycle: If issues back to D
29
29
  XiaoMa Method,xiaoma-checkpoint-preview,Checkpoint,CK,Guided walkthrough of a change from purpose and context into details. Use for human review of commits branches or PRs.,,,4-implementation,,,false,,
30
30
  XiaoMa Method,xiaoma-qa-generate-e2e-tests,QA Automation Test,QA,Generate automated API and E2E tests for implemented code. NOT for code review or story validation — use CR for that.,,,4-implementation,xiaoma-dev-story,,false,implementation_artifacts,test suite
31
31
  XiaoMa Method,xiaoma-retrospective,Retrospective,ER,Optional at epic end: Review completed work lessons learned and next epic or if major issues consider CC.,,,4-implementation,xiaoma-code-review,,false,implementation_artifacts,retrospective
32
- XiaoMa Method,xiaoma-investigate,Investigate,IN,Forensic case investigation calibrated to the input. Evidence-graded analysis with hypothesis tracking. Produces a structured case file.,,4-implementation,,,false,implementation_artifacts,investigation report
32
+ XiaoMa Method,xiaoma-investigate,Investigate,IN,Forensic case investigation calibrated to the input. Evidence-graded analysis with hypothesis tracking. Produces a structured case file.,,,4-implementation,,,false,implementation_artifacts,investigation report
33
+ XiaoMa Method,xiaoma-bug-resolve,Bug Resolve,BR,Resolve one bug end-to-end: intake from defect platform / bug-queue file / user description then root-cause fix verify and close with a fix report.,,,4-implementation,,,false,implementation_artifacts,fix report
34
+ XiaoMa Method,xiaoma-bug-resolve-batch,Bug Resolve Batch,BB,Drain ALL pending bugs sequentially with Agent subprocess isolation until the queue is empty.,,,4-implementation,xiaoma-bug-resolve,,false,implementation_artifacts,fix reports and batch status
@@ -292,34 +292,25 @@ async function applySetOverrides(overrides, xiaomaDir) {
292
292
  // config.toml on the next install (the schema-strict partition drops
293
293
  // it); re-pass `--set` if you need it sticky.
294
294
  const moduleYamlPath = path.join(xiaomaDir, moduleCode, 'config.yaml');
295
- if (await fs.pathExists(moduleYamlPath)) {
296
- try {
297
- const text = await fs.readFile(moduleYamlPath, 'utf8');
298
- const parsed = yaml.parse(text);
299
- if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
300
- // Preserve the installer's banner header (everything up to the
301
- // first non-comment line) so `_xiaoma/<module>/config.yaml` keeps
302
- // its provenance comments after we round-trip it.
303
- const headerLines = [];
304
- for (const line of text.split('\n')) {
305
- if (line.startsWith('#') || line.trim() === '') {
306
- headerLines.push(line);
307
- } else {
308
- break;
309
- }
310
- }
311
- for (const key of Object.keys(moduleOverrides)) {
312
- parsed[key] = moduleOverrides[key];
313
- }
314
- const body = yaml.stringify(parsed, { indent: 2, lineWidth: 0, minContentWidth: 0 });
315
- const header = headerLines.length > 0 ? headerLines.join('\n') + '\n' : '';
316
- await fs.writeFile(moduleYamlPath, header + body, 'utf8');
295
+ await patchModuleYaml(moduleYamlPath, moduleOverrides, { onlyExisting: false });
296
+
297
+ // `--set core.<key>` must ALSO refresh the "Core Configuration Values"
298
+ // snapshot that `generateModuleConfigs` spreads into every non-core
299
+ // module yaml (e.g. `_xiaoma/xmc/config.yaml`). Those snapshots are
300
+ // written BEFORE the overrides are applied, and the auto-* pipelines
301
+ // read the per-module yaml directly at runtime — without this pass a
302
+ // non-interactive `--set core.communication_language=中文` lands in the
303
+ // toml + core/config.yaml but the pipelines still see the stale default.
304
+ // Only keys that already exist in the target yaml are updated (they are
305
+ // the snapshot keys); core-only keys never leak into module yamls.
306
+ if (moduleCode === 'core') {
307
+ const entries = await fs.readdir(xiaomaDir, { withFileTypes: true });
308
+ for (const entry of entries) {
309
+ if (!entry.isDirectory() || entry.name === 'core') continue;
310
+ const siblingYaml = path.join(xiaomaDir, entry.name, 'config.yaml');
311
+ if (await fs.pathExists(siblingYaml)) {
312
+ await patchModuleYaml(siblingYaml, moduleOverrides, { onlyExisting: true });
317
313
  }
318
- } catch {
319
- // Per-module yaml unparseable — skip silently. The central toml was
320
- // already patched above, which is the user-visible state for the
321
- // current install. Carry-forward will fail next install but the
322
- // current install reflects the override.
323
314
  }
324
315
  }
325
316
  }
@@ -327,4 +318,50 @@ async function applySetOverrides(overrides, xiaomaDir) {
327
318
  return applied;
328
319
  }
329
320
 
321
+ /**
322
+ * Upsert key/value pairs into a per-module `config.yaml`, preserving the
323
+ * installer's banner header (leading comment block). With `onlyExisting`,
324
+ * only keys already present in the parsed yaml are updated — used for the
325
+ * core-snapshot refresh so core-only keys are not introduced into module
326
+ * yamls that never declared them.
327
+ *
328
+ * Unparseable yaml is skipped silently: the central toml was already
329
+ * patched, which is the user-visible state for the current install.
330
+ *
331
+ * @param {string} yamlPath absolute path to `_xiaoma/<module>/config.yaml`
332
+ * @param {Object<string, string>} keyValues
333
+ * @param {{onlyExisting: boolean}} opts
334
+ */
335
+ async function patchModuleYaml(yamlPath, keyValues, { onlyExisting }) {
336
+ if (!(await fs.pathExists(yamlPath))) return;
337
+ try {
338
+ const text = await fs.readFile(yamlPath, 'utf8');
339
+ const parsed = yaml.parse(text);
340
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return;
341
+ // Preserve the installer's banner header (everything up to the
342
+ // first non-comment line) so the file keeps its provenance comments
343
+ // after we round-trip it.
344
+ const headerLines = [];
345
+ for (const line of text.split('\n')) {
346
+ if (line.startsWith('#') || line.trim() === '') {
347
+ headerLines.push(line);
348
+ } else {
349
+ break;
350
+ }
351
+ }
352
+ let changed = false;
353
+ for (const key of Object.keys(keyValues)) {
354
+ if (onlyExisting && !Object.prototype.hasOwnProperty.call(parsed, key)) continue;
355
+ parsed[key] = keyValues[key];
356
+ changed = true;
357
+ }
358
+ if (!changed) return;
359
+ const body = yaml.stringify(parsed, { indent: 2, lineWidth: 0, minContentWidth: 0 });
360
+ const header = headerLines.length > 0 ? headerLines.join('\n') + '\n' : '';
361
+ await fs.writeFile(yamlPath, header + body, 'utf8');
362
+ } catch {
363
+ // Per-module yaml unparseable — skip silently (see doc comment).
364
+ }
365
+ }
366
+
330
367
  module.exports = { parseSetEntry, parseSetEntries, applySetOverrides, upsertTomlKey, tomlString };
@@ -222,9 +222,9 @@ function extractYamlRefs(filePath, content) {
222
222
  }
223
223
 
224
224
  // Check for {_xiaoma}/ refs
225
- const bmMatch = value.match(/\{_xiaoma\}\/[^\s'"<>})\]`]+/);
226
- if (bmMatch) {
227
- refs.push({ file: filePath, raw: bmMatch[0], type: 'project-root', line, key: keyPath });
225
+ const xmMatch = value.match(/\{_xiaoma\}\/[^\s'"<>})\]`]+/);
226
+ if (xmMatch) {
227
+ refs.push({ file: filePath, raw: xmMatch[0], type: 'project-root', line, key: keyPath });
228
228
  }
229
229
 
230
230
  // Check for relative paths
@@ -376,8 +376,8 @@ function resolveRef(ref) {
376
376
  const prMatch = ref.raw.match(/\{project-root\}\/_xiaoma\/([^\s'"<>})\]`]+)/);
377
377
  if (prMatch) return mapInstalledToSource(prMatch[0]);
378
378
 
379
- const bmMatch = ref.raw.match(/\{_xiaoma\}\/([^\s'"<>})\]`]+)/);
380
- if (bmMatch) return mapInstalledToSource(bmMatch[0]);
379
+ const xmMatch = ref.raw.match(/\{_xiaoma\}\/([^\s'"<>})\]`]+)/);
380
+ if (xmMatch) return mapInstalledToSource(xmMatch[0]);
381
381
 
382
382
  const bareMatch = ref.raw.match(/_xiaoma\/([^\s'"<>})\]`]+)/);
383
383
  if (bareMatch) return mapInstalledToSource(bareMatch[0]);