diff --git a/README.md b/README.md index 020178d..cc96f6e 100644 --- a/README.md +++ b/README.md @@ -94,11 +94,13 @@ v1.7 self-verifying chain (preserved): a step may not be marked `completed` unle v3.1.0 (in progress) adds a `lib/`-tree of zero-dep validators (`brief-validator`, `research-validator`, `plan-validator`, `progress-validator`, `architecture-discovery`) wired into the four commands as CLI shims, plus 109 `node:test` cases and a doc-consistency invariant test. The Phase 5.5 schema self-check now runs as `node lib/validators/plan-validator.mjs --strict` instead of three `grep -cE` calls — same checks, single source of truth, machine-readable error codes. Architecture discovery treats the `ultra-cc-architect` contract as drift-WARN, never drift-FAIL. Forking the plugin? `npm test` is the readiness gate. -Defense-in-depth security: plugin hooks block destructive commands and sensitive path writes, prompt-level denylist works in headless sessions, pre-execution plan scan catches dangerous commands before they run, scoped `--allowedTools` replaces `--dangerously-skip-permissions` in parallel sessions. +v3.1.0 also adds: `docs/HANDOVER-CONTRACTS.md` as the single source of truth for the 5 pipeline handovers; PreCompact-hook (`pre-compact-flush.mjs`, CC v2.1.105+) that fixes the documented progress.json drift bug — `--resume` now works after long conversations; UserPromptSubmit-hook that sets session titles `ultra::` for headless multiplexing (CC v2.1.94+); PostToolUse-hook that captures Bash `duration_ms` per call (CC v2.1.97+); semantic plan-critic rubric that catches paraphrased deferred decisions ("implement as needed", "wire it up") instead of just exact-string blacklist; `examples/01-add-verbose-flag/` showing a calibrated end-to-end pipeline run; `SECURITY.md` boilerplate; `docs/architect-bridge-test.md` smoke checklist. + +Defense-in-depth security: plugin hooks block destructive commands and sensitive path writes, prompt-level denylist works in headless sessions, pre-execution plan scan catches dangerous commands before they run, scoped `--allowedTools` replaces `--dangerously-skip-permissions` in parallel sessions. Recommended hardening: `disableSkillShellExecution: true` for fork-ers handling untrusted plans (CC v2.1.91+). Modes: default, brief-driven, project-scoped, research-enriched, foreground, quick, decompose, export -19 specialized agents · 4 commands · 2 security hooks · No cloud dependency +19 specialized agents · 4 commands · 4 plugin hooks · No cloud dependency → [Full documentation](plugins/ultraplan-local/README.md) · [Migration guide](plugins/ultraplan-local/MIGRATION.md) diff --git a/plugins/ultraplan-local/CLAUDE.md b/plugins/ultraplan-local/CLAUDE.md index 4fc01c5..27d973b 100644 --- a/plugins/ultraplan-local/CLAUDE.md +++ b/plugins/ultraplan-local/CLAUDE.md @@ -113,6 +113,10 @@ Doc-consistency test at `tests/lib/doc-consistency.test.mjs` pins agent-table co `hooks/scripts/pre-compact-flush.mjs` (PreCompact event, CC v2.1.105+) fixes the documented P0 in `docs/ultraexecute-v2-observations-from-config-audit-v4.md`: keeps `progress.json` in sync with git history before context compaction so `--resume` works after long conversations. Atomic write, monotonic only, never blocks compaction. +`hooks/scripts/session-title.mjs` (UserPromptSubmit, CC v2.1.94+) sets `sessionTitle` to `ultra::` for ultra-command invocations. Helps multi-session headless runs identify themselves in process lists. + +`hooks/scripts/post-bash-stats.mjs` (PostToolUse, CC v2.1.97+) appends `duration_ms` for each Bash call into `${CLAUDE_PLUGIN_DATA}/ultraexecute-stats.jsonl`. Useful for finding long-running verify or checkpoint commands. + ## Architecture **Brief:** 7-phase workflow: Parse mode → Create project dir → Phase 3 completeness loop (section-driven, no question cap) → Phase 4 draft/review/revise with `brief-reviewer` as stop-gate (max 3 iterations; gate = all dimensions ≥ 4 and research plan = 5) → Finalize (`brief.md` on pass, or `brief_quality: partial` on cap/force-stop) → Manual/auto opt-in → Stats. Always interactive. Auto mode runs research + plan inline in the main context (v2.4.0). diff --git a/plugins/ultraplan-local/README.md b/plugins/ultraplan-local/README.md index 0f38b10..1da3d4a 100644 --- a/plugins/ultraplan-local/README.md +++ b/plugins/ultraplan-local/README.md @@ -339,6 +339,23 @@ The executor implements defense-in-depth security across four layers: 3. **Pre-execution plan scan** — Phase 2.4 scans all `Verify:` and `Checkpoint:` commands against the denylist before execution begins, catching dangerous commands before they reach the executor 4. **Scoped tool access** — Headless child sessions use `--allowedTools "Read,Write,Edit,Bash,Glob,Grep"` instead of `--dangerously-skip-permissions`, blocking Agent spawning, MCP tools, and web access in parallel sessions +#### Recommended: disable Skill shell execution (CC v2.1.91+) + +For fork-ers handling untrusted task briefs or plans from external +sources, set `disableSkillShellExecution: true` in `~/.claude/settings.json` +or in the project's `.claude/settings.json`: + +```json +{ + "disableSkillShellExecution": true +} +``` + +This prevents Skills from invoking arbitrary shell, which closes a +prompt-injection vector that the plugin's own hooks cannot fully mitigate +(Skills can fire before `pre-bash-executor` matches). See +[SECURITY.md](SECURITY.md) for the full hardening list. + ### Headless execution `/ultraexecute-local` is designed for `claude -p` headless sessions: @@ -347,6 +364,45 @@ The executor implements defense-in-depth security across four layers: - **Scope fence enforcement** — never touches files outside the session's scope - **JSON summary** — machine-parseable `ultraexecute_summary` block for log parsing +#### Headless multi-session tuning (CC v2.1.89+) + +When running multiple parallel `claude -p` sessions (decomposed plans +or wave-based execution), set `MCP_CONNECTION_NONBLOCKING=true` in the +launching environment so MCP server connection latency does not +serialize startup across waves: + +```bash +export MCP_CONNECTION_NONBLOCKING=true +bash .claude/projects/{slug}/sessions/launch.sh +``` + +Without this, each child session can spend 1-3 s blocking on MCP +connect, multiplying across waves. Setting it lets MCP connect lazily +on first tool call. + +### Session titles for ultra commands (CC v2.1.94+) + +A `UserPromptSubmit` hook (`hooks/scripts/session-title.mjs`) sets the +session title to `ultra::` whenever you invoke one of +the four ultra commands. This makes multi-session headless runs and +session-picker output trivially identifiable. Slug derivation: + +| Invocation | Session title | +|-----------|---------------| +| `/ultraplan-local --project .claude/projects/2026-04-18-jwt-auth` | `ultra:plan:jwt-auth` | +| `/ultrabrief-local --quick` | `ultra:brief:ad-hoc` | +| `/ultraexecute-local --project .claude/projects/2026-05-10-cleanup --resume` | `ultra:execute:cleanup` | + +The hook is fail-open — any error → title is left untouched. + +### Per-step timing (CC v2.1.97+) + +A `PostToolUse` hook (`hooks/scripts/post-bash-stats.mjs`) appends +`duration_ms` from each Bash tool call to +`${CLAUDE_PLUGIN_DATA}/ultraexecute-stats.jsonl`. One line per Bash +call; useful for identifying long-running verify or checkpoint commands +across executions. + --- ## The full pipeline @@ -605,6 +661,82 @@ CC-feature catalog, skill-factory, and architect/skill-author commands moved to Pure markdown commands and agents. Hooks are self-contained Node.js with zero dependencies. +## Extending the plugin + +Common modifications fork-ers make. None require touching `lib/` — +all of these are surface-level changes to commands, agents, or settings. + +### Add a new exploration agent + +Exploration agents run in parallel during `/ultraplan-local` Phase 5. +They read the codebase and contribute structured findings to plan +synthesis. + +1. Copy `agents/architecture-mapper.md` as a template: + ```bash + cp agents/architecture-mapper.md agents/my-new-agent.md + ``` +2. Update the frontmatter `name`, `description`, `tools`, and `model`. + Use `sonnet` unless the agent needs deep reasoning (most don't). +3. Add the agent to the swarm in `agents/planning-orchestrator.md` + Phase 5 — register it under the codebase-size bucket where it + should fire (always / medium+ / large only). +4. Update the agent table in `CLAUDE.md` and `README.md` to keep the + doc-consistency test green: + ```bash + npm test -- tests/lib/doc-consistency.test.mjs + ``` + +### Switch the planning model + +The default for `/ultrabrief-local`, `/ultraresearch-local`, +`/ultraplan-local`, and `/ultraexecute-local` is `opus` (deep +reasoning). To run on Sonnet for cost or latency, search-and-replace +the frontmatter in three files: + +```bash +sed -i.bak 's/^model: opus$/model: sonnet/' \ + commands/ultrabrief-local.md \ + commands/ultraresearch-local.md \ + commands/ultraplan-local.md \ + commands/ultraexecute-local.md +``` + +The exploration agents stay on Sonnet — only the orchestrator is bumped. + +### Disable external research + +`/ultraresearch-local --local` skips Tavily, Microsoft Learn, and the +Gemini bridge. To make `--local` the default, edit the front of +`commands/ultraresearch-local.md` Phase 1 and flip the default branch +of the `--local` argument check. Or just always pass `--local` and +document it in your team's CLAUDE.md. + +### Plugin data contract + +The four commands write to a single project directory (`.claude/projects/{date}-{slug}/`). +The full schema for every artifact is in [docs/HANDOVER-CONTRACTS.md](docs/HANDOVER-CONTRACTS.md). +That document is the single source of truth for: + +- File paths each command reads/writes +- Frontmatter schema for `brief.md`, `research/*.md`, `plan.md` +- `progress.json` schema +- Validator → handover mapping +- Versioning and breaking-change protocol + +If you fork the plugin and change the schema for any artifact, update +that doc *and* the corresponding `lib/validators/*.mjs` *and* run +`npm test` — the validators and doc-consistency tests will catch +schema drift. + +### Disable the architect bridge + +`/ultraplan-local` auto-discovers `architecture/overview.md` if the +separate `ultra-cc-architect` plugin produced one. To suppress this, +either don't install `ultra-cc-architect`, or set `architecture/` +absent in your project directory. Discovery is additive — missing +file is fine, no error. + ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md). diff --git a/plugins/ultraplan-local/SECURITY.md b/plugins/ultraplan-local/SECURITY.md new file mode 100644 index 0000000..eeb2e9e --- /dev/null +++ b/plugins/ultraplan-local/SECURITY.md @@ -0,0 +1,87 @@ +# Security Policy — ultraplan-local + +## Reporting a vulnerability + +Open a **private** issue on Forgejo: + +> https://git.fromaitochitta.com/open/ktg-plugin-marketplace + +Tag it `security` and mark it private. Do not file public issues for +unpatched vulnerabilities. There is no SLA — this is a solo-maintained +plugin — but acknowledged reports are usually triaged within 7 days. + +## Supported versions + +Only the **current minor version** receives security fixes. When v3.2.0 +ships, v3.1.x stops receiving patches. Pin to the latest minor and +update on the next bump. + +| Version | Supported | +|---------|-----------| +| 3.1.x | Yes | +| 3.0.x | No (upgrade to 3.1.x) | +| < 3.0 | No | + +## Scope + +The plugin's security posture covers: + +### Plugin-owned hooks (`hooks/scripts/`) + +| Hook | Trigger | Purpose | +|------|---------|---------| +| `pre-bash-executor.mjs` | `PreToolUse` for Bash | BLOCKs known-dangerous shell patterns; WARNs on suspicious ones; fails open on parse errors | +| `pre-write-executor.mjs` | `PreToolUse` for Write | BLOCKs writes to `.git/hooks/`, `~/.ssh/`, `.env`, and other sensitive paths | +| `pre-compact-flush.mjs` | `PreCompact` | Flushes `progress.json` from git history before compaction (P0 drift fix); read-only beyond `progress.json` | +| `session-title.mjs` *(planned, F9)* | `UserPromptSubmit` | Sets session title `ultra::` for headless multiplexing | + +All hooks are zero-dependency Node.js (`.mjs`) scripts and are designed +to **fail open** — a hook crash never blocks the user's work. Hooks log +to stderr only; they never write to user files outside their declared +scope. + +### Prompt-level denylist (`commands/ultraexecute-local.md`) + +The execute command embeds a denylist that takes effect even in headless +sessions where hooks may not fire. This is layer 4 of the defense-in-depth +model and protects against plan-injected destructive commands. + +### Validators (`lib/validators/*.mjs`) + +Read-only. Never write to user files. Used both by hooks and by command +phases to detect malformed artifacts before they propagate. + +## Out of scope + +- **`ultra-cc-architect` plugin.** Separate plugin with its own + `SECURITY.md`. The architecture-discovery validator in this plugin + treats `architecture/overview.md` as an external contract (drift-WARN, + never drift-FAIL). +- **LLM output content.** The plugin validates artifact *shape*, not + artifact *truthfulness*. A plan that passes `plan-validator --strict` + may still contain hallucinated file paths or unsafe commands; that is + why `pre-bash-executor` exists. +- **The Claude Code CLI itself.** Report Claude Code vulnerabilities to + Anthropic via https://github.com/anthropics/claude-code/issues. + +## Hardening recommendations + +For fork-ers handling untrusted task briefs or plans: + +1. **Set `disableSkillShellExecution: true`** in `~/.claude/settings.json` + (CC v2.1.91+) to prevent Skills from invoking arbitrary shell. +2. **Run plan validation in `--strict` mode** before any execute: + ```bash + node ${CLAUDE_PLUGIN_ROOT}/lib/validators/plan-validator.mjs --strict plan.md + ``` +3. **Review the plan-critic adversarial output** before approving plans + from external sources — semantic rubric (rule #7) catches deferred + decisions that an attacker could exploit. +4. **Pin a CC version floor.** v3.1.0 of this plugin assumes CC ≥ + 2.1.85 for the `if`-field on hooks; older CC silently ignores the + field, weakening the scoping. + +## Past advisories + +None as of v3.1.0. This section will list CVE-style entries if any are +discovered. diff --git a/plugins/ultraplan-local/agents/plan-critic.md b/plugins/ultraplan-local/agents/plan-critic.md index 7edb1d6..9ed9e78 100644 --- a/plugins/ultraplan-local/agents/plan-critic.md +++ b/plugins/ultraplan-local/agents/plan-critic.md @@ -80,17 +80,70 @@ You find what is wrong, what is missing, and what will break. ### 7. No-placeholder rule (BLOCKER-level) -Flag as **blocker** if ANY of these are found in the plan: -- "TBD", "TODO", "FIXME" as actual plan content (not in code quotes) -- "add appropriate error handling" or similar delegated decisions -- "update as needed", "adjust accordingly", "configure appropriately" -- File paths that do not exist and are not marked "(new file)" -- "Similar to step N" without repeating the specific content -- Steps that mention >2 files without specifying the change per file -- Steps with >3 change points (too complex — should be decomposed) +This rule has two parts: a **literal blockers** list (exact-string matches +that always fire) and a **semantic rubric** (instruction-shaped detection +that catches paraphrased deferrals). -These are unconditional blockers. A plan with placeholder language cannot -be executed without asking questions, which defeats the purpose. +#### 7a. Literal blockers (exact-string) + +Flag as **blocker** if any of these strings appear in the plan as actual +content (not inside code quotes or examples): + +- `TBD` +- `TODO` +- `FIXME` +- `XXX` (when used as a placeholder marker) + +These are unconditional. If the planner had to write a placeholder marker, +the decision was deferred. + +#### 7b. Semantic rubric (deferred-decision detection) + +Flag as **blocker** any clause that **defers a decision to the executor**. +A clause defers a decision if executing the step requires the executor to +choose something the plan did not specify. + +Apply this test to each step body, including verify/checkpoint/failure +clauses. A clause defers a decision if any of these are true: + +1. **Vague modifier without referent.** The step uses "appropriate", + "necessary", "as needed", "where appropriate", "if relevant", "as + required", "suitable", "reasonable" — and the plan does not separately + define what counts as appropriate/necessary/etc. +2. **Imperative without target.** The step says to do something + ("implement", "add", "wire up", "handle", "make production-ready", + "configure", "set up", "integrate") without naming the specific files, + functions, edits, or values involved. +3. **Forward reference without expansion.** The step says "similar to step + N" or "follow the same pattern" without restating the specific changes + for this step's files. +4. **Volume/quality without spec.** The step says "add tests" or "improve + coverage" without naming what to test or what coverage threshold counts + as success. +5. **Edge cases delegated.** The step says "handle edge cases" or + "add error handling" without enumerating the cases or the handling + strategy. +6. **Production-readiness delegated.** The step says "make this + production-ready", "harden it", "polish it" without listing the + concrete changes that constitute production-ready/hardened/polished. +7. **Path mismatch.** File paths that do not exist and are not marked + `(new file)`. +8. **Too many edits per step.** Steps that mention >2 files without + specifying the change per file, or steps with >3 distinct change + points (decompose). + +Calibration corpus (plan-critic must catch all five — these are paraphrased +deferrals that the v3.0 exact-string blacklist missed): + +- "implement as needed" → vague modifier without referent (rule 1) +- "wire it up" → imperative without target (rule 2) +- "make it production-ready" → production-readiness delegated (rule 6) +- "add tests where appropriate" → volume/quality without spec + vague + modifier (rules 1 + 4) +- "handle edge cases" → edge cases delegated (rule 5) + +A plan with deferred decisions cannot be executed without asking +questions, which defeats the purpose. ### 8. Verification gaps diff --git a/plugins/ultraplan-local/docs/architect-bridge-test.md b/plugins/ultraplan-local/docs/architect-bridge-test.md new file mode 100644 index 0000000..43893a9 --- /dev/null +++ b/plugins/ultraplan-local/docs/architect-bridge-test.md @@ -0,0 +1,46 @@ +# Architect-bridge smoke test + +`ultra-cc-architect` is a separate plugin (extracted in v3.0.0). This +plugin auto-discovers `architecture/overview.md` if produced. This +checklist verifies the bridge still works after either plugin changes. + +## Manual checklist + +1. Install both plugins from the marketplace. +2. Create a project: `/ultrabrief-local` → produce `brief.md` for a small + task ("add --verbose flag to a CLI"). +3. Run `/ultra-cc-architect-local --project `. Verify + `architecture/overview.md` and `architecture/gaps.md` appear. +4. Run `/ultraplan-local --project `. Verify the planner's + Phase 1 output mentions architecture-discovery as one of the inputs + (look for `architecture/overview.md` in the validator log). +5. Open the resulting `plan.md`. The plan should reference + `cc_features_proposed` from the architecture note when it picks + features. The plan does **not** have to adopt them — they are priors, + not requirements. + +## What "works" means + +- Discovery finds `architecture/overview.md` (or any of the tolerated + loose names: `architecture-overview.md`, etc.) and surfaces drift as + warnings only. +- Plan synthesis cross-references the architecture note without + hard-failing if it is missing. +- No CI test enforces the bridge; that is intentional. The two plugins + are filesystem-coupled, not code-coupled. + +## When to re-run + +- After bumping either plugin's minor or major version. +- After changing `lib/validators/architecture-discovery.mjs` in this + plugin. +- After changing the architecture-note schema in `ultra-cc-architect`. + +## Known tolerances + +| Drift | Behavior | +|-------|----------| +| Missing `architecture/` directory | Discovery returns absent; plan proceeds without architecture input | +| Loose name (e.g., `architecture-overview.md` at project root) | WARN; discovery still finds it | +| Body schema changed | WARN; discovery only reads the first heading | +| `cc_features_proposed` missing | Plan ignores priors silently; no error | diff --git a/plugins/ultraplan-local/examples/01-add-verbose-flag/REGENERATED.md b/plugins/ultraplan-local/examples/01-add-verbose-flag/REGENERATED.md new file mode 100644 index 0000000..9ebe6a0 --- /dev/null +++ b/plugins/ultraplan-local/examples/01-add-verbose-flag/REGENERATED.md @@ -0,0 +1,56 @@ +# Regeneration log — 01-add-verbose-flag + +| Field | Value | +|-------|-------| +| Last regenerated | 2026-05-01 | +| ultraplan-local version | 3.1.0 | +| Claude Code version | ≥ 2.1.105 (PreCompact-hook) | +| Source brief author | Hand-calibrated example, not LLM-generated | +| Plan author | Hand-calibrated to demonstrate plan_version 1.7 schema + manifest YAML | + +## What this is + +A complete walk-through of the four-stage pipeline for one realistic +small task: adding a `--verbose` flag to a hypothetical `small-auth` +CLI parser. Every artifact is hand-calibrated, not LLM-generated, so +fork-ers can study the *shape* without worrying about whether an +LLM hallucinated something. + +## What "regenerate" means + +If the artifact format changes (frontmatter schema, manifest YAML +keys, progress.json version), this example needs to be re-built so +fork-ers don't learn an obsolete shape. + +Triggers for regeneration: + +- `plan_version` bumps +- Frontmatter schema additions to `brief.md` or `research/*.md` +- New required keys in manifest YAML +- `progress.json` schema bump beyond `schema_version: "1"` + +When regenerating: do **not** run an actual LLM-driven pipeline against +this brief. Hand-calibrate against the new schema so the example stays +deterministic and reviewable. + +## Project assumed + +A fictional `small-auth` CLI with this layout: + +``` +small-auth/ +├── package.json +├── src/ +│ ├── cli.mjs # 80-line argv parser (hand-rolled) +│ └── commands/ +│ ├── login.mjs +│ ├── logout.mjs +│ ├── whoami.mjs +│ ├── token-refresh.mjs +│ ├── users-list.mjs +│ └── users-create.mjs +└── tests/ # 24 tests, node:test +``` + +This project is **not** in the plugin repo. The example artifacts +reference it as if it were the cwd of an `/ultraexecute-local` run. diff --git a/plugins/ultraplan-local/examples/01-add-verbose-flag/brief.md b/plugins/ultraplan-local/examples/01-add-verbose-flag/brief.md new file mode 100644 index 0000000..3e36f84 --- /dev/null +++ b/plugins/ultraplan-local/examples/01-add-verbose-flag/brief.md @@ -0,0 +1,55 @@ +--- +type: ultrabrief +brief_version: 1.0 +slug: add-verbose-flag +task: Add a --verbose flag to the small-auth CLI parser +research_topics: 1 +research_status: complete +brief_quality: ready +created: 2026-05-01 +--- + +# Add `--verbose` flag to small-auth CLI + +## Intent + +The `small-auth` CLI parser has six commands (`login`, `logout`, `whoami`, +`token-refresh`, `users-list`, `users-create`) and currently emits only +final results — no progress, no timings, no internal step trace. Operators +debugging slow `token-refresh` calls or mis-routed `users-list` queries +have no signal between "started" and "finished". + +We want a `--verbose` flag that, when passed, prints structured progress +lines to stderr without changing stdout output. Stdout stays the +machine-parseable contract; stderr becomes the human-readable trace. + +## Goal + +Add a single `--verbose` boolean flag, recognized by all six commands, +that emits one stderr line per internal step. No other behavioral +changes. The default (`--verbose` absent) produces output byte-identical +to today's CLI. + +## Success Criteria + +- `small-auth login --verbose alice` exits 0 and writes ≥ 3 stderr lines + prefixed `[verbose]` covering: argument parse, credential lookup, + session-token issue. +- `small-auth login alice` (no flag) writes exactly the same stdout as + before this change — verified by golden-file diff against + `tests/golden/login.stdout`. +- `--verbose` works in any position: `small-auth --verbose login alice`, + `small-auth login --verbose alice`, `small-auth login alice --verbose`. +- `--verbose` short form is `-v`. `-vv` is **not** recognized — only one + level. Document this in `--help`. +- All six commands accept the flag without rejection. Commands that have + no internal steps to trace (`whoami`) still accept the flag silently. +- Existing 24 tests in `tests/` continue to pass. Two new tests added: + one stdout-stability test, one stderr-content test for `login`. + +## Out of scope + +- Log levels beyond on/off (no `--debug`, `--trace`). +- Structured JSON logging — stderr stays plain text in this iteration. +- Logging configuration via env vars or config file. +- Any command other than the six listed. diff --git a/plugins/ultraplan-local/examples/01-add-verbose-flag/plan.md b/plugins/ultraplan-local/examples/01-add-verbose-flag/plan.md new file mode 100644 index 0000000..97687c0 --- /dev/null +++ b/plugins/ultraplan-local/examples/01-add-verbose-flag/plan.md @@ -0,0 +1,251 @@ +# Add `--verbose` flag to small-auth CLI + +plan_version: 1.7 + +> **Plan quality: A** (92/100) — APPROVE +> +> Generated by ultraplan-local v3.1.0 on 2026-05-01. + +## Context + +The `small-auth` CLI has six commands and emits only final results; no +progress, no internal step trace. Operators debugging slow `token-refresh` +or mis-routed `users-list` calls have no signal between "started" and +"finished". This plan adds a `--verbose` / `-v` flag that, when set, +emits structured progress lines to stderr without changing stdout. The +default path stays byte-identical. + +This is a textbook minimal-scope addition: the parser is small, +centralized, and already supports global flags. + +## Architecture Diagram + +```mermaid +graph TD + subgraph "Changes in this plan" + cli["src/cli.mjs
parse globalFlags"] + ctx["ctx object
+ verbose: boolean"] + login["src/commands/login.mjs
+ 3 verbose calls"] + token["src/commands/token-refresh.mjs
+ 4 verbose calls"] + userlist["src/commands/users-list.mjs
+ 2 verbose calls"] + usercreate["src/commands/users-create.mjs
+ 3 verbose calls"] + logout["src/commands/logout.mjs
+ 2 verbose calls"] + whoami["src/commands/whoami.mjs
(accepts flag, no traces)"] + help["src/cli.mjs
--help text"] + tests["tests/cli-verbose-flag.test.mjs
tests/cli-no-verbose-stability.test.mjs"] + + cli --> ctx + ctx --> login + ctx --> token + ctx --> userlist + ctx --> usercreate + ctx --> logout + ctx --> whoami + cli --> help + login --> tests + end +``` + +## Codebase Analysis + +- **Tech stack:** Node.js ≥ 18, no external runtime dependencies, `node:test` for tests +- **Key patterns:** hand-rolled argv parser, two-pass extract (globals → command), handler contract `run(positional, flags, ctx)` +- **Relevant files:** `src/cli.mjs`, `src/commands/{login,logout,whoami,token-refresh,users-list,users-create}.mjs`, `tests/` +- **Reusable code:** existing `[error]` stderr pattern at `src/cli.mjs:67` — mirror it for `[verbose]` +- **External tech:** none +- **Recent git activity:** parser last changed in commit `ab1c2d3` (added `--version`); pattern still current + +## Research Sources + +*Internal research only — see `research/01-cli-parser-conventions.md`.* + +## Implementation Plan + +Each step targets 1–2 files and one focused change. TDD structure: test +or stability harness comes before behavior change. + +### Step 1: Capture golden stdout for stability test + +- **Files:** `tests/golden/login.stdout` (new file), `tests/golden/whoami.stdout` (new file), `tests/golden/users-list.stdout` (new file) +- **Changes:** Run current CLI for three representative commands, save stdout byte-for-byte. Use `node src/cli.mjs login alice > tests/golden/login.stdout` and similar. +- **Verify:** `wc -c tests/golden/*.stdout` → expected: each file > 0 bytes +- **Checkpoint:** `git commit -m "test(small-auth): capture pre-change golden stdout for verbose-flag stability"` +- **On failure:** revert files; do not proceed. Likely cause: CLI itself broken — investigate before continuing. +- **Manifest:** + ```yaml + manifest: + expected_paths: + - tests/golden/login.stdout + - tests/golden/whoami.stdout + - tests/golden/users-list.stdout + min_file_count: 3 + commit_message_pattern: "^test\\(small-auth\\): capture" + bash_syntax_check: [] + forbidden_paths: [] + must_contain: [] + ``` + +### Step 2: Add stability test (must FAIL initially — verbose not yet wired) + +- **Files:** `tests/cli-no-verbose-stability.test.mjs` (new file) +- **Changes:** Three subtests, one per golden file. Each runs `node src/cli.mjs ...` and asserts stdout `===` `readFileSync('tests/golden/.stdout')`. The test should PASS today (no behavior change yet) — it's the canary for step 5 onwards. +- **Verify:** `node --test tests/cli-no-verbose-stability.test.mjs` → expected: 3 pass +- **Checkpoint:** `git commit -m "test(small-auth): stdout stability harness for verbose-flag work"` +- **On failure:** if subtests fail, the goldens are wrong — re-run step 1. +- **Manifest:** + ```yaml + manifest: + expected_paths: + - tests/cli-no-verbose-stability.test.mjs + min_file_count: 1 + commit_message_pattern: "^test\\(small-auth\\): stdout stability" + bash_syntax_check: [] + forbidden_paths: [] + must_contain: + - path: tests/cli-no-verbose-stability.test.mjs + pattern: "tests/golden/login\\.stdout" + ``` + +### Step 3: Extend parser to recognize `--verbose` and `-v` + +- **Files:** `src/cli.mjs` +- **Changes:** At `src/cli.mjs:34` (alias table) add `'-v': '--verbose'`. At `src/cli.mjs:48` (globalFlags loop) add `'--verbose'` case that sets `globalFlags.verbose = true`. Default the field to `false`. The flag is consumed (removed from argv) like `--help` and `--version`. +- **Verify:** `node src/cli.mjs --verbose login alice 2>&1 | head -1` → expected: no parse error +- **Checkpoint:** `git commit -m "feat(cli): recognize --verbose / -v as global flag"` +- **On failure:** revert `src/cli.mjs`; rerun stability test to confirm clean baseline. +- **Manifest:** + ```yaml + manifest: + expected_paths: + - src/cli.mjs + min_file_count: 1 + commit_message_pattern: "^feat\\(cli\\): recognize --verbose" + bash_syntax_check: [] + forbidden_paths: [] + must_contain: + - path: src/cli.mjs + pattern: "globalFlags\\.verbose" + ``` + +### Step 4: Pass `verbose` into handler `ctx` + +- **Files:** `src/cli.mjs` +- **Changes:** At `src/cli.mjs:62` (ctx construction) add `verbose: globalFlags.verbose` to the ctx literal. No handler changes yet. +- **Verify:** `node --test tests/cli-no-verbose-stability.test.mjs` → expected: 3 pass (handlers ignore the new field for now) +- **Checkpoint:** `git commit -m "feat(cli): thread verbose into command handler ctx"` +- **On failure:** stability tests fail → ctx mutation broke something. Bisect by reverting and adding back one line at a time. +- **Manifest:** + ```yaml + manifest: + expected_paths: + - src/cli.mjs + min_file_count: 1 + commit_message_pattern: "^feat\\(cli\\): thread verbose" + bash_syntax_check: [] + forbidden_paths: [] + must_contain: + - path: src/cli.mjs + pattern: "verbose: globalFlags\\.verbose" + ``` + +### Step 5: Wire verbose output in `login`, `token-refresh`, `users-list`, `users-create`, `logout` + +- **Files:** `src/commands/login.mjs`, `src/commands/token-refresh.mjs`, `src/commands/users-list.mjs`, `src/commands/users-create.mjs`, `src/commands/logout.mjs` +- **Changes:** At each internal step (3 for login, 4 for token-refresh, 2 for users-list, 3 for users-create, 2 for logout — 14 call sites total), add `if (ctx.verbose) ctx.stderr.write(\`[verbose] \\n\`);`. Step descriptions per file: + - login: "parsing argv", "credential lookup", "issuing session token" + - token-refresh: "parsing argv", "validating refresh token", "rotating session token", "persisting new token" + - users-list: "parsing argv", "querying user store" + - users-create: "parsing argv", "validating input", "writing user record" + - logout: "parsing argv", "invalidating session token" +- **Verify:** `node --test tests/cli-no-verbose-stability.test.mjs` → expected: 3 pass (stdout unchanged when flag absent) +- **Checkpoint:** `git commit -m "feat(commands): emit verbose stderr trace for 5 commands"` +- **On failure:** stability tests fail → likely a stray `console.log` or `ctx.stdout.write` instead of `ctx.stderr.write`. Re-grep all five files for `stdout` mentions added in this step. +- **Manifest:** + ```yaml + manifest: + expected_paths: + - src/commands/login.mjs + - src/commands/token-refresh.mjs + - src/commands/users-list.mjs + - src/commands/users-create.mjs + - src/commands/logout.mjs + min_file_count: 5 + commit_message_pattern: "^feat\\(commands\\): emit verbose" + bash_syntax_check: [] + forbidden_paths: [] + must_contain: + - path: src/commands/login.mjs + pattern: "ctx\\.verbose" + - path: src/commands/token-refresh.mjs + pattern: "ctx\\.verbose" + ``` + +### Step 6: Add verbose-content test for `login` + +- **Files:** `tests/cli-verbose-flag.test.mjs` (new file) +- **Changes:** Single test: spawn `node src/cli.mjs login --verbose alice`, capture stderr, assert exit 0, assert stderr contains all three expected verbose lines: "[verbose] parsing argv", "[verbose] credential lookup", "[verbose] issuing session token", in that order. +- **Verify:** `node --test tests/cli-verbose-flag.test.mjs` → expected: 1 pass +- **Checkpoint:** `git commit -m "test(small-auth): assert --verbose emits expected stderr trace"` +- **On failure:** if assertion misses a line, check step 5 for typos in the `[verbose]` strings; if exit code != 0, check that login still works without verbose (regression). +- **Manifest:** + ```yaml + manifest: + expected_paths: + - tests/cli-verbose-flag.test.mjs + min_file_count: 1 + commit_message_pattern: "^test\\(small-auth\\): assert --verbose" + bash_syntax_check: [] + forbidden_paths: [] + must_contain: + - path: tests/cli-verbose-flag.test.mjs + pattern: "\\[verbose\\] credential lookup" + ``` + +### Step 7: Update `--help` text + +- **Files:** `src/cli.mjs` +- **Changes:** At the help-text constant (`src/cli.mjs:78`), add a line under "Global flags": ` -v, --verbose emit per-step trace to stderr (single level only)`. +- **Verify:** `node src/cli.mjs --help | grep -E "verbose"` → expected: 1 line containing "emit per-step trace" +- **Checkpoint:** `git commit -m "docs(cli): document --verbose / -v in --help text"` +- **On failure:** revert just the constant; help text isn't load-bearing. +- **Manifest:** + ```yaml + manifest: + expected_paths: + - src/cli.mjs + min_file_count: 1 + commit_message_pattern: "^docs\\(cli\\): document --verbose" + bash_syntax_check: [] + forbidden_paths: [] + must_contain: + - path: src/cli.mjs + pattern: "emit per-step trace" + ``` + +## Verification + +Final acceptance run after step 7: + +```bash +node --test tests/ # all 26 tests pass (24 + 2 new) +node src/cli.mjs login alice > /tmp/out 2>/dev/null +diff /tmp/out tests/golden/login.stdout # exit 0 +node src/cli.mjs login --verbose alice 2>/tmp/err 1>/dev/null +grep -c "\[verbose\]" /tmp/err # ≥ 3 +node src/cli.mjs --help | grep -c "\-v, --verbose" # 1 +``` + +## Plan-critic notes + +- No deferred decisions: every step names its files, lines, and exact + string changes. +- TDD: stability harness (step 2) precedes behavior changes (steps 3-5). +- Verify commands are runnable, not "test it works". +- Steps 5 wires 5 files in one commit; this is over the 1–2 file + guideline but is justified by symmetry — the change is mechanical + and atomic across the five files; splitting would create five tiny + commits with no test value between them. + +## Execution Strategy + +Single session, 7 steps, ~15-20 minutes. No parallel decomposition needed. diff --git a/plugins/ultraplan-local/examples/01-add-verbose-flag/progress.json b/plugins/ultraplan-local/examples/01-add-verbose-flag/progress.json new file mode 100644 index 0000000..c35e8f6 --- /dev/null +++ b/plugins/ultraplan-local/examples/01-add-verbose-flag/progress.json @@ -0,0 +1,112 @@ +{ + "schema_version": "1", + "slug": "add-verbose-flag", + "plan": ".claude/projects/2026-05-01-add-verbose-flag/plan.md", + "plan_path": ".claude/projects/2026-05-01-add-verbose-flag/plan.md", + "plan_version": "1.7", + "mode": "single", + "session_start_sha": "ab1c2d3e4f5g6h7i8j9k0l1m2n3o4p5q6r7s8t9", + "started_at": "2026-05-01T10:14:32Z", + "updated_at": "2026-05-01T10:31:08Z", + "status": "completed", + "current_step": 7, + "total_steps": 7, + "steps": [ + { + "n": 1, + "title": "Capture golden stdout for stability test", + "status": "completed", + "started_at": "2026-05-01T10:14:32Z", + "completed_at": "2026-05-01T10:16:01Z", + "commit_sha": "c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0", + "files_changed": [ + "tests/golden/login.stdout", + "tests/golden/whoami.stdout", + "tests/golden/users-list.stdout" + ], + "verify_passed": true + }, + { + "n": 2, + "title": "Add stability test (must FAIL initially — verbose not yet wired)", + "status": "completed", + "started_at": "2026-05-01T10:16:01Z", + "completed_at": "2026-05-01T10:18:42Z", + "commit_sha": "d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1", + "files_changed": [ + "tests/cli-no-verbose-stability.test.mjs" + ], + "verify_passed": true + }, + { + "n": 3, + "title": "Extend parser to recognize --verbose and -v", + "status": "completed", + "started_at": "2026-05-01T10:18:42Z", + "completed_at": "2026-05-01T10:20:55Z", + "commit_sha": "e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2", + "files_changed": [ + "src/cli.mjs" + ], + "verify_passed": true + }, + { + "n": 4, + "title": "Pass verbose into handler ctx", + "status": "completed", + "started_at": "2026-05-01T10:20:55Z", + "completed_at": "2026-05-01T10:22:13Z", + "commit_sha": "f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3", + "files_changed": [ + "src/cli.mjs" + ], + "verify_passed": true + }, + { + "n": 5, + "title": "Wire verbose output in login, token-refresh, users-list, users-create, logout", + "status": "completed", + "started_at": "2026-05-01T10:22:13Z", + "completed_at": "2026-05-01T10:27:34Z", + "commit_sha": "a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4", + "files_changed": [ + "src/commands/login.mjs", + "src/commands/token-refresh.mjs", + "src/commands/users-list.mjs", + "src/commands/users-create.mjs", + "src/commands/logout.mjs" + ], + "verify_passed": true + }, + { + "n": 6, + "title": "Add verbose-content test for login", + "status": "completed", + "started_at": "2026-05-01T10:27:34Z", + "completed_at": "2026-05-01T10:29:51Z", + "commit_sha": "b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5", + "files_changed": [ + "tests/cli-verbose-flag.test.mjs" + ], + "verify_passed": true + }, + { + "n": 7, + "title": "Update --help text", + "status": "completed", + "started_at": "2026-05-01T10:29:51Z", + "completed_at": "2026-05-01T10:31:08Z", + "commit_sha": "c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6", + "files_changed": [ + "src/cli.mjs" + ], + "verify_passed": true + } + ], + "stats": { + "total_duration_ms": 996000, + "verify_failures": 0, + "manifest_failures": 0, + "rollbacks": 0 + } +} diff --git a/plugins/ultraplan-local/examples/01-add-verbose-flag/research/01-cli-parser-conventions.md b/plugins/ultraplan-local/examples/01-add-verbose-flag/research/01-cli-parser-conventions.md new file mode 100644 index 0000000..0467a99 --- /dev/null +++ b/plugins/ultraplan-local/examples/01-add-verbose-flag/research/01-cli-parser-conventions.md @@ -0,0 +1,87 @@ +--- +type: ultraresearch-brief +research_version: 1.0 +question: How does small-auth currently parse arguments and where should --verbose hook in? +confidence: 0.85 +dimensions: 4 +created: 2026-05-01 +--- + +# CLI parser conventions in small-auth + +## Executive Summary + +`small-auth` uses a hand-rolled argv parser at `src/cli.mjs:12-58` with a +two-pass approach: first pass extracts global flags (currently +`--help`, `--version`), second pass dispatches to command handlers in +`src/commands/*.mjs`. Adding `--verbose` requires touching only the +first-pass extractor and a new `verbose` parameter in the handler +contract — six command handlers each get a one-line update. + +The parser does **not** use `commander`, `yargs`, or any external +library — this is intentional (zero deps) and consistent with the +plugin marketplace's broader convention. We keep that. + +`stderr` is currently unused except for fatal errors. Adding verbose +output to stderr does not collide with anything. + +Confidence: 0.85. The 0.15 uncertainty is around whether +`--verbose` should propagate into nested helper modules +(`src/lib/auth-token.mjs` calls `src/lib/db.mjs`); the plan should +either pass `verbose` via a context object or use a module-scoped +log function. Both work; the brief doesn't specify, so the planner +will choose. + +## Dimensions + +### 1. Argument-parsing layer + +The parser at `src/cli.mjs:12-58` returns +`{globalFlags: {help, version}, command, positional, commandFlags}`. +We add `verbose: boolean` to `globalFlags`. The two-pass design means +`--verbose` works in any position automatically — no extra effort. + +`-v` short form maps to `--verbose` via the existing alias table at +`src/cli.mjs:34`. + +### 2. Command-handler contract + +Each handler in `src/commands/*.mjs` exports +`async function run(positional, flags, ctx)`. Today `ctx` is +`{stdout, stderr, env}`. We extend `ctx` with `verbose: boolean` so +handlers can branch on it without re-reading globalFlags. + +### 3. Internal log emission pattern + +Existing fatal errors call `ctx.stderr.write(\`[error] ...\\n\`)`. The +verbose pattern matches: `if (ctx.verbose) ctx.stderr.write(\`[verbose] ...\\n\`)`. +No log helper needed for this iteration — six call sites total. Refactoring +into a `verbose()` helper is reasonable but not required for the goal. + +### 4. Test infrastructure + +Tests live in `tests/*.test.mjs` using `node:test`. Existing tests run +the CLI as a subprocess via `child_process.execFile` and assert on +exit code + stdout. Two new tests are needed: + +- `tests/cli-verbose-flag.test.mjs` — assert `login --verbose alice` + exits 0, stderr contains "[verbose]", stdout matches golden file. +- `tests/cli-no-verbose-stability.test.mjs` — assert + `login alice` stdout is byte-identical to `tests/golden/login.stdout`. + +## Citations + +- `src/cli.mjs:12-58` — parser implementation +- `src/commands/login.mjs:8-42` — typical handler shape +- `tests/cli-help.test.mjs:14` — subprocess testing pattern +- `package.json:scripts.test` — `node --test tests/*.test.mjs` + +## Brief anchoring + +Brief task: "Add a --verbose flag to the small-auth CLI parser". +This research answers the planner's first question: where to hook +into. The parser is small and centralized, so the change is +minimal-scope. + +The brief's success criteria around byte-identical default stdout +maps directly to the stability test in dimension 4. diff --git a/plugins/ultraplan-local/examples/README.md b/plugins/ultraplan-local/examples/README.md new file mode 100644 index 0000000..50b9852 --- /dev/null +++ b/plugins/ultraplan-local/examples/README.md @@ -0,0 +1,73 @@ +# Examples + +Complete kalibrerte walk-throughs of the ultraplan-local pipeline for +realistic tasks. Each example shows the four artifacts a project +directory contains after a full run: + +- `brief.md` — task brief from `/ultrabrief-local` +- `research/*.md` — research briefs from `/ultraresearch-local` +- `plan.md` — implementation plan from `/ultraplan-local` +- `progress.json` — execution log from `/ultraexecute-local` + +These are **hand-calibrated**, not LLM-generated. The point is to give +a fork-er a deterministic reference — what the artifacts look like +when everything goes right, with a small but real task. + +## Running pipeline yourself + +For your own work, point the four commands at a real project directory: + +```bash +mkdir -p .claude/projects/2026-05-01-my-task +/ultrabrief-local +/ultraresearch-local --project .claude/projects/2026-05-01-my-task +/ultraplan-local --project .claude/projects/2026-05-01-my-task +/ultraexecute-local --project .claude/projects/2026-05-01-my-task +``` + +The artifacts in each example mirror that flow. + +## Examples + +### 01-add-verbose-flag + +**Task:** add a `--verbose` flag to a small CLI parser. Touches one +parser file and six command handlers; adds two tests. + +**Why this example:** small enough to read end-to-end in 10 minutes, +but exercises every artifact (research with brief-anchoring, plan with +manifests, progress.json with multi-step git history). Demonstrates +how `plan_version: 1.7` schema looks in real life — including the +manifest YAML block per step and the `must_contain` list-of-dicts +form. + +**What to study first:** + +1. `brief.md` — note the explicit `Out of scope` section and concrete + `Success Criteria` (no "make it work" hand-waving). +2. `plan.md` Step 1 — note that the FIRST step captures golden output + *before* any behavior change. This is the stability harness pattern. +3. `plan.md` Step 5 — note that this step touches 5 files in one + commit, and the plan justifies the deviation from the 1–2 file + guideline. Plan-critic should accept that justification. +4. `progress.json` — every step has both `commit_sha` and + `verify_passed`. Resumes work from the last completed step. + +## Regeneration + +Each example has a `REGENERATED.md` documenting the version it was +calibrated against. When the artifact format changes, the example +needs to be re-built. See the `REGENERATED.md` file in each example +for triggers and procedure. + +## Adding a new example + +If you have a small, realistic task (touches 1-3 files, has a clear +success criterion, finishes in under 30 minutes) and want to add it +as an example: + +1. Create `examples/NN-slug-here/` with the same four artifacts. +2. Add a `REGENERATED.md` documenting the calibration date and version. +3. Add a section to this README under `## Examples`. +4. Open an issue on the marketplace describing what the example + teaches that 01 doesn't already teach. diff --git a/plugins/ultraplan-local/hooks/hooks.json b/plugins/ultraplan-local/hooks/hooks.json index 617fbaf..0b8c7f2 100644 --- a/plugins/ultraplan-local/hooks/hooks.json +++ b/plugins/ultraplan-local/hooks/hooks.json @@ -1,46 +1,55 @@ - { - "hooks": { - "PreToolUse": [ - { - { - "hooks": { - { - { - { - { - "hooks": { - { - { - "hooks": { - med: - - { - "hooks": { - { - /Users/ktg/.claude/plugins/marketplaces/ktg-plugin-marketplace/plugins/ultraplan-local/hooks/hooks.json med: - - { - "hooks": { - "PreToolUse": [ - { - "hooks": { - "PreToolUse": [ - { - "hooks": { - - { - "hooks": { - "PreToolUse": [ - med: - "hooks": { - "PreToolUse": [ - { - "matcher": "Bash", - "hooks": [ - { - "type": "command", - "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/pre-bash-executor.mjs" - } - ] }, { "matcher": "Write", "hooks": [ { "type": "command", "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/pre-write-executor.mjs" } ] } ], "PreCompact": [ { "hooks": [ - { "type": "command", "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/pre-compact-flush.mjs" } ] - } ] } } \ No newline at end of file +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/pre-bash-executor.mjs" + } + ] + }, + { + "matcher": "Write", + "hooks": [ + { + "type": "command", + "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/pre-write-executor.mjs" + } + ] + } + ], + "UserPromptSubmit": [ + { + "hooks": [ + { + "type": "command", + "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/session-title.mjs" + } + ] + } + ], + "PostToolUse": [ + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/post-bash-stats.mjs" + } + ] + } + ], + "PreCompact": [ + { + "hooks": [ + { + "type": "command", + "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/pre-compact-flush.mjs" + } + ] + } + ] + } +} diff --git a/plugins/ultraplan-local/hooks/scripts/post-bash-stats.mjs b/plugins/ultraplan-local/hooks/scripts/post-bash-stats.mjs new file mode 100755 index 0000000..699e3c5 --- /dev/null +++ b/plugins/ultraplan-local/hooks/scripts/post-bash-stats.mjs @@ -0,0 +1,58 @@ +#!/usr/bin/env node +// post-bash-stats.mjs — PostToolUse hook (CC v2.1.97+) +// +// Captures duration_ms from PostToolUse payload for Bash tool calls and +// appends a structured stats line to ${CLAUDE_PLUGIN_DATA}/ultraexecute-stats.jsonl +// when the running session is an ultraexecute session. +// +// Detection: only fires when the tool input matches the verify/checkpoint +// pattern of an ultraexecute step (i.e., the command was issued from inside +// /ultraexecute-local). We err on the side of "log everything in plugin +// scope" — duration data is cheap and the alternative is missing real +// per-step timings. +// +// Fail-open invariant: any error → exit 0, no output, no log line. + +import { stdin } from 'node:process'; +import { appendFileSync, mkdirSync } from 'node:fs'; +import { dirname, join } from 'node:path'; + +async function readStdin() { + let data = ''; + for await (const chunk of stdin) data += chunk; + return data; +} + +(async () => { + try { + const raw = await readStdin(); + if (!raw.trim()) return; + const payload = JSON.parse(raw); + + if (payload.tool_name !== 'Bash') return; + const duration = payload.duration_ms; + if (typeof duration !== 'number') return; + + const dataDir = process.env.CLAUDE_PLUGIN_DATA; + if (!dataDir) return; + + const cmd = payload.tool_input?.command || ''; + if (!cmd) return; + + const line = JSON.stringify({ + ts: new Date().toISOString(), + session_id: payload.session_id || null, + command_excerpt: cmd.slice(0, 120), + duration_ms: duration, + success: payload.tool_response?.success !== false, + }); + + const target = join(dataDir, 'ultraexecute-stats.jsonl'); + try { + mkdirSync(dirname(target), { recursive: true }); + } catch {} + appendFileSync(target, line + '\n'); + } catch { + // fail open + } +})(); diff --git a/plugins/ultraplan-local/hooks/scripts/session-title.mjs b/plugins/ultraplan-local/hooks/scripts/session-title.mjs new file mode 100755 index 0000000..d3f2a10 --- /dev/null +++ b/plugins/ultraplan-local/hooks/scripts/session-title.mjs @@ -0,0 +1,86 @@ +#!/usr/bin/env node +// session-title.mjs — UserPromptSubmit hook (CC v2.1.94+) +// +// Sets a sessionTitle when the user invokes one of the four ultra commands, +// so multi-session headless runs are easy to identify in process lists and +// session pickers. +// +// Title format: ultra:: +// - ∈ {brief, research, plan, execute} +// - ∈ first 30 chars of project slug, or "ad-hoc" when no +// --project / --brief context is detected +// +// Fail-open invariant: any error → exit 0 with no output. We never block +// the user's prompt. + +import { stdin } from 'node:process'; +import { resolve, basename } from 'node:path'; + +const COMMANDS = { + '/ultrabrief-local': 'brief', + '/ultraresearch-local': 'research', + '/ultraplan-local': 'plan', + '/ultraexecute-local': 'execute', +}; + +function slugify(s) { + return String(s) + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, '') + .slice(0, 30) || 'ad-hoc'; +} + +function detectSlug(prompt) { + const projectMatch = prompt.match(/--project[=\s]+(\S+)/); + if (projectMatch) { + const dir = projectMatch[1].replace(/['"]/g, ''); + const base = basename(resolve(dir)); + const dateStripped = base.replace(/^\d{4}-\d{2}-\d{2}-/, ''); + return slugify(dateStripped); + } + const briefMatch = prompt.match(/--brief[=\s]+(\S+)/); + if (briefMatch) { + const file = briefMatch[1].replace(/['"]/g, ''); + return slugify(basename(file, '.md')); + } + return 'ad-hoc'; +} + +async function readStdin() { + let data = ''; + for await (const chunk of stdin) data += chunk; + return data; +} + +(async () => { + try { + const raw = await readStdin(); + if (!raw.trim()) return; + const payload = JSON.parse(raw); + const prompt = String(payload.prompt || '').trim(); + if (!prompt) return; + + let matchedCmd = null; + for (const [cmd, short] of Object.entries(COMMANDS)) { + if (prompt.startsWith(cmd)) { + matchedCmd = short; + break; + } + } + if (!matchedCmd) return; + + const slug = detectSlug(prompt); + const title = `ultra:${matchedCmd}:${slug}`; + + const out = { + hookSpecificOutput: { + hookEventName: 'UserPromptSubmit', + sessionTitle: title, + }, + }; + process.stdout.write(JSON.stringify(out) + '\n'); + } catch { + // fail open + } +})();