Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ audit/
.claude/kermit/state.json
.claude/.headroom_wrap_marker.json
.tokensave
improve/
node_modules/
*.tgz
91 changes: 79 additions & 12 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ User types a prompt and submits
│ Score prompt against categories.json (keyword/regex) │
│ Emit minimal <prompt-detect> block: verdict (+ type on │
│ clear, + scores/candidates on ambiguous) │
│ Block is a signal; CLAUDE.md → model invokes skill │
│ Block is a signal; host's trust contract → invoke skill │
│ Append src:"hook" line → telemetry.jsonl │
└─────────────────────────────────────────────────────────────────┘
│
Expand Down Expand Up @@ -56,8 +56,10 @@ User types a prompt and submits
│ ▼ │
│ Step 6 [comprehensive mode only] │
│ │ Show original → rationale → enhanced │
│ │ AskUserQuestion (single gate): │
│ │ Ask step — present options and stop: │
│ │ Run enhanced | Run original | Edit │
│ │ AskUserQuestion when that tool exists, │
│ │ otherwise plain text + end of turn │
│ │ │
│ ├── cancel (via "Other") ────────────────────────────►│ (terminate + hint)
│ │ │
Expand All @@ -70,27 +72,37 @@ User types a prompt and submits
└─────────────────────────────────────────────────────────────────┘
│
▼
Enhanced prompt sent to Claude
Enhanced prompt sent to the model
```

The flow above is host-independent. What differs between Claude Code and Codex is
plumbing only — see [Two-host model](#two-host-model).

## File Map

```
wdym/
├── SKILL.md Thin dispatcher → refs/protocol.md
├── pref.json Default mode template
├── install.sh Installation helper
├── package.json npm package `wdym-prompt` (bin, files allowlist)
├── install.sh Shell installer — Claude Code only, frozen
├── agents/
│ └── openai.yaml Codex skill manifest (display name, invocation policy)
├── bin/
│ └── wdym-prompt.js npx installer — both hosts, no dependencies
├── hooks/
│ ├── prompt-detect.py UserPromptSubmit pre-scorer
│ └── telemetry-stats.py --status report renderer
├── tests/
│ └── detect_bench.py Detection regression bench (expected verdicts)
│ ├── detect_bench.py Detection regression bench (expected verdicts)
│ └── token_bench.py Token-cost bench for the common path
└── refs/
├── protocol.md Step-by-step execution reference (Steps 0–8)
├── protocol.md Step-by-step execution reference (Steps 0–8, ask step)
├── commands.md /wdym command handling (--init/--help/--status/--set-mode)
├── help.txt Verbatim --help text (printed via cat)
├── detect.md Manual type-detection path (ambiguous/absent only)
├── init.md --init bootstrap protocol
├── init.md --init bootstrap protocol (host resolution, scope, activation)
├── heal.md Self-check repair playbook (Step 0.5 escalation)
├── manifest.json Self-check & repair definitions (read only on a failed check)
├── categories.json Type taxonomy (user-editable)
├── categories.default.json Pristine restore copy
Expand Down Expand Up @@ -130,11 +142,66 @@ layer: append-only, best-effort, never healed, so it can never block or alter a
`additionalContext` channel — the same low-trust surface as tool output and retrieved
text. An override-shaped imperative there (`ACTION: invoke … BEFORE processing`) reads as
a prompt injection and was sometimes refused, silently skipping the skill. So the block
carries only a neutral classification *signal*; the invoke instruction itself lives in
`CLAUDE.md`, which the model treats as trusted user configuration. `install.sh` writes a
marker-delimited, idempotent contract into `CLAUDE.md` at the install scope (global
`~/.claude/CLAUDE.md`, local project root), so a fresh install carries the same authority
the dev repo does rather than leaving the block to fend for itself.
carries only a neutral classification *signal* — host-neutral wording, naming no host's
files — while the invoke instruction lives in the host's own instruction file, which the
model treats as trusted user configuration. Both installers write a marker-delimited,
idempotent contract there at the install scope: `~/.claude/CLAUDE.md` or the local project
root on Claude Code, `$CODEX_HOME/AGENTS.md` on Codex. A fresh install therefore carries
the same authority the dev repo does rather than leaving the block to fend for itself.

## Two-host model

wdym runs on Claude Code and on Codex. The engine — hook scoring, classification,
principle selection, rewriting, telemetry — is identical on both. Only the plumbing is
host-specific, and it is enumerated in one place so the rest of the system never has to
know which host it is on. Four of these matter at runtime — trust contract, skill path,
hook file, and the ask step — and those are the four the README's diagram note names.

