From ffcce7ac784b137f8e6b09623f4afd1373f520d1 Mon Sep 17 00:00:00 2001 From: "Anthony.Fan" Date: Sat, 25 Jul 2026 17:59:17 +0800 Subject: [PATCH 1/2] chore: record CoEvolve instruction surface --- .evozeus/wrapper.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.evozeus/wrapper.json b/.evozeus/wrapper.json index cac1125..bd48811 100644 --- a/.evozeus/wrapper.json +++ b/.evozeus/wrapper.json @@ -3,6 +3,7 @@ "wrapper_version": "v0.3.0", "applied_at": "2026-06-27", "canonical_repo": "HaodiFan/engineering-everything", + "instruction_surface": "skills/using-engineering-everything/SKILL.md", "managed_files": [ "WRAPPER.md", "docs/index.md", From 6c7387fd75f2bef3b59d83d1646ee83b3ae9a5b8 Mon Sep 17 00:00:00 2001 From: Haodi Fan Date: Sat, 25 Jul 2026 18:45:53 +0800 Subject: [PATCH 2/2] feat: adopt CoEvolve harness for plugin-first Skillware --- .codex-plugin/plugin.json | 2 +- .codex/hooks.json | 17 + CHANGELOG.md => .evozeus-wrapper/CHANGELOG.md | 54 +- WRAPPER.md => .evozeus-wrapper/WRAPPER.md | 40 +- {docs => .evozeus-wrapper/docs}/_config.yml | 0 .../docs}/design-doc-template.md | 0 .../2026-06-27-evozeus-wrapper-adoption.md | 12 +- .../2026-06-27-wrapper-status-prelude.md | 14 +- .../2026-06-28-agentos-bootloader-refactor.md | 6 +- .../designs/2026-06-28-root-skill-demotion.md | 6 +- ...26-07-25-coevolve-skillware-feasibility.md | 48 + .../docs}/designs/README.md | 0 {docs => .evozeus-wrapper/docs}/index.md | 28 +- .../2026-06-27-unknown-to-v0.2.0.md | 26 +- .../2026-06-27-v0.2.0-to-v0.3.0.md | 24 +- .../migrations/2026-07-25-layout-v1-to-v2.md | 46 + .../2026-07-25-v0.11.1-to-v0.11.2.md | 29 + .../docs/migrations}/README.md | 4 +- .evozeus-wrapper/docs/onboarding.md | 43 + .../hooks/evozeus_wrapper_start_check.py | 240 ++++ .evozeus-wrapper/policies/audit-rule.md | 13 + .../policies/feedback-policy.json | 30 + .../scripts/evozeus_wrapper_preflight.py | 1087 +++++++++++++++++ .evozeus-wrapper/wrapper.json | 149 +++ .evozeus/wrapper.json | 26 - .github/ISSUE_TEMPLATE/config.yml | 2 +- .github/pull_request_template.md | 8 +- .../workflows/evozeus-wrapper-preflight.yml | 60 +- README.md | 4 +- agents/openai.yaml | 2 +- .../active/2026-06-27-split-skill-library.md | 2 +- docs/testing.md | 10 +- release-notes-v0.13.0.md | 42 + scripts/evozeus_wrapper_preflight.py | 611 --------- scripts/install.py | 6 +- .../engineering-architecture-design/SKILL.md | 2 +- .../engineering-automation-playbooks/SKILL.md | 2 +- skills/engineering-build-verify/SKILL.md | 2 +- skills/engineering-everything/SKILL.md | 2 +- .../engineering-execution-planning/SKILL.md | 2 +- .../engineering-organization-systems/SKILL.md | 2 +- .../engineering-product-definition/SKILL.md | 2 +- .../engineering-project-inheritance/SKILL.md | 2 +- skills/engineering-refactoring/SKILL.md | 2 +- skills/engineering-review-release/SKILL.md | 2 +- skills/engineering-skill-evolution/SKILL.md | 2 +- skills/using-engineering-everything/SKILL.md | 52 +- tests/test_install_safety.py | 20 + 48 files changed, 2023 insertions(+), 762 deletions(-) create mode 100644 .codex/hooks.json rename CHANGELOG.md => .evozeus-wrapper/CHANGELOG.md (56%) rename WRAPPER.md => .evozeus-wrapper/WRAPPER.md (64%) rename {docs => .evozeus-wrapper/docs}/_config.yml (100%) rename {docs => .evozeus-wrapper/docs}/design-doc-template.md (100%) rename {docs => .evozeus-wrapper/docs}/designs/2026-06-27-evozeus-wrapper-adoption.md (72%) rename {docs => .evozeus-wrapper/docs}/designs/2026-06-27-wrapper-status-prelude.md (75%) rename {docs => .evozeus-wrapper/docs}/designs/2026-06-28-agentos-bootloader-refactor.md (97%) rename {docs => .evozeus-wrapper/docs}/designs/2026-06-28-root-skill-demotion.md (84%) create mode 100644 .evozeus-wrapper/docs/designs/2026-07-25-coevolve-skillware-feasibility.md rename {docs => .evozeus-wrapper/docs}/designs/README.md (100%) rename {docs => .evozeus-wrapper/docs}/index.md (63%) rename {docs/wrapper-migrations => .evozeus-wrapper/docs/migrations}/2026-06-27-unknown-to-v0.2.0.md (60%) rename {docs/wrapper-migrations => .evozeus-wrapper/docs/migrations}/2026-06-27-v0.2.0-to-v0.3.0.md (57%) create mode 100644 .evozeus-wrapper/docs/migrations/2026-07-25-layout-v1-to-v2.md create mode 100644 .evozeus-wrapper/docs/migrations/2026-07-25-v0.11.1-to-v0.11.2.md rename {docs/wrapper-migrations => .evozeus-wrapper/docs/migrations}/README.md (75%) create mode 100644 .evozeus-wrapper/docs/onboarding.md create mode 100755 .evozeus-wrapper/hooks/evozeus_wrapper_start_check.py create mode 100644 .evozeus-wrapper/policies/audit-rule.md create mode 100644 .evozeus-wrapper/policies/feedback-policy.json create mode 100755 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py create mode 100644 .evozeus-wrapper/wrapper.json delete mode 100644 .evozeus/wrapper.json create mode 100644 release-notes-v0.13.0.md delete mode 100755 scripts/evozeus_wrapper_preflight.py diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 0cfd845..4a791c9 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "engineering-everything", - "version": "0.12.0", + "version": "0.13.0", "description": "Bootloader and engineering-route skills for product definition, project inheritance, architecture, execution, verification, refactoring, release, organization systems, automation, and skill evolution.", "author": { "name": "Anthony Fan", diff --git a/.codex/hooks.json b/.codex/hooks.json new file mode 100644 index 0000000..809c488 --- /dev/null +++ b/.codex/hooks.json @@ -0,0 +1,17 @@ +{ + "hooks": { + "SessionStart": [ + { + "matcher": "startup|resume", + "hooks": [ + { + "type": "command", + "command": "/usr/bin/python3 \"$(git rev-parse --show-toplevel)/.evozeus-wrapper/hooks/evozeus_wrapper_start_check.py\"", + "timeout": 30, + "statusMessage": "Checking EvoZeus wrapper harness" + } + ] + } + ] + } +} diff --git a/CHANGELOG.md b/.evozeus-wrapper/CHANGELOG.md similarity index 56% rename from CHANGELOG.md rename to .evozeus-wrapper/CHANGELOG.md index 17cda74..af45115 100644 --- a/CHANGELOG.md +++ b/.evozeus-wrapper/CHANGELOG.md @@ -2,20 +2,50 @@ All notable changes to engineering-everything are recorded here. -Wrapper harness migrations are recorded under `docs/wrapper-migrations/`. Add them here only when the migration also changes this Skill's release contract. +Wrapper harness migrations are recorded under `.evozeus-wrapper/docs/migrations/`. Add them here only when the migration also changes this Skill's release contract. ## [Unreleased] +- Publish the validated `v0.13.0` package and CoEvolve migration after the linked pull request merges; clear this coordination entry in the release commit. + +## [v0.13.0] - 2026-07-25 + ### Skill changes - Demoted root `SKILL.md`: runtime entrypoints now live only under `skills/*/SKILL.md`. - Updated package, self-evolution, and wrapper preflight gates to use `.codex-plugin/plugin.json` plus the plugin skill library as the package contract. - Updated wrapper/dashboard docs to describe plugin-first source governance. +- Migrated the EvoZeus-CoEvolve harness from scattered layout `v0.3.0` to consolidated layout `v0.11.2`. +- Kept `skills/using-engineering-everything/SKILL.md` as the recommended bootloader and preserved all direct sub-Skill entrypoints. +- Aligned `scripts/install.py --target both` with its documented contract by creating canonical sub-Skill symlinks for Codex and Agents on fresh installs. + +### Feedback / Issues + +- Owner request: use Engineering Everything as the first non-project Skillware feasibility target for EvoZeus-CoEvolve. +- Related issue: https://github.com/HaodiFan/engineering-everything/issues/17 +- Migration recovery: the first structure gate exposed missing legacy policy files and root-only runtime pointer assumptions; CoEvolve v0.11.2 fixed both without weakening validation. -### Version Plan +### Verification + +- `python3 scripts/sync_references.py --check --json` +- `python3 scripts/eval_scenarios.py validate --json` +- `python3 scripts/skill_doctor.py --json` +- `python3 scripts/self_evolve.py check --json` +- `python3 scripts/self_evolve.py doctor --json` +- `python3 scripts/lesson.py validate` +- `python3 -m unittest discover -s tests` +- `python3 -m py_compile scripts/*.py` +- `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py structure` +- `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py doctor --repo HaodiFan/engineering-everything` +- `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py version --repo HaodiFan/engineering-everything --current-tag v0.13.0` + +### Release notes -- Recommended next release: `v0.13.0`. -- Rationale: package structure changes while runtime behavior remains routed through existing library skills. +- Major pre-1.0 package transition: plugin-first runtime entrypoints replace the retired root Skill entry. +- CoEvolve adds lifecycle governance and a recommended-entry preflight; it does not change Engineering Everything routing decisions. +- Fresh plugin-library installs now maintain one canonical source through symlinks instead of copied runtime directories. +- Native per-Skill invocation hooks remain unavailable; the bootloader preflight depends on instruction compliance. +- License remains `UNLICENSED`. ## [v0.12.0] - 2026-06-28 @@ -66,10 +96,10 @@ Wrapper harness migrations are recorded under `docs/wrapper-migrations/`. Add th - `python3 scripts/skill_doctor.py --json` - `python3 scripts/self_evolve.py check --json` - `python3 scripts/self_evolve.py doctor --json` -- `python3 scripts/evozeus_wrapper_preflight.py structure` -- `python3 scripts/evozeus_wrapper_preflight.py doctor --repo HaodiFan/engineering-everything` -- `python3 scripts/evozeus_wrapper_preflight.py version --repo HaodiFan/engineering-everything --current-tag v0.11.0` -- `python3 scripts/evozeus_wrapper_preflight.py release --tag v0.11.0 --release-notes /tmp/engineering-everything-v0.11.0-release-notes.md --skip-gh` +- `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py structure` +- `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py doctor --repo HaodiFan/engineering-everything` +- `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py version --repo HaodiFan/engineering-everything --current-tag v0.11.0` +- `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py release --tag v0.11.0 --release-notes /tmp/engineering-everything-v0.11.0-release-notes.md --skip-gh` ## [v0.10.0] - 2026-06-27 @@ -88,10 +118,10 @@ Wrapper harness migrations are recorded under `docs/wrapper-migrations/`. Add th - `python3 scripts/skill_doctor.py --json` - `python3 scripts/self_evolve.py check --json` - `python3 scripts/self_evolve.py doctor --json` -- `python3 scripts/evozeus_wrapper_preflight.py structure` -- `python3 scripts/evozeus_wrapper_preflight.py doctor --repo HaodiFan/engineering-everything` -- `python3 scripts/evozeus_wrapper_preflight.py version --repo HaodiFan/engineering-everything --current-tag v0.10.0` -- `python3 scripts/evozeus_wrapper_preflight.py release --tag v0.10.0 --release-notes release-notes.md --skip-gh` +- `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py structure` +- `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py doctor --repo HaodiFan/engineering-everything` +- `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py version --repo HaodiFan/engineering-everything --current-tag v0.10.0` +- `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py release --tag v0.10.0 --release-notes release-notes.md --skip-gh` ## [v0.9.10] - 2026-06-27 diff --git a/WRAPPER.md b/.evozeus-wrapper/WRAPPER.md similarity index 64% rename from WRAPPER.md rename to .evozeus-wrapper/WRAPPER.md index a0e4ee5..6ba3e86 100644 --- a/WRAPPER.md +++ b/.evozeus-wrapper/WRAPPER.md @@ -7,24 +7,24 @@ This repo is a wrapped Skill repo generated by EvoZeus-wrapper. - Plugin manifest: `.codex-plugin/plugin.json` - Runtime skills: `skills/*/SKILL.md` - EvoZeus project pointer: `~/.evozeus/.projects/HaodiFan/engineering-everything` -- Dashboard page: `docs/index.md` +- Dashboard page: `.evozeus-wrapper/docs/index.md` - Feedback Issue template: `.github/ISSUE_TEMPLATE/skill-feedback.yml` -- Design doc template: `docs/design-doc-template.md` -- Wrapper migration log: `docs/wrapper-migrations/` -- Changelog: `CHANGELOG.md` -- Preflight checker: `scripts/evozeus_wrapper_preflight.py` -- Wrapper manifest: `.evozeus/wrapper.json` +- Design doc template: `.evozeus-wrapper/docs/design-doc-template.md` +- Wrapper migration log: `.evozeus-wrapper/docs/migrations/` +- Changelog: `.evozeus-wrapper/CHANGELOG.md` +- Preflight checker: `.evozeus-wrapper/scripts/evozeus_wrapper_preflight.py` +- Wrapper manifest: `.evozeus-wrapper/wrapper.json` - Current Skill version: `v0.12.0` - Wrapper harness version: `v0.3.0` ## Runtime Status Check -This repo uses a plugin-first layout. Runtime behavior starts from `skills/using-engineering-everything/SKILL.md` or `skills/engineering-everything/SKILL.md`; package governance lives in `.codex-plugin/plugin.json`, `WRAPPER.md`, `docs/index.md`, and `references/self-evolution-harness.md`. +This repo uses a plugin-first layout. Runtime behavior starts from `skills/using-engineering-everything/SKILL.md` or `skills/engineering-everything/SKILL.md`; package governance lives in `.codex-plugin/plugin.json`, `.evozeus-wrapper/WRAPPER.md`, `.evozeus-wrapper/docs/index.md`, and `references/self-evolution-harness.md`. Before evolving or publishing the Skill package: 1. Check the current Skill release against GitHub latest release. -2. Check `.evozeus/wrapper.json` against the current EvoZeus-wrapper harness version. +2. Check `.evozeus-wrapper/wrapper.json` against the current EvoZeus-wrapper harness version. 3. Check the source contract with `doctor`. 4. Continue to the target Skill's main flow only after all three are OK. @@ -41,26 +41,26 @@ If this repo is private, do not assume the GitHub Pages output is private. Keep 1. The target repo is the canonical Skill source. EvoZeus and runtime installs should point to it instead of keeping copied installs. 2. Before evolving or running a wrapper-managed Skill, resolve the source in this order: - - read `.evozeus/wrapper.json`; + - read `.evozeus-wrapper/wrapper.json`; - check `~/.evozeus/.projects/HaodiFan/engineering-everything`; - verify the canonical repo git origin / GitHub repo; - inspect runtime installs only as pointers; - use GitHub user/org/public search only as fallback when wrapper state is absent. 3. Before running the Skill, check whether GitHub has a newer release: - `python3 scripts/evozeus_wrapper_preflight.py version --repo HaodiFan/engineering-everything` + `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py version --repo HaodiFan/engineering-everything` 4. User reports an unsatisfactory Skill result with the feedback Issue template. -5. Maintainer writes a design doc in `docs/designs/`. +5. Maintainer writes a design doc in `.evozeus-wrapper/docs/designs/`. 6. Maintainer opens a PR referencing the design doc. -7. PR updates plugin/library files and `CHANGELOG.md`. +7. PR updates plugin/library files and `.evozeus-wrapper/CHANGELOG.md`. 8. Release tag and release description are created after merge. ## Wrapper Harness Upgrade The Skill release version and wrapper harness version are separate axes. -- Skill release is tracked by GitHub releases and `CHANGELOG.md`. -- Wrapper harness version is tracked by `.evozeus/wrapper.json`. -- Wrapper migrations are recorded under `docs/wrapper-migrations/`. +- Skill release is tracked by GitHub releases and `.evozeus-wrapper/CHANGELOG.md`. +- Wrapper harness version is tracked by `.evozeus-wrapper/wrapper.json`. +- Wrapper migrations are recorded under `.evozeus-wrapper/docs/migrations/`. When EvoZeus-wrapper publishes a newer harness version, do not rewrite target Skill business rules. Generate a migration plan from the EvoZeus-wrapper repo: @@ -82,9 +82,9 @@ Use `vMAJOR.MINOR.PATCH`. ## Local Checks ```bash -python3 scripts/evozeus_wrapper_preflight.py doctor --repo HaodiFan/engineering-everything -python3 scripts/evozeus_wrapper_preflight.py structure -python3 scripts/evozeus_wrapper_preflight.py version --repo HaodiFan/engineering-everything -python3 scripts/evozeus_wrapper_preflight.py pr --design-doc docs/designs/.md -python3 scripts/evozeus_wrapper_preflight.py release --tag v0.12.0 --release-notes /tmp/engineering-everything-v0.12.0-release-notes.md +python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py doctor --repo HaodiFan/engineering-everything +python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py structure +python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py version --repo HaodiFan/engineering-everything +python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py pr --design-doc .evozeus-wrapper/docs/designs/.md +python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py release --tag v0.12.0 --release-notes /tmp/engineering-everything-v0.12.0-release-notes.md ``` diff --git a/docs/_config.yml b/.evozeus-wrapper/docs/_config.yml similarity index 100% rename from docs/_config.yml rename to .evozeus-wrapper/docs/_config.yml diff --git a/docs/design-doc-template.md b/.evozeus-wrapper/docs/design-doc-template.md similarity index 100% rename from docs/design-doc-template.md rename to .evozeus-wrapper/docs/design-doc-template.md diff --git a/docs/designs/2026-06-27-evozeus-wrapper-adoption.md b/.evozeus-wrapper/docs/designs/2026-06-27-evozeus-wrapper-adoption.md similarity index 72% rename from docs/designs/2026-06-27-evozeus-wrapper-adoption.md rename to .evozeus-wrapper/docs/designs/2026-06-27-evozeus-wrapper-adoption.md index 6401140..38a366f 100644 --- a/docs/designs/2026-06-27-evozeus-wrapper-adoption.md +++ b/.evozeus-wrapper/docs/designs/2026-06-27-evozeus-wrapper-adoption.md @@ -11,7 +11,7 @@ Make `engineering-everything` self-evolving through both layers: - Skill-native gates: `scripts/skill_doctor.py` and `scripts/self_evolve.py`. -- Wrapper-managed gates: `.evozeus/wrapper.json`, feedback Issue template, design docs, migration log, and `scripts/evozeus_wrapper_preflight.py`. +- Wrapper-managed gates: `.evozeus-wrapper/wrapper.json`, feedback Issue template, design docs, migration log, and `.evozeus-wrapper/scripts/evozeus_wrapper_preflight.py`. ## Direction @@ -21,21 +21,21 @@ The change is append-only for Skill runtime behavior: - Preserve existing routing and reference files. - Append wrapper source-discovery and migration sections to root `SKILL.md`. -- Record wrapper harness version in `.evozeus/wrapper.json`. +- Record wrapper harness version in `.evozeus-wrapper/wrapper.json`. - Keep wrapper version separate from Skill release version. ## Implementation plan - Skill sections to change: append `## 自进化方法` and `## EvoZeus-wrapper` to root `SKILL.md`. -- Files to update: wrapper-managed templates, `CHANGELOG.md`, version metadata, and migration docs. +- Files to update: wrapper-managed templates, `.evozeus-wrapper/CHANGELOG.md`, version metadata, and migration docs. - Backward compatibility: existing route seeds, references, and subskill routing remain unchanged. ## Verification plan -- Source discovery check: `python3 scripts/evozeus_wrapper_preflight.py doctor --repo HaodiFan/engineering-everything`. -- Manual checks: confirm `.evozeus/wrapper.json` has `wrapper_version: v0.2.0` and `canonical_repo: HaodiFan/engineering-everything`. +- Source discovery check: `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py doctor --repo HaodiFan/engineering-everything`. +- Manual checks: confirm `.evozeus-wrapper/wrapper.json` has `wrapper_version: v0.2.0` and `canonical_repo: HaodiFan/engineering-everything`. - Regression cases: verify `scripts/skill_doctor.py`, `scripts/self_evolve.py check`, and `scripts/self_evolve.py doctor` still pass. -- Preflight command: `python3 scripts/evozeus_wrapper_preflight.py structure`. +- Preflight command: `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py structure`. ## Release plan diff --git a/docs/designs/2026-06-27-wrapper-status-prelude.md b/.evozeus-wrapper/docs/designs/2026-06-27-wrapper-status-prelude.md similarity index 75% rename from docs/designs/2026-06-27-wrapper-status-prelude.md rename to .evozeus-wrapper/docs/designs/2026-06-27-wrapper-status-prelude.md index 25d272b..ca8cf72 100644 --- a/docs/designs/2026-06-27-wrapper-status-prelude.md +++ b/.evozeus-wrapper/docs/designs/2026-06-27-wrapper-status-prelude.md @@ -22,14 +22,14 @@ This is a harness migration, not a routing behavior change: - Preserve Engineering Everything's existing route priority and subskill map. - Keep Skill release version separate from wrapper harness version. -- Record the wrapper migration under `docs/wrapper-migrations/`. +- Record the wrapper migration under `.evozeus-wrapper/docs/migrations/`. ## Implementation plan - Add `EvoZeus-wrapper 状态检查` at the top of root `SKILL.md` after frontmatter. - Update wrapper-managed dashboard files to record Skill `v0.11.0` and wrapper `v0.3.0`. - Copy the `v0.3.0` wrapper preflight checker. -- Update `.evozeus/wrapper.json` to `wrapper_version: v0.3.0`. +- Update `.evozeus-wrapper/wrapper.json` to `wrapper_version: v0.3.0`. - Add changelog and migration records. ## Verification plan @@ -37,10 +37,10 @@ This is a harness migration, not a routing behavior change: - `python3 scripts/skill_doctor.py --json` - `python3 scripts/self_evolve.py check --json` - `python3 scripts/self_evolve.py doctor --json` -- `python3 scripts/evozeus_wrapper_preflight.py structure` -- `python3 scripts/evozeus_wrapper_preflight.py doctor --repo HaodiFan/engineering-everything` -- `python3 scripts/evozeus_wrapper_preflight.py version --repo HaodiFan/engineering-everything --current-tag v0.11.0` -- `python3 scripts/evozeus_wrapper_preflight.py release --tag v0.11.0 --release-notes /tmp/engineering-everything-v0.11.0-release-notes.md --skip-gh` +- `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py structure` +- `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py doctor --repo HaodiFan/engineering-everything` +- `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py version --repo HaodiFan/engineering-everything --current-tag v0.11.0` +- `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py release --tag v0.11.0 --release-notes /tmp/engineering-everything-v0.11.0-release-notes.md --skip-gh` ## Release plan @@ -52,4 +52,4 @@ This is a harness migration, not a routing behavior change: - Risk: agents may treat the status prelude as a replacement for Engineering Everything's existing route logic. - Mitigation: the status prelude explicitly says the main flow starts only after checks pass; it does not redefine routing. -- Rollback plan: revert the migration commit and restore `.evozeus/wrapper.json` to `v0.2.0`. +- Rollback plan: revert the migration commit and restore `.evozeus-wrapper/wrapper.json` to `v0.2.0`. diff --git a/docs/designs/2026-06-28-agentos-bootloader-refactor.md b/.evozeus-wrapper/docs/designs/2026-06-28-agentos-bootloader-refactor.md similarity index 97% rename from docs/designs/2026-06-28-agentos-bootloader-refactor.md rename to .evozeus-wrapper/docs/designs/2026-06-28-agentos-bootloader-refactor.md index 3ac3100..08ce083 100644 --- a/docs/designs/2026-06-28-agentos-bootloader-refactor.md +++ b/.evozeus-wrapper/docs/designs/2026-06-28-agentos-bootloader-refactor.md @@ -169,7 +169,7 @@ tests/ - supported/experimental/not-yet harness matrix; - install/update/uninstall lifecycle; - current limitations. -- Move maintainer dashboard content out of `docs/index.md` or make it clearly maintainer-only. +- Move maintainer dashboard content out of `.evozeus-wrapper/docs/index.md` or make it clearly maintainer-only. - Add PR template Behavior Evaluation fields: - scenario IDs; - entrypoint; @@ -177,7 +177,7 @@ tests/ - pass/fail; - harness/model/date. - Update workflow to run real gates, not only wrapper PR preflight. -- Update `CHANGELOG.md` Unreleased for this scope. +- Update `.evozeus-wrapper/CHANGELOG.md` Unreleased for this scope. ### Phase 3 - Optional SessionStart hook @@ -233,7 +233,7 @@ These are valid before implementation: - `python3 scripts/skill_doctor.py --json` - `python3 scripts/self_evolve.py check --json` - `python3 scripts/lesson.py validate` -- `python3 scripts/evozeus_wrapper_preflight.py pr --design-doc docs/designs/2026-06-28-agentos-bootloader-refactor.md` +- `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py pr --design-doc .evozeus-wrapper/docs/designs/2026-06-28-agentos-bootloader-refactor.md` ### Phase 0 acceptance diff --git a/docs/designs/2026-06-28-root-skill-demotion.md b/.evozeus-wrapper/docs/designs/2026-06-28-root-skill-demotion.md similarity index 84% rename from docs/designs/2026-06-28-root-skill-demotion.md rename to .evozeus-wrapper/docs/designs/2026-06-28-root-skill-demotion.md index 240c497..5d2c253 100644 --- a/docs/designs/2026-06-28-root-skill-demotion.md +++ b/.evozeus-wrapper/docs/designs/2026-06-28-root-skill-demotion.md @@ -12,7 +12,7 @@ - package version 事实源是 `.codex-plugin/plugin.json`。 - runtime entry 只来自 `skills/using-engineering-everything/SKILL.md` 和 `skills/engineering-everything/SKILL.md`。 -- EvoZeus/source governance 进入 `WRAPPER.md`、`docs/index.md` 和 `references/self-evolution-harness.md`。 +- EvoZeus/source governance 进入 `.evozeus-wrapper/WRAPPER.md`、`.evozeus-wrapper/docs/index.md` 和 `references/self-evolution-harness.md`。 - legacy whole-repo skill install 不再作为推荐路径。 ## Direction @@ -24,7 +24,7 @@ 1. 删除 root `SKILL.md`。 2. 更新 `scripts/skill_doctor.py`,以 plugin manifest 为 package version source。 3. 更新 `scripts/self_evolve.py`,检查 plugin/library contract,不再要求 root Skill。 -4. 更新 `scripts/evozeus_wrapper_preflight.py`,检查 plugin/library wrapper contract,不再要求 root status prelude。 +4. 更新 `.evozeus-wrapper/scripts/evozeus_wrapper_preflight.py`,检查 plugin/library wrapper contract,不再要求 root status prelude。 5. 更新 README、dashboard、WRAPPER 和 changelog,把 root Skill 从当前结构中移除。 6. 跑完整本地 gate。 @@ -32,7 +32,7 @@ - `python3 scripts/skill_doctor.py --json` - `python3 scripts/self_evolve.py check --json` -- `python3 scripts/evozeus_wrapper_preflight.py structure` +- `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py structure` - `python3 scripts/eval_scenarios.py validate --json` - `python3 scripts/sync_references.py --check --json` - `python3 scripts/lesson.py validate` diff --git a/.evozeus-wrapper/docs/designs/2026-07-25-coevolve-skillware-feasibility.md b/.evozeus-wrapper/docs/designs/2026-07-25-coevolve-skillware-feasibility.md new file mode 100644 index 0000000..aacc516 --- /dev/null +++ b/.evozeus-wrapper/docs/designs/2026-07-25-coevolve-skillware-feasibility.md @@ -0,0 +1,48 @@ +# Engineering Everything as a CoEvolve Skillware Feasibility Target + +## Related issue + +- https://github.com/HaodiFan/engineering-everything/issues/17 + +## Optimization goal + +Attach the current EvoZeus-CoEvolve harness to the existing plugin-first Skillware without changing its engineering routes, reference contracts, scenario behavior, or direct sub-Skill invocation model. + +## Direction + +Use `skills/using-engineering-everything/SKILL.md` as the recommended prompt-enforced preflight surface. Consolidate wrapper-owned lifecycle assets under `.evozeus-wrapper/`, preserve target-owned plugin entrypoints, and make runtime installations point to direct canonical sub-Skills. + +## Implementation plan + +1. Record the bootloader as the instruction surface in the legacy manifest. +2. Migrate scattered wrapper-owned files from v0.3.0 to the consolidated v2 layout. +3. Preserve the strict structure gate; restore missing feedback/audit policies from public CoEvolve templates. +4. Use CoEvolve v0.11.2 to accept manifest-managed direct `skills/` pointers. +5. Align `scripts/install.py --target both` with the documented canonical-symlink contract. +6. Keep all target routing rules and references unchanged; update only release metadata and the bootloader-owned governance sections. + +## Verification plan + +- Run all native Engineering Everything gates from `docs/testing.md`. +- Run CoEvolve structure, doctor, upgrade, version, and release checks. +- Compare the changed files against baseline commit `abcd3bb26bb2c05236ac041d6cebf3af86a81357`. +- Verify an existing installation is idempotent. +- Install into a fresh synthetic HOME and require 24 symlinks across Codex and Agents with zero copied Skill directories. +- Verify release `v0.13.0`, then check out `v0.12.0` in a separate recovery workspace and rerun its documented native gates. + +## Release plan + +- Merge through a reviewed pull request linked to Issue #17. +- Publish `v0.13.0` using `release-notes-v0.13.0.md`. +- Keep EvoZeus-CoEvolve and Skillware release versions as independent axes. +- Record the final commits, release URLs, checks, and recovery result in the public CoEvolve paper artifact. + +## Rollback plan + +- Before merge: revert the migration commit on the feature branch. +- After release: reinstall `v0.12.0` from a separate clean checkout and verify its documented package gates. +- Keep the canonical GitHub repository and current working checkout intact during the recovery rehearsal. + +## Evidence boundary + +The public artifact may contain repository paths, commits, release URLs, aggregate test results, file-level diffs, and synthetic HOME installation evidence. It excludes private sessions, secrets, customer data, and unrelated local files. diff --git a/docs/designs/README.md b/.evozeus-wrapper/docs/designs/README.md similarity index 100% rename from docs/designs/README.md rename to .evozeus-wrapper/docs/designs/README.md diff --git a/docs/index.md b/.evozeus-wrapper/docs/index.md similarity index 63% rename from docs/index.md rename to .evozeus-wrapper/docs/index.md index 30d508e..efb08f1 100644 --- a/docs/index.md +++ b/.evozeus-wrapper/docs/index.md @@ -17,10 +17,10 @@ title: "engineering-everything 自进化驾驶舱" | Visibility | `public` | | 当前 Skill 版本 | `v0.12.0` | | Wrapper harness 版本 | `v0.3.0` | -| Wrapper manifest | `.evozeus/wrapper.json` | -| Wrapper migrations | [`docs/wrapper-migrations/`](wrapper-migrations/) | -| Changelog | [`CHANGELOG.md`](https://github.com/HaodiFan/engineering-everything/blob/main/CHANGELOG.md) | -| Design docs | [`docs/designs/`](designs/) | +| Wrapper manifest | `.evozeus-wrapper/wrapper.json` | +| Wrapper migrations | [`.evozeus-wrapper/docs/migrations/`](wrapper-migrations/) | +| Changelog | [`.evozeus-wrapper/CHANGELOG.md`](https://github.com/HaodiFan/engineering-everything/blob/main/.evozeus-wrapper/CHANGELOG.md) | +| Design docs | [`.evozeus-wrapper/docs/designs/`](designs/) | ## 反馈入口 @@ -34,11 +34,11 @@ title: "engineering-everything 自进化驾驶舱" ## 进化规则 -本仓库采用 plugin-first 结构:运行时入口只来自 `skills/*/SKILL.md`,repo root 不再保留 `SKILL.md`。EvoZeus-wrapper 状态检查由 `.codex-plugin/plugin.json`、`WRAPPER.md`、`docs/index.md` 和 `references/self-evolution-harness.md` 承载。 +本仓库采用 plugin-first 结构:运行时入口只来自 `skills/*/SKILL.md`,repo root 不再保留 `SKILL.md`。EvoZeus-wrapper 状态检查由 `.codex-plugin/plugin.json`、`.evozeus-wrapper/WRAPPER.md`、`.evozeus-wrapper/docs/index.md` 和 `references/self-evolution-harness.md` 承载。 Wrapper-managed Skill 的源头发现顺序固定: -1. 读取 `.evozeus/wrapper.json`。 +1. 读取 `.evozeus-wrapper/wrapper.json`。 2. 检查 `~/.evozeus/.projects/HaodiFan/engineering-everything` 是否指向 canonical repo。 3. 验证 canonical repo 的 git origin / GitHub repo。 4. 检查 `~/.codex/skills/` 和 `~/.agents/skills/`,它们只能是 runtime pointer。 @@ -47,8 +47,8 @@ Wrapper-managed Skill 的源头发现顺序固定: 每次运行 Skill 前,先检查 GitHub latest release 是否有新版本: ```bash -python3 scripts/evozeus_wrapper_preflight.py doctor --repo HaodiFan/engineering-everything -python3 scripts/evozeus_wrapper_preflight.py version --repo HaodiFan/engineering-everything +python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py doctor --repo HaodiFan/engineering-everything +python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py version --repo HaodiFan/engineering-everything ``` 每次 Skill 更新必须先写 design doc,再开 PR。`.codex-plugin/plugin.json` 是 package manifest,`skills/*/SKILL.md` 是运行时入口;`~/.evozeus/.projects/HaodiFan/engineering-everything` 和 runtime 安装路径应指向同一个 canonical repo,不保留 copied install 作为第二事实源,也不要直接修改 `.codex/skills/...` 或 `.agents/skills/...`。 @@ -60,7 +60,7 @@ python3 scripts/evozeus_wrapper.py harness upgrade-check --target /absolute/path python3 scripts/evozeus_wrapper.py harness upgrade --target /absolute/path/to/this-skill --latest-version --dry-run --json ``` -迁移记录写入 `docs/wrapper-migrations/`,并记录 from/to wrapper version、planned files、plugin/library 处理、append-only 处理、验证命令和回滚方案。wrapper harness version 的事实源是 `.evozeus/wrapper.json`;Skill release 仍以 GitHub release 和 `CHANGELOG.md` 为准。 +迁移记录写入 `.evozeus-wrapper/docs/migrations/`,并记录 from/to wrapper version、planned files、plugin/library 处理、append-only 处理、验证命令和回滚方案。wrapper harness version 的事实源是 `.evozeus-wrapper/wrapper.json`;Skill release 仍以 GitHub release 和 `.evozeus-wrapper/CHANGELOG.md` 为准。 Design doc 至少回答: @@ -82,9 +82,9 @@ Design doc 至少回答: ## 上传前检查 ```bash -python3 scripts/evozeus_wrapper_preflight.py doctor --repo HaodiFan/engineering-everything -python3 scripts/evozeus_wrapper_preflight.py structure -python3 scripts/evozeus_wrapper_preflight.py version --repo HaodiFan/engineering-everything -python3 scripts/evozeus_wrapper_preflight.py pr --design-doc docs/designs/.md -python3 scripts/evozeus_wrapper_preflight.py release --tag v0.12.0 --release-notes /tmp/engineering-everything-v0.12.0-release-notes.md +python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py doctor --repo HaodiFan/engineering-everything +python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py structure +python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py version --repo HaodiFan/engineering-everything +python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py pr --design-doc .evozeus-wrapper/docs/designs/.md +python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py release --tag v0.12.0 --release-notes /tmp/engineering-everything-v0.12.0-release-notes.md ``` diff --git a/docs/wrapper-migrations/2026-06-27-unknown-to-v0.2.0.md b/.evozeus-wrapper/docs/migrations/2026-06-27-unknown-to-v0.2.0.md similarity index 60% rename from docs/wrapper-migrations/2026-06-27-unknown-to-v0.2.0.md rename to .evozeus-wrapper/docs/migrations/2026-06-27-unknown-to-v0.2.0.md index fc01f6a..8ebfc36 100644 --- a/docs/wrapper-migrations/2026-06-27-unknown-to-v0.2.0.md +++ b/.evozeus-wrapper/docs/migrations/2026-06-27-unknown-to-v0.2.0.md @@ -6,7 +6,7 @@ Adopt EvoZeus-wrapper `v0.2.0` for `engineering-everything`. ## From wrapper version -- None. This repo previously had no `.evozeus/wrapper.json`. +- None. This repo previously had no `.evozeus-wrapper/wrapper.json`. ## To wrapper version @@ -19,18 +19,18 @@ The Skill already had native self-evolution gates, but it was not wrapper-manage ## Planned files - `SKILL.md` append-only wrapper sections. -- `.evozeus/wrapper.json`. -- `WRAPPER.md`. -- `CHANGELOG.md`. -- `docs/index.md`. -- `docs/design-doc-template.md`. -- `docs/designs/README.md`. -- `docs/designs/2026-06-27-evozeus-wrapper-adoption.md`. -- `docs/wrapper-migrations/README.md`. +- `.evozeus-wrapper/wrapper.json`. +- `.evozeus-wrapper/WRAPPER.md`. +- `.evozeus-wrapper/CHANGELOG.md`. +- `.evozeus-wrapper/docs/index.md`. +- `.evozeus-wrapper/docs/design-doc-template.md`. +- `.evozeus-wrapper/docs/designs/README.md`. +- `.evozeus-wrapper/docs/designs/2026-06-27-evozeus-wrapper-adoption.md`. +- `.evozeus-wrapper/docs/migrations/README.md`. - `.github/ISSUE_TEMPLATE/skill-feedback.yml`. - `.github/pull_request_template.md`. - `.github/workflows/evozeus-wrapper-preflight.yml`. -- `scripts/evozeus_wrapper_preflight.py`. +- `.evozeus-wrapper/scripts/evozeus_wrapper_preflight.py`. ## SKILL.md handling @@ -41,9 +41,9 @@ Append-only. The existing Engineering Everything routing rules remain in place. - `python3 scripts/skill_doctor.py --json` - `python3 scripts/self_evolve.py check --json` - `python3 scripts/self_evolve.py doctor --json` -- `python3 scripts/evozeus_wrapper_preflight.py structure` -- `python3 scripts/evozeus_wrapper_preflight.py doctor --repo HaodiFan/engineering-everything` -- `python3 scripts/evozeus_wrapper_preflight.py version --repo HaodiFan/engineering-everything --current-tag v0.10.0` +- `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py structure` +- `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py doctor --repo HaodiFan/engineering-everything` +- `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py version --repo HaodiFan/engineering-everything --current-tag v0.10.0` ## Rollback diff --git a/docs/wrapper-migrations/2026-06-27-v0.2.0-to-v0.3.0.md b/.evozeus-wrapper/docs/migrations/2026-06-27-v0.2.0-to-v0.3.0.md similarity index 57% rename from docs/wrapper-migrations/2026-06-27-v0.2.0-to-v0.3.0.md rename to .evozeus-wrapper/docs/migrations/2026-06-27-v0.2.0-to-v0.3.0.md index b1990b9..2058981 100644 --- a/docs/wrapper-migrations/2026-06-27-v0.2.0-to-v0.3.0.md +++ b/.evozeus-wrapper/docs/migrations/2026-06-27-v0.2.0-to-v0.3.0.md @@ -19,15 +19,15 @@ EvoZeus-wrapper `v0.3.0` requires wrapped target Skills to run a status check be ## Planned files - `SKILL.md` status prelude and wrapper migration note. -- `.evozeus/wrapper.json`. -- `WRAPPER.md`. +- `.evozeus-wrapper/wrapper.json`. +- `.evozeus-wrapper/WRAPPER.md`. - `README.md`. -- `CHANGELOG.md`. -- `docs/index.md`. -- `docs/designs/2026-06-27-wrapper-status-prelude.md`. -- `docs/wrapper-migrations/README.md`. -- `docs/wrapper-migrations/2026-06-27-v0.2.0-to-v0.3.0.md`. -- `scripts/evozeus_wrapper_preflight.py`. +- `.evozeus-wrapper/CHANGELOG.md`. +- `.evozeus-wrapper/docs/index.md`. +- `.evozeus-wrapper/docs/designs/2026-06-27-wrapper-status-prelude.md`. +- `.evozeus-wrapper/docs/migrations/README.md`. +- `.evozeus-wrapper/docs/migrations/2026-06-27-v0.2.0-to-v0.3.0.md`. +- `.evozeus-wrapper/scripts/evozeus_wrapper_preflight.py`. - Skill metadata files with version `0.11.0`. ## SKILL.md handling @@ -42,10 +42,10 @@ EvoZeus-wrapper `v0.3.0` requires wrapped target Skills to run a status check be - `python3 scripts/skill_doctor.py --json` - `python3 scripts/self_evolve.py check --json` - `python3 scripts/self_evolve.py doctor --json` -- `python3 scripts/evozeus_wrapper_preflight.py structure` -- `python3 scripts/evozeus_wrapper_preflight.py doctor --repo HaodiFan/engineering-everything` -- `python3 scripts/evozeus_wrapper_preflight.py version --repo HaodiFan/engineering-everything --current-tag v0.11.0` -- `python3 scripts/evozeus_wrapper_preflight.py release --tag v0.11.0 --release-notes /tmp/engineering-everything-v0.11.0-release-notes.md --skip-gh` +- `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py structure` +- `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py doctor --repo HaodiFan/engineering-everything` +- `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py version --repo HaodiFan/engineering-everything --current-tag v0.11.0` +- `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py release --tag v0.11.0 --release-notes /tmp/engineering-everything-v0.11.0-release-notes.md --skip-gh` ## Rollback diff --git a/.evozeus-wrapper/docs/migrations/2026-07-25-layout-v1-to-v2.md b/.evozeus-wrapper/docs/migrations/2026-07-25-layout-v1-to-v2.md new file mode 100644 index 0000000..403d2d9 --- /dev/null +++ b/.evozeus-wrapper/docs/migrations/2026-07-25-layout-v1-to-v2.md @@ -0,0 +1,46 @@ +# EvoZeus-CoEvolve 布局迁移:v1 -> v2 + +- 日期:2026-07-25 +- Wrapper 版本:v0.3.0 -> v0.11.1 +- 新事实源:`.evozeus-wrapper/wrapper.json` +- 目标:把 wrapper 产物归拢到 `.evozeus-wrapper/`。 + +## 移动记录 + +- `CHANGELOG.md` -> `.evozeus-wrapper/CHANGELOG.md` +- `WRAPPER.md` -> `.evozeus-wrapper/WRAPPER.md` +- `docs/_config.yml` -> `.evozeus-wrapper/docs/_config.yml` +- `docs/design-doc-template.md` -> `.evozeus-wrapper/docs/design-doc-template.md` +- `docs/designs/2026-06-27-evozeus-wrapper-adoption.md` -> `.evozeus-wrapper/docs/designs/2026-06-27-evozeus-wrapper-adoption.md` +- `docs/designs/2026-06-27-wrapper-status-prelude.md` -> `.evozeus-wrapper/docs/designs/2026-06-27-wrapper-status-prelude.md` +- `docs/designs/2026-06-28-agentos-bootloader-refactor.md` -> `.evozeus-wrapper/docs/designs/2026-06-28-agentos-bootloader-refactor.md` +- `docs/designs/2026-06-28-root-skill-demotion.md` -> `.evozeus-wrapper/docs/designs/2026-06-28-root-skill-demotion.md` +- `docs/designs/README.md` -> `.evozeus-wrapper/docs/designs/README.md` +- `docs/index.md` -> `.evozeus-wrapper/docs/index.md` +- `docs/wrapper-migrations/2026-06-27-unknown-to-v0.2.0.md` -> `.evozeus-wrapper/docs/migrations/2026-06-27-unknown-to-v0.2.0.md` +- `docs/wrapper-migrations/2026-06-27-v0.2.0-to-v0.3.0.md` -> `.evozeus-wrapper/docs/migrations/2026-06-27-v0.2.0-to-v0.3.0.md` +- `docs/wrapper-migrations/README.md` -> `.evozeus-wrapper/docs/migrations/README.md` +- `scripts/evozeus_wrapper_preflight.py` -> `.evozeus-wrapper/scripts/evozeus_wrapper_preflight.py` +- `.evozeus/wrapper.json` -> `.evozeus-wrapper/wrapper.json` + +## 保留的宿主接点 + +- `.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。 + +## Post-validation recovery + +- Initial structure validation stopped because the legacy v0.3.0 layout did not contain feedback and audit policy files. +- The missing files were restored from the public EvoZeus-CoEvolve templates; the structure gate was rerun without weakening its requirements. +- EvoZeus-CoEvolve v0.11.2 added plugin-first canonical sub-Skill pointer validation, preserving the existing `skills/` installation layout. diff --git a/.evozeus-wrapper/docs/migrations/2026-07-25-v0.11.1-to-v0.11.2.md b/.evozeus-wrapper/docs/migrations/2026-07-25-v0.11.1-to-v0.11.2.md new file mode 100644 index 0000000..b0b8ecc --- /dev/null +++ b/.evozeus-wrapper/docs/migrations/2026-07-25-v0.11.1-to-v0.11.2.md @@ -0,0 +1,29 @@ +# EvoZeus-CoEvolve Harness Recovery: v0.11.1 -> v0.11.2 + +- Date: 2026-07-25 +- Target: `HaodiFan/engineering-everything` +- Skillware release retained during migration: `v0.12.0` +- Instruction surface: `skills/using-engineering-everything/SKILL.md` + +## Trigger + +The first consolidated-v2 post-validation detected missing feedback/audit policies. After policy recovery, the doctor gate also showed that the generic source contract treated valid plugin-first sub-Skill symlinks as root-repo mismatches. + +## Recovery + +- Restored `.evozeus-wrapper/policies/feedback-policy.json` and `audit-rule.md` from the public CoEvolve templates. +- Updated the copied preflight checker from public CoEvolve v0.11.2. +- Preserved the existing Codex and Agents symlinks to direct canonical `skills/` entries. +- Replaced the generic root-Skill onboarding command with Engineering Everything's native `scripts/install.py --target both` workflow. + +## Validation + +- `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py structure` +- `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py doctor --repo HaodiFan/engineering-everything` +- `python3 scripts/skill_doctor.py --json` +- `python3 scripts/self_evolve.py check --json` +- `python3 -m unittest discover -s tests` + +## Rollback + +Revert the migration commit and reinstall the previous `v0.12.0` Skillware release from the canonical GitHub repository. diff --git a/docs/wrapper-migrations/README.md b/.evozeus-wrapper/docs/migrations/README.md similarity index 75% rename from docs/wrapper-migrations/README.md rename to .evozeus-wrapper/docs/migrations/README.md index c4f417b..bbc9c05 100644 --- a/docs/wrapper-migrations/README.md +++ b/.evozeus-wrapper/docs/migrations/README.md @@ -4,9 +4,9 @@ ## 迁移原则 -- `.evozeus/wrapper.json` 是 wrapper harness version 的事实源。 +- `.evozeus-wrapper/wrapper.json` 是 wrapper harness version 的事实源。 - 当前仓库采用 plugin-first 结构;运行时入口在 `skills/*/SKILL.md`,repo root 不保留 `SKILL.md`。 -- wrapper 状态检查由 `.codex-plugin/plugin.json`、`WRAPPER.md`、`docs/index.md` 和 preflight 脚本承载,不写入运行时 Skill 正文。 +- wrapper 状态检查由 `.codex-plugin/plugin.json`、`.evozeus-wrapper/WRAPPER.md`、`.evozeus-wrapper/docs/index.md` 和 preflight 脚本承载,不写入运行时 Skill 正文。 - wrapper-managed files 可以按迁移方案复制或合并;如果已有本地修改,必须先做 merge review。 - Skill release version 和 wrapper harness version 是两条版本轴,不能互相覆盖。 diff --git a/.evozeus-wrapper/docs/onboarding.md b/.evozeus-wrapper/docs/onboarding.md new file mode 100644 index 0000000..fcccdbe --- /dev/null +++ b/.evozeus-wrapper/docs/onboarding.md @@ -0,0 +1,43 @@ +# 安装、初始化与子 Skill 接入 + +本页是 wrapper 的通用接入契约。实际命令以 `.evozeus-wrapper/wrapper.json` 的 `onboarding` 字段为准。 + +## 安装 + +1. 在 EvoZeus-CoEvolve 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-CoEvolve 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。 + +## 初始化 + +- 初始化逻辑归目标 Skill 所有,wrapper 不实现 company 或业务专用初始化。 +- 当 `onboarding.initialization.required` 为 `true` 时,必须运行其中的 `command`,再运行 `verification`;两者缺一不可。 +- 当 `required` 为 `false` 时,不额外推断或执行初始化。 + +## 生成的子 Skill + +- 子 Skill 不继承父级 `.codex/hooks.json`,契约固定为 `hooks_inherited: false`。 +- 每个子 Skill 必须单独运行 EvoZeus-CoEvolve lifecycle,生成自己的 manifest、hook 配置和验证材料。 +- 在 Codex 中通过 `/hooks` 审核并信任新建或变更的 hook。 +- 完成条件包括子 Skill structure preflight 和 consumer-project smoke test;只有文件生成成功不算完成。 + +## Dashboard + +- repo-local dashboard 始终保留在 `.evozeus-wrapper/docs/`。 +- workflow validation 不依赖 GitHub Pages。 +- 只有确认仓库和当前 plan 支持 Pages 后,才设置 repository variable `EVOZEUS_PAGES_ENABLED=true`;否则保持 repository-only mode。 diff --git a/.evozeus-wrapper/hooks/evozeus_wrapper_start_check.py b/.evozeus-wrapper/hooks/evozeus_wrapper_start_check.py new file mode 100755 index 0000000..a6b6b8b --- /dev/null +++ b/.evozeus-wrapper/hooks/evozeus_wrapper_start_check.py @@ -0,0 +1,240 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import json +import os +import re +import sys +import time +from datetime import datetime, timezone +from pathlib import Path +from typing import Any +from urllib.error import HTTPError, URLError +from urllib.request import Request, urlopen + + +MANIFEST_PATH = ".evozeus-wrapper/wrapper.json" +LATEST_VERSION_ENV = "EVOZEUS_WRAPPER_LATEST_VERSION" +ENFORCEMENT_ENV = "EVOZEUS_WRAPPER_HOOK_ENFORCEMENT" +LATEST_RELEASE_URL = "https://api.github.com/repos/MetaInFLow/EvoZeus-CoEvolve/releases/latest" +GLOBAL_CACHE_PATH = ".evozeus/cache/evozeus-wrapper-latest.json" +GLOBAL_CACHE_TTL_SECONDS = 3600 + + +def repo_root() -> Path: + return Path(__file__).resolve().parents[2] + + +def read_json(path: Path) -> dict[str, Any] | None: + if not path.is_file(): + return None + try: + data = json.loads(path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError): + return 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: + return None + return tuple(int(part) for part in match.groups()) + + +def fetch_latest_release() -> dict[str, str | None]: + request = Request( + LATEST_RELEASE_URL, + headers={ + "Accept": "application/vnd.github+json", + "User-Agent": "EvoZeus-CoEvolve-hook", + "X-GitHub-Api-Version": "2022-11-28", + }, + ) + try: + with urlopen(request, timeout=3) as response: + payload = json.loads(response.read().decode("utf-8")) + except (HTTPError, URLError, TimeoutError, OSError, json.JSONDecodeError) as exc: + return {"version": None, "url": None, "error": str(exc)} + version = payload.get("tag_name") if isinstance(payload, dict) else None + url = payload.get("html_url") if isinstance(payload, dict) else None + if not isinstance(version, str) or not version: + return {"version": None, "url": None, "error": "GitHub latest release has no tag_name"} + return {"version": version, "url": url if isinstance(url, str) else None, "error": None} + + +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() + explicit = environment.get(LATEST_VERSION_ENV) + if explicit: + return { + "version": explicit, + "source": "environment", + "checked_at": checked_at, + "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", + "checked_at": checked_at, + "url": release.get("url"), + "error": None, + } + return { + "version": None, + "source": "unavailable", + "checked_at": checked_at, + "url": None, + "error": release.get("error") or "GitHub latest release is unavailable", + } + + +def emit(payload: dict[str, Any]) -> int: + print(json.dumps(payload, ensure_ascii=False)) + return 0 + + +def block(reason: str, next_action: str) -> int: + return emit( + { + "continue": False, + "stopReason": reason, + "systemMessage": reason, + "hookSpecificOutput": { + "hookEventName": "SessionStart", + "additionalContext": ( + "EvoZeus-CoEvolve capability=repo_maintenance_hook; " + f"scope=canonical_repository; blocked start; next_action={next_action}" + ), + }, + } + ) + + +def allow(level: str, message: str, next_action: str) -> int: + return emit( + { + "continue": True, + "systemMessage": message, + "hookSpecificOutput": { + "hookEventName": "SessionStart", + "additionalContext": ( + "EvoZeus-CoEvolve capability=repo_maintenance_hook; " + f"scope=canonical_repository; hook_start_check level={level}; " + f"next_action={next_action}; {message}" + ), + }, + } + ) + + +def main() -> int: + try: + json.loads(sys.stdin.read() or "{}") + except json.JSONDecodeError: + pass + + root = repo_root() + manifest = read_json(root / MANIFEST_PATH) + if not manifest: + return block( + "EvoZeus wrapper harness manifest is missing or unreadable.", + "repair_or_adopt_before_wrapper_managed_execution", + ) + + current = str(manifest.get("wrapper_version") or "") + enforcement = os.environ.get(ENFORCEMENT_ENV, "advisory").strip().lower() + if enforcement not in {"advisory", "strict"}: + enforcement = "advisory" + + latest_resolution = resolve_latest_version(current) + latest = latest_resolution["version"] + current_key = version_key(current) + if not current_key: + return block("EvoZeus wrapper harness version is missing or invalid.", "repair_wrapper_manifest") + if not latest: + detail = ( + "EvoZeus wrapper latest version is unavailable; " + f"source={latest_resolution['source']}; checked_at={latest_resolution['checked_at']}; " + f"error={latest_resolution['error']}" + ) + if enforcement == "strict": + return block(detail, "retry_latest_release_lookup") + return allow("warn", detail, "retry_latest_release_lookup") + latest_key = version_key(latest) + if not latest_key: + return block( + f"EvoZeus wrapper latest version is invalid: {latest}", + f"check_{LATEST_VERSION_ENV}_or_latest_release", + ) + + if latest_key == current_key: + return allow("allow", "EvoZeus wrapper harness is current.", "none") + if latest_key < current_key: + return allow("allow", "EvoZeus wrapper harness is ahead of the configured latest version.", "do_not_downgrade") + if latest_key[0] > current_key[0]: + return block( + "EvoZeus wrapper harness has a major upgrade available.", + "confirm_and_plan_harness_upgrade", + ) + if enforcement == "strict": + return block( + "EvoZeus wrapper harness has a non-breaking upgrade available under strict enforcement.", + "create_harness_upgrade_pr", + ) + return allow( + "warn", + "EvoZeus wrapper harness has a non-breaking upgrade available; continue in advisory mode.", + "create_harness_upgrade_pr", + ) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/.evozeus-wrapper/policies/audit-rule.md b/.evozeus-wrapper/policies/audit-rule.md new file mode 100644 index 0000000..1fbfeca --- /dev/null +++ b/.evozeus-wrapper/policies/audit-rule.md @@ -0,0 +1,13 @@ +# Feedback Audit Rule + +Use this rule at the end of any Skill turn when the user corrected the result, expressed dissatisfaction, identified a reusable Skill/wrapper defect, or asked to preserve a repeatable behavior. + +Return a concise JSON decision with: + +- `should_capture`: whether this feedback should become a tracked issue. +- `reason`: the specific reusable failure or improvement opportunity. +- `route`: `target_skill`, `wrapper`, or `both`. +- `severity`: `low`, `medium`, or `high`. +- `evidence_boundary`: what evidence can be recorded without exposing private session text, customer secrets, credentials, or unreleased commercial context. + +Capture when the issue is reusable beyond the current chat. Do not capture one-off user preferences unless they change the target Skill contract. diff --git a/.evozeus-wrapper/policies/feedback-policy.json b/.evozeus-wrapper/policies/feedback-policy.json new file mode 100644 index 0000000..dca7817 --- /dev/null +++ b/.evozeus-wrapper/policies/feedback-policy.json @@ -0,0 +1,30 @@ +{ + "management_mode": "semi_managed", + "strictness": "medium", + "audit_rule": ".evozeus-wrapper/policies/audit-rule.md", + "capture_evidence_regex": [ + "Skill Feedback Issue", + "gh issue create", + "created issue", + "lesson captured", + "经验已上传", + "自进化记录" + ], + "routing": { + "target_skill_issue_when": [ + "domain_rule_missing", + "tool_profile_wrong", + "business_process_wrong", + "deliverable_gate_wrong" + ], + "wrapper_issue_when": [ + "feedback_not_captured", + "mode_config_missing", + "hook_missing", + "cannot_route_issue_owner" + ], + "both_when": [ + "target_skill_rule_missing_and_wrapper_failed_to_capture" + ] + } +} diff --git a/.evozeus-wrapper/scripts/evozeus_wrapper_preflight.py b/.evozeus-wrapper/scripts/evozeus_wrapper_preflight.py new file mode 100755 index 0000000..6d30dd4 --- /dev/null +++ b/.evozeus-wrapper/scripts/evozeus_wrapper_preflight.py @@ -0,0 +1,1087 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import json +import re +import shutil +import subprocess +import sys +from pathlib import Path + + +GLOBAL_EVOZEUS_HOME = ".evozeus" +GLOBAL_EVOZEUS_PROJECTS_DIR = ".projects" +TARGET_EVOINFRA_DIR = ".evozeus-wrapper" +LEGACY_TARGET_EVOINFRA_DIR = ".evozeus_evoinfra" +OLDEST_TARGET_EVOINFRA_DIR = ".evozeus" +TARGET_WRAPPER_MANIFEST = f"{TARGET_EVOINFRA_DIR}/wrapper.json" +LEGACY_TARGET_WRAPPER_MANIFEST = f"{LEGACY_TARGET_EVOINFRA_DIR}/wrapper.json" +OLDEST_TARGET_WRAPPER_MANIFEST = f"{OLDEST_TARGET_EVOINFRA_DIR}/wrapper.json" +TARGET_CHANGELOG = f"{TARGET_EVOINFRA_DIR}/CHANGELOG.md" +TARGET_WRAPPER_GUIDE = f"{TARGET_EVOINFRA_DIR}/WRAPPER.md" +TARGET_FEEDBACK_POLICY = f"{TARGET_EVOINFRA_DIR}/policies/feedback-policy.json" +TARGET_AUDIT_RULE = f"{TARGET_EVOINFRA_DIR}/policies/audit-rule.md" +CODEX_HOOKS_CONFIG = ".codex/hooks.json" +CODEX_START_HOOK_SCRIPT = f"{TARGET_EVOINFRA_DIR}/hooks/evozeus_wrapper_start_check.py" +TARGET_DASHBOARD_INDEX = f"{TARGET_EVOINFRA_DIR}/docs/index.md" +TARGET_DASHBOARD_CONFIG = f"{TARGET_EVOINFRA_DIR}/docs/_config.yml" +TARGET_DESIGN_TEMPLATE = f"{TARGET_EVOINFRA_DIR}/docs/design-doc-template.md" +TARGET_DESIGNS_DIR = f"{TARGET_EVOINFRA_DIR}/docs/designs" +TARGET_DESIGNS_README = f"{TARGET_DESIGNS_DIR}/README.md" +TARGET_MIGRATIONS_DIR = f"{TARGET_EVOINFRA_DIR}/docs/migrations" +TARGET_MIGRATIONS_README = f"{TARGET_MIGRATIONS_DIR}/README.md" +TARGET_ONBOARDING_GUIDE = f"{TARGET_EVOINFRA_DIR}/docs/onboarding.md" +TARGET_PREFLIGHT_SCRIPT = f"{TARGET_EVOINFRA_DIR}/scripts/evozeus_wrapper_preflight.py" + +REQUIRED_FILES = [ + TARGET_CHANGELOG, + TARGET_WRAPPER_GUIDE, + TARGET_WRAPPER_MANIFEST, + TARGET_FEEDBACK_POLICY, + TARGET_AUDIT_RULE, + CODEX_HOOKS_CONFIG, + CODEX_START_HOOK_SCRIPT, + TARGET_DASHBOARD_INDEX, + TARGET_DASHBOARD_CONFIG, + TARGET_DESIGN_TEMPLATE, + TARGET_DESIGNS_README, + TARGET_MIGRATIONS_README, + TARGET_ONBOARDING_GUIDE, + ".github/ISSUE_TEMPLATE/config.yml", + ".github/ISSUE_TEMPLATE/skill-feedback.yml", + ".github/pull_request_template.md", + ".github/workflows/evozeus-wrapper-preflight.yml", + TARGET_PREFLIGHT_SCRIPT, +] +MAINTAINER_REQUIRED_FILES = REQUIRED_FILES + +ISSUE_TERMS = [ + ["不满意", "unsatisfactory", "bad result"], + ["期望", "expected"], + ["复现", "reproduction", "scenario", "场景"], + ["证据边界", "evidence boundary"], + ["影响", "impact"], +] + +DESIGN_TERMS = [ + ["related issue", "修复", "issue"], + ["optimization goal", "优化目标"], + ["direction", "优化方向"], + ["implementation plan", "怎么优化", "实现"], + ["verification plan", "验证"], + ["release plan", "release", "发布"], +] + +SKILL_EVOLUTION_TERMS = [ + ["EvoZeus-CoEvolve 状态检查", "EvoZeus-wrapper 状态检查"], + ["当前记录版本", "当前 Skill 版本", "Skill release"], + ["解决顺序", "处理顺序", "解决方法"], + ["自进化"], + ["EvoZeus-CoEvolve", "EvoZeus-wrapper"], + [TARGET_WRAPPER_MANIFEST], + ["runtime-only install"], + ["source discovery", "源头发现", "source of truth", "事实源"], + ["~/.evozeus/.projects"], + ["version --repo"], + ["Skill Feedback Issue", "feedback issue"], + [TARGET_DESIGNS_DIR, "design doc"], + [TARGET_MIGRATIONS_DIR, "wrapper migration"], + ["append-only", "追加"], + ["wrapper harness version"], + [TARGET_CHANGELOG], + ["release tag", "release notes"], +] + +PLACEHOLDER_PATTERNS = [ + r"\{\{[A-Z_]+\}\}", + r"", + r"", + r"", + r"\bTBD\b", + r"待填写", +] + +VERSION_RE = re.compile(r"^v(\d+)\.(\d+)\.(\d+)$") +GITHUB_REPO_RE = re.compile(r"^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$") +RUNTIME_REFERENCE_RE = re.compile( + r"(?P(?:references|scripts|assets|templates|agents)/[A-Za-z0-9_.@()/+=,~-]+)", +) +PLUGIN_MANIFEST_CANDIDATES = [ + ".codex-plugin/plugin.json", + ".claude-plugin/plugin.json", + ".cursor-plugin/plugin.json", + ".kimi-plugin/plugin.json", + ".opencode/INSTALL.md", + "gemini-extension.json", + "package.json", +] +WRAPPER_RUNTIME_SECTION_HEADINGS = { + "## EvoZeus-CoEvolve 状态检查", + "## EvoZeus-wrapper 状态检查", + "## 自进化方法", + "## EvoZeus-CoEvolve", + "## EvoZeus-wrapper", +} +STATUS_PRELUDE_HEADINGS = ( + "## EvoZeus-CoEvolve 状态检查", + "## EvoZeus-wrapper 状态检查", +) +BLOCKING_STATUS_PHRASES = [ + "Continue to the target Skill's main flow only after all three are OK.", + "全部 OK 后", + "只有检查结果为 OK", + "才继续进入目标 Skill 原本主链路", + "才继续进入下方原 Skill 流程", +] + + +def fail(message: str) -> None: + print(f"FAIL: {message}", file=sys.stderr) + raise SystemExit(1) + + +def ok(message: str) -> None: + print(f"OK: {message}") + + +def warn(message: str) -> None: + print(f"WARN: {message}") + + +def read_text(path: Path) -> str: + if not path.exists(): + fail(f"missing file: {path}") + return path.read_text(encoding="utf-8") + + +def normalize(text: str) -> str: + return re.sub(r"\s+", " ", text.lower()) + + +def has_any(text: str, terms: list[str]) -> bool: + low = normalize(text) + return any(term.lower() in low for term in terms) + + +def has_real_content(text: str) -> bool: + stripped = text.strip() + if len(stripped) < 40: + return False + return not any(re.search(pattern, stripped, re.IGNORECASE) for pattern in PLACEHOLDER_PATTERNS) + + +def version_key(tag: str) -> tuple[int, int, int]: + match = VERSION_RE.fullmatch(tag) + if not match: + fail(f"release tag must use vMAJOR.MINOR.PATCH format: {tag}") + return tuple(int(part) for part in match.groups()) + + +def run_command(args: list[str], cwd: Path | None = None) -> subprocess.CompletedProcess[str]: + return subprocess.run(args, cwd=cwd, text=True, capture_output=True, check=False) + + +def require_command(command: str) -> None: + if shutil.which(command) is None: + fail(f"missing required dependency: {command}") + + +def path_kind(path: Path) -> str: + if path.is_symlink(): + return "symlink" + if path.is_dir(): + return "directory" + if path.is_file(): + return "file" + return "missing" + + +def resolve_path(path: Path) -> str | None: + if not (path.exists() or path.is_symlink()): + return None + try: + return str(path.resolve()) + except OSError: + return None + + +def runtime_pointer_scope(canonical_path: Path, resolved_path: str | None) -> str | None: + if not resolved_path: + return None + canonical = canonical_path.expanduser().resolve() + resolved = Path(resolved_path).expanduser().resolve() + if resolved == canonical: + return "canonical_repo" + if resolved.parent == canonical / "skills" and (resolved / "SKILL.md").is_file(): + return "canonical_subskill" + return None + + +def wrapper_manifest_path(target: Path) -> Path: + return target / TARGET_EVOINFRA_DIR / "wrapper.json" + + +def legacy_wrapper_manifest_path(target: Path) -> Path: + return target / LEGACY_TARGET_EVOINFRA_DIR / "wrapper.json" + + +def oldest_wrapper_manifest_path(target: Path) -> Path: + return target / OLDEST_TARGET_EVOINFRA_DIR / "wrapper.json" + + +def read_json_object(path: Path) -> dict: + try: + data = json.loads(path.read_text(encoding="utf-8")) + except json.JSONDecodeError as exc: + fail(f"invalid wrapper manifest JSON: {path}: {exc}") + if not isinstance(data, dict): + fail(f"wrapper manifest must be a JSON object: {path}") + return data + + +def load_wrapper_manifest(target: Path) -> dict | None: + current_path = wrapper_manifest_path(target) + legacy_paths = [legacy_wrapper_manifest_path(target), oldest_wrapper_manifest_path(target)] + existing_legacy = [path for path in legacy_paths if path.exists()] + if existing_legacy: + fail( + "legacy wrapper layout requires an upgrade migration before preflight: " + + ", ".join(str(path.relative_to(target)) for path in existing_legacy) + ) + if not current_path.exists(): + return None + return read_json_object(current_path) + + +def project_pointer_path(repo: str) -> Path: + owner, name = repo.split("/", 1) + return Path.home() / GLOBAL_EVOZEUS_HOME / GLOBAL_EVOZEUS_PROJECTS_DIR / owner / name + + +def detected_hook_files(target: Path) -> list[str]: + hooks = [ + path + for path in [ + CODEX_HOOKS_CONFIG, + ".codex/config.toml", + CODEX_START_HOOK_SCRIPT, + ] + if (target / path).is_file() + ] + hooks_dir = target / "hooks" + if hooks_dir.is_dir(): + hooks.extend( + str(path.relative_to(target)) + for path in sorted(hooks_dir.iterdir()) + if path.is_file() + ) + return list(dict.fromkeys(hooks)) + + +def detected_plugin_manifests(target: Path) -> list[str]: + return [path for path in PLUGIN_MANIFEST_CANDIDATES if (target / path).is_file()] + + +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(STATUS_PRELUDE_HEADINGS): + has_status_prelude = True + else: + has_status_prelude = bool( + lines + and lines[0].startswith("# ") + and "\n".join(lines[1:]).lstrip().startswith(STATUS_PRELUDE_HEADINGS) + ) + 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": + 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 + + fail(f"unknown integration.mode: {mode}") + + +def check_onboarding_contract(manifest: dict | None) -> None: + def nonempty_string(value: object) -> bool: + return isinstance(value, str) and bool(value.strip()) + + onboarding = (manifest or {}).get("onboarding") + if not isinstance(onboarding, dict): + fail("wrapper manifest must contain an onboarding contract") + + installation = onboarding.get("installation") + if not isinstance(installation, dict): + fail("onboarding.installation must be an object") + if installation.get("mode") != "canonical_repo_symlink": + fail("onboarding.installation.mode must be canonical_repo_symlink") + if not nonempty_string(installation.get("command")) or not nonempty_string( + installation.get("verification") + ): + fail("onboarding.installation must provide command and verification") + + invocation = onboarding.get("invocation") + if not isinstance(invocation, dict): + fail("onboarding.invocation must be an object") + if invocation.get("mode") != "host_skill_discovery" or invocation.get("owner") != "target_skill": + fail("onboarding.invocation must use host_skill_discovery owned by target_skill") + invocation_verification = invocation.get("verification") + if not nonempty_string(invocation.get("instruction")) or not ( + isinstance(invocation_verification, str) + and "consumer-project smoke test" in invocation_verification + ): + fail("onboarding.invocation must provide instructions and a consumer-project smoke test") + + initialization = onboarding.get("initialization") + if not isinstance(initialization, dict): + fail("onboarding.initialization must be an object") + if initialization.get("owner") != "target_skill": + fail("onboarding.initialization.owner must be target_skill") + if not isinstance(initialization.get("required"), bool): + fail("onboarding.initialization.required must be boolean") + if initialization.get("required") and not ( + nonempty_string(initialization.get("command")) + and nonempty_string(initialization.get("verification")) + ): + fail("required onboarding initialization must provide command and verification") + + children = onboarding.get("generated_child_skills") + if not isinstance(children, dict): + fail("onboarding.generated_child_skills must be an object") + if children.get("hooks_inherited") is not False: + fail("generated child Skills must explicitly declare hooks_inherited=false") + if not isinstance(children.get("supported"), bool): + fail("onboarding.generated_child_skills.supported must be boolean") + if children.get("supported"): + if children.get("attachment") != "separate_wrapper_lifecycle": + fail("generated child Skills must use a separate_wrapper_lifecycle attachment") + if children.get("trust_review") != "/hooks": + fail("generated child Skill hooks must require /hooks trust review") + verification = children.get("verification") or "" + required_terms = ["structure preflight", "consumer-project smoke test"] + if not all(term in verification for term in required_terms): + fail("generated child Skill verification must include structure preflight and consumer-project smoke test") + ok("onboarding contract is complete") + + +def check_dashboard_contract(manifest: dict | None) -> None: + dashboard = (manifest or {}).get("dashboard") + if not isinstance(dashboard, dict): + fail("wrapper manifest must contain a dashboard deployment contract") + if dashboard.get("deployment_mode") != "opt_in_github_pages": + fail("dashboard.deployment_mode must be opt_in_github_pages") + if dashboard.get("enablement_variable") != "EVOZEUS_PAGES_ENABLED": + fail("dashboard.enablement_variable must be EVOZEUS_PAGES_ENABLED") + if dashboard.get("fallback") != "repository_only": + fail("dashboard.fallback must be repository_only") + ok("dashboard deployment contract is complete") + + +def skill_name_from_skill_md(path: Path) -> str | None: + if not path.exists(): + return None + for line in path.read_text(encoding="utf-8").splitlines(): + if line.startswith("name:"): + return line.split(":", 1)[1].strip().strip('"').strip("'") + return None + + +def target_canonical_path(target: Path) -> str: + git_root_result = run_command(["git", "-C", str(target), "rev-parse", "--show-toplevel"]) + if git_root_result.returncode == 0 and git_root_result.stdout.strip(): + return str(Path(git_root_result.stdout.strip()).expanduser().resolve()) + return str(target.expanduser().resolve()) + + +def git_origin_repo(path: Path) -> str | None: + remote_result = run_command(["git", "-C", str(path), "remote", "get-url", "origin"]) + if remote_result.returncode != 0: + return None + return repo_from_remote(remote_result.stdout) + + +def repo_from_remote(remote_url: str) -> str | None: + remote_url = remote_url.strip() + match = re.match(r"^https://github\.com/([^/]+/[^/.]+)(?:\.git)?$", remote_url) + if match: + return match.group(1) + match = re.match(r"^git@github\.com:([^/]+/[^/.]+)(?:\.git)?$", remote_url) + if match: + return match.group(1) + return None + + +def gh_current_login() -> str | None: + result = run_command(["gh", "api", "user", "--jq", ".login"]) + return result.stdout.strip() if result.returncode == 0 else None + + +def gh_orgs() -> list[str]: + result = run_command(["gh", "api", "user/orgs", "--jq", ".[].login"]) + if result.returncode != 0: + return [] + return [line.strip() for line in result.stdout.splitlines() if line.strip()] + + +def gh_search_repos(query: str) -> list[str]: + result = run_command(["gh", "search", "repos", query, "--json", "fullName", "--limit", "10"]) + if result.returncode != 0: + return [] + try: + rows = json.loads(result.stdout) + except json.JSONDecodeError: + return [] + return [row["fullName"] for row in rows if row.get("fullName")] + + +def discover_repo_candidates(skill_name: str) -> list[str]: + candidates: list[str] = [] + login = gh_current_login() + if login: + candidates.extend(gh_search_repos(f"{skill_name} user:{login}")) + for org in gh_orgs(): + for repo in gh_search_repos(f"{skill_name} org:{org}"): + if repo not in candidates: + candidates.append(repo) + if candidates: + return candidates + for repo in gh_search_repos(skill_name): + if repo not in candidates: + candidates.append(repo) + return candidates + + +def is_repo_not_found(output: str) -> bool: + markers = [ + "Could not resolve to a Repository", + "Not Found", + "HTTP 404", + "repository not found", + ] + return any(marker.lower() in output.lower() for marker in markers) + + +def check_terms(text: str, term_groups: list[list[str]], label: str) -> None: + missing = [] + for group in term_groups: + if not has_any(text, group): + missing.append("/".join(group)) + if missing: + fail(f"{label} missing required concepts: {', '.join(missing)}") + + +def content_after_frontmatter(text: str) -> str: + if not text.startswith("---\n"): + return text + end = text.find("\n---\n", 4) + if end == -1: + return text + return text[end + len("\n---\n") :] + + +def check_status_prelude(skill_text: str, label: str = "SKILL.md") -> None: + content = content_after_frontmatter(skill_text).lstrip() + if not content.startswith(STATUS_PRELUDE_HEADINGS): + fail(f"{label} must start with the EvoZeus-CoEvolve status check after frontmatter") + check_runtime_safe_status_prelude(skill_text, label) + + +def root_entry_path(target: Path) -> Path: + manifest = load_wrapper_manifest(target) + if manifest and manifest.get("instruction_surface"): + 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" + if skill.exists(): + return skill + agents = target / "AGENTS.md" + if agents.exists(): + return agents + fail( + "target must contain a detectable evolution instruction surface: " + f"root SKILL.md, root AGENTS.md, or {TARGET_WRAPPER_MANIFEST} instruction_surface selected by diagnosis" + ) + + +def check_agents_status_prelude(agents_text: str) -> None: + content = content_after_frontmatter(agents_text).lstrip() + if content.startswith(STATUS_PRELUDE_HEADINGS): + check_runtime_safe_status_prelude(agents_text, "AGENTS.md") + return + lines = content.splitlines() + if lines and lines[0].startswith("# "): + rest = "\n".join(lines[1:]).lstrip() + if rest.startswith(STATUS_PRELUDE_HEADINGS): + check_runtime_safe_status_prelude(agents_text, "AGENTS.md") + return + fail("AGENTS.md must put the EvoZeus-CoEvolve status check before the main runtime instructions") + + +def normalize_relative_path(raw: str) -> str: + cleaned = raw.strip().strip("`'\"").strip() + cleaned = cleaned.rstrip(".,;:)") + return cleaned.replace("\\", "/") + + +def referenced_runtime_files(text: str) -> list[str]: + files: list[str] = [] + for match in RUNTIME_REFERENCE_RE.finditer(text): + rel = normalize_relative_path(match.group("path")) + if rel and rel not in files: + files.append(rel) + return files + + +def strip_wrapper_runtime_sections(text: str) -> str: + kept: list[str] = [] + skipping = False + for line in text.splitlines(): + stripped = line.strip() + if stripped in WRAPPER_RUNTIME_SECTION_HEADINGS: + skipping = True + continue + if skipping and re.match(r"^#{1,6}\s+", stripped) and stripped not in WRAPPER_RUNTIME_SECTION_HEADINGS: + skipping = False + if not skipping: + kept.append(line) + return "\n".join(kept) + + +def add_tree_files(target: Path, dirname: str, files: list[str]) -> None: + root = target / dirname + if not root.is_dir(): + return + for path in sorted(root.rglob("*")): + if path.is_file(): + rel = str(path.relative_to(target)) + if rel not in files: + files.append(rel) + + +def discover_runtime_bundle(target: Path) -> dict: + manifest = load_wrapper_manifest(target) + runtime_bundle = manifest.get("runtime_bundle") if manifest else None + if isinstance(runtime_bundle, dict): + instruction_surface = str(runtime_bundle.get("instruction_surface") or "SKILL.md") + required = [ + normalize_relative_path(path) + for path in runtime_bundle.get("required_files", []) + if isinstance(path, str) and path.strip() + ] + if instruction_surface not in required: + required.insert(0, instruction_surface) + optional = [ + normalize_relative_path(path) + for path in runtime_bundle.get("optional_files", []) + if isinstance(path, str) and path.strip() + ] + return { + "instruction_surface": instruction_surface, + "required_files": list(dict.fromkeys(required)), + "optional_files": list(dict.fromkeys(optional)), + "external_tools": runtime_bundle.get("external_tools", []), + "source": f"{TARGET_WRAPPER_MANIFEST} runtime_bundle", + } + + entry = root_entry_path(target) + instruction_surface = str(entry.relative_to(target)) + required_files = [instruction_surface] + text = entry.read_text(encoding="utf-8", errors="ignore") + business_text = strip_wrapper_runtime_sections(text) + for rel in referenced_runtime_files(business_text): + if rel not in required_files: + required_files.append(rel) + for dirname in ["references", "assets", "templates"]: + if f"{dirname}/" in business_text: + add_tree_files(target, dirname, required_files) + if "scripts/" in business_text: + add_tree_files(target, "scripts", required_files) + for metadata in ["agents/openai.yaml"]: + if (target / metadata).is_file() and metadata not in required_files: + required_files.append(metadata) + return { + "instruction_surface": instruction_surface, + "required_files": required_files, + "optional_files": [], + "external_tools": [], + "source": "discovered_from_instruction_surface", + } + + +def check_runtime_safe_status_prelude(text: str, label: str) -> None: + if not any(heading.removeprefix("## ") in text for heading in STATUS_PRELUDE_HEADINGS): + return + if "runtime-only install" not in text: + fail(f"{label} wrapper status prelude must include runtime-only install fallback language") + lowered = text.lower() + for phrase in BLOCKING_STATUS_PHRASES: + if phrase.lower() in lowered: + fail( + f"{label} wrapper status prelude contains blocking runtime language; " + f"remove it and keep the runtime-only install fallback: {phrase}" + ) + + +def check_runtime(args: argparse.Namespace) -> None: + target = Path(args.target).resolve() + bundle = discover_runtime_bundle(target) + missing = [path for path in bundle["required_files"] if not (target / path).is_file()] + if missing: + fail("missing required runtime files:\n" + "\n".join(f"- {path}" for path in missing)) + entry = target / bundle["instruction_surface"] + check_runtime_safe_status_prelude(read_text(entry), bundle["instruction_surface"]) + ok("runtime bundle is complete") + + +def check_maintainer(args: argparse.Namespace) -> None: + target = Path(args.target).resolve() + missing = [path for path in MAINTAINER_REQUIRED_FILES if not (target / path).exists()] + if missing: + fail("missing required maintainer wrapper files:\n" + "\n".join(f"- {path}" for path in missing)) + manifest = load_wrapper_manifest(target) + check_onboarding_contract(manifest) + check_dashboard_contract(manifest) + check_integration_contract(target, manifest) + entry = root_entry_path(target) + entry_text = read_text(entry) + label = str(entry.relative_to(target)) + check_terms(entry_text, SKILL_EVOLUTION_TERMS, f"{label} self-evolution method") + if label == "AGENTS.md": + check_agents_status_prelude(entry_text) + else: + check_status_prelude(entry_text, label) + check_runtime(args) + ok("maintainer bundle contains required wrapper files") + + +def check_doctor(args: argparse.Namespace) -> None: + target = Path(args.target).resolve() + require_command("git") + require_command("gh") + + auth = run_command(["gh", "auth", "status"]) + if auth.returncode != 0: + fail("gh is installed but not authenticated; run gh auth login") + ok("gh authenticated") + + repo = args.repo + if repo and not GITHUB_REPO_RE.match(repo): + fail(f"--repo must use OWNER/REPO format: {repo}") + + manifest = load_wrapper_manifest(target) + if manifest: + check_wrapper_managed_doctor(target, repo, manifest, args.allow_missing_repo) + return + + git_root_result = run_command(["git", "-C", str(target), "rev-parse", "--show-toplevel"]) + if git_root_result.returncode == 0: + git_root = Path(git_root_result.stdout.strip()) + remote_result = run_command(["git", "-C", str(git_root), "remote", "get-url", "origin"]) + if remote_result.returncode == 0: + remote_repo = repo_from_remote(remote_result.stdout) + if remote_repo: + repo = repo or remote_repo + ok(f"origin GitHub repo detected: {remote_repo}") + else: + fail(f"origin remote is not a GitHub repo: {remote_result.stdout.strip()}") + elif not repo: + fail("target is a git repo but origin remote is missing; pass --repo OWNER/REPO") + elif not repo: + candidates = discover_repo_candidates(target.name) + if candidates: + repo = candidates[0] + ok(f"target is not a git repo; discovered candidate repo: {repo}") + else: + fail("target is not a git repo and no --repo was provided") + + if repo: + view = run_command(["gh", "repo", "view", repo, "--json", "nameWithOwner,url,visibility"]) + if view.returncode != 0: + detail = (view.stderr or view.stdout or "").strip() + if args.allow_missing_repo and is_repo_not_found(detail): + ok(f"GitHub repo is available to create: {repo}") + return + fail(f"cannot access GitHub repo {repo}: {detail}") + ok(f"GitHub repo accessible: {repo}") + + +def check_wrapper_managed_doctor( + target: Path, + requested_repo: str | None, + manifest: dict, + allow_missing_repo: bool, +) -> None: + manifest_repo = manifest.get("canonical_repo") + if not manifest_repo or not GITHUB_REPO_RE.match(manifest_repo): + fail(f"{TARGET_WRAPPER_MANIFEST} must contain canonical_repo in OWNER/REPO format") + if requested_repo and requested_repo != manifest_repo: + fail(f"--repo {requested_repo} does not match wrapper canonical_repo {manifest_repo}") + ok(f"wrapper manifest detected: canonical_repo={manifest_repo}") + + canonical_path = target_canonical_path(target) + pointer = project_pointer_path(manifest_repo) + if not pointer.exists() and not pointer.is_symlink(): + fail(f"project pointer is missing: {pointer}") + if not pointer.is_symlink(): + fail(f"project pointer must be a symlink to the canonical repo: {pointer}") + pointer_resolved = resolve_path(pointer) + if pointer_resolved != canonical_path: + fail(f"project pointer mismatch: {pointer} -> {pointer_resolved}; expected {canonical_path}") + ok(f"project pointer resolves to canonical repo: {pointer} -> {pointer_resolved}") + + origin_repo = git_origin_repo(Path(canonical_path)) + if origin_repo: + if origin_repo != manifest_repo: + fail(f"canonical repo origin {origin_repo} does not match wrapper canonical_repo {manifest_repo}") + ok(f"canonical repo origin matches wrapper manifest: {origin_repo}") + elif allow_missing_repo: + warn("canonical repo has no GitHub origin yet; allowed only during pre-publish bootstrap") + else: + fail("canonical repo has no GitHub origin; publish or pass --allow-missing-repo only during bootstrap") + + view = run_command(["gh", "repo", "view", manifest_repo, "--json", "nameWithOwner,url,visibility"]) + if view.returncode != 0: + detail = (view.stderr or view.stdout or "").strip() + if allow_missing_repo and is_repo_not_found(detail): + ok(f"GitHub repo is available to create: {manifest_repo}") + else: + fail(f"cannot access GitHub repo {manifest_repo}: {detail}") + else: + ok(f"GitHub repo accessible: {manifest_repo}") + + skill_name = skill_name_from_skill_md(target / "SKILL.md") or target.name + manifest_install_paths = [ + Path(item).expanduser() + for item in manifest.get("install_links", []) + if isinstance(item, str) and item.strip() + ] + install_paths = manifest_install_paths or [ + Path.home() / ".codex" / "skills" / skill_name, + Path.home() / ".agents" / "skills" / skill_name, + ] + install_paths = list(dict.fromkeys(install_paths)) + found_install = False + for install_path in install_paths: + if not install_path.exists() and not install_path.is_symlink(): + continue + found_install = True + kind = path_kind(install_path) + resolved = resolve_path(install_path) + pointer_scope = runtime_pointer_scope(Path(canonical_path), resolved) + if kind == "symlink" and pointer_scope == "canonical_repo": + ok(f"runtime install points to canonical repo: {install_path} -> {resolved}") + elif kind == "symlink" and pointer_scope == "canonical_subskill": + ok(f"runtime install points to canonical sub-Skill: {install_path} -> {resolved}") + elif kind == "symlink": + fail( + f"runtime install symlink mismatch: {install_path} -> {resolved}; " + f"expected {canonical_path} or a direct canonical skills/ entry" + ) + elif kind == "directory": + warn(f"runtime install is a real directory copy, not a source of truth: {install_path}") + else: + warn(f"runtime install is not a usable pointer: {install_path} ({kind})") + if not found_install: + ok("no runtime install pointers found; canonical repo remains the only discovered source") + + +def check_structure(args: argparse.Namespace) -> None: + check_maintainer(args) + + +def check_issue(args: argparse.Namespace) -> None: + body = read_text(Path(args.file)) + check_terms(body, ISSUE_TERMS, "issue") + if not has_real_content(body): + fail("issue body looks empty or placeholder-only") + ok("issue body satisfies feedback template concepts") + + +def find_design_doc(target: Path) -> Path: + docs_dir = target / TARGET_DESIGNS_DIR + candidates = [ + path + for path in docs_dir.glob("*.md") + if path.name.lower() != "readme.md" and "template" not in path.name.lower() + ] + if not candidates: + fail(f"no design doc found under {TARGET_DESIGNS_DIR}/*.md") + return sorted(candidates)[-1] + + +def changelog_has_unreleased_entry(changelog: str) -> bool: + match = re.search(r"^## \[?Unreleased\]?.*?(?=^## |\Z)", changelog, re.IGNORECASE | re.MULTILINE | re.DOTALL) + if not match: + return False + section = match.group(0) + lines = [ + line.strip() + for line in section.splitlines() + if line.strip().startswith("-") and "none yet" not in line.lower() + ] + return bool(lines) + + +def check_design_doc(path: Path) -> None: + text = read_text(path) + check_terms(text, DESIGN_TERMS, f"design doc {path}") + if not has_real_content(text): + fail(f"design doc looks placeholder-only: {path}") + ok(f"design doc has required concepts: {path}") + + +def check_pr(args: argparse.Namespace) -> None: + target = Path(args.target).resolve() + design_doc = Path(args.design_doc).resolve() if args.design_doc else find_design_doc(target) + check_design_doc(design_doc) + + changelog = read_text(target / TARGET_CHANGELOG) + if not changelog_has_unreleased_entry(changelog): + fail(f"{TARGET_CHANGELOG} must contain a non-empty Unreleased entry for the PR") + ok(f"{TARGET_CHANGELOG} has an Unreleased entry") + + if args.pr_body: + body = read_text(Path(args.pr_body)) + if "design doc" not in normalize(body) and "设计" not in body: + fail("PR body should reference the design doc") + if TARGET_CHANGELOG not in body: + fail(f"PR body should confirm {TARGET_CHANGELOG} was updated") + ok("PR body references design doc and changelog") + + +def changelog_has_tag(changelog: str, tag: str) -> bool: + escaped = re.escape(tag) + return bool(re.search(rf"^##\s+\[?{escaped}\]?\b", changelog, re.MULTILINE)) + + +def latest_changelog_tag(changelog: str) -> str | None: + for match in re.finditer(r"^##\s+\[?(v\d+\.\d+\.\d+)\]?\b", changelog, re.MULTILINE): + return match.group(1) + return None + + +def release_body_from_gh(tag: str, repo: str | None) -> str | None: + cmd = ["gh", "release", "view", tag, "--json", "body", "-q", ".body"] + if repo: + cmd.extend(["--repo", repo]) + try: + result = subprocess.run(cmd, check=True, text=True, capture_output=True) + except (FileNotFoundError, subprocess.CalledProcessError): + return None + return result.stdout + + +def latest_release_from_gh(repo: str) -> dict[str, str]: + cmd = ["gh", "release", "view", "--repo", repo, "--json", "tagName,url,publishedAt"] + try: + result = subprocess.run(cmd, check=True, text=True, capture_output=True) + except FileNotFoundError: + fail("gh CLI is required to check the latest GitHub release") + except subprocess.CalledProcessError as exc: + detail = (exc.stderr or exc.stdout or "").strip() + fail(f"could not read latest GitHub release for {repo}: {detail}") + + try: + data = json.loads(result.stdout) + except json.JSONDecodeError: + fail(f"could not parse gh release output for {repo}") + if not data.get("tagName"): + fail(f"latest GitHub release for {repo} has no tagName") + return data + + +def check_release(args: argparse.Namespace) -> None: + target = Path(args.target).resolve() + version_key(args.tag) + changelog = read_text(target / TARGET_CHANGELOG) + if not changelog_has_tag(changelog, args.tag): + fail(f"{TARGET_CHANGELOG} must contain a release entry for {args.tag}") + ok(f"{TARGET_CHANGELOG} contains {args.tag}") + + body = "" + if args.release_notes: + body = read_text(Path(args.release_notes)) + elif not args.skip_gh: + body = release_body_from_gh(args.tag, args.repo) or "" + + if not has_real_content(body): + fail("release description is missing, too short, or placeholder-only") + ok("release description is present") + + +def check_version(args: argparse.Namespace) -> None: + target = Path(args.target).resolve() + changelog = read_text(target / TARGET_CHANGELOG) + current_tag = args.current_tag or latest_changelog_tag(changelog) + if not current_tag: + fail(f"could not infer current version from {TARGET_CHANGELOG}; pass --current-tag vMAJOR.MINOR.PATCH") + current_key = version_key(current_tag) + + latest = latest_release_from_gh(args.repo) + latest_tag = latest["tagName"] + latest_key = version_key(latest_tag) + if latest_key > current_key: + fail(f"newer Skill release available: {latest_tag} > local {current_tag}. Update before running.") + if latest_key < current_key: + if args.no_release_needed: + ok( + f"local changelog version {current_tag} is ahead of latest GitHub release {latest_tag}; " + "--no-release-needed explicitly bypassed release creation" + ) + return + fail( + f"local changelog version {current_tag} is ahead of latest GitHub release {latest_tag}. " + "Create the GitHub release or rerun with --no-release-needed only for changes that do not affect the installable artifact." + ) + return + ok(f"local Skill version matches latest GitHub release: {current_tag}") + + +def main() -> int: + parser = argparse.ArgumentParser(description="Preflight checks for an EvoZeus-CoEvolve Skill repo.") + sub = parser.add_subparsers(dest="command", required=True) + + doctor = sub.add_parser("doctor", help="Check local git/gh dependencies and source repo access.") + doctor.add_argument("--target", default=".", help="Target wrapped Skill repo path.") + doctor.add_argument("--repo", help="GitHub repo in OWNER/REPO format. Defaults to origin remote or discovered candidate.") + doctor.add_argument("--allow-missing-repo", action="store_true", help="Allow --repo to be absent on GitHub when bootstrapping a new repo.") + + runtime = sub.add_parser("runtime", help="Check runtime-copy runnable Skill files.") + runtime.add_argument("--target", default=".", help="Target Skill runtime or wrapped repo path.") + + maintainer = sub.add_parser("maintainer", help="Check maintainer wrapper governance files.") + maintainer.add_argument("--target", default=".", help="Target wrapped Skill repo path.") + + structure = sub.add_parser("structure", help="Check required wrapper files.") + structure.add_argument("--target", default=".", help="Target wrapped Skill repo path.") + + issue = sub.add_parser("issue", help="Check a Skill feedback issue body.") + issue.add_argument("--target", default=".", help="Target wrapped Skill repo path.") + issue.add_argument("--file", required=True, help="Markdown file containing the issue body.") + + pr = sub.add_parser("pr", help="Check Skill evolution PR readiness.") + pr.add_argument("--target", default=".", help="Target wrapped Skill repo path.") + pr.add_argument("--design-doc", help="Path to the design doc for this PR.") + pr.add_argument("--pr-body", help="Optional PR body markdown file.") + + release = sub.add_parser("release", help="Check release readiness.") + release.add_argument("--target", default=".", help="Target wrapped Skill repo path.") + release.add_argument("--tag", required=True, help="Release tag, such as v0.1.0.") + release.add_argument("--release-notes", help="Markdown file containing release notes.") + release.add_argument("--repo", help="GitHub repo in OWNER/REPO format for gh release lookup.") + release.add_argument("--skip-gh", action="store_true", help="Do not call gh release view when release notes are omitted.") + + version = sub.add_parser("version", help="Check whether GitHub has a newer Skill release.") + version.add_argument("--target", default=".", help="Target wrapped Skill repo path.") + version.add_argument("--repo", required=True, help="GitHub repo in OWNER/REPO format.") + version.add_argument( + "--current-tag", + help=f"Current local Skill version. Defaults to latest release tag in {TARGET_CHANGELOG}.", + ) + version.add_argument( + "--no-release-needed", + action="store_true", + help="Explicitly allow local changelog to be ahead when the change does not affect the installable artifact.", + ) + + args = parser.parse_args() + if args.command == "doctor": + check_doctor(args) + elif args.command == "runtime": + check_runtime(args) + elif args.command == "maintainer": + check_maintainer(args) + elif args.command == "structure": + check_structure(args) + elif args.command == "issue": + check_issue(args) + elif args.command == "pr": + check_pr(args) + elif args.command == "release": + check_release(args) + elif args.command == "version": + check_version(args) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/.evozeus-wrapper/wrapper.json b/.evozeus-wrapper/wrapper.json new file mode 100644 index 0000000..35f49a9 --- /dev/null +++ b/.evozeus-wrapper/wrapper.json @@ -0,0 +1,149 @@ +{ + "wrapper_repo": "MetaInFLow/EvoZeus-CoEvolve", + "wrapper_version": "v0.11.2", + "applied_at": "2026-07-25", + "canonical_repo": "HaodiFan/engineering-everything", + "instruction_surface": "skills/using-engineering-everything/SKILL.md", + "managed_files": [ + ".evozeus-wrapper/CHANGELOG.md", + ".evozeus-wrapper/WRAPPER.md", + ".evozeus-wrapper/policies/feedback-policy.json", + ".evozeus-wrapper/policies/audit-rule.md", + ".codex/hooks.json", + ".evozeus-wrapper/hooks/evozeus_wrapper_start_check.py", + ".evozeus-wrapper/docs/index.md", + ".evozeus-wrapper/docs/_config.yml", + ".evozeus-wrapper/docs/design-doc-template.md", + ".evozeus-wrapper/docs/designs/README.md", + ".evozeus-wrapper/docs/migrations/README.md", + ".evozeus-wrapper/docs/onboarding.md", + ".github/ISSUE_TEMPLATE/config.yml", + ".github/ISSUE_TEMPLATE/skill-feedback.yml", + ".github/pull_request_template.md", + ".github/workflows/evozeus-wrapper-preflight.yml", + ".evozeus-wrapper/scripts/evozeus_wrapper_preflight.py" + ], + "install_links": [ + "/Users/anthonyf/.codex/skills/using-engineering-everything", + "/Users/anthonyf/.codex/skills/engineering-everything", + "/Users/anthonyf/.agents/skills/using-engineering-everything", + "/Users/anthonyf/.agents/skills/engineering-everything" + ], + "layout_version": 2, + "target_wrapper_dir": ".evozeus-wrapper", + "target_infra_dir": ".evozeus-wrapper", + "migrated_from_layout": "scattered-v1", + "legacy_layout_dirs": [ + ".evozeus_evoinfra", + ".evozeus" + ], + "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", + "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": "bootstrap_skill", + "native_skill_invocation_hook_installed": false, + "native_host_hook_installed": false, + "codex_project_hook": true, + "plugin_lifecycle_hook": true, + "capabilities": { + "repo_maintenance_hook": { + "installed": true, + "native_enforced": true, + "event": "SessionStart", + "scope": "canonical_repository", + "covers_skill_invocation": false + }, + "plugin_lifecycle_hook": { + "installed": true, + "native_enforced": true, + "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": "hooked_skill_bundle", + "root_entry": null, + "hook_files": [ + ".codex/hooks.json", + ".evozeus-wrapper/hooks/evozeus_wrapper_start_check.py" + ], + "plugin_manifests": [ + ".codex-plugin/plugin.json" + ], + "skill_count": 12, + "description": "Plugin skills are present and may have lifecycle hooks, but no native Skill-invocation event is available.", + "instruction_surface": "skills/using-engineering-everything/SKILL.md" + }, + "onboarding": { + "installation": { + "mode": "canonical_repo_symlink", + "command": "python3 scripts/install.py --target both", + "verification": "python3 scripts/install.py list --target both && python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py doctor --repo HaodiFan/engineering-everything" + }, + "invocation": { + "mode": "host_skill_discovery", + "owner": "target_skill", + "instruction": "Start a new host session in a consumer project and invoke $using-engineering-everything before entering the kernel router.", + "verification": "Confirm the host selects the canonical skills/using-engineering-everything/SKILL.md and pass a consumer-project smoke test." + }, + "initialization": { + "required": false, + "owner": "target_skill", + "command": null, + "verification": null + }, + "generated_child_skills": { + "supported": false, + "hooks_inherited": false, + "attachment": "not_applicable", + "trust_review": "not_applicable", + "verification": "not_applicable" + } + }, + "dashboard": { + "deployment_mode": "opt_in_github_pages", + "enablement_variable": "EVOZEUS_PAGES_ENABLED", + "enabled_value": "true", + "fallback": "repository_only", + "capability_check": "confirm repository Pages support before enabling deployment" + } +} diff --git a/.evozeus/wrapper.json b/.evozeus/wrapper.json deleted file mode 100644 index bd48811..0000000 --- a/.evozeus/wrapper.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "wrapper_repo": "MetaInFLow/EvoZeus-wrapper", - "wrapper_version": "v0.3.0", - "applied_at": "2026-06-27", - "canonical_repo": "HaodiFan/engineering-everything", - "instruction_surface": "skills/using-engineering-everything/SKILL.md", - "managed_files": [ - "WRAPPER.md", - "docs/index.md", - "docs/_config.yml", - "docs/design-doc-template.md", - "docs/designs/README.md", - "docs/wrapper-migrations/README.md", - ".github/ISSUE_TEMPLATE/config.yml", - ".github/ISSUE_TEMPLATE/skill-feedback.yml", - ".github/pull_request_template.md", - ".github/workflows/evozeus-wrapper-preflight.yml", - "scripts/evozeus_wrapper_preflight.py" - ], - "install_links": [ - "/Users/anthonyf/.codex/skills/using-engineering-everything", - "/Users/anthonyf/.codex/skills/engineering-everything", - "/Users/anthonyf/.agents/skills/using-engineering-everything", - "/Users/anthonyf/.agents/skills/engineering-everything" - ] -} diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 787de9e..053ceae 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,5 @@ blank_issues_enabled: false contact_links: - name: Read the Skill dashboard - url: "https://github.com/HaodiFan/engineering-everything/tree/main/docs" + url: "https://github.com/HaodiFan/engineering-everything/tree/main/.evozeus-wrapper/docs" about: Check the current Skill status before opening feedback. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 0aa0932..7f3b667 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -14,14 +14,14 @@ ## Verification -- [ ] `python3 scripts/evozeus_wrapper_preflight.py doctor --repo ` -- [ ] `python3 scripts/evozeus_wrapper_preflight.py structure` -- [ ] `python3 scripts/evozeus_wrapper_preflight.py pr --design-doc ` +- [ ] `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py doctor --repo ` +- [ ] `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py structure` +- [ ] `python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py pr --design-doc ` - [ ] Manual Skill behavior check completed ## Release Readiness -- [ ] `CHANGELOG.md` updated +- [ ] `.evozeus-wrapper/CHANGELOG.md` updated - [ ] Release tag planned - [ ] Release description drafted diff --git a/.github/workflows/evozeus-wrapper-preflight.yml b/.github/workflows/evozeus-wrapper-preflight.yml index 081ecbe..5e3419d 100644 --- a/.github/workflows/evozeus-wrapper-preflight.yml +++ b/.github/workflows/evozeus-wrapper-preflight.yml @@ -1,6 +1,13 @@ name: EvoZeus Wrapper Preflight on: + push: + branches: [main] + paths: + - ".evozeus-wrapper/**" + - ".codex/hooks.json" + - ".github/workflows/evozeus-wrapper-preflight.yml" + workflow_dispatch: issues: types: [opened, edited] pull_request: @@ -8,7 +15,33 @@ on: release: types: [published, edited] +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "evozeus-wrapper-pages" + cancel-in-progress: false + jobs: + validation: + if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Validate wrapper maintainer structure + run: python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py maintainer + - name: Report Pages deployment mode + env: + PAGES_ENABLED: ${{ vars.EVOZEUS_PAGES_ENABLED }} + run: | + if [ "$PAGES_ENABLED" = "true" ]; then + echo "GitHub Pages deployment is enabled for this repository." + else + echo "::notice::GitHub Pages deployment is disabled; wrapper validation passed in repository-only mode." + fi + issue: if: github.event_name == 'issues' runs-on: ubuntu-latest @@ -24,7 +57,7 @@ jobs: Path("/tmp/skill-feedback-issue.md").write_text(os.environ.get("ISSUE_BODY", ""), encoding="utf-8") PY - name: Validate issue template content - run: python3 scripts/evozeus_wrapper_preflight.py issue --file /tmp/skill-feedback-issue.md + run: python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py issue --file /tmp/skill-feedback-issue.md pull-request: if: github.event_name == 'pull_request' @@ -32,7 +65,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Validate Skill evolution PR - run: python3 scripts/evozeus_wrapper_preflight.py pr + run: python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py pr release: if: github.event_name == 'release' @@ -49,4 +82,25 @@ jobs: Path("/tmp/release-notes.md").write_text(os.environ.get("RELEASE_BODY", ""), encoding="utf-8") PY - name: Validate release notes - run: python3 scripts/evozeus_wrapper_preflight.py release --tag "${{ github.event.release.tag_name }}" --release-notes /tmp/release-notes.md --skip-gh + run: python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py release --tag "${{ github.event.release.tag_name }}" --release-notes /tmp/release-notes.md --skip-gh + + pages: + needs: validation + if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && vars.EVOZEUS_PAGES_ENABLED == 'true' + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - uses: actions/checkout@v4 + - uses: actions/configure-pages@v5 + - uses: actions/jekyll-build-pages@v1 + with: + source: ./.evozeus-wrapper/docs + destination: ./_site + - uses: actions/upload-pages-artifact@v3 + with: + path: ./_site + - name: Deploy dashboard + id: deployment + uses: actions/deploy-pages@v4 diff --git a/README.md b/README.md index 125c8d6..ffe585a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ 用工程思维路由创造型任务:先判断需求、架构、执行、验证、发布和沉淀路径,再让 agent 动手。 -- 当前版本:`0.12.0` +- 当前版本:`0.13.0` - Package ID:`engineering-everything` - License:`UNLICENSED` @@ -203,7 +203,7 @@ python3 -m unittest discover -s tests ## Status -- 当前版本:`0.12.0` +- 当前版本:`0.13.0` - GitHub repo:`HaodiFan/engineering-everything` - Runtime layout:plugin-first, `skills/*/SKILL.md` - Recommended next release after root demotion:`v0.13.0` diff --git a/agents/openai.yaml b/agents/openai.yaml index 17efb72..0fe38dd 100644 --- a/agents/openai.yaml +++ b/agents/openai.yaml @@ -2,7 +2,7 @@ # 其他 agent 平台请按各自 manifest 规范另存(例如 agents/claude.yaml、agents/cursor.yaml 等) # version 字段必须与 ../.codex-plugin/plugin.json 保持一致;版本号在 plugin manifest 升级时同步更新。 skill: engineering-everything -version: 0.12.0 +version: 0.13.0 interface: display_name: "Engineering Everything" short_description: "用工程思维路由软件、组织、企业和 SOP" diff --git a/docs/design/active/2026-06-27-split-skill-library.md b/docs/design/active/2026-06-27-split-skill-library.md index 4da1790..e0d1d65 100644 --- a/docs/design/active/2026-06-27-split-skill-library.md +++ b/docs/design/active/2026-06-27-split-skill-library.md @@ -1,6 +1,6 @@ # Design Doc: Engineering Everything 多 Skill 拆分 v0.1 -> Update 2026-06-28: 本设计中的根 `SKILL.md` 和 `--layout legacy` 兼容策略已由 `docs/designs/2026-06-28-root-skill-demotion.md` 取代。当前结构为 plugin-first,运行时入口只在 `skills/*/SKILL.md`。 +> Update 2026-06-28: 本设计中的根 `SKILL.md` 和 `--layout legacy` 兼容策略已由 `.evozeus-wrapper/docs/designs/2026-06-28-root-skill-demotion.md` 取代。当前结构为 plugin-first,运行时入口只在 `skills/*/SKILL.md`。 ## TLDR diff --git a/docs/testing.md b/docs/testing.md index 0f44b69..768a4a0 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -1,6 +1,6 @@ # 对内-未审核-Engineering Everything 测试与验证说明 -本文档说明当前仓库的本地验证门禁。它不是 release notes;发布前仍以 `CHANGELOG.md` 和对应 release checklist 为准。 +本文档说明当前仓库的本地验证门禁。它不是 release notes;发布前仍以 `.evozeus-wrapper/CHANGELOG.md` 和对应 release checklist 为准。 ## 当前必跑 @@ -28,9 +28,9 @@ python3 -m py_compile scripts/*.py ```bash python3 scripts/self_evolve.py doctor --json -python3 scripts/evozeus_wrapper_preflight.py structure -python3 scripts/evozeus_wrapper_preflight.py doctor --repo HaodiFan/engineering-everything -python3 scripts/evozeus_wrapper_preflight.py version --repo HaodiFan/engineering-everything --current-tag v0.12.0 +python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py structure +python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py doctor --repo HaodiFan/engineering-everything +python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py version --repo HaodiFan/engineering-everything --current-tag v0.13.0 ``` -`v0.12.0` 是本次 bootloader refactor 的 release tag。发布前必须确认 changelog、版本元数据、验证记录和 GitHub release notes 一致。 +`v0.13.0` 是 plugin-first package 与 CoEvolve harness migration 的 release tag。发布前必须确认 changelog、版本元数据、验证记录和 GitHub release notes 一致。 diff --git a/release-notes-v0.13.0.md b/release-notes-v0.13.0.md new file mode 100644 index 0000000..3594d7c --- /dev/null +++ b/release-notes-v0.13.0.md @@ -0,0 +1,42 @@ +# Engineering Everything v0.13.0 + +## Plugin-first Skillware with CoEvolve governance + +This release formalizes Engineering Everything as a plugin-first Skillware library and migrates its EvoZeus-CoEvolve lifecycle harness to the consolidated v2 layout. + +## Skillware structure + +- Runtime entrypoints live under `skills/*/SKILL.md`. +- `$using-engineering-everything` remains the recommended bootloader. +- `$engineering-everything` remains the direct kernel-router entry. +- All focused `engineering-*` Skills remain directly installable. + +## CoEvolve harness + +- Migrated the harness from `v0.3.0` scattered files to EvoZeus-CoEvolve `v0.11.2` under `.evozeus-wrapper/`. +- Added a governed Skill Feedback Issue → design doc → PR → changelog → release path. +- Aligned `scripts/install.py --target both` with its documented behavior so fresh Codex and Agents installs create canonical sub-Skill symlinks. +- Verified Codex and Agents runtime symlinks as direct sub-Skills within the canonical repository. +- Recorded the initial structure-gate failure and policy/pointer recovery in the migration ledger. + +## Compatibility boundary + +- Engineering routes, references, scenarios, and tool behavior remain unchanged by the harness migration. +- The project hook covers canonical repository maintenance. +- The recommended bootloader contains a prompt-enforced preflight. +- No native per-Skill invocation event is claimed. + +## Verification + +- Reference distribution check passed. +- Behavior scenario validation passed. +- Skill doctor and self-evolution check/doctor passed. +- Lesson validation passed. +- 9 unit tests passed. +- Python compilation passed. +- CoEvolve structure, source-contract doctor, and version checks passed. +- Codex and Agents installation checks passed for the canonical plugin Skill entries. + +## License + +The repository remains `UNLICENSED`. diff --git a/scripts/evozeus_wrapper_preflight.py b/scripts/evozeus_wrapper_preflight.py deleted file mode 100755 index c296f19..0000000 --- a/scripts/evozeus_wrapper_preflight.py +++ /dev/null @@ -1,611 +0,0 @@ -#!/usr/bin/env python3 -from __future__ import annotations - -import argparse -import json -import re -import shutil -import subprocess -import sys -from pathlib import Path - - -REQUIRED_FILES = [ - "CHANGELOG.md", - "WRAPPER.md", - ".codex-plugin/plugin.json", - ".evozeus/wrapper.json", - "docs/index.md", - "docs/_config.yml", - "docs/design-doc-template.md", - "docs/designs/README.md", - "docs/wrapper-migrations/README.md", - ".github/ISSUE_TEMPLATE/config.yml", - ".github/ISSUE_TEMPLATE/skill-feedback.yml", - ".github/pull_request_template.md", - ".github/workflows/evozeus-wrapper-preflight.yml", - "scripts/evozeus_wrapper_preflight.py", - "skills/using-engineering-everything/SKILL.md", - "skills/engineering-everything/SKILL.md", -] - -ISSUE_TERMS = [ - ["不满意", "unsatisfactory", "bad result"], - ["期望", "expected"], - ["复现", "reproduction", "scenario", "场景"], - ["证据边界", "evidence boundary"], - ["影响", "impact"], -] - -DESIGN_TERMS = [ - ["related issue", "修复", "issue"], - ["optimization goal", "优化目标"], - ["direction", "优化方向"], - ["implementation plan", "怎么优化", "实现"], - ["verification plan", "验证"], - ["release plan", "release", "发布"], -] - -WRAPPER_EVOLUTION_TERMS = [ - ["当前记录版本", "当前 Skill 版本", "Skill release"], - ["解决顺序", "解决方法"], - ["自进化"], - ["EvoZeus-wrapper"], - [".evozeus/wrapper.json"], - ["source discovery", "源头发现", "source of truth", "事实源"], - ["~/.evozeus/.projects", ".evozeus/.projects"], - ["version --repo"], - ["Skill Feedback Issue", "feedback issue"], - ["docs/designs", "design doc"], - ["docs/wrapper-migrations", "wrapper migration"], - ["append-only", "追加"], - ["wrapper harness version"], - ["CHANGELOG.md"], - ["release tag", "release notes"], -] - -PLACEHOLDER_PATTERNS = [ - r"\{\{[A-Z_]+\}\}", - r"", - r"", - r"", - r"\bTBD\b", - r"待填写", -] - -VERSION_RE = re.compile(r"^v(\d+)\.(\d+)\.(\d+)$") -GITHUB_REPO_RE = re.compile(r"^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$") - - -def fail(message: str) -> None: - print(f"FAIL: {message}", file=sys.stderr) - raise SystemExit(1) - - -def ok(message: str) -> None: - print(f"OK: {message}") - - -def warn(message: str) -> None: - print(f"WARN: {message}") - - -def read_text(path: Path) -> str: - if not path.exists(): - fail(f"missing file: {path}") - return path.read_text(encoding="utf-8") - - -def normalize(text: str) -> str: - return re.sub(r"\s+", " ", text.lower()) - - -def has_any(text: str, terms: list[str]) -> bool: - low = normalize(text) - return any(term.lower() in low for term in terms) - - -def has_real_content(text: str) -> bool: - stripped = text.strip() - if len(stripped) < 40: - return False - return not any(re.search(pattern, stripped, re.IGNORECASE) for pattern in PLACEHOLDER_PATTERNS) - - -def version_key(tag: str) -> tuple[int, int, int]: - match = VERSION_RE.fullmatch(tag) - if not match: - fail(f"release tag must use vMAJOR.MINOR.PATCH format: {tag}") - return tuple(int(part) for part in match.groups()) - - -def run_command(args: list[str], cwd: Path | None = None) -> subprocess.CompletedProcess[str]: - return subprocess.run(args, cwd=cwd, text=True, capture_output=True, check=False) - - -def require_command(command: str) -> None: - if shutil.which(command) is None: - fail(f"missing required dependency: {command}") - - -def path_kind(path: Path) -> str: - if path.is_symlink(): - return "symlink" - if path.is_dir(): - return "directory" - if path.is_file(): - return "file" - return "missing" - - -def resolve_path(path: Path) -> str | None: - if not (path.exists() or path.is_symlink()): - return None - try: - return str(path.resolve()) - except OSError: - return None - - -def wrapper_manifest_path(target: Path) -> Path: - return target / ".evozeus" / "wrapper.json" - - -def load_wrapper_manifest(target: Path) -> dict | None: - path = wrapper_manifest_path(target) - if not path.exists(): - return None - try: - return json.loads(path.read_text(encoding="utf-8")) - except json.JSONDecodeError as exc: - fail(f"invalid wrapper manifest JSON: {path}: {exc}") - - -def project_pointer_path(repo: str) -> Path: - owner, name = repo.split("/", 1) - return Path.home() / ".evozeus" / ".projects" / owner / name - - -def skill_name_from_skill_md(path: Path) -> str | None: - if not path.exists(): - return None - for line in path.read_text(encoding="utf-8").splitlines(): - if line.startswith("name:"): - return line.split(":", 1)[1].strip().strip('"').strip("'") - return None - - -def load_json_file(path: Path) -> dict: - try: - return json.loads(read_text(path)) - except json.JSONDecodeError as exc: - fail(f"invalid JSON: {path}: {exc}") - - -def plugin_skill_names(target: Path) -> list[str]: - manifest = load_json_file(target / ".codex-plugin" / "plugin.json") - if manifest.get("skills") != "./skills/": - fail(".codex-plugin/plugin.json skills must be ./skills/") - names = [path.parent.name for path in sorted((target / "skills").glob("*/SKILL.md"))] - if not names: - fail("plugin has no skills under skills/*/SKILL.md") - return names - - -def target_canonical_path(target: Path) -> str: - git_root_result = run_command(["git", "-C", str(target), "rev-parse", "--show-toplevel"]) - if git_root_result.returncode == 0 and git_root_result.stdout.strip(): - return str(Path(git_root_result.stdout.strip()).expanduser().resolve()) - return str(target.expanduser().resolve()) - - -def git_origin_repo(path: Path) -> str | None: - remote_result = run_command(["git", "-C", str(path), "remote", "get-url", "origin"]) - if remote_result.returncode != 0: - return None - return repo_from_remote(remote_result.stdout) - - -def repo_from_remote(remote_url: str) -> str | None: - remote_url = remote_url.strip() - match = re.match(r"^https://github\.com/([^/]+/[^/.]+)(?:\.git)?$", remote_url) - if match: - return match.group(1) - match = re.match(r"^git@github\.com:([^/]+/[^/.]+)(?:\.git)?$", remote_url) - if match: - return match.group(1) - return None - - -def gh_current_login() -> str | None: - result = run_command(["gh", "api", "user", "--jq", ".login"]) - return result.stdout.strip() if result.returncode == 0 else None - - -def gh_orgs() -> list[str]: - result = run_command(["gh", "api", "user/orgs", "--jq", ".[].login"]) - if result.returncode != 0: - return [] - return [line.strip() for line in result.stdout.splitlines() if line.strip()] - - -def gh_search_repos(query: str) -> list[str]: - result = run_command(["gh", "search", "repos", query, "--json", "fullName", "--limit", "10"]) - if result.returncode != 0: - return [] - try: - rows = json.loads(result.stdout) - except json.JSONDecodeError: - return [] - return [row["fullName"] for row in rows if row.get("fullName")] - - -def discover_repo_candidates(skill_name: str) -> list[str]: - candidates: list[str] = [] - login = gh_current_login() - if login: - candidates.extend(gh_search_repos(f"{skill_name} user:{login}")) - for org in gh_orgs(): - for repo in gh_search_repos(f"{skill_name} org:{org}"): - if repo not in candidates: - candidates.append(repo) - if candidates: - return candidates - for repo in gh_search_repos(skill_name): - if repo not in candidates: - candidates.append(repo) - return candidates - - -def is_repo_not_found(output: str) -> bool: - markers = [ - "Could not resolve to a Repository", - "Not Found", - "HTTP 404", - "repository not found", - ] - return any(marker.lower() in output.lower() for marker in markers) - - -def check_terms(text: str, term_groups: list[list[str]], label: str) -> None: - missing = [] - for group in term_groups: - if not has_any(text, group): - missing.append("/".join(group)) - if missing: - fail(f"{label} missing required concepts: {', '.join(missing)}") - - -def content_after_frontmatter(text: str) -> str: - if not text.startswith("---\n"): - return text - end = text.find("\n---\n", 4) - if end == -1: - return text - return text[end + len("\n---\n") :] - - -def check_doctor(args: argparse.Namespace) -> None: - target = Path(args.target).resolve() - require_command("git") - require_command("gh") - - auth = run_command(["gh", "auth", "status"]) - if auth.returncode != 0: - fail("gh is installed but not authenticated; run gh auth login") - ok("gh authenticated") - - repo = args.repo - if repo and not GITHUB_REPO_RE.match(repo): - fail(f"--repo must use OWNER/REPO format: {repo}") - - manifest = load_wrapper_manifest(target) - if manifest: - check_wrapper_managed_doctor(target, repo, manifest, args.allow_missing_repo) - return - - git_root_result = run_command(["git", "-C", str(target), "rev-parse", "--show-toplevel"]) - if git_root_result.returncode == 0: - git_root = Path(git_root_result.stdout.strip()) - remote_result = run_command(["git", "-C", str(git_root), "remote", "get-url", "origin"]) - if remote_result.returncode == 0: - remote_repo = repo_from_remote(remote_result.stdout) - if remote_repo: - repo = repo or remote_repo - ok(f"origin GitHub repo detected: {remote_repo}") - else: - fail(f"origin remote is not a GitHub repo: {remote_result.stdout.strip()}") - elif not repo: - fail("target is a git repo but origin remote is missing; pass --repo OWNER/REPO") - elif not repo: - candidates = discover_repo_candidates(target.name) - if candidates: - repo = candidates[0] - ok(f"target is not a git repo; discovered candidate repo: {repo}") - else: - fail("target is not a git repo and no --repo was provided") - - if repo: - view = run_command(["gh", "repo", "view", repo, "--json", "nameWithOwner,url,visibility"]) - if view.returncode != 0: - detail = (view.stderr or view.stdout or "").strip() - if args.allow_missing_repo and is_repo_not_found(detail): - ok(f"GitHub repo is available to create: {repo}") - return - fail(f"cannot access GitHub repo {repo}: {detail}") - ok(f"GitHub repo accessible: {repo}") - - -def check_wrapper_managed_doctor( - target: Path, - requested_repo: str | None, - manifest: dict, - allow_missing_repo: bool, -) -> None: - manifest_repo = manifest.get("canonical_repo") - if not manifest_repo or not GITHUB_REPO_RE.match(manifest_repo): - fail(".evozeus/wrapper.json must contain canonical_repo in OWNER/REPO format") - if requested_repo and requested_repo != manifest_repo: - fail(f"--repo {requested_repo} does not match wrapper canonical_repo {manifest_repo}") - ok(f"wrapper manifest detected: canonical_repo={manifest_repo}") - - canonical_path = target_canonical_path(target) - pointer = project_pointer_path(manifest_repo) - if not pointer.exists() and not pointer.is_symlink(): - fail(f"project pointer is missing: {pointer}") - if not pointer.is_symlink(): - fail(f"project pointer must be a symlink to the canonical repo: {pointer}") - pointer_resolved = resolve_path(pointer) - if pointer_resolved != canonical_path: - fail(f"project pointer mismatch: {pointer} -> {pointer_resolved}; expected {canonical_path}") - ok(f"project pointer resolves to canonical repo: {pointer} -> {pointer_resolved}") - - origin_repo = git_origin_repo(Path(canonical_path)) - if origin_repo: - if origin_repo != manifest_repo: - fail(f"canonical repo origin {origin_repo} does not match wrapper canonical_repo {manifest_repo}") - ok(f"canonical repo origin matches wrapper manifest: {origin_repo}") - elif allow_missing_repo: - warn("canonical repo has no GitHub origin yet; allowed only during pre-publish bootstrap") - else: - fail("canonical repo has no GitHub origin; publish or pass --allow-missing-repo only during bootstrap") - - view = run_command(["gh", "repo", "view", manifest_repo, "--json", "nameWithOwner,url,visibility"]) - if view.returncode != 0: - detail = (view.stderr or view.stdout or "").strip() - if allow_missing_repo and is_repo_not_found(detail): - ok(f"GitHub repo is available to create: {manifest_repo}") - else: - fail(f"cannot access GitHub repo {manifest_repo}: {detail}") - else: - ok(f"GitHub repo accessible: {manifest_repo}") - - skill_names = plugin_skill_names(target) - install_paths = [ - Path.home() / host / "skills" / skill_name - for host in [".codex", ".agents"] - for skill_name in skill_names - ] - found_install = False - for install_path in install_paths: - if not install_path.exists() and not install_path.is_symlink(): - continue - found_install = True - kind = path_kind(install_path) - resolved = resolve_path(install_path) - expected_skill_source = str((Path(canonical_path) / "skills" / install_path.name).resolve()) - if kind == "symlink" and resolved == expected_skill_source: - ok(f"runtime install points to canonical skill: {install_path} -> {resolved}") - elif kind == "symlink": - fail(f"runtime install symlink mismatch: {install_path} -> {resolved}; expected {expected_skill_source}") - elif kind == "directory": - warn(f"runtime install is a real directory copy, not a source of truth: {install_path}") - else: - warn(f"runtime install is not a usable pointer: {install_path} ({kind})") - if not found_install: - ok("no runtime install pointers found; canonical repo remains the only discovered source") - - -def check_structure(args: argparse.Namespace) -> None: - target = Path(args.target).resolve() - missing = [path for path in REQUIRED_FILES if not (target / path).exists()] - if missing: - fail("missing required wrapper files:\n" + "\n".join(f"- {path}" for path in missing)) - if (target / "SKILL.md").exists(): - fail("root SKILL.md must not exist in plugin-first structure") - plugin_skill_names(target) - wrapper_text = read_text(target / "WRAPPER.md") + "\n" + read_text(target / "docs/index.md") - check_terms(wrapper_text, WRAPPER_EVOLUTION_TERMS, "wrapper governance docs") - ok("structure contains required plugin wrapper files") - - -def check_issue(args: argparse.Namespace) -> None: - body = read_text(Path(args.file)) - check_terms(body, ISSUE_TERMS, "issue") - if not has_real_content(body): - fail("issue body looks empty or placeholder-only") - ok("issue body satisfies feedback template concepts") - - -def find_design_doc(target: Path) -> Path: - docs_dir = target / "docs" / "designs" - candidates = [ - path - for path in docs_dir.glob("*.md") - if path.name.lower() != "readme.md" and "template" not in path.name.lower() - ] - if not candidates: - fail("no design doc found under docs/designs/*.md") - return sorted(candidates)[-1] - - -def changelog_has_unreleased_entry(changelog: str) -> bool: - match = re.search(r"^## \[?Unreleased\]?.*?(?=^## |\Z)", changelog, re.IGNORECASE | re.MULTILINE | re.DOTALL) - if not match: - return False - section = match.group(0) - lines = [ - line.strip() - for line in section.splitlines() - if line.strip().startswith("-") and "none yet" not in line.lower() - ] - return bool(lines) - - -def check_design_doc(path: Path) -> None: - text = read_text(path) - check_terms(text, DESIGN_TERMS, f"design doc {path}") - if not has_real_content(text): - fail(f"design doc looks placeholder-only: {path}") - ok(f"design doc has required concepts: {path}") - - -def check_pr(args: argparse.Namespace) -> None: - target = Path(args.target).resolve() - design_doc = Path(args.design_doc).resolve() if args.design_doc else find_design_doc(target) - check_design_doc(design_doc) - - changelog = read_text(target / "CHANGELOG.md") - if not changelog_has_unreleased_entry(changelog): - fail("CHANGELOG.md must contain a non-empty Unreleased entry for the PR") - ok("CHANGELOG.md has an Unreleased entry") - - if args.pr_body: - body = read_text(Path(args.pr_body)) - if "design doc" not in normalize(body) and "设计" not in body: - fail("PR body should reference the design doc") - if "CHANGELOG.md" not in body: - fail("PR body should confirm CHANGELOG.md was updated") - ok("PR body references design doc and changelog") - - -def changelog_has_tag(changelog: str, tag: str) -> bool: - escaped = re.escape(tag) - return bool(re.search(rf"^##\s+\[?{escaped}\]?\b", changelog, re.MULTILINE)) - - -def latest_changelog_tag(changelog: str) -> str | None: - for match in re.finditer(r"^##\s+\[?(v\d+\.\d+\.\d+)\]?\b", changelog, re.MULTILINE): - return match.group(1) - return None - - -def release_body_from_gh(tag: str, repo: str | None) -> str | None: - cmd = ["gh", "release", "view", tag, "--json", "body", "-q", ".body"] - if repo: - cmd.extend(["--repo", repo]) - try: - result = subprocess.run(cmd, check=True, text=True, capture_output=True) - except (FileNotFoundError, subprocess.CalledProcessError): - return None - return result.stdout - - -def latest_release_from_gh(repo: str) -> dict[str, str]: - cmd = ["gh", "release", "view", "--repo", repo, "--json", "tagName,url,publishedAt"] - try: - result = subprocess.run(cmd, check=True, text=True, capture_output=True) - except FileNotFoundError: - fail("gh CLI is required to check the latest GitHub release") - except subprocess.CalledProcessError as exc: - detail = (exc.stderr or exc.stdout or "").strip() - fail(f"could not read latest GitHub release for {repo}: {detail}") - - try: - data = json.loads(result.stdout) - except json.JSONDecodeError: - fail(f"could not parse gh release output for {repo}") - if not data.get("tagName"): - fail(f"latest GitHub release for {repo} has no tagName") - return data - - -def check_release(args: argparse.Namespace) -> None: - target = Path(args.target).resolve() - version_key(args.tag) - changelog = read_text(target / "CHANGELOG.md") - if not changelog_has_tag(changelog, args.tag): - fail(f"CHANGELOG.md must contain a release entry for {args.tag}") - ok(f"CHANGELOG.md contains {args.tag}") - - body = "" - if args.release_notes: - body = read_text(Path(args.release_notes)) - elif not args.skip_gh: - body = release_body_from_gh(args.tag, args.repo) or "" - - if not has_real_content(body): - fail("release description is missing, too short, or placeholder-only") - ok("release description is present") - - -def check_version(args: argparse.Namespace) -> None: - target = Path(args.target).resolve() - changelog = read_text(target / "CHANGELOG.md") - current_tag = args.current_tag or latest_changelog_tag(changelog) - if not current_tag: - fail("could not infer current version from CHANGELOG.md; pass --current-tag vMAJOR.MINOR.PATCH") - current_key = version_key(current_tag) - - latest = latest_release_from_gh(args.repo) - latest_tag = latest["tagName"] - latest_key = version_key(latest_tag) - if latest_key > current_key: - fail(f"newer Skill release available: {latest_tag} > local {current_tag}. Update before running.") - if latest_key < current_key: - ok(f"local changelog version {current_tag} is ahead of latest GitHub release {latest_tag}") - return - ok(f"local Skill version matches latest GitHub release: {current_tag}") - - -def main() -> int: - parser = argparse.ArgumentParser(description="Preflight checks for an EvoZeus-wrapper Skill repo.") - sub = parser.add_subparsers(dest="command", required=True) - - doctor = sub.add_parser("doctor", help="Check local git/gh dependencies and source repo access.") - doctor.add_argument("--target", default=".", help="Target wrapped Skill repo path.") - doctor.add_argument("--repo", help="GitHub repo in OWNER/REPO format. Defaults to origin remote or discovered candidate.") - doctor.add_argument("--allow-missing-repo", action="store_true", help="Allow --repo to be absent on GitHub when bootstrapping a new repo.") - - structure = sub.add_parser("structure", help="Check required wrapper files.") - structure.add_argument("--target", default=".", help="Target wrapped Skill repo path.") - - issue = sub.add_parser("issue", help="Check a Skill feedback issue body.") - issue.add_argument("--target", default=".", help="Target wrapped Skill repo path.") - issue.add_argument("--file", required=True, help="Markdown file containing the issue body.") - - pr = sub.add_parser("pr", help="Check Skill evolution PR readiness.") - pr.add_argument("--target", default=".", help="Target wrapped Skill repo path.") - pr.add_argument("--design-doc", help="Path to the design doc for this PR.") - pr.add_argument("--pr-body", help="Optional PR body markdown file.") - - release = sub.add_parser("release", help="Check release readiness.") - release.add_argument("--target", default=".", help="Target wrapped Skill repo path.") - release.add_argument("--tag", required=True, help="Release tag, such as v0.1.0.") - release.add_argument("--release-notes", help="Markdown file containing release notes.") - release.add_argument("--repo", help="GitHub repo in OWNER/REPO format for gh release lookup.") - release.add_argument("--skip-gh", action="store_true", help="Do not call gh release view when release notes are omitted.") - - version = sub.add_parser("version", help="Check whether GitHub has a newer Skill release.") - version.add_argument("--target", default=".", help="Target wrapped Skill repo path.") - version.add_argument("--repo", required=True, help="GitHub repo in OWNER/REPO format.") - version.add_argument("--current-tag", help="Current local Skill version. Defaults to latest release tag in CHANGELOG.md.") - - args = parser.parse_args() - if args.command == "doctor": - check_doctor(args) - elif args.command == "structure": - check_structure(args) - elif args.command == "issue": - check_issue(args) - elif args.command == "pr": - check_pr(args) - elif args.command == "release": - check_release(args) - elif args.command == "version": - check_version(args) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/install.py b/scripts/install.py index f14f05b..c18f450 100755 --- a/scripts/install.py +++ b/scripts/install.py @@ -118,7 +118,7 @@ def link_tree(source: Path, dest: Path, delete: bool, dry_run: bool) -> str: def install_library(source: Path, dest_root: Path, delete: bool, dry_run: bool) -> list[str]: results: list[str] = [] for skill_package in discover_skill_packages(source): - results.append(copy_tree(skill_package, dest_root / skill_package.name, delete, dry_run)) + results.append(link_tree(skill_package, dest_root / skill_package.name, delete, dry_run)) return results @@ -156,7 +156,7 @@ def main() -> int: nargs="?", choices=["install", "update", "relink", "uninstall", "list"], default="install", - help="Lifecycle action. update is an alias for install; relink installs symlink pointers.", + help="Lifecycle action. install, update, and relink maintain canonical symlink pointers.", ) parser.add_argument( "--target", @@ -181,7 +181,7 @@ def main() -> int: action="store_true", help="Do not delete the existing destination before copying.", ) - parser.add_argument("--dry-run", action="store_true", help="Print actions without copying files.") + parser.add_argument("--dry-run", action="store_true", help="Print actions without changing files.") args = parser.parse_args() source = package_dir() diff --git a/skills/engineering-architecture-design/SKILL.md b/skills/engineering-architecture-design/SKILL.md index 25f5dbd..7c2db26 100644 --- a/skills/engineering-architecture-design/SKILL.md +++ b/skills/engineering-architecture-design/SKILL.md @@ -2,7 +2,7 @@ name: engineering-architecture-design description: Use when choosing technical architecture, project shape, stack, data source, boundaries, deployment model, AI/Agent architecture, or scaffold strategy. metadata: - version: 0.12.0 + version: 0.13.0 --- # Engineering Architecture Design / 架构设计 diff --git a/skills/engineering-automation-playbooks/SKILL.md b/skills/engineering-automation-playbooks/SKILL.md index 1d37b1e..d69d6dd 100644 --- a/skills/engineering-automation-playbooks/SKILL.md +++ b/skills/engineering-automation-playbooks/SKILL.md @@ -2,7 +2,7 @@ name: engineering-automation-playbooks description: Use when designing or implementing RPA, OCR, CV, LLM production pipelines, browser automation, data governance, analytics reporting, or POC/spike automation. metadata: - version: 0.12.0 + version: 0.13.0 --- # Engineering Automation Playbooks / 自动化场景 diff --git a/skills/engineering-build-verify/SKILL.md b/skills/engineering-build-verify/SKILL.md index e2e385e..752b43d 100644 --- a/skills/engineering-build-verify/SKILL.md +++ b/skills/engineering-build-verify/SKILL.md @@ -2,7 +2,7 @@ name: engineering-build-verify description: Use when implementing, fixing bugs, migrating code, wiring APIs, running validation commands, or closing a build/test loop with minimal scoped changes. metadata: - version: 0.12.0 + version: 0.13.0 --- # Engineering Build Verify / 构建验证 diff --git a/skills/engineering-everything/SKILL.md b/skills/engineering-everything/SKILL.md index 9d49d5a..8f77b45 100644 --- a/skills/engineering-everything/SKILL.md +++ b/skills/engineering-everything/SKILL.md @@ -2,7 +2,7 @@ name: engineering-everything description: Use when a task needs engineering-route judgment before choosing product definition, project inheritance, architecture, execution, build, refactor, review, release, organization, automation, or skill-evolution work. metadata: - version: 0.12.0 + version: 0.13.0 --- # Engineering Everything / 工程化万物 diff --git a/skills/engineering-execution-planning/SKILL.md b/skills/engineering-execution-planning/SKILL.md index 7131ba4..686c7f9 100644 --- a/skills/engineering-execution-planning/SKILL.md +++ b/skills/engineering-execution-planning/SKILL.md @@ -2,7 +2,7 @@ name: engineering-execution-planning description: Use when splitting a feature, writing an implementation plan, choosing worktree strategy, defining vertical slices, validation gates, or review roles before coding. metadata: - version: 0.12.0 + version: 0.13.0 --- # Engineering Execution Planning / 执行规划 diff --git a/skills/engineering-organization-systems/SKILL.md b/skills/engineering-organization-systems/SKILL.md index 6c20696..8b89012 100644 --- a/skills/engineering-organization-systems/SKILL.md +++ b/skills/engineering-organization-systems/SKILL.md @@ -2,7 +2,7 @@ name: engineering-organization-systems description: Use when engineering an organization, company mechanism, SOP, onboarding, interview process, non-software project, WBS, estimate, workspace, knowledge base, table, or document-led operating system. metadata: - version: 0.12.0 + version: 0.13.0 --- # Engineering Organization Systems / 组织系统工程 diff --git a/skills/engineering-product-definition/SKILL.md b/skills/engineering-product-definition/SKILL.md index a40dc5d..7e1c327 100644 --- a/skills/engineering-product-definition/SKILL.md +++ b/skills/engineering-product-definition/SKILL.md @@ -2,7 +2,7 @@ name: engineering-product-definition description: Use when clarifying a product idea, PRD, requirements, PSPS, user scenario, scope, non-goals, or success criteria before architecture or implementation. metadata: - version: 0.12.0 + version: 0.13.0 --- # Engineering Product Definition / 产品定义 diff --git a/skills/engineering-project-inheritance/SKILL.md b/skills/engineering-project-inheritance/SKILL.md index 4a20198..df0713e 100644 --- a/skills/engineering-project-inheritance/SKILL.md +++ b/skills/engineering-project-inheritance/SKILL.md @@ -2,7 +2,7 @@ name: engineering-project-inheritance description: Use when taking over an existing repository, auditing an old project, deciding whether to refactor, or planning how to continue work from current code and documents. metadata: - version: 0.12.0 + version: 0.13.0 --- # Engineering Project Inheritance / 接手已有项目 diff --git a/skills/engineering-refactoring/SKILL.md b/skills/engineering-refactoring/SKILL.md index 5a8eef5..6a5fed8 100644 --- a/skills/engineering-refactoring/SKILL.md +++ b/skills/engineering-refactoring/SKILL.md @@ -2,7 +2,7 @@ name: engineering-refactoring description: Use when refactoring, cleaning up, modularizing, renaming, reducing technical debt, or preserving behavior while improving structure. metadata: - version: 0.12.0 + version: 0.13.0 --- # Engineering Refactoring / 重构治理 diff --git a/skills/engineering-review-release/SKILL.md b/skills/engineering-review-release/SKILL.md index deb2f02..c0ae24a 100644 --- a/skills/engineering-review-release/SKILL.md +++ b/skills/engineering-review-release/SKILL.md @@ -2,7 +2,7 @@ name: engineering-review-release description: Use when reviewing code, PRs, merge readiness, CI failures, release risk, rollback plans, or deciding whether a change is safe to ship. metadata: - version: 0.12.0 + version: 0.13.0 --- # Engineering Review Release / Review 与发布 diff --git a/skills/engineering-skill-evolution/SKILL.md b/skills/engineering-skill-evolution/SKILL.md index 8a77e5d..0bb61bc 100644 --- a/skills/engineering-skill-evolution/SKILL.md +++ b/skills/engineering-skill-evolution/SKILL.md @@ -2,7 +2,7 @@ name: engineering-skill-evolution description: Use when evolving a skill, capturing lessons, promoting patterns, changing routes, updating references, installing skills, validating skill packages, or publishing skill releases. metadata: - version: 0.12.0 + version: 0.13.0 --- # Engineering Skill Evolution / Skill 自进化 diff --git a/skills/using-engineering-everything/SKILL.md b/skills/using-engineering-everything/SKILL.md index 422eddc..68ba8b9 100644 --- a/skills/using-engineering-everything/SKILL.md +++ b/skills/using-engineering-everything/SKILL.md @@ -2,9 +2,34 @@ name: using-engineering-everything description: Use when starting, resuming, or switching an Engineering Everything session before selecting the kernel router or a direct engineering sub-skill. metadata: - version: 0.12.0 + version: 0.13.0 --- +## EvoZeus-CoEvolve 状态检查 + +本段是 Skill 入口 preflight。Agent 选中本 Skill 后、进入业务主链路前执行;它基本绑定当前 Skill,但依赖 instruction compliance,不是 native Skill invocation 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.13.0` + - 检查命令:`python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py version --repo HaodiFan/engineering-everything` + - 如果 GitHub latest release 更新:先更新 canonical repo,并确认 runtime install 仍指向 canonical repo。 + - 如果本地版本领先 GitHub release:先完成 changelog、验证和 `vMAJOR.MINOR.PATCH` release,再把它当作稳定运行版本。 +2. Wrapper harness 状态 + - 当前 wrapper 版本:`v0.11.2` + - 事实源:`.evozeus-wrapper/wrapper.json` + - 检查命令:在 EvoZeus-CoEvolve repo 运行 `python3 scripts/evozeus_wrapper.py harness upgrade-check --target --json` + - 如果 wrapper 落后:先运行 `harness upgrade --dry-run` 生成迁移方案,再按状态检查前置、其他 wrapper 内容 append-only 的规则迁移。 +3. Source contract 状态 + - 检查命令:`python3 .evozeus-wrapper/scripts/evozeus_wrapper_preflight.py doctor --repo HaodiFan/engineering-everything` + - 如果 `~/.evozeus/.projects`、git origin 或 runtime install 不一致:先修复为同一个 canonical repo,再继续。 + +解决顺序:先修 source contract,再修 wrapper harness,最后处理 Skill release;状态已确认或已记录为 runtime-only fallback 后,再进入主链路。 + + # Using Engineering Everything / 启动器 这是 Engineering Everything 的 bootloader。它只负责决定是否进入工程化路由、恢复会话上下文、加载主路由和约束输出,不承载具体 route 表。 @@ -42,8 +67,33 @@ metadata: 只有用户明确要求 `/learn`、`/lesson`、`/pattern`、`/self-evolve`、升级 Skill,或确认要把纠偏沉淀为 lesson issue 时,才交给 `engineering-skill-evolution`。 +## EvoZeus-CoEvolve 自进化治理 + +进入 `engineering-skill-evolution` 后,行为变更必须遵循共享 Skillware 的可追踪发布闭环: + +1. 先创建或关联 Skill Feedback Issue,记录问题、期望、复现场景和证据边界。 +2. 在 `.evozeus-wrapper/docs/designs/` 建立 design doc,再修改 Skill、route、reference 或 script。 +3. PR 同步更新 `.evozeus-wrapper/CHANGELOG.md`,并运行 Engineering Everything 自身验证门禁。 +4. 合并后创建 `vMAJOR.MINOR.PATCH` release tag 和 release notes,使兼容用户获得同一共享版本。 +5. wrapper harness version 由 `.evozeus-wrapper/wrapper.json` 管理;wrapper migration 只允许 append-only 更新,并记录在 `.evozeus-wrapper/docs/migrations/`。 + +业务方法和工程路由继续由本 repo 维护。EvoZeus-CoEvolve 只增加证据、评估、版本和恢复治理。 + ## References - `references/route-contract.md`:route 字段契约和裁决顺序。 - `references/output-contracts.md`:规划、执行、review、eval 输出契约。 - `references/codex-tools.md`:Codex 工具使用和收尾边界。 + +## EvoZeus-CoEvolve Migration Note: v0.3.0 -> v0.11.1 + +- Wrapper harness: `v0.3.0 -> v0.11.1` +- Layout: `scattered-v1 -> consolidated-v2` +- Host hook registration, status prelude, manifest integration, and managed links were refreshed. +- Target business rules were preserved. + +## EvoZeus-CoEvolve Recovery Note: v0.11.1 -> v0.11.2 + +- The first v2 structure gate detected missing policy files and stopped before release. +- CoEvolve v0.11.2 restored the policies from public templates and added canonical sub-Skill pointer validation for this plugin-first Skillware. +- The recommended bootloader and all Engineering Everything business routes remain unchanged. diff --git a/tests/test_install_safety.py b/tests/test_install_safety.py index 7c39b35..0dca0a0 100644 --- a/tests/test_install_safety.py +++ b/tests/test_install_safety.py @@ -52,6 +52,26 @@ def test_discover_skill_packages_includes_bootloader(self) -> None: self.assertIn("using-engineering-everything", names) self.assertIn("engineering-everything", names) + def test_install_library_creates_canonical_symlinks(self) -> None: + with tempfile.TemporaryDirectory() as temp: + source = Path(temp) / "source" + skill = source / "skills" / "example-skill" + skill.mkdir(parents=True) + (skill / "SKILL.md").write_text("example", encoding="utf-8") + destination = Path(temp) / "runtime-skills" + + messages = self.install.install_library( + source, + destination, + delete=True, + dry_run=False, + ) + + installed = destination / "example-skill" + self.assertTrue(installed.is_symlink()) + self.assertEqual(installed.resolve(), skill.resolve()) + self.assertIn("linked", messages[0]) + if __name__ == "__main__": unittest.main()