docs: add fault-details alignment design and plan

Capture the approved fault-details staged-skill design and implementation plan so the remaining work can be resumed from the documented contract.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
木炎
2026-04-10 18:10:02 +08:00
parent 7443b9da7f
commit 311cc1fee6
2 changed files with 1167 additions and 0 deletions

View File

@@ -0,0 +1,672 @@
# Fault Details Full Skill Alignment Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Upgrade `fault-details-report.collect_fault_details` into a real staged browser skill that matches the original fault-details workflow, and make `claw-new` interpret the returned artifact status correctly in the direct-submit path.
**Architecture:** Keep routing and direct-skill selection in `claw-new`, but move all fault-details collection, normalization, classification, summary, export, and report-log behavior into the staged skill under `skill_staging`. Implement the staged skill as a true browser-eval entrypoint that remains valid in page context, while exposing testable pure helpers through an environment-safe export guard for `node:test`; then add a narrow Rust artifact interpreter in `src/compat/direct_skill_runtime.rs` so `ok` / `partial` / `empty` map to successful task completion while `blocked` / `error` map to failed completion.
**Tech Stack:** Rust 2021, `serde_json`, existing `BrowserPipeTool` / `browser_script` runtime, `node:test`, staged skill fixtures, Cargo integration tests.
---
## Execution Context
- Follow @superpowers:test-driven-development for every behavior change.
- Follow @superpowers:verification-before-completion before claiming each task is done.
- Do **not** create a git worktree unless the user explicitly asks. This repo preference is already established.
- Keep scope tight. Do **not** add a new browser protocol, new dispatch metadata, new UI opener behavior, or Rust-side fault classification logic.
- Keep the current direct path bootstrap requirement intact: the user instruction must still include an explicit `YYYY-MM`, but the staged skill must treat the page-selected range as the source of truth for collection once execution begins.
- Preserve parity with the original packages real behavior: port the original classification table, `qxxcjl`-based reason heuristics, canonical detail mapping, summary aggregation rules, localhost export call, and report-log call into the staged skill rather than implementing a fixture-only subset.
## File Map
### Existing files to modify in `claw-new`
- Modify: `src/compat/direct_skill_runtime.rs`
- add narrow structured artifact parsing and status-to-summary mapping
- keep direct-skill routing/config ownership unchanged
- Modify: `tests/agent_runtime_test.rs`
- add direct-submit regressions for `ok`, `partial`, `empty`, `blocked`, and `error`
- Modify: `tests/browser_script_skill_tool_test.rs`
- add browser-script execution-shape regression for browser-eval return payloads used by fault-details
### Existing files to modify in `skill_staging`
- Modify: `D:/data/ideaSpace/rust/sgClaw/claw/claw/skills/skill_staging/skills/fault-details-report/scripts/collect_fault_details.js`
- replace empty shell with browser-eval entrypoint plus parity helpers
- Create: `D:/data/ideaSpace/rust/sgClaw/claw/claw/skills/skill_staging/skills/fault-details-report/scripts/collect_fault_details.test.js`
- deterministic fixture coverage for normalization, classification, summary, artifact contract, export/logging degradation, and entrypoint shape helpers
- Modify: `D:/data/ideaSpace/rust/sgClaw/claw/claw/skills/skill_staging/skills/fault-details-report/SKILL.toml`
- align tool description with real collection/export/report-log behavior
- Modify: `D:/data/ideaSpace/rust/sgClaw/claw/claw/skills/skill_staging/skills/fault-details-report/SKILL.md`
- align written contract with actual runtime behavior and artifact fields
- Modify: `D:/data/ideaSpace/rust/sgClaw/claw/claw/skills/skill_staging/skills/fault-details-report/references/collection-flow.md`
- align flow with page-range/query/export/report-log sequence
- Modify: `D:/data/ideaSpace/rust/sgClaw/claw/claw/skills/skill_staging/skills/fault-details-report/references/data-quality.md`
- make canonical columns, original classification tables, reason heuristics, summary rules, and partial semantics explicit
- Modify: `D:/data/ideaSpace/rust/sgClaw/claw/claw/skills/skill_staging/scenes/fault-details-report/scene.json`
- keep scene output/state contract aligned with real staged artifact behavior
### Existing files to read but not redesign
- Read only: `docs/superpowers/specs/2026-04-10-fault-details-full-skill-alignment-design.md`
- Read only: `src/agent/mod.rs`
- Read only: `src/compat/browser_script_skill_tool.rs`
- Read only: `D:/desk/智能体资料/大四区报告监测项/故障明细/index.html`
---
### Task 1: Add staged-skill red tests for normalization, summary, and artifact-contract semantics
**Files:**
- Create: `D:/data/ideaSpace/rust/sgClaw/claw/claw/skills/skill_staging/skills/fault-details-report/scripts/collect_fault_details.test.js`
- Read only: `D:/data/ideaSpace/rust/sgClaw/claw/claw/skills/skill_staging/skills/fault-details-report/scripts/collect_fault_details.js`
- Read only: `D:/desk/智能体资料/大四区报告监测项/故障明细/index.html`
- [ ] **Step 1: Write the failing staged-skill test file**
Add `collect_fault_details.test.js` using `node:test` and `assert/strict`. Cover these behaviors with fixed fixtures:
```javascript
const test = require('node:test');
const assert = require('node:assert/strict');
const {
DETAIL_COLUMNS,
SUMMARY_COLUMNS,
normalizeDetailRow,
deriveSummaryRows,
determineArtifactStatus,
buildFaultDetailsArtifact,
buildBrowserEntrypointResult
} = require('./collect_fault_details.js');
test('normalizeDetailRow maps canonical detail fields from raw repair rows', () => {
const row = normalizeDetailRow({
qxdbh: 'QX-1',
bxsj: '2026-03-09 08:00:00',
cityName: '国网兰州供电公司',
maintOrgName: '城关供电服务班',
maintGroupName: '抢修一班',
bdzMc: '110kV东岗变',
xlmc10: '10kV东岗线',
byqmc: '东岗1号变',
yjflMc: '电网故障',
ejflMc: '线路故障',
sjflMc: '低压线路',
qxxcjl: '现场检查:低压线路断线,已处理完成',
gzms: '客户报修停电'
}, {
companyName: '国网兰州供电公司'
});
assert.equal(row.slsj, '2026-03-09 08:00:00');
assert.equal(row.gssgs, '甘肃省电力公司');
assert.equal(row.gddw, '城关供电服务班');
assert.equal(row.gds, '抢修一班');
assert.equal(row.clzt, '处理完成');
assert.equal(row.bdz, '110kV东岗变');
assert.equal(row.line, '10kV东岗线');
assert.equal(row.pb, '东岗1号变');
});
test('deriveSummaryRows groups normalized rows by gds and computes counters', () => {
const rows = [
{ gds: '抢修一班', gddw: '城关供电服务班', sgs: '国网兰州供电公司', sxfl1: '无效', sxfl2: '无效', gzsb: '' },
{ gds: '抢修一班', gddw: '城关供电服务班', sgs: '国网兰州供电公司', sxfl1: '有效', sxfl2: '用户侧', gzsb: '表后线' },
{ gds: '抢修一班', gddw: '城关供电服务班', sgs: '国网兰州供电公司', sxfl1: '有效', sxfl2: '电网侧', dwcFl: '低压故障', gzsb: '低压线路' }
];
const summaryRows = deriveSummaryRows(rows, { companyName: '国网兰州供电公司' });
assert.equal(summaryRows.length, 1);
assert.equal(summaryRows[0].className, '抢修一班');
assert.equal(summaryRows[0].allCount, 3);
assert.equal(summaryRows[0].wxCount, 1);
assert.equal(summaryRows[0].khcCount, 0);
assert.equal(summaryRows[0].dyGzCount, 1);
assert.equal(summaryRows[0].dyxlCount, 1);
assert.equal(summaryRows[0].bhxCount, 1);
});
test('determineArtifactStatus follows blocked > error > partial > empty > ok precedence', () => {
assert.equal(determineArtifactStatus({ blockedReason: 'missing_session', fatalError: null, partialReasons: [], detailRows: [{}] }), 'blocked');
assert.equal(determineArtifactStatus({ blockedReason: null, fatalError: 'parse_failed', partialReasons: [], detailRows: [{}] }), 'error');
assert.equal(determineArtifactStatus({ blockedReason: null, fatalError: null, partialReasons: ['export_failed'], detailRows: [{}] }), 'partial');
assert.equal(determineArtifactStatus({ blockedReason: null, fatalError: null, partialReasons: [], detailRows: [] }), 'empty');
assert.equal(determineArtifactStatus({ blockedReason: null, fatalError: null, partialReasons: [], detailRows: [{}] }), 'ok');
});
test('buildFaultDetailsArtifact keeps canonical fields, selected range, counts, and downstream results', () => {
const artifact = buildFaultDetailsArtifact({
period: '2026-03',
selectedRange: { start: '2026-03-08 16:00:00', end: '2026-03-09 16:00:00' },
detailRows: [{ qxdbh: 'QX-1' }],
summaryRows: [{ index: 1 }],
partialReasons: ['report_log_failed'],
downstream: {
export: { attempted: true, success: true, path: 'http://localhost/export.xlsx' },
report_log: { attempted: true, success: false, error: '500' }
}
});
assert.equal(artifact.type, 'report-artifact');
assert.equal(artifact.status, 'partial');
assert.deepEqual(artifact.selected_range, { start: '2026-03-08 16:00:00', end: '2026-03-09 16:00:00' });
assert.equal(artifact.counts.detail_rows, 1);
assert.equal(artifact.counts.summary_rows, 1);
assert.deepEqual(artifact.partial_reasons, ['report_log_failed']);
});
test('buildFaultDetailsArtifact keeps required top-level fields for blocked artifact', () => {
const artifact = buildFaultDetailsArtifact({
period: '2026-03',
blockedReason: 'selected_range_unavailable',
partialReasons: ['selected_range_unavailable']
});
assert.equal(artifact.type, 'report-artifact');
assert.equal(artifact.report_name, 'fault-details-report');
assert.equal(artifact.period, '2026-03');
assert.equal(artifact.status, 'blocked');
assert.deepEqual(artifact.partial_reasons, ['selected_range_unavailable']);
assert.equal('downstream' in artifact, false);
});
test('buildFaultDetailsArtifact keeps known selected range and counts on late error', () => {
const artifact = buildFaultDetailsArtifact({
period: '2026-03',
selectedRange: { start: '2026-03-08 16:00:00', end: '2026-03-09 16:00:00' },
detailRows: [],
summaryRows: [],
fatalError: 'summary_failed',
partialReasons: ['summary_failed']
});
assert.equal(artifact.status, 'error');
assert.deepEqual(artifact.selected_range, { start: '2026-03-08 16:00:00', end: '2026-03-09 16:00:00' });
assert.equal(artifact.counts.detail_rows, 0);
assert.equal(artifact.counts.summary_rows, 0);
});
test('buildBrowserEntrypointResult returns blocked artifact when selected range is unavailable', async () => {
const artifact = await buildBrowserEntrypointResult({
period: '2026-03'
}, {
readSelectedRange: async () => null
});
assert.equal(artifact.status, 'blocked');
assert.ok(artifact.partial_reasons.includes('selected_range_unavailable'));
});
```
- [ ] **Step 2: Run the staged-skill test file and verify it fails**
Run:
```bash
node "D:/data/ideaSpace/rust/sgClaw/claw/claw/skills/skill_staging/skills/fault-details-report/scripts/collect_fault_details.test.js"
```
Expected: FAIL because `collect_fault_details.js` does not export these helpers yet and still only returns an empty shell.
---
### Task 2: Implement staged-skill parity helpers and a valid browser entrypoint
**Files:**
- Modify: `D:/data/ideaSpace/rust/sgClaw/claw/claw/skills/skill_staging/skills/fault-details-report/scripts/collect_fault_details.js`
- Test: `D:/data/ideaSpace/rust/sgClaw/claw/claw/skills/skill_staging/skills/fault-details-report/scripts/collect_fault_details.test.js`
- [ ] **Step 1: Implement the helper exports and browser entrypoint shape needed to satisfy the red tests**
Refactor `collect_fault_details.js` so the file remains a valid browser-eval script in page context while still supporting `node:test` through an environment-safe export guard.
Required implementation pieces:
```javascript
const DETAIL_COLUMNS = [/* existing canonical columns */];
const SUMMARY_COLUMNS = [/* existing summary columns */];
function normalizeDetailRow(raw, context) {
// map qxdbh/gssgs/sgs/gddw/gds/slsj/clzt/bdz/line/pb
// derive sxfl1/sxfl2/sxfl3/gzsb/gzyy from the original package rules
}
function deriveSummaryRows(detailRows, context) {
// group by gds and compute all original package counters
}
function determineArtifactStatus({ blockedReason, fatalError, partialReasons, detailRows }) {
// blocked > error > partial > empty > ok
}
function buildFaultDetailsArtifact({
period,
selectedRange,
detailRows,
summaryRows,
partialReasons,
blockedReason,
fatalError,
downstream
}) {
// return report-artifact with columns, sections, counts, status, partial_reasons, downstream
}
async function buildBrowserEntrypointResult(input, deps = defaultBrowserDeps()) {
// read selected range from page
// collect raw rows from page query
// normalize rows
// derive summary
// attempt export + report log
// return final artifact
}
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
DETAIL_COLUMNS,
SUMMARY_COLUMNS,
normalizeDetailRow,
deriveSummaryRows,
determineArtifactStatus,
buildFaultDetailsArtifact,
buildBrowserEntrypointResult
};
}
return await buildBrowserEntrypointResult(args);
```
Rules:
- keep `DETAIL_COLUMNS` and `SUMMARY_COLUMNS` canonical and stable
- keep helper functions self-contained in this file unless a separate pure helper file becomes necessary for runtime validity
- keep the browser entrypoint compatible with current `eval` wrapper
- keep browser runtime free of unguarded Node-only assumptions
- do **not** invent a new protocol or callback surface
- [ ] **Step 2: Re-run the staged-skill test file and verify it now reaches deeper failures or passes the initial helper coverage**
Run:
```bash
node "D:/data/ideaSpace/rust/sgClaw/claw/claw/skills/skill_staging/skills/fault-details-report/scripts/collect_fault_details.test.js"
```
Expected: either PASS for the Task 1 cases, or fail only on the still-missing full parity/export/history specifics added in Task 3.
---
### Task 3: Add red tests for full classification parity, downstream partials, and empty-result export semantics
**Files:**
- Modify: `D:/data/ideaSpace/rust/sgClaw/claw/claw/skills/skill_staging/skills/fault-details-report/scripts/collect_fault_details.test.js`
- Modify: `D:/data/ideaSpace/rust/sgClaw/claw/claw/skills/skill_staging/skills/fault-details-report/scripts/collect_fault_details.js`
- Read only: `D:/desk/智能体资料/大四区报告监测项/故障明细/index.html`
- [ ] **Step 1: Extend the staged-skill tests with failing parity and downstream cases**
Add focused failing tests such as:
```javascript
test('normalizeDetailRow derives gzyy from qxxcjl text heuristics', () => {
const row = normalizeDetailRow({
qxxcjl: '现场检查:客户表后线烧损,已恢复送电',
ejflMc: '客户侧故障',
sjflMc: '表后线'
}, { companyName: '国网兰州供电公司' });
assert.equal(row.gzsb, '表后线');
assert.equal(row.gzyy, '表后线烧损');
});
test('buildBrowserEntrypointResult returns partial when export fails after detail collection succeeds', async () => {
const artifact = await buildBrowserEntrypointResult({ period: '2026-03' }, {
readSelectedRange: async () => ({ start: '2026-03-08 16:00:00', end: '2026-03-09 16:00:00' }),
queryFaultRows: async () => [{ qxdbh: 'QX-1', bxsj: '2026-03-09 08:00:00', maintGroupName: '抢修一班' }],
readCompanyContext: () => ({ companyName: '国网兰州供电公司' }),
exportWorkbook: async () => {
throw new Error('export_failed');
},
writeReportLog: async () => ({ success: true })
});
assert.equal(artifact.status, 'partial');
assert.ok(artifact.partial_reasons.includes('export_failed'));
assert.equal(artifact.counts.detail_rows, 1);
assert.equal(artifact.downstream.export.attempted, true);
assert.equal(artifact.downstream.export.success, false);
});
test('buildBrowserEntrypointResult returns error when normalized detail rows cannot be produced', async () => {
const artifact = await buildBrowserEntrypointResult({ period: '2026-03' }, {
readSelectedRange: async () => ({ start: '2026-03-08 16:00:00', end: '2026-03-09 16:00:00' }),
queryFaultRows: async () => [{ qxdbh: '', bxsj: '' }],
readCompanyContext: () => ({ companyName: '国网兰州供电公司' })
});
assert.equal(artifact.status, 'error');
assert.ok(artifact.partial_reasons.includes('detail_normalization_failed'));
});
test('buildBrowserEntrypointResult keeps canonical rows empty for empty result and omits downstream before attempts', async () => {
const artifact = await buildBrowserEntrypointResult({ period: '2026-03' }, {
readSelectedRange: async () => ({ start: '2026-03-08 16:00:00', end: '2026-03-09 16:00:00' }),
queryFaultRows: async () => [],
readCompanyContext: () => ({ companyName: '国网兰州供电公司' })
});
assert.equal(artifact.status, 'empty');
assert.deepEqual(artifact.rows, []);
assert.equal('downstream' in artifact, false);
});
```
Also add fixture cases derived from the original packages full classification table and summary counters so the staged skill is forced toward parity, not a subset implementation.
- [ ] **Step 2: Run the staged-skill test file and verify it fails on the new cases**
Run:
```bash
node "D:/data/ideaSpace/rust/sgClaw/claw/claw/skills/skill_staging/skills/fault-details-report/scripts/collect_fault_details.test.js"
```
Expected: FAIL on missing full classification parity or downstream partial/error behavior.
- [ ] **Step 3: Implement the full business logic needed to satisfy the new tests**
In `collect_fault_details.js`:
- port the original classification table and `qxxcjl` text heuristics for `sxfl1`, `sxfl2`, `sxfl3`, `gzsb`, `gzyy`
- port the original summary derivation rules and counters completely
- add required-field validation so structurally unusable normalized rows escalate to `error`
- add downstream `exportWorkbook` and `writeReportLog` stages that record `{attempted, success, path, error}`
- keep collection success distinct from downstream failures so export/logging failures become `partial`, not full failure
- keep placeholder rows, if needed for downstream empty-export payloads, downstream-only and never in canonical returned `rows`
- include both `period` and `selected_range` in the artifact
- omit `downstream` when export/report-log have not been attempted yet
- [ ] **Step 4: Re-run the staged-skill test file and verify it passes**
Run:
```bash
node "D:/data/ideaSpace/rust/sgClaw/claw/claw/skills/skill_staging/skills/fault-details-report/scripts/collect_fault_details.test.js"
```
Expected: PASS.
---
### Task 4: Align staged-skill metadata and reference docs with the implemented behavior
**Files:**
- Modify: `D:/data/ideaSpace/rust/sgClaw/claw/claw/skills/skill_staging/skills/fault-details-report/SKILL.toml`
- Modify: `D:/data/ideaSpace/rust/sgClaw/claw/claw/skills/skill_staging/skills/fault-details-report/SKILL.md`
- Modify: `D:/data/ideaSpace/rust/sgClaw/claw/claw/skills/skill_staging/skills/fault-details-report/references/collection-flow.md`
- Modify: `D:/data/ideaSpace/rust/sgClaw/claw/claw/skills/skill_staging/skills/fault-details-report/references/data-quality.md`
- Modify: `D:/data/ideaSpace/rust/sgClaw/claw/claw/skills/skill_staging/scenes/fault-details-report/scene.json`
- [ ] **Step 1: Update the staged metadata/docs to match the implemented runtime contract**
Required changes:
- `SKILL.toml`: description must say the tool collects rows, derives summary, attempts localhost export, and records report history
- `SKILL.md`: artifact example must include `selected_range`, `counts`, `status`, `partial_reasons`, and `downstream`
- `references/collection-flow.md`: sequence must explicitly include page-selected range -> raw query -> normalization -> summary -> export -> report-log
- `references/data-quality.md`: document the original classification tables, `qxxcjl` heuristics, summary rules, partial/error escalation rules, and empty-result semantics explicitly enough to match the implemented helpers
- `scene.json`: keep inputs/outputs/status semantics aligned with the richer artifact; do not add routing policy there
- [ ] **Step 2: Read the updated staged docs and verify they match the implemented JS behavior**
Read and confirm:
- descriptions no longer claim “artifact shell” behavior
- docs do not move routing ownership out of `claw-new`
- docs do not promise auto-opening/downloading behavior in this slice
- docs reflect blocked/error field-presence rules and downstream-attempt semantics
Expected: staged metadata/docs accurately reflect the implemented collector.
---
### Task 5: Add Rust red tests for artifact-status interpretation in the direct-submit runtime
**Files:**
- Modify: `tests/agent_runtime_test.rs`
- Modify: `tests/browser_script_skill_tool_test.rs`
- Modify: `src/compat/direct_skill_runtime.rs`
- Read only: `src/compat/browser_script_skill_tool.rs`
- [ ] **Step 1: Add failing direct-submit runtime tests for structured artifact statuses**
Extend `tests/agent_runtime_test.rs` with focused regressions that use the existing temp skill-root harness but return real `report-artifact` payloads:
```rust
#[test]
fn submit_task_treats_partial_report_artifact_as_success_with_warning_summary() {
let skill_root = build_direct_runtime_skill_root();
let runtime_context = direct_submit_runtime_context(&skill_root);
let transport = Arc::new(MockTransport::new(vec![success_browser_response(
1,
serde_json::json!({
"text": {
"type": "report-artifact",
"report_name": "fault-details-report",
"period": "2026-03",
"selected_range": { "start": "2026-03-08 16:00:00", "end": "2026-03-09 16:00:00" },
"columns": ["qxdbh"],
"rows": [{ "qxdbh": "QX-1" }],
"sections": [{ "name": "summary-sheet", "columns": ["index"], "rows": [{ "index": 1 }] }],
"counts": { "detail_rows": 1, "summary_rows": 1 },
"status": "partial",
"partial_reasons": ["report_log_failed"],
"downstream": {
"export": { "attempted": true, "success": true, "path": "http://localhost/export.xlsx" },
"report_log": { "attempted": true, "success": false, "error": "500" }
}
}
}),
)]));
// ... invoke handle_browser_message_with_context(...)
// assert TaskComplete.success == true
// assert summary contains partial/report_log_failed/detail_rows=1
}
#[test]
fn submit_task_treats_empty_report_artifact_as_success() { /* status=empty => success=true */ }
#[test]
fn submit_task_treats_blocked_report_artifact_as_failure() { /* status=blocked => success=false */ }
#[test]
fn submit_task_treats_error_report_artifact_as_failure() { /* status=error => success=false */ }
```
Also add one focused helper regression to `tests/browser_script_skill_tool_test.rs` that proves the browser-script helper can return a structured object payload used by the fault-details path without flattening required fields away.
Suggested test name:
```rust
#[tokio::test]
async fn execute_browser_script_tool_preserves_structured_report_artifact_payload() { /* ... */ }
```
- [ ] **Step 2: Run the focused Rust tests and verify they fail**
Run:
```bash
cargo test --test agent_runtime_test submit_task_treats_partial_report_artifact_as_success_with_warning_summary -- --nocapture
cargo test --test browser_script_skill_tool_test execute_browser_script_tool_preserves_structured_report_artifact_payload -- --nocapture
```
Expected: the new `agent_runtime_test` case fails because `execute_direct_submit_skill` still returns raw JSON text and `src/agent/mod.rs` still marks all direct-submit results as success when no Rust-side interpretation exists.
---
### Task 6: Implement narrow Rust artifact interpretation without moving business rules into Rust
**Files:**
- Modify: `src/compat/direct_skill_runtime.rs`
- Modify: `tests/agent_runtime_test.rs`
- Modify: `tests/browser_script_skill_tool_test.rs`
- [ ] **Step 1: Implement a narrow structured-artifact interpreter in `src/compat/direct_skill_runtime.rs`**
Add a small internal result type and parser, for example:
```rust
struct DirectSubmitOutcome {
success: bool,
summary: String,
}
fn interpret_direct_submit_output(output: &str) -> DirectSubmitOutcome {
// parse JSON if possible
// if type == "report-artifact", read status/counts/partial_reasons/downstream
// map ok/partial/empty => success=true
// map blocked/error => success=false
// build concise summary with report_name, period, detail_rows, summary_rows, status, partial reasons
// fall back to raw output text when payload is not a recognized artifact
}
```
Then change the public entrypoint shape from `Result<String, PipeError>` to a narrow result carrying `success` and `summary`, or add a second helper that `src/agent/mod.rs` can use without changing routing ownership.
Rules:
- do **not** reimplement fault normalization/classification/summary in Rust
- do **not** add fault-specific branching in `src/agent/mod.rs`
- keep unrecognized non-artifact outputs working as before
- keep explicit `YYYY-MM` derivation and configured `skill.tool` resolution unchanged
- [ ] **Step 2: Update the submit-path caller to use the interpreted success flag**
Adjust the direct-submit branch so `TaskComplete.success` comes from the artifact interpretation instead of blindly treating every `Ok(summary)` as success.
Implementation target:
- keep the direct path in `src/agent/mod.rs`
- keep error handling narrow
- if needed, return a dedicated direct-submit outcome from `execute_direct_submit_skill`
- [ ] **Step 3: Re-run the focused Rust tests and verify they pass**
Run:
```bash
cargo test --test agent_runtime_test submit_task_treats_partial_report_artifact_as_success_with_warning_summary -- --nocapture
cargo test --test agent_runtime_test submit_task_treats_empty_report_artifact_as_success -- --nocapture
cargo test --test agent_runtime_test submit_task_treats_blocked_report_artifact_as_failure -- --nocapture
cargo test --test agent_runtime_test submit_task_treats_error_report_artifact_as_failure -- --nocapture
cargo test --test browser_script_skill_tool_test execute_browser_script_tool_preserves_structured_report_artifact_payload -- --nocapture
```
Expected: PASS.
---
### Task 7: Run the full verification sweep for the staged skill and direct runtime
**Files:**
- Verify only
- [ ] **Step 1: Run the staged-skill deterministic test file**
Run:
```bash
node "D:/data/ideaSpace/rust/sgClaw/claw/claw/skills/skill_staging/skills/fault-details-report/scripts/collect_fault_details.test.js"
```
Expected: PASS.
- [ ] **Step 2: Run the relevant Rust regression suites**
Run:
```bash
cargo test --test browser_script_skill_tool_test -- --nocapture
cargo test --test agent_runtime_test -- --nocapture
```
Expected: PASS.
- [ ] **Step 3: Run the broader compatibility coverage and build**
Run:
```bash
cargo test --test compat_runtime_test -- --nocapture
cargo test --test compat_config_test -- --nocapture
cargo build --bin sgclaw
```
Expected: PASS.
- [ ] **Step 4: Manually verify the requirements against the approved spec**
Checklist:
- staged skill now reads page-selected range instead of inventing a month window after entry
- staged skill returns canonical detail rows and summary rows
- staged skill ports the original classification table, `qxxcjl` heuristics, and summary counters with parity coverage
- staged skill records downstream export/report-log outcome
- staged skill distinguishes `ok` / `partial` / `empty` / `blocked` / `error`
- `blocked` / `error` artifacts keep the required top-level fields, and preserve known `selected_range` / `counts` when failure happens late enough
- `downstream` is omitted when export/report-log were not attempted and included with attempted/success flags once they were attempted
- empty-result canonical `rows` stay empty even if downstream export uses a placeholder transport row
- `claw-new` maps `ok` / `partial` / `empty` to success and `blocked` / `error` to failure
- no new routing metadata was added to `SKILL.toml` or `scene.json`
- no new browser protocol or opener/UI behavior was introduced
Expected: all checklist items satisfied before calling the work complete.
---
## Verification Checklist
### Staged skill behavior
```bash
node "D:/data/ideaSpace/rust/sgClaw/claw/claw/skills/skill_staging/skills/fault-details-report/scripts/collect_fault_details.test.js"
```
Expected: deterministic fixture coverage passes for normalization, full classification parity, summary derivation, artifact shape, empty semantics, and downstream partial semantics.
### Direct-submit runtime mapping
```bash
cargo test --test agent_runtime_test -- --nocapture
```
Expected:
- valid artifact `ok` / `partial` / `empty` completes successfully
- valid artifact `blocked` / `error` completes as failure
- existing invalid config regression still passes
- existing direct-submit happy path still passes
### Browser-script helper safety
```bash
cargo test --test browser_script_skill_tool_test -- --nocapture
```
Expected: current browser-script execution semantics remain intact while returning structured artifact payloads.
### Compatibility/build
```bash
cargo test --test compat_runtime_test -- --nocapture
cargo test --test compat_config_test -- --nocapture
cargo build --bin sgclaw
```
Expected: no regressions in compat execution/config loading; main binary builds cleanly.
---
## Notes For The Engineer
- The paired spec is `docs/superpowers/specs/2026-04-10-fault-details-full-skill-alignment-design.md`.
- Keep all fault business transforms in `skill_staging`, not in Rust.
- Keep direct routing config-owned via `skillsDir` + `directSubmitSkill`.
- Do **not** broaden this slice into LLM routing, generic dispatch policy, new browser opcodes, or export auto-open behavior.
- If the original package reveals extra classification rules that are needed for parity, add them only inside `collect_fault_details.js` and its staged references/tests, not in `claw-new`.

