From 3db74cc7b013f9827bde3d06b7c2b6ccc31ade68 Mon Sep 17 00:00:00 2001 From: catlog22 Date: Fri, 27 Feb 2026 14:45:38 +0800 Subject: [PATCH] feat: Enhance team lifecycle roles with checkpoint handling and inner loop execution - Added checkpoint gate handling to the coordinator role, defining behavior based on quality gate results. - Updated planner role to utilize inner loop pattern for structured implementation planning and reporting. - Revised writer role to implement inner loop for document generation, delegating CLI execution to a subagent. - Introduced a new doc-generation subagent for isolated CLI calls and document generation strategies. - Enhanced UI components in the frontend to display job statuses, last run times, and improved error handling. - Updated localization files to include new strings for job details and status banners. - Improved CSS styles for markdown previews to enhance readability and presentation. --- .claude/skills/team-lifecycle-v4/SKILL.md | 165 ++++++++- .../roles/coordinator/commands/dispatch.md | 61 +++- .../roles/coordinator/commands/monitor.md | 146 +++++++- .../roles/coordinator/role.md | 19 + .../team-lifecycle-v4/roles/planner/role.md | 13 +- .../roles/writer/commands/generate-doc.md | 7 +- .../team-lifecycle-v4/roles/writer/role.md | 132 ++++++- .../team-lifecycle-v4/specs/team-config.json | 37 +- .../subagents/doc-generation-subagent.md | 62 ++++ ccw/frontend/package.json | 6 +- .../src/components/memory/V2PipelineTab.tsx | 325 +++++++++++++++++- ccw/frontend/src/index.css | 122 +++++++ ccw/frontend/src/lib/api.ts | 1 + ccw/frontend/src/locales/en/memory.json | 31 +- ccw/frontend/src/locales/zh/memory.json | 31 +- 15 files changed, 1110 insertions(+), 48 deletions(-) create mode 100644 .claude/skills/team-lifecycle-v4/subagents/doc-generation-subagent.md diff --git a/.claude/skills/team-lifecycle-v4/SKILL.md b/.claude/skills/team-lifecycle-v4/SKILL.md index 739b3bb3..5d7c424f 100644 --- a/.claude/skills/team-lifecycle-v4/SKILL.md +++ b/.claude/skills/team-lifecycle-v4/SKILL.md @@ -77,6 +77,7 @@ Parse `$ARGUMENTS` to extract `--role`. If absent -> Orchestration Mode (auto ro |----------|------|-------------|---------| | discuss | [subagents/discuss-subagent.md](subagents/discuss-subagent.md) | analyst, writer, reviewer | Multi-perspective critique | | explore | [subagents/explore-subagent.md](subagents/explore-subagent.md) | analyst, planner, any role | Codebase exploration with cache | +| doc-generation | [subagents/doc-generation-subagent.md](subagents/doc-generation-subagent.md) | writer | Document generation (CLI execution) | ### Dispatch @@ -105,6 +106,10 @@ User provides task description |---------|--------| | `check` / `status` | Output execution status graph, no advancement | | `resume` / `continue` | Check worker states, advance next step | +| `revise [feedback]` | Create revision task for specified document + cascade downstream | +| `feedback ` | Analyze feedback impact, create targeted revision chain | +| `recheck` | Re-run QUALITY-001 quality check (after manual edits) | +| `improve [dimension]` | Auto-improve weakest dimension from readiness-report | --- @@ -144,7 +149,8 @@ Task completion with optional fast-advance to skip coordinator round-trip: | Condition | Action | |-----------|--------| -| 1 ready task, simple linear successor | Spawn directly via Task(run_in_background: true) | +| 同前缀后续任务 (Inner Loop 角色) | 不 spawn,主 agent 内循环 (Phase 5-L) | +| 1 ready task, simple linear successor, 不同前缀 | Spawn directly via Task(run_in_background: true) | | Multiple ready tasks (parallel window) | SendMessage to coordinator (needs orchestration) | | No ready tasks + others running | SendMessage to coordinator (status update) | | No ready tasks + nothing running | SendMessage to coordinator (pipeline may be complete) | @@ -152,6 +158,92 @@ Task completion with optional fast-advance to skip coordinator round-trip: **Fast-advance failure recovery**: If a fast-advanced task fails (worker exits without completing), the coordinator detects it as an orphaned in_progress task on next `resume`/`check` and resets it to pending for re-spawn. Self-healing, no manual intervention required. See [monitor.md](roles/coordinator/commands/monitor.md) Fast-Advance Failure Recovery. +### Worker Inner Loop (同前缀多任务角色) + +适用角色:writer (DRAFT-*)、planner (PLAN-*)、executor (IMPL-*) + +当一个角色拥有**同前缀的多个串行任务**时,不再每完成一个就 spawn 新 agent,而是在同一 agent 内循环处理: + +**Inner Loop 流程**: + +``` +Phase 1: 发现任务 (首次) + │ + ├─ 找到任务 → Phase 2-3: 加载上下文 + Subagent 执行 + │ │ + │ v + │ Phase 4: 验证 (+ Inline Discuss if applicable) + │ │ + │ v + │ Phase 5-L: 轻量完成 (Loop variant) + │ │ + │ ├─ TaskUpdate 标完成 + │ ├─ team_msg 记录 + │ ├─ 累积摘要到 context_accumulator + │ │ + │ ├─ 检查:还有同前缀待处理任务? + │ │ ├─ YES → 回到 Phase 1 (内循环) + │ │ └─ NO → Phase 5-F: 最终报告 + │ │ + │ └─ 异常中断条件? + │ ├─ consensus_blocked HIGH → SendMessage → STOP + │ └─ 错误累计 ≥ 3 → SendMessage → STOP + │ + └─ Phase 5-F: 最终报告 (Final) + ├─ SendMessage (含全部任务摘要) + └─ STOP +``` + +**context_accumulator** (主 agent 上下文中维护,不写文件): + +每个 subagent 返回后,主 agent 将结果压缩为摘要追加到 accumulator: + +``` +context_accumulator = [] + +# DRAFT-001 subagent 返回后 +context_accumulator.append({ + task: "DRAFT-001", + artifact: "spec/product-brief.md", + key_decisions: ["聚焦 B2B 场景", "MVP 不含移动端"], + discuss_verdict: "consensus_reached", + discuss_rating: 4.2 +}) + +# DRAFT-002 subagent 返回后 +context_accumulator.append({ + task: "DRAFT-002", + artifact: "spec/requirements/_index.md", + key_decisions: ["REQ-003 降级为 P2", "NFR-perf 新增 SLA"], + discuss_verdict: "consensus_reached", + discuss_rating: 3.8 +}) +``` + +后续 subagent 调用时,将 accumulator 摘要作为 CONTEXT 传入,实现知识传递。 + +**Phase 5-L vs Phase 5-F 区别**: + +| 步骤 | Phase 5-L (循环中) | Phase 5-F (最终) | +|------|-------------------|-----------------| +| TaskUpdate completed | YES | YES | +| team_msg log | YES | YES | +| 累积摘要 | YES | - | +| SendMessage to coordinator | NO | YES (含所有任务汇总) | +| Fast-Advance 到下一前缀 | - | YES (检查跨前缀后续) | + +**中断恢复**: + +如果 Inner Loop agent 在 DRAFT-003 崩溃: +1. DRAFT-001, DRAFT-002 已落盘 + 已标完成 → 安全 +2. DRAFT-003 状态为 in_progress → coordinator resume 时检测到无 active_worker → 重置为 pending +3. 重新 spawn writer → Phase 1 找到 DRAFT-003 → Resume Artifact Check: + - DRAFT-003 产物不存在 → 正常执行 + - DRAFT-003 产物已写但未标完成 → 验证后标完成 +4. 新 writer 从 DRAFT-003 开始循环,丢失的只是 001+002 的隐性摘要(可从磁盘重建基础信息) + +**恢复增强** (可选):在每个 Phase 5-L 后将 context_accumulator 写入 `/shared-memory.json` 的 `context_accumulator` 字段,crash 后可读回。 + ### Inline Discuss Protocol (produce roles: analyst, writer, reviewer) After completing their primary output, produce roles call the discuss subagent inline: @@ -354,10 +446,41 @@ Beat 1 2 3 4 | Trigger | Position | Behavior | |---------|----------|----------| -| Spec->Impl transition | QUALITY-001 completed | Pause, wait for user `resume` | +| Spec->Impl transition | QUALITY-001 completed | Read readiness-report.md, extract gate + scores, display Checkpoint Output Template, pause for user action | | GC loop max | QA-FE max 2 rounds | Stop iteration, report current state | | Pipeline stall | No ready + no running | Check missing tasks, report to user | +**Checkpoint Output Template** (QUALITY-001 completion): + +Coordinator reads `/spec/readiness-report.md`, extracts gate + dimension scores, displays: + +``` +[coordinator] ══════════════════════════════════════════ +[coordinator] SPEC PHASE COMPLETE +[coordinator] Quality Gate: (%) +[coordinator] +[coordinator] Dimension Scores: +[coordinator] Completeness: % +[coordinator] Consistency: % +[coordinator] Traceability: % +[coordinator] Depth: % +[coordinator] Coverage: % +[coordinator] +[coordinator] Available Actions: +[coordinator] resume → Proceed to implementation +[coordinator] improve → Auto-improve weakest dimension +[coordinator] improve → Improve specific dimension +[coordinator] revise → Revise specific document +[coordinator] recheck → Re-run quality check +[coordinator] feedback → Inject feedback, create revision +[coordinator] ══════════════════════════════════════════ +``` + +Gate-specific guidance: +- PASS: All actions available, resume is primary suggestion +- REVIEW: Recommend improve/revise before resume, warn on resume +- FAIL: Recommend improve/revise, do not suggest resume (user can force) + **Stall detection** (coordinator `handleCheck`): | Check | Condition | Resolution | @@ -385,6 +508,8 @@ Beat 1 2 3 4 ## Coordinator Spawn Template +### 标准 Worker (单任务角色: analyst, tester, reviewer, architect) + When coordinator spawns workers, use background mode (Spawn-and-Stop): ``` @@ -419,6 +544,42 @@ Session: }) ``` +### Inner Loop Worker (多任务角色: writer, planner, executor) + +``` +Task({ + subagent_type: "general-purpose", + description: "Spawn worker (inner loop)", + team_name: , + name: "", + run_in_background: true, + prompt: `You are team "" . + +## Primary Instruction +All your work MUST be executed by calling Skill to get role definition: +Skill(skill="team-lifecycle-v4", args="--role=") + +Current requirement: +Session: + +## Inner Loop Mode +You will handle ALL -* tasks in this session, not just the first one. +After completing each task, loop back to find the next -* task. +Only SendMessage to coordinator when: +- All -* tasks are done +- A consensus_blocked HIGH occurs +- Errors accumulate (≥ 3) + +## Role Guidelines +- Only process -* tasks, do not execute other role work +- All output prefixed with [] tag +- Only communicate with coordinator +- Do not use TaskCreate to create tasks for other roles +- Before each SendMessage, call mcp__ccw-tools__team_msg to log +- Use subagent calls for heavy work, retain summaries in context` +}) +``` + ## Session Directory ``` diff --git a/.claude/skills/team-lifecycle-v4/roles/coordinator/commands/dispatch.md b/.claude/skills/team-lifecycle-v4/roles/coordinator/commands/dispatch.md index 1cc922fb..bbb2c0de 100644 --- a/.claude/skills/team-lifecycle-v4/roles/coordinator/commands/dispatch.md +++ b/.claude/skills/team-lifecycle-v4/roles/coordinator/commands/dispatch.md @@ -106,12 +106,21 @@ Every task description includes session, scope, and inline discuss metadata: TaskCreate({ subject: "", owner: "", - description: "\nSession: \nScope: \nInlineDiscuss: ", + description: "\nSession: \nScope: \nInlineDiscuss: \nInnerLoop: ", blockedBy: [], status: "pending" }) ``` +**InnerLoop Flag Rules**: + +| Role | InnerLoop | +|------|-----------| +| writer (DRAFT-*) | true | +| planner (PLAN-*) | true | +| executor (IMPL-*) | true | +| analyst, tester, reviewer, architect, fe-developer, fe-qa | false | + ### Execution Method | Method | Behavior | @@ -129,6 +138,56 @@ TaskCreate({ | Session reference | Every task description contains `Session: ` | | Inline discuss | Spec tasks have InlineDiscuss field matching round config | +### Revision Task Template + +When handleRevise/handleFeedback creates revision tasks: + +``` +TaskCreate({ + subject: "-R1", + owner: "", + description: " revision of .\n + Session: \n + Original artifact: \n + User feedback: \n + Revision scope: \n + InlineDiscuss: \n + InnerLoop: ", + status: "pending", + blockedBy: [] +}) +``` + +**Revision naming**: `-R1` (max 1 revision per task; second revision -> `-R2`; third -> escalate to user) + +**Cascade blockedBy chain example** (revise DRAFT-002): +- DRAFT-002-R1 (no blockedBy) +- DRAFT-003-R1 (blockedBy: DRAFT-002-R1) +- DRAFT-004-R1 (blockedBy: DRAFT-003-R1) +- QUALITY-001-R1 (blockedBy: DRAFT-004-R1) + +### Improvement Task Template + +When handleImprove creates improvement tasks: + +``` +TaskCreate({ + subject: "IMPROVE--001", + owner: "writer", + description: "Quality improvement: .\n + Session: \n + Current score: %\n + Target: 80%\n + Readiness report: /spec/readiness-report.md\n + Weak areas: \n + Strategy: \n + InnerLoop: true", + status: "pending" +}) +``` + +Improvement tasks are always followed by a QUALITY-001-R1 recheck (blockedBy: IMPROVE task). + ## Error Handling | Scenario | Resolution | diff --git a/.claude/skills/team-lifecycle-v4/roles/coordinator/commands/monitor.md b/.claude/skills/team-lifecycle-v4/roles/coordinator/commands/monitor.md index 97c87b16..b5c240f2 100644 --- a/.claude/skills/team-lifecycle-v4/roles/coordinator/commands/monitor.md +++ b/.claude/skills/team-lifecycle-v4/roles/coordinator/commands/monitor.md @@ -33,6 +33,10 @@ Parse `$ARGUMENTS` to determine handler: | 2 | Contains "check" or "status" | handleCheck | | 3 | Contains "resume", "continue", or "next" | handleResume | | 4 | None of the above (initial spawn after dispatch) | handleSpawnNext | +| 5 | Contains "revise" + task ID pattern | handleRevise | +| 6 | Contains "feedback" + quoted/unquoted text | handleFeedback | +| 7 | Contains "recheck" | handleRecheck | +| 8 | Contains "improve" (optionally + dimension name) | handleImprove | Known worker roles: analyst, writer, planner, executor, tester, reviewer, architect, fe-developer, fe-qa. @@ -47,6 +51,8 @@ Worker completed a task. Verify completion, update state, auto-advance. ``` Receive callback from [] +- Find matching active worker by role + +- Is this a progress update (not final)? (Inner Loop intermediate task completion) + | +- YES -> Update session state, do NOT remove from active_workers -> STOP +- Task status = completed? | +- YES -> remove from active_workers -> update session | | +- Handle checkpoints (see below) @@ -87,7 +93,7 @@ Read-only status report. No pipeline advancement. done=completed >>>=running o=pending .=not created [coordinator] Active Workers: - > () - running + > () - running [inner-loop: N/M tasks done] [coordinator] Ready to spawn: [coordinator] Commands: 'resume' to advance | 'check' to refresh @@ -132,6 +138,9 @@ Ready tasks found? +- NONE + work in progress -> report waiting -> STOP +- NONE + nothing in progress -> PIPELINE_COMPLETE -> Phase 5 +- HAS ready tasks -> for each: + +- Is task owner an Inner Loop role AND that role already has an active_worker? + | +- YES -> SKIP spawn (existing worker will pick it up via inner loop) + | +- NO -> normal spawn below +- TaskUpdate -> in_progress +- team_msg log -> task_unblocked +- Spawn worker (see tool call below) @@ -154,11 +163,144 @@ Task({ --- +### Handler: handleRevise + +User requests targeted revision of a completed task. + +``` +Parse: revise [feedback-text] + +- Validate TASK-ID exists and is completed + | +- NOT completed -> error: "Task is not completed, cannot revise" + +- Determine role and doc type from TASK-ID prefix + +- Create revision task: + | TaskCreate({ + | subject: "-R1", + | owner: "", + | description: "User-requested revision of .\n + | Session: \n + | Original artifact: \n + | User feedback: \n + | Revision scope: targeted\n + | InlineDiscuss: \n + | InnerLoop: true", + | status: "pending" + | }) + +- Cascade check (auto): + | +- Find all completed tasks downstream of TASK-ID + | +- For each downstream completed task -> create -R1 + | +- Chain blockedBy: each R1 blockedBy its predecessor R1 + | +- Always end with QUALITY-001-R1 (recheck) + +- Spawn worker for first revision task -> STOP +``` + +**Cascade Rules**: + +| Revised Task | Downstream (auto-cascade) | +|-------------|--------------------------| +| RESEARCH-001 | DRAFT-001~004-R1, QUALITY-001-R1 | +| DRAFT-001 | DRAFT-002~004-R1, QUALITY-001-R1 | +| DRAFT-002 | DRAFT-003~004-R1, QUALITY-001-R1 | +| DRAFT-003 | DRAFT-004-R1, QUALITY-001-R1 | +| DRAFT-004 | QUALITY-001-R1 | +| QUALITY-001 | (no cascade, just recheck) | + +**Cascade depth control**: Only cascade tasks that are already completed. Pending/in_progress tasks will naturally pick up changes. + +--- + +### Handler: handleFeedback + +User injects feedback into pipeline context. + +``` +Parse: feedback + +- Determine pipeline state: + | +- Spec phase in progress -> find earliest affected DRAFT task + | +- Spec phase complete (at checkpoint) -> analyze full impact + | +- Impl phase in progress -> log to wisdom/decisions.md, no revision + +- Analyze feedback impact: + | +- Keyword match against doc types: + | "vision/market/MVP/scope" -> DRAFT-001 (product-brief) + | "requirement/feature/NFR/user story" -> DRAFT-002 (requirements) + | "architecture/ADR/component/tech stack" -> DRAFT-003 (architecture) + | "epic/story/sprint/priority" -> DRAFT-004 (epics) + | +- If unclear -> default to earliest incomplete or most recent completed + +- Write feedback to wisdom/decisions.md + +- Create revision chain (same as handleRevise from determined start point) + +- Spawn worker -> STOP +``` + +--- + +### Handler: handleRecheck + +Re-run quality check after manual edits or revisions. + +``` +Parse: recheck + +- Validate QUALITY-001 exists and is completed + | +- NOT completed -> error: "Quality check hasn't run yet" + +- Create recheck task: + | TaskCreate({ + | subject: "QUALITY-001-R1", + | owner: "reviewer", + | description: "Re-run spec quality check.\n + | Session: \n + | Scope: full recheck\n + | InlineDiscuss: DISCUSS-006", + | status: "pending" + | }) + +- Spawn reviewer -> STOP +``` + +--- + +### Handler: handleImprove + +Quality-driven improvement based on readiness report dimensions. + +``` +Parse: improve [dimension] + +- Read /spec/readiness-report.md + | +- NOT found -> error: "No readiness report. Run quality check first." + +- Extract dimension scores + +- Select target: + | +- dimension specified -> use it + | +- not specified -> pick lowest scoring dimension + +- Map dimension to improvement strategy: + | + | | Dimension | Strategy | Target Tasks | + | |-----------|----------|-------------| + | | completeness | Fill missing sections | DRAFT with missing sections | + | | consistency | Unify terminology/format | All DRAFT (batch) | + | | traceability | Strengthen Goals->Reqs->Arch->Stories chain | DRAFT-002, DRAFT-003, DRAFT-004 | + | | depth | Enhance AC/ADR detail | Weakest sub-dimension's DRAFT | + | | coverage | Add uncovered requirements | DRAFT-002 | + | + +- Create improvement task: + | TaskCreate({ + | subject: "IMPROVE--001", + | owner: "writer", + | description: "Quality improvement: .\n + | Session: \n + | Current score: %\n + | Target: 80%\n + | Weak areas: \n + | Strategy: \n + | InnerLoop: true", + | status: "pending" + | }) + +- Create QUALITY-001-R1 (blockedBy: IMPROVE task) + +- Spawn writer -> STOP +``` + +--- + ### Checkpoints | Completed Task | Mode Condition | Action | |---------------|----------------|--------| -| QUALITY-001 | full-lifecycle or full-lifecycle-fe | Output "SPEC PHASE COMPLETE" checkpoint, pause for user review before impl | +| QUALITY-001 | full-lifecycle or full-lifecycle-fe | Read readiness-report.md -> extract gate + scores -> output Checkpoint Output Template (see SKILL.md) -> pause for user action | --- diff --git a/.claude/skills/team-lifecycle-v4/roles/coordinator/role.md b/.claude/skills/team-lifecycle-v4/roles/coordinator/role.md index d4ec74c5..bd4f524a 100644 --- a/.claude/skills/team-lifecycle-v4/roles/coordinator/role.md +++ b/.claude/skills/team-lifecycle-v4/roles/coordinator/role.md @@ -151,6 +151,25 @@ Delegate to `commands/dispatch.md` which creates the full task chain: - User "check" -> handleCheck (status only) - User "resume" -> handleResume (advance) +### Checkpoint Gate Handling + +When QUALITY-001 completes (spec->impl transition checkpoint): + +1. Read `/spec/readiness-report.md` +2. Parse quality gate: extract `Quality Gate:` line -> PASS/REVIEW/FAIL + score +3. Parse dimension scores: extract `Dimension Scores` table +4. Output Checkpoint Output Template (see SKILL.md Checkpoints) with gate-specific guidance +5. Write gate result to team-session.json: `checkpoint_gate: { gate, score, dimensions }` +6. Pause and wait for user command + +**Gate-specific behavior**: + +| Gate | Primary Suggestion | Warning | +|------|-------------------|---------| +| PASS (>=80%) | `resume` to proceed | None | +| REVIEW (60-79%) | `improve` or `revise` first | Warn on `resume`: "Quality below target, proceed at risk" | +| FAIL (<60%) | `improve` or `revise` required | Block `resume` suggestion, user can force | + --- ## Phase 5: Report + Next Steps diff --git a/.claude/skills/team-lifecycle-v4/roles/planner/role.md b/.claude/skills/team-lifecycle-v4/roles/planner/role.md index 1e8c0016..a8815bc8 100644 --- a/.claude/skills/team-lifecycle-v4/roles/planner/role.md +++ b/.claude/skills/team-lifecycle-v4/roles/planner/role.md @@ -1,10 +1,12 @@ # Role: planner -Multi-angle code exploration (via shared explore subagent with cache) and structured implementation planning. +Multi-angle code exploration and structured implementation planning. +Uses **Inner Loop** pattern for consistency (currently single PLAN-* task, extensible). ## Identity - **Name**: `planner` | **Prefix**: `PLAN-*` | **Tag**: `[planner]` +- **Mode**: Inner Loop - **Responsibility**: Complexity assessment -> Code exploration (shared cache) -> Plan generation -> Approval ## Boundaries @@ -117,6 +119,15 @@ Requirements: 2-7 tasks with id, title, files[].change, convergence.criteria, de --- +## Phase 5: Report (Inner Loop) + +Currently planner only has PLAN-001, so it directly executes Phase 5-F (Final Report). +If future extensions add multiple PLAN-* tasks, Phase 5-L loop activates automatically: +- Phase 5-L: Mark task completed, accumulate summary, loop back to Phase 1 +- Phase 5-F: All PLAN-* done, send final report to coordinator with full summary + +--- + ## Error Handling | Scenario | Resolution | diff --git a/.claude/skills/team-lifecycle-v4/roles/writer/commands/generate-doc.md b/.claude/skills/team-lifecycle-v4/roles/writer/commands/generate-doc.md index 28cb9623..d17964b2 100644 --- a/.claude/skills/team-lifecycle-v4/roles/writer/commands/generate-doc.md +++ b/.claude/skills/team-lifecycle-v4/roles/writer/commands/generate-doc.md @@ -2,7 +2,12 @@ ## Purpose -Multi-CLI document generation for 4 document types. Each uses parallel or staged CLI analysis, then synthesizes into templated documents. +Document generation strategy reference. Used by doc-generation-subagent.md as prompt source. +Writer 主 agent 不再直接执行此文件中的 CLI 调用,而是将对应段落传入 subagent prompt。 + +## Usage + +Writer Phase 3 加载此文件中对应 doc-type 的策略段落,嵌入 subagent prompt 的 "Execution Strategy" 字段。 ## Phase 2: Context Loading diff --git a/.claude/skills/team-lifecycle-v4/roles/writer/role.md b/.claude/skills/team-lifecycle-v4/roles/writer/role.md index 4f91e0f0..109e719e 100644 --- a/.claude/skills/team-lifecycle-v4/roles/writer/role.md +++ b/.claude/skills/team-lifecycle-v4/roles/writer/role.md @@ -1,27 +1,29 @@ # Role: writer -Product Brief, Requirements/PRD, Architecture, and Epics & Stories document generation. Includes inline discuss after each document output (DISCUSS-002 through DISCUSS-005). +Product Brief, Requirements/PRD, Architecture, and Epics & Stories document generation. +Uses **Inner Loop** pattern: one agent handles all DRAFT-* tasks sequentially, +delegating document generation to subagent, retaining summaries across tasks. ## Identity - **Name**: `writer` | **Prefix**: `DRAFT-*` | **Tag**: `[writer]` -- **Responsibility**: Load Context -> Generate Document -> **Inline Discuss** -> Report +- **Mode**: Inner Loop (处理全部 DRAFT-* 任务) +- **Responsibility**: [Loop: Load Context -> Subagent Generate -> Validate + Discuss -> Accumulate] -> Final Report ## Boundaries ### MUST - Only process DRAFT-* tasks -- Read templates before generating (from `../../templates/`) -- Follow document-standards.md (from `../../specs/`) -- Integrate prior discussion feedback when available -- Generate proper YAML frontmatter -- Call discuss subagent after document output (round from InlineDiscuss field) +- Use subagent for document generation (不在主 agent 内执行 CLI) +- Maintain context_accumulator across tasks +- Call discuss subagent after each document output +- Loop through all DRAFT-* tasks before reporting to coordinator ### MUST NOT - Create tasks for other roles - Skip template loading -- Modify discussion records from prior rounds -- Skip inline discuss +- Execute CLI document generation in main agent (delegate to subagent) +- SendMessage to coordinator mid-loop (除非 consensus_blocked HIGH) ## Message Types @@ -35,13 +37,22 @@ Product Brief, Requirements/PRD, Architecture, and Epics & Stories document gene | Tool | Purpose | |------|---------| -| commands/generate-doc.md | Multi-CLI document generation | -| gemini, codex, claude CLI | Multi-perspective content generation | +| subagents/doc-generation-subagent.md | Document generation (per task) | | discuss subagent | Inline discuss critique | --- -## Phase 2: Context & Discussion Loading +## Phase 1: Task Discovery (Inner Loop) + +**首次进入**:标准 Phase 1 流程,找到第一个 DRAFT-* pending 任务。 + +**循环重入**:Phase 5-L 完成后回到此处,TaskList 查找下一个 DRAFT-* pending 且 blockedBy 已全部 completed 的任务。 + +**终止条件**:无更多 DRAFT-* 可处理 → Phase 5-F。 + +--- + +## Phase 2: Context Loading **Objective**: Load all required inputs for document generation. @@ -72,15 +83,62 @@ Product Brief, Requirements/PRD, Architecture, and Epics & Stories document gene | architecture | + requirements/_index.md | | epics | + architecture/_index.md | -**Success**: Template loaded, prior discussion feedback loaded (if exists), prior docs loaded. +**Prior decisions from accumulator**: 将 context_accumulator 中的前序摘要作为 "Prior Decisions" 传入。 + +| Input | Source | Required | +|-------|--------|----------| +| Document standards | `../../specs/document-standards.md` | Yes | +| Template | From routing table | Yes | +| Spec config | `/spec/spec-config.json` | Yes | +| Discovery context | `/spec/discovery-context.json` | Yes | +| Discussion feedback | `/discussions/` | If exists | +| Prior decisions | context_accumulator (内存) | 如果有前序任务 | + +**Success**: Template loaded, prior discussion feedback loaded (if exists), prior docs loaded, accumulator context prepared. --- -## Phase 3: Document Generation +## Phase 3: Subagent Document Generation -**Objective**: Generate document using template and multi-CLI analysis. +**Objective**: Delegate document generation to doc-generation subagent. -Delegate to `commands/generate-doc.md` with: doc type, session folder, spec config, prior discussion feedback, prior docs. +**变化**:不再在主 agent 内执行 CLI 调用,而是委托给 doc-generation subagent。 + +``` +Task({ + subagent_type: "universal-executor", + run_in_background: false, + description: "Generate document", + prompt: `<从 subagents/doc-generation-subagent.md 加载 prompt> + +## Task +- Document type: +- Session folder: +- Template: + +## Context +- Spec config: +- Discovery context: +- Prior discussion feedback: +- Prior decisions (from writer accumulator): + + +## Instructions +<从 commands/generate-doc.md 加载该 doc-type 的具体策略> + +## Expected Output +Return JSON: +{ + "artifact_path": "", + "summary": "<100-200字摘要>", + "key_decisions": ["", "", ...], + "sections_generated": ["", ...], + "warnings": [""] +}` +}) +``` + +**主 agent 拿到的只是上述 JSON 摘要**,不是整篇文档。文档已由 subagent 写入磁盘。 --- @@ -140,11 +198,49 @@ Discussion: /discussions/-discussion.md --- +## Phase 5-L: 循环完成 (Loop Completion) + +在还有后续 DRAFT-* 任务时执行: + +1. **TaskUpdate**: 标记当前任务 completed +2. **team_msg**: 记录任务完成 +3. **累积摘要**: + ``` + context_accumulator.append({ + task: "", + artifact: "", + key_decisions: , + discuss_verdict: , + discuss_rating: , + summary: + }) + ``` +4. **中断检查**: + - consensus_blocked HIGH → SendMessage → STOP + - 累计错误 >= 3 → SendMessage → STOP +5. **Loop**: 回到 Phase 1 + +**不做**:不 SendMessage、不 Fast-Advance spawn。 + +## Phase 5-F: 最终报告 (Final Report) + +当所有 DRAFT-* 任务完成后: + +1. **TaskUpdate**: 标记最后一个任务 completed +2. **team_msg**: 记录完成 +3. **汇总报告**: 所有任务摘要 + discuss 结果 + 产出路径 +4. **Fast-Advance 检查**: 检查跨前缀后续 (如 QUALITY-001 是否 ready) +5. **SendMessage** 或 **spawn successor** + +--- + ## Error Handling | Scenario | Resolution | |----------|------------| +| Subagent 失败 | 重试 1 次,换 subagent_type;仍失败则记录错误,继续下一任务 | +| Discuss subagent 失败 | 跳过 discuss,记录 warning | +| 累计 3 个任务失败 | SendMessage 报告 coordinator,STOP | +| Agent crash mid-loop | Coordinator resume 检测 orphan → 重新 spawn → 从断点恢复 | | Prior doc not found | Notify coordinator, request prerequisite | -| CLI failure | Retry with fallback tool | | Discussion contradicts prior docs | Note conflict, flag for coordinator | -| Discuss subagent fails | Proceed without discuss, log warning in report | diff --git a/.claude/skills/team-lifecycle-v4/specs/team-config.json b/.claude/skills/team-lifecycle-v4/specs/team-config.json index 88b84e5f..2a8c480a 100644 --- a/.claude/skills/team-lifecycle-v4/specs/team-config.json +++ b/.claude/skills/team-lifecycle-v4/specs/team-config.json @@ -22,17 +22,21 @@ "writer": { "task_prefix": "DRAFT", "responsibility": "Product Brief / PRD / Architecture / Epics document generation + inline discuss", + "execution_mode": "inner_loop", + "subagent_type": "universal-executor", "inline_discuss": ["DISCUSS-002", "DISCUSS-003", "DISCUSS-004", "DISCUSS-005"], "message_types": ["draft_ready", "draft_revision", "error"] }, "planner": { "task_prefix": "PLAN", "responsibility": "Multi-angle code exploration (via shared explore), structured implementation planning", + "execution_mode": "inner_loop", "message_types": ["plan_ready", "plan_revision", "error"] }, "executor": { "task_prefix": "IMPL", "responsibility": "Code implementation following approved plans", + "execution_mode": "inner_loop", "message_types": ["impl_complete", "impl_progress", "error"] }, "tester": { @@ -42,10 +46,10 @@ }, "reviewer": { "task_prefix": "REVIEW", - "additional_prefixes": ["QUALITY"], - "responsibility": "Code review (REVIEW-*) + Spec quality validation (QUALITY-*) + inline discuss for sign-off", + "additional_prefixes": ["QUALITY", "IMPROVE"], + "responsibility": "Code review (REVIEW-*) + Spec quality validation (QUALITY-*) + Quality improvement recheck (IMPROVE-*) + inline discuss for sign-off", "inline_discuss": "DISCUSS-006", - "message_types": ["review_result", "quality_result", "fix_required", "error"] + "message_types": ["review_result", "quality_result", "quality_recheck", "fix_required", "error"] }, "architect": { "task_prefix": "ARCH", @@ -83,6 +87,33 @@ } }, + "checkpoint_commands": { + "revise": { + "handler": "handleRevise", + "pattern": "revise [feedback]", + "cascade": true, + "creates": "revision_task" + }, + "feedback": { + "handler": "handleFeedback", + "pattern": "feedback ", + "cascade": true, + "creates": "revision_chain" + }, + "recheck": { + "handler": "handleRecheck", + "pattern": "recheck", + "cascade": false, + "creates": "quality_recheck" + }, + "improve": { + "handler": "handleImprove", + "pattern": "improve [dimension]", + "cascade": false, + "creates": "improvement_task + quality_recheck" + } + }, + "pipelines": { "spec-only": { "description": "Specification pipeline: research+discuss -> draft+discuss x4 -> quality+discuss", diff --git a/.claude/skills/team-lifecycle-v4/subagents/doc-generation-subagent.md b/.claude/skills/team-lifecycle-v4/subagents/doc-generation-subagent.md new file mode 100644 index 00000000..3de40a8e --- /dev/null +++ b/.claude/skills/team-lifecycle-v4/subagents/doc-generation-subagent.md @@ -0,0 +1,62 @@ +# Doc Generation Subagent + +文档生成执行引擎。由 writer 主 agent 通过 Inner Loop 调用。 +负责 CLI 多视角分析 + 模板填充 + 文件写入。 + +## Design Rationale + +从 v4.0 的 writer 内联 CLI 执行,改为 subagent 隔离调用。 +好处:CLI 调用的大量 token 消耗不污染 writer 主 agent 上下文, +writer 只拿到压缩摘要,可在多任务间保持上下文连续。 + +## Invocation + +``` +Task({ + subagent_type: "universal-executor", + run_in_background: false, + description: "Generate ", + prompt: `## Document Generation: + +### Session +- Folder: +- Spec config: + +### Document Config +- Type: +- Template: +- Output: +- Prior discussion: + +### Writer Accumulator (prior decisions) + + +### Execution Strategy +<从 generate-doc.md 对应 doc-type 段落加载> + +### Output Requirements +1. Write document to (follow template + document-standards.md) +2. Return JSON summary: +{ + "artifact_path": "", + "summary": "<100-200字>", + "key_decisions": [], + "sections_generated": [], + "cross_references": [], + "warnings": [] +}` +}) +``` + +## Doc Type Strategies + +(直接引用 generate-doc.md 的 DRAFT-001/002/003/004 策略,不重复) +See: roles/writer/commands/generate-doc.md + +## Error Handling + +| Scenario | Resolution | +|----------|------------| +| CLI 工具失败 | fallback chain: gemini → codex → claude | +| Template 不存在 | 返回错误 JSON | +| Prior doc 不存在 | 返回错误 JSON,writer 决定是否继续 | diff --git a/ccw/frontend/package.json b/ccw/frontend/package.json index b87e0f6d..b31bc0f3 100644 --- a/ccw/frontend/package.json +++ b/ccw/frontend/package.json @@ -53,10 +53,10 @@ "sonner": "^2.0.7", "tailwind-merge": "^2.5.0", "web-vitals": "^5.1.0", - "zod": "^4.1.13", - "zustand": "^5.0.0", "xterm": "^5.3.0", - "xterm-addon-fit": "^0.8.0" + "xterm-addon-fit": "^0.8.0", + "zod": "^4.1.13", + "zustand": "^5.0.0" }, "devDependencies": { "@playwright/test": "^1.57.0", diff --git a/ccw/frontend/src/components/memory/V2PipelineTab.tsx b/ccw/frontend/src/components/memory/V2PipelineTab.tsx index 5699534c..06f70ad7 100644 --- a/ccw/frontend/src/components/memory/V2PipelineTab.tsx +++ b/ccw/frontend/src/components/memory/V2PipelineTab.tsx @@ -5,6 +5,7 @@ import { useState } from 'react'; import { useIntl } from 'react-intl'; +import { formatDistanceToNow } from 'date-fns'; import { Zap, CheckCircle, @@ -17,11 +18,17 @@ import { FileText, Database, Activity, + Copy, + Check, } from 'lucide-react'; +import ReactMarkdown from 'react-markdown'; +import remarkGfm from 'remark-gfm'; +import rehypeHighlight from 'rehype-highlight'; import { Card } from '@/components/ui/Card'; import { Button } from '@/components/ui/Button'; import { Badge } from '@/components/ui/Badge'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/Dialog'; +import 'highlight.js/styles/github-dark.css'; import { useExtractionStatus, useConsolidationStatus, @@ -31,6 +38,24 @@ import { } from '@/hooks/useMemoryV2'; import { cn } from '@/lib/utils'; +// ========== Helper Functions ========== + +/** + * Format a timestamp to relative time string + */ +function formatRelativeTime(timestamp: number | string | undefined): string | null { + if (!timestamp) return null; + + try { + const date = typeof timestamp === 'string' ? new Date(timestamp) : new Date(timestamp); + if (isNaN(date.getTime())) return null; + + return formatDistanceToNow(date, { addSuffix: true }); + } catch { + return null; + } +} + // ========== Status Badge ========== const STATUS_CONFIG: Record = { @@ -66,6 +91,7 @@ function ExtractionCard() { // Check if any job is running const hasRunningJob = status?.jobs?.some(j => j.status === 'running'); + const lastRunText = formatRelativeTime(status?.lastRun); return ( @@ -78,6 +104,11 @@ function ExtractionCard() {

{intl.formatMessage({ id: 'memory.v2.extraction.description', defaultMessage: 'Extract structured memories from CLI sessions' })}

+ {lastRunText && ( +

+ {intl.formatMessage({ id: 'memory.v2.extraction.lastRun', defaultMessage: 'Last run' })}: {lastRunText} +

+ )} {status && (
@@ -150,12 +181,24 @@ function ConsolidationCard() { const { data: status, isLoading, refetch } = useConsolidationStatus(); const trigger = useTriggerConsolidation(); const [showPreview, setShowPreview] = useState(false); + const [copied, setCopied] = useState(false); const handleTrigger = () => { trigger.mutate(); }; + const handleCopy = async () => { + try { + await navigator.clipboard.writeText(status?.memoryMdPreview || ''); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch (err) { + console.error('Failed to copy:', err); + } + }; + const isRunning = status?.status === 'running'; + const lastRunText = formatRelativeTime(status?.lastRun); return ( <> @@ -169,6 +212,11 @@ function ConsolidationCard() {

{intl.formatMessage({ id: 'memory.v2.consolidation.description', defaultMessage: 'Merge extracted results into MEMORY.md' })}

+ {lastRunText && ( +

+ {intl.formatMessage({ id: 'memory.v2.consolidation.lastRun', defaultMessage: 'Last run' })}: {lastRunText} +

+ )}
{status && } @@ -226,17 +274,43 @@ function ConsolidationCard() { {/* MEMORY.md Preview Dialog */} - + - - - MEMORY.md + + + + MEMORY.md + + -
-
-              {status?.memoryMdPreview || 'No content available'}
-            
+
+
+ + {status?.memoryMdPreview || intl.formatMessage({ + id: 'memory.v2.consolidation.noContent', + defaultMessage: 'No content available' + })} + +
@@ -246,10 +320,55 @@ function ConsolidationCard() { // ========== Jobs List ========== +interface V2Job { + kind: string; + job_key: string; + status: 'pending' | 'running' | 'done' | 'error'; + last_error?: string; + worker_id?: string; + started_at?: number; + finished_at?: number; + retry_remaining?: number; +} + function JobsList() { const intl = useIntl(); const [kindFilter, setKindFilter] = useState(''); - const { data, isLoading, refetch } = useV2Jobs(kindFilter ? { kind: kindFilter } : undefined); + const [statusFilter, setStatusFilter] = useState(''); + const [selectedJob, setSelectedJob] = useState(null); + const [copiedError, setCopiedError] = useState(false); + + // Build filters object + const filters = { + ...(kindFilter && { kind: kindFilter }), + ...(statusFilter && { status_filter: statusFilter }), + }; + + const { data, isLoading, refetch } = useV2Jobs(Object.keys(filters).length > 0 ? filters : undefined); + + // Format timestamp to readable string + const formatTimestamp = (timestamp: number | undefined): string => { + if (!timestamp) return '-'; + try { + const date = new Date(timestamp); + return date.toLocaleString(); + } catch { + return '-'; + } + }; + + // Copy error to clipboard + const handleCopyError = async () => { + if (selectedJob?.last_error) { + try { + await navigator.clipboard.writeText(selectedJob.last_error); + setCopiedError(true); + setTimeout(() => setCopiedError(false), 2000); + } catch (err) { + console.error('Failed to copy:', err); + } + } + }; return ( @@ -264,9 +383,36 @@ function JobsList() { onChange={(e) => setKindFilter(e.target.value)} className="px-2 py-1 text-sm border rounded bg-background" > - - - + + + + + + +
+                  {selectedJob.last_error}
+                
+ + )} + + {!selectedJob?.last_error && ( +
+ +

+ {intl.formatMessage({ id: 'memory.v2.jobs.detail.noError', defaultMessage: 'No error' })} +

+
+ )} + + +
); } +// ========== Pipeline Status Banner ========== + +function PipelineStatusBanner() { + const intl = useIntl(); + const { data } = useV2Jobs(); + + // Detect running and error jobs + const jobs = data?.jobs || []; + const runningJobs = jobs.filter(j => j.status === 'running'); + const errorJobs = jobs.filter(j => j.status === 'error'); + const hasRunningJobs = runningJobs.length > 0; + const errorCount = errorJobs.length; + const runningCount = runningJobs.length; + + if (hasRunningJobs) { + return ( +
+ + + {intl.formatMessage( + { id: 'memory.v2.statusBanner.running', defaultMessage: 'Pipeline Running - {count} job(s) in progress' }, + { count: runningCount } + )} + +
+ ); + } + + if (errorCount > 0) { + return ( +
+ + + {intl.formatMessage( + { id: 'memory.v2.statusBanner.hasErrors', defaultMessage: 'Pipeline Idle - {count} job(s) failed' }, + { count: errorCount } + )} + +
+ ); + } + + return null; +} + // ========== Main Component ========== export function V2PipelineTab() { return (
+
diff --git a/ccw/frontend/src/index.css b/ccw/frontend/src/index.css index f416a12b..05dcc459 100644 --- a/ccw/frontend/src/index.css +++ b/ccw/frontend/src/index.css @@ -744,3 +744,125 @@ [data-reduced-motion="true"] .bg-image-layer { transition: none !important; } + +/* =========================== + Markdown Preview Styles + =========================== */ +.markdown-preview { + padding: 1rem; +} + +.markdown-preview .prose { + color: hsl(var(--text)); + max-width: none; +} + +.markdown-preview .prose h1, +.markdown-preview .prose h2, +.markdown-preview .prose h3, +.markdown-preview .prose h4 { + color: hsl(var(--text)); + margin-top: 1.5em; + margin-bottom: 0.5em; + font-weight: 600; +} + +.markdown-preview .prose h1 { font-size: 1.5em; } +.markdown-preview .prose h2 { font-size: 1.25em; } +.markdown-preview .prose h3 { font-size: 1.125em; } +.markdown-preview .prose h4 { font-size: 1em; } + +.markdown-preview .prose p { + margin-top: 0.75em; + margin-bottom: 0.75em; +} + +.markdown-preview .prose ul, +.markdown-preview .prose ol { + margin-top: 0.75em; + margin-bottom: 0.75em; + padding-left: 1.5em; +} + +.markdown-preview .prose li { + margin-top: 0.25em; + margin-bottom: 0.25em; +} + +.markdown-preview .prose code { + background-color: hsl(var(--muted)); + padding: 0.125em 0.375em; + border-radius: 0.25em; + font-size: 0.875em; +} + +.markdown-preview .prose pre { + background-color: hsl(var(--muted)); + padding: 1em; + border-radius: 0.5em; + overflow-x: auto; + margin-top: 0.75em; + margin-bottom: 0.75em; +} + +.markdown-preview .prose pre code { + background-color: transparent; + padding: 0; + font-size: 0.875em; +} + +.markdown-preview .prose blockquote { + border-left: 3px solid hsl(var(--accent)); + padding-left: 1em; + margin-left: 0; + color: hsl(var(--text-secondary)); + font-style: italic; +} + +.markdown-preview .prose a { + color: hsl(var(--accent)); + text-decoration: underline; +} + +.markdown-preview .prose a:hover { + opacity: 0.8; +} + +.markdown-preview .prose table { + width: 100%; + border-collapse: collapse; + margin-top: 0.75em; + margin-bottom: 0.75em; +} + +.markdown-preview .prose th, +.markdown-preview .prose td { + border: 1px solid hsl(var(--border)); + padding: 0.5em 0.75em; + text-align: left; +} + +.markdown-preview .prose th { + background-color: hsl(var(--muted)); + font-weight: 600; +} + +.markdown-preview .prose hr { + border: none; + border-top: 1px solid hsl(var(--border)); + margin-top: 1.5em; + margin-bottom: 1.5em; +} + +/* Highlight.js overrides for dark theme */ +.markdown-preview .hljs { + background: transparent !important; +} + +[data-theme^="dark"] .markdown-preview .prose pre { + background-color: hsl(220 25% 12%); +} + +[data-theme^="dark"] .markdown-preview .prose code { + background-color: hsl(220 25% 18%); +} diff --git a/ccw/frontend/src/lib/api.ts b/ccw/frontend/src/lib/api.ts index cbf70d7e..6da6d13c 100644 --- a/ccw/frontend/src/lib/api.ts +++ b/ccw/frontend/src/lib/api.ts @@ -1642,6 +1642,7 @@ export async function unarchiveMemory(memoryId: string, projectPath?: string): P export interface ExtractionStatus { total_stage1: number; + lastRun?: number; jobs: Array<{ job_key: string; status: string; diff --git a/ccw/frontend/src/locales/en/memory.json b/ccw/frontend/src/locales/en/memory.json index 7719e131..c80413a9 100644 --- a/ccw/frontend/src/locales/en/memory.json +++ b/ccw/frontend/src/locales/en/memory.json @@ -119,6 +119,7 @@ "extracting": "Extracting...", "extracted": "Extracted", "recentJobs": "Recent Jobs", + "lastRun": "Last run", "triggered": "Extraction triggered", "triggerError": "Failed to trigger extraction" }, @@ -132,8 +133,11 @@ "exists": "Exists", "notExists": "Not Exists", "inputs": "Inputs", + "lastRun": "Last run", "triggered": "Consolidation triggered", - "triggerError": "Failed to trigger consolidation" + "triggerError": "Failed to trigger consolidation", + "copySuccess": "Copied", + "noContent": "No content available" }, "jobs": { "title": "Jobs", @@ -144,7 +148,26 @@ "noJobs": "No jobs found", "allKinds": "All Kinds", "extraction": "Extraction", - "consolidation": "Consolidation" + "consolidation": "Consolidation", + "statusFilter": { + "all": "All Status", + "pending": "Pending", + "running": "Running", + "done": "Done", + "error": "Error" + }, + "detail": { + "title": "Job Details", + "kind": "Kind", + "jobKey": "Job ID", + "status": "Status", + "startedAt": "Started At", + "finishedAt": "Finished At", + "workerId": "Worker ID", + "retryRemaining": "Retry Remaining", + "error": "Error", + "noError": "No error" + } }, "status": { "idle": "Idle", @@ -153,6 +176,10 @@ "done": "Done", "error": "Error", "pending": "Pending" + }, + "statusBanner": { + "running": "Pipeline Running - {count} job(s) in progress", + "hasErrors": "Pipeline Idle - {count} job(s) failed" } } } diff --git a/ccw/frontend/src/locales/zh/memory.json b/ccw/frontend/src/locales/zh/memory.json index 61b54121..eba5778c 100644 --- a/ccw/frontend/src/locales/zh/memory.json +++ b/ccw/frontend/src/locales/zh/memory.json @@ -119,6 +119,7 @@ "extracting": "提取中...", "extracted": "已提取", "recentJobs": "最近作业", + "lastRun": "上次运行", "triggered": "提取已触发", "triggerError": "触发提取失败" }, @@ -132,8 +133,11 @@ "exists": "存在", "notExists": "不存在", "inputs": "输入", + "lastRun": "上次运行", "triggered": "合并已触发", - "triggerError": "触发合并失败" + "triggerError": "触发合并失败", + "copySuccess": "复制成功", + "noContent": "暂无内容" }, "jobs": { "title": "作业列表", @@ -144,7 +148,26 @@ "noJobs": "暂无作业记录", "allKinds": "所有类型", "extraction": "提取", - "consolidation": "合并" + "consolidation": "合并", + "statusFilter": { + "all": "所有状态", + "pending": "等待", + "running": "运行中", + "done": "完成", + "error": "错误" + }, + "detail": { + "title": "作业详情", + "kind": "类型", + "jobKey": "作业 ID", + "status": "状态", + "startedAt": "开始时间", + "finishedAt": "结束时间", + "workerId": "Worker ID", + "retryRemaining": "剩余重试次数", + "error": "错误信息", + "noError": "无错误" + } }, "status": { "idle": "空闲", @@ -153,6 +176,10 @@ "done": "完成", "error": "错误", "pending": "等待" + }, + "statusBanner": { + "running": "Pipeline 运行中 - {count} 个作业正在执行", + "hasErrors": "Pipeline 空闲 - {count} 个作业失败" } } }