diff --git a/docs/skill-management.md b/docs/skill-management.md index 28cf20d..f09cf73 100644 --- a/docs/skill-management.md +++ b/docs/skill-management.md @@ -33,6 +33,15 @@ DNA Memory 将“长期记忆”和“行为 Skill”分开治理: Hermes 的目标目录必须链接到同一个 `skill_root` 真源。不要复制三份后分别 修改,否则召回边界和隐私限制会随客户端漂移。 +`platform_skill_roots` 的键可以使用 `platform:instance`,例如 +`hermes:dev`。当注册表目标为 `hermes` 时,`skills sync` 会自动展开到 +所有已配置的 `hermes:*` 实例;新增 Hermes profile 后先登记其 Skill 根目录, +再运行 `skills sync --apply`。不需要在注册表中重复列出每个 profile。 + +Memory Loop 必须传真实客户端元数据:Hermes 使用 `client=hermes`。 +只有运行时明确暴露稳定会话 ID 时才传 `session_id`;否则省略,禁止使用 +`session_12345` 等占位值。 + 对依赖历史的实质任务,Memory Loop 是强制前置步骤;“继续之前的工作”、 “按上次方案”、已有项目路径、用户长期偏好、已知错误和开放事项都属于触发 条件。翻译、当前时间、一步格式化等完全自包含任务仍然跳过召回。 diff --git a/scripts/skill_manager.py b/scripts/skill_manager.py index 87b4828..ba6d55c 100644 --- a/scripts/skill_manager.py +++ b/scripts/skill_manager.py @@ -74,7 +74,16 @@ def build_sync_plan(shared_root: Path, platform_roots: Dict[str, Path], registry plan = [] for name, settings in sorted(registry.get("skills", {}).items()): source = shared_root / name - for platform in settings.get("targets", []): + platforms = [] + for target in settings.get("targets", []): + matches = [ + platform for platform in platform_roots + if platform == target or platform.startswith(f"{target}:") + ] + for platform in matches or [target]: + if platform not in platforms: + platforms.append(platform) + for platform in platforms: root = platform_roots.get(platform) target = (root / name) if root else Path("") / name if root is None: diff --git a/skills/dna-memory-loop/SKILL.md b/skills/dna-memory-loop/SKILL.md index 7e861a7..01aa581 100644 --- a/skills/dna-memory-loop/SKILL.md +++ b/skills/dna-memory-loop/SKILL.md @@ -16,7 +16,10 @@ names an existing project or path, or depends on a durable preference, known err workflow, project state, or open loop. 1. Extract one to four distinctive terms from the request, project, error, or expected result. -2. Call `memory_recall` separately for each term. Include the real client and session ID when available. +2. Call `memory_recall` separately for each term. Always pass the real client: + use `client=codex`, `client=claude-code`, or `client=hermes` for those runtimes. + Never invent client or session metadata. Pass the current stable session ID only + when the runtime exposes it; otherwise omit session_id instead of using a placeholder. 3. Deduplicate by memory ID. Inject at most five memories and about 2,000 tokens total. 4. Use only relevant results. Current files, processes, remote state, and tests override stale memory. diff --git a/tests/test_bundled_skills.py b/tests/test_bundled_skills.py index 0681e33..d68d519 100644 --- a/tests/test_bundled_skills.py +++ b/tests/test_bundled_skills.py @@ -46,3 +46,12 @@ def test_memory_loop_requires_recall_for_history_dependent_work(): assert "one to four distinctive terms" in body assert "simple, self-contained" in body assert "Memory failure must not block" in body + + +def test_memory_loop_requires_truthful_client_metadata(): + body = (ROOT / "skills/dna-memory-loop/SKILL.md").read_text(encoding="utf-8") + + assert "client=hermes" in body + assert "Never invent client or session metadata" in body + assert "omit session_id" in body + assert "placeholder" in body diff --git a/tests/test_skill_manager.py b/tests/test_skill_manager.py index 742d0ce..a783871 100644 --- a/tests/test_skill_manager.py +++ b/tests/test_skill_manager.py @@ -52,3 +52,21 @@ def test_sync_plan_never_overwrites_conflicts(tmp_path): assert actions[("shared-ok", "codex")] == "ok" assert actions[("shared-ok", "claude")] == "blocked_conflict" assert actions[("missing-target", "codex")] == "missing_source" + + +def test_sync_plan_expands_platform_target_to_named_instances(tmp_path): + shared = tmp_path / "shared" + source = skill(shared, "memory-loop", "---\nname: memory-loop\n---\n") + roots = { + "hermes": tmp_path / "hermes-default", + "hermes:htian": tmp_path / "hermes-htian", + "codex": tmp_path / "codex", + } + registry = {"skills": {"memory-loop": {"targets": ["hermes"]}}} + + plan = build_sync_plan(shared, roots, registry) + + assert [(item.platform, item.action, item.source) for item in plan] == [ + ("hermes", "create_link", str(source)), + ("hermes:htian", "create_link", str(source)), + ]