Skip to content
Draft
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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ wherefore/
topics.md controlled tag vocabulary: Areas and Topics
log/YYYY-MM-DD-short-slug.md one decision per file
questions/Q-NNN.md one question per file
plan/short-slug.md forward-looking plans and roadmaps, one per file
```

There is no index file. The frontmatter in each `log/` and `questions/` file is the
Expand Down
33 changes: 32 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,15 @@ wherefore is an open, plain-markdown record of the reasoning behind your technic
decisions. No cloud, no database, no vector store, no lock-in. Because the data is
just files in your repo, any tool or any person can read it.

There are three ways to work with it:
There are a few ways to work with it:

- **A Claude Code plugin** (the richest experience): skills that capture, query,
resolve, and supersede decisions, with Claude handling the tagging and bookkeeping
so the log actually gets maintained.
- **The `wherefore` CLI** ([`wherefore`](https://www.npmjs.com/package/wherefore) on
npm): `npx wherefore init` scaffolds the log and an `AGENTS.md`, and
`npx wherefore dashboard` launches the dashboard. It can also install the skills for
your agent (Claude Code, Codex, Cursor, Copilot, Gemini, Antigravity) as an opt-in.
- **A static dashboard** ([`@dustinvk/wherefore-dashboard`](https://www.npmjs.com/package/@dustinvk/wherefore-dashboard)
on npm): renders your `wherefore/` directory as a browsable site, deployable to
Cloudflare Pages.
Expand Down Expand Up @@ -155,6 +159,32 @@ Or equivalently from the repo root:
node packages/wherefore-dashboard/bin/wherefore-dashboard.js dev --src ./wherefore
```

## Setting up a project

`npx wherefore init` scaffolds everything a project needs: a `wherefore/` directory
(`log/`, `questions/`, `plan/`, and a starter `topics.md`), an `AGENTS.md` so any
coding agent can read and maintain the log, and a `CLAUDE.md` snippet that makes Claude
offer to capture decisions. It also adds a `dist/` line to `.gitignore` and a `wherefore`
devDependency.

By default it installs no agent skills. To also install the SKILL.md skills for your
agent(s), opt in (experimental):

```bash
# the shared .agents/skills path (Copilot, Cursor, Gemini, Antigravity)
npx wherefore init --skills

# specific agents: claude, codex, copilot, cursor, gemini, antigravity, all, auto
npx wherefore init --skills --agent claude,codex

# detect agents from the repo, or install into your user-level dirs
npx wherefore init --skills --agent auto
npx wherefore init --skills --agent claude --global
```

`AGENTS.md` is always written and is the cross-tool floor; installing skills is an opt-in
enhancement on top of it.

## Setup tips

