From 748376d2940eca662c6d048915ea1b410f1553ba Mon Sep 17 00:00:00 2001 From: Alex Wegener Date: Tue, 13 Oct 2026 11:00:47 +0200 Subject: [PATCH 1/4] chore: add Codex plugin support alongside Claude Code --- .agents/plugins/marketplace.json | 20 ++ .codex-plugin/plugin.json | 22 ++ .github/workflows/plugin-check.yml | 30 +++ .ytstack/DECISIONS.md | 26 +++ .ytstack/REVIEW-NOTES.md | 8 + .ytstack/STATE.md | 4 +- AGENTS.md | 4 +- CONTRIBUTING.md | 13 ++ QUICKSTART.md | 4 + README.md | 21 +- bin/ytstack-codex | 39 ++++ docs/codex.md | 95 +++++++++ docs/ux/skill-structure.md | 5 + hooks/codex.json | 40 ++++ hooks/pre-compact | 4 +- hooks/session-start | 8 +- skills/atomic-design/SKILL.md | 3 + skills/deutschland-stack-api/SKILL.md | 3 + skills/european-alternatives-api/SKILL.md | 3 + skills/handoff-session/SKILL.md | 3 + skills/init-project/SKILL.md | 3 + skills/office-hours/SKILL.md | 3 + skills/oss-project/SKILL.md | 3 + skills/plan-ceo-review/SKILL.md | 3 + skills/plan-eng-review/SKILL.md | 3 + skills/plan-milestone/SKILL.md | 3 + skills/plan-task/SKILL.md | 3 + skills/reassess-roadmap/SKILL.md | 3 + skills/resume-session/SKILL.md | 3 + skills/slice-milestone/SKILL.md | 3 + skills/software-craftsmanship/SKILL.md | 3 + skills/spawn-milestone-team/SKILL.md | 3 + skills/summarize-task/SKILL.md | 3 + skills/systematic-debugging/SKILL.md | 3 + skills/test-driven-development/SKILL.md | 3 + skills/using-ytstack/SKILL.md | 22 +- skills/using-ytstack/references/codex.md | 85 ++++++++ .../verification-before-completion/SKILL.md | 3 + tests/codex_plugin_test.py | 195 ++++++++++++++++++ 39 files changed, 686 insertions(+), 19 deletions(-) create mode 100644 .agents/plugins/marketplace.json create mode 100644 .codex-plugin/plugin.json create mode 100644 .github/workflows/plugin-check.yml create mode 100755 bin/ytstack-codex create mode 100644 docs/codex.md create mode 100644 hooks/codex.json create mode 100644 skills/using-ytstack/references/codex.md create mode 100644 tests/codex_plugin_test.py diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json new file mode 100644 index 0000000..040e2b9 --- /dev/null +++ b/.agents/plugins/marketplace.json @@ -0,0 +1,20 @@ +{ + "name": "ytstack", + "interface": { + "displayName": "ytstack" + }, + "plugins": [ + { + "name": "ytstack", + "source": { + "source": "local", + "path": "./" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_INSTALL" + }, + "category": "Developer Tools" + } + ] +} diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json new file mode 100644 index 0000000..b8093e6 --- /dev/null +++ b/.codex-plugin/plugin.json @@ -0,0 +1,22 @@ +{ + "name": "ytstack", + "version": "0.1.6", + "description": "Project memory, milestone planning, and curated gstack and superpowers workflows for AI coding agents.", + "author": { + "name": "Yesterday", + "url": "https://github.com/Yesterday-AI" + }, + "homepage": "https://github.com/Yesterday-AI/ytstack", + "repository": "https://github.com/Yesterday-AI/ytstack", + "license": "MIT", + "keywords": ["codex", "project-management", "development-methodology"], + "skills": "./skills/", + "hooks": "./hooks/codex.json", + "interface": { + "displayName": "ytstack", + "shortDescription": "Plan, build, and resume work across sessions", + "longDescription": "Keep project decisions and progress on disk. Plan milestones, review scope and architecture, and apply test-first development with explicit verification.", + "developerName": "Yesterday", + "category": "Developer Tools" + } +} diff --git a/.github/workflows/plugin-check.yml b/.github/workflows/plugin-check.yml new file mode 100644 index 0000000..4c4344e --- /dev/null +++ b/.github/workflows/plugin-check.yml @@ -0,0 +1,30 @@ +name: Plugin checks + +on: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Codex packaging and lifecycle integration + run: python3 -m unittest discover -s tests -p 'codex_plugin_test.py' -v + - name: Claude commit-hook regression + run: bash tests/post-tool-use-bash.test.sh + - name: Skill contracts + shell: bash + run: | + result=0 + bin/ytstack-check || result=$? + if [ "$result" -ne 0 ] && [ "$result" -ne 2 ]; then + exit "$result" + fi diff --git a/.ytstack/DECISIONS.md b/.ytstack/DECISIONS.md index 501de00..f3b31f8 100644 --- a/.ytstack/DECISIONS.md +++ b/.ytstack/DECISIONS.md @@ -639,3 +639,29 @@ Plus: ops-layer split during the same session (yopstack created as own public re - Regression test: `tests/post-tool-use-bash.test.sh` (10 cases) is the contract guard. **Supersedes:** none. + + +--- + +## 2026-10-13: Add Codex distribution alongside Claude Code + +**Context:** The maintainer requested a PR making the existing Claude plugin usable as a Codex plugin. Codex could not discover ytstack in the active skill catalog. Shared wrappers also rely on Claude skill-directory variables and shell-injection blocks, so a manifest alone would not make them executable. + +**Options considered:** +- A) Add a manifest only; leave host-specific skill and hook assumptions unresolved. +- B) Keep shared skills and immutable vendor content, add a Codex manifest/self-marketplace and an adapter for host mechanics. +- C) Fork all skills into a second Codex-only tree. + +**Chose:** B, implementing the requested additional host with one source of skill content. + +**Reason:** Both hosts can use the same artifacts and procedures. Codex needs explicit execution of the wrapper blocks and correct installed skill paths. A per-skill adapter link covers direct invocation even without trusted lifecycle hooks. A separate hook manifest prevents discovery of Claude-only team and tool events. + +**How to apply:** +- `.codex-plugin/plugin.json` loads the existing `skills/`; `.agents/plugins/marketplace.json` self-marketplaces the repo root as `ytstack@ytstack`. +- `bin/ytstack-codex` derives plugin/skill paths and sets the target project from the working directory for each shared shell block or lifecycle hook. +- Codex registers SessionStart, PreCompact, and SessionEnd only. Hook trust remains the host's explicit user-controlled setting. Without hooks, skills read state directly and support explicit resume/handoff. +- Claude Agent Teams, its subagent role files, and edit/commit hooks remain Claude-specific. Codex's team skill reports the limitation and offers the sequential workflow; it does not emulate those events with Codex subagents. +- No Claude marketplace dependencies are copied into the Codex manifest. Required optional capabilities must be available separately. +- Keep the two plugin manifest versions aligned. Existing Claude installation and vendored content remain unchanged. + +**Supersedes:** The Claude-only host assumption in the 2026-04-23 "Skills-based plugin, not Agent SDK library" and 2026-04-24 path/wrapper decisions. The skills-based architecture, vendor-as-source-of-truth rule, and Claude's native wrapper behavior remain in force. diff --git a/.ytstack/REVIEW-NOTES.md b/.ytstack/REVIEW-NOTES.md index a15bd14..66421b3 100644 --- a/.ytstack/REVIEW-NOTES.md +++ b/.ytstack/REVIEW-NOTES.md @@ -158,3 +158,11 @@ Running list of items to verify, revisit, or polish at end-of-cycle review. Adde - **At v0.1 release:** items remaining must be annotated with target version (v0.1.1, v0.2, etc.) or closed as "intentionally out of scope." **File is append-only within a milestone.** When re-organizing or resolving, add a date and move resolved items to a `## Resolved` section at the bottom (keep for audit). + + +## Codex compatibility (2026-10-13) + +- [x] Verified Codex CLI 0.162.1 local marketplace discovery, install/enable, and app-server skill/hook loading in a temporary profile: 21 skills, 3 lifecycle hooks, no load errors. Hooks remain untrusted until the user reviews them. +- [x] Integration tests cover the configured hook commands, project/user state lookup, compaction handoff, session journaling without tracked-state mutation, all six vendor imports, paths with spaces, invalid skills, and failure propagation. Existing Claude commit-hook regression passes; validator warnings match main (109). +- [ ] Manual interactive Codex desktop walkthrough after hook trust: resume an initialized project, complete a task, compact, then confirm the next session sees the handoff. Deterministic hook tests and app-server discovery do not validate model behavior or the desktop trust UI. +- Codex support deliberately excludes Claude Agent Teams and edit/commit hooks. The shared adapter explains sequential execution and explicit scope/verification; do not advertise full hook/team parity. `docs/codex.md` records the boundary. diff --git a/.ytstack/STATE.md b/.ytstack/STATE.md index ce8d10c..df4a4ca 100644 --- a/.ytstack/STATE.md +++ b/.ytstack/STATE.md @@ -1,7 +1,7 @@ --- project: ytstack slug: ytstack -last_updated: 2026-05-31T13:20:00Z +last_updated: 2026-10-13T08:59:49Z current_milestone: M015 active_slice: none active_task: none @@ -9,6 +9,8 @@ active_task: none # State +**Status (2026-10-13, Codex compatibility PR):** Added a Codex plugin manifest and self-marketplace, a shared host adapter linked from all 21 skills, and an explicit 3-event lifecycle hook configuration. Existing Claude manifests and vendor files are unchanged. Codex CLI 0.162.1 installs/enables the package in a separate temporary profile; app-server discovery loads all 21 namespaced skills and 3 hooks with no errors or warnings. Hooks correctly remain untrusted until reviewed. Nine Codex integration tests and ten existing commit-hook checks pass; `ytstack-check` has the same 109 warnings as the main-branch baseline, no failures. Native Claude Agent Teams and edit/commit hooks remain outside Codex support (see `docs/codex.md`). Awaiting PR review; no release or user-profile installation performed. + **Status (2026-05-31, vendor drift audit + superpowers pull):** Audited both vendored subtrees for upstream breaking changes against the ytstack wrapper contract (wrappers inline the six wrapped SKILL.md verbatim via `` ```!cat ``; breaking surface = path-rename = silent empty `cat`, or procedure rewrite). **superpowers PULLED** `6efe32c -> 6fd4507`: all three wrapped SKILL.md (TDD, systematic-debugging, verification-before-completion) byte-identical upstream, zero ytstack-behavior change; only drumherum moved (README, tests, using-git-worktrees, finishing-a-development-branch). Merge `ca6675e` / squash `ad14900`; `bin/ytstack-check` = 0 fails (exit 2 = warnings-only). **NOTE: ca6675e is already on origin/main (pushed)** -- the push went out without an explicit push in-conversation (auto-mechanism or the still-present `thirsty-bhabha-a03490` worktree); flagged to the human. **gstack NOT pulled**, held at `aeea57f` (upstream `3bef43b` is a v1 restructure, ~1000 changed lines/file): methodology anchors all survive (no hard break), but a net-new `gbrain:` frontmatter `context_queries` block + `GSTACK_PLAN_MODE` is surface NOT covered by DECISIONS 2026-04-25 "Vendored-preamble drift accepted" (that decision only covers `|| true` shell, not frontmatter) -- revisit that decision before pulling gstack. KNOWLEDGE gotchas added (drift-audit method + gbrain). Hold-gstack is deliberately NOT yet a locked DECISIONS entry (left as a STATE/KNOWLEDGE fact pending the gbrain-strategy call). No version bump for the vendor refresh -- decided 2026-05-31, no ytstack-behavior change so it stays 0.1.6. **Status (2026-05-31, M015):** Fixed issue #22 via `systematic-debugging` (root cause empirically reproduced, not trusted from the report). The `post-tool-use-bash` hook is now **stub-once** (creates the draft `T##-SUMMARY.md` once on the first real `git commit`, never re-writes; matcher tightened to a real `git commit` after a command separator) and commit-to-task linking moved into `summarize-task` (greps `git log` for the `M###-S##-T##:` ref into a committed `## Commits` section). This kills the duplicate "Commits so far" lines + never-settling working tree that broke worktree/`spawn-milestone-team` dispatch. New regression test `tests/post-tool-use-bash.test.sh` (10 cases, all green). Version bumped 0.1.5 -> **0.1.6**. DECISIONS entry "post-tool-use-bash is stub-once" + KNOWLEDGE gotcha added. **SHIPPED:** committed (`1c1d612` fix, `77365f5` chore), pushed to `origin/main`, tagged **v0.1.6** (pushed), CHANGELOG.md created, **issue #22 CLOSED**. Cache caveat: live for users only after `/plugin update` to 0.1.6. diff --git a/AGENTS.md b/AGENTS.md index cc429b1..6764707 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,7 +10,7 @@ Before any architectural proposal, read in order: `docs/concept.md` (stable summ Stop. Read this section before acting. -ytstack is an opinionated software-development OS packaged as a Claude Code plugin. It has strict UX contracts and a curated scope. Agents that skip this file produce churn -- the wrong abstraction, the wrong scope, drift away from the artifact model. +ytstack is an opinionated software-development OS packaged for Claude Code and Codex. It has strict UX contracts and a curated scope. Agents that skip this file produce churn -- the wrong abstraction, the wrong scope, drift away from the artifact model. **Your job is to protect the human from shipping low-quality skills.** Before you open a PR or commit a skill change, you MUST: @@ -25,7 +25,7 @@ If you can't verify all five above, stop and tell the human what you can't verif ## What ytstack is (and is not) **Is:** -- A Claude Code plugin (skills + hooks + commands) +- A Claude Code and Codex plugin (shared skills, host-specific manifests and hooks; see `docs/codex.md` for the support boundary) - A curated clamp around selected superpowers and gstack skills, vendored via git subtree, NEVER modified - An artifact-first system: `.ytstack/PROJECT.md`, `DECISIONS.md`, `KNOWLEDGE.md`, `STATE.md`, milestone/slice/task hierarchy - SessionStart-hook-driven context injection (following superpowers' using-superpowers pattern) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 413e2d4..764665a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -105,6 +105,19 @@ Per ytstack's design direction, we credit external methodology work generically, 5. Test standalone with mock `CLAUDE_PROJECT_DIR`. 6. Add REVIEW-NOTES entry for integration-test-needed. +## Codex compatibility + +Both plugin manifests load `skills/`. Keep the Codex adapter link at the top +of each skill so direct invocation works without a SessionStart hook. +Host mappings live in `skills/using-ytstack/references/codex.md`; do not fork +vendored procedures. Register only supported events in `hooks/codex.json`. +Keep `.codex-plugin/plugin.json` and `.claude-plugin/plugin.json` versions aligned. + +Run `python3 -m unittest discover -s tests -p 'codex_plugin_test.py' -v` and +`bash tests/post-tool-use-bash.test.sh` alongside `bin/ytstack-check`. CI accepts +the validator's warnings-only exit code (2) and rejects failures. See +[Codex setup](docs/codex.md) for installation and manual discovery checks. + ## How to add a new vendor subtree 1. Verify the license is compatible (MIT / Apache-2.0 / similar permissive). diff --git a/QUICKSTART.md b/QUICKSTART.md index 23518fb..8651568 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -4,6 +4,10 @@ From zero to a tracked, shipped task -- the way you actually use ytstack: by tal ## Install +For Codex, follow [Codex setup](docs/codex.md). The workflow below uses Claude +Code command names; in Codex select the corresponding skill from ytstack with +`$ytstack:`. Claude Agent Teams are not available through the Codex adapter. + ### Local dev (recommended for first try) ```bash diff --git a/README.md b/README.md index ed32847..79cefb7 100644 --- a/README.md +++ b/README.md @@ -6,13 +6,14 @@

An opinionated OS for AI coding agents. Plan like a PM, execute like a senior eng.

- A Claude Code plugin that turns multi-session, multi-week projects into something your agent can actually hold onto. Project memory on disk, curated skill set, parallel execution via native Agent Teams. One install, one namespace, no stack to maintain. + A plugin for Claude Code and Codex that turns multi-session, multi-week projects into something your agent can actually hold onto. Project memory on disk, curated skill set, and native Agent Teams in Claude Code. One install, one namespace, no stack to maintain.

License version Claude Code + Codex skills hooks

@@ -178,6 +179,23 @@ Source: [`docs/ytstack-debugging-flow.excalidraw`](docs/ytstack-debugging-flow.e ## Install +### Codex + +```bash +codex plugin marketplace add Yesterday-AI/ytstack +codex plugin add ytstack@ytstack +``` + +Start a new session and select `$ytstack:using-ytstack` from ytstack in the skill picker. +On local hosts with hook support, review and trust the plugin hooks for automatic +startup context, compaction handoff, and session journaling. The same 21 skills +are packaged for both hosts. Claude Agent Teams and edit/commit hooks remain +Claude-only; Codex uses the sequential task flow and explicit verification. +See [Codex setup and compatibility](docs/codex.md) for local development, +hook-free usage, and validation. + +The installation methods below are for Claude Code. + ### Local dev (recommended for first try) ```bash @@ -272,6 +290,7 @@ Ships: - 8 hooks (SessionStart / PreCompact / SessionEnd / TeammateIdle / TaskCreated / TaskCompleted / PreToolUse-Edit / PostToolUse-Bash). SessionStart-hook now injects the using-ytstack directive + project state. - Full docs (`AGENTS.md`, `CONTRIBUTING.md`, `QUICKSTART.md`, UX contracts, references, methodology) - Plugin manifest + marketplace manifest, published via the `yesterday-public-plugins` catalog +- Codex manifest + self-marketplace, shared skills, and 3 lifecycle hooks; see [host support](docs/codex.md#supported-behavior) See `.ytstack/ROADMAP.md` for the task list, `.ytstack/STATE.md` for progress, `.ytstack/REVIEW-NOTES.md` for deferred items. diff --git a/bin/ytstack-codex b/bin/ytstack-codex new file mode 100755 index 0000000..b27facf --- /dev/null +++ b/bin/ytstack-codex @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# Run shared ytstack shell blocks and lifecycle hooks in Codex. +set -euo pipefail + +export CLAUDE_PLUGIN_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +export CLAUDE_PROJECT_DIR="$PWD" +export YTSTACK_HOST=codex + +usage() { + echo 'Usage: ytstack-codex skill [bash arguments...] | hook ' >&2 + exit 2 +} + +[ "$#" -ge 2 ] || usage +MODE="$1" +NAME="$2" +shift 2 + +case "$MODE" in + skill) + [[ "$NAME" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]] || usage + export CLAUDE_SKILL_DIR="$CLAUDE_PLUGIN_ROOT/skills/$NAME" + [ -f "$CLAUDE_SKILL_DIR/SKILL.md" ] || { + echo "ytstack: skill not found: $NAME" >&2 + exit 2 + } + exec bash "$@" + ;; + hook) + [ "$#" -eq 0 ] || usage + case "$NAME" in + session-start|pre-compact|session-end) + exec bash "$CLAUDE_PLUGIN_ROOT/hooks/$NAME" + ;; + *) usage ;; + esac + ;; + *) usage ;; +esac diff --git a/docs/codex.md b/docs/codex.md new file mode 100644 index 0000000..f5d788b --- /dev/null +++ b/docs/codex.md @@ -0,0 +1,95 @@ +# Use ytstack in Codex + +ytstack ships a Codex plugin manifest alongside its Claude Code manifest. Both +hosts load the same 21 skills and vendored procedures. Codex uses a separate +hook configuration and a small shell adapter; Claude Code keeps its existing +installation and hooks. + +## Install + +With a Codex CLI that supports `codex plugin`: + +```bash +codex plugin marketplace add Yesterday-AI/ytstack +codex plugin add ytstack@ytstack +``` + +Restart Codex or open a new session after installation. In the desktop Plugins +Directory, the marketplace is named **ytstack**. Select its ytstack plugin to +install or enable it. This repository's Codex self-marketplace does not depend +on changes to Yesterday's shared plugin catalog. + +For a local checkout, replace the first command with: + +```bash +codex plugin marketplace add /absolute/path/to/ytstack +codex plugin add ytstack@ytstack +``` + +The catalog at `.agents/plugins/marketplace.json` points to the checkout root. +Codex copies the package into its plugin cache. After changing local files, +refresh/reinstall the plugin and start a new session before testing. + +Use a local macOS/Linux environment with Bash, Git, and either `jq` or Python 3 +for lifecycle hooks. The artifact validator and tests use Python 3. Native +Windows shells and remote environments without these files are not covered. + +## First session + +Start Codex in the project you want to work on, not the plugin's source tree. +Select **using-ytstack** from ytstack in the skill picker (`$ytstack:using-ytstack`), +then describe the task. To resume an initialized project, select +`$ytstack:resume-session`. Plugin skills with duplicate names are distinguished by +their ytstack origin in the picker. + +On local hosts that support plugin hooks, review and trust ytstack's hooks +through Codex's hook controls. Installing the plugin does not grant hook trust. +Trusted SessionStart hooks inject the directive and state automatically, +including after compaction. Until hooks are trusted, explicit skill loading +works and the adapter reads state from disk. + +The shared skill text uses Claude tool names and fenced `!` shell blocks. +Every skill links to the [Codex adapter](../skills/using-ytstack/references/codex.md), +which maps those operations and uses `bin/ytstack-codex` to set the correct +skill, plugin, and project paths. No Claude executable is needed for those +workflows. + +## Supported behavior + +| Capability | Codex support | +| --- | --- | +| Planning, reviews, TDD, debugging, verification, project artifacts | Shared skills with the Codex adapter. | +| SessionStart, PreCompact, SessionEnd | Registered in `hooks/codex.json`; require a local host with hook support and hook trust. | +| Resume and handoff without hooks | Explicit `resume-session` and `handoff-session` skills. No automatic journal. | +| Claude edit/commit quality hooks | Not registered. Follow task scope and verification explicitly; `summarize-task` owns task closure. | +| Claude Agent Teams and `agents/*.md` roles | Claude-only. `spawn-milestone-team` explains the limitation in Codex and suggests sequential execution. | +| Claude marketplace dependencies | Not installed by Codex. Optional design or gstack sibling tools must be available separately when needed. | + +This is a repository/local plugin distribution. It is not a public OpenAI +directory submission; packages with lifecycle hooks require manual local +installation. Codex subagents are not presented as an equivalent to Claude's +Agent Teams coordination. + +## Verify changes + +```bash +python3 -m unittest discover -s tests -p 'codex_plugin_test.py' -v +bin/ytstack-check +bash tests/post-tool-use-bash.test.sh +``` + +`ytstack-check` returns 2 for warnings without failures. Existing warnings +about vendored sibling skills and gstack preambles are documented separately; +they do not mean those optional tools were installed. + +The tests execute the configured hook commands against temporary projects, +exercise all six vendor imports from paths containing spaces, and verify +project/user state and journal behavior. Also use a clean Codex profile to +confirm marketplace discovery, installation, and the loaded skill list when +changing packaging. + +## Platform references + +- [OpenAI plugin packaging and marketplaces](https://developers.openai.com/plugins/build/plugins) +- [Codex hook events, trust, and payloads](https://learn.chatgpt.com/docs/hooks) +- [Claude plugin compatibility guidance](https://developers.openai.com/plugins/guides/submit-claude-plugin) diff --git a/docs/ux/skill-structure.md b/docs/ux/skill-structure.md index da3c038..cb10967 100644 --- a/docs/ux/skill-structure.md +++ b/docs/ux/skill-structure.md @@ -2,6 +2,11 @@ Every ytstack skill (`skills/*/SKILL.md`) MUST follow this structure. CI validates it. Skills that fail validation are not shipped. +Each shared skill also links to `skills/using-ytstack/references/codex.md` near +the top of its body. Codex reads that adapter before following the procedure; +Claude Code keeps its native tool and shell-injection behavior. The adapter +changes host mechanics, not the skill's artifacts or UX contract. + ## Required sections, in order ``` diff --git a/hooks/codex.json b/hooks/codex.json new file mode 100644 index 0000000..9d361d7 --- /dev/null +++ b/hooks/codex.json @@ -0,0 +1,40 @@ +{ + "description": "Codex lifecycle hooks. Claude tool and Agent Teams events stay in hooks.json.", + "hooks": { + "SessionStart": [ + { + "matcher": "startup|resume|clear|compact", + "hooks": [ + { + "type": "command", + "command": "bash \"${PLUGIN_ROOT}/bin/ytstack-codex\" hook session-start", + "timeout": 10, + "additionalContextLimit": 5000 + } + ] + } + ], + "PreCompact": [ + { + "hooks": [ + { + "type": "command", + "command": "bash \"${PLUGIN_ROOT}/bin/ytstack-codex\" hook pre-compact", + "timeout": 5 + } + ] + } + ], + "SessionEnd": [ + { + "hooks": [ + { + "type": "command", + "command": "bash \"${PLUGIN_ROOT}/bin/ytstack-codex\" hook session-end", + "timeout": 3 + } + ] + } + ] + } +} diff --git a/hooks/pre-compact b/hooks/pre-compact index 6ba63c6..f53bc4c 100644 --- a/hooks/pre-compact +++ b/hooks/pre-compact @@ -1,7 +1,7 @@ #!/usr/bin/env bash # ytstack PreCompact hook # -# Fires before Claude Code compacts the conversation. Writes a handoff snapshot +# Fires before the host compacts the conversation. Writes a handoff snapshot # to .ytstack/HANDOFF.md so the post-compaction session can re-orient quickly # from on-disk state. # @@ -50,7 +50,7 @@ active_task: ${ACTIVE_TASK:-none} # Pre-Compaction Handoff -Claude Code was about to compact the conversation at $TS. This file is a +The agent host was about to compact the conversation at $TS. This file is a breadcrumb so the next session (or post-compact continuation) can re-orient from on-disk state without relying on compressed conversation memory. diff --git a/hooks/session-start b/hooks/session-start index 0f564b1..b57baa6 100644 --- a/hooks/session-start +++ b/hooks/session-start @@ -47,7 +47,9 @@ STATE_FILE="${YT_DIR:+$YT_DIR/STATE.md}" USING_YTSTACK_FILE="${CLAUDE_PLUGIN_ROOT:-$PROJECT_DIR}/skills/using-ytstack/SKILL.md" USING_YTSTACK_CONTENT="" if [ -f "$USING_YTSTACK_FILE" ]; then - USING_YTSTACK_CONTENT=$(cat "$USING_YTSTACK_FILE") + USING_YTSTACK_CONTENT="[ytstack plugin root: $(cd "$(dirname "$USING_YTSTACK_FILE")/../.." && pwd)] + +$(cat "$USING_YTSTACK_FILE")" fi # Silent exit if the plugin itself is not available (no CLAUDE_PLUGIN_ROOT @@ -82,7 +84,7 @@ Greenfield flow per the using-ytstack directive above: 4. Then \`ytstack:init-project\` to scaffold \`.ytstack/\` (populates PROJECT.md from the pitch, no cold PM questions)." emit_json " -You have ytstack. Below is the full content of your 'ytstack:using-ytstack' skill, which tells you how to find and invoke ytstack skills based on user intent. Follow it. For all other skills, use the 'Skill' tool: +You have ytstack. Below is the full content of your 'ytstack:using-ytstack' skill, which tells you how to find and invoke ytstack skills based on user intent. Follow it. Load other skills using your host's skill-loading mechanism (see the Codex adapter when running in Codex): $USING_YTSTACK_CONTENT @@ -152,7 +154,7 @@ The files in $YT_DIR are the source of truth for this project's state. Read STAT # the specific where-we-are facts. USING_YTSTACK_CONTENT is always # non-empty here (early-exit above if missing). FULL_CONTEXT=" -You have ytstack. Below is the full content of your 'ytstack:using-ytstack' skill, which tells you how to find and invoke ytstack skills based on user intent. Follow it. For all other skills, use the 'Skill' tool: +You have ytstack. Below is the full content of your 'ytstack:using-ytstack' skill, which tells you how to find and invoke ytstack skills based on user intent. Follow it. Load other skills using your host's skill-loading mechanism (see the Codex adapter when running in Codex): $USING_YTSTACK_CONTENT diff --git a/skills/atomic-design/SKILL.md b/skills/atomic-design/SKILL.md index 05e2fb7..d221ee3 100644 --- a/skills/atomic-design/SKILL.md +++ b/skills/atomic-design/SKILL.md @@ -14,6 +14,9 @@ allowed-tools: - Grep --- +**Codex:** Before running this skill, read the [Codex adapter](../using-ytstack/references/codex.md). + + # Atomic Design -- Frontend Component Architecture Reference: Brad Frost, *Atomic Design* (https://atomicdesign.bradfrost.com/) diff --git a/skills/deutschland-stack-api/SKILL.md b/skills/deutschland-stack-api/SKILL.md index d175276..fddff51 100644 --- a/skills/deutschland-stack-api/SKILL.md +++ b/skills/deutschland-stack-api/SKILL.md @@ -10,6 +10,9 @@ allowed-tools: - WebFetch --- +**Codex:** Before running this skill, read the [Codex adapter](../using-ytstack/references/codex.md). + + # Deutschland-Stack Offizieller Technologiekatalog des Bundes für Verwaltungs-IT. 128 Open-Source-Projekte, alle graduated oder sandbox, BSI-auditiert. Quelle: . diff --git a/skills/european-alternatives-api/SKILL.md b/skills/european-alternatives-api/SKILL.md index d0412af..b32600d 100644 --- a/skills/european-alternatives-api/SKILL.md +++ b/skills/european-alternatives-api/SKILL.md @@ -10,6 +10,9 @@ allowed-tools: - WebFetch --- +**Codex:** Before running this skill, read the [Codex adapter](../using-ytstack/references/codex.md). + + # European Alternatives Community-kuratierter Katalog unter . 289 Einträge über 46 Kategorien, überwiegend DE/CH/FR/NL. Quelle: . diff --git a/skills/handoff-session/SKILL.md b/skills/handoff-session/SKILL.md index fab819b..ed37045 100644 --- a/skills/handoff-session/SKILL.md +++ b/skills/handoff-session/SKILL.md @@ -15,6 +15,9 @@ triggers: - ytstack handoff --- +**Codex:** Before running this skill, read the [Codex adapter](../using-ytstack/references/codex.md). + + # handoff-session Create a `.ytstack/HANDOFF.md` that lets the next session (yours or someone else's) pick up exactly where this one left off. This is the user-triggered equivalent of the automatic `pre-compact` hook -- but richer, because the user decides what matters. diff --git a/skills/init-project/SKILL.md b/skills/init-project/SKILL.md index 541ed71..9869b2e 100644 --- a/skills/init-project/SKILL.md +++ b/skills/init-project/SKILL.md @@ -16,6 +16,9 @@ triggers: - start ytstack project --- +**Codex:** Before running this skill, read the [Codex adapter](../using-ytstack/references/codex.md). + + # init-project Initialize a `.ytstack/` directory with the core project-memory artifacts that every other ytstack skill depends on. diff --git a/skills/office-hours/SKILL.md b/skills/office-hours/SKILL.md index f0f496e..c5791e2 100644 --- a/skills/office-hours/SKILL.md +++ b/skills/office-hours/SKILL.md @@ -8,6 +8,9 @@ allowed-tools: - AskUserQuestion --- +**Codex:** Before running this skill, read the [Codex adapter](../using-ytstack/references/codex.md). + + # ytstack:office-hours Thin wrapper around the vendored gstack `office-hours` skill (`vendor/gstack/office-hours/SKILL.md`). Runs the six forcing questions verbatim, then writes the outcome as a ytstack artifact. Vendor is single source of truth. diff --git a/skills/oss-project/SKILL.md b/skills/oss-project/SKILL.md index c847ae9..8b252bc 100644 --- a/skills/oss-project/SKILL.md +++ b/skills/oss-project/SKILL.md @@ -18,6 +18,9 @@ allowed-tools: - Glob --- +**Codex:** Before running this skill, read the [Codex adapter](../using-ytstack/references/codex.md). + + # OSS Project Bootstrap, polish, and publish open-source projects with AI-first documentation diff --git a/skills/plan-ceo-review/SKILL.md b/skills/plan-ceo-review/SKILL.md index 9b457d7..19b0aec 100644 --- a/skills/plan-ceo-review/SKILL.md +++ b/skills/plan-ceo-review/SKILL.md @@ -8,6 +8,9 @@ allowed-tools: - AskUserQuestion --- +**Codex:** Before running this skill, read the [Codex adapter](../using-ytstack/references/codex.md). + + # ytstack:plan-ceo-review Thin wrapper around the vendored gstack `plan-ceo-review` skill (`vendor/gstack/plan-ceo-review/SKILL.md`). Injects ytstack milestone context, then inlines the vendored procedure verbatim so it runs without adapt / fork drift. Vendor is single source of truth; upstream updates flow through. diff --git a/skills/plan-eng-review/SKILL.md b/skills/plan-eng-review/SKILL.md index c9150db..ca3d0c7 100644 --- a/skills/plan-eng-review/SKILL.md +++ b/skills/plan-eng-review/SKILL.md @@ -8,6 +8,9 @@ allowed-tools: - AskUserQuestion --- +**Codex:** Before running this skill, read the [Codex adapter](../using-ytstack/references/codex.md). + + # ytstack:plan-eng-review Thin wrapper around the vendored gstack `plan-eng-review` skill (`vendor/gstack/plan-eng-review/SKILL.md`). Injects ytstack slice-plan context, then inlines the vendored procedure verbatim. Vendor is single source of truth. diff --git a/skills/plan-milestone/SKILL.md b/skills/plan-milestone/SKILL.md index 8ba195c..f7e9342 100644 --- a/skills/plan-milestone/SKILL.md +++ b/skills/plan-milestone/SKILL.md @@ -16,6 +16,9 @@ triggers: - define milestone --- +**Codex:** Before running this skill, read the [Codex adapter](../using-ytstack/references/codex.md). + + # plan-milestone Define the next milestone for a ytstack project: goal, exit criteria, rough size. Produces a milestone roadmap skeleton; slice-level detail comes from the next skill (`slice-milestone`). diff --git a/skills/plan-task/SKILL.md b/skills/plan-task/SKILL.md index 16a1fc9..d58b50d 100644 --- a/skills/plan-task/SKILL.md +++ b/skills/plan-task/SKILL.md @@ -15,6 +15,9 @@ triggers: - ytstack plan task --- +**Codex:** Before running this skill, read the [Codex adapter](../using-ytstack/references/codex.md). + + # plan-task Take one task from the current slice's task list and turn it into a concrete plan: which files to touch, what code to write, how to verify. Output lives at `M###-S##-T##-PLAN.md`. diff --git a/skills/reassess-roadmap/SKILL.md b/skills/reassess-roadmap/SKILL.md index 676f706..5050fe2 100644 --- a/skills/reassess-roadmap/SKILL.md +++ b/skills/reassess-roadmap/SKILL.md @@ -15,6 +15,9 @@ triggers: - ytstack reassess --- +**Codex:** Before running this skill, read the [Codex adapter](../using-ytstack/references/codex.md). + + # reassess-roadmap Post-slice checkpoint. Looks at what was learned during the slice and asks whether the milestone roadmap still matches reality. Often the answer is yes -- but when it's no, catching it here is cheap; catching it at milestone-end is expensive. diff --git a/skills/resume-session/SKILL.md b/skills/resume-session/SKILL.md index 71e2e06..ba213ed 100644 --- a/skills/resume-session/SKILL.md +++ b/skills/resume-session/SKILL.md @@ -13,6 +13,9 @@ triggers: - ytstack resume --- +**Codex:** Before running this skill, read the [Codex adapter](../using-ytstack/references/codex.md). + + # resume-session Quick-orient at session start. Reads the on-disk state and produces a 3-paragraph briefing the user can scan in 10 seconds. Complements the SessionStart hook (which auto-injects on every session start) by giving the user an on-demand, richer version. diff --git a/skills/slice-milestone/SKILL.md b/skills/slice-milestone/SKILL.md index c93dd98..e54edbd 100644 --- a/skills/slice-milestone/SKILL.md +++ b/skills/slice-milestone/SKILL.md @@ -16,6 +16,9 @@ triggers: - ytstack slice --- +**Codex:** Before running this skill, read the [Codex adapter](../using-ytstack/references/codex.md). + + # slice-milestone Break an existing milestone into concrete slices. Each slice gets a goal + 1-7 tasks. Writes one `M###-S##-PLAN.md` per slice. diff --git a/skills/software-craftsmanship/SKILL.md b/skills/software-craftsmanship/SKILL.md index 68a5a94..059b05a 100644 --- a/skills/software-craftsmanship/SKILL.md +++ b/skills/software-craftsmanship/SKILL.md @@ -11,6 +11,9 @@ allowed-tools: - Bash --- +**Codex:** Before running this skill, read the [Codex adapter](../using-ytstack/references/codex.md). + + # Software Craftsmanship Skill 🛠️✨ > **Clean Code. Robust Architecture. No Test Slop.** diff --git a/skills/spawn-milestone-team/SKILL.md b/skills/spawn-milestone-team/SKILL.md index 17cbd67..70af5aa 100644 --- a/skills/spawn-milestone-team/SKILL.md +++ b/skills/spawn-milestone-team/SKILL.md @@ -15,6 +15,9 @@ triggers: - ytstack team --- +**Codex:** Before running this skill, read the [Codex adapter](../using-ytstack/references/codex.md). + + # spawn-milestone-team Ask Claude Code to create an Agent Team for the current milestone. Each teammate gets a slice. Teammates work in parallel with isolated contexts, coordinate via the shared task list. This is ytstack's answer to GSD's fresh-subprocess-per-task model -- we use Claude Code's native Agent Teams feature instead of building our own runtime. diff --git a/skills/summarize-task/SKILL.md b/skills/summarize-task/SKILL.md index dffb28b..0b5b633 100644 --- a/skills/summarize-task/SKILL.md +++ b/skills/summarize-task/SKILL.md @@ -16,6 +16,9 @@ triggers: - close task --- +**Codex:** Before running this skill, read the [Codex adapter](../using-ytstack/references/codex.md). + + # summarize-task Close a task: capture what actually happened (often different from the plan), flip checkboxes, advance STATE.md. Produces an append-to-history artifact future sessions can read. diff --git a/skills/systematic-debugging/SKILL.md b/skills/systematic-debugging/SKILL.md index e533ece..b20c436 100644 --- a/skills/systematic-debugging/SKILL.md +++ b/skills/systematic-debugging/SKILL.md @@ -9,6 +9,9 @@ allowed-tools: - AskUserQuestion --- +**Codex:** Before running this skill, read the [Codex adapter](../using-ytstack/references/codex.md). + + # ytstack:systematic-debugging Thin wrapper around the vendored superpowers `systematic-debugging` skill (`vendor/superpowers/skills/systematic-debugging/SKILL.md`). Injects ytstack project context, then inlines the vendored procedure verbatim. Findings are routed into ytstack artifacts. Vendor is single source of truth. diff --git a/skills/test-driven-development/SKILL.md b/skills/test-driven-development/SKILL.md index d51baaa..4b9d8cf 100644 --- a/skills/test-driven-development/SKILL.md +++ b/skills/test-driven-development/SKILL.md @@ -9,6 +9,9 @@ allowed-tools: - AskUserQuestion --- +**Codex:** Before running this skill, read the [Codex adapter](../using-ytstack/references/codex.md). + + # ytstack:test-driven-development Thin wrapper around the vendored superpowers `test-driven-development` skill (`vendor/superpowers/skills/test-driven-development/SKILL.md`). Injects the current task's plan (Files + Verification sections) as context, then inlines the vendored procedure verbatim. Vendor is single source of truth. diff --git a/skills/using-ytstack/SKILL.md b/skills/using-ytstack/SKILL.md index a60737c..98f1052 100644 --- a/skills/using-ytstack/SKILL.md +++ b/skills/using-ytstack/SKILL.md @@ -1,22 +1,24 @@ --- name: using-ytstack -description: "Establishes how agents find and invoke ytstack skills. Requires Skill-tool invocation before any response when a ytstack skill applies. Auto-injected by the SessionStart hook. Not meant for direct user invocation." +description: "Establishes how agents find and invoke ytstack skills. Use at session start or when selecting a ytstack workflow. Loaded by the SessionStart hook when available; can be loaded explicitly in Codex." tier: core -version: 0.2.1 +version: 0.2.2 kind: directive user-invocable: false --- # Using ytstack +**In Codex:** Read [the Codex adapter](references/codex.md) before continuing. It defines skill loading, shell execution, question tools, and the supported lifecycle hooks. + If you were dispatched as an Agent Teams teammate to execute a specific slice or task, skip this skill -- your scope is bounded by the task plan the lead gave you. -This project uses ytstack. A hook has injected project state into your context and you have access to ytstack's skills via the `Skill` tool. +This project uses ytstack. The SessionStart hook supplies project state when available. Load skills with Claude Code's `Skill` tool or Codex's skill-loading mechanism described in the adapter. -If you think there is even a 1% chance a ytstack skill applies to what the user asked, you ABSOLUTELY MUST invoke the skill via the `Skill` tool BEFORE any other action. +If you think there is even a 1% chance a ytstack skill applies to what the user asked, you ABSOLUTELY MUST load the skill using your host's skill-loading mechanism BEFORE any other action. IF A YTSTACK SKILL APPLIES, YOU DO NOT HAVE A CHOICE. USE IT. @@ -27,13 +29,13 @@ This is not negotiable. This is not optional. You cannot rationalize your way ou User instructions always take precedence: -1. **User's explicit instructions** (`CLAUDE.md`, direct messages) -- highest priority. If the user says "skip ytstack for this change", honor it. +1. **User's explicit instructions** (`AGENTS.md`, `CLAUDE.md`, direct messages) -- highest priority. If the user says "skip ytstack for this change", honor it. 2. **ytstack skills** -- override default agent behavior when they apply. 3. **Default agent behavior** -- lowest priority. ## The rule -**Invoke the relevant ytstack skill BEFORE any response or action.** Each skill's frontmatter `description` field states when to use it. Match the user's intent against the descriptions semantically -- not against keyword lists or canonical phrasings. If any skill's description plausibly covers the situation, invoke it via the `Skill` tool to confirm. If the invoked skill turns out to be the wrong fit, that is fine; what is NOT fine is skipping the check. +**Invoke the relevant ytstack skill BEFORE any response or action.** Each skill's frontmatter `description` field states when to use it. Match the user's intent against the descriptions semantically -- not against keyword lists or canonical phrasings. If any skill's description plausibly covers the situation, load it using your host's skill-loading mechanism to confirm. If the invoked skill turns out to be the wrong fit, that is fine; what is NOT fine is skipping the check. Skills you might need (consult each skill's description, not this list, for the actual when-to-use): @@ -74,22 +76,22 @@ digraph using_ytstack { "User message received" [shape=doublecircle]; "Match intent against skill descriptions" [shape=box]; "Any skill plausibly applies?" [shape=diamond]; - "Invoke Skill tool" [shape=box]; + "Load selected skill" [shape=box]; "Follow skill exactly" [shape=box]; "Respond directly" [shape=doublecircle]; "User message received" -> "Match intent against skill descriptions"; "Match intent against skill descriptions" -> "Any skill plausibly applies?"; - "Any skill plausibly applies?" -> "Invoke Skill tool" [label="yes, even 1%"]; + "Any skill plausibly applies?" -> "Load selected skill" [label="yes, even 1%"]; "Any skill plausibly applies?" -> "Respond directly" [label="definitely not"]; - "Invoke Skill tool" -> "Follow skill exactly"; + "Load selected skill" -> "Follow skill exactly"; "Follow skill exactly" -> "Respond directly"; } ``` ## After invoking a skill -Follow its Checklist exactly. Create TodoWrite entries per item. Don't improvise steps. The skill's Terminal State section tells you what to do next -- respect it; don't auto-invoke the suggested follow-up skill unless the user explicitly asks for it. +Follow its Checklist exactly. Track each item with TodoWrite in Claude Code, or the available plan/checklist mechanism in Codex. Don't improvise steps. The skill's Terminal State section tells you what to do next -- respect it; don't auto-invoke the suggested follow-up skill unless the user explicitly asks for it. ## Reporting ytstack issues diff --git a/skills/using-ytstack/references/codex.md b/skills/using-ytstack/references/codex.md new file mode 100644 index 0000000..50a6e45 --- /dev/null +++ b/skills/using-ytstack/references/codex.md @@ -0,0 +1,85 @@ +# Running ytstack skills in Codex + +Read this adapter before following a ytstack skill in Codex. It applies to +the shared skills and their vendored procedures; Claude Code keeps its native +tool and shell-injection behavior. + +## Load skills and context + +Use Codex's skill loader when available. Otherwise read the selected +`skills//SKILL.md` from this installed plugin and follow it. A reference +to `ytstack:` or `/ytstack:` means that skill, not a shell command. +Users can select it with `$ytstack:` in Codex's skill picker; choose the entry +from ytstack if another plugin provides the same name. + +The plugin root is two directories above the selected `SKILL.md` directory. +For this adapter it is three directories above `references/`. Resolve it from +the loaded file's actual location, not the project checkout or a guessed cache +version. `hooks/session-start` also reports the installed plugin root. + +If the SessionStart hook did not provide project context, read the +`using-ytstack` skill and the project's `.ytstack/STATE.md`, `DECISIONS.md`, and +`KNOWLEDGE.md` when present. If project state is absent, check +`~/.ytstack/projects//`. No state does not prove an existing +codebase is a new project; follow the user's requested scope. + +## Run shell blocks explicitly + +Codex does not evaluate the shared skills' fenced `!` blocks as Claude dynamic +context. Execute those blocks explicitly, in order, using Bash from the target +project directory. Honor each hard gate before loading its vendored procedure. +Run ordinary Bash preambles the same way. Each block is a separate process; +use its printed values in subsequent prose steps. + +For every block, use the bundled helper with the current skill name: + +```bash +bash "/absolute/plugin/root/bin/ytstack-codex" skill plan-task <<'YTSTACK_BASH' +# Paste this skill's shell block here, without the Markdown fence. +YTSTACK_BASH +``` + +The helper derives `CLAUDE_PLUGIN_ROOT` and `CLAUDE_SKILL_DIR` from its own +installed location and sets `CLAUDE_PROJECT_DIR` to the command's working +directory. These names are compatibility inputs for the shared files, not a +requirement to install Claude. Use the helper again for every shell call that +needs them; exports from a previous tool call do not persist. + +Read the entire output of the wrapper's vendor `cat` block, including any +referenced files needed for the task. Vendor files remain unchanged. Keep +ytstack's invocation notes, artifact paths, and terminal-state rules around +the upstream procedure. Missing gstack services or sibling skills are not +installed by this plugin; report an essential missing capability instead of +inventing a tool or installing a dependency silently. + +## Map tools to the host + +| Shared instruction | Codex behavior | +| --- | --- | +| `Skill` | Load/read the selected skill as described above. | +| `Bash`, `Read`, `Write`, `Edit`, `Grep`, `Glob` | Use available shell, file, patch, and search tools. | +| `TodoWrite` / checklist tasks | Use the host's plan tool if available, otherwise track the checklist in the conversation. | +| `AskUserQuestion` | Use an available user-input tool in its supported mode, otherwise ask in chat and wait. Keep the skill's question format. | +| `allowed-tools` / Claude-specific frontmatter | Capability hints, never grants of tool access or permission in Codex. | + +A missing question tool does not imply non-interactive mode or consent. +Honor authorization already given in the conversation. User instructions and +the host's tool, approval, and sandbox rules take precedence over this adapter +and any shared or vendored skill instructions. + +## Lifecycle and teams + +Codex's manifest selects only SessionStart, PreCompact, and SessionEnd hooks. +They need a local host with hook support and explicit hook trust. Without +trusted hooks, use `resume-session` at startup and `handoff-session` before +leaving; do not claim automatic context injection or journaling ran. + +Claude's edit/commit hooks and Agent Teams events are not registered in Codex. +Check task scope before editing, run the verification command, and use +`summarize-task` to close work; no automatic draft summary is created on commit. + +`spawn-milestone-team` remains a Claude Code workflow. In Codex, stop before its +Claude capability probe or spawn procedure and explain that limitation. +Suggest the sequential `plan-task` -> implementation -> +`verification-before-completion` -> `summarize-task` flow. Do not pretend Codex +subagents provide Claude's shared team/task hooks or start another host. diff --git a/skills/verification-before-completion/SKILL.md b/skills/verification-before-completion/SKILL.md index a6c92ca..9381e83 100644 --- a/skills/verification-before-completion/SKILL.md +++ b/skills/verification-before-completion/SKILL.md @@ -7,6 +7,9 @@ allowed-tools: - AskUserQuestion --- +**Codex:** Before running this skill, read the [Codex adapter](../using-ytstack/references/codex.md). + + # ytstack:verification-before-completion Thin wrapper around the vendored superpowers `verification-before-completion` skill (`vendor/superpowers/skills/verification-before-completion/SKILL.md`). Injects the current task's verification command extracted from the task-plan, then inlines the vendored procedure verbatim. Vendor is single source of truth. diff --git a/tests/codex_plugin_test.py b/tests/codex_plugin_test.py new file mode 100644 index 0000000..47c7905 --- /dev/null +++ b/tests/codex_plugin_test.py @@ -0,0 +1,195 @@ +"""Codex packaging and shared-skill integration, without an LLM or user config.""" + +import json +import os +from pathlib import Path +import re +import shutil +import subprocess +import tempfile +import unittest + + +ROOT = Path(__file__).resolve().parents[1] + + +class CodexPluginTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.package_temp = tempfile.TemporaryDirectory(prefix="ytstack package ") + cls.package = Path(cls.package_temp.name).resolve() / "installed plugin" + cls.package.mkdir() + for name in (".codex-plugin", ".agents", "bin", "hooks", "skills"): + shutil.copytree(ROOT / name, cls.package / name) + # Copy exactly the vendor files that the shared wrappers import. + for skill in (ROOT / "skills").glob("*/SKILL.md"): + for relative in re.findall(r"vendor/[\w/-]+/SKILL\.md", skill.read_text()): + target = cls.package / relative + target.parent.mkdir(parents=True, exist_ok=True) + shutil.copyfile(ROOT / relative, target) + + @classmethod + def tearDownClass(cls): + cls.package_temp.cleanup() + + def setUp(self): + self.sandbox = tempfile.TemporaryDirectory(prefix="ytstack project ") + self.addCleanup(self.sandbox.cleanup) + self.project = Path(self.sandbox.name).resolve() / "project with spaces" + self.project.mkdir() + self.user_dir = Path(self.sandbox.name) / "test user" + self.user_dir.mkdir() + self.env = {key: value for key, value in os.environ.items() + if not key.startswith(("CLAUDE_", "YTSTACK_", "PLUGIN_"))} + # Isolate the hooks' supported user-scope lookup from the real account. + self.env["HOME"] = str(self.user_dir) + self.env["PLUGIN_ROOT"] = str(self.package) + self.helper = self.package / "bin/ytstack-codex" + + def run_skill(self, name, script, **kwargs): + return subprocess.run( + ["bash", str(self.helper), "skill", name], input=script, + cwd=self.project, env=self.env, text=True, capture_output=True, + **kwargs, + ) + + def fire_hook(self, event): + config = json.loads((self.package / "hooks/codex.json").read_text()) + handlers = config["hooks"][event][0]["hooks"] + self.assertEqual(len(handlers), 1) + payload = { + "session_id": 'codex-session-"quoted"', + "cwd": str(self.project), "hook_event_name": event, + "source": "startup", "reason": "other", + } + result = subprocess.run( + ["bash", "-c", handlers[0]["command"]], + input=json.dumps(payload), cwd=self.project, env=self.env, + text=True, capture_output=True, timeout=handlers[0]["timeout"], + ) + self.assertEqual(result.returncode, 0, result.stderr) + return result.stdout + + def write_state(self, user_scope=False): + if user_scope: + directory = self.user_dir / ".ytstack/projects" / self.project.name + else: + directory = self.project / ".ytstack" + directory.mkdir(parents=True) + (directory / "STATE.md").write_text( + "---\nproject: sample\ncurrent_milestone: M123\n" + "active_slice: S02\nactive_task: T03\n---\n\n" + "## Next action\nVerify the import.\n" + ) + return directory + + def test_manifests_resolve_same_package_and_skills(self): + manifest = json.loads((self.package / ".codex-plugin/plugin.json").read_text()) + claude = json.loads((ROOT / ".claude-plugin/plugin.json").read_text()) + for field in ("name", "version", "author", "repository", "license"): + self.assertEqual(manifest[field], claude[field], field) + marketplace = json.loads((self.package / ".agents/plugins/marketplace.json").read_text()) + entry = marketplace["plugins"][0] + self.assertEqual(entry["name"], manifest["name"]) + self.assertEqual((self.package / entry["source"]["path"]).resolve(), self.package) + skill_paths = sorted((self.package / manifest["skills"]).glob("*/SKILL.md")) + self.assertEqual([p.parent.name for p in skill_paths], + sorted(p.parent.name for p in (ROOT / "skills").glob("*/SKILL.md"))) + for skill in skill_paths: + with self.subTest(skill=skill.parent.name): + references = re.findall(r"\]\(([^)]+codex\.md)\)", skill.read_text()) + self.assertTrue(references, "Direct skill invocation must reach the adapter") + for reference in references: + self.assertTrue((skill.parent / reference).is_file(), reference) + hooks = json.loads((self.package / manifest["hooks"]).read_text())["hooks"] + self.assertEqual(set(hooks), {"SessionStart", "PreCompact", "SessionEnd"}) + self.assertLessEqual(hooks["SessionEnd"][0]["hooks"][0]["timeout"], 3) + + def test_helper_uses_installed_skill_and_target_project(self): + self.env.update(CLAUDE_PLUGIN_ROOT="/wrong-plugin", CLAUDE_PROJECT_DIR="/wrong-project", + CLAUDE_SKILL_DIR="/wrong-skill") + result = self.run_skill("plan-task", 'printf "%s\\n" "$CLAUDE_PLUGIN_ROOT" "$CLAUDE_SKILL_DIR" "$CLAUDE_PROJECT_DIR" "$YTSTACK_HOST"\n') + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout.splitlines(), [ + str(self.package), str(self.package / "skills/plan-task"), + str(self.project), "codex", + ]) + + def test_wrapper_preambles_and_vendor_imports(self): + self.write_state() + imported = [] + for skill in (self.package / "skills").glob("*/SKILL.md"): + blocks = re.findall(r"^```!\n(.*?)^```", skill.read_text(), re.M | re.S) + if not blocks: + continue + with self.subTest(skill=skill.parent.name): + context = self.run_skill(skill.parent.name, blocks[0]) + self.assertEqual(context.returncode, 0, context.stderr) + self.assertIn("HAS_YTSTACK: yes", context.stdout) + vendor = re.search(r"vendor/[\w/-]+/SKILL\.md", blocks[-1]).group() + result = self.run_skill(skill.parent.name, blocks[-1]) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout, (ROOT / vendor).read_text()) + imported.append(skill.parent.name) + self.assertEqual(len(imported), 6) + + def test_helper_rejects_missing_skill_and_path_traversal(self): + for name in ("missing-skill", "../using-ytstack", "/tmp/skill"): + with self.subTest(name=name): + result = self.run_skill(name, "echo should-not-run") + self.assertEqual(result.returncode, 2) + self.assertNotIn("should-not-run", result.stdout) + + def test_helper_propagates_failure(self): + self.assertEqual(self.run_skill("plan-task", "exit 7").returncode, 7) + + def test_startup_greenfield_and_missing_state(self): + output = json.loads(self.fire_hook("SessionStart"))["hookSpecificOutput"] + self.assertEqual(output["hookEventName"], "SessionStart") + self.assertIn("scope: greenfield", output["additionalContext"]) + self.assertIn(str(self.package), output["additionalContext"]) + self.assertIn("references/codex.md", output["additionalContext"]) + self.assertFalse((self.project / ".ytstack").exists()) + (self.project / ".ytstack").mkdir() + output = json.loads(self.fire_hook("SessionStart"))["hookSpecificOutput"] + self.assertIn("STATE.md is missing", output["additionalContext"]) + + def test_lifecycle_preserves_state_and_records_session(self): + for user_scope in (False, True): + with self.subTest(user_scope=user_scope): + directory = self.write_state(user_scope=user_scope) + state = (directory / "STATE.md").read_bytes() + output = json.loads(self.fire_hook("SessionStart"))["hookSpecificOutput"] + self.assertIn("scope: user" if user_scope else "scope: project", output["additionalContext"]) + self.assertIn("M123 / S02 / T03", output["additionalContext"]) + self.assertIn("Verify the import.", output["additionalContext"]) + self.assertEqual(self.fire_hook("PreCompact"), "") + handoff = (directory / "HANDOFF.md").read_text() + self.assertIn("current_milestone: M123", handoff) + self.assertNotIn("Claude Code was about", handoff) + self.assertEqual(self.fire_hook("SessionEnd"), "") + entry = json.loads((directory / "journal/sessions.jsonl").read_text()) + self.assertEqual(entry["session_id"], 'codex-session-"quoted"') + self.assertEqual(entry["task"], "T03") + self.assertEqual((directory / "STATE.md").read_bytes(), state) + shutil.rmtree(directory) + + def test_side_effect_hooks_noop_without_state(self): + self.assertEqual(self.fire_hook("PreCompact"), "") + self.assertEqual(self.fire_hook("SessionEnd"), "") + self.assertFalse((self.project / ".ytstack").exists()) + + def test_claude_startup_still_works(self): + self.write_state() + self.env.update(CLAUDE_PLUGIN_ROOT=str(self.package), CLAUDE_PROJECT_DIR=str(self.project)) + result = subprocess.run( + ["bash", str(self.package / "hooks/session-start")], + cwd=self.project, env=self.env, text=True, capture_output=True, check=True, + ) + context = json.loads(result.stdout)["hookSpecificOutput"]["additionalContext"] + self.assertIn("M123 / S02 / T03", context) + self.assertIn("Claude Code's `Skill` tool", context) + + +if __name__ == "__main__": + unittest.main() From f51964726eeffaa4c607a84d92b97fe0f866cbe7 Mon Sep 17 00:00:00 2001 From: Alex Wegener Date: Tue, 13 Oct 2026 11:17:33 +0200 Subject: [PATCH 2/4] fix: resolve Codex project root from repository subdirectories --- .ytstack/STATE.md | 4 ++- bin/ytstack-codex | 4 ++- skills/using-ytstack/references/codex.md | 4 +-- tests/codex_plugin_test.py | 31 ++++++++++++++++++++---- 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/.ytstack/STATE.md b/.ytstack/STATE.md index df4a4ca..4e943f7 100644 --- a/.ytstack/STATE.md +++ b/.ytstack/STATE.md @@ -1,7 +1,7 @@ --- project: ytstack slug: ytstack -last_updated: 2026-10-13T08:59:49Z +last_updated: 2026-10-13T09:17:17Z current_milestone: M015 active_slice: none active_task: none @@ -9,6 +9,8 @@ active_task: none # State +**PR #23 review follow-up (2026-10-13):** Fixed repository-subdirectory startup in the Codex helper. All lifecycle hooks now find repository-root state; the regression covers project and user scope, handoff, journaling, and unchanged STATE.md. Ten integration tests pass. Host separation is under revision following maintainer feedback; this entry records the independent path fix. + **Status (2026-10-13, Codex compatibility PR):** Added a Codex plugin manifest and self-marketplace, a shared host adapter linked from all 21 skills, and an explicit 3-event lifecycle hook configuration. Existing Claude manifests and vendor files are unchanged. Codex CLI 0.162.1 installs/enables the package in a separate temporary profile; app-server discovery loads all 21 namespaced skills and 3 hooks with no errors or warnings. Hooks correctly remain untrusted until reviewed. Nine Codex integration tests and ten existing commit-hook checks pass; `ytstack-check` has the same 109 warnings as the main-branch baseline, no failures. Native Claude Agent Teams and edit/commit hooks remain outside Codex support (see `docs/codex.md`). Awaiting PR review; no release or user-profile installation performed. **Status (2026-05-31, vendor drift audit + superpowers pull):** Audited both vendored subtrees for upstream breaking changes against the ytstack wrapper contract (wrappers inline the six wrapped SKILL.md verbatim via `` ```!cat ``; breaking surface = path-rename = silent empty `cat`, or procedure rewrite). **superpowers PULLED** `6efe32c -> 6fd4507`: all three wrapped SKILL.md (TDD, systematic-debugging, verification-before-completion) byte-identical upstream, zero ytstack-behavior change; only drumherum moved (README, tests, using-git-worktrees, finishing-a-development-branch). Merge `ca6675e` / squash `ad14900`; `bin/ytstack-check` = 0 fails (exit 2 = warnings-only). **NOTE: ca6675e is already on origin/main (pushed)** -- the push went out without an explicit push in-conversation (auto-mechanism or the still-present `thirsty-bhabha-a03490` worktree); flagged to the human. **gstack NOT pulled**, held at `aeea57f` (upstream `3bef43b` is a v1 restructure, ~1000 changed lines/file): methodology anchors all survive (no hard break), but a net-new `gbrain:` frontmatter `context_queries` block + `GSTACK_PLAN_MODE` is surface NOT covered by DECISIONS 2026-04-25 "Vendored-preamble drift accepted" (that decision only covers `|| true` shell, not frontmatter) -- revisit that decision before pulling gstack. KNOWLEDGE gotchas added (drift-audit method + gbrain). Hold-gstack is deliberately NOT yet a locked DECISIONS entry (left as a STATE/KNOWLEDGE fact pending the gbrain-strategy call). No version bump for the vendor refresh -- decided 2026-05-31, no ytstack-behavior change so it stays 0.1.6. diff --git a/bin/ytstack-codex b/bin/ytstack-codex index b27facf..af27b37 100755 --- a/bin/ytstack-codex +++ b/bin/ytstack-codex @@ -3,7 +3,9 @@ set -euo pipefail export CLAUDE_PLUGIN_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -export CLAUDE_PROJECT_DIR="$PWD" +PROJECT_DIR=$(git rev-parse --show-toplevel 2>/dev/null) || PROJECT_DIR="$PWD" +export CLAUDE_PROJECT_DIR="$PROJECT_DIR" +cd "$PROJECT_DIR" export YTSTACK_HOST=codex usage() { diff --git a/skills/using-ytstack/references/codex.md b/skills/using-ytstack/references/codex.md index 50a6e45..28c0059 100644 --- a/skills/using-ytstack/references/codex.md +++ b/skills/using-ytstack/references/codex.md @@ -40,8 +40,8 @@ YTSTACK_BASH ``` The helper derives `CLAUDE_PLUGIN_ROOT` and `CLAUDE_SKILL_DIR` from its own -installed location and sets `CLAUDE_PROJECT_DIR` to the command's working -directory. These names are compatibility inputs for the shared files, not a +installed location and sets `CLAUDE_PROJECT_DIR` to the Git repository root, +falling back to the working directory outside Git. It runs the block there. These names are compatibility inputs for the shared files, not a requirement to install Claude. Use the helper again for every shell call that needs them; exports from a previous tool call do not persist. diff --git a/tests/codex_plugin_test.py b/tests/codex_plugin_test.py index 47c7905..1f53b45 100644 --- a/tests/codex_plugin_test.py +++ b/tests/codex_plugin_test.py @@ -46,25 +46,26 @@ def setUp(self): self.env["PLUGIN_ROOT"] = str(self.package) self.helper = self.package / "bin/ytstack-codex" - def run_skill(self, name, script, **kwargs): + def run_skill(self, name, script, cwd=None, **kwargs): return subprocess.run( ["bash", str(self.helper), "skill", name], input=script, - cwd=self.project, env=self.env, text=True, capture_output=True, + cwd=cwd or self.project, env=self.env, text=True, capture_output=True, **kwargs, ) - def fire_hook(self, event): + def fire_hook(self, event, cwd=None): + cwd = cwd or self.project config = json.loads((self.package / "hooks/codex.json").read_text()) handlers = config["hooks"][event][0]["hooks"] self.assertEqual(len(handlers), 1) payload = { "session_id": 'codex-session-"quoted"', - "cwd": str(self.project), "hook_event_name": event, + "cwd": str(cwd), "hook_event_name": event, "source": "startup", "reason": "other", } result = subprocess.run( ["bash", "-c", handlers[0]["command"]], - input=json.dumps(payload), cwd=self.project, env=self.env, + input=json.dumps(payload), cwd=cwd, env=self.env, text=True, capture_output=True, timeout=handlers[0]["timeout"], ) self.assertEqual(result.returncode, 0, result.stderr) @@ -179,6 +180,26 @@ def test_side_effect_hooks_noop_without_state(self): self.assertEqual(self.fire_hook("SessionEnd"), "") self.assertFalse((self.project / ".ytstack").exists()) + def test_lifecycle_from_repository_subdirectory(self): + subprocess.run(["git", "init", "-q", str(self.project)], check=True, env=self.env) + nested = self.project / "src/nested directory" + nested.mkdir(parents=True) + for user_scope in (False, True): + with self.subTest(user_scope=user_scope): + directory = self.write_state(user_scope=user_scope) + state = (directory / "STATE.md").read_bytes() + context = json.loads(self.fire_hook("SessionStart", cwd=nested))["hookSpecificOutput"]["additionalContext"] + self.assertIn("scope: user" if user_scope else "scope: project", context) + self.assertIn("M123 / S02 / T03", context) + self.fire_hook("PreCompact", cwd=nested) + self.assertIn("current_milestone: M123", (directory / "HANDOFF.md").read_text()) + self.fire_hook("SessionEnd", cwd=nested) + entry = json.loads((directory / "journal/sessions.jsonl").read_text()) + self.assertEqual(entry["task"], "T03") + self.assertEqual((directory / "STATE.md").read_bytes(), state) + self.assertFalse((nested / ".ytstack").exists()) + shutil.rmtree(directory) + def test_claude_startup_still_works(self): self.write_state() self.env.update(CLAUDE_PLUGIN_ROOT=str(self.package), CLAUDE_PROJECT_DIR=str(self.project)) From 0696406c981674c2d94bd457f32005b8e10b8edc Mon Sep 17 00:00:00 2001 From: Alex Wegener Date: Tue, 13 Oct 2026 11:26:29 +0200 Subject: [PATCH 3/4] fix: generate a separate Codex package from unchanged Claude sources --- .codex-plugin/plugin.json | 4 +- .gitattributes | 2 + .github/workflows/plugin-check.yml | 4 +- .ytstack/DECISIONS.md | 19 + .ytstack/REVIEW-NOTES.md | 4 +- .ytstack/STATE.md | 4 +- AGENTS.md | 2 +- CONTRIBUTING.md | 11 +- README.md | 6 +- bin/build-codex | 123 ++++++ bin/ytstack-codex | 12 +- .../references/codex.md => codex/adapter.md | 41 +- hooks/codex.json => codex/hooks.json | 2 +- codex/hooks/pre-compact | 76 ++++ codex/hooks/session-end | 80 ++++ codex/hooks/session-start | 168 ++++++++ codex/skills/atomic-design/SKILL.md | 186 +++++++++ codex/skills/deutschland-stack-api/SKILL.md | 120 ++++++ .../skills/european-alternatives-api/SKILL.md | 108 +++++ codex/skills/handoff-session/SKILL.md | 164 ++++++++ codex/skills/init-project/SKILL.md | 383 ++++++++++++++++++ codex/skills/office-hours/SKILL.md | 56 +++ codex/skills/oss-project/SKILL.md | 332 +++++++++++++++ .../.github/ISSUE_TEMPLATE/bug_report.md | 39 ++ .../.github/ISSUE_TEMPLATE/feature_request.md | 23 ++ .../.github/pull_request_template.md | 37 ++ .../templates/.github/workflows/ci.yml | 57 +++ .../templates/.github/workflows/release.yml | 43 ++ .../oss-project/templates/AGENTS.md.tmpl | 66 +++ .../oss-project/templates/CONTEXT.md.tmpl | 15 + .../oss-project/templates/DECISIONS.md.tmpl | 44 ++ .../oss-project/templates/README.md.tmpl | 102 +++++ .../oss-project/templates/ROADMAP.md.tmpl | 44 ++ codex/skills/plan-ceo-review/SKILL.md | 75 ++++ codex/skills/plan-eng-review/SKILL.md | 74 ++++ codex/skills/plan-milestone/SKILL.md | 308 ++++++++++++++ codex/skills/plan-task/SKILL.md | 259 ++++++++++++ codex/skills/reassess-roadmap/SKILL.md | 202 +++++++++ codex/skills/resume-session/SKILL.md | 107 +++++ codex/skills/slice-milestone/SKILL.md | 238 +++++++++++ codex/skills/software-craftsmanship/SKILL.md | 94 +++++ .../references/ccd-principles.md | 134 ++++++ codex/skills/spawn-milestone-team/SKILL.md | 41 ++ codex/skills/summarize-task/SKILL.md | 289 +++++++++++++ codex/skills/systematic-debugging/SKILL.md | 54 +++ codex/skills/test-driven-development/SKILL.md | 55 +++ codex/skills/using-ytstack/SKILL.md | 100 +++++ .../verification-before-completion/SKILL.md | 65 +++ docs/codex.md | 39 +- docs/ux/skill-structure.md | 5 - hooks/pre-compact | 4 +- hooks/session-start | 8 +- skills/atomic-design/SKILL.md | 3 - skills/deutschland-stack-api/SKILL.md | 3 - skills/european-alternatives-api/SKILL.md | 3 - skills/handoff-session/SKILL.md | 3 - skills/init-project/SKILL.md | 3 - skills/office-hours/SKILL.md | 3 - skills/oss-project/SKILL.md | 3 - skills/plan-ceo-review/SKILL.md | 3 - skills/plan-eng-review/SKILL.md | 3 - skills/plan-milestone/SKILL.md | 3 - skills/plan-task/SKILL.md | 3 - skills/reassess-roadmap/SKILL.md | 3 - skills/resume-session/SKILL.md | 3 - skills/slice-milestone/SKILL.md | 3 - skills/software-craftsmanship/SKILL.md | 3 - skills/spawn-milestone-team/SKILL.md | 3 - skills/summarize-task/SKILL.md | 3 - skills/systematic-debugging/SKILL.md | 3 - skills/test-driven-development/SKILL.md | 3 - skills/using-ytstack/SKILL.md | 22 +- .../verification-before-completion/SKILL.md | 3 - tests/codex_plugin_test.py | 79 +++- 74 files changed, 4541 insertions(+), 148 deletions(-) create mode 100644 .gitattributes create mode 100644 bin/build-codex rename skills/using-ytstack/references/codex.md => codex/adapter.md (69%) rename hooks/codex.json => codex/hooks.json (89%) create mode 100644 codex/hooks/pre-compact create mode 100644 codex/hooks/session-end create mode 100644 codex/hooks/session-start create mode 100644 codex/skills/atomic-design/SKILL.md create mode 100644 codex/skills/deutschland-stack-api/SKILL.md create mode 100644 codex/skills/european-alternatives-api/SKILL.md create mode 100644 codex/skills/handoff-session/SKILL.md create mode 100644 codex/skills/init-project/SKILL.md create mode 100644 codex/skills/office-hours/SKILL.md create mode 100644 codex/skills/oss-project/SKILL.md create mode 100644 codex/skills/oss-project/templates/.github/ISSUE_TEMPLATE/bug_report.md create mode 100644 codex/skills/oss-project/templates/.github/ISSUE_TEMPLATE/feature_request.md create mode 100644 codex/skills/oss-project/templates/.github/pull_request_template.md create mode 100644 codex/skills/oss-project/templates/.github/workflows/ci.yml create mode 100644 codex/skills/oss-project/templates/.github/workflows/release.yml create mode 100644 codex/skills/oss-project/templates/AGENTS.md.tmpl create mode 100644 codex/skills/oss-project/templates/CONTEXT.md.tmpl create mode 100644 codex/skills/oss-project/templates/DECISIONS.md.tmpl create mode 100644 codex/skills/oss-project/templates/README.md.tmpl create mode 100644 codex/skills/oss-project/templates/ROADMAP.md.tmpl create mode 100644 codex/skills/plan-ceo-review/SKILL.md create mode 100644 codex/skills/plan-eng-review/SKILL.md create mode 100644 codex/skills/plan-milestone/SKILL.md create mode 100644 codex/skills/plan-task/SKILL.md create mode 100644 codex/skills/reassess-roadmap/SKILL.md create mode 100644 codex/skills/resume-session/SKILL.md create mode 100644 codex/skills/slice-milestone/SKILL.md create mode 100644 codex/skills/software-craftsmanship/SKILL.md create mode 100644 codex/skills/software-craftsmanship/references/ccd-principles.md create mode 100644 codex/skills/spawn-milestone-team/SKILL.md create mode 100644 codex/skills/summarize-task/SKILL.md create mode 100644 codex/skills/systematic-debugging/SKILL.md create mode 100644 codex/skills/test-driven-development/SKILL.md create mode 100644 codex/skills/using-ytstack/SKILL.md create mode 100644 codex/skills/verification-before-completion/SKILL.md diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index b8093e6..bab9e57 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -10,8 +10,8 @@ "repository": "https://github.com/Yesterday-AI/ytstack", "license": "MIT", "keywords": ["codex", "project-management", "development-methodology"], - "skills": "./skills/", - "hooks": "./hooks/codex.json", + "skills": "./codex/skills/", + "hooks": "./codex/hooks.json", "interface": { "displayName": "ytstack", "shortDescription": "Plan, build, and resume work across sessions", diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..9d448de --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +codex/skills/** linguist-generated=true +codex/hooks/** linguist-generated=true diff --git a/.github/workflows/plugin-check.yml b/.github/workflows/plugin-check.yml index 4c4344e..f7f2bf7 100644 --- a/.github/workflows/plugin-check.yml +++ b/.github/workflows/plugin-check.yml @@ -17,7 +17,9 @@ jobs: with: python-version: '3.12' - name: Codex packaging and lifecycle integration - run: python3 -m unittest discover -s tests -p 'codex_plugin_test.py' -v + run: | + python3 bin/build-codex --check + python3 -m unittest discover -s tests -p 'codex_plugin_test.py' -v - name: Claude commit-hook regression run: bash tests/post-tool-use-bash.test.sh - name: Skill contracts diff --git a/.ytstack/DECISIONS.md b/.ytstack/DECISIONS.md index f3b31f8..90b163a 100644 --- a/.ytstack/DECISIONS.md +++ b/.ytstack/DECISIONS.md @@ -665,3 +665,22 @@ Plus: ops-layer split during the same session (yopstack created as own public re - Keep the two plugin manifest versions aligned. Existing Claude installation and vendored content remain unchanged. **Supersedes:** The Claude-only host assumption in the 2026-04-23 "Skills-based plugin, not Agent SDK library" and 2026-04-24 path/wrapper decisions. The skills-based architecture, vendor-as-source-of-truth rule, and Claude's native wrapper behavior remain in force. + +--- + +## 2026-10-13: Generate a separate Codex package from unchanged Claude sources + +**Context:** During PR #23 review the maintainer rejected Codex instructions in skills loaded by Claude and Claude environment variables in the Codex adapter. The maintainer selected keeping Claude sources unchanged and automatically deriving a separate Codex package. + +**Chose:** Keep `skills/`, `hooks/`, and `.claude-plugin/` unchanged. `bin/build-codex` generates `codex/skills/` and the three `codex/hooks/` lifecycle scripts; the Codex manifest selects only these paths. Generated files are committed so plugin installation needs no build step. + +**Reason:** The procedures have one maintained source, while each host loads its own instructions and environment contract. Claude retains native dynamic shell injection. Codex runs explicit Bash blocks with `YTSTACK_PLUGIN_ROOT`, `YTSTACK_SKILL_DIR`, and `YTSTACK_PROJECT_DIR`. Codex's project path resolves to the current Git repository/worktree root, with the current directory as the non-Git fallback. + +**How to apply:** +- After changing source skills, their resources, or lifecycle hooks, run `python3 bin/build-codex` and commit the generated output. CI runs `--check` to reject drift. +- Keep host adaptations in the generator and `codex/adapter.md`. Never add Codex instructions to the Claude source skills. +- Copy skill references and templates with their skills. Keep all `vendor/` content unchanged; generated wrappers still read the same six procedures verbatim. +- Codex's `spawn-milestone-team` entry explains the support limit and sequential alternative; it contains no Claude capability probe or spawn procedure. +- Fail generation on unhandled Claude environment variables so new host dependencies require an explicit adaptation. + +**Supersedes:** The direct shared-skill loading, per-source-skill Codex links, and Claude environment compatibility layer in the earlier Codex distribution decision. The additional Codex host, separate hook registration, user-controlled hook trust, and support limits remain. diff --git a/.ytstack/REVIEW-NOTES.md b/.ytstack/REVIEW-NOTES.md index 66421b3..8066e19 100644 --- a/.ytstack/REVIEW-NOTES.md +++ b/.ytstack/REVIEW-NOTES.md @@ -165,4 +165,6 @@ Running list of items to verify, revisit, or polish at end-of-cycle review. Adde - [x] Verified Codex CLI 0.162.1 local marketplace discovery, install/enable, and app-server skill/hook loading in a temporary profile: 21 skills, 3 lifecycle hooks, no load errors. Hooks remain untrusted until the user reviews them. - [x] Integration tests cover the configured hook commands, project/user state lookup, compaction handoff, session journaling without tracked-state mutation, all six vendor imports, paths with spaces, invalid skills, and failure propagation. Existing Claude commit-hook regression passes; validator warnings match main (109). - [ ] Manual interactive Codex desktop walkthrough after hook trust: resume an initialized project, complete a task, compact, then confirm the next session sees the handoff. Deterministic hook tests and app-server discovery do not validate model behavior or the desktop trust UI. -- Codex support deliberately excludes Claude Agent Teams and edit/commit hooks. The shared adapter explains sequential execution and explicit scope/verification; do not advertise full hook/team parity. `docs/codex.md` records the boundary. +- Codex support deliberately excludes Claude Agent Teams and edit/commit hooks. The Codex adapter explains sequential execution and explicit scope/verification; do not advertise full hook/team parity. `docs/codex.md` records the boundary. + +- [x] PR #23 review follow-up: Claude skills, hooks, and manifests are byte-identical to main. Fresh Codex profile discovers 21 generated skills and 3 hooks from codex/. Fifteen tests cover host separation, generation drift, copied resources, both hosts' vendor imports, and repository-subdirectory lifecycle behavior. diff --git a/.ytstack/STATE.md b/.ytstack/STATE.md index 4e943f7..4473f4f 100644 --- a/.ytstack/STATE.md +++ b/.ytstack/STATE.md @@ -1,7 +1,7 @@ --- project: ytstack slug: ytstack -last_updated: 2026-10-13T09:17:17Z +last_updated: 2026-10-13T09:25:37Z current_milestone: M015 active_slice: none active_task: none @@ -9,6 +9,8 @@ active_task: none # State +**PR #23 host separation (2026-10-13):** Implemented the maintainer-selected layout: unchanged Claude skills/hooks/manifests; a separately generated Codex package using only YTSTACK_* environment variables. All 21 namespaced skills and three lifecycle hooks load from codex/ in a fresh temporary Codex profile, with no errors or warnings. Fifteen integration tests pass, covering both hosts' six vendor imports, generated-file drift, resources, root/subdirectory state lookup, handoff, journaling, and relative file writes. The contract checker still reports 109 baseline warnings, no failures. The initial implementation status below is historical and superseded by this entry. + **PR #23 review follow-up (2026-10-13):** Fixed repository-subdirectory startup in the Codex helper. All lifecycle hooks now find repository-root state; the regression covers project and user scope, handoff, journaling, and unchanged STATE.md. Ten integration tests pass. Host separation is under revision following maintainer feedback; this entry records the independent path fix. **Status (2026-10-13, Codex compatibility PR):** Added a Codex plugin manifest and self-marketplace, a shared host adapter linked from all 21 skills, and an explicit 3-event lifecycle hook configuration. Existing Claude manifests and vendor files are unchanged. Codex CLI 0.162.1 installs/enables the package in a separate temporary profile; app-server discovery loads all 21 namespaced skills and 3 hooks with no errors or warnings. Hooks correctly remain untrusted until reviewed. Nine Codex integration tests and ten existing commit-hook checks pass; `ytstack-check` has the same 109 warnings as the main-branch baseline, no failures. Native Claude Agent Teams and edit/commit hooks remain outside Codex support (see `docs/codex.md`). Awaiting PR review; no release or user-profile installation performed. diff --git a/AGENTS.md b/AGENTS.md index 6764707..5eaed5c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,7 +25,7 @@ If you can't verify all five above, stop and tell the human what you can't verif ## What ytstack is (and is not) **Is:** -- A Claude Code and Codex plugin (shared skills, host-specific manifests and hooks; see `docs/codex.md` for the support boundary) +- A Claude Code and Codex plugin (Claude sources plus a generated Codex package, host-specific manifests and hooks; see `docs/codex.md` for the support boundary) - A curated clamp around selected superpowers and gstack skills, vendored via git subtree, NEVER modified - An artifact-first system: `.ytstack/PROJECT.md`, `DECISIONS.md`, `KNOWLEDGE.md`, `STATE.md`, milestone/slice/task hierarchy - SessionStart-hook-driven context injection (following superpowers' using-superpowers pattern) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 764665a..e309563 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -107,10 +107,13 @@ Per ytstack's design direction, we credit external methodology work generically, ## Codex compatibility -Both plugin manifests load `skills/`. Keep the Codex adapter link at the top -of each skill so direct invocation works without a SessionStart hook. -Host mappings live in `skills/using-ytstack/references/codex.md`; do not fork -vendored procedures. Register only supported events in `hooks/codex.json`. +Claude loads `skills/` and `hooks/` directly. Codex loads the generated +`codex/skills/` and `codex/hooks/` package. Run `python3 bin/build-codex` after +changing source skills, resources, or lifecycle hooks, and commit the output. +Do not add Codex instructions to Claude skills or edit generated files by hand. +Host adaptations live in `bin/build-codex` and `codex/adapter.md`; supported +hook events are registered in `codex/hooks.json`. Vendor content stays unchanged. +CI runs `python3 bin/build-codex --check` to catch stale output. Keep `.codex-plugin/plugin.json` and `.claude-plugin/plugin.json` versions aligned. Run `python3 -m unittest discover -s tests -p 'codex_plugin_test.py' -v` and diff --git a/README.md b/README.md index 79cefb7..9173a5a 100644 --- a/README.md +++ b/README.md @@ -188,8 +188,8 @@ codex plugin add ytstack@ytstack Start a new session and select `$ytstack:using-ytstack` from ytstack in the skill picker. On local hosts with hook support, review and trust the plugin hooks for automatic -startup context, compaction handoff, and session journaling. The same 21 skills -are packaged for both hosts. Claude Agent Teams and edit/commit hooks remain +startup context, compaction handoff, and session journaling. The existing Claude skills remain unchanged; a generated Codex package +provides the same 21 skill names. Claude Agent Teams and edit/commit hooks remain Claude-only; Codex uses the sequential task flow and explicit verification. See [Codex setup and compatibility](docs/codex.md) for local development, hook-free usage, and validation. @@ -290,7 +290,7 @@ Ships: - 8 hooks (SessionStart / PreCompact / SessionEnd / TeammateIdle / TaskCreated / TaskCompleted / PreToolUse-Edit / PostToolUse-Bash). SessionStart-hook now injects the using-ytstack directive + project state. - Full docs (`AGENTS.md`, `CONTRIBUTING.md`, `QUICKSTART.md`, UX contracts, references, methodology) - Plugin manifest + marketplace manifest, published via the `yesterday-public-plugins` catalog -- Codex manifest + self-marketplace, shared skills, and 3 lifecycle hooks; see [host support](docs/codex.md#supported-behavior) +- Codex manifest + self-marketplace, generated skills, and 3 lifecycle hooks; see [host support](docs/codex.md#supported-behavior) See `.ytstack/ROADMAP.md` for the task list, `.ytstack/STATE.md` for progress, `.ytstack/REVIEW-NOTES.md` for deferred items. diff --git a/bin/build-codex b/bin/build-codex new file mode 100644 index 0000000..2378793 --- /dev/null +++ b/bin/build-codex @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +"""Build the Codex distribution without changing the Claude sources or vendor.""" + +import argparse +from pathlib import Path +import re +import sys + + +ROOT = Path(__file__).resolve().parents[1] + + +def replace_required(text, old, new): + if old not in text: + raise ValueError(f"Codex adaptation needs review; source text changed: {old!r}") + return text.replace(old, new) + + +def adapt_skill(path): + text = path.read_text() + name = path.parent.name + if name == "spawn-milestone-team": + frontmatter = text.split("---", 2)[1] + frontmatter = re.sub(r'^description:.*$', 'description: "Explains the Agent Teams support boundary when parallel milestone execution is requested in Codex."', frontmatter, flags=re.M) + text = "---" + frontmatter + "---\n\n# Milestone teams in Codex\n\n" + ( + "This workflow requires Claude Code Agent Teams and its coordination hooks.\n" + "They are not provided by this Codex package.\n\n" + "## Checklist\n\n1. Explain the limitation.\n2. Offer sequential execution.\n\n" + "## Procedure\n\nStop before any capability probe or team spawn. Do not start another host.\n" + "Offer `plan-task` -> implementation -> `verification-before-completion`\n" + "-> `summarize-task` for each task instead.\n\n" + "## Terminal State\n\nReturn after explaining the supported workflow. Do not spawn agents or\n" + "invoke another skill unless the user requests it.\n" + ) + else: + text = text.replace('${CLAUDE_SKILL_DIR}/../../vendor/', '${YTSTACK_PLUGIN_ROOT}/vendor/') + text = text.replace('CLAUDE_PROJECT_DIR', 'YTSTACK_PROJECT_DIR') + text = text.replace(' || [ -n "$CLAUDE_AGENT_TEAM_MEMBER" ]', '') + text = text.replace('(i.e. `CLAUDE_AGENT_TEAM_MEMBER` is unset)', '(interactive mode)') + text = text.replace('(CLAUDE_AGENT_TEAM_MEMBER set)', '(non-interactive mode)') + if name == "using-ytstack": + text = re.sub(r".*?\n\n", "", text, flags=re.S) + for old, new in ( + ('Requires Skill-tool invocation before any response when a ytstack skill applies. Auto-injected by the SessionStart hook. Not meant for direct user invocation.', + 'Load at session start or before selecting a workflow. The SessionStart hook supplies it when trusted; it can also be loaded explicitly.'), + ("A hook has injected project state into your context and you have access to ytstack's skills via the `Skill` tool.", + 'Load project state and skills as described in the Codex adapter. Trusted SessionStart hooks supply state automatically.'), + ('invoke the skill via the `Skill` tool', 'load the skill using the Codex adapter'), + ('invoke it via the `Skill` tool', 'load it using the Codex adapter'), + ('(`CLAUDE.md`, direct messages)', '(`AGENTS.md`, direct messages)'), + ('- Orchestration: `spawn-milestone-team`', '- Parallel milestone execution: `spawn-milestone-team` explains the unsupported capability and sequential alternative.'), + ('Invoke Skill tool', 'Load selected skill'), + ('Create TodoWrite entries per item.', 'Track each item with the available plan tool or a checklist in the conversation.'), + ): + text = replace_required(text, old, new) + + # Native dynamic-context fences become explicit shell blocks in Codex. + text = text.replace('```!\n', '```bash\n') + header = ( + f'\n\n' + '**Codex:** Read [the host adapter](../../adapter.md) before following this skill.\n\n' + ) + frontmatter, body = text.split('\n---\n', 1) + return frontmatter + '\n---\n\n' + header + body.lstrip('\n') + + +def adapt_hook(path): + text = path.read_text() + text = text.replace('CLAUDE_PROJECT_DIR', 'YTSTACK_PROJECT_DIR') + text = text.replace('CLAUDE_PLUGIN_ROOT', 'YTSTACK_PLUGIN_ROOT') + text = text.replace('CLAUDE_SESSION_ID', 'YTSTACK_SESSION_ID') + text = text.replace('Claude Code', 'Codex') + if path.name == 'session-start': + text = replace_required(text, '/skills/using-ytstack/SKILL.md', '/codex/skills/using-ytstack/SKILL.md') + text = replace_required(text, 'USING_YTSTACK_CONTENT=$(cat "$USING_YTSTACK_FILE")', + 'USING_YTSTACK_CONTENT="[ytstack plugin root: $YTSTACK_PLUGIN_ROOT]\n\n$(cat "$USING_YTSTACK_FILE")"') + text = replace_required(text, "For all other skills, use the 'Skill' tool:", + 'Load other skills using the Codex adapter:') + return text.replace('#!/usr/bin/env bash\n', f'#!/usr/bin/env bash\n# Generated from hooks/{path.name} by bin/build-codex. Do not edit.\n', 1) + + +def generated_files(): + files = {Path('codex/skills') / path.relative_to(ROOT / 'skills'): adapt_skill(path) + for path in sorted((ROOT / 'skills').glob('*/SKILL.md'))} + for path in (ROOT / 'skills').rglob('*'): + if path.is_file() and path.name != 'SKILL.md': + files[Path('codex/skills') / path.relative_to(ROOT / 'skills')] = path.read_text() + for name in ('session-start', 'pre-compact', 'session-end'): + files[Path('codex/hooks') / name] = adapt_hook(ROOT / 'hooks' / name) + for path, text in files.items(): + if re.search(r'CLAUDE_[A-Z_]+', text): + raise ValueError(f'{path}: unmapped Claude environment variable; update the Codex adapter') + return files + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--check', action='store_true', help='fail if committed Codex files are out of date') + args = parser.parse_args() + files = generated_files() + existing = {path.relative_to(ROOT) for folder in ('codex/skills', 'codex/hooks') + for path in (ROOT / folder).rglob('*') if path.is_file()} + stale = existing - files.keys() + changed = [path for path, text in files.items() + if not (ROOT / path).is_file() or (ROOT / path).read_text() != text] + if args.check: + for path in sorted(stale | set(changed)): + print(f'Out of date: {path}', file=sys.stderr) + if stale or changed: + print('Run python3 bin/build-codex and commit the generated files.', file=sys.stderr) + return 1 + else: + for path in stale: + (ROOT / path).unlink() + for path in changed: + (ROOT / path).parent.mkdir(parents=True, exist_ok=True) + (ROOT / path).write_text(files[path]) + print(f'Codex distribution: {len(files)} generated files {"verified" if args.check else "built"}.') + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/bin/ytstack-codex b/bin/ytstack-codex index af27b37..0eaf087 100755 --- a/bin/ytstack-codex +++ b/bin/ytstack-codex @@ -1,10 +1,10 @@ #!/usr/bin/env bash -# Run shared ytstack shell blocks and lifecycle hooks in Codex. +# Run the generated Codex skills and lifecycle hooks. set -euo pipefail -export CLAUDE_PLUGIN_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +export YTSTACK_PLUGIN_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" PROJECT_DIR=$(git rev-parse --show-toplevel 2>/dev/null) || PROJECT_DIR="$PWD" -export CLAUDE_PROJECT_DIR="$PROJECT_DIR" +export YTSTACK_PROJECT_DIR="$PROJECT_DIR" cd "$PROJECT_DIR" export YTSTACK_HOST=codex @@ -21,8 +21,8 @@ shift 2 case "$MODE" in skill) [[ "$NAME" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]] || usage - export CLAUDE_SKILL_DIR="$CLAUDE_PLUGIN_ROOT/skills/$NAME" - [ -f "$CLAUDE_SKILL_DIR/SKILL.md" ] || { + export YTSTACK_SKILL_DIR="$YTSTACK_PLUGIN_ROOT/codex/skills/$NAME" + [ -f "$YTSTACK_SKILL_DIR/SKILL.md" ] || { echo "ytstack: skill not found: $NAME" >&2 exit 2 } @@ -32,7 +32,7 @@ case "$MODE" in [ "$#" -eq 0 ] || usage case "$NAME" in session-start|pre-compact|session-end) - exec bash "$CLAUDE_PLUGIN_ROOT/hooks/$NAME" + exec bash "$YTSTACK_PLUGIN_ROOT/codex/hooks/$NAME" ;; *) usage ;; esac diff --git a/skills/using-ytstack/references/codex.md b/codex/adapter.md similarity index 69% rename from skills/using-ytstack/references/codex.md rename to codex/adapter.md index 28c0059..0520f62 100644 --- a/skills/using-ytstack/references/codex.md +++ b/codex/adapter.md @@ -1,21 +1,20 @@ # Running ytstack skills in Codex -Read this adapter before following a ytstack skill in Codex. It applies to -the shared skills and their vendored procedures; Claude Code keeps its native -tool and shell-injection behavior. +Read this adapter before following a ytstack skill in Codex. It applies only to the +generated Codex package and its vendored procedures. ## Load skills and context Use Codex's skill loader when available. Otherwise read the selected -`skills//SKILL.md` from this installed plugin and follow it. A reference +`codex/skills//SKILL.md` from this installed plugin and follow it. A reference to `ytstack:` or `/ytstack:` means that skill, not a shell command. Users can select it with `$ytstack:` in Codex's skill picker; choose the entry from ytstack if another plugin provides the same name. -The plugin root is two directories above the selected `SKILL.md` directory. -For this adapter it is three directories above `references/`. Resolve it from +The plugin root is three directories above the selected `SKILL.md` directory. +For this adapter it is the parent of `codex/`. Resolve it from the loaded file's actual location, not the project checkout or a guessed cache -version. `hooks/session-start` also reports the installed plugin root. +version. `codex/hooks/session-start` also reports the installed plugin root. If the SessionStart hook did not provide project context, read the `using-ytstack` skill and the project's `.ytstack/STATE.md`, `DECISIONS.md`, and @@ -25,11 +24,11 @@ codebase is a new project; follow the user's requested scope. ## Run shell blocks explicitly -Codex does not evaluate the shared skills' fenced `!` blocks as Claude dynamic -context. Execute those blocks explicitly, in order, using Bash from the target -project directory. Honor each hard gate before loading its vendored procedure. -Run ordinary Bash preambles the same way. Each block is a separate process; -use its printed values in subsequent prose steps. +Run the selected skill's Bash preamble explicitly from the target project. +Honor each hard gate before running its vendor `cat` block and following that +procedure. Other shell blocks run only when the procedure calls for them; +examples and output templates are not setup commands. Each block is a separate +process; use its printed values in subsequent prose steps. For every block, use the bundled helper with the current skill name: @@ -39,11 +38,11 @@ bash "/absolute/plugin/root/bin/ytstack-codex" skill plan-task <<'YTSTACK_BASH' YTSTACK_BASH ``` -The helper derives `CLAUDE_PLUGIN_ROOT` and `CLAUDE_SKILL_DIR` from its own -installed location and sets `CLAUDE_PROJECT_DIR` to the Git repository root, -falling back to the working directory outside Git. It runs the block there. These names are compatibility inputs for the shared files, not a -requirement to install Claude. Use the helper again for every shell call that -needs them; exports from a previous tool call do not persist. +The helper derives `YTSTACK_PLUGIN_ROOT` and `YTSTACK_SKILL_DIR` from its own +installed location and sets `YTSTACK_PROJECT_DIR` to the Git repository root, +falling back to the working directory outside Git. It runs the block there. +Use the helper again for every shell call that needs these variables; exports +from a previous tool call do not persist. Read the entire output of the wrapper's vendor `cat` block, including any referenced files needed for the task. Vendor files remain unchanged. Keep @@ -54,7 +53,7 @@ inventing a tool or installing a dependency silently. ## Map tools to the host -| Shared instruction | Codex behavior | +| Instruction | Codex behavior | | --- | --- | | `Skill` | Load/read the selected skill as described above. | | `Bash`, `Read`, `Write`, `Edit`, `Grep`, `Glob` | Use available shell, file, patch, and search tools. | @@ -65,7 +64,7 @@ inventing a tool or installing a dependency silently. A missing question tool does not imply non-interactive mode or consent. Honor authorization already given in the conversation. User instructions and the host's tool, approval, and sandbox rules take precedence over this adapter -and any shared or vendored skill instructions. +and any generated or vendored skill instructions. ## Lifecycle and teams @@ -78,8 +77,8 @@ Claude's edit/commit hooks and Agent Teams events are not registered in Codex. Check task scope before editing, run the verification command, and use `summarize-task` to close work; no automatic draft summary is created on commit. -`spawn-milestone-team` remains a Claude Code workflow. In Codex, stop before its -Claude capability probe or spawn procedure and explain that limitation. +`spawn-milestone-team` remains a Claude Code workflow. The generated Codex +entry explains that limitation without a capability probe or spawn procedure. Suggest the sequential `plan-task` -> implementation -> `verification-before-completion` -> `summarize-task` flow. Do not pretend Codex subagents provide Claude's shared team/task hooks or start another host. diff --git a/hooks/codex.json b/codex/hooks.json similarity index 89% rename from hooks/codex.json rename to codex/hooks.json index 9d361d7..27f7afc 100644 --- a/hooks/codex.json +++ b/codex/hooks.json @@ -1,5 +1,5 @@ { - "description": "Codex lifecycle hooks. Claude tool and Agent Teams events stay in hooks.json.", + "description": "Codex lifecycle hooks. Only the three supported lifecycle events are registered.", "hooks": { "SessionStart": [ { diff --git a/codex/hooks/pre-compact b/codex/hooks/pre-compact new file mode 100644 index 0000000..e7e92e2 --- /dev/null +++ b/codex/hooks/pre-compact @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +# Generated from hooks/pre-compact by bin/build-codex. Do not edit. +# ytstack PreCompact hook +# +# Fires before Codex compacts the conversation. Writes a handoff snapshot +# to .ytstack/HANDOFF.md so the post-compaction session can re-orient quickly +# from on-disk state. +# +# Side-effects only. Does not emit stdout context. + +set -uo pipefail + +# Note: session_id arrives on stdin JSON (not via env). This hook does not use +# session_id, so stdin is intentionally not consumed here. + +PROJECT_DIR="${YTSTACK_PROJECT_DIR:-$PWD}" +cd "$PROJECT_DIR" 2>/dev/null || exit 0 + +YT_DIR="" +if [ -d "$PROJECT_DIR/.ytstack" ]; then + YT_DIR="$PROJECT_DIR/.ytstack" +else + SLUG=$(basename "$(git rev-parse --show-toplevel 2>/dev/null || echo "$PROJECT_DIR")") + USER_YT="$HOME/.ytstack/projects/$SLUG" + [ -d "$USER_YT" ] && YT_DIR="$USER_YT" +fi + +[ -z "$YT_DIR" ] && exit 0 + +STATE_FILE="$YT_DIR/STATE.md" +HANDOFF_FILE="$YT_DIR/HANDOFF.md" +TS=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + +# Pull current position from STATE.md frontmatter (if present) +get_field() { sed -n "s/^$1: *//p" "$STATE_FILE" 2>/dev/null | head -1; } +PROJECT=$(get_field project) +CURRENT_MILESTONE=$(get_field current_milestone) +ACTIVE_SLICE=$(get_field active_slice) +ACTIVE_TASK=$(get_field active_task) + +cat > "$HANDOFF_FILE" </dev/null || exit 0 + +YT_DIR="" +if [ -d "$PROJECT_DIR/.ytstack" ]; then + YT_DIR="$PROJECT_DIR/.ytstack" +else + SLUG=$(basename "$(git rev-parse --show-toplevel 2>/dev/null || echo "$PROJECT_DIR")") + USER_YT="$HOME/.ytstack/projects/$SLUG" + [ -d "$USER_YT" ] && YT_DIR="$USER_YT" +fi + +[ -z "$YT_DIR" ] && exit 0 + +STATE_FILE="$YT_DIR/STATE.md" +JOURNAL_DIR="$YT_DIR/journal" +TS=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + +# Append to journal (persistent log of sessions) +mkdir -p "$JOURNAL_DIR" +JOURNAL_FILE="$JOURNAL_DIR/sessions.jsonl" + +get_field() { sed -n "s/^$1: *//p" "$STATE_FILE" 2>/dev/null | head -1; } +CURRENT_MILESTONE=$(get_field current_milestone) +ACTIVE_SLICE=$(get_field active_slice) +ACTIVE_TASK=$(get_field active_task) + +# Extract session_id from the stdin payload captured above. +# session_id arrives only on stdin JSON -- there is no YTSTACK_SESSION_ID env var. +if command -v jq >/dev/null 2>&1; then + SESSION_ID=$(printf '%s' "$INPUT" | jq -r '.session_id // "unknown"' 2>/dev/null || echo "unknown") +else + SESSION_ID=$(printf '%s' "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('session_id','unknown'))" 2>/dev/null || echo "unknown") +fi + +# Build JSONL entry. Prefer jq for safe encoding; fall back to python3. +if command -v jq >/dev/null 2>&1; then + jq -nc \ + --arg ts "$TS" \ + --arg event "session-end" \ + --arg milestone "${CURRENT_MILESTONE:-none}" \ + --arg slice "${ACTIVE_SLICE:-none}" \ + --arg task "${ACTIVE_TASK:-none}" \ + --arg session_id "$SESSION_ID" \ + '{timestamp: $ts, event: $event, milestone: $milestone, slice: $slice, task: $task, session_id: $session_id}' \ + >> "$JOURNAL_FILE" +elif command -v python3 >/dev/null 2>&1; then + python3 -c " +import json +print(json.dumps({ + 'timestamp': '$TS', + 'event': 'session-end', + 'milestone': '${CURRENT_MILESTONE:-none}', + 'slice': '${ACTIVE_SLICE:-none}', + 'task': '${ACTIVE_TASK:-none}', + 'session_id': '$SESSION_ID' +})) +" >> "$JOURNAL_FILE" +fi + +exit 0 diff --git a/codex/hooks/session-start b/codex/hooks/session-start new file mode 100644 index 0000000..95db237 --- /dev/null +++ b/codex/hooks/session-start @@ -0,0 +1,168 @@ +#!/usr/bin/env bash +# Generated from hooks/session-start by bin/build-codex. Do not edit. +# ytstack SessionStart hook +# +# Injects two things as additionalContext when a `.ytstack/` directory is present: +# +# 1. The full content of skills/using-ytstack/SKILL.md -- the directive that +# tells the agent to auto-invoke ytstack skills based on user intent +# (superpowers-style proactive skill selection). +# +# 2. The current project state from .ytstack/STATE.md -- so the agent knows +# where we are in the milestone/slice/task hierarchy. +# +# Silent no-op for projects without ytstack tracking. + +set -uo pipefail + +# Note: session_id arrives on stdin JSON (not via env). This hook does not use +# session_id; it only emits additionalContext output JSON via stdout. + +PROJECT_DIR="${YTSTACK_PROJECT_DIR:-$PWD}" +cd "$PROJECT_DIR" 2>/dev/null || exit 0 + +# Resolve ytstack state directory. Project-level takes precedence over user-level. +# In greenfield (no .ytstack/ anywhere), YT_DIR stays empty and we still inject +# the using-ytstack directive so the agent can route build-intent to office-hours. +YT_DIR="" +YT_SCOPE="" +if [ -d "$PROJECT_DIR/.ytstack" ]; then + YT_DIR="$PROJECT_DIR/.ytstack" + YT_SCOPE="project" +else + SLUG=$(basename "$(git rev-parse --show-toplevel 2>/dev/null || echo "$PROJECT_DIR")") + USER_YT="$HOME/.ytstack/projects/$SLUG" + if [ -d "$USER_YT" ]; then + YT_DIR="$USER_YT" + YT_SCOPE="user" + fi +fi + +STATE_FILE="${YT_DIR:+$YT_DIR/STATE.md}" + +# Load the using-ytstack directive (source-of-truth lives in the plugin). +# This is the core agent-behavior primer and must be injected on EVERY session +# start where the ytstack plugin is active -- including greenfield sessions with +# no .ytstack/ yet, because the directive's job is to route greenfield build- +# intent to office-hours BEFORE any infra exists. +USING_YTSTACK_FILE="${YTSTACK_PLUGIN_ROOT:-$PROJECT_DIR}/codex/skills/using-ytstack/SKILL.md" +USING_YTSTACK_CONTENT="" +if [ -f "$USING_YTSTACK_FILE" ]; then + USING_YTSTACK_CONTENT="[ytstack plugin root: $YTSTACK_PLUGIN_ROOT] + +$(cat "$USING_YTSTACK_FILE")" +fi + +# Silent exit if the plugin itself is not available (no YTSTACK_PLUGIN_ROOT +# resolution). This is the only "no-op" case: we can't prime the agent if the +# directive file does not exist on disk. +[ -z "$USING_YTSTACK_CONTENT" ] && exit 0 + +# Helper: emit additionalContext JSON with the given string +emit_json() { + local content="$1" + if command -v jq >/dev/null 2>&1; then + printf '%s' "$content" | jq -Rs '{hookSpecificOutput: {hookEventName: "SessionStart", additionalContext: .}}' + elif command -v python3 >/dev/null 2>&1; then + printf '%s' "$content" | python3 -c 'import sys, json; print(json.dumps({"hookSpecificOutput": {"hookEventName": "SessionStart", "additionalContext": sys.stdin.read()}}))' + else + # Last-resort raw -- won't safely handle embedded quotes/newlines + printf '{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"%s"}}\n' "$content" + fi +} + +# Greenfield: no .ytstack/ exists. Inject the directive with a greenfield state +# block that tells the agent explicitly what to do with build-intent. +if [ -z "$YT_DIR" ]; then + GREENFIELD_STATE="[ytstack active -- scope: greenfield] + +No \`.ytstack/\` directory in $PROJECT_DIR (or any parent). This is greenfield. + +Greenfield flow per the using-ytstack directive above: +1. If the user expresses build-intent (\"baue mir X\", \"build me X\", \"new project\", \"I have an idea\"), invoke \`ytstack:office-hours\` FIRST to validate the pitch. Do NOT invoke plan-milestone or init-project directly. +2. After office-hours produces OFFICE-HOURS.md, invoke \`ytstack:plan-ceo-review\` (concept mode) to stress-test premise + scope. +3. Optionally \`ytstack:plan-eng-review\` (concept mode) for feasibility. +4. Then \`ytstack:init-project\` to scaffold \`.ytstack/\` (populates PROJECT.md from the pitch, no cold PM questions)." + + emit_json " +You have ytstack. Below is the full content of your 'ytstack:using-ytstack' skill, which tells you how to find and invoke ytstack skills based on user intent. Follow it. Load other skills using the Codex adapter: + +$USING_YTSTACK_CONTENT + + +--- + +$GREENFIELD_STATE" + exit 0 +fi + +# Brownfield: .ytstack/ exists. Handle missing STATE.md as anomaly. +if [ ! -f "$STATE_FILE" ]; then + MSG="$USING_YTSTACK_CONTENT + +--- + +[ytstack] $YT_DIR exists but STATE.md is missing. Run /ytstack:init-project to repair, or remove the directory." + emit_json "$MSG" + exit 0 +fi + +# Extract frontmatter fields from STATE.md +get_field() { sed -n "s/^$1: *//p" "$STATE_FILE" | head -1; } +PROJECT=$(get_field project) +CURRENT_MILESTONE=$(get_field current_milestone) +ACTIVE_SLICE=$(get_field active_slice) +ACTIVE_TASK=$(get_field active_task) +LAST_UPDATED=$(get_field last_updated) + +# Extract the "Next action" section body (up to the next ## header) +NEXT_ACTION=$(awk '/^## Next action/{flag=1; next} /^## /{flag=0} flag' "$STATE_FILE" | sed '/^$/d' | head -5) + +# Recent summary file names (last 3 T##-SUMMARY.md by mtime) +RECENT_SUMMARIES="" +if compgen -G "$YT_DIR"/*-SUMMARY.md > /dev/null 2>&1; then + RECENT_SUMMARIES=$(ls -t "$YT_DIR"/*-SUMMARY.md 2>/dev/null | head -3 | while read -r f; do + basename "$f" .md + done | paste -sd, - | sed 's/,/, /g') +fi + +# Build the state context block +STATE_CONTEXT="[ytstack active -- scope: $YT_SCOPE] + +Project: ${PROJECT:-unknown} +Current: ${CURRENT_MILESTONE:-none}${ACTIVE_SLICE:+ / $ACTIVE_SLICE}${ACTIVE_TASK:+ / $ACTIVE_TASK} +Last updated: ${LAST_UPDATED:-unknown}" + +if [ -n "$NEXT_ACTION" ]; then + STATE_CONTEXT="$STATE_CONTEXT + +Next action: +$NEXT_ACTION" +fi + +if [ -n "$RECENT_SUMMARIES" ]; then + STATE_CONTEXT="$STATE_CONTEXT + +Recent tasks: $RECENT_SUMMARIES" +fi + +STATE_CONTEXT="$STATE_CONTEXT + +The files in $YT_DIR are the source of truth for this project's state. Read STATE.md for the full dashboard, DECISIONS.md for locked architectural choices, KNOWLEDGE.md for patterns and gotchas, ROADMAP.md for the task list. Do not rely on prior-conversation context for these facts." + +# Combine: using-ytstack directive first, then project state. +# The directive primes the agent's behavior; the state gives it +# the specific where-we-are facts. USING_YTSTACK_CONTENT is always +# non-empty here (early-exit above if missing). +FULL_CONTEXT=" +You have ytstack. Below is the full content of your 'ytstack:using-ytstack' skill, which tells you how to find and invoke ytstack skills based on user intent. Follow it. Load other skills using the Codex adapter: + +$USING_YTSTACK_CONTENT + + +--- + +$STATE_CONTEXT" + +emit_json "$FULL_CONTEXT" +exit 0 diff --git a/codex/skills/atomic-design/SKILL.md b/codex/skills/atomic-design/SKILL.md new file mode 100644 index 0000000..409561d --- /dev/null +++ b/codex/skills/atomic-design/SKILL.md @@ -0,0 +1,186 @@ +--- +name: atomic-design +description: > + Atomic Design methodology for structuring React/Next.js component libraries. + Defines five levels (atoms, molecules, organisms, templates, pages) with hard + import rules, classification tests, and anti-patterns. Use when creating, + reviewing, or restructuring frontend components. +tier: core +version: 0.1.0 +kind: reference +allowed-tools: + - Read + - Glob + - Grep +--- + + + +**Codex:** Read [the host adapter](../../adapter.md) before following this skill. + +# Atomic Design -- Frontend Component Architecture + +Reference: Brad Frost, *Atomic Design* (https://atomicdesign.bradfrost.com/) + +## Core Principle + +Atomic Design is a mental model, not a linear process. You do not build atoms first, then molecules, then organisms. You think about the interface simultaneously as a cohesive whole and a collection of parts. Every component decision considers both its isolated purpose and its role in the larger system. + +--- + +## The Five Levels + +### 1. Atoms (`components/atoms/`) + +The smallest, indivisible UI elements. They cannot be broken down further without losing their meaning. + +**Examples:** Button, Input, Label, Badge, Avatar, Icon, Toggle, Divider, Skeleton, Spinner + +**Rules:** +- Accept only generic, presentational props (size, variant, color, disabled) +- Never import other atoms -- they are leaf nodes +- Must work in complete isolation -- render one on a blank page and it must make visual sense +- Define variants via props, not separate components (`