Files
ironclaw/scripts/run-reborn-webui.sh
Josh Ford 50311eab44 docs: enforce the docs/ publication boundary (frozen .mintignore + CI gate) and consolidate internal docs under docs/internal/ (#7259)
* docs: enforce the docs/ publication boundary with a frozen .mintignore and CI gate

docs/ mixes the public Mintlify site with internal engineering docs, and
omission from docs.json navigation is not a publication boundary: a page
left out of navigation is still deployed, reachable by URL, and indexable.
docs/design/ and docs/research/ were never added to docs/.mintignore, so
both internal docs have been served as hidden pages on the public site.

Close the gap and the process hole behind it:

* Move docs/design/ and docs/research/ under docs/internal/, the one
  growing home for internal material — new internal docs now land inside
  the fence by default instead of requiring a .mintignore edit.
* Freeze docs/.mintignore: scripts/ci/docs_publication_boundary.py rejects
  any new entry (legacy directories stay listed until consolidated into
  internal/; entries may only be removed).
* Gate in CI (Code Style): every .md/.mdx under docs/ must be in docs.json
  navigation, matched by .mintignore, or carry `hidden: true` frontmatter
  marking a deliberately unlisted public page; navigation entries must have
  a source file. The gate has its own has_docs trigger because docs-only
  PRs skip every Rust lane, and it is checked in the roll-up before the
  has_code early exit so it blocks docs-only PRs too.

Regression coverage: scripts/ci/test_docs_publication_boundary.py (16
cases, run by the CI job before the check; one pins the real docs/ tree as
clean). Red/green verified: the checker flagged exactly
docs/design/agent-activity-streaming.md and
docs/research/pi-agent-deep-dive.md before the fix and passes after.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: relocate legacy internal doc directories under docs/internal/ — text-only

Move plans/, superpowers/, qa/, adr/, architecture-video/, and
reborn-binary.md from docs/ into docs/internal/, and rewrite every repo
reference to the old paths (guidance files, script and workflow comments,
Rust doc comments, the render-architecture-video VIDEO_DIR, the
architecture-video skill, .coderabbit.yaml). Behavior unchanged: all
references to these directories were textual except the video script's
VIDEO_DIR, the skill paths, and the .coderabbit.yaml ignore, which are
updated in step.

docs/reborn/ deliberately stays put: its path is load-bearing
(ironclaw_capabilities and ironclaw_architecture_tests read contract files
from it at test time, and reborn-e2e.yml scope filters match it — pinned
by scripts/ci/ws12_workflow_contracts.py). It consolidates into internal/
in a follow-up when those consumers can move with it; docs/.mintignore and
FROZEN_MINTIGNORE_PATTERNS shrink to internal/ + reborn/ accordingly.

Verified: docs publication boundary check green, its 16 self-tests green,
ws12 workflow contracts green (46), touched YAML parses, zero references
to the old paths remain outside git history.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: cover the docs gate's entry point and pin its trigger in ws12 contracts

Fixes the three findings from the multi-agent code review of this branch
(security/bugs/performance/conventions clean; tests reviewer found 3):

* main() was never called by any test — the exit-code contract, the three
  stderr violation blocks, and main()'s MintignoreSyntaxError handling were
  uncovered, so a regression returning 0 despite violations would have
  passed all tests while turning the CI gate into a no-op. Three new tests
  drive main() directly (clean tree, all violation classes, syntax error).
* The has_docs trigger grep and the fail-closed roll-up guard had no pin.
  ws12_workflow_contracts.py now carries a has_docs CrateScopeFilter
  (docs/, the gate's own files, and the workflow in scope; crates and
  README out) plus code_style.yml REQUIRED_MARKERS for the job, both
  steps, and the roll-up guard — with sabotage tests proving narrowing
  the grep or removing a marker fails loudly. Red/green verified.
* is_ignored()'s slash-glob pattern branch (contains '/' but not
  trailing) had no fixture; covered with design/*.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: pin the docs-gate guard's ordering, not just its presence

Fixes the three findings from review round 2 (security/bugs/performance
clean; tests found 2, conventions found 1):

* REQUIRED_MARKERS is presence-only, so relocating the docs-gate roll-up
  guard to after the has_code early exit — the exact silent-skip bug the
  guard exists to prevent — passed every contract check. New
  validate_code_style_docs_guard_order() pins guard-before-early-exit in
  code_style.yml, with a sabotage test that relocates the guard line and
  a checked-in-order pass test. Red/green verified.
* CrateScopeFilterSabotageTests' docstring still described "the three
  remaining crate-keyed filters"; updated for the fourth, non-crate-keyed
  has_docs pin (review-discipline.md: guardrail docs must match the code).
* is_ignored()'s nested-directory pattern branch (a trailing-slash entry
  with an internal slash, e.g. `design/sub/`) never executed under the
  suite; covered by test_mintignore_nested_directory_pattern_fences.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: fix relative ADR links missed by the path sweep; align checker hint with the frozen fence

Addresses the Copilot review on #7259:

* The reference sweep rewrote literal `docs/adr` strings but not relative
  markdown links: `../../adr/` in target-architecture/{CHECKLIST,PROPOSAL}.md,
  `../../../adr/` in families/domains.md, and the hooks CLAUDE.md link (which
  also carried a pre-existing wrong depth from the WS7 family move) all
  resolved to the old location. A repo-wide relative-link scan found exactly
  these seven move-caused breaks; the remaining broken links predate this
  branch (WS7 crate-move fallout in testing-playbook.md, one dead June plan
  link) or are Mintlify extensionless links that resolve on the site.
* The checker's remediation hint said "add its directory to docs/.mintignore",
  contradicting the frozen-fence rule the same script enforces; it now directs
  authors to move internal material under docs/internal/.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: only executable guard occurrences satisfy the docs-gate order pin

CodeRabbit (Major, #7259): validate_code_style_docs_guard_order used a raw
str.find, so a commented-out copy of the guard above the has_code early
exit — a realistic refactor leftover — satisfied the pin while the
executable guard sat below the exit, silently unhooking the gate for
docs-only PRs. The validator now strips comment lines before matching and
requires EVERY live guard occurrence to precede the first early-exit
occurrence; a comment-only occurrence reports the order as unassertable.
New decoy sabotage test red/green verified. Also parenthesized the
implicit string concatenations Ruff flagged (ISC004).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(docs): align all Remotion packages to 4.0.499

CodeRabbit flagged the moved architecture-video project's manifest: Remotion
requires every @remotion/* package at one identical version, but dependabot
#6658 bumped only @remotion/cli and @remotion/tailwind-v4 to 4.0.499,
leaving remotion, @remotion/transitions, and @remotion/eslint-config-flat
at 4.0.447 — a pre-existing break on main that surfaced here because the
directory rename presents as a new project. Aligned all five to 4.0.499 and
regenerated the lockfile; `npx remotion versions` now reports all packages
at the correct version.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: assert both signals in the literal-pattern fencing case

Copilot (#7259): test_mintignore_literal_file_fences used a pattern outside
the frozen allowlist but discarded the `unexpected` result, so it passed
while the checker it pins would fail — a misleading regression pin. The
case now asserts both independent signals explicitly: the literal entry
still fences its file (no publication leak) AND trips the frozen-list rule,
with the interplay documented in the test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: mark the architecture video as stale pre-Reborn content

Copilot flagged the relocated video scenes for citing crates/ironclaw_engine
paths that no longer exist. The scenes are untouched April 2026 content
(#2365) presenting as new because of the directory rename; regenerating
them against the Reborn architecture is deliberately out of scope for this
move-only PR. Until that regeneration happens, a prominent README banner
states what the video describes, why it is wrong today, where current docs
live (openwiki/), and how to regenerate (architecture-video skill) — so the
content cannot mislead contributors in the meantime.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: probe docs/docs.json in the has_docs scope pin

Copilot (#7259): navigation is half the publication-boundary contract — a
nav-only edit can orphan a page into hidden-page territory or reference a
missing source file — but no has_docs probe covered docs.json, so a future
markdown-only narrowing of the trigger grep (e.g. ^docs/.*\.(md|mdx)$)
would silently skip the gate for nav changes while every existing probe
stayed green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ci): map the two sweep-touched dev scripts and satisfy rustfmt

Two root causes behind the red CI on #7259, both fallout from the docs
path sweep touching files no docs-only change normally touches:

* reborn_composition_boundaries.rs reads the (moved) composition pub-use
  snapshot; the longer docs/internal/plans/ path pushed the line past
  rustfmt's width. Reformatted.
* The Reborn PR test planner fails closed on unmapped repo-root scripts.
  The sweep touched two local dev tools no workflow invokes —
  check-type-duplicates.py (docstring path) and
  render-architecture-video.sh (VIDEO_DIR path) — mapped both with
  per-file decisions in the planner's established style. Verified by
  running the planner against this branch's full 173-file changed list
  (green) plus its 61 self-tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: commit the messaging-framework path rewrites dropped by the previous merge

The prior merge commit staged files before running the path sweep, so its
rewrites of #6831's new files (docs/superpowers -> docs/internal/superpowers
in three Rust doc comments, the plan, and standard-operations.md) were left
unstaged and its message wrongly called the sweep a no-op. This commit is
those rewrites.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 00:53:02 +00:00

148 lines
6.3 KiB
Bash
Executable File

#!/usr/bin/env bash
# Launch IronClaw Reborn with the WebChat v2 web UI for local testing.
#
# Handles the setup footguns from docs/internal/reborn-binary.md for you:
# - keeps the Reborn home OUTSIDE the repo (serve uses the cwd as the
# local-dev workspace root and rejects overlap with it);
# - configures the model route via `models set-provider`;
# - generates the WebUI bearer token and sets the WebUI user to the home's
# `[identity].default_owner` (falling back to `reborn-cli`, config init's
# default) so serve's owner check doesn't refuse to start.
#
# Usage:
# scripts/run-reborn-webui.sh # NEAR AI defaults
# PROVIDER=openai scripts/run-reborn-webui.sh
# PROVIDER=anthropic MODEL=claude-sonnet-4-20250514 scripts/run-reborn-webui.sh
#
# Before running, export your provider's API key, e.g.:
# export NEARAI_API_KEY=... # or OPENAI_API_KEY / ANTHROPIC_API_KEY
#
# Overridable via environment:
# PROVIDER provider id (default: nearai)
# MODEL model id (default: provider catalog default)
# REBORN_HOST listen host (default: 127.0.0.1)
# REBORN_PORT listen port (default: 3000)
# IRONCLAW_REBORN_HOME (default: $HOME/.ironclaw-reborn-demo)
# IRONCLAW_REBORN_WEBUI_USER_ID (default: home's [identity].default_owner)
# IRONCLAW_REBORN_WEBUI_TOKEN (default: generated and printed)
#
# REBORN_HOST/REBORN_PORT are deliberately prefixed: a bare HOST would collide
# with zsh's auto-set $HOST (the machine hostname), which could bind serve to a
# non-loopback interface and expose the bearer token over plain HTTP.
set -euo pipefail
PROVIDER="${PROVIDER:-nearai}"
MODEL="${MODEL:-}"
REBORN_HOST="${REBORN_HOST:-127.0.0.1}"
REBORN_PORT="${REBORN_PORT:-3000}"
# This launcher prints a login URL for a browser, so a fixed port is required.
# `serve --port 0` (kernel-picks-a-free-port) is for test harnesses only and
# would print an unusable http://REBORN_HOST:0/ here.
if [ "$REBORN_PORT" = "0" ]; then
echo "error: REBORN_PORT=0 (kernel-assigned port) isn't usable for browser onboarding." >&2
echo " Set a fixed REBORN_PORT, or run the test-harness form directly:" >&2
echo " cargo run -q -p ironclaw \\" >&2
echo " --bin ironclaw -- serve --port 0" >&2
exit 1
fi
# Run cargo from the workspace root regardless of where the script is invoked.
REPO_ROOT="$(git -C "$(dirname "${BASH_SOURCE[0]}")" rev-parse --show-toplevel)"
cd "$REPO_ROOT"
# Resolved by crate NAME through the shared inventory
# (scripts/ci/lib/crate_tree.py) rather than a literal `crates/ironclaw_webui`
# path, so the target-architecture family move (PROPOSAL §5) cannot leave this
# pointed at a directory that no longer exists
# (docs/reborn/target-architecture/CHECKLIST.md WS10).
FRONTEND_DIR="$("$REPO_ROOT/scripts/ci/crate-dir.sh" ironclaw_webui "$REPO_ROOT")/frontend"
if ! command -v pnpm >/dev/null 2>&1; then
if command -v corepack >/dev/null 2>&1; then
corepack enable pnpm
fi
fi
if ! command -v pnpm >/dev/null 2>&1; then
echo "error: pnpm is required to build WebUI v2 assets." >&2
echo " Install Node 22 from .nvmrc and enable pnpm with: corepack enable pnpm" >&2
exit 1
fi
echo "==> Building WebUI v2 frontend assets"
(
cd "$FRONTEND_DIR"
pnpm install --frozen-lockfile
pnpm build
)
export IRONCLAW_REBORN_HOME="${IRONCLAW_REBORN_HOME:-$HOME/.ironclaw-reborn-demo}"
# Reject a home inside the repo, which would trip the workspace/skill-root
# overlap validation in serve. Canonicalize both paths first (resolving `..`
# and symlinks, like serve does) so e.g. `../reborn-home` isn't mis-flagged.
# Resolve via the parent dir so we don't have to create the home to normalize
# it; if the parent doesn't exist yet, skip this friendly check and let serve's
# own validation handle it.
case "$IRONCLAW_REBORN_HOME" in
/*) home_abs="$IRONCLAW_REBORN_HOME" ;;
*) home_abs="$PWD/$IRONCLAW_REBORN_HOME" ;;
esac
home_parent="$(cd "$(dirname "$home_abs")" 2>/dev/null && pwd -P || true)"
repo_canonical="$(cd "$REPO_ROOT" && pwd -P)"
if [ -n "$home_parent" ]; then
home_canonical="$home_parent/$(basename "$home_abs")"
case "$home_canonical/" in
"$repo_canonical"/*)
echo "error: IRONCLAW_REBORN_HOME ($home_canonical) is inside the repo ($repo_canonical)." >&2
echo " serve uses the cwd as the workspace root and rejects overlap." >&2
echo " Point it somewhere else, e.g. \$HOME/.ironclaw-reborn-demo." >&2
exit 1
;;
esac
fi
# Generate a WebUI bearer token if the caller didn't supply one.
if [ -z "${IRONCLAW_REBORN_WEBUI_TOKEN:-}" ]; then
export IRONCLAW_REBORN_WEBUI_TOKEN="$(openssl rand -hex 32)"
fi
CARGO=(cargo run -q -p ironclaw --bin ironclaw --)
# Configure the model route (compiles the binary on first run).
set_provider_args=(models set-provider "$PROVIDER")
if [ -n "$MODEL" ]; then
set_provider_args+=(--model "$MODEL")
fi
echo "==> Configuring model route: provider=$PROVIDER ${MODEL:+model=$MODEL}"
"${CARGO[@]}" "${set_provider_args[@]}"
# Match the WebUI user to the home's identity owner so serve's owner check
# passes (set-provider has now written/seeded config.toml). A caller-supplied
# IRONCLAW_REBORN_WEBUI_USER_ID wins; otherwise read [identity].default_owner
# from the config, falling back to reborn-cli (config init's default).
config_file="$IRONCLAW_REBORN_HOME/config.toml"
config_owner=""
if [ -f "$config_file" ]; then
config_owner="$(sed -n 's/^[[:space:]]*default_owner[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' "$config_file" | head -1)"
fi
export IRONCLAW_REBORN_WEBUI_USER_ID="${IRONCLAW_REBORN_WEBUI_USER_ID:-${config_owner:-reborn-cli}}"
# Discover the credential env var for this provider and warn if it is unset.
key_env="$("${CARGO[@]}" models status 2>/dev/null \
| sed -n 's/^default\.api_key_env: //p' || true)"
if [ -n "$key_env" ] && [ -z "${!key_env:-}" ]; then
echo "warning: $key_env is not set. Required-key providers (openai, anthropic, …)" >&2
echo " fail at startup; export it before turns will work." >&2
fi
cat <<EOF
==> Starting WebChat v2 on http://$REBORN_HOST:$REBORN_PORT/
login token : $IRONCLAW_REBORN_WEBUI_TOKEN
login user : $IRONCLAW_REBORN_WEBUI_USER_ID
reborn home : $IRONCLAW_REBORN_HOME
EOF
exec "${CARGO[@]}" serve --host "$REBORN_HOST" --port "$REBORN_PORT"