First-time setup in a project (optional but recommended):
Expand Down Expand Up @@ -212,6 +242,7 @@ wherefore/
│ └── workflows/
│ └── validate-plugins.yml # CI: validates manifests + plugin on every push
├── packages/
│ ├── wherefore/ # the `wherefore` CLI: init + dashboard launcher (published to npm)
│ └── wherefore-dashboard/ # the static dashboard (published to npm)
├── plugins/
│ └── wherefore/
Expand Down
4 changes: 4 additions & 0 deletions packages/wherefore-dashboard/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ Wherefore captures the reasoning behind your engineering decisions, what you cho
[![npm](https://img.shields.io/npm/v/@dustinvk/wherefore-dashboard)](https://www.npmjs.com/package/@dustinvk/wherefore-dashboard)
[![license](https://img.shields.io/npm/l/@dustinvk/wherefore-dashboard)](https://github.com/DustinVK/wherefore/blob/main/LICENSE)

> This package only builds and serves the dashboard. To scaffold a `wherefore/` log or
> install skills for your agent, use the `wherefore` CLI: `npx wherefore init`. You can
> also launch this dashboard through it with `npx wherefore dashboard`.

## Quick start

From any directory that contains a `wherefore/` folder, no install and no flags:
Expand Down
11 changes: 5 additions & 6 deletions packages/wherefore-dashboard/bin/wherefore-dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,14 @@ const USAGE = `wherefore-dashboard -- build or preview a static dashboard from a
Usage:
wherefore-dashboard build [--src <path>] [--out <path>] [--title <string>]
wherefore-dashboard dev [--src <path>] [--title <string>]
wherefore-dashboard init

Options:
--src <path> Path to the wherefore/ directory to render. Default: ./wherefore
--out <path> Output directory for the built site. Default: ./dist
--title <string> Override the dashboard title.
-h, --help Show this help.`;
-h, --help Show this help.

To scaffold a wherefore/ log or install skills, use the wherefore CLI: npx wherefore init.`;

function checkSrc(src) {
const logDir = resolve(src, 'log');
Expand Down Expand Up @@ -113,13 +114,11 @@ if (command === 'build') {
});

} else if (command === 'init') {
console.log('init: not yet implemented.');
console.log('Intended: scaffold package.json with @dustinvk/wherefore-dashboard as devDependency,');
console.log('a .gitignore entry for dist/, and an optional wherefore-dashboard.config.json.');
console.error('init moved to the wherefore CLI. Run: npx wherefore init');
process.exit(1);

} else {
console.error(`Unknown command: ${command ?? '(none)'}`);
console.error('Usage: wherefore-dashboard <build|dev|init> [--src <path>] [--out <path>] [--title <string>]');
console.error('Usage: wherefore-dashboard <build|dev> [--src <path>] [--out <path>] [--title <string>]');
process.exit(1);
}
3 changes: 3 additions & 0 deletions packages/wherefore/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules/
skills/
templates/
113 changes: 113 additions & 0 deletions packages/wherefore/MANUAL_TESTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# Manual testing plan: `wherefore` CLI

`npm test` in this package already covers the mechanics: default-off skills, the
per-agent / `all` / `auto` / unknown / global / force paths, the dashboard launcher
(arg forwarding + exit code), and the CLAUDE.md / .gitignore / package.json regressions.

This document covers what those tests cannot: running the packaged artifact the way a
user would, and confirming that a real agent actually discovers the installed skills.
Work through it before publishing.

## Prerequisites

- Node >= 18.
- Pick how you'll run the CLI:
- **Packaged (preferred, closest to real usage).** From this directory, `npm pack`.
Its `prepack` script generates `skills/` and `templates/`, so the tarball is complete.
Then `npm i -g ./wherefore-0.1.0.tgz` and invoke `wherefore ...`. Uninstall with
`npm rm -g wherefore` when done.
- **Direct (fastest for iterating).** Run `node bin/prepare-package.js` once so
`skills/`/`templates/` exist, then `node bin/wherefore.js ...`. Skipping the prepare
step makes skill/AGENTS.md/CLAUDE.md steps fail.
- Run every case in a throwaway project directory (`mkdir $(mktemp -d)` with a
`package.json` inside). For `--global`, set `HOME=$(mktemp -d)` on the command so you
never write to your real `~/.claude`, `~/.codex`, or `~/.agents`.

Below, `wherefore` means whichever invocation you chose.

## A. Help and dispatch

| Command | Expected |
| --- | --- |
| `wherefore` / `wherefore --help` | Prints usage, exit 0 |
| `wherefore bogus` | `Unknown command: bogus`, exit 1 |

## B. Default init installs no skills

In an empty dir containing a minimal `package.json`, run `wherefore init`. Expect exit 0
and the console line `Skipping agent skill install (experimental; ...)`. Verify:

- `wherefore/` has `log/`, `questions/`, `plan/`, and `topics.md`.
- `AGENTS.md` exists (the always-on cross-tool floor).
- `CLAUDE.md` contains `## Wherefore` and does **not** contain `paste from here` or
`Paste the block below`.
- `package.json` gained a `wherefore` devDependency.
- `.gitignore` contains a bare `dist/` line.
- **No** `.agents/skills`, `.claude/skills`, or `.codex/skills` directories exist.

## C. Idempotency

Run `wherefore init` a second time in the same dir. Expect exit 0, "already exists"
messages for topics/AGENTS/CLAUDE, and that `CLAUDE.md` still has exactly one `## Wherefore`
block (no duplicate append). Confirm `package.json` was not rewritten (dependency not added
twice, formatting unchanged).

## D. Opt-in skill install

Each in a fresh dir:

| Command | Expected |
| --- | --- |
| `wherefore init --skills` | `.agents/skills/{capture,ask,resolve,supersede}/SKILL.md`; no `.claude`/`.codex` |
| `wherefore init --skills --agent claude,codex` | `.claude/skills` and `.codex/skills` written; `.agents/skills` absent |
| `wherefore init --skills --agent all` | all three roots written |
| `mkdir .codex && wherefore init --skills --agent auto` | only `.codex/skills` written; `.cursor/skills` absent |
| `wherefore init --skills --agent bogus` | exit 1, error lists valid agent names |
| re-run `--agent claude` then again with `--force` | first re-run skips existing skill; `--force` overwrites it |
| `HOME=$(mktemp -d) wherefore init --global --agent claude` | skill lands under that temp `HOME`'s `.claude/skills`, not the project |

## E. Robustness

- Put invalid JSON in `package.json`, then `wherefore init`. Expect a `Warning: Could not
update package.json` line, the rest of the scaffold still created, and a non-zero exit
with `Initialization completed with errors`.
- Confirm a genuinely healthy run exits 0 (already covered in B, note it here for contrast).

## F. Dashboard launcher

- Override path: `WHEREFORE_DASHBOARD_BIN=/path/to/stub.js wherefore dashboard build --src x`
forwards `build --src x` to the stub and returns the stub's exit code. (This is what the
automated test does; rerun it manually if you touched the launcher.)
- Real path: in a dir containing a `wherefore/`, run `wherefore dashboard dev`. It should
`npx @dustinvk/wherefore-dashboard dev` on demand (first run downloads it), serve the
dashboard, and stop cleanly on Ctrl-C. Try `wherefore dashboard --help` and confirm the
dashboard's own help appears (the launcher forwards `--help` rather than showing the CLI's).

## G. Real cross-agent verification (the point of this doc)

For each agent you actually have installed, install its skills and confirm the agent
discovers them. This is the part unit tests can't reach.

- **Claude Code:** `wherefore init --skills --agent claude`, then in Claude Code confirm the
four skills are discovered from `.claude/skills/`. Known limitation to eyeball: their
descriptions advertise `/wherefore:*` triggers, which only resolve for the marketplace
plugin, not for filesystem-installed skills.
- **Codex CLI:** `--agent codex`, then run `codex` in that repo and confirm the skills load
from `.codex/skills/`. Separately confirm Codex reads the `AGENTS.md` floor with no skills
installed at all.
- **Copilot / Cursor / Gemini / Antigravity:** `--agent <that agent>` (all write
`.agents/skills/`), open the tool, and confirm it discovers the skills there. For Cursor
and Codex, also confirm plain `AGENTS.md` (default init, no skills) is picked up.
- Note any agent whose skill directory has moved since this was written; the paths in
`bin/wherefore.js` (`AGENT_DIRS`) may need updating.

## H. Full end-to-end dry run

In a throwaway project: `wherefore init`, capture a decision through whichever agent you
set up (or hand-write a `wherefore/log/*.md`), then `wherefore dashboard build` and open the
generated `dist/` to confirm the entry and any questions render.

## Cleanup

Remove the throwaway directories and temp `HOME`s. If you installed the tarball,
`npm rm -g wherefore` and delete the `.tgz`.
83 changes: 83 additions & 0 deletions packages/wherefore/bin/prepare-package.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { cp, rm, mkdir, stat, readdir } from 'node:fs/promises';
import { existsSync } from 'node:fs';
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = dirname(fileURLToPath(import.meta.url));
const PKG_ROOT = resolve(__dirname, '..');
const REPO_ROOT = resolve(PKG_ROOT, '..', '..');

const SKILLS_SRC = resolve(REPO_ROOT, 'plugins', 'wherefore', 'skills');
const SNIPPET_SRC = resolve(REPO_ROOT, 'plugins', 'wherefore', 'CLAUDE.snippet.md');
const AGENTS_SRC = resolve(REPO_ROOT, 'AGENTS.md');
const SOURCES = [SKILLS_SRC, SNIPPET_SRC, AGENTS_SRC];

const destSkills = resolve(PKG_ROOT, 'skills');
const destTemplates = resolve(PKG_ROOT, 'templates');

// Newest mtime anywhere under a file or directory tree.
async function newestMtime(path) {
const info = await stat(path);
let newest = info.mtimeMs;
if (info.isDirectory()) {
for (const entry of await readdir(path, { withFileTypes: true })) {
const m = await newestMtime(resolve(path, entry.name));
if (m > newest) newest = m;
}
}
return newest;
}

async function main() {
const sourcesExist = SOURCES.every((p) => existsSync(p));
const assetsPresent = existsSync(destSkills) && existsSync(destTemplates);

// Outside the monorepo (e.g. an installed package), the sources are gone but the
// assets were already shipped in the tarball, so there is nothing to regenerate.
if (!sourcesExist) {
if (assetsPresent) {
console.log('Package assets already present; skipping generation.');
return;
}
console.warn('Package assets missing and source files unavailable; cannot generate skills/templates.');
return;
}

// Skip the rm+recopy when the generated assets are already newer than every
// source, so dev/build/test do not rebuild the whole tree on every run. Any
// error here (missing/racey files) falls through to a full, correct rebuild.
if (assetsPresent) {
try {
const srcNewest = Math.max(...(await Promise.all(SOURCES.map(newestMtime))));
const destNewest = Math.max(await newestMtime(destSkills), await newestMtime(destTemplates));
if (destNewest >= srcNewest) {
console.log('Package assets up to date; skipping regeneration.');
return;
}
} catch {
// fall through and rebuild
}
}

console.log('Preparing package assets (copying skills and templates)...');

// Clean old files
await rm(destSkills, { recursive: true, force: true });
await rm(destTemplates, { recursive: true, force: true });

// Create directories
await mkdir(destSkills, { recursive: true });
await mkdir(destTemplates, { recursive: true });

// Copy files
await cp(SKILLS_SRC, destSkills, { recursive: true });
await cp(SNIPPET_SRC, resolve(destTemplates, 'CLAUDE.snippet.md'));
await cp(AGENTS_SRC, resolve(destTemplates, 'AGENTS.md'));

console.log('Package assets prepared successfully.');
}

main().catch(err => {
console.error('Failed to prepare package assets:', err);
process.exit(1);
});
Loading
Loading