linc-crucible
Quality gates for mobile apps, answering one question: is this app ready to ship? crucible
builds an Android (Gradle) or iOS (xcodebuild) app and deploys it to devices through
linc-nerve, runs your lint and tests and folds them into a
7-dimension quality score with a ship gate at 85, checks app-store readiness (5 dimensions,
threshold 80%), runs YAML build-verification tests (BVT) against visual and accessibility-tree
baselines, and pulls ranked marketing screenshots out of a screen recording. It is one layer of a
4-layer stack: linc-device-kit (shared models) → linc-nerve (device I/O) → linc-crucible
(quality). crucible itself contains no device code; everything that touches a device goes through
the nerve daemon's HTTP API.
8 MCP tools | Python 3.13+ | Android + iOS
Install
Requires Python 3.13+ (python3 --version); with pipx/uv, --python 3.13.
pipx install linc-crucible(orpipx install --python 3.13 linc-crucible)uv tool install linc-crucible(oruv tool install --python 3.13 linc-crucible)pip install linc-crucible(inside a virtual environment)
AI-ranked frame picks need the unpublished linc-ai package; without it crucible_extract_assets uses evenly-spaced frames and reports fallback_used: true.
Project setup
crucible discovers projects by scanning $LINC_PROJECTS_DIR/*/linc-manifest.yaml (default
~/projects/*/linc-manifest.yaml). Two files live in the project root:
linc-manifest.yaml— identity.identity.nameis the project name every command takes;identity.packageis the Android applicationId / iOS bundle ID..forge.yaml— holds the build/test/lint commands; the name is historical. Commands in.forge.yamlrun from the project root, so the./gradlewexample below requires a Gradle wrapper (./gradlew) in that directory. When neitherlint_commandnortest_commandruns to a measurement (nothing measured),crucible assayreports"status": "unmeasured"and exits1.
# ~/projects/myapp/linc-manifest.yaml
schema_version: 1
identity:
name: myapp
package: com.example.myapp
# ~/projects/myapp/.forge.yaml
forge:
build_command: "./gradlew :app:assembleDebug"
test_command: "./gradlew test"
lint_command: "./gradlew lint"
For crucible preflight --project myapp, place store metadata in <project>/store/:
<project>/store/store-listing.md— markdown with headings# Title,# Short Description,# Full Description,# Keywords,# Category.<project>/store/brand-brief.json— JSON object with required fieldsapp_name,tagline,emotion,primary_color,accent_color,background_color,icon_concept,style,platforms.
Apps with a phone and a watch module sharing one package ID can declare modules: in
.forge.yaml (name, device_types, build_command, apk_pattern) so each artifact is routed
to the right device type: crucible pour --project myapp --module watchApp --device-type watch.
Quickstart
After Project setup above (myapp has
linc-manifest.yaml and .forge.yaml in $LINC_PROJECTS_DIR/myapp), the first four commands
need no daemon, device or Docker:
crucible fuelcheck
crucible assay --project myapp
crucible score myapp
crucible preflight --project myapp
# pour, bvt and baseline capture reach devices through the nerve daemon:
pipx install "linc-nerve>=0.1,<0.2" && nerve daemon &
claude mcp add linc-crucible -- crucible bellows
crucible fuelcheck reports which build tools, projects and services it can see and exits 0 on
a fresh machine. Seven of the 8 MCP tools map directly to CLI commands (crucible_extract_assets →
crucible extract-assets), while crucible_baseline is served by crucible baseline (capture,
list, prune; see MCP Tools); options are
flags, not positionals (crucible pour --project myapp), except crucible score PROJECT [--history] [--limit N] and crucible baseline. Output is JSON on stdout; for text, put
--human before the command (crucible --human score myapp). Exit 0 success (status: "partial" stays exit 0 with ship_gate: false in the payload), 1 tool error (a run that measured nothing, or a BVT that did not run, exits 1), 2 usage error. crucible schema --json
prints every command and option.
MCP Tools (8)
Seven of the 8 MCP tools are reflected directly as CLI commands (crucible_pour → crucible pour).
crucible_baseline is served on the CLI by the hand-written crucible baseline group (capture,
list, prune for screenshot baselines); its a11y mode and latest / promote actions are
MCP-only.
| Tool | Needs nerve daemon | Description |
|---|---|---|
crucible_pour |
yes (no with build_only) |
Build, install and launch on devices (launch_intent deep-link URI, e.g. myapp://settings). Android/iOS, phone/watch routing, build_only and skip_build modes |
crucible_assay |
no (live a11y tree check uses it when running) | Quality analysis: lint, tests, optional security (MobSF), perf, static a11y, live a11y tree (a11y_tree honours the personal-device policy; allow_personal=True overrides). Modes: quick or full |
crucible_bvt |
yes | Run a YAML Build Verification Test flow on a device: scripted UI steps with screenshot, element and assert checks |
crucible_score |
no | Latest quality score or history. 7-dimension weighted composite, ship gate 85 |
crucible_preflight |
no | App-store readiness: code quality, store listing (<project>/store/store-listing.md), visual assets, brand brief (<project>/store/brand-brief.json), build artifacts. Threshold 80% |
crucible_baseline |
yes for capture; no for latest, list, prune |
Golden baselines for screenshot and a11y-tree regression detection (crucible baseline capture|list|prune on the CLI; a11y mode, latest and promote are MCP-only) |
crucible_extract_assets |
no | Ranked marketing screenshots (with social/store variants) from a video recording |
crucible_usage_stats |
no | Usage analytics over the local MCP call log: per-tool calls, success rate, p50/p95 latency, dormant tools |
Configuration
| Environment variable | Default | Purpose |
|---|---|---|
LINC_HOME |
~/.linc |
Root of crucible state ($LINC_HOME/crucible/*) and the MCP call log |
LINC_PROJECTS_DIR |
~/projects |
Directory scanned for */linc-manifest.yaml project discovery |
LINC_WORKSPACE |
$LINC_HOME/projects/LINC (or ~/projects/LINC when LINC_HOME is unset) |
Optional LINC workspace fallback read by preflight when <project>/store/ is absent |
LINC_DEVICE_FLEET_FILE |
$LINC_HOME/device-fleet.yaml |
Personal-device registry; devices marked personal: true are refused unless allow_personal=True. A missing file means no personal devices are registered; a file that exists but cannot be read or parsed refuses EVERY device operation unless allow_personal=True |
LINC_LOG_LEVEL |
WARNING |
Level of the structured logs written to stderr |
LINC_BUILD_GATE |
(unset) | 1 wraps builds in linc-build-gate (host build-slot mutex) |
LINC_CRUCIBLE_BUILD_TIMEOUT |
600 |
Build timeout in seconds for pour; invalid values fall back to the default |
FORGE_PROJECTS_FILE |
$LINC_HOME/.forge/projects.yaml |
Legacy project registry, read-only; manifests discovered on disk take precedence |
FORGE_RUN_ID |
manual |
Deprecated session id, read when CRUCIBLE_RUN_ID is unset or empty |
CRUCIBLE_USAGE_LOG |
redacted |
Local usage log mode: off, redacted or full; an unknown value means redacted (see Trust) |
CRUCIBLE_RUN_ID |
FORGE_RUN_ID, else manual |
Session id stamped on usage log entries; a value outside [A-Za-z0-9._-]{1,128} is logged as manual |
CRUCIBLE_SCORES_DIR |
$LINC_HOME/crucible/scores |
Quality score history |
CRUCIBLE_BASELINES_DIR |
$LINC_HOME/crucible/baselines |
Visual and a11y-tree baselines |
CRUCIBLE_FLEET_FILE |
$LINC_HOME/crucible/fleet.yaml |
Device fleet registry |
CRUCIBLE_IOS_TEAM_ID |
(unset) | Apple Developer Team ID for physical-iOS device builds |
CRUCIBLE_MOBSF_API_KEY |
(unset) | API key for the optional MobSF security scan (assay mode full) |
ANDROID_HOME |
(unset) | Android SDK root; its build-tools/*/aapt2 reads the package name from a built APK |
ANDROID_SDK_ROOT |
(unset) | Legacy Android SDK root, used when ANDROID_HOME is unset |
Generated from linc_crucible.config_contract.ENV_VARS (crucible fuelcheck --env-table); pinned to the variables read under src/ by tests/test_config_contract.py.
State crucible writes between runs:
| Path | Contents |
|---|---|
$LINC_HOME/crucible/scores/<project>/ |
Quality score history (JSON) |
$LINC_HOME/crucible/baselines/<project>/ |
Visual and a11y-tree baselines |
$LINC_HOME/crucible/assets/ |
Extracted screenshots |
$LINC_HOME/crucible/fleet.yaml |
Device fleet registry |
$LINC_HOME/mcp-calls.jsonl |
Local MCP usage log; redacted by default (CRUCIBLE_USAGE_LOG): keeps tool name, timing, success and allowlisted parameter values, replaces every other parameter value with <redacted len=N> and absolute paths in error with <path> (dir 0700, file 0600) |
Troubleshooting
| Problem | Cause | Fix |
|---|---|---|
| "Project not found" | No linc-manifest.yaml under $LINC_PROJECTS_DIR |
Add the manifest to the project root, or point LINC_PROJECTS_DIR at its parent |
"Nerve unreachable at http://localhost:8111 — start it with nerve daemon" |
nerve daemon not running | nerve daemon, then curl http://localhost:8111/health |
| "No APK found" | Build not run | crucible pour --project myapp; check build_command in .forge.yaml |
| "No quality score found" | No assay yet | crucible assay --project myapp |
| Build timeout (>10 min) | Cold build cache | Build once by hand, then pour with skip_build |
Trust
- Your project's commands run as you.
assayandpourrun thebuild_command,test_commandandlint_commandfrom the project's.forge.yamlwith your user's permissions. crucible never spawns a shell itself (argvlist,shell=False,shlex.split) and refuses|,;,&,$(and backticks, but a project command that is itself a shell (sh -c '…',bash script.sh) runs a shell because the project asked for it: the trust boundary is the project. Only point crucible at projects you trust. Details: SECURITY.md. - No telemetry, no phone-home. Nothing leaves the machine: crucible talks to your local nerve
daemon, and to MobSF only if you run a
fullassay against one you started. The nerve daemon on loopback is a trusted peer: crucible acts on the device list, element trees and file paths (e.g. the screenshot path) nerve returns. - A local usage log, under your control. Each MCP tool call is appended to
$LINC_HOME/mcp-calls.jsonl(default~/.linc/mcp-calls.jsonl) with the tool name, timing and success. SetCRUCIBLE_USAGE_LOG=off|redacted|full:redacted(default) — keeps allowlisted parameter values (such asproject,mode,actionand boolean/numeric flags), replaces every other parameter value length-only (<redacted len=N>) and scrubs redacted values, host roots and absolute paths (<path>) fromerror; new state directories are created0700and the log file is created owner-only (0600).off— nothing is written.full— raw parameter values. Opt in only on a machine you trust.
How this repo works
This is a release mirror of LINC's private repository. The code is built by LINC's AI software factory under human review; each release lands as one commit. Issues are welcome; pull requests by arrangement — see CONTRIBUTING.md.
Built by LINC Innovations — we diagnose flaky device test suites.
Licensed under the Apache License 2.0.
Metadata
Release files for linc-crucible 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| linc_crucible-0.2.0.tar.gz | 441.4 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| linc_crucible-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 640.2 kB
Release files / linc_crucible-0.2.0.tar.gz
| Download URL | linc_crucible-0.2.0.tar.gz |
|---|---|
| Size | 441.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
cbe660142ec863a5cb36add570db6a1c61c26f4ed771b89ddab217b7f939566c
|
|
BLAKE2b-256 checksum How to use checksums |
3021474ff1aacc84c8b34a48fe5b4275da456255d3965dcf07179eca31ae1865
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.
Transparency logRelease files / linc_crucible-0.2.0-py3-none-any.whl
| Download URL | linc_crucible-0.2.0-py3-none-any.whl |
|---|---|
| Size | 198.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
83fd89197078e08d51ee9477014b9e31621c6cc75062c5a2605297531f21997d
|
|
BLAKE2b-256 checksum How to use checksums |
f0f3390a12b561a677c2dbdf63141d2414c38e4fd854768df826342eec63ba6a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.
Transparency logSecurity for linc-crucible 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for linc_crucible-0.2.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.