solverforge-cli owns one public scaffold entry point:
solverforge new <name>
The generated project is a neutral shell. The default shell is web; users can
choose --shell api for an HTTP API without frontend assets, --shell cli for
a Clap command-line app without Axum, or --shell mcp for an MCP server that
any MCP-capable agent harness can drive. Users add facts, entities, variables,
solution/score metadata, constraints, solver config, and generated data after
scaffolding. Shell choice is a delivery surface, not a model family. The
current shell set is exactly web, api, cli, and mcp; Tauri is deferred
and has no public scaffold selector in this release line.
Current CLI package version: 3.0.0.
Required Rust version: 1.95 or later.
Current generated projects target:
solverforge 0.19.4solverforge-ui 0.7.0for the web shellsolverforge-maps 2.1.4for the web shellrmcp 3.3.0for the MCP shell
The CLI version is separate from those targets and must remain visible in version output and generated README content.
skills/solverforge-modeling/ is a portable, harness-agnostic Agent Skill that
teaches a coding agent to model a planning problem with this CLI. It is an
in-repo product artifact, not generated code: a SKILL.md entry point with
references/ and scripts/, discovered by opencode, Claude Code, and the
cross-harness Agent Skills standard.
scripts/install-skill installs it by copying into each selected harness's own
skills directory at user or project scope. It never symlinks and never shares a
single location; every destination receives an independent copy. Ownership is
recorded by the skill's marker file, so the installer refuses to overwrite or
remove an entry it did not install, and --list/--uninstall are idempotent
against that marker. The default target is the cross-harness ~/.agents/skills
directory, which avoids duplicate definitions in harnesses that scan more than
one skills directory. make install-skill wraps the script.
The skill bundles scripts/solve-smoke-test.sh, a per-shell development aid: it
builds a generated app and drives it (web/API require a clean COMPLETED solve
with published scores; MCP requires a healthy, panic-free HTTP transport; CLI
validates demo-data serialization). It complements, and does not replace, the
repository's generated-app suites.
Planning variable kinds are:
scalar: one assigned value from either a fact collection, configured with--range <FACT_COLLECTION>, or a non-negative half-open integer domain, configured with--countable-range <FROM..TO>list: an ordered sequence of values from a fact collection, configured with--elements <FACT_COLLECTION>
Scalar variable generation accepts optional hook-name flags for current SolverForge scalar metadata:
--candidate-values--nearby-value-candidates--nearby-entity-candidates--nearby-value-distance-meter--nearby-entity-distance-meter--construction-entity-order-key--construction-value-order-key
These flags are metadata-only. The CLI emits the planning-variable attribute,
syncs the values through solverforge.app.toml, writes them to
static/generated/ui-model.json for web-shell projects, and leaves the Rust hook
function bodies under domain ownership.
Countable ranges are validated before mutation, emitted as
countable_range = "from..to", synchronized through the app spec, and projected
as structured from/to bounds for numeric-lane rendering in the web shell.
Fact ranges and countable ranges are mutually exclusive.
List variable generation accepts the complete current list metadata surface:
--domain cvrp--distance-meterand--intra-distance-meter--route-hooks,--savings-hooks, and--savings-metric-class-fn--element-owner-fn--construction-element-order-key--precedence-duration-fnand--precedence-successors-fn--solution-trait
The values are emitted into #[planning_list_variable(...)], synchronized to
the app spec, and projected to the web UI model. The cvrp profile supplies
its stock distance meters, route/savings hooks, metric class, and solution
trait as one coherent runtime profile; explicit overrides of those fields are
rejected when the profile is selected.
Scalar groups and conflict repairs are exact-ID resources. Scalar groups are
named by the user-provided group ID; conflict repairs are named by the
snake_case constraint ID they repair. Assignment-backed and candidate-backed
scalar groups participate in grouped construction and grouped local search
unless --skip-solver-config is used.
standard is not a planning variable kind. It is only the default demo data
size name alongside small and large.
The web shell generates:
Cargo.tomlwith Rust1.95,solverforge,solverforge-ui, andsolverforge-mapsdependencies, plus explicit current web, serialization, and utility dependency baselines. Generated projects do not depend on SolverForge runtime subcrates directly.solver.tomlas the search strategy and termination configuration layersolverforge.app.tomlas the scaffolded app/domain contractsrc/domain/with a neutralPlansolution and managed domain exportssrc/constraints/with an empty managed constraint setsrc/api/exposing the retained/jobsREST/SSE contract expected bysolverforge-ui, plus qualified-run and detailed telemetry routessrc/solver/tracking best solution, complete compact status telemetry, lifecycle events, snapshot-bound analysis, and separately retained bounded candidate-pull detailsrc/data/mod.rsas the stable data import wrappersrc/data/data_seed.rsas compiler-owned generated sample datastatic/index.htmlloading/sf/sf.cssand/sf/sf.jsstatic/app.jscontaining app composition and projection-specific renderingstatic/generated/ui-model.jsonas the compiler-owned UI model projectionstatic/sf-config.jsonas the preserved customization seam
The API shell uses the same domain, constraints, solver, data, DTO, route, and
SSE surfaces, but omits static/, solverforge-ui, solverforge-maps, and
static file serving.
The CLI shell keeps the domain, constraints, solver, data, and shared DTO
surface, but omits Axum routes, SSE route files, frontend assets,
solverforge-ui, and solverforge-maps. Its binary is a Clap command-line
entry point.
The MCP shell keeps the domain, constraints, solver, data, and shared DTO
surface, but omits REST route files, SSE files, frontend assets,
solverforge-ui, and solverforge-maps. It adds src/mcp/, an MCP server
built on rmcp 3.3.0 that projects the retained solver job lifecycle onto
twelve annotated tools with typed schemas. solve returns an MCP task handle
with the retained jobId in result metadata to task-capable clients (MCP
2026-07-28) and an immediate job summary to other clients. Each task has no
TTL, so expiry cannot orphan an active solve; task records remain for the
server process lifetime. Failed or cancelled solves terminate with the
corresponding MCP task state. stdio is the default transport;
--http serves stateless Streamable HTTP at /mcp on the loopback interface
unless --host selects a concrete IP. Wildcard binds are rejected so rmcp Host
validation remains active. One solver service and one task store are shared
across every stateless HTTP negotiation. The MCP shell keeps the runtime
console feature off because the runtime banner writes to stdout, which is the
stdio MCP transport channel;
diagnostics go to stderr. Every shell declares schemars as an optional
dependency behind a schema feature, and only the MCP shell enables it.
solverforge connect prints MCP client configuration derived from the project
(app name, resolved binary profile, and server port) and writes only in-project
configuration: --write vscode merges the project entry into
.vscode/mcp.json while preserving other servers and refusing to rewrite a
file that is not valid JSON. Global client configuration files are printed with
their exact path rather than modified.
templates/scalar/generic is the embedded template used by solverforge new.
templates/list/generic is not a public scaffold selector.
Generated mutation is canonical-only. The CLI rewrites explicit managed block regions and does not infer old unmanaged shapes.
Required managed surfaces:
src/domain/mod.rs:solverforge::planning_model!manifest withroot = "src/domain"and thedomain-exportsblock- solution files:
solution-imports,solution-collections,solution-constructor-params, andsolution-constructor-init - entity files:
entity-variablesandentity-variable-init src/constraints/mod.rs:constraint-modulesandconstraint-callssolver.toml: one generated model-resource region marked with# @solverforge:begin solver-configand# @solverforge:end solver-config; each generated phase has an exact-ID# @solverforge:owner <kind> <exact-id> <role>marker
Commands that add or remove generated resources should fail clearly when those current markers are missing or duplicated. Project-local entity and solution override templates are only valid when they emit the same managed block set; free-form overrides are intentionally rejected.
solverforge.app.toml is the project model used to regenerate frontend and data
projections. It tracks:
- app metadata, including fixed
starter = "neutral-shell"metadata, selectedshell, and CLI version - runtime target metadata and
runtime_source - web-shell
ui_source; API and CLI specs must keepui_sourceabsent, including after follow-up domain mutations - demo data sizes
- solution name and score type
- fact collections
- planning entity collections
scalarandlistvariable declarations- scalar fact/countable value-source metadata, scalar hooks, and list domain/hook metadata
- constraint modules
- scalar groups and conflict repairs by exact ID
For the web shell, static/generated/ui-model.json is derived from the app
spec and current domain parsing. API, CLI, and MCP shells do not create
frontend projection files. Unknown variable kinds are errors, not aliases.
For any generated frontend feature, ask first: does solverforge-ui already
provide it?
If yes, the scaffold should use the shipped solverforge-ui surface instead of
re-implementing it. Generated apps should rely on:
solverforge_ui::routes()for/sf/*assetsSF.createBackend()andSF.createSolver()for retained solver lifecycleSF.createHeader(),SF.createStatusBar(),SF.createModal(),SF.createApiGuide(), andSF.createFooter()SF.createTable()for tabular projectionsSF.rail.createTimeline(...)for variable-driven timelines
The scaffold may own thin composition code for domain projections that are not
available as turnkey solverforge-ui views.
Generated apps should behave like production references:
- status endpoints expose
currentScore,bestScore, solver status, and latest snapshot revision - status, snapshot, and SSE payloads expose every compact SolverForge telemetry aggregate and phase/selector/move/applied-move breakdown
GET /jobs/{id}/telemetryexposes the atomically paired retained status and opt-in bounded candidate-pull trace; candidate pulls never ride normal SSE, status, or snapshot trafficPOST /jobs/qualifiedaccepts externally attested schema, instance, initial-state, core-tree, and build SHA-256 digests and starts the same retained lifecycle with qualified trace provenance- SSE messages carry typed lifecycle metadata including
eventType,eventSequence,lifecycleState, andsnapshotRevision - retained lifecycle events include
progress,best_solution,pause_requested,paused,resumed,completed,cancelled, andfailed - progress-only events update status, not the rendered board
- best-solution snapshots remain separate from live progress telemetry
/jobs/{id}/snapshotstays aligned with snapshot-bound analysis- reconnect bootstrap comes from current
SolverManagerstatus plus the latest retained snapshot, not cached last SSE event text - Pause resumes from a retained checkpoint, Stop calls runtime cancel, and Delete is available only for terminal retained jobs before the next Solve
solverforge generate data owns generated sample data:
- preserves
src/data/mod.rsas the stable wrapper - rewrites
src/data/data_seed.rs - persists the selected demo size in
solverforge.app.toml - supports
samplemode by default andstubmode for shape-only data - serves the generated data catalog from
/demo-dataand selected demo data from/demo-data/{id}
Generated data should be deterministic and structurally useful for optimization testing. It should not pretend to be domain-specific business data.
solver.toml owns solver behavior, including phases and termination settings.
solverforge config show|set edits non-phase settings in that file through
dotted TOML paths such as termination.seconds_spent_limit. Ordered phases
are the solver execution graph and are not edited through config set.
Model-resource entries in solver.toml are graph references, not flat text
matches. Validation must inspect construction phases, top-level
move_selector, neighborhoods, nested selector children, and partition child
phases. Destroy re-renders the CLI-managed solver config region from
solverforge.app.toml; nested or user-authored refs block the destroy before
any project file is written. solverforge-cli must not link to
solverforge-config for this; the graph scan is CLI-private TOML-structure
validation.
Bounded candidate diagnostics are enabled through
candidate_trace.max_entries, including
solverforge config set candidate_trace.max_entries <N>. The CLI rejects a
missing, zero, non-integer, or out-of-range capacity. The neutral template
documents this setting but leaves it disabled by default because trace detail
can be large.
.solverforgerc is loaded from the project root first, then from
~/.solverforgerc. It is intentionally narrow and only carries local CLI
preferences:
portno_colorquiet
Validation should use ephemeral generated apps rather than checked-in sample projects:
- scaffold contract tests verify dependency targets, generated README metadata,
managed block markers, generated app specs, and
cargo check - runtime pipeline tests run phase-marked generated-app scenarios
- Playwright tests boot generated apps on random ports and verify browser-visible lifecycle behavior
Current scenario policy:
- neutral shell: bootable empty app
- mixed app: seeded scalar-plus-list solve through the runtime and browser, including required scalar assignment, complete list placement, cancel, and terminal cleanup
- scalar-only app: seeded solve flow through typed SSE, status, snapshot-bound analysis, checkpointed pause/resume, cancel, terminal-only delete, and reconnect bootstrap, including full aggregate telemetry, bounded candidate detail, and qualified trace provenance
- MCP app: modeled mixed scalar-plus-list app driven through the official
rmcpclient over stdio and stateless Streamable HTTP, covering the annotated tool surface, task-backed solve to a terminal snapshot with scalar assignment and complete list placement, the retained lifecycle through a legacy client, and byte-level stdio stdout purity during a solve
SolverForge 0.19.4 uses list variables for ordered sequences and routes. Both
generated-app end-to-end suites start and observe the real mixed scalar/list
path.
Do not reintroduce:
- public scaffold-family flags
- public Tauri shell selection before a real Tauri scaffold exists
- MCP or other shell aliases beyond the canonical
web,api,cli, andmcpselectors - hidden
standardvariable-kind aliases - scalar predecessor topology; ordered sequences belong to list variables
- hidden console/scaffold aliases
- compatibility migrations for unmanaged legacy generated files
- raw score-only SSE payloads
- separate starter-specific solve/render lifecycles