View File

@@ -0,0 +1,495 @@
# Fault Details Full Skill Alignment Design
**Goal:** Upgrade `fault-details-report.collect_fault_details` from an empty artifact shell into a real staged business skill that matches the original fault-details package's collection, normalization, summary, export, and report-history behavior, while keeping direct-skill routing config-owned in `claw-new`.
**Status:** Approved design direction for the next remediation slice.
---
## Decision Summary
1. Keep direct-skill selection in `claw-new` via `skillsDir` + `directSubmitSkill`; do not move dispatch ownership into `skill_staging` manifests.
2. Put the fault-details business logic in `D:/data/ideaSpace/rust/sgClaw/claw/claw/skills/skill_staging`, not in `claw-new`.
3. Align the staged skill with the original package's real behavior: query raw rows, normalize detail columns, derive summary rows, call localhost export, and write report history.
4. Keep the current browser-execution seam narrow: use the existing `browser_script` / browser-eval path, not a new browser protocol or new opcodes.
5. Add a narrow artifact interpreter in `claw-new` so structured fault-results map cleanly to `TaskComplete.success` and a readable completion summary.
---
## Why This Slice Exists
The current staged skill contract and the current staged skill implementation do not match.
### What the original package actually does
The original package under `D:/desk/智能体资料/大四区报告监测项/故障明细` does all of the following:
- reads the selected date range from the page UI
- queries the D4 repair-order data source
- filters and normalizes raw rows into the canonical detail export schema
- derives grouped summary rows by `gds`
- calls `http://localhost:13313/SurfaceServices/personalBread/export/faultDetailsExportXLSXS`
- auto-opens/downloads the generated file
- writes report history through `http://localhost:13313/ReportServices/Api/setReportLog`
### What the staged skill currently does
The current staged `collect_fault_details.js` only returns an empty `report-artifact` shell with empty `rows` and empty summary `sections`.
It also still uses a Node-style export shape instead of the browser-eval entrypoint shape that the current `browser_script` runtime expects. In practice, this means the staged script is not yet aligned with the real runtime contract even before business behavior is considered.
This slice closes that gap by making the staged skill actually perform the work the original package performs, but through the current sgClaw direct-skill runtime.
---
## Design Rules
### 1. `claw-new` owns routing, not business transforms
`claw-new` stays responsible for:
- loading config
- deciding whether submit-task takes the direct-skill path
- resolving the configured staged skill
- executing the staged browser-script tool
- turning the returned artifact into `TaskComplete.success` + human-readable summary
`claw-new` must **not** become the place where the original fault classification table, detail-row field mapping, or summary aggregation rules are reimplemented.
### 2. `skill_staging` owns fault-details business behavior
The staged skill package owns:
- query orchestration inside the browser page context
- raw-row extraction
- canonical detail-row normalization
- classification and derived fields
- summary-sheet derivation
- localhost export request
- localhost report-log request
- structured result payload
### 3. Keep the current browser seam narrow
Do not introduce a new browser bridge, callback protocol, or skill-specific browser opcode for this slice.
The implementation should continue using the current `browser_script` execution seam already wired through `claw-new/src/compat/browser_script_skill_tool.rs` and `claw-new/src/compat/direct_skill_runtime.rs`.
### 4. Match business behavior, not the original shell verbatim
The original package is a local HTML/Vue shell that uses `BrowserAction(...)`, timers, and hidden-browser choreography. That shell does **not** need to be recreated inside `claw-new`.
What must be preserved is the business outcome:
- same canonical detail columns
- same key field mappings
- same classification rules
- same summary metrics
- same downstream export/history behavior
- same distinction between empty, partial, blocked, and failed work
---
## Ownership Boundary and Landing Zones
### Staged skill changes
These changes land in `D:/data/ideaSpace/rust/sgClaw/claw/claw/skills/skill_staging`.
Primary files:
- `skills/fault-details-report/scripts/collect_fault_details.js`
- becomes the real browser-eval entrypoint
- must directly `return` the final structured artifact from the wrapped browser script
- may contain internal helper functions, but should remain self-contained for the current runtime
- `skills/fault-details-report/SKILL.toml`
- keep `browser_script`
- tighten the tool description so it matches the real behavior
- do not turn `SKILL.toml` into the source of truth for classification rules or routing policy
- `skills/fault-details-report/SKILL.md`
- align the written contract with the implemented runtime behavior
- `skills/fault-details-report/references/collection-flow.md`
- align the staged flow with the implemented query/export/history sequence
- `skills/fault-details-report/references/data-quality.md`
- stay authoritative for canonical columns, required fields, classification tables, `qxxcjl`-based reason heuristics, summary rules, and partial semantics
- `scenes/fault-details-report/scene.json`
- keep the scene contract aligned with the actual output and state semantics
- do not move classification or routing policy into scene metadata
### Caller/runtime changes
These changes land in `D:/data/ideaSpace/rust/sgClaw/claw-new`.
Primary files:
- `src/compat/direct_skill_runtime.rs`
- keep configured direct-skill execution here
- add narrow structured-artifact interpretation after the browser-script returns
- `src/agent/mod.rs`
- keep the current direct-submit routing seam here
- do not add fault-specific business logic here
- `src/compat/browser_script_skill_tool.rs`
- keep the browser-script contract strict: browser-eval entrypoint, no Node-only assumptions
- `tests/agent_runtime_test.rs`
- direct-submit path and result-surface regressions
- `tests/browser_script_skill_tool_test.rs`
- browser-script execution-shape regressions
If a new helper is needed in `claw-new`, it should be a narrow artifact-format/parser helper, not a new business-rules module.
---
## Target Runtime Flow
### Step 1: Submit-task stays config-owned
The user still types natural language into the current sgClaw input.
`claw-new`:
- receives `BrowserMessage::SubmitTask`
- loads `SgClawSettings`
- sees `directSubmitSkill = "fault-details-report.collect_fault_details"`
- bypasses LLM routing exactly as it does now
- resolves the staged skill from `skillsDir`
This preserves the already approved config-owned routing boundary.
### Step 2: Browser-script tool executes as a true browser entrypoint
`collect_fault_details.js` must be shaped for the current runtime:
- the script runs inside the current browser page context through `eval`
- it must not rely on `module.exports`
- it must directly `return collectFaultDetails(args)` from the wrapped script body
This is required because the current sgClaw browser-script runtime reads one script file and wraps it in a browser-side IIFE.
### Step 3: The skill reads the page-selected time range
The source-of-truth query window should come from the current page state, matching the original package behavior.
Design rule:
- read the selected start and end time from the business page controls or page state
- include that exact selected range in the returned artifact
- keep `period` as a bootstrap label from `claw-new`, not as a license to silently guess a different business range
Compatibility rule with the current direct-submit seam:
- the current `claw-new` direct path still requires an explicit `YYYY-MM` token in the user's instruction in order to enter the configured direct-skill flow
- that requirement remains in place for this slice
- once inside the skill, the browser page's selected start/end range is the source of truth for collection
- the returned artifact should include both the user-visible `period` label and the exact selected page range so mismatches are observable instead of hidden
If the page-selected range cannot be read reliably, the skill should return `blocked` instead of inventing a month-wide query window from `period` alone.
### Step 4: The skill collects raw rows and normalizes detail fields
The staged skill must reproduce the original package's detail normalization logic inside the browser-executed script.
That includes preserving the canonical detail schema from the original `excleIni[0].cols`, including the key transforms already present in the original package, such as:
- `slsj = bxsj`
- `gssgs = "甘肃省电力公司"`
- `sgs` derived from the current company/city context
- `gddw = maintOrgName`
- `gds = maintGroupName`
- `clzt = "处理完成"`
- `bdz = bdzMc`
- `line = xlmc10`
- `pb = byqmc`
The staged skill must also port the original classification/derivation logic that fills:
- `sxfl1`
- `sxfl2`
- `sxfl3`
- `gzsb`
- `gzyy`
That includes the original matching table and the `qxxcjl`-based text extraction heuristics that derive the fault reason.
### Step 5: The skill derives summary rows from normalized detail rows
The staged skill must derive the summary sheet from grouped detail rows, keyed around the same business totals the original package computes.
At minimum that includes:
- `index`
- `gsName`
- `fwDept`
- `className`
- `allCount`
- `wxCount`
- `khcCount`
- `sbdSbCount`
- `gyGzCount`
- `dyGzCount`
- `tqdzCount`
- `tqbxCount`
- `dyxlCount`
- `bqxCount`
- `jllCount`
- `bhxCount`
- `qftdCount`
The summary derivation must stay in the staged skill so the same package can later be routed by LLM without moving business logic back into `claw-new`.
### Step 6: The skill performs downstream export and report logging
After detail rows and summary rows are available, the staged skill should reproduce the original package's downstream behavior:
- build the export payload for `faultDetailsExportXLSXS`
- call the localhost export endpoint
- capture the returned export path/URL
- write report history via `setReportLog`
Important boundary:
- export/report-log are downstream side effects
- they do not redefine whether collection itself succeeded
- if collection succeeds but export/logging fails, the result is `partial`, not a full collection failure
- auto-opening/downloading the exported file is out of scope for this slice; this slice records the export path/result in the artifact but does not add new opener/UI behavior in `claw-new`
### Step 7: The skill returns one structured artifact
The staged skill should return one self-describing JSON artifact containing:
- business identity (`type`, `report_name`)
- selected period label
- exact selected start/end range
- canonical detail columns + normalized rows
- summary section columns + rows
- counts
- business status
- partial reasons if any
- downstream export outcome
- downstream report-log outcome
### Step 8: `claw-new` interprets the artifact, not the business rules
After the browser-script returns, `claw-new` should parse the JSON artifact and map it into final submit-task behavior.
Recommended mapping:
- `status = ok` -> `TaskComplete.success = true`
- `status = partial` -> `TaskComplete.success = true`, with warnings in summary
- `status = empty` -> `TaskComplete.success = true`, clearly reported as empty-result
- `status = blocked` -> `TaskComplete.success = false`
- `status = error` -> `TaskComplete.success = false`
This keeps business classification in the staged skill while preventing false-positive success in the direct path.
---
## Artifact Contract
The returned payload should stay `type = "report-artifact"`, but it must become rich enough to describe the real run.
Recommended contract:
```json
{
"type": "report-artifact",
"report_name": "fault-details-report",
"period": "2026-03",
"selected_range": {
"start": "2026-03-08 16:00:00",
"end": "2026-03-09 16:00:00"
},
"columns": ["qxdbh", "gssgs", "sgs", "gddw", "gds", "slsj", "yjflMc", "ejflMc", "sjflMc", "gzms", "yhbh", "yhmc", "lxr", "gzdd", "lxdh", "bxsj", "gdsj", "clzt", "qxxcjl", "bdz", "line", "pb", "sxfl1", "sxfl2", "sxfl3", "gzsb", "gzyy", "bz"],
"rows": [],
"sections": [
{
"name": "summary-sheet",
"columns": ["index", "gsName", "fwDept", "className", "allCount", "wxCount", "khcCount", "sbdSbCount", "gyGzCount", "dyGzCount", "tqdzCount", "tqbxCount", "dyxlCount", "bqxCount", "jllCount", "bhxCount", "qftdCount"],
"rows": []
}
],
"counts": {
"detail_rows": 0,
"summary_rows": 0
},
"status": "ok",
"partial_reasons": [],
"downstream": {
"export": {
"attempted": true,
"success": true,
"path": "http://localhost:13313/.../fault-details.xlsx"
},
"report_log": {
"attempted": true,
"success": true,
"report_name": "国网XX故障报修明细表(03月09日)",
"path": "http://localhost:13313/.../fault-details.xlsx"
}
}
}
```
### Contract notes
- `rows` is the canonical returned detail table, not the export-service transport payload.
- If the export service still requires a placeholder row for an empty spreadsheet, that placeholder should be synthesized only for the downstream export call, not as the canonical returned `rows` contract.
- `counts` should be computed from the canonical returned tables.
- `selected_range`, `columns`, `sections`, `counts`, `status`, and `partial_reasons` should always be present for `ok`, `partial`, and `empty`.
- For `blocked` and `error`, the artifact should still include `type`, `report_name`, `period`, `status`, and `partial_reasons`; `selected_range`, `columns`, `sections`, and `counts` should be included whenever they were already known before the failure point.
- `downstream` should be omitted only when export/report-log were not attempted yet; otherwise include it with `attempted` / `success` flags and any available path or failure detail.
---
## Error Handling and Status Semantics
### `ok`
Use `ok` when all of the following are true:
- raw collection succeeded
- required detail-field normalization succeeded
- summary derivation succeeded
- export succeeded
- report-log write succeeded
### `partial`
Use `partial` when detail collection succeeded but at least one downstream stage degraded, including:
- one or more required fields could not be normalized, but the row set still remains exportable and summary derivation can proceed with explicit gaps recorded
- summary derivation was incomplete, but the detail table is still available
- export failed after rows were available
- report-log write failed after rows/export were available
Escalation rule:
- if the raw query succeeds but required fields are missing so broadly that the canonical detail table cannot be produced at all, use `error`, not `partial`
- if summary derivation cannot even start because the normalized detail rows are structurally unusable, use `error`, not `partial`
`partial_reasons` must name the degraded stage instead of hiding it.
### `empty`
Use `empty` when:
- the query succeeds for the selected range
- zero real detail rows match
This is not a failure.
If the business flow still wants an empty export file or placeholder export payload, that happens downstream without changing the semantic meaning of the result.
### `blocked`
Use `blocked` when the page/session preconditions are not met, for example:
- expected page/session is not available
- required page controls cannot be read
- login/session state is missing or expired
- required browser-visible APIs are unavailable in the current page context
### `error`
Use `error` when the run starts but fails due to operational or parsing problems, for example:
- request failure
- page script failure
- raw response parse failure
- malformed export response
### `claw-new` completion mapping
`claw-new` should convert structured status into final submit completion behavior:
- `ok` / `partial` / `empty`: return a success completion with a concise human summary
- `blocked` / `error`: return a failed completion with a concise human summary
This avoids the current risk where a structured error-like payload could still be surfaced as a nominal success string.
---
## Testing and Acceptance Strategy
### Skill-side deterministic coverage
Add deterministic coverage around the staged skill's business logic in `skill_staging` for:
- canonical detail field mapping
- classification table parity
- `gzyy` extraction heuristics
- summary aggregation parity
- empty-result handling
- partial-result generation when downstream export/logging fails
- browser-script entrypoint shape (`return ...`, not `module.exports`)
The classification/summary tests should use fixed raw-row fixtures so the business rules are validated without a live browser session.
### `claw-new` runtime regressions
Add Rust coverage in `claw-new` for:
- direct-submit success with a populated `report-artifact`
- `partial` artifact mapping to `TaskComplete.success = true`
- `empty` artifact mapping to `TaskComplete.success = true`
- `blocked` / `error` artifact mapping to `TaskComplete.success = false`
- browser-script helper behavior for a real browser-eval return payload
### Manual acceptance
The live manual acceptance bar for this slice should be:
1. Configure `skillsDir` to the staged skill root and `directSubmitSkill` to `fault-details-report.collect_fault_details`.
2. Attach sgClaw to the real target browser page/session.
3. Submit a natural-language fault-details request without LLM routing.
4. Verify the staged skill:
- reads the selected page range
- queries real fault rows
- produces populated detail rows
- produces populated summary rows
- exports the workbook through localhost
- writes report history
5. Verify the final sgClaw completion message reports the correct status, counts, and downstream file/log outcome.
### Acceptance matrix
At minimum, acceptance should cover:
- normal populated result
- empty result with no matching rows
- partial result where export or report-log fails after collection
- blocked result where page/session preconditions are missing
- error result where parsing/query execution fails
---
## Explicit Non-Goals
This slice does **not**:
- move routing ownership out of `claw-new`
- require LLM routing to be available first
- add per-skill dispatch metadata to external manifests for routing policy
- introduce a new browser protocol or browser opcode
- recreate the original Vue shell inside `claw-new`
- move fault classification logic into Rust
- redesign the submit-task protocol beyond better interpretation of the returned artifact
---
## Resulting Design Rule
For the fault-details path:
- `claw-new` decides whether to invoke the fixed staged skill
- the staged skill performs the real fault business workflow
- the staged skill returns a structured artifact that describes collection + downstream outcomes
- `claw-new` interprets that artifact for submit-task success/failure and summary output
That keeps routing config-owned, keeps business logic with the staged skill, and makes `fault-details-report.collect_fault_details` ready for both the current no-LLM path and a later LLM-routed path.
---
## Document Landing Zones
- Approved spec: `docs/superpowers/specs/2026-04-10-fault-details-full-skill-alignment-design.md`
- Follow-up implementation plan: `docs/superpowers/plans/2026-04-10-fault-details-full-skill-alignment-plan.md`