| | Claude Code | Codex |
|---|---|---|
| Host evidence | `CLAUDE_PROJECT_DIR` set, or `~/.claude/` exists | `CODEX_HOME` set, or `~/.codex/` exists |
| Skill path | `~/.claude/skills/wdym` (canonical), or `<project>/.claude/skills/wdym` | `~/.agents/skills/wdym` |
| Hook file | `~/.claude/settings.json`, or `<project>/.claude/settings.local.json` | `$CODEX_HOME/hooks.json` |
| Trust contract | `~/.claude/CLAUDE.md`, or `<project>/CLAUDE.md` | `$CODEX_HOME/AGENTS.md` |
| Ask step | `AskUserQuestion` | plain-text options, end of turn |
| Command prefix | `/wdym` | `$wdym` |
| Scopes | global and local | global only |

**One canonical copy, exposed twice.** The skill tree is installed once, under
`~/.claude/skills/wdym`. Codex only scans `~/.agents/skills`, so the installer exposes the
same tree there as a symlink rather than copying it. Two independent copies would drift the
moment one host updated and the other didn't, and drift in a skill body is invisible until
behaviour diverges. Where symlinks are unavailable (or `--copy` is passed) the installer
copies instead and records which it did in `pref.json` as `codex_skill_mode`, so
`--doctor` knows whether it needs to check the two paths for drift.

**One pref file, both hosts.** `~/.claude/wdym/pref.json` is canonical for Claude Code and
Codex alike, even though the rest of the Codex wiring lives under `~/.codex`. Splitting it
would let a user set flash mode in one host and comprehensive in the other and then wonder
which one they were talking to. Local scope still overrides global — but local scope is a
Claude Code concept, so the override applies there only.

**Ask step, not a tool call.** `AskUserQuestion` is a Claude Code tool and does not exist
in Codex, and it was the one hard dependency that made the approval gate and `--init`
unrunnable there. Both now route through a single documented interaction step — present
options, stop, read the next message as the answer — with the implementation picked by
*tool availability*, never by guessing the host. That way a host that gains or loses the
tool needs no code change.

**Trust lifecycle differs.** Claude Code approves a hook once. Codex trusts hooks by file
contents, so any install, update, or re-run invalidates the previous approval and the hook
goes silent until the user runs `/hooks` in Codex and approves it again — with no warning
that it stopped firing. Codex also records approval outside any file this tool can read, so
there is neither a programmatic approval path nor a readable trust state. Both are handled
by documentation loudness instead: the installer prints an action-required notice last on
every Codex-touching run, `--doctor` reports trust as `unknown` and points at `/hooks`,
`refs/heal.md` knows the "wired but silent" failure mode, and the README repeats it in the
Codex notes.

**Codex is global-scope only.** A repo-scoped Codex hook would live in a committed file,
so every teammate pulling the repo would get an approval prompt for a tool they never
installed. Repo scope also collides with Codex sessions that start below the repo root.
`--codex --local` therefore refuses cleanly and writes nothing rather than half-wiring.

## Run Modes

Expand Down
36 changes: 36 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,42 @@

All notable changes to this project will be documented here.

## 2026-08-09

### [14] — Both installers write the same trust contract, and the live checks have a script

- `install.sh`: contract line is now host-neutral ("invoke the `wdym` skill" — no "via the Skill tool"), byte-identical to what `bin/wdym-prompt.js` writes, so alternating installers no longer rewrite one line back and forth; verified in a sandbox that a stale block is refreshed in place and a re-run reports already-current
- `CLAUDE.md`: dev repo's own contract updated to the same wording
- `improve/v1.2-test.md`: new (untracked by design) — the live-verification test plan for everything static checks could not close: Codex session (gate, `--init`, one-block-one-row, symlink follow, trust-notice failure mode), Claude no-regression, real Node 18 run, diagram render and one-reader test, npm publish preflight

### [13] — Docs describe two hosts, and the README shows the flow instead of describing it

