Repository source: docs/reference/v3_cli.md
v3 CLI Reference
./v3 dispatches to scripts in scripts/. Use the launcher for local startup
and dependency checks: it handles executable/version checks and server shutdown.
Many other PHP commands can also be invoked directly as
php scripts/<script>.php ....
Run ./v3 help, ./v3 --help, or ./v3 -h to print the command list.
Running ./v3 with no arguments also prints it (with exit code 1).
Use ./v3 import-instance --help for remote-import options and recovery.
CLI error-handling contract
When adding or changing a ./v3 command or worker command:
validate the command and its options before filesystem, database, network, or other state-changing work begins;
accept only documented options and reject unknown commands or options with a nonzero exit code;
write a concise error and the relevant usage text to stderr—never expose an uncaught PHP exception or stack trace;
support
-hand--helpwith exit code 0; andadd a regression test through the
./v3dispatcher for unknown-option handling.
Most data-touching commands accept optional positional repository_root and
database_path arguments. When omitted they fall back to, in order:
the relevant
FORUM_*environment variable (FORUM_REPOSITORY_ROOT,FORUM_DATABASE_PATH,FORUM_STATIC_HTML_ROOT)the default local repository/database bootstrapped under
state/
See Local Run in the README for the defaults and the bootstrap flow.
Synchronize Railspike workflow documentation
./v3 railspike sync [--repository-root=/path/repository] [--remote-url=https://github.com/rlspk/railspike.git] [--ref=v0.4.0-beta.1]
./v3 fdp sync ... # deprecated alias; updates Railspike, never FDP
Updates the vendored docs/railspike/ subtree to an explicit release tag
(--ref, default the pinned v0.4.0-beta.1), creating Git update commits. Branch
names such as main are refused, so a plain sync never moves the installed
version. The tag must descend from the commit in docs/railspike/RAILSPIKE_SOURCE.md,
which the command updates and commits after a successful merge. The target defaults to this checkout; --repository-root
selects another already-migrated repository root. A clean working tree and
complete subtree import history are required. Unmigrated targets receive
migration guidance without modification.
The default source is https://github.com/rlspk/railspike.git. An existing
railspike remote must match it; --remote-url explicitly chooses another
source or fork. The selected source is recorded as the railspike remote only
after a successful merge (including a no-op). It must retain the recorded
upstream revision in its tag history. No fdp remote is changed.
Review upstream before updating; the command does not pause for diff approval.
For conflicts, resolve and commit or use git merge --abort in the target.
See the project workflow guide for review, recovery,
and existing-plan policy.
Testing account approval
Create an ignored .test-self-approval file in the project root once per testing
checkout (touch .test-self-approval). Start the server normally, set up or
restore your browser account, and open Account Key (/account/key/). Click
Approve current account to apply the same root approval seed as
./v3 approve <current_identity_id>, with immediate member access.
The shortcut requires an authenticated account and a direct loopback connection
using localhost, 127.0.0.1, or [::1]; public hostnames and forwarded proxy
requests are ineligible. The file contents do not matter. Remove the file
(rm .test-self-approval) to disable further submissions immediately, including
from pages already open. Existing approvals remain valid. Each new checkout
needs its own marker; no extra startup option or restart is required.
If approval reports incomplete refresh, follow the displayed rebuild command, then retry Approve current account. A persisted approval is retained during repair. The marker file is not created by installation or tracked in Git.
Start the local dev server
./v3 start [--listen-all|host:port] [--verbose|--quiet] [--log-file=/path/dev.log]
Starts the PHP built-in dev server on 127.0.0.1:8000 by default.
Checks local-server prerequisites before application loading, logs, or bootstrap.
Missing required capabilities produce runtime-specific recovery guidance.
On Ubuntu 22.04/PHP 8.1 or 24.04/PHP 8.3 distribution packages, terminal input/output
and root or potentially usable sudo allow a reviewed Recover missing dependencies
and continue startup? [Y/n] prompt. Enter/Y/y accepts; n/N/invalid input/EOF or
pre-execution cancellation stops without changes. Optional intl is a separate,
default-off choice; optional-only limitations never trigger recovery.
Authenticate sudo after consent. Success plus fresh readiness resumes startup
once with the original address, logging options and caller identity; failures
return 1 without starting a listener. Redirected startup never prompts or installs.
Options are validated before recovery. Stop the launcher with Ctrl+C or SIGTERM.
--listen-all— bind0.0.0.0:8000instead of127.0.0.1:8000host:port— bind an explicit address or bare port number instead of the default--verbose/--quiet— show all requests / server errors only--log-file=...— append plain request logs to the chosen file
Check local-server prerequisites
./v3 check-dependencies [--install|--help]
./v3 dependency-check [--install|--help]
Uses the same checks as startup without starting the server or changing application
state. Without --install, it is read-only and noninteractive. Reports Bash 4.1+,
Git, PHP CLI 8.1+, PDO/PDO SQLite, mbstring, and PHP
process execution; intl, PCNTL, terminal shell execution, and stty are optional.
Returns 0 when required capabilities pass (even with warnings), or 1 for missing,
disabled, or unusable required capabilities. -h/--help returns 0 without
checking PHP or Git. --install selects the interactive mode below; other
arguments are rejected.
Status rows use ✓/✗/!/? for available, unavailable, optional limitations, and
unchecked capabilities. Runtime details and recovery instructions follow the
readiness result. Terminals show colored statuses and highlighted setup commands;
redirected output and NO_COLOR use plain text with the same symbols.
With --install, Ubuntu 22.04/PHP 8.1 or 24.04/PHP 8.3 distribution users can review required
packages, explicitly select optional intl, and confirm the full resolved APT
transaction. Confirmation requires terminal input/output and exact yes;
no unattended bypass is supported. Root or sudo privileges are needed only after
confirmation. Metadata refresh precedes installation; a changed transaction needs
new confirmation. Installation preserves configuration and rejects removals,
unverified sources and other PHP runtime families. Custom runtimes, PATH or
configuration problems, and unsupported environments retain manual recovery.
Installation returns 0 only when the requested package operation succeeds and required readiness passes, or a ready no-op (including declined optional-only setup). It returns 1 for refusal, cancelled installation, package failure, or remaining required problems. Package outcome is separate from readiness. Failure or interruption may leave partial changes; no rollback, repair or automatic retry is performed. If package processes survive interruption, reassessment is deferred and manual inspection is required. Declining before execution makes no changes; declining a revised transaction after refresh leaves only refreshed metadata.
After explicit --install, retry startup manually with the same options. Recovery
accepted from start continues automatically only after success. Neither check mode validates
repository, database, permissions, listening address, other-command, or deployment
readiness. See installation scope, source restrictions and recovery
and external launcher prerequisites.
Run the test suite
./v3 test
Runs the custom test runner (tests/run.php) covering parser, rebuild, and
app smoke tests.
any arguments — passed straight through to the test runner, e.g. a specific test class name
Check the live OpenPGP asset contract
./v3 openpgp smoke [--origin=zenmemes.com]
Performs a read-only live check of the HTTP and HTTPS browser runtime paths.
For each scheme it reads window.__forumAssetPaths from the public page, selects
the same OpenPGP v5 (HTTP) or v6 (HTTPS) asset the browser would use, then also
checks both legacy raw OpenPGP bundle URLs used by cached clients. It reports
the selected URL, status, content type, redirects, and a failure reason. The
default host is zenmemes.com; use --origin=staging.example to check a staging
host. No records, browser identities, or posts are created.
Run the manual OpenPGP production canary
npm install
./v3 openpgp canary --confirm-production-write --browser-executable=/path/to/chromium
This is deliberately not a CI or deployment command. After the read-only
smoke passes, manually run the canary from a machine with Node 18+ and local
Chromium. It uses which chromium automatically; pass
--browser-executable=/path/to/chromium only when that lookup is unsuitable.
It opens an isolated HTTP browser profile, creates a new identity using the
consistent username release-check, publishes “New release just dropped,
making sure it works,” reloads the post, and exercises the existing browser identity path to
confirm no second username prompt appears. It leaves the identity and post in
place intentionally. Do not rerun it after an ambiguous result—first inspect
the reported post URL. --confirm-production-write is mandatory.
Synchronize code checkouts
Use this from an IntelliJ terminal in any checkout containing the command:
./v3 checkouts add /home/wsl/v3
./v3 checkouts add /home/wsl/agent/v3-codex
./v3 checkouts list
./v3 checkouts
./v3 checkouts preview
./v3 checkouts apply <preview-id> --sessions-paused
./v3 checkouts (or overview) immediately displays cached observations in
aligned CHECKOUT / BRANCH / ACTION / HINT columns. The default does not inspect
Git in the foreground or wait on a running refresh/apply lock. The footer shows
observation age and refresh state. Branch/action labels describe the last
observation; they do not establish the checkout's live state or permission to
apply. First use, changed registration or invalid cache shows unchecked rows
while refresh is scheduled. No apply ID or command comes from cached output.
Background refresh is scheduled when no matching cache exists or its latest
attempt is at least 30 seconds old. Repeated calls coalesce into one worker, with
up to three fetches across independent Git stores. Fetch children never write
history evidence; the existing manager lock protects coordinator classification
and publication. Observations publish atomically only for the same registry and
selection. Fresh preview also warms the matching observation cache; an attempted
apply invalidates affected cached rows. Test/server exclusions still apply.
States include queued, running, fresh, busy, deferred and failed.
A manager/apply lock can defer a worker; cached output stays available. A queued
worker that never starts becomes a visible failure after 10 seconds; failed or
deferred launch attempts use a short retry cooldown. To fetch and diagnose now:
./v3 checkouts refresh
./v3 checkouts --no-refresh --json
refresh waits for fresh observations but does not create an apply ID. On an
offline/failed observation, retain the previous good row and its original timestamp
with last_error/error_at; a stale-error hint and failure state distinguish it
from a successful new observation. A cold failure shows unavailable. Correct
access/state permissions and retry refresh; when another command owns the manager
lock, retry once it finishes. --no-refresh deliberately reads cache without
scheduling a worker. Neither this command nor background refresh moves branches.
Detached background refresh needs nohup and CLI PHP's POSIX/pcntl support.
If unavailable, cached output reports failure and refresh remains the synchronous
recovery route. Worker lifetime is bounded to 120 seconds, each Git child to
30 seconds; handled cancellation terminates its isolated process group. An
uncatchable kill may still require process/Git inspection. Worker status is a
bounded private file, output is detached from the terminal, and only a small
bounded set of observation-cache generations is retained. Do not clear history
evidence to force freshness.
preview deliberately waits for fresh source inspection and creates a signed
update plan, so it can still take several seconds. Then pause coding/server
sessions before apply. Cached overview's subsecond target is measured locally;
network-complete preview has no subsecond promise. list remains registry-only.
Rows use unique short path suffixes; full paths/reasons remain in JSON. COLUMNS
controls width (80-column fallback, bounded 20–240); narrow terminals omit HINT
and abbreviate values with an explicit full-details hint. Control characters are
sanitized in human output. For abbreviated apply recovery, the printed result-file
path preserves the complete resolve/abort instructions without replaying an apply.
Register only directories you want to update. add validates the existing clone
and records its absolute root, Git storage identity, expected remote source,
remote name (--remote=origin), target (--target=main) and role. The default role
is development. Test/server roles (--role=test or --role=server) appear as
excluded in batch previews; select one explicitly with
./v3 checkouts preview --path=/absolute/registered/path. Shared-storage linked
worktrees and symlink aliases cannot be registered as independent clones.
Nothing scans your home directory or enrolls existing copies automatically.
All projects use one private registry at ~/.config/v3/checkouts.json and private
history/preview/results at ~/.local/state/v3/checkouts; XDG_CONFIG_HOME and
XDG_STATE_HOME override those base directories. --registry=/path/file.json
and --state-dir=/private/path allow isolation. Pass the same overrides to each
command; the preview's suggested apply command includes them. These files contain
local paths; keep them local. Do not edit saved previews. remove <registered-path>
forgets registration and archives its enrollment evidence without deleting files.
For a moved/missing directory, remove its old absolute registered path and add the
new location; generate a new preview after any registry change.
Preview fetches only the registered source branch, updates its remote-tracking
ref, and saves the exact fetched revision plus each checkout's state under an
opaque preview ID. It changes no checked-out files or branches. Results distinguish
fast-forward, merge, current, ahead, dirty, busy, diverged,
migration-required, unavailable, stale and excluded. A missing source or
credential/access failure does not silently select another remote. Correct access
or registration and retry preview. Raw remote/hook errors are suppressed because
they may include credentials; inspect Git locally when diagnosing failures.
To fetch and apply all eligible development checkouts without copying a preview ID, pause their coding/server sessions first, then run:
./v3 checkouts apply --sessions-paused
Without an ID, apply creates a fresh preview under the same command lock and
immediately applies eligible rows. It does not use cached observations or a prior
preview. Dirty/busy/excluded/migration-required folders remain skipped; results can
be partial. To inspect the changes first, keep using preview followed by
apply <preview-id> --sessions-paused.
Before applying, coordinate agents and servers in the selected folders, then pass
--sessions-paused. This is an operator assertion; clean Git status is not proof
that an agent is idle. Tool locks serialize commands using the same state
directory, not external Git/editor processes. Apply revalidates registry, source,
branch, HEAD, target and dirty/operation state; changed inputs invalidate that
folder's update. It applies the previewed source commit without fetching again:
a later remote update requires a fresh preview. Main must fast-forward; eligible
feature branches may fast-forward or receive an ordinary merge preserving local
commits. Ignored local files are protected against overwrite; an ignored-file
collision fails for manual recovery. Submodule working trees are not recursively
updated. A locally ahead branch stays untouched. Existing dirty/untracked work and
Git operations are skipped; the tool never auto-stashes, switches branches,
rebases, resets, pushes, clones, deletes or changes IDE trust.
History eligibility is deliberately conservative. Enrollment records a baseline only when the checked-out branch and fetched target are directly comparable by ancestry. A first-run divergent branch requires manual reconciliation, even when it is legitimate work. Subsequent known feature divergence can merge while source history remains forward and branch continuity is proved. Source rewinds/replacements block, including an externally fetched rewrite where persisted baseline evidence shows the break. When no prior evidence exists, an already-fetched divergent branch also blocks. Common ancestry alone never authorizes a merge. This cannot infer all past force-pushes from Git alone; uncertain cases need review. Follow the history migration runbook for separately authorized reconciliation. After verified migration, explicitly remove/re-add registration to archive the sticky block and establish new evidence; this does not migrate any Git history itself.
Apply records each attempt before changing a branch. On conflict, it leaves Git's
merge state and prints commands to resolve/continue or abort in that directory;
independent folders may still update. Preserve work, resolve or abort the merge,
then run a fresh preview. A failed hook, missing identity or timed-out operation
requires inspecting Git status, configuration, hooks and any remaining processes
or locks before retrying. Git subprocesses have a 30-second limit; timeout does not
prove child hooks have stopped or undo changes. Interrupted/attempted preview IDs
cannot be replayed. Inspect the private <id>.result.json and Git status, then
create a new preview. No automatic rollback or whole-batch atomicity is promised.
--json keeps full paths/reasons, per-row observation timestamps and refresh
metadata; human output abbreviates for terminal width.
Exit 0 means completion without attention, 1 means command/configuration
failure, and 2 means partial results or attention required; inspect every row.
These results cover selected checked-out branches, not every local branch,
stash or detached worktree, and never establish historical erasure. Preview rows
not applied retain their observed disposition; verify current conditions with a
fresh preview.
Run real-Git acceptance tests with php tests/run.php CheckoutsCommandTest
(or python3 tests/test_checkouts.py directly); they use
isolated temporary registries and clones and do not alter registered live copies.
For the eight-checkout WSL timing/terminal replay, run
python3 tests/checkouts_benchmark.py --output-dir=/tmp/checkout-benchmark.
It retains every cold/warm/slow/offline/busy sample and reports min/median/p95/max;
the recorded target applies to this host, not arbitrary OS scheduling delays.
Inspect operator status
./v3 status [--repository-root=/path/repository] [--database-path=/path/read-model.sqlite3] [--queue-database-path=/private/path/tasks.sqlite3] [--url=http://127.0.0.1:8001]
Read-only, concise status for the repository, read model, shared execution
lock, and background queue. It reports a read-model rebuild task as queued,
running, failed, or absent, then prints the relevant next action.
--repository-root=...— canonical repository to compare with read-model metadata--database-path=...— read-model SQLite database to inspect--queue-database-path=...— task-queue SQLite database to inspect--url=...— frontend URL override for this invocation
Frontend selection uses --url, then FORUM_STATUS_URL from the existing
private configuration (environment wins), then a live registered development
server for this checkout/instance. Otherwise it checks only local ports 8000
and 80 and requires a unique instance match. The selected URL and source appear
in the Frontend row. Newly started ./v3 start 8001 servers register automatically;
restart an older development server once to enable custom-port discovery.
Linux process identity guards development registrations; other platforms can
use a saved target. Status never edits these registrations.
For production virtual hosts, HTTPS, or an unresolved/ambiguous target, add
'FORUM_STATUS_URL' => 'https://forum.example/' to the existing
../forum-private/secrets.php (or FORUM_SECRETS_PATH file), or set the same
environment variable. Use the URL whose Host/TLS name routes to this instance.
Rerun plain status to verify; --url temporarily overrides the saved value.
Malformed targets require correction; credentials and fragments are rejected.
A saved/selected outage stays FAIL without trying another server. Unresolved
or ambiguous discovery is WARN and prints the configuration action.
Discovery and HTTP probing share a three-second network budget. The read-only
/__v3/status-identity response distinguishes the checkout, repository and
read-model path without returning paths, cookies or cached data; it bypasses
repository initialization. It is diagnostic identity, not authentication. The
web Codebase State surface continues to omit frontend probing.
The command remains useful when optional runtime files are missing or
unreadable: it reports the affected state without creating a queue, lock, or
read-model database. A Shared lock: locked result only means protected
activity is in progress; it does not prove a manual rebuild is running.
Use ./v3 task-queue status for individual task details and
./v3 fast-score status for Fastmod work details.
Open the terminal operator UI
./v3 tui
Opens a whiptail dashboard in an interactive terminal. It displays the
canonical ./v3 status result and a redacted effective private-config view,
including each value's source. Environment overrides are read-only in the UI;
manage site feature flags through /tools/feature-flags/.
The launcher shows the selected command before running one of its fixed
read-only entries: ./v3 status, ./v3 task-queue status, or
./v3 fast-score status. It accepts no command arguments and does not edit
private config, modify feature flags, enqueue work, or offer destructive
workflows. The private-config editor and guided destructive workflows remain
separate follow-up work.
The LLM connection editor offers OpenAI, OpenRouter, Anthropic, stub, and custom-provider presets. It masks API-key entry, shows a redacted review before an atomic save, and refuses partial editing when any LLM connection setting is set through the environment; change that deployment setting and restart instead.
whiptail and interactive stdin, stdout, and stderr are required. If either
is unavailable, ./v3 tui makes no change and directs the operator to:
./v3 status
./v3 private-config view
Rebuild the SQLite read model
./v3 rebuild [repository_root] [database_path]
./v3 rebuild diagnose [repository_root] [database_path]
./v3 rebuild recover --confirm [repository_root] [database_path]
Rebuilds the SQLite read model from canonical records in the repository root.
Reports source scanning, candidate construction and validation, lock wait,
promotion, and offline snapshot readiness. Indexing reports parsing, history
resolution, sorting/derivation, and writes separately, with elapsed milliseconds.
A parsing count such as 210/210 means those files have been examined; it does
not mean indexing or the rebuild is finished. Counters name their work unit
(records, posts, threads, profiles, or history lines), include skipped records,
and emit at most 100 intermediate checkpoints plus start/final boundaries per
loop. Approval replay identifies each pass. Empty loops report 0/0.
Thread labels, post reactions, and signed feature-flag changes resolve source
commits in a single history walk per nonempty family, rather than one Git
invocation per record. Other long operations identify their start and outcome;
blocking Git, storage, and lock operations do not emit a heartbeat or estimate
remaining time. A failed operation reports failed, with existing errors and
recovery guidance preserved. Missing Git history retains the existing nullable
provenance/omitted-flag-activity behavior; a failed Git read may therefore be
followed by successful completion of the containing fallback operation.
Timings cover the named operation and may nest; do not sum them to calculate
total rebuild duration. Repeated timing keys accumulate across approval passes
or repeated history reads. The final Elapsed line measures the overall CLI
run. Output also includes source counts (posts, identities, approval seeds) and
resulting model counts (posts, threads, profiles, activity). Phase timings and
query-count comparisons help locate remaining costs; production speed depends
on repository history and storage.
diagnose is read-only: it reports SQLite sidecars, the application rebuild
lock, and Linux /proc file holders. If a stopped process left sidecars behind,
recover --confirm snapshots and archives the database and all sidecars together
under state/cache/read-model-recovery-*, then rebuilds a fresh derived model.
It refuses recovery while the lock or an open file holder is detected.
repository_root— canonical records checkout to rebuild fromdatabase_path— SQLite file to write the read model to
Sweep retracted local content
./v3 retraction-sweep preview|run|status|migrate [--json] [--limit=250] [--repository-root=/path/repository] [--static-html-root=/path/static] [--exchange-database-path=/private/exchanges.sqlite3] [--state-path=/private/sweep.json]
Preview is non-mutating. Run executes one bounded batch immediately; repeat while
remaining is true. The normal queue worker also discovers and continues sweeps.
See coverage, exclusions, progress and recovery. migrate handles legacy source-removal receipts only; previous retractions are not migrated.
Manage the background task queue
./v3 task-queue enqueue-retraction-sweep [--queue-database-path=/private/path/tasks.sqlite3]
./v3 task-queue enqueue-rebuild [--queue-database-path=/private/path/tasks.sqlite3]
./v3 task-queue enqueue-fast-score [--queue-database-path=/private/path/tasks.sqlite3]
./v3 task-queue enqueue-offline-snapshot [--queue-database-path=/private/path/tasks.sqlite3]
./v3 task-queue reset-recovery [--queue-database-path=/private/path/tasks.sqlite3]
./v3 task-queue run [--limit=1] [--score-limit=25] [--work-limit=250] [--dry-run] [--quiet] [--verbose] [--repository-root=/path/repository] [--database-path=/path/read-model.sqlite3] [--queue-database-path=/private/path/tasks.sqlite3]
./v3 task-queue status [--limit=25] [--queue-database-path=/private/path/tasks.sqlite3]
./v3 task-queue cron [--log=<application-private-log-path>]
A small SQLite-backed job queue (scripts/task_queue.php) serializes
read-model rebuilds, Fastmod sweeps, offline publication, and requested
agent replies so concurrent triggers coalesce instead of racing. docs/runbooks/production_deploy.md and
docs/runbooks/operator_recovery.md reference this command and depend on it
being installed via cron.
enqueue-retraction-sweep— coalesces or explicitly retries local retraction cleanup. Automatic discovery uses the existing cron worker; completed scans are reconciled every five minutes and terminal failures stay visible.enqueue-rebuild— enqueues aread-modelrebuild task (a no-op if one is already queued/running);--queue-database-path=...overrides the default queue database locationenqueue-fast-score— enqueues the coalesced Fastmod sweep (also a no-op if one is already queued/running). It evaluates all nonempty posts over successive bounded runs when Fastmod is enabled.enqueue-offline-snapshot— enqueues the coalesced public offline snapshot publication (also a no-op if one is already queued/running).reset-recovery— resets the blocked automatic read-model schema-recovery circuit. It is an operator action; it does not enqueue or run a rebuild.run— claims and runs up to--limitqueued tasks (default 1), recovering any abandoned in-progress tasks first; guarded by an exclusive file lock so concurrent invocations don't double-run.--dry-runreports the queued count without running anything;--quietsuppresses progress output;--repository-root=.../--database-path=...override the read model to rebuild against.--score-limit=...independently sets the maximum provider calls a claimed Fastmod sweep may make (default 25); provider failures and invalid structured responses count because a request was made.--work-limit=...bounds all examined Fastmod work rows, including local heuristic exclusions (default 250).--limit=...is only the maximum queue tasks claimed.--verbosereports each Fastmod result and provider request as it happens.--quietsuppresses all worker progress output, including verbose output when both options are supplied.Agent replies are enqueued automatically when an approved user requests one; they are processed by
runand do not have a separate worker command.status— prints queued/running/completed/failed counts plus the--limit(default 25) most recent tasks with attempt counts, failure codes, and the latest private rebuild checkpoint when available. Terminal task and executor history is bounded; active tasks are retained.cron— prints one ready-to-install crontab line running the worker once a minute. The default log path is<application-root>/state/private/task_queue_cron.log;--log=...changes the path baked into the line.
Audit or backfill Fastmod
./v3 fast-score status [--limit=10] [--verbose]
./v3 fast-score audit --include-existing [--database-path=/path/read-model.sqlite3]
./v3 fast-score backfill --include-existing --confirm --max-posts=N --max-cost-usd=N [--database-path=/path/read-model.sqlite3]
./v3 fast-score retry --post-id=... --content-hash=... --rubric-revision=...
./v3 fast-score invalidate --post-id=... --content-hash=... --rubric-revision=...
./v3 fast-score smoke --post-id=... [--database-path=/path/read-model.sqlite3]
./v3 fast-score prune [--before=ISO-8601]
audit --include-existing is a read-only historical count and configured-model
cost estimate. backfill requires separate historical scope, confirmation,
post-count, and spend bounds; it creates one private, bounded batch and queues
the normal worker. status provides the next action, distinguishes regular
work from historical backfill, and shows batch progress and reservation state.
Use --verbose for recent individual work rows. See
Fastmod for pricing configuration, retention, and the
controlled operator workflow.
Import content from a remote instance
./v3 import-instance <name|hostname|url> [--sources=/private/instances.json] [--dry-run] [--verbose] [--repository-root=/path/repository] [--database-path=/path/index.sqlite3] [--static-html-root=/path/static_html] [--time-limit=300]
./v3 import-instance --resume [--repository-root=/path/repository] [--database-path=/path/index.sqlite3] [--static-html-root=/path/static_html] [--time-limit=300]
Downloads the source's public repository archive, merges supported public forum
content while preserving destination settings/approval authority, refreshes the
read model and public offline data, and withdraws stale static HTML. Import and
--resume never render pages or fingerprint/copy presentation assets. PHP serves
imported content; run ./v3 build-static separately when static output is needed.
Hostnames use HTTPS; short names require a JSON object mapping names
to URLs in --sources. URLs may include an instance base path. Conflicts retain
local records. Legacy creation dates are recovered from isolated source history
and retained as hash-bound metadata without changing signed post bytes. See the
import runbook for limits and recovery.
--dry-run— download and validate the archive, then report proposed imports, duplicates, conflicts, and exclusions without changing destination records, commits, the read model, or published views. Temporary downloads and lock files may be created. Cannot be combined with--resume.--time-limit=SECONDS— positive finite whole-invocation budget (default 300), including lock waits/publication, with up to five seconds process cleanup. Linux/WSL PCNTL/POSIX supervision is required; file limits remain unchanged.--verbose— list every rejected/excluded file and dependency root cause. The default output groups review causes and summarizes intentional exclusions.
Preview an import:
./v3 import-instance https://forum.example --dry-run
Remove --dry-run to perform the import.
Source HTTP failures report bounded, sanitized plain-text error detail when
available. Repair the source download and retry; --resume advice appears only
when the selected destination has an interrupted-import journal. Supported legacy
posts do not need their original bytes rewritten for source backups.
Exit codes are 0 for complete supported-content results, 2 for partial results
requiring review, and 1 for failures. Reports group exclusions and rejected
records; --verbose includes per-file details. --resume recovers a saved interrupted run without another download;
use the original destination options and site profile. The destination must be
initialized with a clean Git checkout. Scheduling, authenticated sources, and
web controls are deferred.
See Instance Content Import for coverage, alias examples, fixed transfer/archive limits, publication, and recovery.
Import a repository archive
./v3 import-repository <archive.tar.gz> [repository_root] [database_path] [artifact_root] [--dry-run] [--no-commit]
Imports a .tar.gz repository archive into the target repository root,
rebuilds the read model, and rebuilds static artifacts (when an artifact root
is resolved). The command reports each phase (validation, extraction, record
indexing, staging, commit, and derived rebuilds) and emits bounded record-file
progress checkpoints while it processes the archive.
archive.tar.gz— required path to the archive to importrepository_root— canonical records checkout to import intodatabase_path— SQLite file to rebuild after importartifact_root— static HTML output directory to rebuild after import--dry-run— validate the archive without writing anything--no-commit— skip the git commit step after import
Inspect a thread's attributes and labels
./v3 thread-attributes <thread_id_or_record> [repository_root] [database_path]
Diagnostic/read-only. Prints the target item, canonical root post attributes, derived read-model thread attributes, effective labels, and each thread-label record that contributes a label.
thread_id_or_record— a thread ID, post ID, canonical record path, or an unambiguous canonical record filename stemrepository_root— canonical records checkout to read fromdatabase_path— read-model SQLite file to read from
Delete a canonical record
./v3 delete-record <record_path_or_id> [repository_root] [database_path] [artifact_root]
Deletes a canonical file under records/ with git rm, commits the
removal, rebuilds the read model, and rebuilds static artifacts when an
artifact root is resolved (via argument or FORUM_PUBLIC_ARTIFACT_ROOT).
This is the operator removal mechanism. Authors retract their own posts through the site instead; see Author post retraction.
record_path_or_id— a relative path (e.g.records/thread-labels/thread-label-20260530000001-zenrules.txt) or an unambiguous filename stem (e.g.thread-label-20260530000001-zenrules)repository_root— canonical records checkout to delete fromdatabase_path— SQLite file to rebuild after deletionartifact_root— static HTML output directory to rebuild after deletion
Source-post deletion and label repair
Deleting a post also removes its proven generated thread-label records and their
signatures, retains content-free root/parent placeholders for surviving replies,
and invalidates generated presentation. Independent label actions remain active.
Source-owned labels also stop applying after signed browser retraction. When a
prebuilt static release is required, run ./v3 build-static with the deployment's
normal repository/database/artifact settings after deletion.
Source-post operations accept --inspect, --repair-labels, repeatable
--resolve-label=<label-id>, and --json. Inspection is read-only; JSON reports
acceptance separately from label_cleanup_status and unresolved legacy records.
Repair does not delete a live source. An already deleted source is represented
by retained structural removal evidence. Example:
./v3 delete-record <source-post-id> [repository_root] [database_path] --inspect --json
./v3 delete-record <source-post-id> [repository_root] [database_path] --repair-labels --resolve-label=<label-record-id> --json
A legacy candidate is not proof of ownership. Review the source text, target thread and trusted history before explicitly resolving a label. Timestamp/author matches and a shared bulk-import commit alone do not establish ownership. Repair can use an original source recovered from Git history; if history or necessary legacy timestamp evidence is unavailable, restore it before repairing.
Repeat the same command after an interrupted commit or failed refresh. The private
Git-directory journal resumes accepted work, and already accepted deletions do
not need another acceptance commit. Changed source/label bytes require inspection
rather than blind retry. Preserve records/post-removals/ and trusted Git history
in backups: copied JSON alone does not establish removal authority. Imports reject
foreign removal claims and do not restore locally removed sources or labels.
Backfill Unicode-risk analysis for posts
./v3 unicode-risk-backfill [repository_root] [database_path] [--with-llm]
Backfills Unicode-risk analysis for existing posts. By default uses deterministic detection only.
repository_root— canonical records checkout to analyzedatabase_path— SQLite file to read posts from and write results to--with-llm— also run the configured LLM provider, not just deterministic detection--deterministic-only— explicitly disable LLM use (useful after--with-llmearlier in the same invocation)
Publish documentation independently
./v3 docs publish [--json] [--repository-root=/path/records] [--static-html-root=/path/static]
Publishes the complete eligible docs library, browse/search pages, downloads and
assets atomically under <static-root>-docs/releases/, selected by its own
current pointer. Use the site's normal FORUM_SITE_ID profile/environment.
Options default to FORUM_REPOSITORY_ROOT (or state/local_repository) and
FORUM_STATIC_HTML_ROOT (or the active profile's default static root).
Readable output reports progress; --json returns status, docs root, release,
version and document count, or an error with a next action and nonzero exit.
./v3 docs publish --help describes setup and retry.
Run separately for initial static docs setup or after docs/application/profile/
asset changes. Failure retains the prior docs library; correct the reported cause
and retry with the same destination/profile. Legacy combined-release docs remain
available until successful migration. This command does not replace forum HTML,
the read model or forum offline snapshot. /api/docs-version and docs reload
notifications are independent of forum /api/version.
Imports/resumes, full forum builds and shared refreshes do no docs rendering or bulk inventory/validation/copying. Missing/broken docs cannot block those commands. Without a docs publication, PHP readers remain available; static-only hosting needs this separate publication and its docs/asset routing.
Build static HTML artifacts
./v3 build-static [repository_root] [database_path] [artifact_root]
Builds and atomically activates a complete forum static HTML release. Docs use
./v3 docs publish separately. It creates the
candidate read model and HTML release before replacing either live pointer, so
normal requests continue using the prior complete state while the command runs.
repository_root— canonical records checkout to render fromdatabase_path— read-model SQLite file to render fromartifact_root— static release root; defaults tostate/static_htmlwhen not given via argument orFORUM_STATIC_HTML_ROOT. The active release isartifact_root/current.
FORUM_STATIC_DETAIL_PAGES_ENABLED defaults to false. Full builds skip
bulk thread and post rendering; individual thread, post, and numeric quote
requests use PHP at runtime. Shared pages (including QDB's home, Latest, Top,
and Leetness), tag/profile pages, and the offline snapshot are still generated.
To pre-build detail HTML for static-only hosting, enable Static detail pages
on the instance feature-flags page or set
FORUM_STATIC_DETAIL_PAGES_ENABLED=true in the build environment. An explicit
environment value takes precedence over an instance setting; either explicit
setting takes precedence over the default. Existing explicit enablement remains
in effect after an upgrade, so disable it if these builds are still slow.
Run a full build after changing the effective flag value. A full build activates
a fresh release, removing old detail HTML from the active release when disabled;
--shared-only preserves existing detail artifacts and cannot make this
transition. Runtime detail hosting requires PHP routing (as provided by the
checked-in Apache configuration or local PHP router). Static-only hosts should
opt in before building/deploying. To restore static details, enable the flag and
run another full build.
Refresh only shared static pages
./v3 build-static --shared-only [repository_root] [database_path] [artifact_root]
Quickly creates and activates a release by copying the active complete release
and rerendering only shared routes (Board, Activity, Users, Tools, and similar
pages), including the service worker and its referenced assets. It does not
rebuild the read model or render tag, thread, post, or profile pages. Use it
after deploying presentation or fingerprinted-asset changes when the active
release already exists. It preserves the existing detail pages, so use the
full build-static after content changes or changes to a detail-page template.
Archive and remove a thread
./v3 archive-thread <thread_id> [repository_root] [database_path] [artifact_root] [archive_path]
Archives a full thread (root post, replies, supporting public keys) into a
.tar.gz archive, removes the component records from the live repository,
and rebuilds affected artifacts.
thread_id— required ID of the thread to archiverepository_root— canonical records checkout to archive from and remove records fromdatabase_path— SQLite file to rebuild after archivingartifact_root— static HTML output directory to rebuild after archivingarchive_path— output path for the.tar.gz; defaults to a generated path under the project's archive directory
Manage the private LLM config
./v3 private-config [view|edit|refresh-template|--view|--edit|--force|--api-key-stdin] [--path=/private/path/secrets.php]
Creates or updates the private PHP config consumed by
ForumRewrite\Support\PrivateConfig (LLM provider, API key, and related
settings for agent-reply features). LLM_PROVIDER is required (no default);
supported values are openai, openrouter, anthropic, stub, and
OpenAI-compatible gateways. Legacy DEDALUS_* settings are still read as
fallbacks, but new writes use LLM_* names.
view/--view— print a redacted summary and update reminders without writing the fileedit/--edit— open an existing config in$VISUAL,$EDITOR, orvi; it does not print secrets or create a missing filerefresh-template— rewrite the file with current comments/examples and every schema template setting, includingFORUM_STATUS_URL, while preserving existing values--force— overwrite without the usual confirmation/skip behavior--api-key-stdin— read the API key from stdin instead of an argument, so it never lands in shell history:printf '%s\n' "$LLM_API_KEY" | ./v3 private-config --api-key-stdin--path=...— write to a specific file instead of the default../forum-private/secrets.php(relative to this checkout)
Show agent-reply diagnostics
./v3 agent-reply status [post_id] [--limit=25] [--database-path=/path/post_index.sqlite3]
Read-only diagnostics for skipped or failed agent-reply generation rows.
post_id— when given, shows that post's rows (limit defaults to 10, capped at 100); when omitted, shows the most recent skipped rows (limit defaults to 25)--limit=...— maximum number of rows to show--database-path=...— read-model SQLite file to read from
Test the live LLM provider connection
./v3 agent-reply test [--timeout=30]
Sends one live plain-text task prompt to the configured LLM provider to validate the API key and model/service reachability.
--timeout=...— seconds to wait for the provider response
Run the local agent-reply test suite
./v3 agent-reply test-local
Runs the local (non-live) agent-reply test suite: AgentReplyGenerationTest,
AgentReplyCommandTest, and targeted LocalAppSmokeTest /
WriteApiSmokeTest cases covering status and task-queue fulfillment.
no parameters
Run approved Codex handoff requests
./v3 codex-handoff run [--limit=1] [--dry-run] [--database-path=/path/post_index.sqlite3] [--codex-bin=/path/codex]
Runs approved Codex handoff requests through the local codex binary.
--limit=...— maximum number of handoffs to run--dry-run— report the approved-handoff count without running anything--database-path=...— read-model SQLite file to read handoffs from--codex-bin=...— path to thecodexexecutable; also overridable viaFORUM_CODEX_EXECUTABLE
Run the local Codex handoff test suite
./v3 codex-handoff test-local
Runs the local Codex handoff test suite: CodexHandoffDraftServiceTest,
CodexHandoffStoreTest, CodexHandoffRunnerTest, and the WriteApiSmokeTest
cases covering the handoff API, approval/rejection flow, development-tag
requirements, activity lifecycle, and UI bindings.
no parameters
Seed an approved identity (shorthand)
./v3 approve <identity_id> [seed_reason] [repository_root] [database_path]
Shorthand alias for ./v3 approval seed (see below for parameter details).
Seed an approved identity
./v3 approval seed <identity_id> [seed_reason] [repository_root] [database_path]
Seeds an initial approved identity (e.g. the first admin) so
FORUM_APPROVED_MEMBERS_ONLY can be enabled without a chicken-and-egg
approval problem.
identity_id— required identity to seed as approvedseed_reason— free-text reason recorded with the seed; defaults to"initial approved user"repository_root— canonical records checkout to write the approval seed todatabase_path— SQLite file to rebuild after seeding
Approve an identity
./v3 approval approve <approver_identity_id> <target_identity_id> [repository_root] [database_path] [artifact_root]
Records an approval of target_identity_id by an already-approved
approver_identity_id, and rebuilds affected artifacts when an artifact root
is resolved.
approver_identity_id— required, must already be an approved identitytarget_identity_id— required identity being approvedrepository_root— canonical records checkout to write the approval todatabase_path— SQLite file to rebuild after approvingartifact_root— static HTML output directory to rebuild after approving
Standalone scripts (not wired into ./v3)
These live in scripts/ but have no ./v3 dispatcher entry — invoke them
directly with php scripts/<script>.php.
Audit post signatures
php scripts/audit_post_signatures.php [repository_root]
Scans canonical post records and reports counts for signed_valid,
missing_signature, invalid_signature, unknown_author_key, and
anonymous_unsigned.
repository_root— canonical records checkout to audit; defaults toFORUM_REPOSITORY_ROOTor the bootstrapped local repository
Build the SQLite query catalog
php scripts/build_sqlite_query_catalog.php [source_directory] [browser_asset_path] [local_pack_path]
Regenerates the SQLite viewer's preset query catalog from queries/sqlite/:
rewrites the generated block inside the browser viewer asset
(public/assets/sqlite_viewer.js) and writes a local .sql query pack.
Run this after adding or editing a query under queries/sqlite/.
source_directory— query source directory; defaults toqueries/sqlitebrowser_asset_path— viewer JS asset to rewrite the generated block in; defaults topublic/assets/sqlite_viewer.jslocal_pack_path— output path for the local.sqlquery pack; defaults topublic/assets/sqlite_query_catalog.sql
Check static artifacts for missing fingerprinted assets
php scripts/check_static_artifacts.php [artifact_root]
Scans every .html file under artifact_root for fingerprinted asset
references (/assets/name.<12-hex>.ext) and fails (exit 1, listing each
missing reference) if any referenced asset file doesn't exist. Useful after
a static build or asset-fingerprint change to catch broken references
before they ship.
artifact_root— directory to scan; defaults toFORUM_PUBLIC_ARTIFACT_ROOTorpublic/
Startup dependency failures list missing capabilities, group required Ubuntu extension installs into one command, and show optional recovery separately. Commands are highlighted on terminals unless NO_COLOR is set. dependency-check is an alias for check-dependencies.
Verify or restore image backups
./v3 images verify|restore <images.zip> <repository_root> <database_path>
[--image-root=/private/images] checks a revision-matched image/repository pair.
ZIP and legacy TAR image archives are supported.
verify stages and validates without activation; restore atomically installs
into a fresh private image directory or accepts an identical existing store.
It never overwrites a different store. Exit 0 means complete verification/restore;
exit 1 includes actionable failure text. Rebuild the read model after restoring.
See Image backup and recovery for the full sequence,
limits, deployment requirements, and interrupted-operation recovery.
Agent reply completion allowance
AGENT_REPLY_MAX_COMPLETION_TOKENS in private configuration (or the environment)
controls reply generation: default 8192, integer range 1–32768. Invalid values
fail before a provider call. Larger allowances can increase cost and latency;
they do not guarantee model support or successful completion. Exhausted replies,
including partial text, are rejected rather than published. Correct settings and
explicitly retry affected work; no automatic budget escalation occurs.
Recover one failed analysis
Inspect the post with ./v3 agent-reply status --post-id=POST and correct the
provider/model or completion allowance first. Use the full content hash from
that request's private record. Requeue and process only its returned task ID:
./v3 task-queue retry-agent-reply --post-id=POST --content-hash=HASH
./v3 task-queue run --task-id=TASK_ID
Pass the same repository, read-model, and queue path overrides to both commands
when working outside the default instance. Recovery preserves the original mode,
rejects changed/ineligible/published targets, and is safe to repeat if enqueueing
was interrupted. Running a completed task again makes no provider call. A
configuration or exhaustion failure needs another deliberate retry after correction.
status exposes outcome details in progress/history; a skipped request is not a
published reply. Targeted runs do not reconcile or process the unrelated backlog.
For one failed moderation work item, use its full content and rubric hashes:
./v3 fast-score retry --post-id=POST --content-hash=HASH --rubric-revision=RUBRIC --run
--run validates current content/rubric and processes only that pending/failed
normal moderation item under the worker lock, with at most one provider call.
It does not enqueue a broad sweep. Without --run, existing enqueue-for-sweep
behavior remains available. Backfill work retains its separate budget controls.
Scripture responses and on-demand expansion
All fresh agent replies share prompts/agent_response_personality.txt, including
ordinary, selected-mode, verified scripture, and expansion replies. It asks for
warm, direct engagement and eager discussion of Bible or Jesus topics while
preserving each task's format and quotation rules. Deploy this prompt with the
application code and restart long-running workers after changing it.
The generation prompt includes application-resolved approval and admin status for the original human author and a distinct requester. Admin means the existing root-approved operator role; unknown identity/profile information stays unknown. These facts provide context only: they do not authorize actions or change access rules. Expansion uses the original human author's status, not the initial reply-agent's. Fresh retries resolve current status; completed drafts and published replies are not regenerated merely because status or prompts changed.
Fresh legacy/automatic replies retain analysis eligibility checks but now use the common reply writer, adding one provider call instead of publishing the analysis suggestion. A writer failure uses the existing status and targeted retry commands above. Deploy/rollback code and prompts together; rolling back does not remove already published replies. No schema migration is needed.
Scripture responses follow editorial guidance rather than word counts or verse
quotas. Edit prompts/agent_response_scripture_oriented_analysis.txt for initial
answers and prompts/agent_response_scripture_expansion.txt for further reflection.
Both ask for appropriately quoted scripture with references/translation labels;
when no biblical translation is supplied, guidance prefers accurately recalled
KJV wording. Uncertain wording should be labeled as paraphrase. The technical
completion allowance remains independent of these writing instructions.
Approved members can select Expand beneath a verified scripture response. One shared expansion is generated by the existing task worker, displayed beneath the initial answer, and available to authorized readers on thread/post pages. The requester sees the result open when ready; other readers open it themselves. No generation happens merely from opening stored supplementary content.
POST /api/expand_agent_replyacceptspost_ididentifying the initial response.GET /api/agent_expansion_status?post_id=...returns progress, completion, or safe failure feedback.The existing
./v3 task-queue runfulfills expansion work. Use its returned task ID with--task-idto process only that item.For failed expansion recovery, use
./v3 agent-reply status --post-id=INITIAL_REPLYto find the target hash, correct the cause, then usetask-queue retry-agent-replywith that initial reply ID/hash and run only the returned task ID. The original response and its sources must still be available and unchanged.
Canonical provenance and a stable expansion ID prevent duplicate publication and survive read-model rebuilds. Changed/hidden/retracted sources suppress expansion; requester approval is rechecked before publication. Expansions cannot recursively request expansion. Existing responses need trusted canonical/request provenance to qualify; copying the response prefix does not establish eligibility.
Static/offline views retain stored supplementary content without generation controls. Snapshot schema 4 carries provenance and unavailable-expansion IDs; updates upgrade older metadata columns and remove unavailable expansion content.
Scripture quality and latency controls
These settings are opt-in until comparative evaluation passes:
SCRIPTURE_RESPONSE_GUIDANCE=current|revised|verifiedselects the original prompt pair orprompts/agent_response_scripture_oriented_analysis_revised.txtandprompts/agent_response_scripture_expansion_revised.txt. Default:current.verifieduses the source-owned quotation contract for initial replies and Expand. See verified scripture operations.SCRIPTURE_SOURCE_PATHoptionally overrides the local KJV JSON path; the pinned digest is always required. Missing/corrupt source permits explicit paraphrase only, or terminal failure; it never triggers a network download.SCRIPTURE_RESPONSE_REASONING_EFFORTdefaults to blank (provider default). Explicitminimal|low|medium|highcurrently supports direct OpenAI GPT-5, GPT-5 mini, and GPT-5 nano, including their 2025-08-07 snapshots. Unsupported combinations fail before generation. This setting applies only to scripture initial replies and expansions, never moderation or other response modes.SCRIPTURE_RESPONSE_IMMEDIATE_DISPATCH=truewakes the existing queue worker for each newly queued scripture request. Default: false. The host needs CLI PHP,proc_open, andnohup; PHP must be on PATH for FPM/CGI hosts. The worker waits up to 60 seconds for the queue lock, then leaves work queued for normal scheduled recovery. Keep the regular worker schedule: dispatch is an accelerator, not a substitute for recovery. It never repeats a failed provider generation.SCRIPTURE_RESPONSE_TIMING_ENABLED=trueappends phase markers (post ID, event, millisecond timestamp) to privatestate/private/scripture_timing.jsonl. Default: false. Disable after diagnosis and rotate/remove this private log as needed; it contains no forum text or credentials. Browser timing supplies the submission and visible-completion boundaries; phase durations must not be summed twice when work overlaps.
GET /api/scripture_reply_status?post_id=SOURCE reads existing initial-response
status without generating work. The browser polls pending scripture requests,
imports the canonical rendered reply card when ready, and preserves drafts.
Status respects discussion access and hidden/retracted sources/results. Existing
expansion status and polling remain available. Technical worker invocations may
use task-queue run --task-id=N --lock-wait-seconds=0..60; the default wait is zero.
To roll back candidate behavior, restore current, clear explicit reasoning,
and disable immediate dispatch. Existing canonical replies and queued requests
remain intact. Correct failed settings and use explicit targeted retry; changing
configuration does not rewrite old replies or silently regenerate failures.
For verified-mode rollback, disable agent_response_requests_enabled before
changing generation settings; follow the verified scripture operations guide.
Do not silently downgrade pending verified requests to unverified generation.
Scripture writer override: SCRIPTURE_RESPONSE_MODEL is blank by default (inherits LLM_MODEL). OpenAI scripture requests and Expand may select gpt-5-mini or gpt-5.6-sol; other response modes and structured moderation retain the shared model. Sol explicit reasoning is currently restricted to the evaluated low setting. This prepares isolated comparison; it does not activate verified guidance or establish release acceptance.
High-traffic mode
./v3 high-traffic enable|disable|status|restore|purge|refresh [/route/]
switches and manages instance-local Apache HTML caching. Run
./v3 high-traffic run /path/to/access.log every minute for popularity-driven
atomic refresh. See host setup, limits, verification and recovery.
Automatic traffic/load activation is deferred.
CLI output is readable by default; use --json for automation.
./v3 high-traffic cron /path/to/access.log prints the installation instructions
and crontab line; run executes one refresh. Existing schedules using cron
as the worker must change to run. Enable/status also report the cron line;
they do not install or verify a schedule. Use the web app's environment and user.