///|
pub(all) struct RuleSpec {
id : String
kind : CheckKind
default_severity : Severity
title : String
evidence_hint : String
repair_hint : String
penalty : Int
} derive(Eq, Debug)
///|
fn rule_spec(
id : String,
kind : CheckKind,
default_severity : Severity,
title : String,
evidence_hint : String,
repair_hint : String,
penalty : Int,
) -> RuleSpec {
{ id, kind, default_severity, title, evidence_hint, repair_hint, penalty }
}
///|
pub fn rule_catalog() -> Array[RuleSpec] {
[
rule_spec(
"moon.mod.name",
PackageConfig,
Fail,
"moon.mod declares package name",
"name = \"/\"",
"Keep moon.mod name identical to Mooncakes owner/package.",
12,
),
rule_spec(
"moon.mod.name-format",
PackageConfig,
Fail,
"moon.mod package name is publishable",
"URL-safe owner and package slug",
"Use owner/package with letters, numbers, dash or underscore.",
8,
),
rule_spec(
"moon.mod.version",
PackageConfig,
Fail,
"Version is SemVer",
"x.y.z",
"Use a concrete release version such as 0.1.0.",
8,
),
rule_spec(
"moon.mod.readme",
PackageConfig,
Warn,
"Readme metadata uses README.md",
"readme = \"README.md\"",
"Point moon.mod to the root README.md file.",
4,
),
rule_spec(
"moon.mod.repository",
PackageConfig,
Fail,
"Repository URL is public and complete",
"https://github.com//.git",
"Set a public GitHub repository URL.",
10,
),
rule_spec(
"moon.mod.license",
License,
Fail,
"License is OSI-recognized",
"MIT, Apache-2.0, BSD, MPL-2.0 or ISC",
"Use an OSI-recognized SPDX license identifier.",
10,
),
rule_spec(
"moon.mod.description",
PackageConfig,
Warn,
"Description explains the package",
"description length >= 20",
"Write one clear sentence about the reusable capability.",
4,
),
rule_spec(
"mooncakes.name-sync",
Mooncakes,
Fail,
"Mooncakes owner/package match moon.mod",
"snapshot owner/package equals moon.mod name",
"Keep Mooncakes, README and moon.mod names identical.",
10,
),
rule_spec(
"readme.exists",
Readme,
Fail,
"README is present",
"README.md content",
"Add a complete README at repository root.",
14,
),
rule_spec(
"readme.identity",
Readme,
Warn,
"README identifies the project",
"project or package name appears near the top",
"Mention the project name and Mooncakes package name.",
3,
),
rule_spec(
"readme.problem",
Readme,
Warn,
"Problem statement",
"problem, why, usage, purpose or Chinese equivalents",
"Explain what problem the package solves.",
3,
),
rule_spec(
"readme.install",
Readme,
Warn,
"Installation",
"moon add or install section",
"Show the Mooncakes install command.",
3,
),
rule_spec(
"readme.usage",
Readme,
Warn,
"Minimum usage example",
"example or moon run command",
"Provide a copyable minimum example.",
3,
),
rule_spec(
"readme.api",
Readme,
Warn,
"Core API list",
"API or feature list",
"List public functions and the data model.",
3,
),
rule_spec(
"readme.scope",
Readme,
Warn,
"Supported scope",
"supported scope",
"State what the package supports.",
3,
),
rule_spec(
"readme.out-of-scope",
Readme,
Warn,
"Unsupported scope",
"out of scope",
"State what the package does not support.",
3,
),
rule_spec(
"readme.tests",
Readme,
Warn,
"Verification commands",
"moon check, moon build, moon test",
"Document all local verification commands.",
3,
),
rule_spec(
"readme.license",
Readme,
Warn,
"License and third-party notes",
"license and third-party notes",
"Explain license and third-party code or asset usage.",
3,
),
rule_spec(
"ci.exists",
ContinuousIntegration,
Fail,
"GitHub Actions workflow exists",
".github/workflows/*.yml",
"Add a CI workflow.",
14,
),
rule_spec(
"ci.install",
ContinuousIntegration,
Fail,
"Installs MoonBit",
"cli.moonbitlang installer",
"Install MoonBit in CI before running moon commands.",
7,
),
rule_spec(
"ci.check",
ContinuousIntegration,
Fail,
"Runs moon check",
"moon check",
"Add moon check to CI.",
7,
),
rule_spec(
"ci.build",
ContinuousIntegration,
Fail,
"Runs moon build",
"moon build",
"Add moon build to CI.",
7,
),
rule_spec(
"ci.test",
ContinuousIntegration,
Fail,
"Runs moon test",
"moon test",
"Add moon test to CI.",
7,
),
rule_spec(
"ci.example",
ContinuousIntegration,
Fail,
"Runs an example",
"moon run examples/basic or moon run cmd/main",
"Run at least one example in CI.",
7,
),
rule_spec(
"tests.exists",
Tests,
Fail,
"Tests are present",
"*_test.mbt or *_wbtest.mbt evidence",
"Add tests for core behavior.",
14,
),
rule_spec(
"tests.coverage-shape",
Tests,
Warn,
"Tests cover core paths",
"valid, invalid, boundary and export evidence",
"Cover normal input, invalid input, boundary input and export output.",
6,
),
rule_spec(
"examples.exists",
Examples,
Fail,
"Runnable example is present",
"examples/basic",
"Add a runnable example package.",
12,
),
rule_spec(
"commands.verify",
Build,
Fail,
"Verification commands include check/build/test",
"moon check, moon build, moon test",
"Keep verification commands documented and runnable.",
10,
),
rule_spec(
"git.repository-public",
GitTrace,
Fail,
"Repository is public",
"public repository signal",
"Make the GitHub repository public before final submission.",
12,
),
rule_spec(
"git.commit-count",
GitTrace,
Warn,
"Development history has at least 6 commits",
"commit_count >= 6",
"Keep meaningful commits for implementation, tests, docs and release prep.",
5,
),
rule_spec(
"mooncakes.published",
Mooncakes,
Fail,
"Package is published to Mooncakes",
"published package signal",
"Run moon login, moon publish --dry-run and moon publish.",
14,
),
rule_spec(
"license.file",
License,
Fail,
"License file is present",
"LICENSE",
"Add the full OSI license text.",
10,
),
rule_spec(
"maint.changelog",
Boundary,
Warn,
"Changelog or release notes exist",
"CHANGELOG.md",
"Add a changelog with the public release notes.",
3,
),
rule_spec(
"maint.trace-notes",
Boundary,
Warn,
"Design and issue notes are retained",
"docs/design.md and docs/issues.md",
"Keep design notes and issue records.",
4,
),
]
}
///|
pub fn rules_for_kind(kind : CheckKind) -> Array[RuleSpec] {
let out = Array::new(capacity=8)
for spec in rule_catalog() {
if spec.kind == kind {
out.push(spec)
}
}
out
}
///|
pub fn find_rule(id : String) -> RuleSpec? {
for spec in rule_catalog() {
if spec.id == id {
return Some(spec)
}
}
None
}
///|
pub fn rule_catalog_markdown() -> String {
let out = StringBuilder()
out.write_string("# HarborCheck Rule Catalog\n\n")
for spec in rule_catalog() {
out.write_string("- `" + spec.id + "` ")
out.write_string("(" + kind_text(spec.kind) + ", ")
out.write_string(severity_text(spec.default_severity) + "): ")
out.write_string(spec.title + ". ")
out.write_string("Evidence: " + spec.evidence_hint + ". ")
out.write_string("Repair: " + spec.repair_hint + "\n")
}
out.to_string()
}
///|
pub fn catalog_size() -> Int {
rule_catalog().length()
}