Init
⚠️ Critical execution directive — read first
Execute the entire pipeline continuously, without pausing. Do NOT wait for user input between steps. The ONLY user-facing pauses are:
- Step 1 —
AskUserQuestion, twice at most: the working-directory confirmation, only ifpwdshows neither.git/nor a Step 4 manifest; and the permission check - Step 7a (resolve ambiguous categories) —
AskUserQuestion, only if any categories were marked/ambiguous - Step 7b (per-candidate interview) —
AskUserQuestion, including the single batched tail question foroverlap_tail[]items, if any - Step 8 (RED security verdict) —
AskUserQuestion, only for RED candidates - Step 12.5 (platform-mismatch self-check) — only if a mismatch is actually caught, and never in this step directly: the pauses belong to the
/ievo:evohandoff, which under its own Step 1 overlay-only carve-out can raise at most two, each independently conditional — Step 5.6's upstream-feedback offer (if the lesson classifies as upstream-relevant, which this one does) and Step 5.7's extraction offer (only if that overlay already holds a cluster). Choosing to share at Step 5.6 hands off to/ievo:feedback, which adds its own public-posting gate - Step 13 (final feedback prompt) —
AskUserQuestion
Between every other step, proceed immediately to the next step. If you find yourself thinking "should I confirm with the user before doing X?" — the answer is NO. Just do it. Write to the log so the user can monitor via tail -f.
Especially: between Step 5 (discover.mjs result) and Step 6 (index-repos) → no pause, no confirmation, no summary checkpoint. Just chain straight through.
Pipeline
Set up iEvo in the current project. Pipeline (v0.6.0+):
discover.mjs (Node, parallel skills.sh API queries)
↓
index-repos (parallel repo-indexer sub-agents, local scan)
↓
categorical rank — top-N per category
↓
interview (per candidate, AskUserQuestion)
↓
security-auditor (parallel sub-agents, antivirus deep scan)
↓
install (vendor or plugin, project-scope, copy + source SHA metadata)
v0.6.0 — zero-prereq architecture: dropped find-skills manual install. Discovery happens via own discover.mjs script (skills.sh API direct). All scanning, ranking, audit, and install decisions happen on user's machine. Independent and verifiable per-user, no central trust gates.
Install model (Step 9): project-scope, into the invoking client's own load paths — Claude Code: .claude/agents/, .claude/skills/; Codex (Step 1.5's detection rule): .agents/skills/ (skills only — see Step 7a's platform filter) — copy files via Write tool (NOT symlink — robust against source moves). Source repo + commit SHA recorded in .ievo/evolution/<scope>/<name>.md frontmatter for upstream-update tracking via /ievo:update.
Step 0: Print version banner (read from disk — never infer)
MANDATORY first action. The version MUST come from actual disk read of the plugin.json file. If you "know" the version from prior conversation turns, from being trained, or from SKILL.md text — IGNORE that knowledge. The diagnostic value depends on showing what's actually loaded, not what you expect.
Step 0a — Read plugin.json from disk (Read tool, not Bash)
Use the Read tool on:
${CLAUDE_PLUGIN_ROOT}/.claude-plugin/plugin.json
The Read tool returns the file contents verbatim in the tool result — no chance of substitution or inference. Extract the version field from the JSON.
If Read fails (file missing, permission denied, etc.), print error and stop:
❌ Cannot read plugin.json from ${CLAUDE_PLUGIN_ROOT}/.claude-plugin/plugin.json
<error message>
Reinstall plugin: /plugin reinstall ievo@ievo-skills
Do NOT fall back to "I think it's v0.2.x" — that defeats the diagnostic.
Step 0b — Print banner
Output exactly (substitute only <version-from-read> with the value extracted in 0a):
🧬 iEvo init v<version-from-read>
from: ${CLAUDE_PLUGIN_ROOT}/.claude-plugin/plugin.json
The from: line is part of the banner — shows the user the exact path the version came from. They can manually inspect that file to verify if suspicious.
Why Read instead of Bash
jq via Bash works, but Bash tool invocations can be skipped by some inference paths — the model can complete the next line as if Bash had returned the expected value. Read tool is deterministic: its result is a file content snapshot in the tool response. The version IS what Read returned, by construction.
Step 0c — Record version in the run-log
The banner version becomes section 0 of the run-log, written when the log file is created in Step 2.5 (format: log-format.md §0). The plugin path + commit SHA recorded there help diagnose which install dir Claude Code loaded the plugin from.
Step 1: Verify prerequisites
Working directory — check before anything else. Every step below — .ievo/
setup, .claude/settings.json, Step 9's project-scoped installs — writes into the
session's CURRENT working directory, and nothing in this pipeline switches directory
later. A session started outside the project to init (e.g. ~ or ~/Desktop)
therefore misdirects the whole run silently.
Run pwd, then look in that same directory for a project signal: a .git/ entry or
any Step 4 manifest. If both are absent, ask before doing anything else via
AskUserQuestion (an empty, brand-new project directory is legitimate — the user
answers for it; never decide this yourself):
- Question:
No project detected in <pwd output>. Initialize iEvo here? - Header:
Directory - Options:
Yes — this is the project— description:Continue. .ievo/ and all installs go here.No — stop— description:Halt so the session can be moved to the right directory.
On Yes, continue with the prereq checks below. On No, halt — you cannot move
the session yourself, so print the message below and stop:
/cdis a built-in Claude Code command, recognized only when the user types it at the start of a message (commands reference:/cd <path>— "Move this session to a new working directory", requires v2.1.169+; earlier versions reportUnknown command: /cd). It is not callable from a skill.Bash(cd ...)is not a substitute. It changes only that call's shell directory;Read/Write,.claude/settings resolution and Step 9's installs all keep resolving against the session's working directory, so.ievo/and every install still land outside the project — the exact silent misdirection this check exists to catch. Never use it to "fix" the directory.
Move this session to the project, then re-run /ievo:init:
• Claude Code v2.1.169+ — type `/cd <project-path>` (preserves the prompt cache).
• Older Claude Code, or Codex — no in-session directory switch is documented;
quit and relaunch from inside the project directory.
Hard prereqs (v0.6.0+ — no more find-skills install):
gitCLI —which git. Used for checkout-based indexing.ghCLI —which ghandgh auth status. Used by security-auditor (audit data from skills.sh) and uninstall (marker discovery).node(≥18) —node --version. Used bydiscover.mjs,scan_repo.mjs,validate_agents.mjs. Node ships with Claude Code and Codex, so this is normally always available — but if user has a damaged install, hard-fail.- Bash permissions for the commands init will run (see below)
v0.6.0 change: dropped find-skills prereq. Discovery now happens via own Node script (discover.mjs) hitting https://skills.sh/api/search directly — no manual prereq install required.
If gh missing or unauthenticated:
This skill needs the `gh` CLI for indexing and security checks. Install:
brew install gh # macOS
# or see https://cli.github.com
gh auth login
If node missing OR version < 18:
This skill needs Node.js 18+ for repo scanning. Normally Node ships with Claude Code —
if it's missing your install may be damaged. Try reinstalling Claude Code, or install Node directly:
brew install node # macOS
apt install nodejs # Debian/Ubuntu
# or see https://nodejs.org
Verify: node --version → must be v18.0.0 or higher
Stop init on missing node. No graceful fallback — scan_repo.mjs is core to Step 6.
Permission check (auto-mode classifier)
Platform — skip this entire subsection on Codex. .claude/settings.local.json / .claude/settings.json permissions.allow entries are Claude Code's permission mechanism; Codex never reads them, so writing them from a Codex session configures the wrong client (this exact miss shipped permissions into .claude/settings.json from a Codex run — issue #432). If the host platform is Codex (per Step 1.5's detection rule — $CODEX_CLI, or a Codex Desktop signal), skip the settings read, the AskUserQuestion, and any write — Codex's own approval flow prompts per command as needed. The hard prereq checks above (git / gh / node) still apply on every platform.
Init will run network/CLI commands the auto-mode classifier may block: gh api, gh search. Without pre-approval, each call hits a confirmation prompt — friction during the discovery phase.
(v0.6.0 dropped npx skills permissions — discovery now happens via local discover.mjs script which is a normal node invocation, not blocked by the auto-classifier.)
Recommended: ensure .claude/settings.local.json (per-user, gitignored) OR .claude/settings.json (team-shared, committed) contains:
{
"permissions": {
"allow": [
"Bash(gh api*)",
"Bash(gh search*)"
]
}
}
Check at init start: read the project's settings files. If the two patterns above are NOT present, ask user via AskUserQuestion:
- Question:
Init needs Bash permissions for gh CLI. Add them? - Header:
Permissions - Options:
Add to .claude/settings.local.json (Recommended)— description:Per-user permission, gitignored. Only affects you on this machine.Add to .claude/settings.json (team-shared)— description:Permission shared with team via git commit. Useful if everyone runs iEvo here.Skip — I'll approve each command manually— description:Each blocked Bash call needs explicit Allow. Slower but no permission file changes.
For Add to ... options: merge the two patterns into the existing permissions.allow array. Do not overwrite other permissions. If file doesn't exist, create with minimal {"permissions": {"allow": [...]}}.
For Skip: continue — but expect blocked commands during the run.
Stop only on missing gh / git / node prereqs. Permission setup is opt-in but strongly recommended.
Auto Mode + classifyAllShell interaction (CC v2.1.193+). The permissions.allow entries above bypass the classifier only under Auto Mode's default behavior, where narrow Bash allow rules (like Bash(gh api*)) resolve before the classifier runs — and only Auto Mode is affected at all; other permission modes are untouched either way. If the user has autoMode.classifyAllShell: true set, that default is suspended: every bash call in this pipeline — 20+ across discovery, indexing, and scanning — is routed through the classifier individually, regardless of permissions.allow. This trades latency for coverage (a classifier round-trip per call instead of an instant allow-rule match) and any call the classifier doesn't recognize as safe may still be blocked, which can interrupt this skill's "execute continuously, without pausing" directive. There's no code-level workaround for this skill: tell the user to disable autoMode.classifyAllShell for the init session, or proceed knowing the whole pipeline now pays the per-call classifier cost.
Step 1.5: Client detection (plugin-wide canonical rule) + Codex environment pre-flight
Client detection (canonical — every other skill's "same rule as Step 1.5" cites this exact rule; issue #461). Evaluate these checks in order and stop at the first one that matches:
$CLAUDECODEis set and$CODEX_CLIis not → Claude Code. Claude Code exportsCLAUDECODE=1into the environment of the commands it runs, making it the one positive Claude Code signal available (the same variabledebug-on/SKILL.md's session-start marker already reads). This check must come before the Codex Desktop markers below, because those markers are ordinary environment variables and are therefore inherited by every descendant process —__CFBundleIdentifier=com.openai.codexin particular is set for the whole Codex Desktop app subtree, so a Claude Code CLI session started from a terminal that Codex Desktop spawned carries it too. Without this positive check that session would detect as Codex and vendor into.agents/skills/, the wrong client's load path (the issue #432 class of bug, reached by a new trigger). The$CODEX_CLI-unset half of the condition keeps check 2 authoritative for the mirror case (a Codex CLI session started from inside a Claude Code shell inheritsCLAUDECODEthe same way).$CODEX_CLIis set → Codex — Codex CLI (terminal) sessions.- A Codex Desktop signal is present → Codex:
CODEX_INTERNAL_ORIGINATOR_OVERRIDE=Codex Desktop, or (macOS only)__CFBundleIdentifier=com.openai.codex(both verified empirically against a live Codex Desktop session, issue #461 — Codex Desktop never sets$CODEX_CLI, which is exactly why the pre-#461 "$CODEX_CLIenv var ONLY" rule misdetected it as Claude Code and then read/wrote the wrong client's config throughout every gated skill). Neither marker is documented in Codex's public environment-variable reference — treat them as best-effort corroborating evidence, not a guaranteed contract, and re-verify if a future Codex release stops setting them. Being the weakest and most inheritance-prone signals, they are deliberately ranked last.
Absent all of the above → Claude Code (unchanged default — this rule adds a positive Codex Desktop detection path plus the positive Claude Code check that bounds it; it does not change what "no signal at all" means).
Still do not key off command -v codex — a Claude Code user may have the Codex CLI installed alongside, which would false-trigger Codex-only behavior on a genuine Claude Code run (unchanged rule).
Every other "$CODEX_CLI set" / "$CODEX_CLI unset" mention in this skill, and in any other iEvo skill/agent that cites "the same rule as Step 1.5" or "per Step 1.5", means this whole ordered rule — never the bare environment variable in isolation, and never the Codex signals without the $CLAUDECODE check that precedes them.
If the host platform is Codex per the rule above, run codex doctor and check the exit code. codex doctor shipped in Codex rust-v0.131.0 (May 18 2026) as a first-class diagnostic across runtime, auth, terminal, network, config, and local state.
codex doctor
Exit 0 → environment healthy, continue to Step 2.
Non-zero exit → surface the doctor output to the user and halt. Show this message:
Codex environment is unhealthy (see `codex doctor` output above). Fix the reported issues and re-run `/ievo:init`.Common fixes: re-login to Codex (
codex login), regenerate auth (codex auth refresh), update Codex CLI to the latest release.
On Claude Code: the client-detection rule above still applies (it's what determined "Claude Code" in the first place, and every other skill/agent citing "Step 1.5" depends on it) — only the codex doctor diagnostic and its halt-on-failure gate are skipped, since Claude Code has no equivalent built-in diagnostic command yet (May 2026). The Step 1 prereq checks above cover the same surface (git / gh / node). Update this skill when Claude Code ships an equivalent.
Step 2: Prepare project directories
Create if missing:
.ievo/evolution/agents/.ievo/evolution/skills/.ievo/log/.ievo/log/hooks/— append-only audit log for lifecycle hook fires (events.log appended by every hook configured via/ievo:hooks-setup).ievo/cache/index/.ievo/hooks/— signal-file directory for lifecycle hooks; Step 11.5 writesinit-completehere, evo/SKILL.md Step 5.5 writesevolution-captured, security-auditor.md Step 6 writessecurity-red(RED-only). Created defensively even if/ievo:hooks-setuphasn't been run yet.ievo/log/pending-reports/— for security-issue reports that couldn't be filed live (gh auth missing, rate limit, repo issues disabled). User can file manually later from these saved bodies.
Plus the platform's vendor root — create only the invoking client's directories (Step 1.5's detection rule), never both:
- Claude Code (Step 1.5: no Codex signal):
.claude/— root for vendored items.claude/agents/— for vendored agents.claude/skills/— for vendored skills (init uses direct file writes via Write tool, NOTnpx skills add)
- Codex (Step 1.5:
$CODEX_CLIset, or a Codex Desktop signal):.agents/skills/— for vendored skills. Codex scans.agents/skillsfrom the working directory up to the repo root, plus$HOME/.agents/skills(Codex skills docs);.claude/*is invisible to Codex, so writing there from a Codex session installs nothing (issue #432). No agents directory — Codex documents no project-level custom-agent load path (see Step 7a's platform filter).
Do NOT touch CLAUDE.md or AGENTS.md here.
Migration check (Claude Code ↔ Codex). Before creating the dirs, check whether
.ievo/evolution/ already holds overlay files (.ievo/evolution/skills/*.md or
.ievo/evolution/agents/*.md). If so, existing iEvo state is present — likely
migrated from another platform on the shared filesystem (e.g. via Codex /import).
Preserve it (create only missing dirs; never overwrite existing overlays) and
tell the user: "Existing iEvo evolution state detected — kept as-is. Init continues
for discovery; your overlays stay active." The idempotent inventory (Step 3)
already prevents re-suggesting installed items.
Step 2.2: Self-register iEvo for team sync (Claude Code, plugin-mode only)
Every candidate this pipeline discovers gets bootstrapped into .claude/settings.json
at install time (Step 9b) so a teammate who git pulls the project auto-receives it.
iEvo itself never got the same treatment — a teammate cloning a project that already
has iEvo installed had no equivalent auto-install path, and had to /plugin install
manually on every machine. This step closes that gap for iEvo's own entry.
Gate — plugin-mode only, no new detection needed. Self-registration only makes
sense when iEvo is running as an installed Claude Code plugin (there's a marketplace
install to register); a vendored copy (manual git clone into a skills directory) has
no marketplace concept to self-register, and writing extraKnownMarketplaces /
enabledPlugins there would advertise a mechanism the current machine isn't using.
Step 0a above already hard-stops the entire pipeline if
${CLAUDE_PLUGIN_ROOT}/.claude-plugin/plugin.json isn't readable — so simply
reaching this step already proves plugin-mode. No additional check is needed.
Platform — skip entirely on Codex. .claude/settings.json is a Claude
Code-specific file. Detect Codex the same way Step 1.5 does ($CODEX_CLI set, or
a Codex Desktop signal — never command -v codex, for the same false-trigger
reason given there) and skip this step there; see Step 2.3 for the Codex-side note.
Action:
- Read or create
.claude/settings.json. - Merge (same merge-not-overwrite semantics as
install-protocol.md§ 9b — preserve every other key, never overwrite):"extraKnownMarketplaces": { "ievo-skills": { "source": { "source": "github", "repo": "ievo-ai/skills" } } }, "enabledPlugins": { "ievo@ievo-skills": true } - Write the merged JSON back.
Failure handling. If the read/merge/write fails (malformed existing JSON, write permission denied, etc.), report it in the final summary (Step 12) and continue — do NOT abort init, same as Step 9's per-item failure handling.
No autoUpdate key. Claude Code's own default for a third-party marketplace
entry is autoUpdate: false — matches the "must remain an explicit user choice"
constraint on auto-updates. Step 12's existing summary already tells the user how
to opt into autoUpdate: true manually; this step doesn't change that.
Idempotent. If extraKnownMarketplaces.ievo-skills and
enabledPlugins["ievo@ievo-skills"] already match the values above, this is a
no-op — safe on every re-run, same as Step 9b's own merge.
This file is committed to git → teammates git pull → Claude Code prompts them to
trust the folder → iEvo itself is now discoverable/installable the same way a
vendored third-party plugin already is via Step 9b.
Step 2.3: Codex — documented limitation, no self-registration (yet)
On Codex, the equivalent bootstrap does not exist upstream today: project-level
.codex/config.toml [plugins.*].enabled entries are silently ignored — only
user-level ~/.codex/config.toml is authoritative (confirmed open as of
2026-07-24: openai/codex#18115).
Writing a project-level entry would silently do nothing, so this step is a
documentation no-op, not a file write:
- Do NOT write anything to
.codex/config.tomlfor iEvo self-registration — there is nothing to gain and it would misleadingly suggest persistence that doesn't happen. - Do NOT change
.codex-plugin/marketplace.json'spolicy.installation(AVAILABLE) as part of this — that governs onboarding/default-install UX for iEvo's own public marketplace across every future Codex user, a separate, deliberately-deferred call, out of scope here. - If the host platform is Codex (Step 1.5's rule), tell the user once in the final summary (Step 12): "iEvo's own project-level auto-bootstrap isn't available on Codex yet — Codex doesn't persist project-scoped plugin config (openai/codex#18115). Install/update iEvo manually on each machine for now."
Step 2.5: Create run-log file (incremental writes — do not defer!)
Critical: the log is written incrementally, after each major step — not as a single flush at the end. If init hangs, crashes, or the user cancels mid-run, the diagnostic log up to the point of failure must be on disk.
Create the file NOW with timestamp and section 0:
LOG_PATH=".ievo/log/init-$(date -u +%Y%m%d-%H%M%S).md"
mkdir -p .ievo/log
cat > "$LOG_PATH" <<EOF
# Init run — $(date -u +%Y-%m-%dT%H:%M:%SZ)
## 0. Plugin metadata
- iEvo plugin: <version from Step 0 banner>
- Plugin commit SHA: <or "marketplace-installed">
- Client: <Codex (\$CODEX_CLI set, or Codex Desktop signal — Step 1.5) | Claude Code (\`claude --version\`)>
- OS: <uname -srm>
- Run started: <ISO-8601 timestamp>
EOF
Remember LOG_PATH for all subsequent steps. Each step below has a Log: instruction — it means append that section to $LOG_PATH immediately, before proceeding to the next step.
If a step takes a long time (e.g. discover.mjs or index-repos for big repos), the user can tail -f $LOG_PATH in another shell and see progress.
Step 3: Build installed inventory
The inventory answers "what is already available to THIS client" — so scan the invoking client's load paths, not the other platform's.
On Claude Code (Step 1.5: no Codex signal), collect names from:
Skills installed:
.claude/skills/<name>/SKILL.md.claude/plugins/*/skills/<name>/SKILL.md~/.claude/skills/<name>/SKILL.md~/.claude/plugins/*/skills/<name>/SKILL.md
Agents installed:
.claude/agents/<name>.md.claude/plugins/*/agents/<name>.md~/.claude/agents/<name>.md~/.claude/plugins/*/agents/<name>.md
Plugins enabled:
- Parse
.claude/settings.jsonfieldenabledPluginskeys
On Codex (Step 1.5: $CODEX_CLI set, or a Codex Desktop signal), collect
names from the Codex-visible skill directories instead:
.agents/skills/<name>/SKILL.md(working directory up to repo root)~/.agents/skills/<name>/SKILL.md
Do NOT parse .claude/settings.json and do NOT count .claude/skills/ /
.claude/agents/ contents as installed on Codex — Codex never loads them. If
.claude/skills/ does contain vendored items (a project previously initialized
from Claude Code — the issue #432 migration case), list them in the log and the
Step 12 summary as "present under .claude/skills/ but not visible to Codex",
and let them re-surface as candidates: re-accepting one re-vendors it to
.agents/skills/, which is the repair path for a Claude-Code-configured
project now driven from Codex. (The existing .ievo/evolution/skills/<name>.md
overlay is preserved by install-protocol.md §9a step 4 when the re-vendored
source matches its recorded source.repo.) Provenance guard for exactly this
re-surface path: when a candidate shares its name with a .claude/skills/
item, compare the candidate's <owner>/<repo> against that overlay's
source.repo before Step 7b — on a mismatch, say so in the candidate's
interview question ("same name, DIFFERENT source than your existing
.claude/skills copy — this is a different item, not a repair") instead of
letting it read as a plain re-install; §9a step 4's source-change rule then
governs the overlay if the user proceeds.
Log section 3 NOW — do not defer. Write the full inventory with complete lists, never truncated — if a project has 26 agents, log all 26 names; "12 iEvo-managed plus N others" loses information needed for step-5 filtering. Format: log-format.md §3.
Step 4: Detect stack and dependencies
Parse manifest files (full per-stack table in the Manifest reference below — covers Python, Node/TS, Rust, Go, Java/Kotlin, Ruby, PHP, Dart, Elixir, .NET, Swift/iOS, Haskell, Clojure, Crystal, OCaml, Nim, Lua, R, Julia, Zig, C/C++, Unreal, Godot, Unity).
For each found manifest, extract direct (top-level) dependency names.
Output stack + deps summary. Log to buffer (section 4).
Manifest reference
Parse the manifest(s) found, extracting direct (top-level) dependency names, per the manifest reference table — covers Python, Node/TS/Bun, Deno, Rust, Go, Java/Kotlin, Ruby, PHP, Dart, Elixir, .NET, Swift/iOS, Haskell, Clojure, Crystal, OCaml, Nim, Lua, R, Julia, Zig, C/C++, Unreal, Godot, Unity. Tag deps with their source manifest for polyglot projects.
Step 4.5: Disambiguate broad categories
For each category present in the project, resolve to sub-types using the ambiguous-category registry (kept inline for stability):
| Broad | Sub-types | Signal hints |
|---|---|---|
i18n |
code-strings, documentation |
.po/.mo/locale/ → code-strings; mkdocs.yml with i18n plugin / docs/locales/ → documentation |
testing |
unit, integration, e2e |
vitest.config/jest.config/pytest.ini → unit; playwright/cypress → e2e; tox/separate integration_tests/ → integration |
security |
app-sec, supply-chain, static-analysis |
Helmet/JWT → app-sec; npm audit/Snyk/Dependabot → supply-chain; bandit/semgrep/CodeQL → static-analysis |
documentation |
user, api, internal |
mkdocs/docusaurus/sphinx → user; openapi/swagger → api |
linting |
style, types, security |
prettier/black/rustfmt → style; mypy/tsc/pyright → types; bandit/semgrep → security |
observability |
logging, tracing, metrics |
structlog/pino → logging; opentelemetry → tracing; prometheus → metrics |
state-mgmt (frontend) |
redux/zustand/mobx/recoil | match dep name in package.json |
build-tools |
bundler/package-manager/task-runner | vite/webpack/rollup vs npm/yarn vs Makefile/just |
database |
orm/query-builder/migrations/driver | sqlalchemy/prisma vs kysely/knex vs alembic/flyway |
packaging |
published, internal-only |
Publish/release CI present (.github/workflows/* invoking pypa/gh-action-pypi-publish, npm publish, cargo publish, twine upload, or equivalent) OR manifest carries non-private registry metadata (package.json without "private": true; pyproject.toml [project.urls] set) → published. Explicit private marker ("private": true; Private :: Do Not Upload classifier) or no registry/publish signal at all → internal-only (resolved directly, no ask — issue #427: python-packaging was declined for a project never published to PyPI). Registry-shaped metadata present but conflicting/incomplete (e.g. [project.urls] set with no publish workflow and no private marker either) → genuinely unresolved, tag packaging/ambiguous |
If signals unclear → tag <category>/ambiguous, ask user in step 7a. For packaging specifically, internal-only/published are terminal resolutions (feed Step 7a's stack-relevance filter directly, no ask needed) — only the true packaging/ambiguous case reaches the step 7a question, phrased as "Is this project published anywhere (PyPI/npm/crates.io/etc.)?" with the usual "Skip category" option dropping all packaging candidates.
Log resolution outcomes (section 4.5).
Step 5: Invoke discover.mjs for candidate discovery (v0.6.0+)
Run our own discovery script (replaces find-skills prereq). It hits skills.sh API directly (https://skills.sh/api/search) — no manual prereq install, no npx skills, no auto-classifier friction.
Step 5a — Build stack input JSON
From Steps 3 + 4 + 4.5 build:
{
"languages": ["python"],
"deps": ["pytest", "fastapi", "sqlalchemy"],
"categories": ["testing", "linting", "security", "frameworks", "databases"],
"frameworks": ["fastapi"]
}
Inputs come from:
languages— detected stack types (Step 4)deps— direct top-level deps from manifests (Step 4)categories— resolved category list (Step 4.5)frameworks— major frameworks present (Step 4)
Write this JSON to disk via the Write tool, NOT via an inline Bash string.
Several supported manifest formats legitimately permit near-arbitrary text in a
dependency line — e.g. requirements.txt PEP 508 environment markers routinely
contain single quotes, such as numpy; python_version=='3.9' — so deps/
categories/frameworks values are not safe to embed textually inside a
quoted shell argument (skills#567: a single quote inside any of them breaks out
of a single-quoted echo argument and the remainder is parsed as unquoted
shell syntax). The Write tool writes literal bytes with no shell involved.
# Write tool (NOT Bash):
# file_path: <project>/.ievo/log/discover-stack-input.json
# content: <the stack JSON built above> (literal bytes, no shell expansion)
.ievo/log/ already exists by this point (created in Step 2.5, mkdir -p .ievo/log) and is gitignored (Step 10) — this file is diagnostic, not project
state.
Step 5b — Invoke discover.mjs via Bash
node "${CLAUDE_PLUGIN_ROOT}/scripts/discover.mjs" --stack-file .ievo/log/discover-stack-input.json --limit 50 --concurrency 8
--stack-file reads the JSON from the fixed path Step 5a just wrote — never
from stdin, and never with the stack JSON text embedded in the command line
itself. discover.mjs contains its own --stack-file hardening
(assertStackFileAllowed/assertStackFileReadable: containment to
<project>/.ievo/, regular-file-only, 256 KiB cap — skills#543), so this call
site inherits that same protection already in place for every other
cross-repo-boundary fetch in this plugin (mirrors evolution_candidates.mjs's
--text-file fix, #523, and the feedback/SKILL.md Step 6 convention of
writing untrusted text via the Write tool rather than an inline shell string).
The script:
- Builds 15-30 queries from the stack (language fundamentals + per-dep + per-category + stack-specific compound + a fixed stack-independent group for general-purpose codebase-audit/planning-advisor meta-tools — not gated behind any detected category, since that class of skill isn't tied to a language/framework/dep)
- Parallel-fetches
https://skills.sh/api/search?q=<q>&limit=10for each - If the
codexCLI is present, also reads its marketplace catalog (codex plugin list --json→available[]) and merges those uninstalled plugins as extra candidates. Absent codex / non-zero exit / unparseable output → silently skipped (no behaviour change for Claude Code-only users). - Deduplicates by skill
id, computesrank_score(log10(installs) × reputation_boost × match_breadth_bonus). Codex plugins carry no install count → get a visibility floor (≈ a 10-install skill) so they surface mid-pack instead of being sliced off by--limit, and are taggedsource_origin: codex-marketplace. - Returns JSON:
{sources, queries, candidates: [{id, name, source_repo, source_origin, installs, quality_tier, matched_queries, rank_score}]}. (Codex candidates always havematched_queries: []— they're grouped via an internal source sentinel that's stripped from the public output; usesource_origin: codex-marketplaceto identify them, notmatched_queries.)sources[]carries one entry per origin —skills.shandcodex-marketplace(withavailable/raw_results/error). Thecodex-marketplaceentry is emitted on every run that reaches discovery (transparent about what was attempted) — when codex is absent it readsavailable: false, raw_results: 0. (The empty-stack early-return, exit code 5, producessources: []before any source is queried — don't read the codex entry unconditionally.) Note:availablemeans "codex produced non-empty stdout" (it can betruealongsideerror: "unparseable codex output"), not "plugins were found" (raw_resultsis the plugin count). Codex candidates carryquality_tier: "unranked"— they have no install count, so the install-based tiers don't apply.
Typical wall-clock: 3-6 seconds for a rich stack. The codex source runs concurrently with the skills.sh queries (Promise.all), so it usually overlaps — but a hung codex binary is capped at its 5 s timeout, which becomes the wall-clock ceiling in that worst case.
Step 5b1 — Handle discover.mjs exit codes
The script exits with distinct codes — branch on them:
| Code | Meaning | What init must do |
|---|---|---|
0 |
Success — all queries returned data | Proceed to Step 5c |
0 + WARN on stderr |
Partial failure — some queries failed, candidates still usable | Log the warning in section 5d, proceed but tell the user "discovery was partial (N/M queries failed)" in the summary |
1 |
No stack input on stdin AND no --stack-file |
Should not happen — init always provides stack JSON. If it does, log and abort. |
3 |
Bad input — malformed JSON, missing file, invalid CLI args | Log and abort init (stack input is broken) |
4 |
Total failure — ALL queries failed (skills.sh down, network outage). candidates: [] |
Log + tell user "discovery failed — skills.sh unreachable". Ask via AskUserQuestion: continue with auto-available repos only OR abort? |
5 |
No queries derived from stack (empty input) | Log + abort init — stack detection (Step 4) produced nothing useful |
Capture stderr separately from stdout: node discover.mjs ... 2>discover.err >discover.out. The structured JSON is on stdout; the WARN/FATAL messages are on stderr.
Step 5c — Filter against installed inventory
From the discover.mjs output candidates[], drop any candidate whose name matches the inventory from Step 3 (already-installed skills/agents/plugins). The script doesn't know the user's installed state; init applies that filter post-hoc.
Step 5d — Log section 5
Log section 5 NOW — do not defer. Format: log-format.md §5
(stack input, sources, queries, ranked-candidates table, dropped-already-installed).
Then pass final_candidates[] to Step 6.
Step 6: Expand via index-repos (parallel local scan)
Extract the unique set of <owner>/<repo> values from discover.mjs' candidates (Step 5). Also include this small list of auto-available repos (not on skills.sh but always relevant):
- anthropics/claude-plugins-official (official, built-in to Claude Code)
- anthropics/claude-code (demo plugins)
For each unique repo, dispatch a repo-indexer sub-agent via Task tool. Send ALL dispatches in a SINGLE message so they run in parallel.
SINGLE MESSAGE with N Task tool calls (one per unique repo):
Task(subagent_type="repo-indexer", prompt="Index <repo-1>. project_root=<abs-path>. force_refresh=false")
Task(subagent_type="repo-indexer", prompt="Index <repo-2>. project_root=<abs-path>. force_refresh=false")
...
Task(subagent_type="repo-indexer", prompt="Index <repo-N>. project_root=<abs-path>. force_refresh=false")
Each sub-agent invokes scan_repo.mjs which does ONE shallow clone + filesystem scan + writes its own index file. They are isolated — no shared state, no contention. The slowest repo determines total wall-clock time (~30-60 sec for big repos like wshobson/agents).
Wait for all to complete. Collect their one-line summaries.
Each repo-indexer writes to <project>/.ievo/cache/index/<owner>-<repo>-<hash>.md (no conflicts — different paths per repo, hash-suffixed so colliding slugs can't overwrite each other; the hash isn't computable from text alone, so resolve the actual file via Glob <owner>-<repo>-*.md rather than assuming the literal name).
(Parallel via sub-agents: ~30-60s cold-cache wall-clock for 8 repos vs ~4-8 min
sequential — slowest repo wins, each has isolated context + returns one summary
line; cache hits sub-second. Surface progress via tail -f .ievo/log/init-*.md.)
Read each generated index and expand the candidate list:
- All standalone skills from index
- All standalone agents from index
- All plugins from index (each plugin = candidate with
type: plugin)
Now your candidate list has three types: skill (vendor), agent (vendor),
plugin (marketplace settings).
Log section 6 NOW — do not defer (index-repos can take 5-15 min for big repos like wshobson/agents). Format: log-format.md §6.
Step 7: Categorical ranking — top-N per category
Filter and rank per category (not overall):
Step 7a — Filter
- Platform filter (Codex only, Step 1.5's detection rule) — drop every
type: agentcandidate, reason"not installable on Codex: Codex loads only skills (.agents/skills); no documented project-level custom-agent path"(per the Codex skills docs — re-check on a major Codex release and lift this filter if agent loading ships). Same visible-drop semantics as the stack-relevance filter below: logged in section 6b with the reason, never silent. Do NOT vendor an agent.mdanywhere on Codex — a copy under.claude/agents/would be invisible to the client that installed it, the exact issue #432 failure. On Claude Code this filter is a no-op. - Drop candidates whose name conflicts with installed inventory (already-installed check applies to expanded list, not just discover.mjs' direct returns).
- Match name + description against stack/deps:
- Direct keyword match (skill named "pytest" for Python project with pytest) → high score
- Description mentions deps from step 4 → medium
- Generic universally-useful (e.g. "code-reviewer") → low but non-zero
- Capability-overlap filter (issue #427 — real
/ievo:inittelemetry: 3 of 4 declines in one run were pure overlap
…(truncated)