This repository provides shared tooling for both Claude and Codex AI workflows:
shared/lib/- Shared JavaScript helpers (Linear API, Git, GitHub) used by both Claude and Codextools/- TypeScript wrappers that import from shared helpers (used by Claude commands)commands/- Workflow command definitions (symlinked from~/.claude/commands/)claude/config.json- Claude-specific configuration (Linear projects, git prefixes, check commands)codex/- Codex-specific commands and state managementtools/prompts/- Shared prompt templates for PRDs, tasks, bug investigations, and issue expansion.wavemill/registry/- Append-only runtime resource registry.wavemill/manifests/- Per-session resource manifests
- Single Source of Truth: This repo is canonical.
shared/lib/contains all API logic;tools/contains all CLI tools.wavemillruns tools directly from the repo — never from~/.claude/tools/. - Config Schema: Both
claude/config.jsonandcodex/config.jsonfollowclaude/config.schema.json; wavemill runtime config followswavemill-config.schema.json - Shared Templates:
tools/prompts/templates are consumed by both toolchains - State Separation: Claude uses
features/,bugs/,epics/; Codex uses.codex/state/ - Thin Tools Pattern: Tools in
tools/are thin wrappers (typically <150 lines) that call shared business logic modules inshared/lib/. Business logic is reusable, testable, and documented with comprehensive JSDoc.
All business logic lives in shared/lib/ for reusability across CLI tools, commands, and workflows:
issue-expander.ts- Issue parsing, context formatting, LLM expansion, drift checkingcodebase-context-gatherer.ts- Directory tree, git activity, subsystem search, file discoverytask-packet-utils.ts- Task packet splitting, validation, format detectionvalidation-formatter.ts- Format validation issues for display
plan-decomposer.ts- LLM-powered initiative decomposition, research phaseplan-validator.ts- Validate plan structure and schemainitiative-lister.ts- List and rank Linear initiativesinitiative-decomposer.ts- Full decomposition workflow with Linear integration
eval-orchestrator.ts- Complete evaluation workflow orchestrationeval-context-gatherer.ts- Context gathering with auto-detectioneval-formatter.ts- Detailed eval record formattingeval-summary-printer.ts- One-line eval summarieseval-record-builder.ts- Enrich records with metadataintervention-detector.ts- Detect human interventionsdifficulty-analyzer.ts- Analyze PR difficultytask-context-analyzer.ts- Analyze task characteristicsrepo-context-analyzer.ts- Analyze repository contextoutcome-collectors.ts- Collect CI, test, review outcomes
prompt-utils.ts- Prompt template fillingllm-cli.ts- Claude CLI integrationstring-utils.ts- String manipulation (kebab-case, etc.)shell-utils.ts- Safe shell command executionlinear.js- Linear API clientconfig.ts- Centralized config loadingmodel-registry.ts- Canonical model capability registry with task-specific fallback laddersresource-registry.ts/resource-manifest.ts- Runtime resource tracking and per-run attribution
When creating or refactoring tools:
-
Extract business logic to focused modules in
shared/lib/:// shared/lib/my-feature.ts export async function doSomething(options: Options): Promise<Result> { // Business logic here }
-
Keep tools thin - just CLI argument parsing and orchestration:
// tools/my-tool.ts import { runTool } from '../shared/lib/tool-runner.ts'; import { doSomething } from '../shared/lib/my-feature.ts'; runTool({ name: 'my-tool', description: 'Does something useful', async run({ args, positional }) { const result = await doSomething({ ...args }); console.log(result); }, });
-
Benefits:
- Business logic is reusable across tools, commands, and workflows
- Easier to test (test modules, not CLI wrappers)
- Better separation of concerns
- Self-documenting with JSDoc
To fetch the Linear backlog:
npx tsx tools/get-backlog.ts "Project Name"Available in ~/.claude/commands/:
/workflow- Full feature workflow (task selection → plan → implementation → validation → PR)/plan- Epic decomposition into sub-issues/bugfix- Bug investigation and fix workflow/create-plan- Research and create implementation plan/implement-plan- Execute plan with phase gates/validate-plan- Validate implementation against plan
Use docs/prompt-locations.md as the canonical registry for agent instruction locations that must be updated together.
shared/lib/agent-adapters.sh-agent_launch_autonomous()andagent_launch_interactive()define mill-mode agent launch behavior. Active phase prompts arebuild_planning_prompt,build_coding_prompt, andbuild_review_prompt; legacybuild_routing_promptandbuild_interactive_promptare test-only render surfaces, andbuild_autonomous_prompthas been removed.shared/lib/wavemill-startup-runner.shis the active startup launcher.shared/lib/wavemill-orchestrator.shis the deprecated compatibility wrapper.codex/prompts/*.mdwithcodex/src/commands/workflow.jsform an intentional Codex-native parallel workflow rather than a shell prompt builder path.commands/workflow.md- Phase 4 owns self-review for the interactive/workflowcommand.tools/prompts/review-general.mdandtools/prompts/review-general-scoped.md-shared/lib/review-engine.tsswitches between them based on operating mode; constrained/survival mode uses the scoped checklist and can emitneeds_stronger_reviewer.commands/bugfix.md- Bug workflow does not include self-review.commands/implement-plan.md- Does not define self-review;/workflowowns it.
Wavemill tracks agent lifecycle using a JSON status file contract at /tmp/wavemill-${SESSION}-${ISSUE}.hook. This replaces tmux pane liveness checks with richer state reporting (working/idle/waiting/error) and supports staleness detection via timestamps.
All JSON state read-modify-write updates must use state_mutate from shared/lib/wavemill-common.sh in shell or mutateJsonState from shared/lib/state-mutex.ts in TypeScript. These helpers serialize concurrent writers with a file lock before writing a temporary file and atomically renaming it into place.
Append-only files such as JSONL logs and .wavemill/registry/ entries remain lock-free. Hook status files at /tmp/wavemill-*.hook also keep their existing single-writer temporary-file pattern.
Shared Protocol (wavemill-hook-protocol.sh):
wavemill_hook_check()- Ensures hooks are no-ops outside wavemill contextswavemill_hook_write(state, event, detail, agent)- Atomic JSON writes with timestamps- 300s TTL for staleness detection
Agent Adapters:
- Claude (claude-status-hook.sh) - Hooks configured per-worktree in
.claude/settings.local.json(gitignored). Fires on UserPromptSubmit, PreToolUse, Stop, StopFailure, and Notification events. - Codex (codex-status-monitor.sh) - Monitors JSONL event stream from
codex exec --json - Generic (process-status-monitor.sh) - Fallback monitoring via child process detection
Status Reading (wavemill-status.sh):
- Reads JSON hook files with TTL validation (300s)
- Falls back to tmux pane liveness if hook is stale or missing
- Extracts detail field (tool names, error messages) for dashboard
Hook Configuration (wavemill-common.sh):
configure_agent_hooks()dynamically writes.claude/settings.local.jsonper-worktree- Claude hook adapters are always loaded from the wavemill installation at
$TOOLS_DIR/../shared/hooks/ - Only affects wavemill-launched agents, not standalone Claude usage
- Called before each phase launch (planning, coding, review)
- Missing wavemill hook adapters warn once per session via
/tmp/wavemill-${SESSION}-hook-warnings.txt
{
"state": "working",
"event": "PreToolUse",
"detail": "Read",
"agent": "claude",
"timestamp": 1712345678
}States: working (agent active), idle (stopped normally), waiting (blocked on user input), error (failure)
TTL: 300s - dashboard falls back to pane liveness if timestamp is stale
Atomic Writes: Uses tmp file + mv to prevent partial reads
The dashboard supports immediate updates via USR1 in addition to polling:
- After each successful atomic hook write,
wavemill_hook_notify()sendsUSR1to$WAVEMILL_DASHBOARD_PID wavemill-status.shtrapsUSR1and setsWAVEMILL_REDRAW=1- The dashboard loop uses an interruptible wait (
sleep "$REFRESH" &; wait) for fast wakeups - A 2-second default poll fallback remains in place in case signals are missed
WAVEMILL_DASHBOARD_REFRESH_SECONDScan override the fallback cadence with integer values from1through10; invalid values fall back to2- Signal delivery is best-effort with full PID validation (invalid/stale PID is a no-op, never fails hook writes)
PID propagation architecture (tmux environment-based):
setup_control_dashboard()inwavemill-startup-runner.shcaptures the dashboard pane PID after spawn- The PID is set as a tmux session environment variable:
tmux set-environment -t "$SESSION" WAVEMILL_DASHBOARD_PID "$WAVEMILL_DASHBOARD_PID" agent_resolve_dashboard_pid()inagent-adapters.shreads the PID from either:- The current shell environment (if already set), or
- The tmux session environment via
tmux show-environment
agent_launch_autonomous()andagent_launch_interactive()exportWAVEMILL_DASHBOARD_PIDfor all agent types (Claude, Codex, generic)- Hook scripts inherit the PID from their environment and call
wavemill_hook_notify()for validated signal delivery
This architecture ensures complete agent coverage without coupling hook configuration to PID injection.
jq(required for all adapters) - JSON parsing and creationpgrep(optional) - Child process detection for generic adapter
Without jq, hooks are no-ops. Without pgrep, generic adapter degrades to initial/final state only.
- Prefer native hooks if the CLI exposes them (like Claude Code)
- Otherwise prefer structured event streams (like Codex JSONL)
- Fall back to process monitoring for agents without hooks/streams
All adapters:
- Source
wavemill-hook-protocol.sh - Call
wavemill_hook_check()at startup - Use
wavemill_hook_write()for all status updates - Set
WAVEMILL_SESSIONandWAVEMILL_ISSUEenvironment variables
Template usage is automatically logged to .wavemill/evals/prompt-registry.jsonl for GEPA training attribution. Each entry captures:
- Template name (e.g., "issue-writer", "eval-judge")
- Template hash (SHA-256) for version tracking
- Usage timestamp (ISO 8601 format)
- Content snapshot (only stored for new hash values to save space)
Usage: Use loadPromptTemplate() from shared/lib/prompt-utils.ts to load templates with automatic registry logging:
import { loadPromptTemplate } from '../shared/lib/prompt-utils.ts';
// Load with automatic registry logging
const template = await loadPromptTemplate('tools/prompts/issue-writer.md');
// Opt out if needed
const template = await loadPromptTemplate(
'tools/prompts/issue-writer.md',
{ skipRegistry: true }
);Deduplication: The registry only stores template content once per hash. Subsequent uses of the same template version log the timestamp but not the content.
Graceful degradation: Registry failures don't break workflows - errors are logged as warnings and template loading continues.
The .wavemill/project-context.md file maintains living documentation of:
- Architectural decisions and patterns established in the codebase
- Key conventions (state management, API patterns, styling approach)
- Recent work log - automatically updated after each PR merge
- Known gotchas and constraints discovered during development
This file is automatically included when agents expand Linear issues, enabling them to build on previous work rather than starting from scratch.
Recommended: Use wavemill init which will prompt you to initialize project context:
cd ~/your-repo
wavemill init
# Answer 'Y' when prompted to initialize project contextManual initialization (if you skipped it during wavemill init):
npx tsx tools/init-project-context.ts
# Overwrite existing context (use with caution)
npx tsx tools/init-project-context.ts --forceAuto-initialization: When you run wavemill mill or wavemill expand for the first time, you'll be prompted to initialize if the file doesn't exist. You can skip this check with:
SKIP_CONTEXT_CHECK=true wavemill millThe "Recent Work" section is automatically updated after each PR merge in mill mode. The post-completion hook:
- Analyzes the PR diff
- Generates a concise summary using LLM
- Appends the summary to project-context.md
Manual edits to other sections (Architecture, Conventions, etc.) are encouraged to keep documentation current.
If the file exceeds 100KB, you'll receive warnings during issue expansion. To manage size:
# Archive old entries
mv .wavemill/project-context.md .wavemill/project-context-archive-$(date +%Y%m).md
npx tsx tools/init-project-context.ts
# Then manually copy relevant patterns/conventions to new fileBest practice: Keep the "Recent Work" log to the last 20-30 entries, archiving older history.
The .wavemill/context/ directory contains detailed specifications for each logical subsystem in the codebase. This implements a three-tier memory system inspired by "Codified Context: Infrastructure for AI Agents" (arXiv:2602.20478):
- Hot memory:
project-context.md- Concise constitution (always loaded) - Cold memory: Subsystem specs and concept pages (loaded on-demand)
- Agent memory: Session-specific context (per workflow)
.wavemill/
├── project-context.md # Hot memory (always loaded)
└── context/ # Cold memory (load on-demand)
├── *.md # Subsystem specs (module-oriented)
└── concepts/ # Concept pages (cross-cutting knowledge)
├── progressive-disclosure.md
├── task-packet-format.md
└── model-routing-strategy.md
Subsystem specs (.wavemill/context/*.md):
- Module-oriented documentation
- Tied to specific files and directories
- Describe implementation details, constraints, failure modes
- Example:
linear-api.md,eval-system.md,router.md
Concept pages (.wavemill/context/concepts/*.md):
- Cross-cutting knowledge that spans multiple subsystems
- Durable across refactoring (not tied to specific files)
- Define shared vocabulary, invariants, decision criteria
- Example:
progressive-disclosure.md(applies to task packets, context management, UI design)
When to use concepts vs subsystems:
- Use subsystem specs when documenting a specific module's implementation
- Use concept pages when documenting knowledge that:
- Applies across multiple subsystems
- Defines shared vocabulary or patterns
- Should survive subsystem refactors (file moves, renames)
- Represents architectural invariants or design principles
Each subsystem spec is structured for machine consumption:
# Subsystem: {name}
**Last updated:** {timestamp}
**Files touched:** {count} files in last 30 days
## Purpose
[1-2 sentence description]
## Key Files
| File | Role | Notes |
|------|------|-------|
| ... | ... | ... |
## Architectural Constraints
### DO
- [Concrete rule]
### DON'T
- [Anti-pattern]
## Known Failure Modes
| Symptom | Root Cause | Fix |
|---------|------------|-----|
| ... | ... | ... |
## Testing Patterns
...
## Dependencies
...
## Related Subsystems
- [eval-system](eval-system.md) — eval orchestrator consumes router decisions
- [tools-prompts](tools-prompts.md) — prompt templates reference subsystem constraints
## Recent Changes
[Auto-updated after each PR]Each concept page follows this structure (defined in tools/prompts/concept-page-template.md):
# Concept: {name}
**Concept ID:** `{id}`
## Purpose
What the concept is and why it exists
## When It Applies
Situations where this concept matters
## Core Invariants
Rules that must remain true regardless of implementation
## Mental Model
How to reason about the concept
## Operational Rules
Actionable constraints an agent should follow
## Boundaries And Non-Goals
What the concept does NOT cover
## References In This Repo
Related subsystem specs, docs, or files
## Examples
Concrete instances in the codebase
## Guidance For Future Updates
What kinds of repo changes should update this pageTo create a new concept page:
npx tsx tools/generate-concept.ts progressive-disclosure
# Or with context from specific subsystems:
npx tsx tools/generate-concept.ts task-packet-format --subsystems linear-api,context-managementThis uses LLM to generate a structured concept page at .wavemill/context/concepts/{concept-id}.md.
Subsystems are auto-detected during wavemill init using heuristic analysis:
- Directory structure: Top-level modules in
src/,shared/,tools/ - File naming patterns:
*-router.ts,*-analyzer.ts, etc. - Package dependencies: Files importing same external packages
- Git activity: Frequently co-modified files
After each PR merge, the post-completion hook:
- Detects which subsystems were affected by the PR
- Updates the relevant
.wavemill/context/{subsystem}.mdfiles - Adds entry to "Recent Changes" section
- Updates architectural constraints if new patterns were established
- Documents failure modes if bugs were fixed
Before expanding a Linear issue, the system checks if subsystem specs are stale:
- Compares spec last-modified timestamp vs recent file changes
- Warns when spec is >7 days older than most recent PR
- Lists which PRs affected the subsystem since last update
Example warning:
⚠️ DRIFT DETECTED: Some subsystem specs are stale
The following subsystems have been modified since their specs were last updated:
• Linear API (linear-api)
Last updated: 2026-02-18 (10 days ago)
Files modified: 2026-02-28
Recent PRs: #123, #124, #125
Consider refreshing these specs before relying on them for implementation.
Run: npx tsx tools/init-project-context.ts --force
To regenerate subsystem specs:
# Regenerate all subsystem specs
npx tsx tools/init-project-context.ts --force
# This will:
# 1. Re-detect subsystems from current codebase
# 2. Regenerate all .wavemill/context/*.md files
# 3. Update project-context.md with new subsystem linksNote: Manual edits to subsystem specs are preserved in version control, but will be overwritten by --force. Consider updating via PR instead.
- Trust the documentation: Agents rely on subsystem specs - keep them current
- Structured format: Use tables and lists (not prose) for machine readability
- Maintenance cost: ~1-2 hours/week for 34 subsystem specs (per research paper)
- Knowledge ratio: Aim for ~24% (1 doc line per 4 code lines)
When Linear issues are expanded into task packets, they use a progressive disclosure approach to reduce context overload:
-
Header (
task-packet-header.mdor loaded directly)- Brief overview (~50 lines)
- Objective (2-3 sentences)
- Top 5 key files to modify
- Top 3 critical constraints
- High-level success criteria
- Links to detailed sections
-
Details (
task-packet-details.md)- Complete 9-section specification
- Section 1: Complete Objective & Scope
- Section 2: Technical Context (all files, dependencies, architecture)
- Section 3: Implementation Approach (step-by-step plan)
- Section 4: Success Criteria (with [REQ-FX] requirement tags)
- Section 5: Implementation Constraints (all rules)
- Section 6: Validation Steps (concrete test scenarios)
- Section 7: Definition of Done
- Section 8: Rollback Plan
- Section 9: Proposed Labels (for conflict detection)
- Initial context: Agents receive the brief header (~50 lines vs ~500 lines)
- On-demand details: Agents read specific sections from
task-packet-details.mdas needed - Benefits: Reduces initial token usage by ~90%, keeps context focused on implementation
- Existing full-format task packets (9 sections in one file) continue to work
is_task_packet()function recognizes both old and new formats- Linear issues always receive full content (no user-visible changes)
When you see a task packet header:
- Start with the header to understand the objective
- Read
task-packet-details.mdsections on-demand as you implement - Section 6 (Validation Steps) contains concrete test scenarios
- Section 4 (Success Criteria) has all requirements with [REQ-FX] tags
TypeScript modules use shared/lib/config.ts for centralized config loading:
import { loadWavemillConfig } from './config.ts';
// Load and validate config (cached per repo directory)
const config = loadWavemillConfig(repoDir);
console.log(config.router?.enabled); // typed access
// Or use typed accessors for specific sections
import { getRouterConfig, getEvalConfig } from './config.ts';
const routerConfig = getRouterConfig(repoDir);
const evalConfig = getEvalConfig(repoDir);Key features:
- Configs are cached per-process (singleton per repoDir)
- Validated against
wavemill-config.schema.jsonat load time - All fields are optional (graceful degradation)
- Use
clearConfigCache(repoDir)to force reload in tests - Config ownership and precedence reference:
docs/config-files.md
Implementation:
- Replaces ~7 independent
readFileSync+JSON.parseblocks - Uses Ajv for schema validation
- Provides TypeScript types matching the schema
The permissions section in .wavemill-config.json allows you to configure auto-approval for read-only commands, reducing confirmation prompts when working in worktrees.
Add to .wavemill-config.json:
{
"permissions": {
"autoApprovePatterns": [
"git status*",
"git log*",
"gh pr view*",
"find *",
"ls *"
],
"worktreeMode": {
"enabled": true,
"autoApproveReadOnly": true
}
}
}import {
matchesPattern,
matchesAnyPattern,
isSafePattern,
getDefaultPatterns
} from './shared/lib/permission-patterns.ts';
// Check if a command matches a pattern
matchesPattern('git status --short', 'git status*') // true
// Check if a command matches any pattern
const patterns = ['git status*', 'git log*'];
matchesAnyPattern('git status', patterns) // true
// Validate pattern is safe (no destructive commands)
isSafePattern('git status*') // true
isSafePattern('rm *') // false
// Get all default read-only patterns
const defaults = getDefaultPatterns();Default patterns are organized by category:
- File System Read:
find *,ls *,cat *,head *,tail *, etc. - Git Read:
git status*,git log*,git show*,git diff*, etc. - GitHub CLI Read:
gh pr view*,gh issue view*, etc. - Text Search:
grep *,rg *,ag *,ack * - Package Managers:
npm list*,pnpm list*,yarn list*
For Claude Code:
npx tsx tools/generate-permissions.ts --agent claude
# Apply generated settings to Claude Code (see docs/worktree-auto-approve.md)For Codex:
npx tsx tools/generate-permissions.ts --agent codex
# Copy to ~/.codex/permissions.json and restart Codex- Permission Configuration Guide - Full reference
- Worktree Auto-Approve Guide - Agent setup instructions