wicked-garden 12.57.0 → 12.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@
2
2
  "_comment": "Issue #328: Component declarations for wicked-garden plugin. Claude Code plugin.json does not yet support inline component declarations beyond path overrides. This manifest documents all components for tooling, validation, and future schema support. When plugin.json gains a 'components' field, merge this data into plugin.json. The plugin is skills-only: former commands/ became actions of consolidated per-domain skills, and former agents/ became standalone context:fork worker skills (enumerated under fork_skills). Regenerate derived blocks with scripts/ci/sync_components.py.",
3
3
  "domains": ["agentic", "crew", "data", "demo", "engineering", "jam", "mem", "persona", "platform", "product", "qe", "search"],
4
4
  "summary": {
5
- "skills": 138,
5
+ "skills": 139,
6
6
  "fork_skills": 69,
7
7
  "hooks": 13,
8
8
  "specialists": 7
@@ -13,6 +13,7 @@
13
13
  "agentic-performance-analyst": ["agentic-performance-analyst"],
14
14
  "agentic-safety-reviewer": ["agentic-safety-reviewer"],
15
15
  "archetype": ["archetype"],
16
+ "build-guide": ["build-guide"],
16
17
  "classify": ["classify"],
17
18
  "core": ["core"],
18
19
  "data": ["data"],
@@ -1 +1 @@
1
- {"name": "wicked-garden", "owner": {"name": "Mike Parcewski"}, "plugins": [{"name": "wicked-garden", "source": "./", "description": "Your coding agent already plans and swarms \u2014 Wicked Garden is the curated toolkit for what it can't: proof-not-claims gates, code links grep can't see, deterministic refactor, cross-session memory, multi-model second opinions (fallback seats labelled). Don't fight the harness; fill its gaps. The gate needs wicked-vault (a self-contained infra peer, installed directly) and re-derives through garden's built-in engine; estate/bus are opt-in layers (wicked-testing and wicked-brain are retired \u2014 QE ships in-catalog as the qe domain, memory lives in wicked-estate).", "version": "12.57.0"}], "version": "12.57.0"}
1
+ {"name": "wicked-garden", "owner": {"name": "Mike Parcewski"}, "plugins": [{"name": "wicked-garden", "source": "./", "description": "Your coding agent already plans and swarms \u2014 Wicked Garden is the curated toolkit for what it can't: proof-not-claims gates, code links grep can't see, deterministic refactor, cross-session memory, multi-model second opinions (fallback seats labelled). Don't fight the harness; fill its gaps. The gate needs wicked-vault (a self-contained infra peer, installed directly) and re-derives through garden's built-in engine; estate/bus are opt-in layers (wicked-testing and wicked-brain are retired \u2014 QE ships in-catalog as the qe domain, memory lives in wicked-estate).", "version": "12.59.0"}], "version": "12.59.0"}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wicked-garden",
3
- "version": "12.57.0",
3
+ "version": "12.59.0",
4
4
  "description": "Your coding agent's harness already plans and swarms; Wicked Garden is the curated toolkit for what it can't do alone. Proof, not claims \u2014 gates re-derive 'done' through the internal loom engine (fail-closed, independently attested via wicked-vault), so a self-asserted pass can't lie its way green. Relationships grep can't see \u2014 the wicked-estate code graph + injected bus/dispatch/capability/archetype edges power blast-radius & lineage. Plus deterministic multi-file refactor (wicked-patch), cross-session memory + knowledge (the mem domain over wicked-estate), multi-model second opinions (fallback seats labelled) (jam:council), portable expertise as on-demand skill-refs, and evidence-gated testing (the in-catalog qe domain). It reads the shape of your work to apply the right rigor \u2014 steering, not a pipeline \u2014 then gets out of the way. The compiler stamps a self-contained evidence gate into any repo that runs with no wicked-garden installed. Don't fight the harness; fill its gaps. The evidence gate requires wicked-vault, a self-contained infra peer wicked-garden installs directly; wicked-bus is an opt-in layer \u2014 install what you'll use, the toolkit works without the rest.",
5
5
  "wicked_vault_version": ">=0.8.0",
6
6
  "wicked_ledger_version": "^0.6.0",
@@ -0,0 +1,27 @@
1
+ {
2
+ "mcpServers": {
3
+ "wicked-estate": {
4
+ "command": "wicked-garden",
5
+ "args": [
6
+ "run",
7
+ "scripts/estate_mcp.mjs"
8
+ ],
9
+ "env_vars": [
10
+ "WICKED_ESTATE_MCP_BIN",
11
+ "WICKED_ESTATE_DB",
12
+ "WICKED_HOME",
13
+ "WICKED_MEMORY_DB",
14
+ "WICKED_KNOWLEDGE_DB",
15
+ "WICKED_XEDGE_DB",
16
+ "WICKED_ESTATE_READONLY",
17
+ "WICKED_GOVERNED",
18
+ "WICKED_RUN_ID",
19
+ "WICKED_RUN_UNIT",
20
+ "WICKED_RUN_AGENT",
21
+ "WICKED_RUN_PROJECT",
22
+ "WICKED_GATE_SCOPE",
23
+ "WICKED_WRITE_ROOTS"
24
+ ]
25
+ }
26
+ }
27
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "wicked-garden",
3
+ "version": "12.59.0",
4
+ "description": "The skills your coding agent can't build alone: \"done\" re-checked from evidence, graph-aware search and multi-file refactor, 40 QE specialists, review panels from other models, and cross-session memory. Works in Claude Code, Codex, Antigravity, OpenCode and Pi, in CI, or in a wicked-crew job.",
5
+ "keywords": [
6
+ "coding-agent",
7
+ "evidence-gate",
8
+ "code-graph",
9
+ "refactoring",
10
+ "testing",
11
+ "memory",
12
+ "multi-model"
13
+ ],
14
+ "skills": "./skills",
15
+ "mcpServers": "./.codex-plugin/mcp.json",
16
+ "hooks": {
17
+ "description": "No hooks on Codex: hooks/hooks.json is Claude Code's event model; garden's skills run the gate themselves on skills-only hosts.",
18
+ "hooks": {}
19
+ },
20
+ "interface": {
21
+ "displayName": "Wicked Garden",
22
+ "shortDescription": "Evidence-gated done, graph-aware search and refactor, QE specialists, review panels, cross-session memory.",
23
+ "developerName": "Mike Parcewski",
24
+ "category": "Developer Tools",
25
+ "websiteURL": "https://github.com/mikeparcewski/wicked-garden"
26
+ }
27
+ }
package/CHANGELOG.md CHANGED
@@ -2,6 +2,33 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [12.59.0] — 2026-10-25
6
+
7
+ ### Added
8
+ - **`wicked-garden-build-guide`: discover how a repo builds and tests, and propose its build/test guide (#1332, DES-BUILD-GUIDE; pairs with wicked-core#1066).** The guide is `.wicked/build-guide.json` in contract v1. The wicked-core floor interprets it instead of hardcoding runners, and reads it from the base tree.
9
+ - **Discover.** `scripts/build_guide/discover.py` reads CI config first, then the manifests, follows `make`/`just`/`npm` one level into what they run, and lists every test candidate with its `file:line`. It holds no runner table. The agent then learns each runner's formats and its list, run-one and deselect flags from the runner's own `--version` and `--help`.
10
+ - **Results.** Structured results come first, but only when their ids round-trip through `list`, `alone` and `deselect`. Otherwise the skill writes a text parser: RE2-only, `^…$` rules on the runner's own final summary.
11
+ - **Validate.** The skill writes two mutation probes (`mutation`, `mutation2`), then runs `wicked-core floor probe`, which alone writes `verified`. It stops after at most 3 attempts per check. When the probe is unavailable, it says plainly that the guide is unverified and the floor will not use it for exclusion.
12
+ - **Propose.** The guide lands as a PR, or through the deliver gate inside a crew run. It is never written silently.
13
+ - **Scripts.** `check.py` validates a guide against `schemas/build-guide.v1.json`, vendored byte for byte from wicked-core main (72da31b, the file as #1066 landed it) and pinned to its git blob by a test. The stdlib validator implements `propertyNames`, so the schema's env-name rule is checked. For a test check (one with `list`), it flags what the floor requires: a non-empty `failedExit`, `alone`, `deselect`, a `text`/`junit` kind and two distinct mutation probes. It also flags a missing `version` piece and any required probe that did not pass. It reports the floor's load-time refusals in core's words: an empty `run`, a bad `stage`, a `list` with no `failedExit`, a `0` in `failedExit`, an invalid `cwd` (`""`, a `.` or empty segment, `..`, `\`, `:` or a drive), a check `name` that is not 1-64 of `A-Z a-z 0-9 . _ -` (or is `.`/`..`), an env name that is not `[A-Z_][A-Z0-9_]*` or is a loader/shell hook, and `{ids:re|}` among the per-id tokens or beside `{q}`. JUnit is read with a real XML parser (`xml.etree`), as core reads it with roxmltree. With `--repo`, it warns for every CI step that no check declares in that step's working directory. In the directories its checks cover, a guide's checks replace the floor's detection, so gates such as install, build and lint must be declared too, sub-packages included. `rehearse.py` mirrors the core probe (`list`, `run`, `mutation`, `ids`, `alone`, `deselect`, `multi`, `affected`, `version`) in a scratch copy. Like core, it needs a green clean run. When any probe fails, it re-runs the whole probe once on a fresh copy and records that retry, each result marked `(retried)`. It composes argvs around `--` as core does, and it skips gates. The skill tells the user to onboard from a green commit. `probe.py` wraps the core command, and the probe records `runnerVersion` from the check's `version` piece. Like core, `rehearse.py` runs the guide's `install` step for the check's `cwd` first in every fresh copy (a failed install fails every probe; with an install step nothing is linked to the host repo), and writes a mutation file only inside the copy, never through a link. A check name is unique per `cwd` (one `install` per directory). `discover.py` resolves a step's `make`/`just`/`npm` command against its own `working-directory`, ends a `- run: |` block at the step's other keys, and counts a CI wrapper whose recipe runs the tests (`make check`) as a candidate; `check.py --repo` counts a recipe as declared only when every command in it is, reports a schema-invalid guide instead of crashing, and no longer flags an escaped metacharacter (`C\++`) as a non-RE2 construct.
14
+ - **Seeds** for cargo/libtest (reconciled with wicked-core's own probed guide: merged stderr, binary-qualified ids), pytest (text on the short summary, `--force-short-summary`) and vitest (JUnit, `results.id: ["name"]`); each test check carries two mutation probes and a `version` piece, and each seed declares its fixture's CI gates.
15
+ - **Onboarding.** Full onboarding in `wicked-garden-core` `setup` (step E) hands off to it.
16
+ - **Tests and evals.** `tests/build_guide/` holds real-runner tests on five fixture repos: cargo with a text parser, pytest with JUnit, vitest with a JUnit reporter, a fake runner with no structured output, and a repo with no tests. It also holds five eval cases with a deterministic grader.
17
+ - **Contract notes.** The skill's `refs/contract-notes.md` records the pinned semantics and the coordinator's rulings: `{id:re}`/`{ids:re|}` regex placeholders, the `version` piece, retry once, exit-code classes, and guide checks replacing detection. It also explains why the pytest seed is text: JUnit cannot rebuild a class method's node id.
18
+
19
+ ### Fixed
20
+ - Constrain the MCP TypeScript template's `@opentelemetry/api` dependency to `~1.9.0`, so a fresh `npm install` no longer hits the `@opentelemetry/sdk-logs` peer conflict now that API 1.10.0 is published (#1344).
21
+ - **`install.mjs` finds a binary on PATH without spawning a shell (#1345).** `findBin` ran `command -v` (`where` on Windows) under a 2-second timeout, so on a loaded machine a slow fork read as "not installed": `pack check` said Python was missing, and `install` skipped `uv sync` as if uv were absent. Found by the build guide's floor probe on a host at load 50-110, where `tests/packs/*` and `test_uv_sync_failure_is_surfaced_not_swallowed` failed for that reason alone. It now scans PATH itself and returns the absolute path of the first executable file `command -v` or `where` would name: an empty POSIX entry is the current directory, a relative entry is read against the current directory, and Windows looks in the current directory first and tries each `PATHEXT` extension unless the name already ends with one. The dry-run spawn-guard test's positive control now plants a `python3` on PATH, and its negative plants a `uv` the install would run; new tests pin that uv is found without a child process, that a non-executable file is not a binary, and that relative and empty PATH entries resolve as `command -v` resolves them.
22
+
23
+ ## [12.58.0] — 2026-10-20
24
+
25
+ ### Added
26
+ - **garden is a registered Codex plugin (#1326).** `.codex-plugin/plugin.json` (Codex prefers it over `.claude-plugin/plugin.json`) declares the skills directory (`./skills`; Codex namespaces them `wicked-garden:<skill>`), the `wicked-estate` MCP server (`.codex-plugin/mcp.json`) and an explicit empty `hooks` block, because without one Codex would load `hooks/hooks.json`, which is Claude Code's hook model. Codex installs it from the existing `.claude-plugin/marketplace.json`: `codex plugin marketplace add <garden package root>`, then `codex plugin add wicked-garden@wicked-garden`. The MCP server starts through `wicked-garden run scripts/estate_mcp.mjs`. That new launcher finds `wicked-estate-mcp` the way `scripts/_estate_client.py` does (`WICKED_ESTATE_MCP_BIN`, then PATH, then `~/.local/bin`), so no install path is written into the manifest. The server forwards the binary override, estate's store pins (`WICKED_ESTATE_DB`, `WICKED_HOME`, `WICKED_*_DB`), the read-only opt-in and the run markers through `env_vars`, because Codex hands a stdio MCP server only a default allowlist (PATH, HOME, …) plus the variables named there. The launcher also applies `_estate_client.py`'s rules. It accepts only `--readonly`, `--governed` and `--db <path>`/`--db=<path>`, and refuses a misspelt flag. `--readonly` is added for the flag, for `WICKED_ESTATE_READONLY`, `WICKED_GOVERNED` or `--governed`, and for a run marker. A governed unit with no pinned store is refused. The child gets `[--readonly] [--db <path>]`, the only spelling estate reads. It passes stdio, arguments, cwd and the exit code through, and when no binary is found it prints one JSON line and exits 1. A `wicked-estate` server in the user's Codex config takes precedence over the plugin's.
27
+ - **The Codex manifest is kept in version lockstep and checked in CI (#1326).** `sync-plugin-version.mjs` (the npm `version` hook the release runs) now bumps `.codex-plugin/plugin.json` too, and so does the dev `release.py` (with `package.json`). `scripts/ci/validate.py` runs `scripts/ci/codex_plugin.py`, which applies the rules Codex 0.163 uses: name matches the Claude manifest, and the Codex and Claude versions both match `package.json`; paths start with `./` and stay under the root; the hooks opt-out is present; the estate server goes through a launcher script under the root. It also requires that the skills Codex would discover (recursive, hidden directories skipped, string names of at most 64 characters, a string description present) are exactly the catalog's. `tests/test_codex_plugin_manifest.py` checks that the npm package ships those same skills plus the manifest files.
28
+
29
+ ### Fixed
30
+ - **Governed-worker evaluators defer delivery-only criteria instead of failing them (rule E6, #1328).** A criterion whose evidence exists only after the deliver phase pushes (CI on the opened pull request, the pull request's title or body, a merge, a release) is now DEFERRED, neither PASS nor FAIL. Every part the run can show is still judged, and a failing in-run part still FAILs the item (deny-dominates). The evaluator names each deferred item (`ITEM <n>: DEFERRED — <the artifact it waits for>`, or a "Deferred to delivery" list) so the deliver gate can show it to the operator; a deferred item never fails the verdict by itself. E6 is distinct from E3: an in-run item the creator left as a follow-up is still judged. Pinned by `tests/test_governed_worker_rules.py`.
31
+
5
32
  ## [12.57.0] — 2026-10-18
6
33
 
7
34
  ### Added
@@ -1170,7 +1197,9 @@ through `v9.2.18`. Major themes across that span:
1170
1197
 
1171
1198
  For each of those releases, see the corresponding tag in git history.
1172
1199
 
1173
- [Unreleased]: https://github.com/mikeparcewski/wicked-garden/compare/v12.57.0...HEAD
1200
+ [Unreleased]: https://github.com/mikeparcewski/wicked-garden/compare/v12.59.0...HEAD
1201
+ [12.59.0]: https://github.com/mikeparcewski/wicked-garden/compare/v12.58.0...v12.59.0
1202
+ [12.58.0]: https://github.com/mikeparcewski/wicked-garden/compare/v12.57.0...v12.58.0
1174
1203
  [12.57.0]: https://github.com/mikeparcewski/wicked-garden/compare/v12.56.0...v12.57.0
1175
1204
  [12.56.0]: https://github.com/mikeparcewski/wicked-garden/compare/v12.55.1...v12.56.0
1176
1205
  [12.55.1]: https://github.com/mikeparcewski/wicked-garden/compare/v12.55.0...v12.55.1
package/README.md CHANGED
@@ -69,7 +69,17 @@ installs/updates the whole wicked-\* family (garden, its peers, and the rest).
69
69
  > and the previous copy is put back). Those transient dirs are **not plugins**; a leftover after an interrupted install is
70
70
  > safe to delete (`status` lists them).
71
71
 
72
- **Other CLIs (Codex, OpenCode, Pi, Antigravity)** — `npx wicked-installer install-<cli> wicked-garden` copies
72
+ **Codex (codex-cli with `codex plugin`, 0.163+)** — garden is a registered Codex plugin: `.codex-plugin/plugin.json`
73
+ declares the skills (`./skills`, namespaced `wicked-garden:<skill>`), the `wicked-estate` MCP server (started through
74
+ `wicked-garden run scripts/estate_mcp.mjs`, which finds `wicked-estate-mcp` the way garden's scripts do:
75
+ `WICKED_ESTATE_MCP_BIN` → PATH → `~/.local/bin`; that override, estate's store pins and the read-only/run markers are forwarded through `env_vars`, since Codex passes an MCP server only those plus PATH/HOME and the like, and `WICKED_ESTATE_READONLY` or a run marker adds `--readonly` as garden's estate client does), and no hooks (`hooks/hooks.json` is Claude Code's). By hand:
76
+ `npm i -g wicked-garden`, then `codex plugin marketplace add "$(npm root -g)/wicked-garden"` and
77
+ `codex plugin add wicked-garden@wicked-garden`. wicked-installer's Codex script does the same (pick Codex in
78
+ `npx wicked-installer`, or run `node "$(npm root -g)/wicked-installer/dist/install-codex.js" wicked-garden`) and
79
+ removes the loose skill copies it made earlier. A `[mcp_servers.wicked-estate]` entry in your Codex config takes
80
+ precedence over the plugin's server.
81
+
82
+ **Other CLIs (OpenCode, Pi, Antigravity, and Codex without `codex plugin`)** — `npx wicked-installer install-<cli> wicked-garden` copies
73
83
  the skills only (flat, by name — no `scripts/`, no venv). Every skill's text works there as written: own files are
74
84
  referenced relative to the skill's directory, other skills by name, and the shared runtime only through the
75
85
  launcher — `wicked-garden run scripts/<x>.py …`. So for script-backed skills the runtime prerequisite on those
@@ -130,7 +140,7 @@ false "done" runs into depends on the host and the entry path:
130
140
  |---|---|---|
131
141
  | **Any host — the gate, invoked** (`wicked-garden-prove`, an archetype's produces-gate → `scripts/qe/vault_gate.py`) | Re-hashes the recorded evidence and re-runs its verifier through `wicked-vault`: a false claim is **rejected**, an unresolvable backend **fails closed** (`gate: "unavailable"`). Hard gates also need an attestation under an explicit `--actor` that differs from the author's — a local label, not an authenticated identity. The verdict stops whatever acts on it. | With `--no-require`, a backend that cannot run falls back to the claim-only tracker and says so (`claim-only`, not re-derived); a working backend still re-derives. |
132
142
  | **Claude Code — plugin hooks** (plain conversation, "just work") | `PreToolUse` denies a few specific tool calls: native `EnterPlanMode` (always — planning goes through the archetype playbooks), writes to `MEMORY.md` / the auto-memory dir, build-phase writes on a high-complexity crew project with no challenge artifacts, a `phase_manager.py approve` whose gate preflight fails, a worktree cwd leak, and — only in `strict` mode (default `warn`) — invalid task metadata (`WG_TASK_METADATA`) and orphan state writes (`WG_BUS_EMIT_LINT`). Prompts are held until `/wicked-garden-core setup` completes. | Everything else. The archetype detector **steers** (it does not gate), the Stop-time claim sentinel is a fail-open **nudge** when a "done/passing" claim has no verdict for HEAD, `TaskCompleted` never blocks, and output governance is advisory. A completion claim is **not** blocked unless a gate is run. |
133
- | **Codex · OpenCode · Pi · Antigravity — skills only** (`npx wicked-installer install-<cli> wicked-garden`) | Nothing by itself: no plugin hooks are installed on those hosts. | The skill text tells the agent to run the gate; it blocks only when the agent does run it (and the launcher + `wicked-vault` are present). |
143
+ | **Codex (plugin) · OpenCode · Pi · Antigravity — skills only** (`npx wicked-installer install-<cli> wicked-garden`) | Nothing by itself: no hooks are installed on those hosts (the Codex plugin opts out of them). | The skill text tells the agent to run the gate; it blocks only when the agent does run it (and the launcher + `wicked-vault` are present). |
134
144
  | **Compiled repo gate** (`/wicked-garden-prove compile <repo> --trigger hook,ci`) | `.wicked/gate.py` fails the **git pre-push hook / CI job** it is wired into, with no wicked-garden installed. | Without a trigger it is a script you run by hand. |
135
145
  | **wicked-crew governed run** | crew's engine decides each gated phase deny-dominates, on the layers that actually ran — deterministic floors, repo checks, policies, a judge. Review moves to a distinct seat when the roster has an eligible one; team runs refuse without one. | An ordinary run on a too-small roster keeps review on the creator seat and labels it "evaluator ≠ creator not held"; a pinned floor with no distinct judge is labelled "floor only"; a phase nothing gated is "approved by default, not verified". |
136
146
 
package/install.mjs CHANGED
@@ -49,14 +49,14 @@
49
49
  * diagnostic names where the previous and the failed trees were left).
50
50
  */
51
51
  import {
52
- closeSync, constants, cpSync, existsSync, fstatSync, lstatSync, mkdirSync, openSync,
53
- readdirSync, readFileSync, realpathSync, renameSync, rmSync,
52
+ accessSync, closeSync, constants, cpSync, existsSync, fstatSync, lstatSync, mkdirSync, openSync,
53
+ readdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync,
54
54
  } from "node:fs";
55
- import { join, dirname, isAbsolute, relative, resolve, sep } from "node:path";
55
+ import { delimiter, join, dirname, isAbsolute, relative, resolve, sep } from "node:path";
56
56
  import { homedir } from "node:os";
57
57
  import { fileURLToPath } from "node:url";
58
58
  import { randomBytes } from "node:crypto";
59
- import { execSync, spawnSync } from "node:child_process";
59
+ import { spawnSync } from "node:child_process";
60
60
 
61
61
  const __dirname = dirname(fileURLToPath(import.meta.url));
62
62
  const pkg = JSON.parse(readFileSync(join(__dirname, "package.json"), "utf8"));
@@ -97,16 +97,32 @@ function skip(src) {
97
97
  return false;
98
98
  }
99
99
 
100
+ // The first `name` on PATH, as `command -v` (POSIX) or `where` (Windows) would find it, read from
101
+ // the filesystem: no subprocess. The old `command -v` spawn had a 2 s timeout, and on a loaded
102
+ // machine a slow fork read as "not installed": pack tooling said Python was missing, and `install`
103
+ // skipped `uv sync` as if uv were absent. Parity with those commands: an empty POSIX entry is the
104
+ // current directory; Windows looks in the current directory first and tries each PATHEXT extension
105
+ // (unless the name already ends with one). The result is absolute, so a caller may run it from
106
+ // another working directory.
100
107
  function findBin(name) {
101
- try {
102
- const out = execSync(
103
- process.platform === "win32" ? `where ${name}` : `command -v ${name}`,
104
- { stdio: ["ignore", "pipe", "ignore"], timeout: 2000, encoding: "utf8" }
105
- ).trim().split("\n")[0];
106
- return out || undefined;
107
- } catch {
108
- return undefined;
108
+ const win = process.platform === "win32";
109
+ const pathext = (process.env.PATHEXT || ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean);
110
+ const exts = win && !pathext.some((e) => name.toLowerCase().endsWith(e.toLowerCase())) ? pathext : [""];
111
+ const dirs = (process.env.PATH || "").split(delimiter).map((d) => (d === "" && !win ? "." : d)).filter(Boolean);
112
+ if (win) dirs.unshift(".");
113
+ for (const dir of dirs) {
114
+ for (const ext of exts) {
115
+ const candidate = resolve(dir, name + ext);
116
+ try {
117
+ if (!statSync(candidate).isFile()) continue;
118
+ if (!win) accessSync(candidate, constants.X_OK);
119
+ return candidate;
120
+ } catch {
121
+ // not here, or not executable: keep looking
122
+ }
123
+ }
109
124
  }
125
+ return undefined;
110
126
  }
111
127
 
112
128
  // ---------------------------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wicked-garden",
3
- "version": "12.57.0",
3
+ "version": "12.59.0",
4
4
  "type": "module",
5
5
  "description": "The skills your coding agent can't build alone: \"done\" re-checked from evidence, graph-aware search and multi-file refactor, 40 QE specialists, review panels from other models, and cross-session memory. Works in Claude Code, Codex, Antigravity, OpenCode and Pi, in CI, or in a wicked-crew job.",
6
6
  "keywords": [
@@ -34,7 +34,7 @@
34
34
  "main": "install.mjs",
35
35
  "scripts": {
36
36
  "test": "node scripts/ci/run_pytest.js",
37
- "version": "node .github/scripts/sync-plugin-version.mjs && git add .claude-plugin/plugin.json .claude-plugin/marketplace.json",
37
+ "version": "node .github/scripts/sync-plugin-version.mjs && git add .claude-plugin/plugin.json .claude-plugin/marketplace.json .codex-plugin/plugin.json",
38
38
  "prepublishOnly": "node install.mjs --version"
39
39
  },
40
40
  "engines": {
package/schemas/README.md CHANGED
@@ -26,6 +26,7 @@ cosmetic and pre-dates them):
26
26
  | Schema | Format it defines | Version field | Validated by |
27
27
  |--------|-------------------|---------------|--------------|
28
28
  | `wicked-pack.schema.json` | Third-party skill-pack manifest (`wicked-pack.json`) | `spec` (currently 1) | `npx wicked-garden pack check <dir>` (`scripts/pack/check.py`) |
29
+ | `build-guide.v1.json` | A repo's build/test guide (`.wicked/build-guide.json`, DES-BUILD-GUIDE contract v1). **Vendored byte for byte from wicked-core** main (re-vendor whenever core's copy changes) | `version` (1) | `scripts/build_guide/check.py`; `tests/build_guide/test_build_guide_check.py` pins the copy to core's git blob |
29
30
  | `campaign-recon.schema.json` | qe campaign recon + plan artifact (capability inventory, environment-manifest ref, dependency-ordered scenario ladder with pass criteria, claim ceilings and per-rung `isolation` — ADR 0006) | `spec` (1 \| 2 — v2 is the TH-16+TH-22 shared bump: rung `isolation` + desktop stub-eligibility; v1 plans still validate) | `tests/qe/test_campaign_recon_schema.py` (fixture round-trip + nonconforming rejection). The sibling evidence contract (`scenario_evidence` + `claim_level`) is owned by wicked-ledger (manifest 2.1). |
30
31
 
31
32
  ## Usage
@@ -0,0 +1,210 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://wickedagile.com/schemas/build-guide.v1.json",
4
+ "title": "Build guide (DES-BUILD-GUIDE, contract v1)",
5
+ "description": "A repository's build/test guide: `.wicked/build-guide.json`, committed, read by the repo-checks floor from the BASE tree only. When the base carries a guide, its `checks` (for the stage) ARE the floor's checks for the directories they cover (their `cwd`s, at any stage): there their argv, cwd and stage replace detection (a build, lint or install step is declared as a check), while a detected package no check covers keeps its detected checks, exit codes only. A STALE guide (a `derivedFrom` file changed on the base since the guide was last committed or since a check was verified, or the change touches one) may name outdated commands: in the directories it covers, the floor then runs the UNION of its checks and the detected ones (an identical argv in the same directory once), more checks and never fewer. Accepted by design: a directory whose checks are all `verify`-only has no checks at the creator floor; the creator floor is fast feedback, and the verify floor runs every check before delivery. With no guide, or one that does not parse or fails the load-time checks (a non-empty `run`, `stage` values, `failedExit` on a check with a `list`), detection is the whole floor. Each check's pieces are checked by `wicked-core floor probe`; the floor relies on a check's exclusion only when every probe passed on a commit that is an ancestor of the base, no `derivedFrom` or `invalidatedBy` file changed on the base since, and the change touches neither (a deletion, a rename's old path and a type change, such as a file replaced by a link, count as touching). All regexes are RE2 syntax, applied to one line at a time.",
6
+ "type": "object",
7
+ "required": ["version", "checks"],
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "version": { "const": 1 },
11
+ "derivedFrom": {
12
+ "description": "Repo-relative paths (git pathspecs: globs such as `crates/*/Cargo.toml` match) the guide was derived from (CI files, manifests): a change to one, on the base since the guide or a check's verification or in the change itself, makes the guide stale: no check's exclusion is relied on, and detection runs beside the guide's checks where they cover a directory.",
13
+ "type": "array",
14
+ "items": { "type": "string" }
15
+ },
16
+ "checks": { "type": "array", "items": { "$ref": "#/$defs/check" } }
17
+ },
18
+ "$defs": {
19
+ "argv": { "type": "array", "items": { "type": "string" }, "minItems": 1 },
20
+ "regex": { "type": "string", "minLength": 1 },
21
+ "mutation": {
22
+ "type": "object",
23
+ "required": ["file", "content", "expectId"],
24
+ "additionalProperties": false,
25
+ "properties": {
26
+ "file": { "type": "string", "description": "Relative to `cwd`." },
27
+ "content": { "type": "string", "description": "A deliberately failing test." },
28
+ "expectId": { "type": "string" },
29
+ "expectQ": { "type": "string" }
30
+ }
31
+ },
32
+ "check": {
33
+ "type": "object",
34
+ "description": "A test check has a `list` (and so `results`); a gate check (a build, a lint, an install) has neither and gates on its exit code alone. A check named `install` is the floor's install step: the install timeout, never compared with the base, and a failure reads as an environment finding; declare it before the checks that need it (the floor's sandbox confines writes, not the network). `version` is for test checks only.",
35
+ "required": ["name", "run"],
36
+ "dependentRequired": { "list": ["results", "failedExit"], "version": ["list"] },
37
+ "additionalProperties": false,
38
+ "properties": {
39
+ "name": { "type": "string", "pattern": "^(?!\\.\\.?$)[A-Za-z0-9._-]{1,64}$", "description": "1-64 of `A-Z a-z 0-9 . _ -`, never `.` or `..` (it keys files the engine writes). A check named `install` is the floor's install step." },
40
+ "cwd": {
41
+ "type": "string",
42
+ "default": ".",
43
+ "description": "Relative to the repo root: `.` or `/`-separated segments, none empty, `.` or `..`, and no `\\` or `:` (no Windows drive).",
44
+ "pattern": "^(\\.|(?!.*(^|/)\\.\\.?(/|$))[^/\\\\:]+(/[^/\\\\:]+)*)$"
45
+ },
46
+ "stage": {
47
+ "description": "The floor stages the check runs at; absent or empty = both.",
48
+ "type": "array",
49
+ "items": { "enum": ["creator", "verify"] }
50
+ },
51
+ "run": {
52
+ "$ref": "#/$defs/argv",
53
+ "description": "The exact argv. Exclusion happens only for a floor check whose argv equals this exactly: the guide is the allowlist."
54
+ },
55
+ "failedExit": {
56
+ "description": "Exit codes that mean 'tests failed'. Any other non-zero exit (a crash, a build error) decides the check `could_not_run`. A failing exit whose results name no test gives the base no candidates (`base_red_unseparated`), and a deselected head run that fails with nothing named is still a failure (`new_failure`): exclusion never passes a run that failed.",
57
+ "type": "array",
58
+ "minItems": 1,
59
+ "items": { "type": "integer", "not": { "const": 0 } }
60
+ },
61
+ "results": {
62
+ "type": "object",
63
+ "required": ["kind"],
64
+ "additionalProperties": false,
65
+ "properties": {
66
+ "kind": {
67
+ "enum": ["text", "junit", "json-lines", "tap"],
68
+ "description": "v1 reads `text` and `junit`; `json-lines` and `tap` are reserved."
69
+ },
70
+ "how": {
71
+ "type": "object",
72
+ "additionalProperties": false,
73
+ "properties": {
74
+ "argvAppend": {
75
+ "description": "Added to every argv whose results are read (the base run, the probes' runs). Composition with an argv that has an end of options (`--`), for this and for `deselect.append`: the tokens before the list's own `--` (all of them when it has none) go just before the argv's `--` (at the end when it has none); the tokens after it go last, behind ONE `--`. A second `--` is never emitted.",
76
+ "type": "array",
77
+ "items": { "type": "string" }
78
+ },
79
+ "env": { "type": "object", "propertyNames": { "pattern": "^(?!(LD_|DYLD_))(?!(BASH_ENV|ENV|SHELLOPTS|BASHOPTS|IFS|GCONV_PATH|LOCPATH)$)[A-Z_][A-Z0-9_]*$" }, "additionalProperties": { "type": "string" }, "description": "Set on the command INSIDE the floor sandbox only (never on the sandbox launcher). Names are `[A-Z_][A-Z0-9_]*` and never a loader or shell hook: `LD_*`, `DYLD_*`, `BASH_ENV`, `ENV`, `SHELLOPTS`, `BASHOPTS`, `IFS`, `GCONV_PATH`, `LOCPATH`." },
80
+ "path": { "type": "string", "description": "The results file, relative to `cwd`, when not stdout." },
81
+ "merge": {
82
+ "type": "boolean",
83
+ "description": "Capture stderr into the same stream as stdout, in order (a runner that names its test binaries on stderr)."
84
+ }
85
+ }
86
+ },
87
+ "id": {
88
+ "description": "(junit) The testcase attributes whose values, joined by `idJoin`, form an id. Default [\"classname\", \"name\"].",
89
+ "type": "array",
90
+ "items": { "type": "string" }
91
+ },
92
+ "idJoin": {
93
+ "description": "(junit) What joins the `id` attributes (default `::`), so the ids can be the list's.",
94
+ "type": "string"
95
+ }
96
+ }
97
+ },
98
+ "parser": {
99
+ "description": "Only for results.kind = text.",
100
+ "type": "object",
101
+ "additionalProperties": false,
102
+ "properties": {
103
+ "section": {
104
+ "$ref": "#/$defs/regex",
105
+ "description": "Lines that open a qualifier scope (a test binary), with a named group `q`; ids are (q, id)."
106
+ },
107
+ "failed": {
108
+ "type": "array",
109
+ "items": { "$ref": "#/$defs/regex" },
110
+ "description": "Per-line failures, each with a named group `id`."
111
+ },
112
+ "blocks": {
113
+ "type": "array",
114
+ "items": {
115
+ "type": "object",
116
+ "required": ["start", "item", "end"],
117
+ "additionalProperties": false,
118
+ "properties": {
119
+ "start": { "$ref": "#/$defs/regex" },
120
+ "item": { "$ref": "#/$defs/regex", "description": "With a named group `id`." },
121
+ "end": { "$ref": "#/$defs/regex" },
122
+ "use": {
123
+ "enum": ["last-before-end", "all"],
124
+ "default": "last-before-end",
125
+ "description": "A block is the contiguous item lines (blank lines allowed) after a start; any other line ends it. At an end, the last block since the previous end (or every one) is taken."
126
+ }
127
+ }
128
+ }
129
+ }
130
+ }
131
+ },
132
+ "list": {
133
+ "type": "object",
134
+ "required": ["run", "item"],
135
+ "additionalProperties": false,
136
+ "properties": {
137
+ "run": { "$ref": "#/$defs/argv" },
138
+ "item": { "$ref": "#/$defs/regex", "description": "With a named group `id`." },
139
+ "section": {
140
+ "$ref": "#/$defs/regex",
141
+ "description": "With a named group `q`. Without it ids carry no qualifier, and results are compared by id alone."
142
+ },
143
+ "merge": { "type": "boolean" }
144
+ }
145
+ },
146
+ "alone": {
147
+ "type": "object",
148
+ "required": ["run"],
149
+ "additionalProperties": false,
150
+ "properties": { "run": { "$ref": "#/$defs/argv", "description": "With `{id}` (and optionally `{q}`, `{id:re}`, `{ids:re|}`, as in `deselect.append`): values fill whole argv elements and never pass through a shell, so no escaping is needed." } }
151
+ },
152
+ "deselect": {
153
+ "type": "object",
154
+ "required": ["append"],
155
+ "additionalProperties": false,
156
+ "properties": {
157
+ "append": {
158
+ "type": "array",
159
+ "items": { "type": "string" },
160
+ "description": "`{ids:re|}` and `{q}` never appear in the same `deselect.append` (the pattern cannot bind ids to qualifiers). Tokens before the first one with `{id}`/`{q}` are appended once, the tokens from the first through the last such one are repeated once per id, the rest once. So a flag and its value must be ONE token (`--skip={id}`, not `--skip`, `{id}`). `{id}`/`{q}` values fill whole argv elements verbatim and never pass through a shell: no escaping is needed. For a runner that takes a regex: `{id:re}` is the id regex-escaped, repeated per id like `{id}`; `{ids:re|}` is EVERY id, regex-escaped and joined by `|`, in ONE token that is never repeated (`--testNamePattern=^(?!(?:{ids:re|})$)`); a guide that puts `{ids:re|}` among the per-id tokens is refused. The tokens are then composed onto the argv around its `--` (see `results.how.argvAppend`)."
161
+ }
162
+ }
163
+ },
164
+ "affected": {
165
+ "type": "object",
166
+ "required": ["run"],
167
+ "additionalProperties": false,
168
+ "properties": { "run": { "$ref": "#/$defs/argv", "description": "With `{files}`; absent = the full run at every stage." } }
169
+ },
170
+ "version": {
171
+ "description": "The runner's version command. `floor probe` runs it and records its first output line (stdout and stderr merged) as `verified.runnerVersion`; the floor runs it too, and a check whose runner now prints something else is stale (exit codes only).",
172
+ "type": "object",
173
+ "required": ["run"],
174
+ "additionalProperties": false,
175
+ "properties": { "run": { "$ref": "#/$defs/argv" } }
176
+ },
177
+ "invalidatedBy": {
178
+ "description": "Globs (`**`, `*`, `?`): a file that matches one, changed on the base since the check was verified or touched by the change (deleted, renamed away or type-changed included), disables exclusion for this check.",
179
+ "type": "array",
180
+ "items": { "type": "string" }
181
+ },
182
+ "probes": {
183
+ "type": "object",
184
+ "additionalProperties": false,
185
+ "properties": {
186
+ "mutation": { "$ref": "#/$defs/mutation" },
187
+ "mutation2": {
188
+ "$ref": "#/$defs/mutation",
189
+ "description": "A second deliberately failing test (another file): with both written, both must be parsed and deselecting both must run clean."
190
+ }
191
+ }
192
+ },
193
+ "verified": {
194
+ "description": "Written by `wicked-core floor probe`, which first runs the guide's `install` check for the same `cwd` in every fresh copy it probes (an install that fails fails every probe). `results` keys: list, run, ids, alone, deselect, mutation, multi, plus affected and version when the check has that piece. The probes need a green clean run. When any probe fails, the whole probe re-runs once on a fresh copy and that retry's results are recorded, each marked ` (retried)`: `pass (retried)` counts as a pass, `fail: … (retried)` does not.",
195
+ "type": "object",
196
+ "additionalProperties": false,
197
+ "properties": {
198
+ "baseSha": { "type": "string" },
199
+ "runnerVersion": { "type": "string", "description": "The `version` command's first output line when the probes ran; empty without a `version` piece." },
200
+ "at": { "type": "string" },
201
+ "results": {
202
+ "type": "object",
203
+ "additionalProperties": { "type": "string", "pattern": "^(pass( \\(retried\\))?|fail: .*)$" }
204
+ }
205
+ }
206
+ }
207
+ }
208
+ }
209
+ }
210
+ }