@zeyue0329/xiaoma-cli 1.15.0 → 1.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/xmc-skills/1-analysis/xiaoma-auto-requirements-pipeline/steps/step-05-validate-prd.md +3 -1
- package/src/xmc-skills/4-implementation/xiaoma-auto-story-pipeline-batch/workflow.md +2 -2
- package/src/xmc-skills/5-full-pipeline/xiaoma-auto-prd-to-stories/steps/step-04-batch-create-stories.md +3 -2
- package/tools/installer/set-overrides.js +64 -27
- package/tools/validate-file-refs.js +5 -5
package/package.json
CHANGED
package/src/xmc-skills/1-analysis/xiaoma-auto-requirements-pipeline/steps/step-05-validate-prd.md
CHANGED
|
@@ -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
|
|
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
|
**步骤:**
|
|
@@ -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,
|
|
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
|
|
|
@@ -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,
|
|
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 }`
|
|
@@ -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
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
|
226
|
-
if (
|
|
227
|
-
refs.push({ file: filePath, raw:
|
|
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
|
|
380
|
-
if (
|
|
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]);
|