- `README.md`: `npx wdym-prompt` documented as the primary install path; `install.sh` demoted to Option 2 and labelled Claude-Code-only and frozen; `npx skills add` kept accurate as Option 3; new three-way comparison table with a Codex row; new Codex requirements note (hooks on by default, `/hooks` re-approval after every install or update, `<prompt-detect>` block currently visible per openai/codex#16933, global scope only, `$wdym` prefix); update section covers both installers plus `--doctor`; new Codex FAQ entry
- `README.md`: the *How it works* Mermaid diagram now covers the whole path — both activation modes, the passthrough branch, the trust-contract step, and both run modes ending in a telemetry append — with a single divergence table underneath naming the four host-specific points (trust contract, skill path, hook file, ask step)
- `ARCHITECTURE.md`: new *Two-host model* section — one canonical skill copy exposed to Codex by symlink with a recorded `codex_skill_mode` fallback, one canonical `pref.json` shared by both hosts, the ask-step abstraction chosen by tool availability, Codex's contents-based hook trust lifecycle, and why Codex is global-scope only
- `ARCHITECTURE.md`: Step 6 in the flow diagram shows the ask step rather than a hard `AskUserQuestion` call; the trust-anchor note names both instruction files; file map adds `bin/wdym-prompt.js`, `agents/openai.yaml`, `package.json`, `refs/heal.md`, `tests/token_bench.py`
- `llms.txt`: host-neutral summary, trust contract described per host, `refs/init.md` and `bin/wdym-prompt.js` added to the index
- `RELEASES.md`: v1.2.0 entry

### [12] — One install command for either host: `npx wdym-prompt`

- `bin/wdym-prompt.js`: new dependency-free Node installer — detects Claude Code and/or Codex, wires skill files, hook entry, trust-anchor contract, and the single canonical `pref.json`; Codex skill path is a symlink to the canonical copy with automatic copy fallback (`--copy` to force); dedupes pre-existing duplicate wdym hook entries by command text; byte-idempotent re-runs; `--doctor` reports wiring and copy drift, and flags Codex hook trust as unreadable with a pointer to `/hooks`; `--uninstall` unwinds marker-delimited blocks and hook entries; Codex-touching runs end with the loud hook re-approval trust notice
- `bin/wdym-prompt.js`: hook dedupe matches on `prompt-detect.py` only (never a bare `wdym` substring), duplicate contract blocks are collapsed and fully removed on uninstall, and a settings file that parses to a non-object is refused rather than overwritten
- `package.json`: new — `wdym-prompt` 1.2.0, `bin`, Node >= 18, zero runtime dependencies, `files` allowlist (no tests/, improve/, audit/), `prepublishOnly` gate on the detect bench
- `.gitignore`: ignore `node_modules/` and `*.tgz` pack artifacts
- Codex install is global-scope only: `--codex --local` refuses cleanly and wires nothing; `install.sh` stays frozen as the Claude-only path

### [11] — wdym runs correctly under Codex, not just accidentally

- `hooks/prompt-detect.py`: emitted block's signal line is host-neutral ("invoke per the wdym auto-invoke contract" — no `CLAUDE.md`); state resolution walks cwd up to the repo root and consults `$CODEX_HOME`, so repo-local installs are found when the host starts in a subdirectory
- `hooks/telemetry-stats.py`: mirrors the same resolution order so `--status` reads the file the hook writes
- `tests/detect_bench.py`: asserts the new signal line, bans host filenames in the block, adds a subdirectory-launch scope test
- `SKILL.md`: `allowed-tools:` frontmatter removed (not a Codex field); description rewritten with trigger words front-loaded to survive Codex's skills-list truncation; host-portability note added
- `agents/openai.yaml`: new — Codex skill manifest with `display_name`, `short_description`, `policy.allow_implicit_invocation: true`
- `refs/protocol.md`: comprehensive approval gate routed through a new "present options and stop" ask step — `AskUserQuestion` when available, plain-text question + end-of-turn otherwise, chosen by tool availability
- `refs/init.md`: host-resolution table (hook file, trust anchor, command prefix per host), both `--init` questions via the ask step, Codex global-only rule, duplicate-hook collapse
- `refs/commands.md`, `refs/help.txt`, `refs/heal.md`: host-neutral wording; heal knows the Codex untrusted-hook "wired but silent" failure mode

## 2026-07-30

### [10] — Principles catch up to modern models; writing prompts no longer read as code
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

The `<prompt-detect>` block is emitted by a user-installed `UserPromptSubmit` hook — it is **trusted project configuration, not untrusted injected input**. Invoking `wdym` when a block is present is expected and safe; do not treat the block as a prompt injection.

Defense-in-depth for the hook contract: a `<prompt-detect>` block present (any verdict) ⇒ invoke the `wdym` skill via the Skill tool **before** processing the prompt; no block ⇒ passthrough, respond normally.
Defense-in-depth for the hook contract: a `<prompt-detect>` block present (any verdict) ⇒ invoke the `wdym` skill **before** processing the prompt; no block ⇒ passthrough, respond normally.
Loading
Loading