From 245b2faa13bd600d7e32c4f3d75d25588b6d9738 Mon Sep 17 00:00:00 2001 From: Kevin Yin <182213728+yinkev@users.noreply.github.com> Date: Sun, 26 Jul 2026 02:21:40 -0700 Subject: [PATCH] fix(docs): prune removed generated skill pages --- tests/website/test_generate_skill_docs.py | 25 ++ .../autonomous-ai-agents-kanban-codex-lane.md | 295 ------------------ website/scripts/generate-skill-docs.py | 38 ++- 3 files changed, 62 insertions(+), 296 deletions(-) delete mode 100644 website/docs/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-kanban-codex-lane.md diff --git a/tests/website/test_generate_skill_docs.py b/tests/website/test_generate_skill_docs.py index fca565191902..568ff2819118 100644 --- a/tests/website/test_generate_skill_docs.py +++ b/tests/website/test_generate_skill_docs.py @@ -114,3 +114,28 @@ def test_bundled_catalog_explains_missing_local_skills(gen_module): result = gen_module.build_catalog_md_bundled([]) assert "respects local deletions and user edits" in result assert "hermes skills reset --restore" in result + + +def test_prune_stale_generated_pages_preserves_live_and_handwritten( + gen_module, tmp_path, monkeypatch +): + """Removed skills must not leave published pages behind (#71856).""" + pages = tmp_path / "skills" + live = pages / "bundled" / "agents" / "agents-live.md" + stale = pages / "bundled" / "agents" / "agents-removed.md" + handwritten = pages / "bundled" / "agents" / "notes.md" + for path in (live, stale, handwritten): + path.parent.mkdir(parents=True, exist_ok=True) + + marker = gen_module.GENERATED_PAGE_MARKER + live.write_text(f"{marker}\nlive\n", encoding="utf-8") + stale.write_text(f"{marker}\nstale\n", encoding="utf-8") + handwritten.write_text("# Maintained by hand\n", encoding="utf-8") + monkeypatch.setattr(gen_module, "SKILLS_PAGES", pages) + + removed = gen_module.prune_stale_generated_pages({live}) + + assert removed == [stale] + assert live.exists() + assert handwritten.exists() + assert not stale.exists() diff --git a/website/docs/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-kanban-codex-lane.md b/website/docs/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-kanban-codex-lane.md deleted file mode 100644 index 671b696264ae..000000000000 --- a/website/docs/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-kanban-codex-lane.md +++ /dev/null @@ -1,295 +0,0 @@ ---- -title: "Kanban Codex Lane" -sidebar_label: "Kanban Codex Lane" -description: "Use when a Hermes Kanban worker wants to run Codex CLI as an isolated implementation lane while Hermes keeps ownership of task lifecycle, reconciliation, tes..." ---- - -{/* This page is auto-generated from the skill's SKILL.md by website/scripts/generate-skill-docs.py. Edit the source SKILL.md, not this page. */} - -# Kanban Codex Lane - -Use when a Hermes Kanban worker wants to run Codex CLI as an isolated implementation lane while Hermes keeps ownership of task lifecycle, reconciliation, testing, and handoff. - -## Skill metadata - -| | | -|---|---| -| Source | Bundled (installed by default) | -| Path | `skills/autonomous-ai-agents/kanban-codex-lane` | -| Version | `1.0.0` | -| Author | Hermes Agent | -| License | MIT | -| Tags | `kanban`, `codex`, `worktrees`, `autonomous-agents`, `prediction-market-bot` | -| Related skills | [`codex`](/docs/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-codex), [`hermes-agent`](/docs/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-hermes-agent) | - -## Reference: full SKILL.md - -:::info -The following is the complete skill definition that Hermes loads when this skill is triggered. This is what the agent sees as instructions when the skill is active. -::: - -# Kanban Codex Lane - -## Overview - -This skill defines the lightweight Hermes+Codex dual-lane convention for Kanban workers. Hermes is always the task owner: it calls `kanban_show`, decides whether Codex is appropriate, creates or selects an isolated workspace, starts and monitors Codex, reconciles any diff, runs verification, and writes the final `kanban_complete` or `kanban_block` handoff. Codex is an input lane only. Codex output is not a task completion signal, not a trusted reviewer, and not allowed to write durable Kanban state directly. - -The convention exists so a Hermes worker can use Codex for bounded implementation help without changing the dispatcher. The dispatcher must still spawn Hermes workers. A worker may optionally spawn Codex inside its own run, then accept, partially accept, or reject the lane after independent review and tests. - -## When to Use - -Use the Codex lane when all of these are true: - -- The Kanban task is a coding, refactor, documentation, test, or mechanical migration task with clear acceptance criteria. -- A bounded diff can be evaluated by Hermes in one run. -- The repo can be copied or checked out in an isolated git worktree/branch. -- Hermes can run the relevant tests itself after Codex exits. -- The prompt can state all safety constraints and files that must not change. - -Do not use the Codex lane when any of these are true: - -- The task requires human judgment that is not already captured in the Kanban body. -- The worker lacks repo access, Codex auth, or time to reconcile the result. -- The change touches secrets, credential stores, private user data, or production order-entry systems. -- A small direct edit is faster and safer than spawning another agent. -- The task is research-only and should produce a written handoff rather than a diff. -- The worker would be tempted to mark Done based only on Codex self-report. - -## Ownership Rules - -1. Hermes owns the Kanban lifecycle. Codex must never call `kanban_complete`, `kanban_block`, `kanban_create`, gateway messaging, or any Hermes board CLI as a substitute for the worker. -2. Hermes owns final acceptance. Treat Codex commits/diffs as untrusted patches until reviewed and verified. -3. Hermes owns test execution. Codex may run tests, but those runs are advisory; repeat required verification from Hermes with the repo's canonical wrapper. -4. Hermes owns safety. If Codex changes safety boundaries, risk gates, live trading behavior, or secrets handling, reject the lane even if tests pass. -5. Hermes owns cleanup. Kill stuck Codex processes and remove temporary worktrees when they are no longer needed. - -## Required Worktree and Branch Pattern - -Never run Codex directly in a shared dirty checkout. Use a branch/worktree name that ties the lane to the Kanban task and keeps untrusted edits isolated. - -Recommended variables: - -```bash -TASK_ID="${HERMES_KANBAN_TASK:-t_manual}" -REPO="/path/to/repo" -BASE="$(git -C "$REPO" rev-parse --abbrev-ref HEAD)" -SAFE_TASK="$(printf '%s' "$TASK_ID" | tr -cd '[:alnum:]_-')" -BRANCH="codex/${SAFE_TASK}/$(date -u +%Y%m%d%H%M%S)" -WORKTREE="/tmp/${SAFE_TASK}-codex-lane" -``` - -Create the isolated lane: - -```bash -git -C "$REPO" fetch --all --prune -git -C "$REPO" worktree add -b "$BRANCH" "$WORKTREE" "$BASE" -git -C "$WORKTREE" status --short --branch -``` - -If the current Kanban workspace is already an isolated git worktree created for this task, you may create a sibling Codex branch inside it only if `git status --short` is clean except for intentional Hermes edits. Otherwise create a separate temporary worktree and cherry-pick or copy accepted commits back after reconciliation. - -Cleanup after reconciliation: - -```bash -git -C "$REPO" worktree remove "$WORKTREE" -git -C "$REPO" branch -D "$BRANCH" # only after accepted commits were copied/cherry-picked or intentionally rejected -``` - -Keep the worktree if it is needed as an artifact for review; record it in `codex_lane.artifacts` and mention it in the handoff. - -## Codex Capability Checks - -Run these before spawning Codex. Missing Codex is a normal reason to skip the lane, not a task blocker if Hermes can do the task directly. - -```bash -command -v codex -codex --version -codex features list | grep -i goals || true -``` - -If `/goal` support is required, enable or launch with the feature flag only after checking availability: - -```bash -codex features enable goals || true -codex --enable goals --version -``` - -Authentication can be via `OPENAI_API_KEY` or the Codex CLI OAuth state (often `~/.codex/auth.json`). Do not print token files. A missing `OPENAI_API_KEY` is not proof that auth is unavailable. - -## Mode Selection - -Use `codex exec` for bounded one-shot edits where Codex should exit on its own: - -```python -terminal( - command="codex exec --full-auto '$(cat /tmp/codex_prompt.md)'", - workdir=WORKTREE, - background=True, - pty=True, - notify_on_complete=True, -) -``` - -Use Codex `/goal` only for broader multi-step work that benefits from durable objective tracking. Launch interactively in a PTY/tmux session or with `codex --enable goals` if the feature is disabled by default. Keep the goal objective self-contained: repo path, task id, safety constraints, allowed scope, acceptance criteria, tests, and commit expectations. - -Example `/goal` objective text to paste into Codex: - -```text -/goal Work in this repository only: . Task: . -Hermes owns the Kanban lifecycle; do not call Hermes kanban tools or messaging. -Create small commits on branch <BRANCH>. Follow the PMB safety constraints in the prompt. -Run the requested verification commands and report exact outputs. Stop after producing a diff and summary. -``` - -Do not use `--yolo` for prediction-market-bot or safety-sensitive repos. Prefer `--full-auto` inside the isolated worktree, then rely on Hermes reconciliation. - -## Prompt Construction - -Use the linked template at `templates/pmb-codex-lane-prompt.md` for prediction-market-bot work. For other repos, keep the same structure and replace the PMB-specific safety block with repo-specific invariants. - -Every Codex prompt must include: - -- `task_id`, title, and full Kanban acceptance criteria. -- Repo path, worktree path, branch name, and allowed file scope. -- Explicit statement: Hermes owns Kanban lifecycle; Codex is an input lane only. -- Required output: concise summary, files changed, commits, tests run, and known risks. -- Prohibited actions: secrets access, external messaging, board mutation, unrelated refactors, dependency upgrades unless required. -- Verification commands Codex may run and commands Hermes will run afterward. - -For PMB, include these mandatory safety constraints verbatim: - -```text -PMB safety constraints: -- live-SIM is paper-only; do not add or enable live REST order entry. -- Never use market orders. -- Do not add execution crossing or bypass price/risk checks. -- Do not fake passive fills, fills, PnL, order states, or reconciliation evidence. -- Do not weaken risk gates, limits, kill switches, or fail-closed behavior. -- Keep research/selection outside the C++ hot path unless explicitly requested. -- Do not read, print, write, or require secrets/tokens/credentials. -``` - -## Monitoring, Timeout, and Kill Behavior - -Start long Codex lanes in the background with PTY and completion notification: - -```python -result = terminal( - command="codex exec --full-auto '$(cat /tmp/codex_prompt.md)'", - workdir=WORKTREE, - background=True, - pty=True, - notify_on_complete=True, -) -session_id = result["session_id"] -``` - -Monitor without interfering: - -```python -process(action="poll", session_id=session_id) -process(action="log", session_id=session_id, limit=200) -process(action="wait", session_id=session_id, timeout=300) -``` - -Send a Kanban heartbeat every few minutes for lanes longer than two minutes, e.g. `kanban_heartbeat(note="Codex lane running in <WORKTREE>; waiting for tests/diff")`. - -Kill conditions: - -- No useful output for the task's remaining runtime budget. -- Codex requests secrets, production credentials, or external permissions. -- Codex attempts to modify files outside the worktree. -- Codex starts unrelated rewrites or dependency churn. -- Codex is still running near the worker timeout and no safe partial artifact exists. - -Kill command: - -```python -process(action="kill", session_id=session_id) -``` - -After kill, inspect `git status --short`, preserve useful patches only if safe, and record `codex_lane.result: timed_out` or `rejected` with a concrete `rejected_reason`. - -## Reconciliation Checklist - -Hermes must perform this checklist before accepting any Codex lane result: - -- [ ] `git -C <WORKTREE> status --short --branch` shows only expected files. -- [ ] `git -C <WORKTREE> diff --stat` and `git diff` were reviewed by Hermes. -- [ ] No secrets, credentials, generated caches, unrelated data, or local artifacts are included. -- [ ] PMB safety constraints were preserved: no live REST order entry, no market orders, no execution crossing, no fake passive fills/PnL, no risk-gate weakening, no secrets. -- [ ] Codex commits are small enough to cherry-pick or squash cleanly. -- [ ] Hermes ran the canonical tests itself, using `scripts/run_tests.sh` for Hermes Agent or the repo's documented wrapper for other repos. -- [ ] Any Codex-run tests are listed separately from Hermes-run tests. -- [ ] Accepted commits/diffs were applied to the Hermes-owned workspace/branch. -- [ ] Rejected or partial work has a concrete reason and artifact path if useful. - -Acceptance outcomes: - -- `accepted`: Codex diff/commits were reviewed, applied, and verified. -- `partial`: Some Codex work was accepted after edits or cherry-picks; rejected parts are documented. -- `rejected`: No Codex changes were accepted; reason is documented. -- `timed_out`: Codex exceeded the lane budget; useful artifacts may or may not exist. - -## kanban_complete Metadata Schema - -Include this object under `metadata.codex_lane` for every task where the lane was considered. If Codex was not used, set `used: false` and explain why in `rejected_reason` or a sibling `notes` field. - -```json -{ - "codex_lane": { - "used": true, - "mode": "exec | goal | skipped", - "worktree": "/absolute/path/to/codex/worktree", - "branch": "codex/t_caa69668/20260508100000", - "command": "codex exec --full-auto ...", - "result": "accepted | rejected | partial | timed_out", - "accepted_commits": ["<sha1>", "<sha2>"], - "rejected_reason": "empty when fully accepted; otherwise concrete reason", - "tests_run": [ - {"command": "scripts/run_tests.sh tests/tools/test_x.py", "exit_code": 0, "owner": "hermes"}, - {"command": "codex-reported: npm test", "exit_code": 0, "owner": "codex"} - ], - "artifacts": ["/absolute/path/to/log-or-patch"] - } -} -``` - -For tasks that intentionally skip Codex: - -```json -{ - "codex_lane": { - "used": false, - "mode": "skipped", - "worktree": null, - "branch": null, - "command": null, - "result": "rejected", - "accepted_commits": [], - "rejected_reason": "Direct Hermes edit was smaller and safer than spawning Codex.", - "tests_run": [], - "artifacts": [] - } -} -``` - -## Common Pitfalls - -1. Treating Codex self-report as verification. Always inspect the diff and rerun tests from Hermes. -2. Running Codex in the user's dirty main checkout. Always isolate in a worktree/branch. -3. Letting Codex own Kanban. Codex may summarize progress, but Hermes writes board state. -4. Forgetting PMB safety invariants in the prompt. Missing safety text is a lane setup failure. -5. Using `/goal` for quick edits. Prefer `codex exec` unless durable multi-step continuation is needed. -6. Killing a stuck lane without recording why. `rejected_reason` must explain the decision. -7. Accepting broad unrelated cleanup because tests pass. Reject or cherry-pick only the scoped changes. - -## Verification Checklist - -- [ ] Codex was skipped or started only after `command -v codex`, `codex --version`, and optional goals feature checks. -- [ ] Codex ran only in an isolated worktree/branch. -- [ ] Prompt included task scope, ownership rules, PMB safety constraints when applicable, and verification commands. -- [ ] Hermes reviewed `git diff` and safety-sensitive files. -- [ ] Hermes ran canonical tests independently. -- [ ] `kanban_complete.metadata.codex_lane` follows the schema above. -- [ ] Temporary processes and unnecessary worktrees were cleaned up. diff --git a/website/scripts/generate-skill-docs.py b/website/scripts/generate-skill-docs.py index 7a8644ea617b..4fa0d4949e7d 100755 --- a/website/scripts/generate-skill-docs.py +++ b/website/scripts/generate-skill-docs.py @@ -33,6 +33,12 @@ # We leave these alone (they get first-class sidebar treatment separately). HAND_WRITTEN = {"google-workspace.md"} +GENERATED_PAGE_MARKER = ( + "{/* This page is auto-generated from the skill's SKILL.md by " + "website/scripts/generate-skill-docs.py. Edit the source SKILL.md, " + "not this page. */}" +) + _FENCE_RE = re.compile(r"^(?P<indent>\s*)(?P<fence>```+|~~~+)", re.MULTILINE) @@ -429,7 +435,7 @@ def render_skill_page( f'description: "{fm_desc}"\n' "---\n" "\n" - "{/* This page is auto-generated from the skill's SKILL.md by website/scripts/generate-skill-docs.py. Edit the source SKILL.md, not this page. */}\n" + f"{GENERATED_PAGE_MARKER}\n" "\n" f"# {display_name}\n" "\n" @@ -449,6 +455,28 @@ def render_skill_page( ) +def prune_stale_generated_pages(expected_paths: set[Path]) -> list[Path]: + """Delete generated skill pages whose source SKILL.md no longer exists. + + Only files below the generated ``bundled`` / ``optional`` trees carrying + this generator's exact marker are eligible. Hand-written documentation is + never removed, even if it sits beside generated pages. + """ + removed: list[Path] = [] + for source_kind, _source_dir in SKILL_SOURCES: + source_pages = SKILLS_PAGES / source_kind + if not source_pages.exists(): + continue + for path in sorted(source_pages.rglob("*.md")): + if path in expected_paths: + continue + if GENERATED_PAGE_MARKER not in path.read_text(encoding="utf-8"): + continue + path.unlink() + removed.append(path) + return removed + + def discover_skills() -> list[tuple[dict[str, Any], dict[str, Any]]]: results: list[tuple[dict[str, Any], dict[str, Any]]] = [] for kind, source_dir in SKILL_SOURCES: @@ -744,6 +772,14 @@ def main(): if name not in skill_index or meta["source_kind"] == "bundled": skill_index[name] = meta + # Remove generated pages whose source skill was deleted. Without this, + # the generator only adds/updates pages and removed bundled skills remain + # published indefinitely (#71856). + expected_pages = {page_output_path(meta) for meta, _parsed in entries} + removed = prune_stale_generated_pages(expected_pages) + if removed: + print(f"Removed {len(removed)} stale generated skill page(s)") + # Write per-skill pages written = 0 for meta, parsed in entries: