Skip to content

Latest commit

 

History

History
586 lines (509 loc) · 32.7 KB

developer.adoc

File metadata and controls

586 lines (509 loc) · 32.7 KB

check-script — Developer guide

1. Overview

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.

1.1. Principles

  • 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.

2. Source tree

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

3. Analysis flow

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)

4. Model

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-based TextEdit, exclusive end; coming from ShellCheck’s fix or from attachFixes for the built-in fixes). Finding.fromJson reads back toJson. fixPreview derives 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 / lexer for built-in rules).

ScriptReport

script, tools, findings, 5 CategoryScore, global score, grade, date, suppressed, profile, contexts, comparison.

5. Analyzers

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

bash|sh|ksh|zsh -n FILE

parseSyntaxErrors → SYNTAX Robustness/Critical

shellcheck

--format=json1 --enable=… --exclude=… --shell=… [--external-sources --source-path=SCRIPTDIR]

parseShellcheckJson + classifyShellcheck

gitleaks

detect --no-git --redact --exit-code=0 --report-format=json --report-path=/dev/stdout

parseGitleaks → GL:<rule> Critical

trufflehog

filesystem FILE --json --no-update --no-verification

parseTrufflehog → TH:<detector> High (Critical if verified)

checkbashisms

--extra [--force], sh/dash scripts or without shebang

parseCheckbashisms → CB

builtin

shfmt --to-json -ln=… on standard input (rerun in bash for a non-POSIX sh)

AstFacts.fromJson then runBuiltinRules

shfmt

-d -ln=… -i=<unit>; rerun with -ln=bash if POSIX parsing fails

parseShfmtDiff (FORMAT), parseShfmtErrors (PARSE / DIALECT)

bashate

--ignore=… --max-line-length=N

parseBashate (2.x and 0.x)

syntax (Python)

python3 -c <pythonCompileScript> FILE: compile() without pycache

parsePythonSyntaxError (line:column:message)

ruff

check --isolated --no-cache --target-version=pyNN --line-length=N --select=… --ignore=… --output-format=json then format --diff

parseRuff + classifyRuff, ruffEquivalents; safe fixes → edits

bandit

-f json -q -s …

parseBandit (severity × confidence, banditMap, redacted secrets)

semgrep

scan --config=p/python --json --metrics=off --quiet --disable-version-check

parseSemgrep (category from metadata; fix → edits)

mypy

--output=json --no-incremental --cache-dir=/dev/null --ignore-missing-imports --check-untyped-defs [--python-version]

parseMypy (one JSON line per error)

pyright

--outputjson --pythonversion V

parsePyright (0-based positions)

pylint

--output-format=json2 --score=n --persistent=n --py-version=V --disable=…

parsePylint + pylintMap

radon

cc -j -s, mi -j -s

parseRadonCc (CC, ≥ 11), parseRadonMi (MI)

vermin

--no-tips --violations -t=V- --format parsable

parseVermin (VERMIN)

builtin (Python)

python3 -c <pythonFactsScript> FILE

PythonFacts.fromJson (otherwise lexPythonFacts) then runPythonRules

5.1. ShellCheck code classification

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.

5.2. Deduplication

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.

5.3. Python equivalences

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).

6. Built-in rules

6.1. Catalog

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.

6.2. Syntax tree (shfmt) and lexer fallback

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 (null if 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.

6.3. Adding a rule

  1. Add a RuleInfo to ruleCatalog (fr/en title and hint, contexts if any).

  2. Add its detection: a LineRule in lineRules (with equivalents, and astCovered + a case in _astStructure if it concerns a command), or a file rule in runBuiltinRules.

  3. If it must be raised in a context, complete contextEscalations.

  4. Tests: a triggering case and a clean case (builtin_rules_test.dart or new_rules_test.dart), then UPDATE_GOLDEN=1 dart test test/corpus_test.dart if the corpus changes — checking every difference.

  5. Update the rules table in doc/user.adoc.

7. Directives, contexts, profiles

  • Suppressions.parse reads # check-script disable= (line, or next code line for a standalone comment), disable-next-line=, disable-file=; wildcards * and all.

  • ExecContext (root, cron, systemd): conditional rules (RuleInfo.contexts) and contextEscalations (severity only raised).

  • Profile: CheckConfig.forProfile provides the base; CheckConfig.parse applies the YAML on top (the command-line profile takes precedence over the profile: key).

8. Scoring and calibration

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 ranges

Two 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.

9. Automatic fixing

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.

10. Baseline

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).

11. Reports

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-severity for security), results with partialFingerprints, 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"}]}]
  }]
}

12. Internationalization

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.

13. Flutter interface

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.dart

French 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).

14. Tests

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

builtin_rules_test.dart, new_rules_test.dart

Each rule (trigger / clean case), dialects, contexts, profiles, entropy, catalog

ast_test.dart

Facts extracted from the real shfmt tree; rules on the tree; loadAst

parsers_test.dart

Parsers on the tools' real outputs

suppressions_test.dart

Directives

baseline_test.dart

Fingerprints, comparison

fixer_test.dart

ShellCheck replacements, built-in fixes, abort, diff

formats_test.dart

SARIF, HTML, hints, external secrets, progress, cancellation

engine_test.dart, scoring_test.dart, config_test.dart

Engine, scoring, configuration

reporters_test.dart, cli_test.dart

Reports, options and exit codes

corpus_test.dart

Snapshots, mandatory / forbidden rules, grade ranges

v080_test.dart

Obsolete directives (MNT011), score explanation and quick win, SARIF fixes, results cache, --changed-since (real git repository), project configuration

improvements_test.dart

Same rule across tools, forwarded exclusions, --fix and disabled rules, multi-line duplicates, # noqa / # nosec, parallel analyses, Semgrep cache, YAML export (round trip), folder HTML report, Python calibration

python_test.dart

Python tool parsers (real outputs from fixtures/outputs/python/, captured on fixtures/bad.py), deduplication, secrets, built-in Python rules (lexer and real ast), --fix, discovery, pythonTarget

fix_suggestions_test.dart

Per-finding fixes, examples, HTML report

ci_examples_test.dart

CI examples (doc/ci/, ci.adoc): every check-script command is accepted by the real option parser, action inputs and pre-commit hooks exist, cited versions are current

scoring_golden_test.dart

Scoring regression: snapshot of the corpus scores for the three profiles (test/corpus/expected_scores.txt, UPDATE_GOLDEN=1 to regenerate), scoring constants = user guides

tools_test.dart

check-script diff, --baseline-update, --fix --interactive, --format gitlab

subcommands_test.dart

.gitignore patterns, --exclude / .checkscriptignore / exclude:, check-script explain and check-script init

integration_test.dart

Real tools (skipped if absent), including Ruff, Bandit, mypy, Radon, Vermin on bad.py / good.py

Every rule fix comes with a test that fails before the fix; every false positive found on a real script joins the corpus.

15. Commits and releases

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:": incompatible

Types: 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.sh enables .githooks/ (core.hooksPath); the commit-msg hook rejects a non-conforming message (git commit --no-verify to bypass it).

  • CI check: .github/workflows/commits.yml checks the new commits of each push and pull request (tool/commit_lint.dart --range).

  • Release: dart run tool/release.dart --dry-run displays the next version and the CHANGELOG section; without --dry-run, it synchronizes the version files, writes the CHANGELOG, commits chore(release): X.Y.Z and tags vX.Y.Z (--push to 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.

16. Build and packaging

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.