[
  {
    "id": "1.1-no-files-outside-keyboard-folder",
    "section": "1. Repository hygiene",
    "band": "layer-c-enforce",
    "description": "No files modified outside the keyboard's own folder",
    "lintRuleId": "KM_LINT_FILES_OUTSIDE_FOLDER"
  },
  {
    "id": "1.2-keyboard-under-release-or-experimental",
    "section": "1. Repository hygiene",
    "band": "scaffolder-bake",
    "description": "Keyboard folder lives under `release/` or `experimental/`",
    "scaffolderRule": "place-under-release-or-experimental"
  },
  {
    "id": "1.2b-keyboard-under-release-or-experimental",
    "section": "1. Repository hygiene",
    "band": "layer-c-enforce",
    "description": "The keyboard folder lives under `release/` or `experimental/`.",
    "lintRuleId": "KM_LINT_REPO_PLACEMENT_BAD"
  },
  {
    "id": "1.3-keyboard-under-organizing-folder",
    "section": "1. Repository hygiene",
    "band": "scaffolder-bake",
    "description": "Keyboard sits under an alphabetic or org-code organizing subfolder, not at the root of `release/`/`experimental/`",
    "scaffolderRule": "place-under-organizing-subfolder"
  },
  {
    "id": "1.3b-keyboard-under-organizing-folder",
    "section": "1. Repository hygiene",
    "band": "layer-c-enforce",
    "description": "The keyboard sits under an organizing subfolder (`release/<org>/<id>`).",
    "lintRuleId": "KM_LINT_REPO_NO_ORGANIZING_FOLDER"
  },
  {
    "id": "1.4-no-source-files-at-repo-root",
    "section": "1. Repository hygiene",
    "band": "scaffolder-bake",
    "description": "No source files placed at the repository root",
    "scaffolderRule": "no-root-source-files"
  },
  {
    "id": "1.4b-no-source-files-at-repo-root",
    "section": "1. Repository hygiene",
    "band": "layer-c-enforce",
    "description": "No source files are placed at the repository root.",
    "lintRuleId": "KM_LINT_REPO_ROOT_SOURCE"
  },
  {
    "id": "1.5-keyboard-id-not-already-used",
    "section": "1. Repository hygiene",
    "band": "layer-c-enforce",
    "description": "The keyboard ID (folder name) is not already used in `experimental/`, `legacy/`, or `release/`",
    "lintRuleId": "KM_LINT_DUPLICATE_KEYBOARD_ID"
  },
  {
    "id": "1.6-id-does-not-contain-keyboard",
    "section": "1. Repository hygiene",
    "band": "layer-c-enforce",
    "description": "Keyboard ID, folder name, and display name do not contain the word \"keyboard\"",
    "lintRuleId": "KM_LINT_ID_CONTAINS_KEYBOARD"
  },
  {
    "id": "1.7-no-keyboard-info-file",
    "section": "1. Repository hygiene",
    "band": "scaffolder-bake",
    "description": "No `keyboard.info` file in the keyboard folder",
    "scaffolderRule": "omit-keyboard-info-file"
  },
  {
    "id": "1.7b-no-keyboard-info-file",
    "section": "1. Repository hygiene",
    "band": "layer-c-enforce",
    "description": "No `keyboard.info` file is present in the keyboard folder.",
    "lintRuleId": "KM_LINT_KEYBOARD_INFO_PRESENT"
  },
  {
    "id": "1.8-no-duplicate-docs-subfolder",
    "section": "1. Repository hygiene",
    "band": "scaffolder-bake",
    "description": "No `docs/` or similar subfolder containing duplicate HISTORY/README/INSTALL files",
    "scaffolderRule": "omit-docs-subfolder"
  },
  {
    "id": "1.8b-no-duplicate-docs-subfolder",
    "section": "1. Repository hygiene",
    "band": "layer-c-enforce",
    "description": "No `docs/` (or similar) subfolder holds duplicate HISTORY / README / INSTALL files.",
    "lintRuleId": "KM_LINT_DOCS_SUBFOLDER_DUPLICATES"
  },
  {
    "id": "1.9-no-duplicate-source-files",
    "section": "1. Repository hygiene",
    "band": "layer-c-enforce",
    "description": "No duplicate copies of the same source file inside the keyboard folder",
    "lintRuleId": "KM_LINT_DUPLICATE_SOURCE_FILES"
  },
  {
    "id": "1.10-kpj-current-format",
    "section": "1. Repository hygiene",
    "band": "scaffolder-bake",
    "description": "`.kpj` file uses the current/newest project-file format (the scaffolder template bakes the current `.kpj` schema version \u2014 the `<Options><Version>2.0</Version>` element form; re-pinned 2026-10-14 by Matthew's ruling: the earlier `version=\"10.0\"` note was a data defect, the keyboards corpus carries 2.0; bump this note when the format moves). Covers newly scaffolded keyboards only; an adapted keyboard's imported `.kpj` is not regenerated by the scaffolder \u2014 see 1.10b for that path.",
    "scaffolderRule": "set-kpj-current-format"
  },
  {
    "id": "1.10b-kpj-format-adapted",
    "section": "1. Repository hygiene",
    "band": "layer-c-enforce",
    "description": "The `.kpj` file uses the current project-file schema version.",
    "lintRuleId": "KM_LINT_KPJ_FORMAT_OUTDATED"
  },
  {
    "id": "1.11-no-kpj-user-committed",
    "section": "1. Repository hygiene",
    "band": "scaffolder-bake",
    "description": "No `.kpj.user` file committed",
    "scaffolderRule": "gitignore-kpj-user"
  },
  {
    "id": "1.11b-no-kpj-user-committed",
    "section": "1. Repository hygiene",
    "band": "layer-c-enforce",
    "description": "No `.kpj.user` file is committed.",
    "lintRuleId": "KM_LINT_KPJ_USER_PRESENT"
  },
  {
    "id": "1.12-no-build-folder-committed",
    "section": "1. Repository hygiene",
    "band": "scaffolder-bake",
    "description": "No `build/` folder contents committed",
    "scaffolderRule": "gitignore-build-folder"
  },
  {
    "id": "1.12b-no-build-folder-committed",
    "section": "1. Repository hygiene",
    "band": "layer-c-enforce",
    "description": "No `build/` folder contents are committed.",
    "lintRuleId": "KM_LINT_BUILD_FOLDER_PRESENT"
  },
  {
    "id": "1.13-no-stray-scratch-files",
    "section": "1. Repository hygiene",
    "band": "layer-c-enforce",
    "description": "No stray scratch or test files in the keyboard folder",
    "lintRuleId": "KM_LINT_STRAY_SCRATCH_FILES"
  },
  {
    "id": "2.1-version-bumped-if-package-changed",
    "section": "2. Version bumping",
    "band": "layer-c-enforce",
    "description": "If any file that ships in the package was modified, the keyboard version was bumped",
    "lintRuleId": "KM_LINT_VERSION_NOT_BUMPED"
  },
  {
    "id": "2.2-version-not-required-if-only-nonpackage",
    "section": "2. Version bumping",
    "band": "layer-c-enforce",
    "description": "If only non-package files were modified, a version bump is not required",
    "lintRuleId": "KM_LINT_SPURIOUS_VERSION_BUMP"
  },
  {
    "id": "2.3-version-numeric-dot-separated",
    "section": "2. Version bumping",
    "band": "layer-c-enforce",
    "description": "Version number is dot-separated and entirely numeric (SemVer or CalVer)",
    "lintRuleId": "KM_LINT_VERSION_FORMAT"
  },
  {
    "id": "2.4-no-leading-zero-in-version-component",
    "section": "2. Version bumping",
    "band": "layer-c-enforce",
    "description": "No version component has a leading zero",
    "lintRuleId": "KM_LINT_VERSION_LEADING_ZERO"
  },
  {
    "id": "2.5-no-v-prefix-or-labels",
    "section": "2. Version bumping",
    "band": "layer-c-enforce",
    "description": "Version has no `v` prefix and no non-numeric labels",
    "lintRuleId": "KM_LINT_VERSION_V_PREFIX"
  },
  {
    "id": "2.6-new-version-strictly-greater",
    "section": "2. Version bumping",
    "band": "layer-c-enforce",
    "description": "New version is strictly greater than the previously deployed version",
    "lintRuleId": "KM_LINT_VERSION_NOT_INCREMENTED"
  },
  {
    "id": "2.7-kpj-version-not-bumped",
    "section": "2. Version bumping",
    "band": "layer-c-enforce",
    "description": "The `.kpj` file's `<Version>` field has not been bumped (it's the file-format version, not keyboard version)",
    "lintRuleId": "KM_LINT_KPJ_VERSION_BUMPED"
  },
  {
    "id": "3.1-history-file-exists",
    "section": "3. `HISTORY.md`",
    "band": "scaffolder-bake",
    "description": "`HISTORY.md` file exists",
    "scaffolderRule": "create-history-md"
  },
  {
    "id": "3.1b-history-file-exists",
    "section": "3. `HISTORY.md`",
    "band": "layer-c-enforce",
    "description": "A `HISTORY.md` exists at the keyboard root.",
    "lintRuleId": "KM_LINT_HISTORY_MD_ABSENT"
  },
  {
    "id": "3.2-history-header-sensible",
    "section": "3. `HISTORY.md`",
    "band": "yellow-survey",
    "description": "HISTORY.md header is sensible",
    "surveyQuestionId": "history-header-sensible"
  },
  {
    "id": "3.3-history-recent-change-at-top",
    "section": "3. `HISTORY.md`",
    "band": "layer-c-enforce",
    "description": "Most recent change entry is at the top of the file",
    "lintRuleId": "KM_LINT_HISTORY_ORDER"
  },
  {
    "id": "3.4-history-cumulative",
    "section": "3. `HISTORY.md`",
    "band": "layer-c-enforce",
    "description": "HISTORY.md is cumulative \u2014 prior entries are preserved, not overwritten",
    "lintRuleId": "KM_LINT_HISTORY_TRUNCATED"
  },
  {
    "id": "3.5-history-entry-format",
    "section": "3. `HISTORY.md`",
    "band": "layer-c-enforce",
    "description": "Each entry follows `<version> (<YYYY-MM-DD>)` format underlined with hyphens and bullet items",
    "lintRuleId": "KM_LINT_HISTORY_ENTRY_FORMAT"
  },
  {
    "id": "3.6-history-version-matches-kmn",
    "section": "3. `HISTORY.md`",
    "band": "layer-c-enforce",
    "description": "Top HISTORY.md entry version matches the version in the `.kmn` file",
    "lintRuleId": "KM_LINT_HISTORY_VERSION_MISMATCH"
  },
  {
    "id": "3.7-history-bullets-no-deleted-files",
    "section": "3. `HISTORY.md`",
    "band": "layer-c-enforce",
    "description": "HISTORY.md bullets do not reference files that no longer exist in the keyboard",
    "lintRuleId": "KM_LINT_HISTORY_STALE_FILE_REFS"
  },
  {
    "id": "4.1-copyright-present-with-year",
    "section": "4. `LICENSE.md` (copyright)",
    "band": "scaffolder-bake",
    "description": "Copyright statement is present with year(s)",
    "scaffolderRule": "set-copyright-with-year"
  },
  {
    "id": "4.1b-copyright-present-with-year",
    "section": "4. `LICENSE.md` (copyright)",
    "band": "layer-c-enforce",
    "description": "A copyright statement with year(s) is present in LICENSE.md.",
    "lintRuleId": "KM_LINT_COPYRIGHT_ABSENT"
  },
  {
    "id": "4.2-copyright-year-range-if-multiyear",
    "section": "4. `LICENSE.md` (copyright)",
    "band": "yellow-survey",
    "description": "If updated across multiple years, copyright uses a year range",
    "surveyQuestionId": "copyright-year-range-multiyear"
  },
  {
    "id": "4.3-copyright-holder-is-authorized",
    "section": "4. `LICENSE.md` (copyright)",
    "band": "red-checklist",
    "description": "Copyright holder is the author or a legal entity they are authorized to claim",
    "preSubmitChecklistText": "The copyright holder named in LICENSE.md is the author or a legal entity the author is authorized to represent"
  },
  {
    "id": "4.4-org-legal-name-current",
    "section": "4. `LICENSE.md` (copyright)",
    "band": "yellow-survey",
    "description": "If an organization is named, its current legal name is used",
    "surveyQuestionId": "copyright-org-legal-name"
  },
  {
    "id": "4.5-copyright-syntax-exact",
    "section": "4. `LICENSE.md` (copyright)",
    "band": "scaffolder-bake",
    "description": "Copyright line follows exact syntax: `Copyright (C) <year> <holder>`",
    "scaffolderRule": "set-copyright-syntax"
  },
  {
    "id": "4.5b-copyright-syntax-exact",
    "section": "4. `LICENSE.md` (copyright)",
    "band": "layer-c-enforce",
    "description": "The LICENSE.md copyright line follows the `Copyright (C) <year> <holder>` syntax.",
    "lintRuleId": "KM_LINT_COPYRIGHT_SYNTAX"
  },
  {
    "id": "4.6-copyright-year-not-in-kmn-or-kps",
    "section": "4. `LICENSE.md` (copyright)",
    "band": "scaffolder-bake",
    "description": "Copyright year is not embedded in the `.kmn` or `.kps` copyright strings",
    "scaffolderRule": "strip-copyright-year-from-kmn-kps"
  },
  {
    "id": "4.6b-copyright-year-not-in-kmn-or-kps",
    "section": "4. `LICENSE.md` (copyright)",
    "band": "layer-c-enforce",
    "description": "No copyright year is embedded in the .kmn or .kps copyright strings.",
    "lintRuleId": "KM_LINT_COPYRIGHT_YEAR_EMBEDDED"
  },
  {
    "id": "4.7-copyright-holder-consistent-across-files",
    "section": "4. `LICENSE.md` (copyright)",
    "band": "layer-c-enforce",
    "description": "Copyright holder name is identical across `LICENSE.md`, `.kmn`, `.kps`, `README.md`, and `HISTORY.md`",
    "lintRuleId": "KM_LINT_COPYRIGHT_HOLDER_INCONSISTENT"
  },
  {
    "id": "4.8-year-range-starts-at-original",
    "section": "4. `LICENSE.md` (copyright)",
    "band": "yellow-survey",
    "description": "If a year range is used, it starts at the original year and extends to the current year",
    "surveyQuestionId": "copyright-year-range-starts-at-original"
  },
  {
    "id": "5.1-readme-good-header",
    "section": "5. `README.md`",
    "band": "yellow-survey",
    "description": "README.md has a good header",
    "surveyQuestionId": "readme-header-quality"
  },
  {
    "id": "5.2-no-version-in-readme-or-docs",
    "section": "5. `README.md`",
    "band": "scaffolder-bake",
    "description": "No version number embedded in README.md, readme.htm, welcome.htm, or help .php",
    "scaffolderRule": "strip-version-from-docs"
  },
  {
    "id": "5.2b-no-version-in-readme-or-docs",
    "section": "5. `README.md`",
    "band": "layer-c-enforce",
    "description": "No version number is embedded in README.md, readme.htm, welcome.htm, or the help .php (heuristic: any X.Y[.Z] pattern).",
    "lintRuleId": "KM_LINT_VERSION_IN_DOCS"
  },
  {
    "id": "5.3-no-copyright-in-readme-or-docs",
    "section": "5. `README.md`",
    "band": "scaffolder-bake",
    "description": "No copyright year/statement in README.md, readme.htm, or welcome.htm",
    "scaffolderRule": "strip-copyright-from-docs"
  },
  {
    "id": "5.3b-no-copyright-in-readme-or-docs",
    "section": "5. `README.md`",
    "band": "layer-c-enforce",
    "description": "No copyright year/statement is in README.md, readme.htm, or welcome.htm.",
    "lintRuleId": "KM_LINT_COPYRIGHT_IN_DOCS"
  },
  {
    "id": "5.4-readme-description-sensible",
    "section": "5. `README.md`",
    "band": "yellow-survey",
    "description": "README.md description makes sense",
    "surveyQuestionId": "readme-description-quality"
  },
  {
    "id": "5.5-readme-keyman-home-link",
    "section": "5. `README.md`",
    "band": "scaffolder-bake",
    "description": "README.md includes link to `https://keyman.com/keyboards/<file_name>`",
    "scaffolderRule": "set-readme-keyman-home-link"
  },
  {
    "id": "5.5b-readme-keyman-home-link",
    "section": "5. `README.md`",
    "band": "layer-c-enforce",
    "description": "README.md links to https://keyman.com/keyboards/<id>.",
    "lintRuleId": "KM_LINT_README_KEYMAN_LINK_ABSENT"
  },
  {
    "id": "5.6-readme-help-link",
    "section": "5. `README.md`",
    "band": "scaffolder-bake",
    "description": "README.md includes link to `http://help.keyman.com/keyboard/<file_name>`",
    "scaffolderRule": "set-readme-help-link"
  },
  {
    "id": "5.6b-readme-help-link",
    "section": "5. `README.md`",
    "band": "layer-c-enforce",
    "description": "README.md links to http(s)://help.keyman.com/keyboard/<id>.",
    "lintRuleId": "KM_LINT_README_HELP_LINK_ABSENT"
  },
  {
    "id": "5.7-readme-targets-match-kmn",
    "section": "5. `README.md`",
    "band": "layer-c-enforce",
    "description": "Targets listed in README.md match those in the `.kmn`",
    "lintRuleId": "KM_LINT_README_TARGETS_MISMATCH"
  },
  {
    "id": "6.1-readme-htm-exists",
    "section": "6. Documentation files (`source/`)",
    "band": "scaffolder-bake",
    "description": "`readme.htm` exists in `source/`",
    "scaffolderRule": "create-readme-htm"
  },
  {
    "id": "6.1b-readme-htm-exists",
    "section": "6. Documentation files (`source/`)",
    "band": "layer-c-enforce",
    "description": "`readme.htm` exists in `source/`.",
    "lintRuleId": "KM_LINT_README_HTM_ABSENT"
  },
  {
    "id": "6.2-readme-htm-short",
    "section": "6. Documentation files (`source/`)",
    "band": "yellow-survey",
    "description": "`readme.htm` content is short \u2014 just a brief description",
    "surveyQuestionId": "readme-htm-length-appropriate"
  },
  {
    "id": "6.3-welcome-htm-exists",
    "section": "6. Documentation files (`source/`)",
    "band": "scaffolder-bake",
    "description": "`welcome.htm` exists in `source/`",
    "scaffolderRule": "create-welcome-htm"
  },
  {
    "id": "6.3b-welcome-htm-exists",
    "section": "6. Documentation files (`source/`)",
    "band": "layer-c-enforce",
    "description": "`welcome.htm` exists in `source/` (flat or the `source/welcome/` folder convention).",
    "lintRuleId": "KM_LINT_WELCOME_HTM_ABSENT"
  },
  {
    "id": "6.4-welcome-htm-describes-usage",
    "section": "6. Documentation files (`source/`)",
    "band": "yellow-survey",
    "description": "`welcome.htm` describes how to use the keyboard",
    "surveyQuestionId": "welcome-htm-describes-usage"
  },
  {
    "id": "6.5-welcome-htm-no-install-instructions",
    "section": "6. Documentation files (`source/`)",
    "band": "yellow-survey",
    "description": "`welcome.htm` does not include instructions for installing Keyman \u2014 references help URLs instead",
    "surveyQuestionId": "welcome-htm-no-install-instructions"
  },
  {
    "id": "6.6-welcome-htm-lang-attribute",
    "section": "6. Documentation files (`source/`)",
    "band": "scaffolder-bake",
    "description": "`<html lang=\"...\">` attribute in `welcome.htm` matches the keyboard's primary BCP47 tag",
    "scaffolderRule": "set-welcome-htm-lang-attribute"
  },
  {
    "id": "6.6b-welcome-htm-lang-attribute",
    "section": "6. Documentation files (`source/`)",
    "band": "layer-c-enforce",
    "description": "The welcome.htm <html lang> attribute matches the keyboard's primary BCP47 tag.",
    "lintRuleId": "KM_LINT_WELCOME_LANG_MISMATCH"
  },
  {
    "id": "6.7-welcome-htm-not-political",
    "section": "6. Documentation files (`source/`)",
    "band": "yellow-survey",
    "description": "`welcome.htm` content is not political or self-promoting",
    "surveyQuestionId": "welcome-htm-not-political"
  },
  {
    "id": "7.1-kmn-version-matches-history",
    "section": "7. Keyboard source (`.kpj` \u2192 Keyboards tab \u2192 `.kmn`)",
    "band": "layer-c-enforce",
    "description": "`.kmn` version matches `HISTORY.md` top entry",
    "lintRuleId": "KM_LINT_KMN_VERSION_MISMATCH"
  },
  {
    "id": "7.2-targets-any-alone",
    "section": "7. Keyboard source (`.kpj` \u2192 Keyboards tab \u2192 `.kmn`)",
    "band": "layer-c-enforce",
    "description": "`.kmn` targets line uses `any` alone, not combined with individual platform names",
    "lintRuleId": "KM_LINT_TARGETS_ANY_NOT_ALONE"
  },
  {
    "id": "7.3-targets-no-unsupported-mobile",
    "section": "7. Keyboard source (`.kpj` \u2192 Keyboards tab \u2192 `.kmn`)",
    "band": "layer-c-enforce",
    "description": "`.kmn` targets do not include `mobile` unless a touch layout exists",
    "lintRuleId": "KM_LINT_TARGETS_MOBILE_WITHOUT_TOUCH"
  },
  {
    "id": "7.4-modifier-names-consistent",
    "section": "7. Keyboard source (`.kpj` \u2192 Keyboards tab \u2192 `.kmn`)",
    "band": "layer-c-enforce",
    "description": "Modifier key names consistent across `.kmn`, `.kvks`, and `.keyman-touch-layout`",
    "lintRuleId": "KM_LINT_MODIFIER_NAME_INCONSISTENT"
  },
  {
    "id": "7.5-ralt-usealtgr-tag",
    "section": "7. Keyboard source (`.kpj` \u2192 Keyboards tab \u2192 `.kmn`)",
    "band": "layer-c-enforce",
    "description": "If keyboard uses RALT, the `.kvks` includes the `usealtgr` tag",
    "lintRuleId": "KM_LINT_RALT_MISSING_USEALTGR"
  },
  {
    "id": "7.6-compiles-no-errors-no-warnings",
    "section": "7. Keyboard source (`.kpj` \u2192 Keyboards tab \u2192 `.kmn`)",
    "band": "layer-c-enforce",
    "description": "Keyboard compiles with no errors and no warnings (source review pass; stricter than 7.21 which checks errors only)",
    "lintRuleId": "KM_LINT_COMPILE_HAS_WARNINGS"
  },
  {
    "id": "7.7-display-name-no-underscores",
    "section": "7. Keyboard source (`.kpj` \u2192 Keyboards tab \u2192 `.kmn`)",
    "band": "yellow-survey",
    "description": "Keyboard display name is a readable phrase that describes the keyboard (judgment half; the mechanical underscore check is 7.7a)",
    "surveyQuestionId": "display-name-readable"
  },
  {
    "id": "7.7a-display-name-no-underscores-lint",
    "section": "7. Keyboard source (`.kpj` \u2192 Keyboards tab \u2192 `.kmn`)",
    "band": "layer-c-enforce",
    "description": "Keyboard display name contains no literal underscore characters (mechanical half of 7.7; the readability judgment stays with 7.7)",
    "lintRuleId": "KM_LINT_DISPLAY_NAME_UNDERSCORE"
  },
  {
    "id": "7.8-rtl-flag-matches-script",
    "section": "7. Keyboard source (`.kpj` \u2192 Keyboards tab \u2192 `.kmn`)",
    "band": "yellow-survey",
    "description": "RTL flag in `.kmn`/`.kps` matches the script's actual writing direction",
    "surveyQuestionId": "rtl-flag-matches-script"
  },
  {
    "id": "7.9-bitmap-not-template-placeholder",
    "section": "7. Keyboard source (`.kpj` \u2192 Keyboards tab \u2192 `.kmn`)",
    "band": "layer-c-enforce",
    "description": "`store(&BITMAP)` references the actual keyboard `.ico`, not a placeholder like `qaa.ico`",
    "lintRuleId": "KM_LINT_BITMAP_PLACEHOLDER"
  },
  {
    "id": "7.10-kmn-stores-balanced-syntax",
    "section": "7. Keyboard source (`.kpj` \u2192 Keyboards tab \u2192 `.kmn`)",
    "band": "layer-c-enforce",
    "description": "All `.kmn` string-literal stores have balanced quotes and valid syntax",
    "lintRuleId": "KM_LINT_STORE_SYNTAX_INVALID"
  },
  {
    "id": "7.11-author-in-kps-not-kmn",
    "section": "7. Keyboard source (`.kpj` \u2192 Keyboards tab \u2192 `.kmn`)",
    "band": "scaffolder-bake",
    "description": "Author name belongs in the `.kps`, not in a `.kmn` Author store",
    "scaffolderRule": "move-author-from-kmn-to-kps"
  },
  {
    "id": "7.11b-author-in-kps-not-kmn",
    "section": "7. Keyboard source (`.kpj` \u2192 Keyboards tab \u2192 `.kmn`)",
    "band": "layer-c-enforce",
    "description": "The author name is in the .kps, not in a .kmn Author store.",
    "lintRuleId": "KM_LINT_AUTHOR_IN_KMN"
  },
  {
    "id": "7.12-pua-in-experimental",
    "section": "7. Keyboard source (`.kpj` \u2192 Keyboards tab \u2192 `.kmn`)",
    "band": "layer-c-enforce",
    "description": "If keyboard uses PUA codepoints, it is in `experimental/` (source review pass; see also 13.1 which checks the same condition from the encoding/script angle)",
    "lintRuleId": "KM_LINT_PUA_NOT_IN_EXPERIMENTAL"
  },
  {
    "id": "7.13-pua-documentation",
    "section": "7. Keyboard source (`.kpj` \u2192 Keyboards tab \u2192 `.kmn`)",
    "band": "yellow-survey",
    "description": "If keyboard uses PUA codepoints, documentation explicitly states characters/script are not in Unicode",
    "surveyQuestionId": "pua-documentation-present"
  },
  {
    "id": "7.14-pua-font-displaymap",
    "section": "7. Keyboard source (`.kpj` \u2192 Keyboards tab \u2192 `.kmn`)",
    "band": "layer-c-enforce",
    "description": "If keyboard ships a PUA-encoded font, `.kmn` includes `store(&DISPLAYMAP)` path",
    "lintRuleId": "KM_LINT_PUA_FONT_NO_DISPLAYMAP"
  },
  {
    "id": "7.15-osk-reviewed",
    "section": "7. Keyboard source (`.kpj` \u2192 Keyboards tab \u2192 `.kmn`)",
    "band": "yellow-survey",
    "description": "OSK has been reviewed for rules, output characters, and layout consistency",
    "surveyQuestionId": "osk-reviewed"
  },
  {
    "id": "7.16-kvks-not-blank",
    "section": "7. Keyboard source (`.kpj` \u2192 Keyboards tab \u2192 `.kmn`)",
    "band": "layer-c-enforce",
    "description": "The `.kvks` on-screen keyboard layout is populated, not blank",
    "lintRuleId": "KM_LINT_KVKS_BLANK"
  },
  {
    "id": "7.17-osk-font-selected",
    "section": "7. Keyboard source (`.kpj` \u2192 Keyboards tab \u2192 `.kmn`)",
    "band": "yellow-survey",
    "description": "If a special font is needed for output characters, an appropriate font is selected in the OSK",
    "surveyQuestionId": "osk-font-appropriate"
  },
  {
    "id": "7.18-touch-layout-reviewed",
    "section": "7. Keyboard source (`.kpj` \u2192 Keyboards tab \u2192 `.kmn`)",
    "band": "yellow-survey",
    "description": "Touch layout reviewed for layers, output, and fingering",
    "surveyQuestionId": "touch-layout-reviewed"
  },
  {
    "id": "7.19-identical-phone-tablet-deduplicated",
    "section": "7. Keyboard source (`.kpj` \u2192 Keyboards tab \u2192 `.kmn`)",
    "band": "layer-c-enforce",
    "description": "If both phone and tablet touch layouts are identical, removal of one was requested",
    "lintRuleId": "KM_LINT_REDUNDANT_TOUCH_LAYOUTS"
  },
  {
    "id": "7.20-same-font-phone-tablet",
    "section": "7. Keyboard source (`.kpj` \u2192 Keyboards tab \u2192 `.kmn`)",
    "band": "layer-c-enforce",
    "description": "Same font is selected for phone and tablet layouts",
    "lintRuleId": "KM_LINT_TOUCH_LAYOUT_FONT_MISMATCH"
  },
  {
    "id": "7.21-compiles-no-errors",
    "section": "7. Keyboard source (`.kpj` \u2192 Keyboards tab \u2192 `.kmn`)",
    "band": "layer-c-enforce",
    "description": "Keyboard compiles with no errors (build-output context; errors only, not warnings \u2014 see 7.6 for the stricter source-review check)",
    "lintRuleId": "KM_LINT_COMPILE_ERRORS"
  },
  {
    "id": "8.1-kps-includes-kmx",
    "section": "8. Package contents (`.kps` \u2192 Packaging tab \u2192 Files)",
    "band": "scaffolder-bake",
    "description": "Compiled `.kmx` is listed in the `.kps` package",
    "scaffolderRule": "kps-include-kmx"
  },
  {
    "id": "8.1b-kps-includes-kmx",
    "section": "8. Package contents (`.kps` \u2192 Packaging tab \u2192 Files)",
    "band": "layer-c-enforce",
    "description": "A compiled `.kmx` is listed in the `.kps` package.",
    "lintRuleId": "KM_LINT_KPS_KMX_ABSENT"
  },
  {
    "id": "8.2-kps-includes-kvk",
    "section": "8. Package contents (`.kps` \u2192 Packaging tab \u2192 Files)",
    "band": "scaffolder-bake",
    "description": "Compiled `.kvk` is listed in the `.kps` package",
    "scaffolderRule": "kps-include-kvk"
  },
  {
    "id": "8.2b-kps-includes-kvk",
    "section": "8. Package contents (`.kps` \u2192 Packaging tab \u2192 Files)",
    "band": "layer-c-enforce",
    "description": "A compiled `.kvk` is listed in the `.kps` package.",
    "lintRuleId": "KM_LINT_KPS_KVK_ABSENT"
  },
  {
    "id": "8.3-kps-includes-js-if-mobile",
    "section": "8. Package contents (`.kps` \u2192 Packaging tab \u2192 Files)",
    "band": "scaffolder-bake",
    "description": "Compiled `.js` is listed if a mobile layout exists",
    "scaffolderRule": "kps-include-js-if-mobile"
  },
  {
    "id": "8.3b-kps-includes-js-if-mobile",
    "section": "8. Package contents (`.kps` \u2192 Packaging tab \u2192 Files)",
    "band": "layer-c-enforce",
    "description": "A compiled `.js` is listed in the `.kps` package when the keyboard has a touch layout.",
    "lintRuleId": "KM_LINT_KPS_JS_ABSENT"
  },
  {
    "id": "8.4-kps-includes-license-md",
    "section": "8. Package contents (`.kps` \u2192 Packaging tab \u2192 Files)",
    "band": "scaffolder-bake",
    "description": "`LICENSE.md` is listed in the `.kps` package",
    "scaffolderRule": "kps-include-license-md"
  },
  {
    "id": "8.4b-kps-includes-license-md",
    "section": "8. Package contents (`.kps` \u2192 Packaging tab \u2192 Files)",
    "band": "layer-c-enforce",
    "description": "`LICENSE.md` is listed in the `.kps` package.",
    "lintRuleId": "KM_LINT_KPS_LICENSE_ABSENT"
  },
  {
    "id": "8.5-kps-includes-readme-htm",
    "section": "8. Package contents (`.kps` \u2192 Packaging tab \u2192 Files)",
    "band": "scaffolder-bake",
    "description": "`readme.htm` is listed in the `.kps` package",
    "scaffolderRule": "kps-include-readme-htm"
  },
  {
    "id": "8.5b-kps-includes-readme-htm",
    "section": "8. Package contents (`.kps` \u2192 Packaging tab \u2192 Files)",
    "band": "layer-c-enforce",
    "description": "`readme.htm` is listed in the `.kps` package.",
    "lintRuleId": "KM_LINT_KPS_README_ABSENT"
  },
  {
    "id": "8.6-kps-includes-welcome-and-graphics",
    "section": "8. Package contents (`.kps` \u2192 Packaging tab \u2192 Files)",
    "band": "scaffolder-bake",
    "description": "`welcome.htm` and associated graphics are listed in the `.kps` package",
    "scaffolderRule": "kps-include-welcome-and-graphics"
  },
  {
    "id": "8.6b-kps-includes-welcome-and-graphics",
    "section": "8. Package contents (`.kps` \u2192 Packaging tab \u2192 Files)",
    "band": "layer-c-enforce",
    "description": "`welcome.htm` and associated graphics are listed in the `.kps` package.",
    "lintRuleId": "KM_LINT_KPS_WELCOME_ABSENT"
  },
  {
    "id": "8.7-kps-includes-required-fonts",
    "section": "8. Package contents (`.kps` \u2192 Packaging tab \u2192 Files)",
    "band": "layer-c-enforce",
    "description": "All required fonts referenced by the `.kmn` and touch layout are listed in the `.kps` (family-level presence check; full typeface \u2014 Bold/Italic \u2014 completeness is best-effort, not mechanically guaranteed)",
    "lintRuleId": "KM_LINT_KPS_FONT_MISSING"
  },
  {
    "id": "8.8-kps-no-php-file",
    "section": "8. Package contents (`.kps` \u2192 Packaging tab \u2192 Files)",
    "band": "scaffolder-bake",
    "description": "No `.php` help file listed in the `.kps` package",
    "scaffolderRule": "kps-exclude-php-file"
  },
  {
    "id": "8.8b-kps-no-php-file",
    "section": "8. Package contents (`.kps` \u2192 Packaging tab \u2192 Files)",
    "band": "layer-c-enforce",
    "description": "No `.php` help file is listed in the `.kps` package.",
    "lintRuleId": "KM_LINT_KPS_PHP_LISTED"
  },
  {
    "id": "8.9-kps-no-help-folder-files",
    "section": "8. Package contents (`.kps` \u2192 Packaging tab \u2192 Files)",
    "band": "scaffolder-bake",
    "description": "No files from the `help/` folder listed in the `.kps` package",
    "scaffolderRule": "kps-exclude-help-folder"
  },
  {
    "id": "8.9b-kps-no-help-folder-files",
    "section": "8. Package contents (`.kps` \u2192 Packaging tab \u2192 Files)",
    "band": "layer-c-enforce",
    "description": "No files from the `help/` folder are listed in the `.kps` package.",
    "lintRuleId": "KM_LINT_KPS_HELP_LISTED"
  },
  {
    "id": "8.10-kps-no-uncompiled-sources",
    "section": "8. Package contents (`.kps` \u2192 Packaging tab \u2192 Files)",
    "band": "scaffolder-bake",
    "description": "No uncompiled sources (.kmn, .ico, .kvks, .keyman-touch-layout, .kps) listed in the `.kps`",
    "scaffolderRule": "kps-exclude-uncompiled-sources"
  },
  {
    "id": "8.10b-kps-no-uncompiled-sources",
    "section": "8. Package contents (`.kps` \u2192 Packaging tab \u2192 Files)",
    "band": "layer-c-enforce",
    "description": "No uncompiled sources (.kmn, .ico, .kvks, .keyman-touch-layout, .kps) are listed in the `.kps`.",
    "lintRuleId": "KM_LINT_KPS_SOURCE_LISTED"
  },
  {
    "id": "8.11-kps-no-displaymap-json",
    "section": "8. Package contents (`.kps` \u2192 Packaging tab \u2192 Files)",
    "band": "scaffolder-bake",
    "description": "No font display-map `.json` files listed in the `.kps`",
    "scaffolderRule": "kps-exclude-displaymap-json"
  },
  {
    "id": "8.11b-kps-no-displaymap-json",
    "section": "8. Package contents (`.kps` \u2192 Packaging tab \u2192 Files)",
    "band": "layer-c-enforce",
    "description": "No font display-map `.json` files are listed in the `.kps`.",
    "lintRuleId": "KM_LINT_KPS_JSON_LISTED"
  },
  {
    "id": "8.12-kps-no-industry-fonts",
    "section": "8. Package contents (`.kps` \u2192 Packaging tab \u2192 Files)",
    "band": "layer-c-enforce",
    "description": "No industry fonts (Arial, Times, Tahoma, etc.) in the package",
    "lintRuleId": "KM_LINT_KPS_INDUSTRY_FONT"
  },
  {
    "id": "8.13-kps-targets-match-compiled",
    "section": "8. Package contents (`.kps` \u2192 Packaging tab \u2192 Files)",
    "band": "layer-c-enforce",
    "description": "Targets declared in `.kmn` match compiled files listed in the `.kps`",
    "lintRuleId": "KM_LINT_KPS_TARGETS_MISMATCH"
  },
  {
    "id": "9.1-font-license-nonempty",
    "section": "9. Font licensing",
    "band": "layer-c-enforce",
    "description": "Every bundled font has a non-empty license field in the `.kps`",
    "lintRuleId": "KM_LINT_FONT_LICENSE_EMPTY"
  },
  {
    "id": "9.2-font-license-open-identifier",
    "section": "9. Font licensing",
    "band": "layer-c-enforce",
    "description": "Font license identifier matches a known Open license (OFL, GPL, MIT, Apache, CC-BY, etc.)",
    "lintRuleId": "KM_LINT_FONT_LICENSE_NOT_OPEN"
  },
  {
    "id": "9.3-font-license-genuinely-open",
    "section": "9. Font licensing",
    "band": "yellow-survey",
    "description": "Font license text/spirit is genuinely Open, not just labeled \"freeware\"",
    "surveyQuestionId": "font-license-genuinely-open"
  },
  {
    "id": "9.4-shared-font-in-shared-folder",
    "section": "9. Font licensing",
    "band": "layer-c-enforce",
    "description": "If a font is used by more than one keyboard, it lives under `shared/fonts`",
    "lintRuleId": "KM_LINT_SHARED_FONT_WRONG_LOCATION"
  },
  {
    "id": "9.5-shared-font-current-path",
    "section": "9. Font licensing",
    "band": "red-checklist",
    "description": "Shared font path points to the current/newest shared-fonts location, not a deprecated duplicate",
    "preSubmitChecklistText": "I have verified the shared font path points to the current shared/fonts location in keymanapp/keyboards, not a deprecated duplicate."
  },
  {
    "id": "9.6-no-woff-fonts",
    "section": "9. Font licensing",
    "band": "layer-c-enforce",
    "description": "No `.woff` or web-only font formats in keyboard packages",
    "lintRuleId": "KM_LINT_WOFF_FONT_IN_PACKAGE"
  },
  {
    "id": "9.7-pua-font-submitted-to-skeyman",
    "section": "9. Font licensing",
    "band": "yellow-survey",
    "description": "If an experimental keyboard ships a PUA font, the font has been submitted to `s.keyman.com`",
    "surveyQuestionId": "pua-font-submitted-to-skeyman"
  },
  {
    "id": "10.1-kps-font-selected",
    "section": "10. Package metadata (`.kps`)",
    "band": "scaffolder-bake",
    "description": "Font is selected in the `.kps` Keyboards tab if a font is included in Files",
    "scaffolderRule": "kps-select-font"
  },
  {
    "id": "10.1b-kps-font-selected",
    "section": "10. Package metadata (`.kps`)",
    "band": "layer-c-enforce",
    "description": "A font is selected in the .kps Keyboards tab when a font is included in the package files.",
    "lintRuleId": "KM_LINT_KPS_FONT_UNSELECTED"
  },
  {
    "id": "10.2-kps-language-tags-selected",
    "section": "10. Package metadata (`.kps`)",
    "band": "scaffolder-bake",
    "description": "Language tag(s) are selected in the `.kps` Keyboards tab",
    "scaffolderRule": "kps-select-language-tags"
  },
  {
    "id": "10.2b-kps-language-tags-selected",
    "section": "10. Package metadata (`.kps`)",
    "band": "layer-c-enforce",
    "description": "Language tag(s) are selected in the .kps Keyboards tab.",
    "lintRuleId": "KM_LINT_KPS_LANGUAGES_UNSELECTED"
  },
  {
    "id": "10.3-kps-tags-minimal",
    "section": "10. Package metadata (`.kps`)",
    "band": "yellow-survey",
    "description": "Language tags are minimal \u2014 no unnecessary script/region subtags",
    "surveyQuestionId": "lang-tags-minimal"
  },
  {
    "id": "10.4-kps-unusual-tag-flagged",
    "section": "10. Package metadata (`.kps`)",
    "band": "yellow-survey",
    "description": "If the tag is new or unusual, it is flagged for possible submission to langtags/Ethnologue",
    "surveyQuestionId": "unusual-lang-tag-flagged"
  },
  {
    "id": "10.5-kps-conlang-flagged",
    "section": "10. Package metadata (`.kps`)",
    "band": "yellow-survey",
    "description": "If keyboard is for a constructed language, it is flagged for special review",
    "surveyQuestionId": "conlang-flagged-for-review"
  },
  {
    "id": "10.6-kps-numeric-region-flagged",
    "section": "10. Package metadata (`.kps`)",
    "band": "layer-c-enforce",
    "description": "If the language tag includes numeric regional codes, it is flagged for special review",
    "lintRuleId": "KM_LINT_KPS_NUMERIC_REGION_TAG"
  },
  {
    "id": "10.7-kps-details-files-selected",
    "section": "10. Package metadata (`.kps`)",
    "band": "scaffolder-bake",
    "description": "`readme.htm`, `welcome.htm`, and `LICENSE.md` are selected in the `.kps` Details tab License field",
    "scaffolderRule": "kps-select-details-files"
  },
  {
    "id": "10.7b-kps-details-files-selected",
    "section": "10. Package metadata (`.kps`)",
    "band": "layer-c-enforce",
    "description": "Readme.htm, welcome.htm, and LICENSE.md are selected in the .kps Details tab.",
    "lintRuleId": "KM_LINT_KPS_DETAILS_UNSELECTED"
  },
  {
    "id": "10.8-kps-follow-keyboard-version",
    "section": "10. Package metadata (`.kps`)",
    "band": "scaffolder-bake",
    "description": "\"Follow keyboard version\" is selected in the `.kps` Details tab",
    "scaffolderRule": "kps-set-follow-keyboard-version"
  },
  {
    "id": "10.8b-kps-follow-keyboard-version",
    "section": "10. Package metadata (`.kps`)",
    "band": "layer-c-enforce",
    "description": "\"Follow keyboard version\" is selected in the .kps Details tab.",
    "lintRuleId": "KM_LINT_KPS_FOLLOW_VERSION_UNSET"
  },
  {
    "id": "10.9-kps-description-nonempty",
    "section": "10. Package metadata (`.kps`)",
    "band": "scaffolder-bake",
    "description": "Description field is present and non-empty in the `.kps`",
    "scaffolderRule": "kps-set-description"
  },
  {
    "id": "10.9b-kps-description-nonempty",
    "section": "10. Package metadata (`.kps`)",
    "band": "layer-c-enforce",
    "description": "The .kps description is present and non-empty.",
    "lintRuleId": "KM_LINT_KPS_DESCRIPTION_EMPTY"
  },
  {
    "id": "10.10-kps-description-meaningful",
    "section": "10. Package metadata (`.kps`)",
    "band": "yellow-survey",
    "description": "Description is meaningful (becomes the keyboard home page content)",
    "surveyQuestionId": "kps-description-meaningful"
  },
  {
    "id": "10.11-kps-no-spelling-errors",
    "section": "10. Package metadata (`.kps`)",
    "band": "yellow-survey",
    "description": "No spelling errors in user-facing `.kps` text fields (Description, Author name, WebSite text)",
    "surveyQuestionId": "kps-text-no-spelling-errors"
  },
  {
    "id": "11.1-php-exists-with-header",
    "section": "11. Online help (`source/help/<name>.php`)",
    "band": "scaffolder-bake",
    "description": "`<name>.php` file exists and has the proper PHP header",
    "scaffolderRule": "create-php-with-header"
  },
  {
    "id": "11.1b-php-exists-with-header",
    "section": "11. Online help (`source/help/<name>.php`)",
    "band": "layer-c-enforce",
    "description": "The <name>.php help file exists and has the proper PHP header (<?php + the /* Name: ... */ block).",
    "lintRuleId": "KM_LINT_PHP_HEADER_BAD"
  },
  {
    "id": "11.2-php-osk-all-layers",
    "section": "11. Online help (`source/help/<name>.php`)",
    "band": "layer-c-enforce",
    "description": "If using OSK syntax, all desktop and mobile layers are listed in `data-states` (complementary to 11.6 \u2014 see that entry for the direction split)",
    "lintRuleId": "KM_LINT_PHP_OSK_LAYERS_INCOMPLETE"
  },
  {
    "id": "11.3-php-style-inside-header",
    "section": "11. Online help (`source/help/<name>.php`)",
    "band": "scaffolder-bake",
    "description": "All style information is inside the PHP header, not outside it",
    "scaffolderRule": "php-move-style-to-header"
  },
  {
    "id": "11.3b-php-style-inside-header",
    "section": "11. Online help (`source/help/<name>.php`)",
    "band": "layer-c-enforce",
    "description": "All style information (<style> tags) is inside the PHP header block, not outside it.",
    "lintRuleId": "KM_LINT_PHP_STYLE_OUTSIDE"
  },
  {
    "id": "11.4-php-no-closing-body-html",
    "section": "11. Online help (`source/help/<name>.php`)",
    "band": "scaffolder-bake",
    "description": "`.php` does not contain closing `</body>` or `</html>` tags",
    "scaffolderRule": "php-strip-closing-tags"
  },
  {
    "id": "11.4b-php-no-closing-body-html",
    "section": "11. Online help (`source/help/<name>.php`)",
    "band": "layer-c-enforce",
    "description": "The .php has no closing </body> or </html> tags.",
    "lintRuleId": "KM_LINT_PHP_CLOSING_TAGS"
  },
  {
    "id": "11.5-php-htm-html-wellformed",
    "section": "11. Online help (`source/help/<name>.php`)",
    "band": "layer-c-enforce",
    "description": "HTML in `welcome.htm` and `.php` is well-formed \u2014 balanced tags, proper headings, no unclosed elements (in-browser check via DOMParser)",
    "lintRuleId": "KM_LINT_HTML_NOT_WELL_FORMED"
  },
  {
    "id": "11.6-php-osk-data-states-all-layers",
    "section": "11. Online help (`source/help/<name>.php`)",
    "band": "layer-c-enforce",
    "description": "`.php` `data-states` attribute lists every layer defined in the `.kmn` and touch layout (complementary to 11.2: 11.2 catches layers missing from `data-states` \u2014 under-documentation; 11.6 catches `data-states` values naming layers that don't exist \u2014 phantom layers)",
    "lintRuleId": "KM_LINT_PHP_DATA_STATES_INCOMPLETE"
  },
  {
    "id": "11.7-php-pagename-format",
    "section": "11. Online help (`source/help/<name>.php`)",
    "band": "layer-c-enforce",
    "description": "`.php` `pagename` follows the standard help-site format",
    "lintRuleId": "KM_LINT_PHP_PAGENAME_FORMAT"
  },
  {
    "id": "11.8-php-osk-image-constrained",
    "section": "11. Online help (`source/help/<name>.php`)",
    "band": "layer-c-enforce",
    "description": "OSK graphics either use Keyman-generated form or custom images have width constraints",
    "lintRuleId": "KM_LINT_PHP_OSK_IMAGE_UNCONSTRAINED"
  },
  {
    "id": "11.9-php-htm-body-parity",
    "section": "11. Online help (`source/help/<name>.php`)",
    "band": "layer-c-enforce",
    "description": "`welcome.htm` and `.php` body content are identical after stripping headers and normalizing whitespace",
    "lintRuleId": "KM_LINT_PHP_HTM_BODY_MISMATCH"
  },
  {
    "id": "11.10-php-htm-style-parity",
    "section": "11. Online help (`source/help/<name>.php`)",
    "band": "layer-c-enforce",
    "description": "CSS in `welcome.htm` and `.php` are identical modulo non-rendering whitespace",
    "lintRuleId": "KM_LINT_PHP_HTM_STYLE_MISMATCH"
  },
  {
    "id": "12.1-bcp47-tag-correct",
    "section": "12. Language tagging (BCP47)",
    "band": "yellow-survey",
    "description": "BCP47 tag is correct for the language and script",
    "surveyQuestionId": "bcp47-tag-correct"
  },
  {
    "id": "12.2-no-iso-639-5-codes",
    "section": "12. Language tagging (BCP47)",
    "band": "layer-c-enforce",
    "description": "No ISO 639-5 collective language codes used",
    "lintRuleId": "KM_LINT_ISO_639_5_LANG_CODE"
  },
  {
    "id": "12.3-no-numeric-region-unless-contrastive",
    "section": "12. Language tagging (BCP47)",
    "band": "layer-c-enforce",
    "description": "No numeric regional codes unless explicitly contrastive",
    "lintRuleId": "KM_LINT_NUMERIC_REGION_NON_CONTRASTIVE"
  },
  {
    "id": "12.4-no-001-region",
    "section": "12. Language tagging (BCP47)",
    "band": "layer-c-enforce",
    "description": "Region code `001` is never used in BCP47 tags",
    "lintRuleId": "KM_LINT_REGION_001_TAG"
  },
  {
    "id": "12.5-script-mismatch-flagged",
    "section": "12. Language tagging (BCP47)",
    "band": "yellow-survey",
    "description": "If a language uses a different script than what langtags currently records, the mismatch is flagged",
    "surveyQuestionId": "script-mismatch-flagged"
  },
  {
    "id": "12.6-script-mismatch-submitted-to-langtags",
    "section": "12. Language tagging (BCP47)",
    "band": "red-checklist",
    "description": "A flagged script mismatch has been submitted to langtags",
    "preSubmitChecklistText": "If a script mismatch was flagged, I have opened an issue at github.com/silnrsi/langtags documenting the mismatch."
  },
  {
    "id": "13.1-pua-keyboard-in-experimental",
    "section": "13. Encoding / script issues",
    "band": "layer-c-enforce",
    "description": "PUA codepoint use means keyboard is in `experimental/` (encoding/script context; see also 7.12 which checks the same condition during source review)",
    "lintRuleId": "KM_LINT_PUA_KEYBOARD_PLACEMENT"
  },
  {
    "id": "13.2-pua-notices-in-docs",
    "section": "13. Encoding / script issues",
    "band": "yellow-survey",
    "description": "PUA codepoint use means explicit PUA notices appear in readme.htm, welcome.htm, .php, and .kps description",
    "surveyQuestionId": "pua-notices-in-docs"
  },
  {
    "id": "13.3-no-unencoded-script-codepoint-reuse",
    "section": "13. Encoding / script issues",
    "band": "yellow-survey",
    "description": "Keyboard does not reuse existing codepoints for unencoded scripts; if it does, warnings are in place",
    "surveyQuestionId": "unencoded-script-codepoint-reuse"
  },
  {
    "id": "13.4-no-math-latin-lookalikes",
    "section": "13. Encoding / script issues",
    "band": "layer-c-enforce",
    "description": "No mathematical Latin look-alike characters (U+1D400-U+1D7FF) in keyboard output",
    "lintRuleId": "KM_LINT_MATH_LATIN_LOOKALIKE"
  },
  {
    "id": "13.5-math-latin-not-spoofing",
    "section": "13. Encoding / script issues",
    "band": "yellow-survey",
    "description": "If math-Latin or other Latin look-alikes are present, the use case does not look like spoofing",
    "surveyQuestionId": "math-latin-not-spoofing"
  },
  {
    "id": "14.1-third-party-patch-author-consulted",
    "section": "14. Authorship / change provenance",
    "band": "red-checklist",
    "description": "If a third party submits a patch to an existing keyboard, the original author was consulted and approved",
    "preSubmitChecklistText": "If this is a patch to another author's keyboard, the original author was consulted and has approved the change before this PR was opened"
  },
  {
    "id": "15.1-relicense-permission-granted",
    "section": "15. Deprecating a keyboard",
    "band": "red-checklist",
    "description": "Original author has granted permission to relicense under MIT",
    "preSubmitChecklistText": "The original author has confirmed permission to relicense this keyboard under MIT \u2014 written confirmation is referenced in this PR or on file"
  },
  {
    "id": "15.2-new-keyboard-created-in-release",
    "section": "15. Deprecating a keyboard",
    "band": "layer-c-enforce",
    "description": "New keyboard has been created in `release/` (renamed if needed)",
    "lintRuleId": "KM_LINT_DEPRECATED_RELEASE_COPY_MISSING"
  },
  {
    "id": "15.3-legacy-listed-as-deprecated-in-kps",
    "section": "15. Deprecating a keyboard",
    "band": "layer-c-enforce",
    "description": "Legacy package ID is listed in the new keyboard's `.kps` Related packages as Deprecated",
    "lintRuleId": "KM_LINT_DEPRECATED_KPS_LINKAGE_MISSING"
  },
  {
    "id": "15.4-deprecated-md-created",
    "section": "15. Deprecating a keyboard",
    "band": "layer-c-enforce",
    "description": "A `DEPRECATED.md` file exists in the legacy keyboard's folder naming the replacement",
    "lintRuleId": "KM_LINT_DEPRECATED_MD_MISSING"
  },
  {
    "id": "15.5-php-title-includes-deprecated",
    "section": "15. Deprecating a keyboard",
    "band": "layer-c-enforce",
    "description": "The deprecated keyboard's help `.php` title includes \"(deprecated)\"",
    "lintRuleId": "KM_LINT_DEPRECATED_PHP_TITLE_MISSING"
  },
  {
    "id": "16.1-partner-notes-followed",
    "section": "16. Partner-organization packages",
    "band": "yellow-survey",
    "description": "Partner package's \"note for keyboard maintainers\" has been followed",
    "surveyQuestionId": "partner-notes-followed"
  },
  {
    "id": "16.2-partner-keyboards-csv-updated",
    "section": "16. Partner-organization packages",
    "band": "layer-c-enforce",
    "description": "Keyboard version has been updated in the partner's `keyboards.csv` under `oem/`",
    "lintRuleId": "KM_LINT_OEM_CSV_NOT_UPDATED"
  },
  {
    "id": "17.1-build-trigger-pr-comments-resolved",
    "section": "17. Build trigger",
    "band": "red-checklist",
    "description": "Author has resolved all PR comments before triggering a TeamCity build",
    "preSubmitChecklistText": "I have resolved all PR comments before triggering a TeamCity build."
  },
  {
    "id": "17.2-new-author-local-pull",
    "section": "17. Build trigger",
    "band": "red-checklist",
    "description": "For a new author's first PR, the PR was pulled locally and checked thoroughly",
    "preSubmitChecklistText": "For a first-time contributor: this PR has been pulled locally and the build verified clean before triggering TeamCity"
  },
  {
    "id": "17.3-build-issues-fed-back",
    "section": "17. Build trigger",
    "band": "red-checklist",
    "description": "After the build runs, any issues it catches that local review missed were fed back to the author",
    "preSubmitChecklistText": "Any issues the TeamCity build caught that local review missed have been fed back to the author."
  },
  {
    "id": "18.1-longpress-within-limit",
    "section": "18. Design heuristics (DISCUS)",
    "band": "layer-c-enforce",
    "principle": "simplicity",
    "lintRuleId": "KM_WARN_LONGPRESS_OVERSIZE",
    "description": "No touch long-press menu offers more than 8 options (warning), and none exceeds the hard cap of 10"
  },
  {
    "id": "18.2-touch-rows-within-range",
    "section": "18. Design heuristics (DISCUS)",
    "band": "layer-c-enforce",
    "principle": "usability",
    "lintRuleId": "KM_WARN_TOUCH_ROW_COUNT",
    "description": "Touch layout uses 4-5 rows on phone and 5 rows on tablet"
  },
  {
    "id": "18.3-keys-per-row-within-range",
    "section": "18. Design heuristics (DISCUS)",
    "band": "layer-c-enforce",
    "principle": "usability",
    "lintRuleId": "KM_WARN_TOUCH_KEYS_PER_ROW",
    "description": "Touch layout uses at most 10 keys per row on phone and 13 on tablet"
  },
  {
    "id": "18.4-control-keys-stable-across-layers",
    "section": "18. Design heuristics (DISCUS)",
    "band": "layer-c-enforce",
    "principle": "usability",
    "lintRuleId": "KM_WARN_CONTROL_KEY_DRIFT",
    "description": "Backspace, Enter, and other control keys do not move or resize across touch layers"
  },
  {
    "id": "18.5-layer-switch-toggles-back",
    "section": "18. Design heuristics (DISCUS)",
    "band": "layer-c-enforce",
    "principle": "usability",
    "lintRuleId": "KM_WARN_LAYER_SWITCH_NO_RETURN",
    "description": "Every layer-switch key has a corresponding key that toggles back to the previous layer"
  },
  {
    "id": "18.6-inventory-fully-covered",
    "section": "18. Design heuristics (DISCUS)",
    "band": "layer-c-enforce",
    "principle": "discoverability",
    "lintRuleId": "KM_LINT_INVENTORY_UNCOVERED",
    "description": "Every character in the confirmed linguist inventory is produced by some reachable input sequence in the draft keyboard"
  },
  {
    "id": "18.7-mandated-currency-present",
    "section": "18. Design heuristics (DISCUS)",
    "band": "layer-c-enforce",
    "principle": "standards",
    "lintRuleId": "KM_LINT_MANDATED_CHAR_MISSING",
    "description": "The locale's mandated currency symbol (from CLDR) is reachable on the keyboard"
  },
  {
    "id": "18.8-character-set-is-minimal",
    "section": "18. Design heuristics (DISCUS)",
    "band": "yellow-survey",
    "principle": "simplicity",
    "surveyQuestionId": "discus-minimal-charset",
    "description": "The character set was kept focused - extra characters were resisted and each addition was deliberate"
  },
  {
    "id": "18.9-rare-chars-discoverable",
    "section": "18. Design heuristics (DISCUS)",
    "band": "yellow-survey",
    "principle": "discoverability",
    "surveyQuestionId": "discus-rare-chars-discoverable",
    "description": "Rare characters are findable without experimentation, not buried on deep long-press menus alone"
  },
  {
    "id": "18.10-frequent-chars-reachable",
    "section": "18. Design heuristics (DISCUS)",
    "band": "yellow-survey",
    "principle": "consistency",
    "surveyQuestionId": "discus-frequent-chars-reachable",
    "description": "Frequently used characters sit on easy-to-reach positions consistent with the script's structure"
  },
  {
    "id": "18.11-tested-with-real-users",
    "section": "18. Design heuristics (DISCUS)",
    "band": "red-checklist",
    "principle": "usability",
    "preSubmitChecklistText": "This layout has been tested with target users - good on paper does not equal good in practice",
    "description": "The layout has been tested with real target users before submission"
  },
  {
    "id": "18.12-meets-legal-accessibility-requirements",
    "section": "18. Design heuristics (DISCUS)",
    "band": "red-checklist",
    "principle": "standards",
    "preSubmitChecklistText": "This keyboard complies with any legislated or accessibility requirements for its target locale \u2014 if you are unsure whether requirements apply, check before submitting.",
    "description": "The keyboard meets any applicable legislated, societal, and accessibility requirements for its locale"
  },
  {
    "id": "19.1-import-attribution-in-pr-body",
    "section": "19. Import output",
    "band": "scaffolder-bake",
    "scaffolderRule": "emit-import-attribution-block",
    "description": "When a keyboard was adapted from a release/ keyboard, the PR body must include the attribution block (adapted-from path, commit SHA, round-trip status, opaque feature inventory). Auto-enforced by the output service at PR submission time."
  },
  {
    "id": "19.1b-import-attribution-in-pr-body",
    "section": "19. Import output",
    "band": "layer-c-enforce",
    "description": "The PR body includes the import attribution block.",
    "lintRuleId": "KM_LINT_PR_ATTRIBUTION_BAD"
  },
  {
    "id": "19.2-import-attribution-in-history-md",
    "section": "19. Import output",
    "band": "scaffolder-bake",
    "scaffolderRule": "emit-import-attribution-history-bullet",
    "description": "When a keyboard was adapted from a release/ keyboard, HISTORY.md must contain an 'Adapted from <sourcePath>' bullet under the 1.0 entry. Auto-enforced by the scaffolder at emit time (D14, mandatory carrier)."
  },
  {
    "id": "19.2b-import-attribution-in-history-md",
    "section": "19. Import output",
    "band": "layer-c-enforce",
    "description": "HISTORY.md contains the 'Adapted from' entry.",
    "lintRuleId": "KM_LINT_HISTORY_ATTRIBUTION_BAD"
  }
]
