geneseed 3.17.0 → 3.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +17 -3
  3. package/SHIPPED.md +3 -3
  4. package/adapters/claude-code/skills/geneseed/SKILL.md +1 -1
  5. package/adapters/opencode/README.md +2 -2
  6. package/adapters/opencode/plugins/geneseed-context.js +19 -17
  7. package/adapters/opencode/plugins/geneseed-guard.js +7 -7
  8. package/bin/geneseed-cli.mjs +5 -0
  9. package/docs/concepts/exclusions.md +1 -1
  10. package/docs/concepts/skills.md +4 -0
  11. package/docs/extending.md +33 -0
  12. package/docs/guides/read-non-markdown.md +1 -1
  13. package/docs/guides/uninstall.md +1 -1
  14. package/docs/guides/upgrade.md +1 -1
  15. package/docs/guides/wiki.md +4 -4
  16. package/docs/opencode-plugin-setup.md +1 -1
  17. package/docs/reference/env-context.md +2 -2
  18. package/docs/reference/plugin-context.md +2 -2
  19. package/docs/reference/plugin-guard.md +1 -1
  20. package/docs/token-footprint.md +18 -15
  21. package/docs/understand/take-it-out.md +1 -1
  22. package/harness.config.json +1 -1
  23. package/js/README.md +4 -1
  24. package/js/build/args.mjs +22 -20
  25. package/js/build/bundle.mjs +1 -3
  26. package/js/build/emit-claude.mjs +5 -4
  27. package/js/build/emit-opencode.mjs +9 -2
  28. package/js/build/generate.mjs +6 -6
  29. package/js/build/render.mjs +19 -12
  30. package/js/build/source.mjs +82 -7
  31. package/js/build/stubs.mjs +35 -6
  32. package/js/build/themes.mjs +1 -3
  33. package/js/cli-table.json +53 -0
  34. package/js/hosts/hooks-context.mjs +13 -6
  35. package/js/hosts/hosts.mjs +1 -3
  36. package/js/hosts/installs.mjs +13 -7
  37. package/js/hosts/mcp.mjs +1 -1
  38. package/js/hosts/native.mjs +60 -5
  39. package/js/hosts/opencode.mjs +37 -6
  40. package/js/hosts/settings.mjs +1 -0
  41. package/js/hosts/shim.mjs +1 -0
  42. package/js/inspect/checks-authoring.mjs +4 -1
  43. package/js/inspect/checks-repo.mjs +164 -2
  44. package/js/inspect/diff.mjs +5 -1
  45. package/js/inspect/doctor.mjs +2 -1
  46. package/js/inspect/inventory.mjs +7 -9
  47. package/js/inspect/scorecard-checks.mjs +571 -0
  48. package/js/inspect/scorecard-svg.mjs +85 -0
  49. package/js/inspect/scorecard.mjs +839 -0
  50. package/js/lib/json.mjs +1 -1
  51. package/js/maintain/migrate.mjs +2 -1
  52. package/js/maintain/uninstall.mjs +2 -3
  53. package/js/maintain/update.mjs +10 -3
  54. package/js/ui/menu.mjs +2 -2
  55. package/js/ui/tui.mjs +1 -0
  56. package/js/web/api.mjs +2 -0
  57. package/js/web/catalog.mjs +24 -7
  58. package/js/web/daemon.mjs +2 -1
  59. package/js/web/routes.mjs +2 -0
  60. package/js/web/server.mjs +1 -1
  61. package/js/web/user-files.mjs +1 -0
  62. package/package.json +11 -3
  63. package/registry.json +12 -75
  64. package/src/AGENT.md.tmpl +13 -22
  65. package/src/agents/docs.md +1 -1
  66. package/src/agents/researcher.md +1 -1
  67. package/src/bricks/bruno-test.md +2 -2
  68. package/src/bricks/ci-fix.md +3 -3
  69. package/src/bricks/deps-audit.md +3 -3
  70. package/src/bricks/migration-plan.md +2 -2
  71. package/src/doctrines/process.md +2 -2
  72. package/src/laws/universal.md +2 -2
  73. package/src/skills/brainstorm.md +14 -10
  74. package/src/skills/bruno.md +84 -0
  75. package/src/skills/codebase-design.md +41 -23
  76. package/src/skills/daydream/SKILL.md +3 -3
  77. package/src/skills/daydream/VENDOR.md +2 -2
  78. package/src/skills/daydream/instructions.md +1 -1
  79. package/src/skills/debug/ci.md +11 -0
  80. package/src/skills/debug.md +11 -8
  81. package/src/skills/dependencies.md +65 -0
  82. package/src/skills/develop.md +9 -4
  83. package/src/skills/document-project.md +27 -22
  84. package/src/skills/geneseed-code-review.md +13 -8
  85. package/src/skills/geneseed.md +4 -1
  86. package/src/skills/git-history.md +78 -0
  87. package/src/skills/handoff.md +4 -3
  88. package/src/skills/parallel-agents.md +1 -0
  89. package/src/skills/plan.md +5 -1
  90. package/src/skills/prototype.md +10 -7
  91. package/src/skills/research.md +6 -1
  92. package/src/skills/rule/consolidate.md +32 -0
  93. package/src/skills/rule.md +14 -4
  94. package/src/skills/security-audit.md +1 -1
  95. package/src/skills/ship.md +8 -5
  96. package/src/skills/teach.md +5 -3
  97. package/src/skills/wayfinder.md +19 -16
  98. package/src/skills/wiki.md +2 -2
  99. package/themes/_TEMPLATE.json +7 -16
  100. package/themes/biker.json +7 -16
  101. package/themes/commentator.json +7 -16
  102. package/themes/cyberpunk.json +7 -16
  103. package/themes/gamer.json +7 -16
  104. package/themes/imperial.json +7 -16
  105. package/themes/joker.json +7 -16
  106. package/themes/marvin.json +7 -16
  107. package/themes/mean.json +7 -16
  108. package/themes/military.json +7 -16
  109. package/themes/neutral.json +7 -16
  110. package/themes/pirate.json +7 -16
  111. package/themes/sports.json +7 -16
  112. package/themes/verstappen.json +7 -16
  113. package/themes/wizard.json +7 -16
  114. package/web/dist/assets/{Activity-DonKN1pi.js → Activity-4M4VQJOh.js} +1 -1
  115. package/web/dist/assets/{ActivityDetail-e47a3cdE.js → ActivityDetail-CnmDs43F.js} +1 -1
  116. package/web/dist/assets/{Dashboard-Cr7i0PsO.js → Dashboard-lTaH58R9.js} +2 -2
  117. package/web/dist/assets/{Docs-Lne3nuzD.js → Docs-DU5znu5f.js} +1 -1
  118. package/web/dist/assets/FilterInput-BSfpStOZ.js +1 -0
  119. package/web/dist/assets/{GreenhouseView-4CbIuTpl.js → GreenhouseView-BxO84bKX.js} +1 -1
  120. package/web/dist/assets/{Installs-CIzbV1Ks.js → Installs-B1WcObxK.js} +1 -1
  121. package/web/dist/assets/{JournalView-BlBfuvzj.js → JournalView-0YNKcaaD.js} +1 -1
  122. package/web/dist/assets/{Laws-B-pjcjHr.js → Laws-DnDpRwpE.js} +1 -1
  123. package/web/dist/assets/Library-DBkKsBMe.js +1 -0
  124. package/web/dist/assets/{Loops-DIOxW1xH.js → Loops-P-6RCdzS.js} +1 -1
  125. package/web/dist/assets/Markdown-C6fRd4A4.js +67 -0
  126. package/web/dist/assets/{OperatorHudView-D7S7z7CJ.js → OperatorHudView-BZZ3uJPH.js} +1 -1
  127. package/web/dist/assets/{Personal-Ws3v35AB.js → Personal-BoVw6OCW.js} +1 -1
  128. package/web/dist/assets/{Tabs-6Z1eziAq.js → Tabs-VYLhUTiX.js} +1 -1
  129. package/web/dist/assets/{activity-Dr7ZMBI3.js → activity-dcg5vF9j.js} +1 -1
  130. package/web/dist/assets/{drawable-ATNeYKbT.js → drawable-CdNARIKC.js} +1 -1
  131. package/web/dist/assets/{index-CDg5XUhG.js → index-hsabpYzv.js} +2 -2
  132. package/web/dist/assets/react-DRhoX0kS.js +9 -0
  133. package/web/dist/assets/{usePoll-DD8VjtSg.js → usePoll-BmR631BB.js} +1 -1
  134. package/web/dist/index.html +2 -2
  135. package/src/skills/bruno-collection-generator.md +0 -52
  136. package/src/skills/bruno-test-writer.md +0 -51
  137. package/src/skills/ci-fix.md +0 -49
  138. package/src/skills/consolidate-memory.md +0 -50
  139. package/src/skills/deps-audit.md +0 -46
  140. package/src/skills/domain-modeling.md +0 -27
  141. package/src/skills/git-archaeology.md +0 -36
  142. package/src/skills/git-rescue.md +0 -58
  143. package/src/skills/migrate.md +0 -34
  144. package/src/skills/opencode-theme.md +0 -42
  145. package/src/skills/repo-map.md +0 -25
  146. package/src/skills/tickets.md +0 -33
  147. package/web/dist/assets/FilterInput-CVyqmYWM.js +0 -1
  148. package/web/dist/assets/Library-x8Nzg6yq.js +0 -1
  149. package/web/dist/assets/Markdown-X2a3qvOh.js +0 -38
  150. package/web/dist/assets/react-B9Z_tXRV.js +0 -8
