check-script is a Dart library (lib/) exposed through a CLI
(bin/check_script.dart) and a Flutter interface (gui/). It orchestrates
analyzers for shell and Python 3 scripts (external tools and built-in
rules),
normalizes their results into findings (Finding) classified by category and
severity, deduplicates them, applies directives, contexts and configuration,
computes the scores and produces the reports. It can also fix the
safe defects and compare an analysis against a baseline.
-
Use existing tools (ShellCheck, shfmt, bashate, checkbashisms, gitleaks, trufflehog,
bash -n) rather than rewrite them; the built-in rules cover what they do not handle and provide a minimal analysis without them. -
Graceful degradation: a missing tool is reported, never blocking; without shfmt, the structural rules fall back to a heuristic lexer.
-
Library first:
lib/never writes to the console; the CLI and the interface are only facades. -
Testability: command execution goes through
CommandRunner(replaced by a fake in the tests); parsers are pure functions tested on recorded real outputs; a corpus freezes the behavior on real scripts. -
No secret leaks: secrets are never copied into reports, trufflehog is run without network verification.
bin/check_script.dart CLI: options, fixing, outputs, thresholds, exit codes
lib/check_script.dart Library exports
lib/src/
model/finding.dart Category, Severity, Finding (hint, url, fingerprint), ToolRun
model/report.dart ScriptReport, Comparison (+ toJson)
script_info.dart ScriptInfo: shebang, Dialect, metrics, CRLF
config.dart CheckConfig, Profile, ToolConfig, ScoringConfig, Thresholds (YAML)
i18n.dart Lang, Tr (bilingual text), Messages (labels)
engine.dart Engine: orchestration, progress, cancellation, deduplication,
enrichment (links), contexts, configuration, sorting
scoring.dart scoreCategory, globalScore, sizeFactor, gradeFor
suppressions.dart # check-script disable… directives
baseline.dart Fingerprints (FNV-1a), Baseline, comparison
fixer.dart --fix: ShellCheck, built-in, shfmt fixes; syntax check
diff.dart Unified diff (Myers)
discovery.dart collectScripts, isScriptFile, isExcluded (files, folders,
host files)
watch.dart watchTargets: batches of modified files (--watch, Folder view)
embedded.dart Embedded scripts (GitHub Actions, GitLab CI, Dockerfile,
Makefile, Ansible): line-by-line virtual script, SEC023
version.dart appVersion (kept in sync with pubspec.yaml)
rules/catalog.dart Catalog of built-in rules (title, hint, context),
ExecContext, severity escalations by context
rules/examples.dart "To avoid / to write" examples per rule (shell and Python)
rules/custom_rules.dart CustomRule (rules.custom), CustomRulesAnalyzer
rules/references.dart CWE / OWASP / ANSSI references of the rules (ruleReferences),
titles, links, findingReferences, matchesReference (--ref)
lsp/server.dart LSP server (check-script lsp): JSON-RPC frames,
diagnostics (full / while typing), hover,
quick fixes, formatting, checkScript/score
highlight.dart Highlighting: tokens per line (shell, Python, YAML,
Dockerfile, Makefile), multiline states (heredoc…)
explain.dart ScoreExplanation, explainScore (cost and gain per rule, plan
to the next grade), sortByQuickWin
result_cache.dart ResultCache (JSON reports by key), fastHash (64-bit FNV-1a)
git.dart changedSince (--changed-since), normalizedPath
history.dart FolderHistory, HistoryEntry (folder history: GUI,
--history-dir, --dashboard)
feedback.dart FalsePositive, FalsePositiveLog, anonymizeCode
json_num.dart jsonInt: tolerant reading of numbers in the tools' JSON
(number or string depending on the version)
reporters/dashboard.dart renderDashboard, DashboardRepo, sparkline (dashboard)
config_discovery.dart findProjectConfig (.checkscript.yaml up to the git root)
ignore.dart IgnoreRules (.gitignore-style patterns: --exclude, exclude:,
.checkscriptignore), used by collectScripts and watchTargets
init.dart check-script init: suggestProfile, renderInitConfig
rule_doc.dart check-script explain: findRules, similarRuleIds, renderRuleDoc
report_diff.dart check-script diff: diffReports, ReportsDiff, renderDiffText / Markdown / Json
baseline_update.dart --baseline-update: updateBaseline (tightening of the baseline)
rules/same_rules.dart sameRuleIds: the same rule in several tools (Ruff S ↔
Bandit B, Ruff PL ↔ Pylint, ShellCheck ↔ built-in)
rules/registry.dart RuleEntry, knownRules: built-in rules and classified
external codes, bilingual titles (Rules tab of the interface)
analyzers/
analyzer.dart Analyzer, AnalysisContext, CommandRunner, CancelToken
shellcheck.dart ShellCheck (JSON) and SCxxxx classification table
external_tools.dart Syntax check (bash -n, Python compilation), shfmt,
bashate, checkbashisms and their parsers
python_tools.dart Ruff, Bandit, Semgrep, mypy, Pyright, Pylint, Radon, Vermin:
invocation, parsers, classification, equivalents
python_rules.dart Built-in Python rules; PythonFacts (ast module via python3,
line-by-line fallback)
python_deps.dart Imports (pydeps: PYROB005, PYPOR002), declared dependencies
(requirements, pyproject, PEP 723…), pip-audit
commands.dart Inventory of external commands (CommandUse), dnf / apt
packages, presence (command -v), ROB017
ci_tools.dart hadolint (Dockerfile), actionlint and zizmor (workflows):
host file on standard input, appliesTo, parsers
secrets.dart gitleaks, trufflehog
ast.dart AstFacts: facts extracted from `shfmt --to-json`
shell_lexer.dart Heuristic lexer (fallback without shfmt)
builtin_rules.dart Detection of built-in rules (lines, structure, file)
reporters/
reporters.dart Terminal, Markdown, AsciiDoc, JSON; OutputFormat, render()
html.dart Standalone HTML (filters, annotated source)
sarif.dart SARIF 2.1.0
codeclimate.dart GitLab Code Quality
junit.dart JUnit XML, GitHub Actions annotations (--format github)
pdf.dart PDF audit report (pdf package): system fonts,
latin-1 fallback; renderBytes (reporters.dart) writes it
gui/ Flutter interface (see <<gui>>)
editors/vscode/ VS Code extension (TypeScript, vscode-languageclient):
npm run package → .vsix; npm test → end-to-end test
in a real VS Code (CHECK_SCRIPT, VSCODE_EXECUTABLE)
editors/eclipse/ Eclipse plugin (LSP4E, generic editor): Maven Tycho,
mvn -f editors/eclipse/pom.xml verify → update site
(first copy LICENSE into bundles/fr.chla28.checkscript/)
editors/geany/lsp.conf Configuration of Geany's LSP Client plugin
action.yml Composite GitHub Action (builds check-script, annotations,
summary, SARIF); tested by .github/workflows/self-check.yml
tool/calibrate.dart Calibration on the corpus
tool/false_positives.dart Summary of reported false positives (per rule)
test/ Tests; fixtures/ (including real tool outputs), corpus/
completions/ Bash and zsh completions
doc/ Guides, man page (check-script.1.adoc), CI examples
packaging/rpm/ RPM specs (CLI, interface)
scripts/ build-dist.sh, build-rpm.sh, build-editors.sh, install.sh, uninstall.sh,
pre-commit-hook.sh
.pre-commit-hooks.yaml Hooks for the pre-commit framework
file ──► ScriptInfo.fromContent (shebang or .py extension → Dialect, CRLF, lines)
│
▼
Engine.analyze ── for each enabled Analyzer of the script's language (analyzersFor),
│ in priority order:
│ shell : syntax → shellcheck → gitleaks → trufflehog → checkbashisms
│ → builtin (shfmt tree if available) → shfmt → bashate
│ python : syntax → bandit → ruff → semgrep → mypy → pyright → pylint
│ → vermin → radon → gitleaks → trufflehog → builtin (ast)
│ tools run in parallel (Future.wait), results stored in
│ priority order; Engine(parallel: false) for strict order
│ onProgress(AnalysisProgress); CancelToken checked for each tool
│ analyzeFiles(paths, jobs): several scripts at once (default
│ Engine.defaultJobs = processors / 4, between 1 and 4), order preserved
▼
deduplicate ── keeps the first of each group of duplicates
Suppressions ── # check-script disable… directives (counted in `suppressed`)
enrich ── documentation links, generic hints for external tools
escalate ── severities raised according to contexts (root, cron, systemd)
applyConfig ── rules.disabled, rules.overrides
fingerprintAll── stable fingerprints (baseline, SARIF, Code Quality)
sortFindings ── category, severity, line
scoreCategory × 5, globalScore
▼
ScriptReport ──► Baseline.compare (if baseline) ──► render(format)
Finding-
tool,ruleId,category,severity,line(0 = file),column,message,snippet,equivalents(codes of other tools designating the same defect),hint(advice),url(documentation),fingerprint,endLine(end of the reported multi-line statement, used for deduplication: line overlap, extent ≤maxDuplicateSpan),edits(concrete fix: 1-basedTextEdit, exclusive end; coming from ShellCheck’sfixor fromattachFixesfor the built-in fixes).Finding.fromJsonreads backtoJson.fixPreviewderives the before / after lines from it;exampleFor(rules/examples.dart) gives the generic example of a rule. ToolRun-
ok,missing,skipped,failed,disabled; version; number of findings;detail(ast/lexerfor built-in rules). ScriptReport-
script, tools, findings, 5
CategoryScore, global score, grade, date,suppressed,profile,contexts,comparison.
abstract class Analyzer {
String get name; // configuration key
ToolLanguage get language; // shell (default), python or any
Future<AnalyzerResult> analyze(AnalysisContext ctx);
Future<String?> version(CommandRunner runner, CheckConfig config); // null = absent, '?' = unreadable
}CommandRunner.run(exe, args, {stdin, cancel}) returns null if
the executable cannot be found. ProcessCommandRunner forces LC_ALL=C, writes
stdin (used by shfmt --to-json, bash -n, the formatting of --fix)
and kills the process when the CancelToken is triggered. Analyzers
go through ctx.run(…), which forwards the token.
| Tool | Invocation | Parser / classification |
|---|---|---|
syntax |
|
|
shellcheck |
|
|
gitleaks |
|
|
trufflehog |
|
|
checkbashisms |
|
|
builtin |
|
|
shfmt |
|
|
bashate |
|
|
syntax (Python) |
|
|
ruff |
|
|
bandit |
|
|
semgrep |
|
|
mypy |
|
|
pyright |
|
|
pylint |
|
|
radon |
|
|
vermin |
|
|
builtin (Python) |
|
|
shellcheckMap explicitly classifies the significant codes; otherwise
classifyShellcheck applies: SC3xxx → Portability (Medium/Low), SC1xxx
error → Robustness/High, style → Maintainability/Low, otherwise Robustness
according to the level. Bashisms that break under dash (SC2112/2113
function, SC3010 [[, SC3011 <<<, SC3020 &>, SC3030/3054 arrays,
SC3005/3006 ((, SC3018, SC3001) are High — a decision resulting from
calibration.
Two findings are duplicates if one of the equivalents of one matches the
ruleId of the other (exact or * prefix) and they concern the same
line — or one of them is a "file" finding (line 0) or a SYNTAX error. Same
tool: same line and (same rule or equivalent). The first one encountered is
kept; the order of defaultAnalyzers() sets the priority.
Ruff Snnn = Bandit Bnnn (same numbers); Ruff PLxnnnn = Pylint
xnnnn; _ruffToPylint table for the others (E722 = W0702, B006 =
W0102…); Semgrep is equivalent to the Bandit and Ruff security codes on the same
line; mypy to Pyright diagnostics (report*); version errors
(match in 3.9) from Ruff and Pyright are classified as Portability and are
equivalent to VERMIN. Bandit B105–B107 messages and Semgrep "secret" messages
are redacted, and their lines masked (_revealsSecret).
rules/catalog.dart describes each rule: identifier, category, severity,
bilingual title and fix hint (Tr), activation contexts.
Detection is separate (builtin_rules.dart); add(id, line, …) creates
the Finding from the catalog and ignores rules whose context
is not declared.
AstFacts (analyzers/ast.dart) walks the JSON of shfmt --to-json:
-
functions: name, start and end lines; -
blocks: control structures and depth (else/elif at the same level); -
commands: literal name, literal arguments (nullif expansion),hasExpansion,checked(if/while condition, left operand of&&/||— operators 10 and 11 —, negated command),inLoop,inSubstitution.
With the tree, command rules (SEC003, SEC012, ROB005, ROB007,
ROB011, POR004, POR005, POR007, PERF003, PERF004) and structural rules (MNT002,
MNT003) are exact; the LineRule`s marked `astCovered are then
ignored. Without shfmt, lexScript (comments, strings masked in
bare, heredocs) and regular expressions take over.
-
Add a
RuleInfotoruleCatalog(fr/en title and hint, contexts if any). -
Add its detection: a
LineRuleinlineRules(withequivalents, andastCovered+ a case in_astStructureif it concerns a command), or a file rule inrunBuiltinRules. -
If it must be raised in a context, complete
contextEscalations. -
Tests: a triggering case and a clean case (
builtin_rules_test.dartornew_rules_test.dart), thenUPDATE_GOLDEN=1 dart test test/corpus_test.dartif the corpus changes — checking every difference. -
Update the rules table in
doc/user.adoc.
-
Suppressions.parsereads# check-script disable=(line, or next code line for a standalone comment),disable-next-line=,disable-file=; wildcards*andall. -
ExecContext(root,cron,systemd): conditional rules (RuleInfo.contexts) andcontextEscalations(severity only raised). -
Profile:CheckConfig.forProfileprovides the base;CheckConfig.parseapplies the YAML on top (the command-line profile takes precedence over theprofile:key).
factor(n) = √(reference / max(reference, lines of code)) reference = 100 penalty = Σ_rules weight(severity) × (1 + log2 occurrences) × (factor if Medium/Low) score = clamp(10 − penalty, 0, 10), rounded to the tenth global = min(weighted average, worst category + 1.5) maxGapToWorst grade = A ≥ 9, B ≥ 7.5, C ≥ 6, D ≥ 4, E
The test/corpus/ corpus gathers frozen copies of scripts from the
AuditTools suite and typical scripts (curl | bash installer, old
cron backup, POSIX init script with bashisms, clean library), and
12 py_* Python scripts written for calibration (clean tool, sloppy API
client, dangerous installer, Python 2 style, 3.10+ features…). Semgrep is left
out of the grade checks (downloaded rules). labels.yaml gives for each
script, established a priori by reading the script, the expected range of
grades (with and without context), the mandatory and forbidden rules.
dart run tool/calibrate.dart --details # actual scores vs expected rangesTwo adjustments came out of it: dash-breaking bashisms classified High, and the global score cap lowered from "worst + 2.5" to "worst + 1.5". Any weight change must leave the corpus compliant.
The Python calibration (0.7.0) aligned severities with the shell scale without
touching the model: hardcoded secrets Critical if the value is realistic (like
SEC002), code or data execution High (like SEC003), PYSEC001
Medium (like SEC022), python shebang High; it added PYROB003 (cron
without lock, like ROB014) and PYROB004 (lost main() return code),
and counts docstrings as documentation. Three a priori ranges
judged too severe with respect to the shell scale were revised, with their
justification in labels.yaml.
fixScript chains: ShellCheck replacements (parseShellcheckFixes,
applyEdits: 1-based positions, exclusive end, tab = 1 column,
conflicting edits ignored), built-in fixes (applyBuiltinFixes,
never inside a heredoc, a multi-line string or a comment;
replaceBackticks ignores apostrophes and escaped backticks), then
shfmt -ln -i on standard input. If <shell> -n accepts the original but
rejects the result, FixResult.aborted is set and nothing is written.
For a Python script (_fixPython): ruff check --fix then ruff format
on standard input (--stdin-filename=script.py), fixes being
counted from the JSON output; checked by Python compilation
(syntaxRegression uses SyntaxAnalyzer.commandFor).
unifiedDiff (Myers) produces the diff of --dry-run and of the interface.
fingerprintAll computes fnv1a64(tool|rule|normalized line|rank): the
line number is not part of the fingerprint. Baseline.parse reads back a JSON
report; compare matches by fingerprint (multiset) and returns
Comparison (new, fixed, unchanged, previous scores). Reports
then detail only the new findings (detailFindings).
render(reports, format, RenderOptions); OutputFormat: terminal,
markdown, asciidoc, json, sarif, html, codeclimate (deduced from
the extension by OutputFormat.fromPath).
-
SARIF: one rule per tool/rule pair (
help,helpUri,security-severityfor security), results withpartialFingerprints,startLine ≥ 1. -
Code Quality:
check_name,severity(blocker/critical/major/minor),fingerprint,location.lines.begin. -
HTML: single page without external resources, light/dark theme, filters, annotated source (anchors
#s<i>-L<n>).
JSON format (excerpt):
{
"tool": "check-script", "version": "0.24.1",
"reports": [{
"file": "deploy.sh", "dialect": "bash",
"lines": {"total": 16, "code": 15, "comments": 0},
"profile": "standard", "contexts": ["root"],
"global": {"score": 1.5, "grade": "E"},
"categories": [{"category": "security", "score": 0.0, "total": 10,
"critical": 3, "high": 3, "medium": 3, "low": 1}],
"suppressed": 0,
"comparison": {"new": 1, "fixed": 2, "unchanged": 30, "previousGlobal": 2.5,
"previousScores": {"security": 0.0}},
"tools": [{"tool": "shellcheck", "status": "ok", "version": "0.11.0", "findings": 14}],
"findings": [{"tool": "builtin", "rule": "SEC001", "category": "security",
"severity": "critical", "line": 5, "column": 0, "message": "…",
"snippet": "…", "hint": "…", "fingerprint": "9f2c…",
"edits": [{"line": 5, "column": 12, "endLine": 5,
"endColumn": 12, "replacement": " || exit"}]}]
}]
}Lang.fromEnvironment (LC_ALL > LC_MESSAGES > LANG); labels in
Messages (library) and S (interface); rules: Tr(fr, en) in the
catalog; messages from external tools are not translated; decimal comma
in French.
gui/ depends on the library through path: ../.
gui/lib/main.dart Application, navigation, drag-and-drop, progress
gui/lib/app_state.dart AppState (ChangeNotifier) and GuiSettings (shared_preferences)
gui/lib/strings.dart Interface-specific labels (fr/en)
gui/lib/screens/analysis_screen.dart Source + Summary / Findings tabs, fixing, export
gui/lib/screens/folder_screen.dart Sortable table, trend (sortReports)
gui/lib/screens/settings_screen.dart Language, theme, profile, contexts, tools, YAML
gui/lib/editor.dart detectEditor, editorCommandLine, openInEditor
gui/lib/code_style.dart Code font (embedded JetBrains Mono, gui/fonts/, OFL),
CodeFont (InheritedWidget), token colors,
installed mono fonts (fc-list)
gui/lib/screens/rules_screen.dart Rules: checkboxes (GuiSettings.disabledRules, merged
into rules.disabled by buildConfig; those from baseConfig
are locked), search, filters, encountered rules
(AppState.seenRules, remembered)
gui/lib/widgets/ radar_chart (RadarPainter), score_panel, source_view,
findings_list, fix_panel (fix code), split_view
(draggable divider), common (colors, badges, DeltaChip)
AppState builds the configuration (buildConfig: optional YAML,
profile, contexts, disabled tools), runs Engine with onProgress and a
CancelToken, manages the baseline, proposeFix / applyFix (.orig copy)
and the export. Beware: Flutter has a Baseline widget; import
package:flutter/material.dart with hide Baseline wherever the library’s
class is used.
Tests: flutter test (state, sorting, radar, screens). Off-screen captures with
the real system fonts:
cd gui && SCREENSHOT_DIR=/tmp/shots flutter test test/screenshot_test.dartFrench captures are written to SCREENSHOT_DIR, English ones to SCREENSHOT_DIR/en.
The interface is declared in the MainGUI launcher (kToolSpecs, id
check_script, command check-script-gui, bundle check_script_gui).
dart test # library and CLI
UPDATE_GOLDEN=1 dart test test/corpus_test.dart # regenerate snapshots (after checking)
dart analyze --fatal-infos
(cd gui && flutter analyze && flutter test)| File | Content |
|---|---|
|
Each rule (trigger / clean case), dialects, contexts, profiles, entropy, catalog |
|
Facts extracted from the real shfmt tree; rules on the tree; |
|
Parsers on the tools' real outputs |
|
Directives |
|
Fingerprints, comparison |
|
ShellCheck replacements, built-in fixes, abort, diff |
|
SARIF, HTML, hints, external secrets, progress, cancellation |
|
Engine, scoring, configuration |
|
Reports, options and exit codes |
|
Snapshots, mandatory / forbidden rules, grade ranges |
|
Obsolete directives (MNT011), score explanation and quick win, SARIF |
|
Same rule across tools, forwarded exclusions, |
|
Python tool parsers (real outputs from |
|
Per-finding fixes, examples, HTML report |
|
CI examples ( |
|
Scoring regression: snapshot of the corpus scores for the three profiles ( |
|
|
|
|
|
Real tools (skipped if absent), including Ruff, Bandit, mypy, Radon, Vermin on |
Every rule fix comes with a test that fails before the fix; every false positive found on a real script joins the corpus.
Commit messages follow Conventional Commits: types and scopes and description in English (the history up to 0.18.5 is in French), no trailing period, first line of at most 100 characters.
feat(gui): ajoute l'onglet Règles
fix(flatpak): lance les outils par leur chemin absolu
docs(user): précise --changed-since
feat(engine)!: change le format JSON ← "!" or "BREAKING CHANGE:": incompatibleTypes: feat, fix, perf, refactor, docs, test, build, ci,
style, chore, revert. Usual scopes: engine, cli, gui,
python, rules, reporters, flatpak, appimage, rpm, ci, doc.
-
Local check:
scripts/install-git-hooks.shenables.githooks/(core.hooksPath); thecommit-msghook rejects a non-conforming message (git commit --no-verifyto bypass it). -
CI check:
.github/workflows/commits.ymlchecks the new commits of each push and pull request (tool/commit_lint.dart --range). -
Release:
dart run tool/release.dart --dry-rundisplays the next version and the CHANGELOG section; without--dry-run, it synchronizes the version files, writes the CHANGELOG, commitschore(release): X.Y.Zand tagsvX.Y.Z(--pushto push, which triggers the GitHub release). Version:feat→ minor, other change → patch, incompatible → major (minor while the version is 0.x). Notes written under "## Unreleased" are kept at the top of the section.
scripts/build-dist.sh [VERSION] [--rpm] [--editors] [--install] [--skip-tests] [--no-gui]
(also called by the AuditTools/scripts/build-dist.sh orchestrator):
analysis and tests (Dart and Flutter), CLI binary, Flutter bundle (version
injected by --build-name), man page (asciidoctor), completions,
documentation and CI examples, .desktop and icon, SBOM and CVE report if
sbom-generator is present, archive, RPM (scripts/build-rpm.sh, from
git archive HEAD: only committed content is packaged).
scripts/build-editors.sh [VERSION] [--skip-tests] [--no-vscode] [--no-eclipse]:
VS Code extension (npm ci, end-to-end tests under xvfb-run or DISPLAY,
vsce package) and Eclipse update site (Tycho) → dist/check-script-VERSION.vsix
and dist/check-script-eclipse-VERSION.zip.
scripts/build-appimage.sh [--skip-build]: Flutter bundle and compiled
CLI in an AppDir (AppRun: interface by default, CLI with --cli
or through a link named check-script), shared .desktop, icon and AppStream
metadata (packaging/linux/), then appimagetool (PATH,
APPIMAGETOOL, or downloaded into build/appimage/) → dist/CheckScript-VERSION-ARCH.AppImage.
scripts/build-flatpak.sh [--install] [--skip-build]: same binaries,
manifest packaging/flatpak/fr.chla28.check_script_gui.yml (GNOME 50
runtime, which provides GTK 3), flatpak-builder (or org.flatpak.Builder),
runtime and SDK installed from Flathub for the user →
dist/CheckScript-VERSION-ARCH.flatpak. Inside the Flatpak,
ProcessCommandRunner runs the tools on the host (hostInvocation:
flatpak-spawn --host --env=LC_ALL=C …, presence checked with command -v
on the host); the launchers put TMPDIR in the application cache,
whose path is identical in the sandbox and on the host.
Inside the Flatpak, the user’s PATH on the host is read once by
a login shell (bash -lc, for ~/.local/bin, ~/bin…) and each
tool is launched by its absolute path resolved on the host: the portal
looks for the executable with its own minimal PATH, not the one from --env.
Release (.github/workflows/release.yml): on each v* tag, tests
(without external tools, for results independent of the tool versions
shipped by distributions), then archive, AppImage and Flatpak (Ubuntu 24.04), RPM
(Fedora 44 container), and GitHub release with the files and the CHANGELOG
excerpt. Flutter is pinned (FLUTTER_VERSION).
Both scripts share scripts/package-common.sh and display the minimum
glibc required by the binaries (currently 2.34: Rocky 9, Flatpak
runtimes); for a widely compatible AppImage, build on the oldest
targeted distribution.
The specs neutralize strip and debuginfo (they corrupt Dart AOT
binaries); the interface spec disables automatic dependency
scanning (embedded Flutter engine). License: LGPL-3.0-or-later
(LICENSE), reused by the specs and the AppStream metadata.
The version appears in pubspec.yaml, gui/pubspec.yaml,
lib/src/version.dart and CHANGELOG.md; build-dist.sh reports a
mismatch between the first two Dart files.