diff --git a/.codex/hooks.json b/.codex/hooks.json index 6f20ff8..809c488 100644 --- a/.codex/hooks.json +++ b/.codex/hooks.json @@ -2,7 +2,7 @@ "hooks": { "SessionStart": [ { - "matcher": "startup|resume|clear|compact", + "matcher": "startup|resume", "hooks": [ { "type": "command", diff --git a/.evozeus-wrapper/CHANGELOG.md b/.evozeus-wrapper/CHANGELOG.md index 8bbf64b..23c231e 100644 --- a/.evozeus-wrapper/CHANGELOG.md +++ b/.evozeus-wrapper/CHANGELOG.md @@ -8,15 +8,20 @@ Wrapper harness migrations are recorded under `.evozeus-wrapper/docs/migrations/ ### Skill changes -- None yet. +- Upgraded the embedded EvoZeus-wrapper harness from v0.9.1 to v0.10.0. +- Clarified that the project hook covers canonical-repository maintenance while Skill-entry preflight remains prompt-enforced. ### Feedback / Issues -- None yet. +- MetaInFLow/EvoZeus-wrapper#12: correct hook capability scope and add the global session dispatcher lifecycle. +- MetaInFLow/EvoZeus-wrapper#14: preserve target headings and accurate layout labels during harness refresh. ### Verification -- None yet. +- `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py structure` +- `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py doctor --repo MetaInFLow/metainflow-dev-tasks` +- Skill format validation and repository hook adapter smoke test. + ## [v0.1.0] - 2026-07-20 diff --git a/.evozeus-wrapper/docs/designs/2026-07-20-upgrade-wrapper-v0.10.0.md b/.evozeus-wrapper/docs/designs/2026-07-20-upgrade-wrapper-v0.10.0.md new file mode 100644 index 0000000..12ff66a --- /dev/null +++ b/.evozeus-wrapper/docs/designs/2026-07-20-upgrade-wrapper-v0.10.0.md @@ -0,0 +1,38 @@ +# Upgrade EvoZeus-wrapper Harness to v0.10.0 + +## Related Issue + +- MetaInFLow/EvoZeus-wrapper#12 defines the corrected hook capability scopes and global session dispatcher. +- MetaInFLow/EvoZeus-wrapper#14 tracks the migration regression found while applying this upgrade. + +## Optimization Goal + +Upgrade the target harness from v0.9.1 to v0.10.0 without changing the development-task management rules. The resulting contract must distinguish canonical-repository maintenance hooks, the global session dispatcher, and prompt-enforced Skill-entry preflight. + +## Direction + +- Refresh only EvoZeus-wrapper-managed files. +- Preserve the target Skill title and all business instruction sections. +- Record the actual `consolidated-v2 -> consolidated-v2` version refresh. +- Keep the Skill release version independent from the wrapper harness version. +- Do not broadly upgrade other registered Skills until wrapper issue #14 is resolved. + +## Implementation Plan + +1. Apply the official v0.10.0 harness migration after a conflict-free dry-run. +2. Restore target-owned heading content removed by the migrator. +3. Correct the generated layout label and retain the wrapper migration record. +4. Refresh the manifest, preflight checker, onboarding guide, project hook adapter, and Skill status prelude. +5. Install the user-level global dispatcher separately with backup and trust-pending state. + +## Verification Plan + +- Run target structure and source-contract preflight checks. +- Validate the Skill format. +- Execute the repository hook adapter with synthetic `SessionStart` input. +- Check the staged diff for whitespace errors and confirm business sections remain present. +- Require the GitHub pull-request gate to pass before merge. + +## Release Plan + +Merge the harness-only PR into `main` without changing Skill release v0.1.0. The wrapper migration remains independently versioned as v0.10.0. Roll back by reverting the migration commit and uninstalling the global dispatcher if required. diff --git a/.evozeus-wrapper/docs/migrations/2026-07-20-v0.9.1-to-v0.10.0.md b/.evozeus-wrapper/docs/migrations/2026-07-20-v0.9.1-to-v0.10.0.md new file mode 100644 index 0000000..dc7ba11 --- /dev/null +++ b/.evozeus-wrapper/docs/migrations/2026-07-20-v0.9.1-to-v0.10.0.md @@ -0,0 +1,26 @@ +# EvoZeus-wrapper Harness Refresh:v0.9.1 -> v0.10.0 + +- 日期:2026-07-20 +- Wrapper 版本:v0.9.1 -> v0.10.0 +- 新事实源:`.evozeus-wrapper/wrapper.json` +- 目标:修复 consolidated-v2 harness 并刷新 wrapper-managed contract。 + +## 移动记录 + +- 无文件移动;刷新 consolidated-v2 managed files。 + +## 保留的宿主接点 + +- `.codex/hooks.json` +- `.github/ISSUE_TEMPLATE/` +- `.github/pull_request_template.md` +- `.github/workflows/evozeus-wrapper-preflight.yml` + +## 验证 + +- `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py structure` +- `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py runtime` + +## 回滚 + +- 回滚包含本次迁移的 Git commit。 diff --git a/.evozeus-wrapper/docs/onboarding.md b/.evozeus-wrapper/docs/onboarding.md index 42abc71..2d95bcf 100644 --- a/.evozeus-wrapper/docs/onboarding.md +++ b/.evozeus-wrapper/docs/onboarding.md @@ -7,12 +7,21 @@ 1. 在 EvoZeus-wrapper repo 中运行 `onboarding.installation.command`,把 runtime install 建成指向 canonical repo 的 symlink。 2. 真实目录不会被删除。先用 `--dry-run` 查看计划;确认归档后显式增加 `--approve-archive`。原目录归档到 `~/.evozeus/archives/runtime-installs/`。 3. 运行 `onboarding.installation.verification`,确认 runtime pointer 和 canonical repo 一致。 +4. runtime Skill 安装与全局 hook 安装是两个状态;软链接成功不代表 `global_session_dispatcher` 已安装或已信任。 + +## 全局 Dispatcher + +- 在 EvoZeus-wrapper repo 先运行 `python3 scripts/evozeus_wrapper.py hook global plan --json`。 +- 用户明确确认后运行 `hook global install --approve --json`,结构化合并 `~/.codex/hooks.json`。 +- 安装后通过 `/hooks` 审核,再用 `hook global trust --status trusted --approve --json` 记录审核结果。 +- global dispatcher 在 `SessionStart` 聚合检查全部 registered wrapped Skills,不精确绑定随后被选中的某个 Skill。 ## 调用 - 调用触发词和业务入口归目标 Skill 的 canonical `SKILL.md` 所有,wrapper 不猜测业务调用方式。 - 按 `onboarding.invocation.instruction` 在新的消费项目会话中调用 Skill。 - 运行 `onboarding.invocation.verification`,确认宿主加载的是 canonical repo,并通过 consumer-project smoke test。 +- Skill 被选中后,instruction surface 的 `skill_entry_preflight` 在业务主链路前检查当前 Skill;该步骤不是 native hook。 ## 初始化 diff --git a/.evozeus-wrapper/hooks/evozeus_wrapper_start_check.py b/.evozeus-wrapper/hooks/evozeus_wrapper_start_check.py old mode 100644 new mode 100755 index 68e40f5..7a1b82e --- a/.evozeus-wrapper/hooks/evozeus_wrapper_start_check.py +++ b/.evozeus-wrapper/hooks/evozeus_wrapper_start_check.py @@ -5,6 +5,7 @@ import os import re import sys +import time from datetime import datetime, timezone from pathlib import Path from typing import Any @@ -16,6 +17,8 @@ LATEST_VERSION_ENV = "EVOZEUS_WRAPPER_LATEST_VERSION" ENFORCEMENT_ENV = "EVOZEUS_WRAPPER_HOOK_ENFORCEMENT" LATEST_RELEASE_URL = "https://api.github.com/repos/MetaInFLow/EvoZeus-wrapper/releases/latest" +GLOBAL_CACHE_PATH = ".evozeus/cache/evozeus-wrapper-latest.json" +GLOBAL_CACHE_TTL_SECONDS = 3600 def repo_root() -> Path: @@ -32,6 +35,24 @@ def read_json(path: Path) -> dict[str, Any] | None: return data if isinstance(data, dict) else None +def write_latest_cache(home: Path, version: str, checked_at_epoch: int) -> None: + path = home / GLOBAL_CACHE_PATH + try: + path.parent.mkdir(parents=True, exist_ok=True) + temporary = path.with_name(f".{path.name}.tmp") + temporary.write_text( + json.dumps( + {"version": version, "checked_at_epoch": checked_at_epoch}, + ensure_ascii=False, + ) + + "\n", + encoding="utf-8", + ) + temporary.replace(path) + except OSError: + pass + + def version_key(tag: str) -> tuple[int, int, int] | None: match = re.fullmatch(r"v(\d+)\.(\d+)\.(\d+)", tag) if not match: @@ -64,6 +85,8 @@ def resolve_latest_version( current: str, environment: dict[str, str] | None = None, fetcher=None, + home: Path | None = None, + now_epoch: int | None = None, ) -> dict[str, str | None]: environment = os.environ if environment is None else environment checked_at = datetime.now(timezone.utc).isoformat() @@ -76,8 +99,27 @@ def resolve_latest_version( "url": None, "error": None, } + home = Path.home() if home is None else home.expanduser().resolve() + now_epoch = int(time.time()) if now_epoch is None else now_epoch + cache = read_json(home / GLOBAL_CACHE_PATH) or {} + cached_version = cache.get("version") + cached_at = cache.get("checked_at_epoch") + if ( + isinstance(cached_version, str) + and version_key(cached_version) + and isinstance(cached_at, int) + and max(0, now_epoch - cached_at) <= GLOBAL_CACHE_TTL_SECONDS + ): + return { + "version": cached_version, + "source": "global_dispatcher_cache", + "checked_at": checked_at, + "url": None, + "error": None, + } release = (fetcher or fetch_latest_release)() if release.get("version"): + write_latest_cache(home, release["version"], now_epoch) return { "version": release["version"], "source": "github_latest_release", @@ -107,7 +149,10 @@ def block(reason: str, next_action: str) -> int: "systemMessage": reason, "hookSpecificOutput": { "hookEventName": "SessionStart", - "additionalContext": f"EvoZeus-wrapper blocked start. next_action={next_action}", + "additionalContext": ( + "EvoZeus-wrapper capability=repo_maintenance_hook; " + f"scope=canonical_repository; blocked start; next_action={next_action}" + ), }, } ) @@ -121,7 +166,9 @@ def allow(level: str, message: str, next_action: str) -> int: "hookSpecificOutput": { "hookEventName": "SessionStart", "additionalContext": ( - f"EvoZeus-wrapper hook_start_check level={level}; next_action={next_action}; {message}" + "EvoZeus-wrapper capability=repo_maintenance_hook; " + f"scope=canonical_repository; hook_start_check level={level}; " + f"next_action={next_action}; {message}" ), }, } diff --git a/.evozeus-wrapper/scripts/evozeus_wrapper_preflight.py b/.evozeus-wrapper/scripts/evozeus_wrapper_preflight.py index 3666610..03305ce 100755 --- a/.evozeus-wrapper/scripts/evozeus_wrapper_preflight.py +++ b/.evozeus-wrapper/scripts/evozeus_wrapper_preflight.py @@ -267,25 +267,80 @@ def detected_plugin_manifests(target: Path) -> list[str]: def check_integration_contract(target: Path, manifest: dict | None) -> None: integration = (manifest or {}).get("integration") or {} + registration = ((manifest or {}).get("hook_registration") or {}).get("codex") or {} mode = integration.get("mode") + capabilities = integration.get("capabilities") or {} + repo_hook = capabilities.get("repo_maintenance_hook") or {} + global_dispatcher = capabilities.get("global_session_dispatcher") or {} + skill_entry = capabilities.get("skill_entry_preflight") or {} + invocation_hook = capabilities.get("skill_invocation_hook") or {} + + if repo_hook.get("covers_skill_invocation"): + fail("repo_maintenance_hook cannot claim Skill-invocation coverage") + if global_dispatcher.get("covers_skill_invocation"): + fail("global_session_dispatcher cannot claim per-Skill invocation coverage") + if global_dispatcher.get("installed") or global_dispatcher.get("native_enforced"): + fail("portable manifest cannot persist user-level global dispatcher installation state") + if global_dispatcher.get("trust_status") not in {None, "not_installed"}: + fail("portable manifest cannot persist user-level global dispatcher trust state") + if skill_entry.get("native_enforced"): + fail("skill_entry_preflight is prompt-compliance based, not native enforcement") + if skill_entry.get("installed"): + surface_rel = (manifest or {}).get("instruction_surface") or integration.get("root_entry") + if not isinstance(surface_rel, str) or not surface_rel: + fail("skill_entry_preflight installation requires an instruction_surface") + relative_surface = Path(surface_rel) + if relative_surface.is_absolute() or ".." in relative_surface.parts: + fail("skill_entry_preflight instruction_surface must stay inside target") + surface = target / relative_surface + cursor = surface + while cursor != target: + if cursor.is_symlink(): + fail("skill_entry_preflight instruction_surface cannot use symlink components") + cursor = cursor.parent + try: + surface.resolve(strict=True).relative_to(target.resolve()) + except (OSError, ValueError): + fail("skill_entry_preflight instruction_surface is missing or outside target") + if not surface.is_file(): + fail("skill_entry_preflight instruction_surface is missing") + content = content_after_frontmatter(surface.read_text(encoding="utf-8")).lstrip() + lines = content.splitlines() + if content.startswith("## EvoZeus-wrapper 状态检查"): + has_status_prelude = True + else: + has_status_prelude = bool( + lines + and lines[0].startswith("# ") + and "\n".join(lines[1:]).lstrip().startswith( + "## EvoZeus-wrapper 状态检查" + ) + ) + if not has_status_prelude: + fail("skill_entry_preflight installation requires the status prelude") + if registration.get("capability") == "repo_maintenance_hook": + if registration.get("scope") != "canonical_repository": + fail("repo_maintenance_hook registration scope must be canonical_repository") + if registration.get("covers_skill_invocation"): + fail("repo_maintenance_hook registration cannot claim Skill-invocation coverage") + if not mode: warn("wrapper manifest has no integration.mode; treating runtime checks as prompt/manual fallback") return if mode == "native_host_hook": - hooks = detected_hook_files(target) - plugins = detected_plugin_manifests(target) - codex_project_hook = CODEX_HOOKS_CONFIG in hooks and CODEX_START_HOOK_SCRIPT in hooks - plugin_lifecycle_hook = bool(hooks and plugins) - if not codex_project_hook and not plugin_lifecycle_hook: - fail( - "integration.mode is native_host_hook but host hook evidence is missing: " - f"hooks={hooks or 'none'}, plugin_manifests={plugins or 'none'}" - ) - ok("integration contract has host hook evidence") + if not integration.get("native_skill_invocation_hook_installed"): + fail("native Skill-invocation coverage requires explicit invocation-hook evidence") + if not invocation_hook.get("supported") or not invocation_hook.get("installed"): + fail("native Skill-invocation coverage requires a supported installed SkillInvoke hook") + ok("integration contract has native Skill-invocation hook evidence") return if mode in {"bootstrap_skill", "prompt_runtime_check", "manual_only"}: + if integration.get("native_skill_invocation_hook_installed"): + fail(f"integration.mode={mode} conflicts with native Skill-invocation hook installation") + if integration.get("native_host_hook_installed"): + fail("deprecated native_host_hook_installed cannot overstate Skill-invocation coverage") ok(f"integration contract declares non-native mode: {mode}") return @@ -479,8 +534,24 @@ def check_status_prelude(skill_text: str, label: str = "SKILL.md") -> None: def root_entry_path(target: Path) -> Path: manifest = load_wrapper_manifest(target) if manifest and manifest.get("instruction_surface"): - manifest_surface = target / manifest["instruction_surface"] - if manifest_surface.exists(): + raw_surface = manifest["instruction_surface"] + relative = Path(raw_surface) if isinstance(raw_surface, str) else Path("") + if relative.is_absolute() or ".." in relative.parts: + fail(f"manifest instruction_surface must stay inside target: {raw_surface}") + manifest_surface = target / relative + cursor = manifest_surface + while cursor != target: + if cursor.is_symlink(): + fail( + "manifest instruction_surface cannot use symlink components: " + f"{raw_surface}" + ) + cursor = cursor.parent + try: + manifest_surface.resolve(strict=True).relative_to(target.resolve()) + except (OSError, ValueError): + fail(f"manifest instruction_surface is missing or outside target: {raw_surface}") + if manifest_surface.is_file(): return manifest_surface fail(f"manifest instruction_surface is missing: {manifest['instruction_surface']}") skill = target / "SKILL.md" diff --git a/.evozeus-wrapper/wrapper.json b/.evozeus-wrapper/wrapper.json index a3ed500..85cec7e 100644 --- a/.evozeus-wrapper/wrapper.json +++ b/.evozeus-wrapper/wrapper.json @@ -1,6 +1,6 @@ { "wrapper_repo": "MetaInFLow/EvoZeus-wrapper", - "wrapper_version": "v0.9.1", + "wrapper_version": "v0.10.0", "applied_at": "2026-07-20", "layout_version": 2, "target_wrapper_dir": ".evozeus-wrapper", @@ -65,20 +65,66 @@ }, "hook_registration": { "codex": { + "capability": "repo_maintenance_hook", "config_file": ".codex/hooks.json", "hook_script": ".evozeus-wrapper/hooks/evozeus_wrapper_start_check.py", "event": "SessionStart", - "matcher": "startup|resume|clear|compact", + "matcher": "startup|resume", + "scope": "canonical_repository", + "covers_skill_invocation": false, + "installation_status": "installed", + "trust_status": "pending_review", "trust_review": "required_by_codex_hooks", "latest_version_env": "EVOZEUS_WRAPPER_LATEST_VERSION", "enforcement_env": "EVOZEUS_WRAPPER_HOOK_ENFORCEMENT" } }, "integration": { - "mode": "native_host_hook", - "native_host_hook_installed": true, + "mode": "prompt_runtime_check", + "native_skill_invocation_hook_installed": false, + "native_host_hook_installed": false, "codex_project_hook": true, "plugin_lifecycle_hook": false, + "capabilities": { + "repo_maintenance_hook": { + "installed": true, + "native_enforced": true, + "event": "SessionStart", + "scope": "canonical_repository", + "covers_skill_invocation": false + }, + "plugin_lifecycle_hook": { + "installed": false, + "native_enforced": false, + "scope": "plugin_lifecycle", + "covers_skill_invocation": false + }, + "global_session_dispatcher": { + "installed": false, + "native_enforced": false, + "event": "SessionStart", + "scope": "all_registered_wrapped_skills", + "covers_skill_invocation": false + }, + "skill_entry_preflight": { + "installed": true, + "native_enforced": false, + "scope": "selected_skill_instruction_surface", + "covers_skill_invocation": true + }, + "tool_gateway": { + "installed": false, + "native_enforced": false, + "event": "PreToolUse", + "scope": "toolized_execution_path", + "covers_skill_invocation": false + }, + "skill_invocation_hook": { + "supported": false, + "installed": false, + "event": null + } + }, "manual_wrapper_command": "not_runtime_integration", "target_kind": "single_skill", "root_entry": "SKILL.md", @@ -88,6 +134,8 @@ ], "plugin_manifests": [], "skill_count": 0, - "description": "Codex project-local SessionStart hook is registered under .codex/hooks.json." - } + "description": "The instruction surface can require a Skill-entry preflight, but enforcement depends on prompt compliance.", + "instruction_surface": "SKILL.md" + }, + "instruction_surface": "SKILL.md" } diff --git a/SKILL.md b/SKILL.md index c224e42..5394374 100644 --- a/SKILL.md +++ b/SKILL.md @@ -5,9 +5,11 @@ description: 通过 lark-cli/LarkSuite CLI 处理「源子技术部管理系统 ## EvoZeus-wrapper 状态检查 -执行本 Skill 主链路前,优先完成状态检查。若当前只是 runtime-only install,缺少维护资产时不要把安装副本当作事实源,回 canonical repo 处理 wrapper harness 或 Skill release。 +本段是 Skill 入口 preflight。Agent 选中本 Skill 后、进入业务主链路前执行;它基本绑定当前 Skill,但依赖 instruction compliance,不是 native Skill invocation hook。 -本检查的运行时集成等级记录在 `.evozeus-wrapper/wrapper.json` 的 `integration.mode`。只有 `native_host_hook` 表示 Codex project-local hook 或其他宿主/plugin lifecycle hook 会自动触发;`prompt_runtime_check` 只是说明入口要求 agent 执行检查,不是真 hook;`hook start-check` 这类 wrapper CLI 命令只有被宿主自动调用时才算 runtime hook。 +`.evozeus-wrapper/wrapper.json` 分开记录 capability:`repo_maintenance_hook` 只在 canonical repository 作为活动项目时原生触发;`global_session_dispatcher` 在每个任务启动时聚合检查全部 wrapped Skills;本入口仍记录为 `prompt_runtime_check`。当前 Codex 没有 `SkillInvoke` 事件,不得把前两者描述成 per-Skill native invocation hook。 + +若当前只是 runtime-only install,缺少维护资产时不要把安装副本当作事实源,回 canonical repo 处理 wrapper harness 或 Skill release。 1. Skill release 状态 - 当前记录版本:`v0.1.0` @@ -15,7 +17,7 @@ description: 通过 lark-cli/LarkSuite CLI 处理「源子技术部管理系统 - 如果 GitHub latest release 更新:先更新 canonical repo,并确认 runtime install 仍指向 canonical repo。 - 如果本地版本领先 GitHub release:先完成 changelog、验证和 `vMAJOR.MINOR.PATCH` release,再把它当作稳定运行版本。 2. Wrapper harness 状态 - - 当前 wrapper 版本:`v0.9.1` + - 当前 wrapper 版本:`v0.10.0` - 事实源:`.evozeus-wrapper/wrapper.json` - 检查命令:在 EvoZeus-wrapper repo 运行 `python3 scripts/evozeus_wrapper.py harness upgrade-check --target --json` - 如果 wrapper 落后:先运行 `harness upgrade --dry-run` 生成迁移方案,再按状态检查前置、其他 wrapper 内容 append-only 的规则迁移。 @@ -194,8 +196,7 @@ description: 通过 lark-cli/LarkSuite CLI 处理「源子技术部管理系统 Target repo: `MetaInFLow/metainflow-dev-tasks` Visibility: `public` Current Skill version: `v0.1.0` -Wrapper harness version: `v0.9.1` - +Wrapper harness version: `v0.10.0` ## EvoZeus-wrapper 本区由 EvoZeus-wrapper 追加,用来说明本 Skill 的 wrapper harness 路由、版本记录和迁移规则。它不覆盖原 Skill 的业务规则;涉及业务行为变化时,仍必须走 Issue、design doc、PR、CHANGELOG 和 release。 @@ -223,7 +224,7 @@ Append-only 迁移规则: - 每次 wrapper 升级必须记录 from/to wrapper version、planned files、验证命令、回滚方案和是否需要人工 merge review。 - wrapper version 事实源是 `.evozeus-wrapper/wrapper.json` 的 `wrapper_version`;Skill release 仍以 GitHub release / `.evozeus-wrapper/CHANGELOG.md` 为准。 -Wrapper harness version: `v0.9.1` +Wrapper harness version: `v0.10.0` Wrapper manifest: `.evozeus-wrapper/wrapper.json` Feedback audit policy: `.evozeus-wrapper/policies/feedback-policy.json` Feedback audit rule: `.evozeus-wrapper/policies/audit-rule.md` @@ -235,3 +236,10 @@ Runtime integration modes: - `bootstrap_skill`:插件 Skill 基础设施可加载控制 Skill,但没有检测到宿主 lifecycle hook。 - `prompt_runtime_check`:说明入口要求 agent 执行检查,依赖 prompt compliance。 - `manual_only`:只能手动运行 wrapper 命令。 + +## EvoZeus-wrapper Migration Note: v0.9.1 -> v0.10.0 + +- Wrapper harness: `v0.9.1 -> v0.10.0` +- Layout: `consolidated-v2 -> consolidated-v2` (version refresh only) +- Host hook registration, status prelude, manifest integration, and managed links were refreshed. +- Target business rules were preserved.