@@ -0,0 +1,571 @@
1
+ /**
2
+ * The scorecard's 31 checks — a port of upstream's `checks/*.py`.
3
+ *
4
+ * UPSTREAM: markmishaev76/ai-harness-scorecard, pinned at commit
5
+ * 5536e96227dc8278301eaff3f4ca3faaf13cf278, MIT License, "Copyright (c) 2026 Mark Mishaev".
6
+ *
7
+ * Ported line for line, in upstream's order: the order is the scoring (a check returns on its
8
+ * FIRST matching arm, so reordering two arms changes which partial score a repo gets). Every
9
+ * pattern is upstream's Python raw string as a `String.raw` — compiled with `new RegExp` by the
10
+ * context, and printed as written in evidence, never as `.source` (which escapes `/`).
11
+ *
12
+ * Every language branch stays, Rust and Go included. Nothing Node-native is added (`node --test`
13
+ * is not a test runner here): parity with upstream comes first, and its score is a floor.
14
+ */
15
+
16
+ const R = String.raw;
17
+
18
+ /** `BaseCheck` — the check's identity plus the three result shapes. */
19
+ function check(id, name, maxPoints, source, run) {
20
+ const base = { id, name, maxPoints, source };
21
+ const res = (passed, score, evidence, remediation) => ({
22
+ ...base, passed, score, evidence, remediation,
23
+ });
24
+ return {
25
+ ...base,
26
+ run: (ctx) => run(ctx, {
27
+ pass: (evidence) => res(true, maxPoints, evidence, ''),
28
+ fail: (evidence, remediation) => res(false, 0, evidence, remediation),
29
+ // `passed = score > 0`: a 0.0 partial is a fail that still carries its remediation.
30
+ partial: (score, evidence, remediation = '') => res(score > 0, Math.min(score, maxPoints), evidence, remediation),
31
+ }),
32
+ };
33
+ }
34
+
35
+ // ---------------------------------------------------------------------------------------------
36
+ // documentation.py — Architectural Documentation (20%)
37
+ // ---------------------------------------------------------------------------------------------
38
+
39
+ const MODULE_CONSTRAINT_PATTERNS = [
40
+ R`never\s+depend`, R`must\s+not\s+depend`, R`does\s+not\s+(import|depend)`,
41
+ R`must\s+not\s+import`, R`no\s+dependency\s+on`, R`independent\s+of`, R`zero.dependency`,
42
+ ];
43
+
44
+ const DOCUMENTATION_CHECKS = [
45
+ check('architecture_doc', 'Architecture Documentation', 5.0, 'matklad ARCHITECTURE.md guide', (ctx, r) => {
46
+ const found = ctx.hasFile('architecture.md', 'architecture', 'docs/architecture.md',
47
+ 'docs/architecture/*.md', 'doc/architecture.md');
48
+ if (found) return r.pass(`Found: ${found}`);
49
+ return r.fail('No architecture documentation found',
50
+ "Create ARCHITECTURE.md at repo root following matklad's pattern: "
51
+ + 'short, stable, focused on module boundaries and constraints.');
52
+ }),
53
+ check('agent_instructions', 'Agent Instructions', 5.0, 'OpenAI Harness Engineering (2026)', (ctx, r) => {
54
+ const found = ctx.hasFile('claude.md', 'agents.md', '.cursorrules',
55
+ '.github/copilot-instructions.md', 'copilot.md');
56
+ if (found) return r.pass(`Found: ${found}`);
57
+ if (ctx.hasDir('.cursor/rules')) return r.pass('Found: .cursor/rules/ directory');
58
+ return r.fail('No AI agent instruction files found',
59
+ 'Create CLAUDE.md or AGENTS.md with project context, code style, '
60
+ + 'and constraints so AI agents produce consistent output.');
61
+ }),
62
+ check('adr_presence', 'Architecture Decision Records', 3.0, 'DORA 2025 Report - AI-accessible documentation', (ctx, r) => {
63
+ const dir = ctx.hasDir('docs/adr', 'docs/decisions', 'docs/ADR', 'adr', 'doc/adr', 'doc/decisions');
64
+ if (dir) return r.pass(`Found ADR directory: ${dir}`);
65
+ const found = ctx.hasFile('docs/adr-*.md', 'docs/decisions/*.md', 'docs/000*.md');
66
+ if (found) return r.pass(`Found ADR-like file: ${found}`);
67
+ return r.fail('No Architecture Decision Records found',
68
+ 'Create docs/adr/ directory with numbered markdown decision records. '
69
+ + 'Use adr-tools or a simple template.');
70
+ }),
71
+ check('module_boundary_docs', 'Module Boundary Documentation', 4.0, 'matklad ARCHITECTURE.md - constraints as absences', (ctx, r) => {
72
+ const files = ['architecture.md', 'docs/architecture.md', 'claude.md', 'agents.md', 'readme.md', 'docs/*.md'];
73
+ for (const p of MODULE_CONSTRAINT_PATTERNS) {
74
+ const found = ctx.searchAnyFile(files, p);
75
+ if (found) return r.pass(`Module boundary constraints found in ${found}`);
76
+ }
77
+ return r.fail('No module boundary constraints documented',
78
+ 'Document which modules must NOT depend on each other in ARCHITECTURE.md. '
79
+ + "Example: 'The fields crate never depends on any other workspace crate.'");
80
+ }),
81
+ check('api_contracts', 'API Documentation', 3.0, 'DORA 2025 - AI-accessible documentation', (ctx, r) => {
82
+ if (ctx.ciHasCommand(R`cargo\s+doc|rustdoc|typedoc|jsdoc|sphinx|mkdocs|pdoc|javadoc|godoc|swag|dokka`)) {
83
+ return r.pass('Doc generation found in CI');
84
+ }
85
+ const spec = ctx.hasFile('openapi.yaml', 'openapi.json', 'openapi.yml', 'swagger.yaml',
86
+ 'swagger.json', 'docs/openapi*.yaml', 'docs/openapi*.json', 'docs/openapi*.yml',
87
+ 'api-docs/*.yaml', 'api-docs/*.json');
88
+ if (spec) return r.pass(`API spec found: ${spec}`);
89
+ for (const dep of ['build.gradle', 'build.gradle.kts', '*/build.gradle', '*/build.gradle.kts']) {
90
+ if (ctx.searchAnyFile([dep], R`springdoc-openapi|springfox`)) {
91
+ return r.pass(`Runtime API documentation library found in ${dep}`);
92
+ }
93
+ }
94
+ const conf = ctx.hasFile('mkdocs.yml', 'docs/conf.py', 'typedoc.json', 'jsdoc.json');
95
+ if (conf) return r.pass(`Doc generation config found: ${conf}`);
96
+ return r.fail('No API documentation generation or spec files found',
97
+ 'Add doc generation to CI (cargo doc, typedoc, sphinx) '
98
+ + 'or maintain OpenAPI/Swagger specs.');
99
+ }),
100
+ ];
101
+
102
+ // ---------------------------------------------------------------------------------------------
103
+ // constraints.py — Mechanical Constraints (25%)
104
+ // ---------------------------------------------------------------------------------------------
105
+
106
+ const JAVA_BUILD_FILES = ['build.gradle', '*/build.gradle', 'build.gradle.kts', '*/build.gradle.kts'];
107
+
108
+ const LINTER_PATTERNS = [
109
+ R`cargo\s+clippy`, R`eslint`, R`flake8`, R`pylint`, R`ruff\s+(check|\.)`, R`rubocop`,
110
+ R`golangci-lint`, R`mypy`, R`pyright`, R`ktlint`, R`swiftlint`, R`checkstyle`,
111
+ R`\bpmd:(check|aggregate-pmd-check)\b`,
112
+ R`\b(?:\./)?gradlew?\b.*\b(pmdMain|pmdTest)\b`,
113
+ R`\bcom\.github\.spotbugs:spotbugs-maven-plugin:(check|spotbugs)\b`,
114
+ R`\bspotbugs:(check|spotbugs)\b`,
115
+ R`\b(?:\./)?gradlew?\b.*\bspotbugs(?:[A-Z]\w*)\b`,
116
+ ];
117
+
118
+ const GRADLE_CHECK_PATTERN = R`\b(?:\./)?gradlew?\b.*\bcheck\b`;
119
+
120
+ const PMD_GRADLE = R`id\("pmd"\)|id\s+[\'"]pmd[\'"]|apply\s+plugin:\s*[\'"]pmd[\'"]|pmd\s*\{`;
121
+ const SPOTBUGS_GRADLE = R`id\("com\.github\.spotbugs"\)|id\s+[\'"]com\.github\.spotbugs[\'"]|`
122
+ + R`apply\s+plugin:\s*[\'"]com\.github\.spotbugs[\'"]|spotbugs\s*\{`;
123
+
124
+ /** `_has_java_linter_config` with `has_gradle_config` always supplied, as both callers do. */
125
+ function javaLinterConfig(ctx, configFiles, pomPattern, hasGradle) {
126
+ return Boolean(ctx.hasFile(...configFiles))
127
+ || Boolean(ctx.searchAnyFile(['pom.xml', '*/pom.xml'], pomPattern)) || hasGradle;
128
+ }
129
+
130
+ const FORMATTER_PATTERNS = [
131
+ R`cargo\s+fmt.*--check`, R`prettier\s+--check`, R`black\s+--check`, R`ruff\s+format\s+--check`,
132
+ R`gofmt\s+-l`, R`goimports`, R`rustfmt.*--check`, R`scalafmt\s+--check`, R`spotless:check`,
133
+ R`spotlessCheck`, R`\.?/?gradlew\s+spotlessCheck`, R`ktlintCheck`, R`ktfmt`,
134
+ ];
135
+
136
+ const AUDIT_PATTERNS = [
137
+ R`cargo\s+(audit|deny)`, R`npm\s+audit`, R`safety\s+check`, R`pip-audit`, R`snyk\s+test`,
138
+ R`trivy\s+fs`, R`grype`, R`dependency.scanning`, R`gemnasium`, R`dependabot`, R`renovate`,
139
+ R`snyk/actions`, R`dependency-check-maven`, R`ossindex-maven-plugin`,
140
+ ];
141
+
142
+ const CONSTRAINT_CHECKS = [
143
+ check('ci_pipeline_exists', 'CI Pipeline', 3.0, 'DORA 2025 Report', (ctx, r) => {
144
+ if (ctx.ciConfigs.length) return r.pass(`CI detected: ${ctx.ciConfigs.map((c) => c.ciType).join(', ')}`);
145
+ const found = ctx.hasFile('.travis.yml', 'Jenkinsfile', '.circleci/config.yml',
146
+ 'azure-pipelines.yml', 'bitbucket-pipelines.yml');
147
+ if (found) return r.pass(`CI config found: ${found}`);
148
+ return r.fail('No CI configuration found',
149
+ 'Add .gitlab-ci.yml or .github/workflows/ to run automated checks on every change.');
150
+ }),
151
+ check('linter_enforcement', 'Linter Enforcement', 4.0, 'OpenAI Harness Engineering - mechanical constraints', (ctx, r) => {
152
+ const hasPmdGradle = Boolean(ctx.searchAnyFile(JAVA_BUILD_FILES, PMD_GRADLE));
153
+ const hasSpotbugsGradle = Boolean(ctx.searchAnyFile(JAVA_BUILD_FILES, SPOTBUGS_GRADLE));
154
+ const anyGradle = hasPmdGradle || hasSpotbugsGradle;
155
+ for (const p of LINTER_PATTERNS) {
156
+ if (ctx.ciHasBlockingCommand(p)) return r.pass(`Blocking linter found in CI: ${p}`);
157
+ }
158
+ if (anyGradle && ctx.ciHasBlockingCommand(GRADLE_CHECK_PATTERN)) {
159
+ return r.pass(`Blocking linter found in CI: ${GRADLE_CHECK_PATTERN}`);
160
+ }
161
+ const notBlocking = 'Ensure linter job is not set to allow_failure / continue-on-error.';
162
+ for (const p of LINTER_PATTERNS) {
163
+ if (ctx.ciHasCommand(p)) return r.partial(2.0, `Linter found in CI (${p}) but may not be blocking`, notBlocking);
164
+ }
165
+ if (anyGradle && ctx.ciHasCommand(GRADLE_CHECK_PATTERN)) {
166
+ return r.partial(2.0, `Linter found in CI (${GRADLE_CHECK_PATTERN}) but may not be blocking`, notBlocking);
167
+ }
168
+ const java = ctx.languages.includes('java');
169
+ if (java && ctx.hasFile('checkstyle.xml')) {
170
+ return r.partial(2.0, 'Checkstyle config found but not confirmed in CI',
171
+ 'Add checkstyle to your CI pipeline as a blocking job.');
172
+ }
173
+ if (java) {
174
+ if (javaLinterConfig(ctx, ['pmd.xml', '*/pmd.xml'], R`maven-pmd-plugin`, hasPmdGradle)) {
175
+ return r.partial(2.0, 'PMD config found but not confirmed in CI',
176
+ 'Add PMD to your CI pipeline as a blocking job.');
177
+ }
178
+ if (javaLinterConfig(ctx, ['spotbugs-exclude.xml', '*/spotbugs-exclude.xml'],
179
+ R`spotbugs-maven-plugin`, hasSpotbugsGradle)) {
180
+ return r.partial(2.0, 'SpotBugs config found but not confirmed in CI',
181
+ 'Add SpotBugs to your CI pipeline as a blocking job.');
182
+ }
183
+ }
184
+ return r.fail('No linter found in CI',
185
+ 'Add a linter to CI that blocks merges on violations '
186
+ + '(e.g. cargo clippy -- -D warnings, eslint --max-warnings 0).');
187
+ }),
188
+ check('formatter_enforcement', 'Formatter Enforcement', 3.0, 'OpenAI Harness Engineering - mechanical constraints', (ctx, r) => {
189
+ for (const p of FORMATTER_PATTERNS) {
190
+ if (ctx.ciHasCommand(p)) return r.pass(`Formatter check found in CI: ${p}`);
191
+ }
192
+ if (ctx.languages.includes('java') || ctx.languages.includes('kotlin')) {
193
+ if (ctx.searchAnyFile(['pom.xml', '*/pom.xml'], R`spotless-maven-plugin`)) {
194
+ return r.partial(2.0, 'Spotless plugin found but not confirmed in CI',
195
+ 'Add spotless:check to your CI pipeline.');
196
+ }
197
+ if (ctx.searchAnyFile(JAVA_BUILD_FILES, R`com\.diffplug\.spotless|spotless|spotlessGradlePlugin`)) {
198
+ return r.partial(2.0, 'Spotless plugin found but not confirmed in CI',
199
+ 'Add spotlessCheck to your CI pipeline.');
200
+ }
201
+ }
202
+ return r.fail('No formatter check found in CI',
203
+ 'Add a formatter check to CI (e.g. cargo fmt --all -- --check, prettier --check).');
204
+ }),
205
+ check('type_safety', 'Type Safety', 3.0, 'SlopCodeBench - preventing subtle type errors', (ctx, r) => {
206
+ const langs = ctx.languages;
207
+ if (langs.includes('rust')) return r.pass('Rust: type safety enforced by compiler');
208
+ if (langs.includes('go')) return r.pass('Go: type safety enforced by compiler');
209
+ if (langs.includes('java') || langs.includes('kotlin')) {
210
+ return r.pass('JVM language: type safety enforced by compiler');
211
+ }
212
+ const ts = ctx.hasFile('tsconfig.json');
213
+ if (ts && ctx.searchFile(ts, R`"strict"\s*:\s*true`)) return r.pass('TypeScript strict mode enabled');
214
+ if (ts) {
215
+ return r.partial(1.5, 'TypeScript found but strict mode not confirmed',
216
+ 'Enable "strict": true in tsconfig.json.');
217
+ }
218
+ if (ctx.ciHasCommand(R`mypy|pyright|pytype`)) return r.pass('Python type checker found in CI');
219
+ const py = ctx.hasFile('pyproject.toml');
220
+ if (py && ctx.searchFile(py, R`\[tool\.mypy\]|\[tool\.pyright\]`)) {
221
+ return r.partial(1.5, 'Type checker configured but not confirmed in CI',
222
+ 'Add mypy/pyright to your CI pipeline.');
223
+ }
224
+ return r.fail('No type safety enforcement found',
225
+ 'Use a statically typed language, enable TypeScript strict mode, '
226
+ + 'or add mypy/pyright to CI for Python.');
227
+ }),
228
+ check('dependency_auditing', 'Dependency Auditing', 4.0, 'Blog: security infrastructure reliability', (ctx, r) => {
229
+ for (const p of AUDIT_PATTERNS) {
230
+ if (ctx.ciHasBlockingCommand(p)) return r.pass(`Blocking dependency audit in CI: ${p}`);
231
+ }
232
+ for (const p of AUDIT_PATTERNS) {
233
+ if (ctx.ciHasCommand(p)) {
234
+ return r.partial(2.0, `Dependency audit found (${p}) but set to allow_failure`,
235
+ 'Make the audit job blocking (remove allow_failure / continue-on-error).');
236
+ }
237
+ }
238
+ const conf = ctx.hasFile('deny.toml', '.cargo/audit.toml', '.snyk', 'renovate.json',
239
+ '.github/dependabot.yml', '.github/dependabot.yaml');
240
+ if (conf) {
241
+ return r.partial(1.0, `Audit config found (${conf}) but not confirmed in CI`,
242
+ 'Add the audit tool to your CI pipeline as a blocking job.');
243
+ }
244
+ return r.fail('No dependency auditing found',
245
+ 'Add cargo deny/audit, npm audit, pip-audit, or Snyk to CI as a blocking check.');
246
+ }),
247
+ check('conventional_commits', 'Conventional Commits', 2.0, 'DORA 2025 - working in small batches', (ctx, r) => {
248
+ if (ctx.ciHasCommand(R`commitlint|conventional-changelog|semantic-release`)) {
249
+ return r.pass('Conventional commit enforcement found in CI');
250
+ }
251
+ const conf = ctx.hasFile('.commitlintrc.yml', '.commitlintrc.json', '.commitlintrc.js',
252
+ 'commitlint.config.js', '.releaserc.json', '.releaserc.yml');
253
+ if (conf) return r.pass(`Commit lint config found: ${conf}`);
254
+ return r.fail('No conventional commit enforcement found',
255
+ 'Add commitlint or equivalent to CI to enforce consistent commit message format.');
256
+ }),
257
+ check('unsafe_code_policy', 'Unsafe Code Policy', 3.0, 'Blog: 80% problem in AI-generated code', (ctx, r) => {
258
+ const cargo = ctx.hasFile('cargo.toml');
259
+ if (cargo && ctx.searchFile(cargo, R`unsafe_code\s*=\s*"forbid"`)) return r.pass('Rust: unsafe_code = forbid');
260
+ const eslintrc = ctx.hasFile('.eslintrc.json', '.eslintrc.yml', '.eslintrc.js', 'eslint.config.js');
261
+ if (eslintrc && ctx.searchFile(eslintrc, R`no-eval|no-implied-eval`)) return r.pass('ESLint unsafe pattern rules found');
262
+ if (ctx.ciHasCommand(R`semgrep|bandit|brakeman|gosec`)) return r.pass('Security linter found in CI');
263
+ const raw = ctx.ciRawContent().toLowerCase();
264
+ if (raw.includes('sast') || raw.includes('semgrep')) return r.pass('SAST scanning found in CI');
265
+ return r.fail('No explicit policy against unsafe code patterns',
266
+ 'Add unsafe_code = forbid (Rust), security linting (semgrep/bandit), '
267
+ + 'or ESLint rules against dangerous patterns.');
268
+ }),
269
+ ];
270
+
271
+ // ---------------------------------------------------------------------------------------------
272
+ // testing.py — Testing & Stability (25%)
273
+ // ---------------------------------------------------------------------------------------------
274
+
275
+ const COVERAGE_PATTERNS = [
276
+ R`llvm-cov|tarpaulin|cargo-llvm-cov`, R`coverage\.py|pytest-cov|--cov`, R`istanbul|nyc|c8\s`,
277
+ R`jacoco|cobertura`, R`simplecov`, R`go\s+tool\s+cover|gocover`, R`codecov|coveralls`,
278
+ ];
279
+
280
+ const TESTING_CHECKS = [
281
+ check('test_suite_exists', 'Test Suite', 3.0, 'Kent Beck - tests define what correct means', (ctx, r) => {
282
+ const hasTests = ctx.hasDir('tests', 'test', 'spec', 'src/test', '__tests__')
283
+ || ctx.hasFile('tests/*.rs', 'tests/*.py', 'tests/*.ts', 'tests/*.js', 'test_*.py', '*_test.go',
284
+ '*_test.rs', '*.test.ts', '*.test.js', '*.spec.ts', '*.spec.js');
285
+ const inCi = ctx.ciHasCommand(R`cargo\s+(test|nextest)|pytest|jest|mocha|vitest|go\s+test|rspec|`
286
+ + R`gradlew?\s+test|\.\/gradlew\s+test|mvn\s+test|dotnet\s+test`);
287
+ if (hasTests && inCi) return r.pass('Tests present and executed in CI');
288
+ if (hasTests) {
289
+ return r.partial(1.5, 'Tests found but not confirmed in CI', 'Add test execution to your CI pipeline.');
290
+ }
291
+ return r.fail('No test suite found',
292
+ "Add tests and run them in CI. As Kent Beck says: 'the test defines what correct means.'");
293
+ }),
294
+ check('feature_matrix_testing', 'Feature Matrix Testing', 3.0, 'DORA 2025 - stability through comprehensive testing', (ctx, r) => {
295
+ const testJobs = ctx.ciConfigs.flatMap((ci) => ci.jobs).filter((j) => {
296
+ const all = j.commands.join(' ').toLowerCase();
297
+ return ['test', 'pytest', 'jest', 'rspec', 'nextest'].some((kw) => all.includes(kw));
298
+ });
299
+ if (testJobs.length >= 3) {
300
+ return r.pass(`Multiple test jobs in CI: ${testJobs.slice(0, 5).map((j) => j.name).join(', ')}`);
301
+ }
302
+ const raw = ctx.ciRawContent().toLowerCase();
303
+ if (raw.includes('matrix') || raw.includes('parallel')) return r.pass('Matrix/parallel testing strategy found in CI');
304
+ const combos = [R`--all-features`, R`--no-default-features`, R`--features\s`, R`NODE_ENV=`]
305
+ .filter((p) => ctx.ciHasCommand(p)).length;
306
+ if (combos >= 2) return r.pass(`Feature combination testing found (${combos} variants)`);
307
+ if (testJobs.length === 2) {
308
+ return r.partial(1.5, 'Two test jobs found, consider adding more configurations',
309
+ 'Test with different feature flags, environments, or dependency versions.');
310
+ }
311
+ return r.fail('Only one test configuration found',
312
+ 'Add CI jobs for different feature flags, environments, or dependency versions '
313
+ + '(e.g. --all-features, --no-default-features, MSRV check).');
314
+ }),
315
+ check('coverage_measurement', 'Code Coverage', 4.0, 'DORA 2025 - stability feedback loops', (ctx, r) => {
316
+ for (const p of COVERAGE_PATTERNS) {
317
+ if (ctx.ciHasCommand(p)) return r.pass(`Coverage measurement in CI: ${p}`);
318
+ }
319
+ const conf = ctx.hasFile('.codecov.yml', 'codecov.yml', '.coveragerc', 'coverage.config.js', 'jest.config.*');
320
+ if (conf) {
321
+ return r.partial(2.0, `Coverage config found (${conf}) but not confirmed in CI`,
322
+ 'Add coverage reporting to your CI pipeline.');
323
+ }
324
+ return r.fail('No code coverage measurement found',
325
+ 'Add cargo llvm-cov, pytest-cov, istanbul/c8, or equivalent to CI. '
326
+ + 'Even informational coverage provides a feedback loop.');
327
+ }),
328
+ check('mutation_testing', 'Mutation Testing', 4.0, "SlopCodeBench - code that 'appears correct but is unreliable'", (ctx, r) => {
329
+ if (ctx.ciHasCommand(R`cargo[\s-]mutants|stryker|mutmut|pitest|mull`)) return r.pass('Mutation testing found in CI');
330
+ const conf = ctx.hasFile('stryker.conf.js', 'stryker.conf.json', '.stryker-tmp', 'mutmut_config.py', '.mutmut');
331
+ if (conf) {
332
+ return r.partial(2.0, `Mutation testing config found (${conf})`,
333
+ 'Add mutation testing to CI, even on a scheduled basis.');
334
+ }
335
+ return r.fail('No mutation testing found',
336
+ 'Add cargo-mutants (Rust), Stryker (JS/TS), mutmut (Python), or PIT (Java). '
337
+ + 'Mutation testing catches tests that pass without verifying behavior.');
338
+ }),
339
+ check('property_based_testing', 'Property-Based Testing', 3.0, 'Blog: catching edge cases in AI-generated code', (ctx, r) => {
340
+ const depFiles = ['cargo.toml', 'pyproject.toml', 'package.json', 'go.mod', 'pom.xml', 'build.gradle', 'build.gradle.kts'];
341
+ const patterns = [R`proptest|quickcheck|arbtest`, R`hypothesis`, R`fast-check|jsverify`, R`rapid`, R`jqwik`];
342
+ for (const dep of depFiles) {
343
+ for (const p of patterns) {
344
+ if (ctx.searchAnyFile([dep, `*/${dep}`], p)) return r.pass(`Property-based testing library found in ${dep}`);
345
+ }
346
+ }
347
+ const testFiles = ctx.findFiles('tests/*.rs', 'tests/*.py', 'test_*.py', '*.test.ts', '*PropertyTest.java', '*PropertyTest.kt');
348
+ const propPatterns = [R`proptest!`, R`@given`, R`fc\.(assert|property)`, R`rapid\.Check`, R`@Property`];
349
+ for (const tf of testFiles) {
350
+ for (const p of propPatterns) {
351
+ if (ctx.searchFile(tf, p)) return r.pass(`Property-based tests found in ${tf}`);
352
+ }
353
+ }
354
+ return r.fail('No property-based testing found',
355
+ 'Add proptest (Rust), hypothesis (Python), fast-check (JS/TS), or jqwik (Java) '
356
+ + 'for testing invariants with random structured inputs.');
357
+ }),
358
+ check('fuzz_testing', 'Fuzz Testing', 3.0, 'Blog: 80% problem - catching what AI misses', (ctx, r) => {
359
+ for (const dep of ['pom.xml', 'build.gradle', 'build.gradle.kts']) {
360
+ if (ctx.searchAnyFile([dep, `*/${dep}`], 'jazzer-junit')) return r.pass(`Jazzer fuzz testing library found in ${dep}`);
361
+ }
362
+ const dir = ctx.hasDir('fuzz', 'fuzz_targets', 'fuzzing');
363
+ if (dir) return r.pass(`Fuzz testing directory found: ${dir}`);
364
+ const file = ctx.hasFile('fuzz/fuzz_targets/*.rs', 'fuzz/*.py', 'fuzz_test.go', '*_fuzz_test.go', '*FuzzTest.java', '*FuzzTest.kt');
365
+ if (file) return r.pass(`Fuzz target found: ${file}`);
366
+ if (ctx.ciHasCommand(R`cargo\s+fuzz|go\s+test.*-fuzz|afl-fuzz|honggfuzz`)) return r.pass('Fuzz testing found in CI');
367
+ return r.fail('No fuzz testing found', 'Add fuzz targets for parsing-heavy and input-handling code paths.');
368
+ }),
369
+ check('contract_tests', 'Contract / Compatibility Tests', 3.0, 'OpenAI Harness Engineering - mechanical constraints', (ctx, r) => {
370
+ const files = ctx.findFiles('*contract*', '*golden*', '*compat*', '*snapshot*', '*fixture*')
371
+ .filter((f) => ['.rs', '.py', '.ts', '.js', '.go', '.json'].some((ext) => f.includes(ext)));
372
+ if (files.length) return r.pass(`Contract/compatibility tests found: ${files.slice(0, 3).join(', ')}`);
373
+ if (ctx.ciHasCommand(R`golden|snapshot|contract`)) return r.pass('Contract/snapshot testing found in CI');
374
+ return r.fail('No contract or compatibility tests found',
375
+ 'Add contract tests that verify external interface stability '
376
+ + '(golden fixtures, snapshot tests, wire-format checks).');
377
+ }),
378
+ check('tests_blocking_ci', 'Tests Block Merge', 2.0, 'DORA 2025 - stability metrics', (ctx, r) => {
379
+ const kws = ['test', 'pytest', 'jest', 'rspec', 'nextest', 'spec'];
380
+ const blocking = [];
381
+ const nonBlocking = [];
382
+ for (const job of ctx.ciConfigs.flatMap((ci) => ci.jobs)) {
383
+ const isTest = kws.some((kw) => job.name.toLowerCase().includes(kw)
384
+ || job.commands.some((c) => c.toLowerCase().includes(kw)));
385
+ if (!isTest) continue;
386
+ (job.allowFailure ? nonBlocking : blocking).push(job.name);
387
+ }
388
+ if (blocking.length && !nonBlocking.length) {
389
+ return r.pass(`All test jobs are blocking: ${blocking.slice(0, 3).join(', ')}`);
390
+ }
391
+ if (blocking.length) {
392
+ return r.partial(1.0, `Some test jobs are allow_failure: ${nonBlocking.slice(0, 3).join(', ')}`,
393
+ 'Make all test jobs blocking to prevent merging broken code.');
394
+ }
395
+ if (nonBlocking.length) {
396
+ return r.fail(`Test jobs found but all are allow_failure: ${nonBlocking.slice(0, 3).join(', ')}`,
397
+ 'Remove allow_failure from test jobs.');
398
+ }
399
+ return r.fail('No test jobs found in CI', 'Add test execution to CI as a blocking job.');
400
+ }),
401
+ ];
402
+
403
+ // ---------------------------------------------------------------------------------------------
404
+ // review.py — Review & Drift Prevention (15%)
405
+ // ---------------------------------------------------------------------------------------------
406
+
407
+ const REVIEW_CHECKS = [
408
+ check('code_review_required', 'Code Review Required', 4.0, 'OpenAI Harness Engineering - author/reviewer separation', (ctx, r) => {
409
+ const owners = ctx.hasFile('CODEOWNERS', '.github/CODEOWNERS', 'docs/CODEOWNERS');
410
+ if (owners) return r.pass(`CODEOWNERS file found: ${owners}`);
411
+ const raw = ctx.ciRawContent().toLowerCase();
412
+ if (raw.includes('approval') || raw.includes('review')) {
413
+ return r.partial(2.0, 'Review-related configuration found in CI',
414
+ 'Enforce code review via branch protection rules (requires API access to verify).');
415
+ }
416
+ return r.partial(0.0,
417
+ 'Cannot verify branch protection without API access. '
418
+ + 'Run with --github-token or --gitlab-token for full assessment.',
419
+ 'Enable required reviews in branch protection settings and add CODEOWNERS.');
420
+ }),
421
+ check('scheduled_ci', 'Scheduled CI Jobs', 3.0, 'OpenAI Harness Engineering - garbage collection agents', (ctx, r) => {
422
+ if (ctx.ciHasScheduledJob()) return r.pass('Scheduled CI pipeline found');
423
+ const raw = ctx.ciRawContent().toLowerCase();
424
+ if (raw.includes('cron') || raw.includes('schedule')) return r.pass('Scheduled trigger found in CI config');
425
+ return r.fail('No scheduled CI jobs found',
426
+ 'Add scheduled/nightly CI pipelines for drift detection: '
427
+ + 'stricter lints, dependency freshness, doc coverage scans.');
428
+ }),
429
+ check('stale_doc_detection', 'Stale Documentation Detection', 2.0, 'OpenAI Harness Engineering - quality drift', (ctx, r) => {
430
+ if (ctx.ciHasCommand(R`todo|fixme|hack`)) return r.pass('TODO/FIXME scanning found in CI');
431
+ for (const p of [R`link.check|markdown.link|lychee|linkinator`, R`vale|textlint|markdownlint`]) {
432
+ if (ctx.ciHasCommand(p)) return r.pass(`Documentation quality check in CI: ${p}`);
433
+ }
434
+ return r.fail('No stale documentation detection found',
435
+ 'Add TODO/FIXME scanning, link checking (lychee), or prose linting (vale) to CI.');
436
+ }),
437
+ check('mr_template', 'PR/MR Template', 2.0, 'DORA 2025 - working in small batches', (ctx, r) => {
438
+ const found = ctx.hasFile('.github/pull_request_template.md', '.github/PULL_REQUEST_TEMPLATE.md',
439
+ '.gitlab/merge_request_templates/*.md', 'docs/pull_request_template.md');
440
+ if (found) return r.pass(`PR/MR template found: ${found}`);
441
+ if (ctx.hasDir('.gitlab/merge_request_templates')) return r.pass('GitLab MR template directory found');
442
+ return r.fail('No PR/MR template found',
443
+ 'Add .github/PULL_REQUEST_TEMPLATE.md or '
444
+ + '.gitlab/merge_request_templates/Default.md with '
445
+ + 'sections for description, testing, and impact.');
446
+ }),
447
+ check('automated_review', 'Automated Code Review', 2.0, 'OpenAI Harness Engineering - separate authoring and reviewing agents', (ctx, r) => {
448
+ const found = ctx.hasFile('.coderabbit.yaml', '.github/copilot-review.yml', 'renovate.json', '.renovaterc',
449
+ '.renovaterc.json', 'dependabot.yml', '.github/dependabot.yml');
450
+ if (found) return r.pass(`Automated review tool configured: ${found}`);
451
+ const raw = ctx.ciRawContent().toLowerCase();
452
+ for (const kw of ['coderabbit', 'codeclimate', 'sonarqube', 'sonarcloud', 'duo']) {
453
+ if (raw.includes(kw)) return r.pass(`Automated review tool found in CI: ${kw}`);
454
+ }
455
+ return r.fail('No automated review tools found',
456
+ 'Configure CodeRabbit, SonarCloud, Dependabot/Renovate, or equivalent '
457
+ + 'for automated review on every PR/MR.');
458
+ }),
459
+ check('doc_sync_check', 'Documentation Sync Check', 2.0, 'OpenAI Harness Engineering - curated knowledge base', (ctx, r) => {
460
+ for (const p of [R`diff\s+.*\.md`, R`doc.*sync`, R`agent.*sync`, R`golden.*check`]) {
461
+ if (ctx.ciHasCommand(p)) return r.pass(`Doc sync check found in CI: ${p}`);
462
+ }
463
+ const raw = ctx.ciRawContent().toLowerCase();
464
+ if (raw.includes('sync') && (raw.includes('doc') || raw.includes('agent'))) {
465
+ return r.pass('Documentation sync job found in CI');
466
+ }
467
+ return r.fail('No documentation sync checks found in CI',
468
+ 'Add CI jobs that verify related docs stay in sync '
469
+ + '(e.g. diff AGENTS.md CLAUDE.md, golden fixture checks).');
470
+ }),
471
+ ];
472
+
473
+ // ---------------------------------------------------------------------------------------------
474
+ // ai_safeguards.py — AI-Specific Safeguards (15%)
475
+ // ---------------------------------------------------------------------------------------------
476
+
477
+ const AI_NORM_FILES = ['claude.md', 'agents.md', 'contributing.md', 'docs/ai-*.md', 'docs/development.md', '.cursor/rules/*.mdc'];
478
+ const AI_NORM_PATTERNS = [
479
+ R`ai.*(usage|policy|guideline|norm|review)`,
480
+ R`(review|verify|check).*(ai|generated|agent)`,
481
+ R`(test|tests)\s+before\s+(implement|code|asking)`,
482
+ R`code\s+style`,
483
+ R`(naming|error.handling|comment)\s+(convention|standard|policy)`,
484
+ ];
485
+
486
+ const AI_SAFEGUARD_CHECKS = [
487
+ check('ai_usage_norms', 'AI Usage Norms', 4.0, 'DORA 2025 - clear organizational stance on AI use', (ctx, r) => {
488
+ for (const p of AI_NORM_PATTERNS) {
489
+ const found = ctx.searchAnyFile(AI_NORM_FILES, p);
490
+ if (found) return r.pass(`AI usage norms found in ${found}`);
491
+ }
492
+ const agent = ctx.hasFile('claude.md', 'agents.md');
493
+ if (agent) {
494
+ return r.partial(2.0, `Agent file found (${agent}) but no explicit review norms detected`,
495
+ 'Add a section on AI review expectations: what reviewers should specifically '
496
+ + 'check in AI-generated code, when to require manual implementation.');
497
+ }
498
+ return r.fail('No AI usage norms documented',
499
+ 'Document AI usage policies: review expectations for AI-generated code, '
500
+ + 'when manual implementation is required, testing-before-implementation norms.');
501
+ }),
502
+ check('small_batch_enforcement', 'Small Batch Enforcement', 3.0, 'DORA 2025 - working in small batches', (ctx, r) => {
503
+ if (ctx.ciHasCommand(R`danger|pr-size|diffstat|size-limit`)) return r.pass('PR size check tool found in CI');
504
+ const contributing = ctx.hasFile('contributing.md', 'CONTRIBUTING.md');
505
+ if (contributing && ctx.searchFile(contributing, R`small\s+(pr|mr|batch|change)|size\s+limit|keep.*(small|focused)`)) {
506
+ return r.pass(`Small batch guidelines found in ${contributing}`);
507
+ }
508
+ const agent = ctx.hasFile('claude.md', 'agents.md');
509
+ if (agent && ctx.searchFile(agent, R`small|batch|incremental|focused`)) {
510
+ return r.partial(1.5, `Small batch hints found in ${agent}`,
511
+ 'Add explicit PR size guidelines or automated size checks to CI.');
512
+ }
513
+ return r.fail('No small batch enforcement found',
514
+ 'Add PR size checks (Danger, pr-size-labeler) or document size guidelines '
515
+ + 'in CONTRIBUTING.md. Large AI-generated PRs are harder to review.');
516
+ }),
517
+ check('multiple_approach_culture', 'Design-Before-Code Culture', 3.0, 'Blog: cognitive offloading guardrails', (ctx, r) => {
518
+ const dir = ctx.hasDir('docs/rfcs', 'rfcs', 'docs/proposals', 'docs/designs');
519
+ if (dir) return r.pass(`RFC/design document directory found: ${dir}`);
520
+ const docs = ctx.findFiles('docs/*plan*.md', 'docs/*design*.md', 'docs/*rfc*.md', 'docs/*proposal*.md', 'docs/phase-*.md');
521
+ if (docs.length) return r.pass(`Design/plan documents found: ${docs.slice(0, 3).join(', ')}`);
522
+ const agent = ctx.hasFile('claude.md', 'agents.md');
523
+ if (agent && ctx.searchFile(agent, R`plan.*before|design.*first|multiple.*approach|spec\.md|plan\.md`)) {
524
+ return r.pass(`Plan-before-code workflow documented in ${agent}`);
525
+ }
526
+ if (ctx.hasDir('.cursor/rules')) {
527
+ for (const rf of ctx.findFiles('.cursor/rules/*.mdc')) {
528
+ if (ctx.searchFile(rf, R`plan|design|spec`)) return r.pass(`Plan-driven development rule found in ${rf}`);
529
+ }
530
+ }
531
+ return r.fail('No design-before-code process found',
532
+ 'Create docs/rfcs/ or docs/designs/ directory. Document a process where '
533
+ + 'significant changes start with a design doc or plan before implementation.');
534
+ }),
535
+ check('error_handling_policy', 'Error Handling Policy', 3.0, 'Blog: AI agents deleting tests, using expect()', (ctx, r) => {
536
+ const cargo = ctx.hasFile('cargo.toml');
537
+ if (cargo && ctx.searchFile(cargo, R`unwrap_used|expect_used`)) return r.pass('Clippy panic-prevention lints configured');
538
+ if (ctx.ciHasCommand(R`clippy.*unwrap_used|clippy.*expect_used`)) return r.pass('Panic-prevention clippy lints in CI');
539
+ const eslintrc = ctx.hasFile('.eslintrc.json', '.eslintrc.yml', 'eslint.config.js');
540
+ if (eslintrc && ctx.searchFile(eslintrc, R`no-throw-literal|no-implicit-coercion`)) {
541
+ return r.pass('Error handling ESLint rules configured');
542
+ }
543
+ const agent = ctx.hasFile('claude.md', 'agents.md');
544
+ if (agent && ctx.searchFile(agent, R`error.handling|unwrap|expect|panic|Result.*\?`)) {
545
+ return r.partial(1.5, `Error handling guidelines found in ${agent}`,
546
+ 'Enforce error handling rules mechanically via lints, not just documentation.');
547
+ }
548
+ return r.fail('No error handling policy found',
549
+ 'Add clippy lints (unwrap_used, expect_used) for Rust, '
550
+ + 'ESLint rules for JS/TS, or document error handling patterns in agent instructions.');
551
+ }),
552
+ check('security_critical_marking', 'Security-Critical Path Marking', 2.0, 'Blog: 80% problem in security infrastructure', (ctx, r) => {
553
+ const owners = ctx.hasFile('CODEOWNERS', '.github/CODEOWNERS');
554
+ if (owners) return r.pass(`CODEOWNERS found: ${owners}`);
555
+ const sec = ctx.hasFile('SECURITY.md', 'security.md');
556
+ if (sec) return r.pass(`Security policy found: ${sec}`);
557
+ if (ctx.ciHasCommand(R`sast|dast|semgrep|bandit|gosec|brakeman`)) return r.pass('Security scanning in CI');
558
+ return r.fail('No security-critical path marking found',
559
+ 'Add CODEOWNERS for sensitive directories, SECURITY.md for vuln reporting, '
560
+ + 'or SAST scanning in CI.');
561
+ }),
562
+ ];
563
+
564
+ /** `ALL_CHECKS` — category id → its checks, in upstream's order. */
565
+ export const ALL_CHECKS = {
566
+ documentation: DOCUMENTATION_CHECKS,
567
+ constraints: CONSTRAINT_CHECKS,
568
+ testing: TESTING_CHECKS,
569
+ review: REVIEW_CHECKS,
570
+ ai_safeguards: AI_SAFEGUARD_CHECKS,
571
+ };