Skip to content

feat: v0.20 UX & onboarding — per-command help, doctor --fix, forge tools + auto-gitignore, zero-config wiring, dash clarity#69

Merged
CodeWithJuber merged 7 commits into
masterfrom
claude/research-paper-implementation-va5g2u
Jul 17, 2026
Merged

feat: v0.20 UX & onboarding — per-command help, doctor --fix, forge tools + auto-gitignore, zero-config wiring, dash clarity#69
CodeWithJuber merged 7 commits into
masterfrom
claude/research-paper-implementation-va5g2u

Conversation

@CodeWithJuber

Copy link
Copy Markdown
Owner

What & why

Follow-up round addressing three concrete UX/onboarding problems raised after the v0.20.0 feature drop.

1 — Help & discoverability

  • feat(help)forge <command> --help/-h now works for every command (it was advertised in the banner but silently dropped by each command's flag filter — now intercepted centrally before dispatch). Adds word forms forge help [command] and forge version, and "did you mean …?" on unknown commands via a character-bigram suggest() over the existing setOverlap. COMMANDS entries may now be a summary string OR a rich {summary, usage, flags, examples, env} object (both coexist; read through new commandSummary/commandHelp; docs_check reads only keys, so the migration is safe).

2 — Zero-config onboarding (the "too hard to set up" complaint)

  • feat(doctor)forge doctor --fix: each safely-fixable finding now carries a repair that reuses an existing idempotent function — missing hooks/permissions → mergeSettings, missing ledger gitattributes → ensureLedgerGitattributes, stale/missing AGENTS.mdsync, non-executable guards → chmod +x. Prints a repairs: block, re-checks, idempotent on a second run; unsafe findings stay report-only. Also reconciles the node check with engines (>=20: fail <18, warn 18–19, ok 20+).
  • feat(init)forge init --settings-only runs only the marker-guarded settings merge (no repo emit), and install.sh now calls it instead of printing a block to paste by hand. A stateless first-run hint points at forge init/forge doctor --fix until settings are forge-managed (FORGE_NO_HINT=1 mutes it).

3 — Agentic-folder noise (the ".cursor/.gemini clutter" complaint)

  • feat(tools)forge tools: forge tools <name> records the primary tool in .forge/config.json and writes a marked, reversible .gitignore block (# forge:gitignore:begin … :end, src/gitignore.js) ignoring exactly the NON-primary emit targets, computed from sync()'s own report rows. User lines, AGENTS.md, and the primary tool's files are never touched; --reset strips only the block. Opt-in — plain forge sync never writes .gitignore.

Plus

  • feat(dash) — first-run empty-state banner + a ? panel legend explaining each panel in one sentence (the "dashboard is confusing" complaint). Display-only; append-only writes unchanged.
  • chore(hooks)web session-start install hook (.claude/hooks/session-start.sh, wired alongside the existing recall/cortex hooks): synchronous, idempotent, gated on CLAUDE_CODE_REMOTE=true, so Claude Code on the web can lint and test.

Checklist

  • npm test passes — 886 pass / 0 fail / 2 pre-existing skips
  • npm run check passes (Biome — 6 pre-existing warnings, 0 errors)
  • New public functions have a test (test/help.test.js, test/gitignore.test.js, test/tools.test.js, + extended doctor/init/dash/dogfood)
  • Conventional commit messages
  • CHANGELOG.md updated under ## [Unreleased] (six bullets)
  • No new runtime dependency
  • Substrate/docs updated — forge tools in README + docs/GUIDE.md; FORGE_NO_HINT documented; docs check green

Risk & rollback

  • Risk level: medium — new surfaces are additive; doctor --fix only calls already-idempotent functions, unsafe checks stay report-only; the .gitignore writer manages only its own marked block and is opt-in + reversible; the first-run hint is stateless with a kill switch; the session-start hook is a no-op outside CLAUDE_CODE_REMOTE=true.
  • Rollback plan: revert the merge; forge tools --reset strips the gitignore block; FORGE_NO_HINT=1 mutes the hint. No ledger schema change.

Extra checks (tick if applicable)

  • npm run typecheck passes (JSDoc/tsc)
  • Input validated at boundaries; errors handled — doctor --fix and the gitignore writer are fail-safe; hook is set -euo pipefail + shellcheck-clean
  • Authorization/ownership checked (n/a — local dev tooling)
  • Logs contain no secrets/PII
  • If AI-assisted: understood, package APIs verified, tests included

🤖 Generated with Claude Code

https://claude.ai/code/session_017Ebv4QDTqzPzgApBW2KUsU


Generated by Claude Code

claude added 7 commits July 17, 2026 13:34
Makes `forge <cmd> --help` real (it was advertised in the banner but silently
dropped by each command's flag filter) by intercepting --help/-h centrally in the
dispatcher before command dispatch. Adds word forms `forge help [cmd]` and
`forge version`, and a "did you mean" suggestion on unknown commands via a
character-bigram suggest() over the existing setOverlap.

COMMANDS entries may now be a summary string OR a rich {summary,usage,flags,
examples,env} object; both coexist and are read through commandSummary/commandHelp
(printHelp, the only value-reader, goes through commandSummary). docs_check reads
only Object.keys(COMMANDS), so the migration is safe. High-traffic commands
(init/sync/doctor/update) migrated to rich help; the rest stay strings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Ebv4QDTqzPzgApBW2KUsU
…es/chmod) + node threshold

Each fixable check in src/doctor.js now carries an optional {id,label,run} descriptor.
doctor({fix}) runs run() for every warn/fail result that has one, collects a repairs[]
list, then re-runs all checks so the returned results reflect the repaired state.

Fix map reuses existing idempotent functions only:
- missing settings hooks/permissions -> mergeSettings({settingsPath}) (src/init.js)
- missing ledger union-merge rule    -> ensureLedgerGitattributes (src/init.js)
- missing/stale AGENTS.md            -> sync({targetRoot}) (src/sync.js)
- guards not +x                      -> chmodSync mode | 0o111

A new checkSettings surfaces a warn (with a mergeSettings fix) when the user's
~/.claude/settings.json lacks the forge marker or hooks. Unsafe checks (provider
keys, MCP count, pricing, gateway) carry no descriptor and stay report-only.

checkNode reconciled with package.json engines (>=20): fail <18, warn 18-19, ok >=20,
ending the old 18-vs-20 mismatch without hard-breaking node-18 users.

CLI `forge doctor` parses --fix, printing a repairs: block then the re-checked results;
--json includes repairs. doctor already documents --fix in its COMMANDS entry.

Docs: README + docs/GUIDE mention `forge doctor --fix`; CHANGELOG [Unreleased] bullet.
Tests: settings warn->ok via --fix, gitattributes rule added, --fix idempotent (no-op).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Ebv4QDTqzPzgApBW2KUsU
Zero-config onboarding (Concern 2b + 2c):

- init({settingsOnly}) runs the idempotent, _forge-marker-guarded mergeSettings
  ONLY — no repo emit, no AGENTS.md, no gitattributes — so install.sh can wire
  hooks + permissions into ~/.claude/settings.json without touching the repo.
- cli.js `init` handler parses --settings-only and reports the merge outcome.
- install.sh replaces its printed paste-by-hand settings block with an actual
  `node "$REPO/src/cli.js" init --settings-only` call, honoring --dry-run (still
  prints the call for transparency). mergeSettings is idempotent + marker-guarded,
  so re-running install.sh never clobbers existing settings.
- First-run hint in run(): when ~/.claude/settings.json is missing or lacks the
  _forge:"forge-managed" marker, a real command is invoked, and FORGE_NO_HINT!=1,
  print one tip line to stderr pointing at `forge init` / `forge doctor --fix`.
  Stateless, zero writes, self-silences once init runs.

Docs: FORGE_NO_HINT documented in README + docs/GUIDE; CHANGELOG [Unreleased]
bullet. Tests: init({settingsOnly}) merges hooks + permissions but emits no repo
config, and is idempotent on re-run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Ebv4QDTqzPzgApBW2KUsU
…dary-tool artifacts

Concern 3: a repo that only uses one agent tool no longer has to track every
other tool's emitted artifacts (.cursor/, .gemini/, .codex/, .zed/, …).

- src/gitignore.js: ensureGitignoreBlock(root, paths) writes/updates ONLY a marked
  block (# forge:gitignore:begin … :end), replacing content between the markers or
  appending if absent, and never touching a user's own lines. Idempotent
  (writeIfChanged contract — identical content is a no-op), handles a missing trailing
  newline, and removeGitignoreBlock(root) strips only the block. readGitignoreBlock
  reads back the listed paths.
- src/repo_config.js: readRepoConfig(root) → {primaryTool?, tools?} from
  .forge/config.json (throw-free), with detectPrimaryTool auto-detecting the primary
  tool from which agent folder exists (CLAUDE.md/.cursor/.gemini/.codex/.zed/.vscode),
  mirroring autoDetectProvider. nonPrimaryTargets computes the emit targets belonging
  to non-primary tools from sync()'s report rows; applyPrimaryTool writes the config
  and gitignore block (sync injectable for tests).
- forge tools command (cli.js handler + COMMANDS entry + GROUPS.Core): `forge tools`
  shows the detected/primary tool and gitignored targets; `forge tools <name>` sets the
  primary tool and gitignores the rest from a real sync report; `forge tools --reset`
  clears config + removes the block. Opt-in only — plain `forge sync` never writes
  .gitignore. Default emit behaviour is unchanged; config only drives gitignoring.
- Docs: README + docs/GUIDE mention `forge tools`; CHANGELOG [Unreleased] bullet.
- Tests: test/gitignore.test.js (block isolation, user lines preserved, idempotent,
  reset removes only the block, no-trailing-newline file) + test/tools.test.js
  (readRepoConfig auto-detect, config+block from real emit targets, --reset clean).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Ebv4QDTqzPzgApBW2KUsU
The dashboard was called "bad and confusing" — it showed blank panels on a
fresh repo with no explanation of what each lens meant. This adds the clarity
layer (Concern 1b), display-only, preserving the append-only write discipline.

- dash.js dashData() gains meta:{empty, forgeDir}. empty is true only when
  .forge/ holds no ledger claims AND no metrics events; it reuses the already
  computed ledger.stats.total and metrics.recent signals (recent is capped but
  only empty at zero events) so there is no extra read, and it never throws —
  a corrupt store degrades to empty, matching dashData's discipline.
- dash.html gets a "?" legend toggle (a pure display toggle, no fetch, no
  writes) that explains every panel in one plain sentence, plus an empty-state
  banner keyed off meta.empty: "No data in .forge/ yet — run `forge sync`,
  then work a session." Brand tokens reused from the existing inlined palette.
- test/dash.test.js: bare repo -> meta.empty===true (+ forgeDir), populated
  fixture and claims-only / metrics-only repos -> false, corrupt store -> empty
  without throwing.
- CHANGELOG [Unreleased] bullet.

No new command, env var, or dependency.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Ebv4QDTqzPzgApBW2KUsU
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Ebv4QDTqzPzgApBW2KUsU
…ssions

Adds .claude/hooks/session-start.sh — synchronous, idempotent, gated on
CLAUDE_CODE_REMOTE=true (a no-op locally), skips the install when the dev
toolchain (biome) is already resolvable. Wired into the repo's SessionStart
hooks alongside the existing recall-load + cortex context hooks (both preserved).
dogfood.test.js now accepts repo-local .claude/hooks/*.sh scripts in addition to
global/guards/*.sh (both must exist and be executable).

Completes the /session-start-hook request. Full suite 886 pass / 0 fail / 2 skip.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Ebv4QDTqzPzgApBW2KUsU
@CodeWithJuber
CodeWithJuber marked this pull request as ready for review July 17, 2026 14:49
@CodeWithJuber
CodeWithJuber merged commit f33c9de into master Jul 17, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants