feat: Update project documentation and workflow to reflect changes in task dispatching and testing processes
- Changed references from "kilo" to "Agent 子代理" in various documentation files to align with the new task dispatching method. - Updated CLAUDE.md to reflect the new agent-based execution model and modified task dispatch template. - Revised PROGRESS.md to indicate successful testing and acceptance of M1.2 with detailed results. - Enhanced communication and workflow documentation to clarify the use of Agent 子代理 for real-time collaboration and task management. - Updated team member soul files with lessons learned from recent testing and integration experiences. - Refactored test files to remove unused code and improve clarity, ensuring compliance with zero warning standards. - Documented specific experiences and lessons learned during M1.2 testing, including handling of edge cases and configuration validation.
This commit is contained in:
@@ -11,7 +11,7 @@
|
||||
|
||||
use anyhow::Result;
|
||||
use showen_v2::core::config::AppConfig;
|
||||
use showen_v2::core::message::{Destination, Envelope, Message};
|
||||
use showen_v2::core::message::Message;
|
||||
use showen_v2::core::plugin::{CapabilityTestResult, Platform, Plugin, PluginContext, PluginInfo};
|
||||
use showen_v2::core::plugin_loader::{
|
||||
ErrorPolicy, PluginLoader, PluginManifest, PluginRegistry, PluginRegistryEntry,
|
||||
@@ -84,21 +84,6 @@ fn manifest(id: &str, version: &str) -> PluginManifest {
|
||||
}
|
||||
}
|
||||
|
||||
fn manifest_with_caps(id: &str, version: &str, caps: Vec<&str>, required: Vec<&str>) -> PluginManifest {
|
||||
PluginManifest {
|
||||
id: id.to_string(),
|
||||
version: version.to_string(),
|
||||
sdk_version: "0.2.0".to_string(),
|
||||
dependencies: vec![],
|
||||
error_policy: ErrorPolicy::AutoRollback,
|
||||
so_filename: format!("lib{}.so", id.replace('-', "_")),
|
||||
capabilities: caps.iter().map(|s| s.to_string()).collect(),
|
||||
required_capabilities: required.iter().map(|s| s.to_string()).collect(),
|
||||
test_timeout_ms: 5000,
|
||||
auto_test: true,
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_registry(store: &Path, plugin_id: &str, active: &str, stable: Option<&str>) {
|
||||
let loader = PluginLoader::new(store);
|
||||
let mut registry = PluginRegistry::default();
|
||||
|
||||
@@ -7,18 +7,21 @@
|
||||
//!
|
||||
//! 经过代码审查,插件管理 API 的 Custom 消息已在 ServiceManager 中完整处理:
|
||||
//! - `plugin_enable` / `plugin_disable` → `set_plugin_enabled()` → `broadcast_plugin_states()`
|
||||
//! - `plugin_rollback` / `plugin_switch` / `plugin_install` / `plugin_check_updates` 已注册但未完整实现业务逻辑
|
||||
//! - `plugin_rollback` / `plugin_switch` / `plugin_install` 均为完整实现:
|
||||
//! `rollback_plugin`(service_manager.rs:432) / `switch_plugin_version`(:617) /
|
||||
//! `install_plugin`(:665),张明远 #25 修复(handle_manager_message Custom 分支)后落地
|
||||
//! - HTTP routes 的 `plugin_enable_route`/`plugin_disable_route` 发送 `Message::Custom{kind:"plugin_enable",...}`
|
||||
//! 到 `Destination::Manager`,ServiceManager 能够接收并处理。
|
||||
//! - `/api/plugins` 通过 `HttpState::plugin_states()` 读取状态,状态由
|
||||
//! `Message::Custom{kind:"plugin_states",...}` 广播更新,ServiceManager 在每次
|
||||
//! enable/disable 后调用 `broadcast_plugin_states()` 发出该消息。
|
||||
//! - **结论**:enable/disable 命令闭环已修复(CLAUDE.md #25 已修复项),基本链路可工作。
|
||||
//! plugin_rollback/switch/install/check_updates 仅发送消息,ServiceManager 侧仅打印日志,
|
||||
//! 尚无完整业务逻辑,属 M1.2 已知待实现项。
|
||||
//! - **结论**:Custom 六种 plugin_* kind 全部闭环,enable/disable/rollback/switch/install
|
||||
//! 均为完整实现;唯 `plugin_check_updates` 为部分实现——`check_plugin_updates`(:730)
|
||||
//! 会真实查询 repo,但结果仅 println、不回传 HTTP/不广播,结果回传链路属 Phase 2
|
||||
//! 待补(非回归缺陷)。详见 2026-07-03 王浩然风险3全量核查报告(.showen/TEAM_CHAT.md)。
|
||||
|
||||
use anyhow::Result;
|
||||
use showen_v2::core::config::AppConfig;
|
||||
use showen_v2::core::config::{self, AppConfig};
|
||||
use showen_v2::core::message::{Destination, Envelope, Message, PlayerStatusData};
|
||||
use showen_v2::core::plugin::{Platform, Plugin, PluginContext, PluginInfo};
|
||||
use showen_v2::core::service_manager::ServiceManager;
|
||||
@@ -121,7 +124,6 @@ struct RecordingPlugin {
|
||||
id: String,
|
||||
deps: Vec<String>,
|
||||
events: Arc<Mutex<Vec<String>>>,
|
||||
ctx: Option<PluginContext>,
|
||||
}
|
||||
|
||||
impl RecordingPlugin {
|
||||
@@ -130,17 +132,12 @@ impl RecordingPlugin {
|
||||
id: id.to_string(),
|
||||
deps: deps.into_iter().map(str::to_string).collect(),
|
||||
events,
|
||||
ctx: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn record(&self, entry: impl Into<String>) {
|
||||
lock_events(&self.events).push(entry.into());
|
||||
}
|
||||
|
||||
fn sender(&self) -> Option<std::sync::mpsc::Sender<Envelope>> {
|
||||
self.ctx.as_ref().map(|c| c.tx.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin for RecordingPlugin {
|
||||
@@ -161,8 +158,7 @@ impl Plugin for RecordingPlugin {
|
||||
self.deps.clone()
|
||||
}
|
||||
|
||||
fn init(&mut self, ctx: PluginContext) -> Result<()> {
|
||||
self.ctx = Some(ctx);
|
||||
fn init(&mut self, _ctx: PluginContext) -> Result<()> {
|
||||
self.record(format!("init:{}", self.id));
|
||||
Ok(())
|
||||
}
|
||||
@@ -412,9 +408,9 @@ fn test_config_update_with_corrupted_json_does_not_reload() {
|
||||
)));
|
||||
manager.start_all().expect("start_all should succeed");
|
||||
|
||||
// 写入损坏的 JSON(模拟 http 路由验证失败后不写文件,Manager 不会收到 reload 请求)
|
||||
// 在 routes.rs 中 handle_config_update 先用 config::parse_str 验证,验证失败则直接返回 400
|
||||
// 不会发送 ConfigReloadRequest。因此这里我们验证:不发 ConfigReloadRequest 则不广播 ConfigReloaded。
|
||||
// routes.rs 的 handle_config_update 先用 config::parse_str 验证请求体,
|
||||
// 损坏的 JSON 验证失败直接返回 400:不写盘、也不发送 ConfigReloadRequest。
|
||||
// 因此这里验证链路的后半段:不发 ConfigReloadRequest 则不广播 ConfigReloaded。
|
||||
|
||||
let sender = manager.sender();
|
||||
// 不发 ConfigReloadRequest,只发 Shutdown
|
||||
@@ -492,23 +488,39 @@ fn test_playlist_snapshot_includes_current_index() {
|
||||
fs::remove_dir_all(dir).expect("test dir should be removed");
|
||||
}
|
||||
|
||||
/// 播放列表为空时,系统不应崩溃(边界条件)。
|
||||
/// 播放列表为空时,配置校验应在入口边界拒绝(防退化)。
|
||||
///
|
||||
/// 说明:本测试的原始版本假设空 playlist 配置可以通过 `AppConfig::from_file`
|
||||
/// 加载并启动 ServiceManager,但产品在 config.rs `validate()` 中故意
|
||||
/// `bail!("playlist 不能为空")`(自 init 提交即存在的校验行为)。
|
||||
/// 运行时也不存在把 config.playlist 清空的路径:
|
||||
/// - `DELETE /api/videos/:name` 只删除磁盘上的视频文件,不修改 config.playlist;
|
||||
/// - `POST /api/config` 在写盘前用 `config::parse_str` 验证,空 playlist 直接 400;
|
||||
/// - `ConfigReloadRequest` 走 `from_file`,同样会被 validate 拦下。
|
||||
/// 因此"运行时空 playlist"在 AppConfig 层不可达,本测试改为锁定两个入口
|
||||
/// 边界的拒绝行为,防止该校验未来被误删。
|
||||
#[test]
|
||||
fn test_empty_playlist_does_not_panic() {
|
||||
let (mut manager, _events, dir) = setup("empty_playlist", 0);
|
||||
fn test_empty_playlist_rejected() {
|
||||
let dir = unique_test_dir("empty_playlist_rejected");
|
||||
let config_path = write_test_config(&dir, "test-title", 0);
|
||||
|
||||
// 空 playlist 仍能正常启动和关闭
|
||||
let sender = manager.sender();
|
||||
manager.start_all().expect("start_all with empty playlist should succeed");
|
||||
sender
|
||||
.send(Envelope {
|
||||
from: "test".to_string(),
|
||||
to: Destination::Manager,
|
||||
message: Message::Shutdown,
|
||||
})
|
||||
.expect("shutdown should send");
|
||||
// 启动边界:AppConfig::from_file 必须拒绝空 playlist
|
||||
let err = AppConfig::from_file(&config_path)
|
||||
.expect_err("from_file should reject empty playlist config");
|
||||
assert!(
|
||||
format!("{err:#}").contains("playlist 不能为空"),
|
||||
"from_file error should mention empty playlist, got: {err:#}"
|
||||
);
|
||||
|
||||
manager.run().expect("run with empty playlist should succeed");
|
||||
// HTTP 边界:POST /api/config 依赖 config::parse_str 的这个错误返回 400
|
||||
// (routes.rs handle_config_update 先验证后写盘)
|
||||
let raw = config_json_with_playlist("test-title", 0);
|
||||
let err = config::parse_str(&raw, config_path.clone())
|
||||
.expect_err("parse_str should reject empty playlist config");
|
||||
assert!(
|
||||
format!("{err:#}").contains("playlist 不能为空"),
|
||||
"parse_str error should mention empty playlist, got: {err:#}"
|
||||
);
|
||||
|
||||
fs::remove_dir_all(dir).expect("test dir should be removed");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user