1use std::{fmt, str::FromStr};
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
24pub enum Category {
25 Typ,
26 Par,
27 Nam,
28 Cap,
29 Llm,
30 Orc,
31 Std,
32 Prm,
33 Mod,
34 Rmd,
35 Sus,
36 Lnt,
37 Fmt,
38 Imp,
39 Own,
40 Rcv,
41 Mat,
42 Pol,
43 Met,
47 Cst,
51 Cmp,
56}
57
58impl Category {
59 pub const ALL: &'static [Category] = &[
60 Category::Typ,
61 Category::Par,
62 Category::Nam,
63 Category::Cap,
64 Category::Llm,
65 Category::Orc,
66 Category::Std,
67 Category::Prm,
68 Category::Mod,
69 Category::Rmd,
70 Category::Sus,
71 Category::Lnt,
72 Category::Fmt,
73 Category::Imp,
74 Category::Own,
75 Category::Rcv,
76 Category::Mat,
77 Category::Pol,
78 Category::Met,
79 Category::Cst,
80 Category::Cmp,
81 ];
82
83 pub const fn as_str(self) -> &'static str {
84 match self {
85 Category::Typ => "TYP",
86 Category::Par => "PAR",
87 Category::Nam => "NAM",
88 Category::Cap => "CAP",
89 Category::Llm => "LLM",
90 Category::Orc => "ORC",
91 Category::Std => "STD",
92 Category::Prm => "PRM",
93 Category::Mod => "MOD",
94 Category::Rmd => "RMD",
95 Category::Sus => "SUS",
96 Category::Lnt => "LNT",
97 Category::Fmt => "FMT",
98 Category::Imp => "IMP",
99 Category::Own => "OWN",
100 Category::Rcv => "RCV",
101 Category::Mat => "MAT",
102 Category::Pol => "POL",
103 Category::Met => "MET",
104 Category::Cst => "CST",
105 Category::Cmp => "CMP",
106 }
107 }
108}
109
110impl fmt::Display for Category {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 f.write_str(self.as_str())
113 }
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
118pub struct RegistryEntry {
119 pub code: Code,
120 pub identifier: &'static str,
121 pub category: Category,
122 pub summary: &'static str,
123}
124
125macro_rules! diagnostic_codes {
126 ($($variant:ident, $identifier:literal, $category:ident, $summary:literal;)*) => {
127 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
129 pub enum Code {
130 $($variant,)*
131 }
132
133 impl Code {
134 pub const ALL: &'static [Code] = &[
135 $(Code::$variant,)*
136 ];
137
138 pub const fn as_str(self) -> &'static str {
139 match self {
140 $(Code::$variant => $identifier,)*
141 }
142 }
143
144 pub const fn category(self) -> Category {
145 match self {
146 $(Code::$variant => Category::$category,)*
147 }
148 }
149
150 pub const fn summary(self) -> &'static str {
151 match self {
152 $(Code::$variant => $summary,)*
153 }
154 }
155
156 pub const fn explanation(self) -> &'static str {
160 match self {
161 $(Code::$variant => include_str!(
162 concat!("diagnostic_codes/explanations/", $identifier, ".md")
163 ),)*
164 }
165 }
166 }
167
168 pub const REGISTRY: &[RegistryEntry] = &[
169 $(RegistryEntry {
170 code: Code::$variant,
171 identifier: $identifier,
172 category: Category::$category,
173 summary: $summary,
174 },)*
175 ];
176 };
177}
178
179diagnostic_codes! {
180 TypeMismatch, "HARN-TYP-001", Typ, "expected and actual types are incompatible";
181 InvalidBinaryOperator, "HARN-TYP-002", Typ, "binary operator is not defined for the operand types";
182 StringInterpolationRewrite, "HARN-TYP-003", Typ, "string concatenation should be rewritten as interpolation";
183 ReturnTypeMismatch, "HARN-TYP-004", Typ, "returned expression does not match the declared return type";
184 AssignmentTypeMismatch, "HARN-TYP-005", Typ, "assigned value does not match the target type";
185 ArgumentTypeMismatch, "HARN-TYP-006", Typ, "argument value does not match the parameter type";
186 VariableTypeMismatch, "HARN-TYP-007", Typ, "initializer does not match the declared variable type";
187 ClosureReturnTypeMismatch, "HARN-TYP-008", Typ, "closure return expression does not match its declared type";
188 FieldTypeMismatch, "HARN-TYP-009", Typ, "field value does not match its declared type";
189 MethodTypeMismatch, "HARN-TYP-010", Typ, "method receiver or result type is incompatible";
190 GenericTypeArgumentUnsupported, "HARN-TYP-011", Typ, "callable does not accept type arguments";
191 GenericTypeArgumentMismatch, "HARN-TYP-012", Typ, "type argument does not satisfy the generic parameter";
192 GenericTypeArgumentArity, "HARN-TYP-013", Typ, "generic call has the wrong number of type arguments";
193 TypeParameterArity, "HARN-TYP-014", Typ, "declaration has the wrong number of type parameters";
194 WhereConstraintMismatch, "HARN-TYP-015", Typ, "type argument does not satisfy a where-clause constraint";
195 IterableExpected, "HARN-TYP-016", Typ, "expression must be iterable";
196 InvalidIndexType, "HARN-TYP-017", Typ, "subscript index type is invalid";
197 CallableExpected, "HARN-TYP-018", Typ, "expression must be callable";
198 InvalidCast, "HARN-TYP-019", Typ, "cast cannot be proven valid";
199 UnknownTypeName, "HARN-TYP-020", Typ, "type name cannot be resolved";
200 InvalidVariantUse, "HARN-TYP-021", Typ, "variant type is used in an invalid position";
201 InvalidStructLiteral, "HARN-TYP-022", Typ, "struct literal is invalid";
202 InvalidEnumConstruct, "HARN-TYP-023", Typ, "enum construction is invalid";
203 InvalidPatternBinding, "HARN-TYP-024", Typ, "pattern binding is invalid for the expected type";
204 InvalidOptionalAccess, "HARN-TYP-025", Typ, "optional access is invalid for the receiver type";
205 ThrowsTypeMismatch, "HARN-TYP-026", Typ, "thrown value type is not covered by the callable's declared throws set";
206 TupleIndexOutOfBounds, "HARN-TYP-027", Typ, "constant tuple index is outside the fixed arity";
207 ParserUnexpectedToken, "HARN-PAR-001", Par, "parser found an unexpected token";
208 ParserUnexpectedEof, "HARN-PAR-002", Par, "parser reached end of file while expecting syntax";
209 ParserUnexpectedCharacter, "HARN-PAR-003", Par, "lexer found an unexpected character";
210 ParserUnterminatedString, "HARN-PAR-004", Par, "string literal is unterminated";
211 ParserUnterminatedBlockComment, "HARN-PAR-005", Par, "block comment is unterminated";
212 ParserIntegerLiteralOutOfRange, "HARN-PAR-006", Par, "integer literal is out of range for int (i64)";
213 CompilerError, "HARN-CMP-001", Cmp, "the program failed to compile to bytecode";
214 UndefinedVariable, "HARN-NAM-001", Nam, "variable name cannot be resolved";
215 UndefinedFunction, "HARN-NAM-002", Nam, "function name cannot be resolved";
216 UnknownAttribute, "HARN-NAM-003", Nam, "attribute name is not recognized";
217 UnknownField, "HARN-NAM-004", Nam, "field name does not exist on the target type";
218 UnknownMethod, "HARN-NAM-005", Nam, "method name does not exist on the receiver type";
219 DuplicateArgument, "HARN-NAM-006", Nam, "argument name is duplicated";
220 UnknownOption, "HARN-NAM-007", Nam, "option key is not recognized";
221 UnknownBuiltin, "HARN-NAM-008", Nam, "builtin name cannot be resolved";
222 DeprecatedFunction, "HARN-NAM-009", Nam, "function call targets a deprecated declaration";
223 UnknownDeclaration, "HARN-NAM-010", Nam, "declaration reference cannot be resolved";
224 InvalidAttributeTarget, "HARN-NAM-011", Nam, "attribute is attached to an unsupported declaration";
225 InvalidAttributeArgument, "HARN-NAM-012", Nam, "attribute argument is invalid";
226 InvalidMainSignature, "HARN-NAM-101", Nam, "`fn main` must take an explicit `harness: Harness` parameter";
227 CapabilityPayloadInvalid, "HARN-CAP-001", Cap, "capability payload is invalid";
228 HitlMissingApprovalPolicy, "HARN-CAP-002", Cap, "human approval construct is missing policy";
229 HitlInvalidApprovalArgument, "HARN-CAP-003", Cap, "human approval argument is invalid";
230 CapabilityResultUnchecked, "HARN-CAP-004", Cap, "capability result must be checked";
231 CapabilityUnknownOperation, "HARN-CAP-005", Cap, "host capability operation is not declared";
232 CapabilityCallStaticNameRequired, "HARN-CAP-006", Cap, "host capability call must use a static operation name";
233 CapabilityBindingInvalid, "HARN-CAP-007", Cap, "tool host capability binding is invalid";
234 EffectInheritanceViolation, "HARN-CAP-301", Cap, "child agent effect set exceeds the parent's declared effects";
235 UnknownLlmOption, "HARN-LLM-001", Llm, "LLM option key is not recognized";
236 DeprecatedLlmOption, "HARN-LLM-002", Llm, "LLM option key is deprecated";
237 LlmSchemaMissing, "HARN-LLM-003", Llm, "LLM call is missing schema validation";
238 LlmSchemaInvalid, "HARN-LLM-004", Llm, "LLM schema option is invalid";
239 LlmProviderIdentityBranch, "HARN-LLM-005", Llm, "prompt branches on provider identity instead of capability flags";
240 LlmToolFormatCompositionInvalid, "HARN-LLM-006", Llm, "provider, model, and tool format form a known-unsafe composition";
241 OrchestrationArity, "HARN-ORC-001", Orc, "orchestration construct has invalid arity";
242 OrchestrationType, "HARN-ORC-002", Orc, "orchestration construct argument has invalid type";
243 AgentDefinitionInvalid, "HARN-ORC-003", Orc, "agent declaration is invalid";
244 WorkflowDefinitionInvalid, "HARN-ORC-004", Orc, "workflow declaration is invalid";
245 ToolDefinitionInvalid, "HARN-ORC-005", Orc, "tool declaration is invalid";
246 PipelineDefinitionInvalid, "HARN-ORC-006", Orc, "pipeline declaration is invalid";
247 InvalidSelectConstruct, "HARN-ORC-007", Orc, "select construct is invalid";
248 UnreachableCode, "HARN-ORC-008", Orc, "statement cannot be reached";
249 FlowInvariantAttributeInvalid, "HARN-ORC-009", Orc, "Flow invariant attribute set is invalid";
250 ExecutionTargetMissing, "HARN-ORC-010", Orc, "execution target path cannot be found";
251 SelfDeadlockDetected, "HARN-ORC-011", Orc, "a self-deadlock acquire would block forever";
252 WaitForGraphDeadlockDetected, "HARN-ORC-012", Orc, "a wait-for graph cycle would block forever";
253 DeprecatedStdlibSymbol, "HARN-STD-001", Std, "stdlib symbol has been renamed or deprecated";
254 StdlibUsageInvalid, "HARN-STD-002", Std, "stdlib call is invalid";
255 BuiltinArity, "HARN-STD-003", Std, "builtin call has invalid arity";
256 LintMissingStdlibMetadata, "HARN-STD-101", Std, "public stdlib function is missing declared metadata";
257 LintMissingStdlibReturnType, "HARN-STD-102", Std, "public stdlib function is missing an explicit return type";
258 PromptTemplateParse, "HARN-PRM-001", Prm, "prompt template cannot be parsed";
259 PromptVariantExplosion, "HARN-PRM-002", Prm, "prompt template has too many capability-aware branches";
260 PromptInjectionRisk, "HARN-PRM-003", Prm, "prompt construction risks direct injection";
261 PromptProviderIdentityBranch, "HARN-PRM-004", Prm, "prompt template branches on provider identity";
262 PromptToolSurfaceUnknown, "HARN-PRM-005", Prm, "prompt references a tool outside the declared surface";
263 PromptToolSurfaceDeferredReference, "HARN-PRM-006", Prm, "prompt references a deferred tool without tool search";
264 PromptTargetMissing, "HARN-PRM-007", Prm, "prompt or template target cannot be found";
265 ModuleImportUnresolved, "HARN-MOD-001", Mod, "module import cannot be resolved";
266 ModuleImportUnused, "HARN-MOD-002", Mod, "module import is unused";
267 ModuleImportOrder, "HARN-MOD-003", Mod, "module imports are not in canonical order";
268 ModuleExportInvalid, "HARN-MOD-004", Mod, "module export is invalid";
269 ModuleImportCollision, "HARN-MOD-005", Mod, "module imports expose colliding names";
270 ModuleReExportConflict, "HARN-MOD-006", Mod, "module re-exports conflict";
271 ModuleImportCompileFailed, "HARN-MOD-007", Mod, "imported module failed to compile";
272 ReminderUnknownOption, "HARN-RMD-001", Rmd, "reminder lifecycle option key is not recognized";
273 ReminderInvalidShape, "HARN-RMD-002", Rmd, "reminder payload shape is invalid";
274 ReminderUnsupportedUserBlockRoleHint, "HARN-RMD-003", Rmd, "retired provider-specific reminder role-hint diagnostic";
275 ReminderInfiniteDiscardable, "HARN-RMD-004", Rmd, "discardable reminder has no TTL";
276 ReminderUnknownPropagate, "HARN-RMD-005", Rmd, "reminder propagate value is not recognized";
277 ReminderProviderMalformedSpec, "HARN-RMD-006", Rmd, "reminder provider returned a malformed reminder spec";
278 ReminderProviderBloat, "HARN-RMD-007", Rmd, "too many reminder providers are enabled";
279 ReminderUnsupportedHookEvent, "HARN-RMD-008", Rmd, "hook event does not support reminder effects";
280 SuspendWorkerNotRunning, "HARN-SUS-001", Sus, "suspend_agent target worker is not running";
281 ResumeConditionsInvalid, "HARN-SUS-002", Sus, "ResumeConditions validation failed";
282 ResumeWorkerNotSuspended, "HARN-SUS-003", Sus, "resume_agent target worker is not suspended";
283 ResumeSnapshotInvalid, "HARN-SUS-004", Sus, "resume snapshot cannot be loaded or used";
284 AwaitResumptionOutsideAgentLoop, "HARN-SUS-005", Sus, "agent_await_resumption was invoked outside agent_loop structural handling";
285 ConcurrentResumeConflict, "HARN-SUS-006", Sus, "concurrent resume changed the worker before resume could complete";
286 ResumeTriggerRegistrationFailed, "HARN-SUS-007", Sus, "ResumeConditions trigger could not be registered";
287 ResumeTimeoutUnsupported, "HARN-SUS-008", Sus, "resume timeout action is unsupported";
288 ResumeInputInvalid, "HARN-SUS-009", Sus, "resume input failed agent_loop input validation";
289 ResumeWorkerClosed, "HARN-SUS-010", Sus, "closed suspended worker cannot be resumed";
290 ReplayResumeInputHashMismatch, "HARN-SUS-011", Sus, "replay resume input hash diverges from journaled suspension";
291 ReplayDrainDecisionPromptHashMismatch, "HARN-SUS-012", Sus, "replay drain decision prompt hash diverges from journaled receipt";
292 LifecycleSignatureMismatch, "HARN-SUS-013", Sus, "lifecycle receipt signed timestamp failed verification";
293 LintRenamedStdlibSymbol, "HARN-LNT-001", Lnt, "renamed stdlib symbol lint";
294 LintCyclomaticComplexity, "HARN-LNT-002", Lnt, "cyclomatic complexity lint";
295 LintNamingConvention, "HARN-LNT-003", Lnt, "naming convention lint";
296 LintEagerCollectionConversion, "HARN-LNT-004", Lnt, "eager collection conversion lint";
297 LintRedundantClone, "HARN-LNT-005", Lnt, "redundant clone lint";
298 LintLongRunningWithoutCleanup, "HARN-LNT-006", Lnt, "long-running workflow cleanup lint";
299 LintMcpToolAnnotations, "HARN-LNT-007", Lnt, "MCP tool annotations lint";
300 LintPrOpenWithoutSecretScan, "HARN-LNT-008", Lnt, "PR open without secret scan lint";
301 LintShadowVariable, "HARN-LNT-009", Lnt, "shadow variable lint";
302 LintPersonaHookTarget, "HARN-LNT-010", Lnt, "persona hook target lint";
303 LintDeadCodeAfterReturn, "HARN-LNT-011", Lnt, "dead code after return lint";
304 LintLetThenReturn, "HARN-LNT-012", Lnt, "let then return lint";
305 LintUnhandledApprovalResult, "HARN-LNT-013", Lnt, "unhandled approval result lint";
306 LintUnusedVariable, "HARN-LNT-014", Lnt, "unused variable lint";
307 LintUnusedPatternBinding, "HARN-LNT-015", Lnt, "unused pattern binding lint";
308 LintUnusedParameter, "HARN-LNT-016", Lnt, "unused parameter lint";
309 LintUnusedImport, "HARN-LNT-017", Lnt, "unused import lint";
310 LintMutableNeverReassigned, "HARN-LNT-018", Lnt, "mutable never reassigned lint";
311 LintUnusedFunction, "HARN-LNT-019", Lnt, "unused function lint";
312 LintUnusedType, "HARN-LNT-020", Lnt, "unused type lint";
313 LintPersonaBodyMustCallSteps, "HARN-LNT-021", Lnt, "persona body must call steps lint";
314 LintUndefinedFunction, "HARN-LNT-022", Lnt, "undefined function lint";
315 LintPipelineReturnType, "HARN-LNT-023", Lnt, "pipeline return type lint";
316 LintMissingHarndoc, "HARN-LNT-024", Lnt, "missing harndoc lint";
317 LintAssertOutsideTest, "HARN-LNT-025", Lnt, "assert outside test lint";
318 LintPromptInjectionRisk, "HARN-LNT-026", Lnt, "prompt injection risk lint";
319 LintConnectorEffectPolicy, "HARN-LNT-027", Lnt, "connector effect policy lint";
320 LintUnnecessaryCast, "HARN-LNT-028", Lnt, "unnecessary cast lint";
321 LintUntypedDictAccess, "HARN-LNT-029", Lnt, "untyped dict access lint";
322 LintConstantLogicalOperand, "HARN-LNT-030", Lnt, "constant logical operand lint";
323 LintPointlessComparison, "HARN-LNT-031", Lnt, "pointless comparison lint";
324 LintComparisonToBool, "HARN-LNT-032", Lnt, "comparison to bool lint";
325 LintInvalidBinaryOpLiteral, "HARN-LNT-033", Lnt, "invalid binary operator literal lint";
326 LintRedundantNilTernary, "HARN-LNT-034", Lnt, "redundant nil ternary lint";
327 LintEmptyBlock, "HARN-LNT-035", Lnt, "empty block lint";
328 LintUnnecessaryElseReturn, "HARN-LNT-036", Lnt, "unnecessary else return lint";
329 LintDuplicateMatchArm, "HARN-LNT-037", Lnt, "duplicate match arm lint";
330 LintRequireInTest, "HARN-LNT-038", Lnt, "require in test lint";
331 LintBreakOutsideLoop, "HARN-LNT-039", Lnt, "break outside loop lint";
332 LintTemplateParse, "HARN-LNT-040", Lnt, "template parse lint";
333 LintBlankLineBetweenItems, "HARN-LNT-041", Lnt, "blank line between items lint";
334 LintTrailingComma, "HARN-LNT-042", Lnt, "trailing comma lint";
335 LintUnnecessaryParentheses, "HARN-LNT-043", Lnt, "unnecessary parentheses lint";
336 LintTemplateVariantExplosion, "HARN-LNT-044", Lnt, "template variant explosion lint";
337 LintRequireFileHeader, "HARN-LNT-045", Lnt, "require file header lint";
338 LintTemplateProviderIdentityBranch, "HARN-LNT-046", Lnt, "template provider identity branch lint";
339 LintImportOrder, "HARN-LNT-047", Lnt, "import order lint";
340 LintPreferOptionalShorthand, "HARN-LNT-048", Lnt, "prefer optional shorthand lint";
341 LintLegacyDocComment, "HARN-LNT-049", Lnt, "legacy doc comment lint";
342 LintDeprecatedLlmOptions, "HARN-LNT-050", Lnt, "deprecated LLM options lint";
343 LintUnnecessarySafeNavigation, "HARN-LNT-051", Lnt, "unnecessary safe navigation lint";
344 LintAmbientClockBuiltin, "HARN-LNT-052", Lnt, "ambient clock builtin replaced by `harness.clock.*`";
345 LintAmbientStdioBuiltin, "HARN-LNT-053", Lnt, "ambient stdio builtin replaced by `harness.stdio.*`";
346 LintAmbientFsBuiltin, "HARN-LNT-054", Lnt, "ambient fs builtin replaced by `harness.fs.*`";
347 LintAmbientEnvBuiltin, "HARN-LNT-055", Lnt, "ambient env builtin replaced by `harness.env.*`";
348 LintAmbientRandomBuiltin, "HARN-LNT-056", Lnt, "ambient random builtin replaced by `harness.random.*`";
349 LintAmbientNetBuiltin, "HARN-LNT-057", Lnt, "ambient net builtin replaced by `harness.net.*`";
350 LintVacuousCondition, "HARN-LNT-058", Lnt, "if / while / guard condition is statically known to always succeed or always fail";
351 LintRuleEngine, "HARN-LNT-059", Lnt, "project rule-engine or native lint rule";
352 LintUnnormalizedOptions, "HARN-LNT-060", Lnt, "inline options dict bypasses the typed option constructors";
353 LintNilCoalesceNoop, "HARN-LNT-061", Lnt, "nil coalesce fallback is nil";
354 LintNilCoalesceUnreachableFallback, "HARN-LNT-062", Lnt, "nil coalesce fallback is unreachable";
355 LintUnnecessaryNonNullAssert, "HARN-LNT-063", Lnt, "non-null assertion `!` on an already-non-nil value";
356 LintMutableCaptureAcrossParallel, "HARN-LNT-064", Lnt, "a mutable variable captured from an enclosing scope is reassigned inside a `parallel`/`spawn` body, so concurrent branches share one cell and race";
357 LintNilCoalesceSelfFallback, "HARN-LNT-065", Lnt, "nil coalesce fallback repeats the left identifier";
358 LintDiscardedPureResult, "HARN-LNT-066", Lnt, "the result of a pure collection method is discarded, so the call has no effect on the receiver";
359 LintMissingPublicApiType, "HARN-LNT-067", Lnt, "public callable parameter or return is missing an explicit type";
360 LintTemplateUnknownFilter, "HARN-LNT-068", Lnt, "prompt template names a filter the engine does not implement";
361 LintBroadHarnessParameter, "HARN-LNT-069", Lnt, "helper accepts root Harness but uses only narrow capability handles";
362 LintHomogeneousPositionalApi, "HARN-LNT-070", Lnt, "public API has too many same-typed positional parameters";
363 SandboxCapabilityDenied, "HARN-CAP-201", Cap, "harness capability denied by active sandbox profile";
364 FormatterParseFailed, "HARN-FMT-001", Fmt, "formatter could not parse the source";
365 FormatterWouldReformat, "HARN-FMT-002", Fmt, "source is not in canonical format";
366 FormatterTrailingComma, "HARN-FMT-003", Fmt, "formatter normalized trailing comma layout";
367 ImportResolutionFailed, "HARN-IMP-001", Imp, "import target cannot be resolved";
368 ImportSymbolMissing, "HARN-IMP-002", Imp, "imported symbol does not exist";
369 ImportCycle, "HARN-IMP-003", Imp, "import graph contains a cycle";
370 ImmutableAssignment, "HARN-OWN-001", Own, "immutable binding is reassigned";
371 MutableNeverReassigned, "HARN-OWN-002", Own, "mutable binding is never reassigned";
372 OwnershipEscape, "HARN-OWN-003", Own, "owned value escapes its valid scope";
373 BoundaryValueUnvalidated, "HARN-OWN-004", Own, "unvalidated boundary value is used directly";
374 RescueOutsideFunction, "HARN-RCV-001", Rcv, "rescue construct is outside a function body";
375 TryOutsideFunction, "HARN-RCV-002", Rcv, "try construct is outside a function body";
376 InvalidRescueConstruct, "HARN-RCV-003", Rcv, "rescue construct is invalid";
377 NonExhaustiveMatch, "HARN-MAT-001", Mat, "match expression is not exhaustive";
378 DuplicateMatchArm, "HARN-MAT-002", Mat, "match expression contains a duplicate arm";
379 InvalidMatchPattern, "HARN-MAT-003", Mat, "match pattern is invalid";
380 PoolBackpressureFull, "HARN-POL-001", Pol, "pool backpressure rejected a submit";
381 PoolFailFastFull, "HARN-POL-002", Pol, "fail-fast pool has no immediate capacity";
382 ConstEvalDisallowedExpression, "HARN-MET-001", Met, "expression is not permitted in a const initializer";
383 ConstEvalStepLimit, "HARN-CST-001", Cst, "const initializer exceeded the step budget";
384 ConstEvalRecursionLimit, "HARN-CST-002", Cst, "const initializer exceeded the recursion depth budget";
385 ConstEvalSandboxViolation, "HARN-CST-003", Cst, "const initializer attempted a sandboxed capability";
386 ConstEvalRuntimeError, "HARN-CST-004", Cst, "const initializer raised a runtime error during evaluation";
387}
388impl Code {
389 pub const fn registry() -> &'static [RegistryEntry] {
390 REGISTRY
391 }
392
393 pub const fn related(self) -> &'static [Code] {
398 match self {
399 Code::TypeMismatch => &[
402 Code::AssignmentTypeMismatch,
403 Code::ArgumentTypeMismatch,
404 Code::ReturnTypeMismatch,
405 Code::VariableTypeMismatch,
406 Code::FieldTypeMismatch,
407 ],
408 Code::AssignmentTypeMismatch => &[Code::TypeMismatch, Code::VariableTypeMismatch],
409 Code::ArgumentTypeMismatch => &[Code::TypeMismatch, Code::GenericTypeArgumentMismatch],
410 Code::ReturnTypeMismatch => &[Code::TypeMismatch, Code::ClosureReturnTypeMismatch],
411 Code::VariableTypeMismatch => &[Code::TypeMismatch, Code::AssignmentTypeMismatch],
412 Code::ClosureReturnTypeMismatch => &[Code::ReturnTypeMismatch],
413 Code::FieldTypeMismatch => &[Code::TypeMismatch, Code::InvalidStructLiteral],
414 Code::MethodTypeMismatch => &[Code::TypeMismatch, Code::CallableExpected],
415 Code::GenericTypeArgumentUnsupported => &[
417 Code::GenericTypeArgumentMismatch,
418 Code::GenericTypeArgumentArity,
419 ],
420 Code::GenericTypeArgumentMismatch => &[
421 Code::GenericTypeArgumentArity,
422 Code::WhereConstraintMismatch,
423 ],
424 Code::GenericTypeArgumentArity => {
425 &[Code::GenericTypeArgumentMismatch, Code::TypeParameterArity]
426 }
427 Code::TypeParameterArity => &[Code::GenericTypeArgumentArity],
428 Code::WhereConstraintMismatch => &[Code::GenericTypeArgumentMismatch],
429 Code::UndefinedVariable => &[Code::UndefinedFunction, Code::UnknownDeclaration],
431 Code::UndefinedFunction => &[Code::UnknownBuiltin, Code::UnknownDeclaration],
432 Code::UnknownField => &[Code::UnknownMethod, Code::InvalidStructLiteral],
433 Code::UnknownMethod => &[Code::UnknownField, Code::CallableExpected],
434 Code::UnknownAttribute => {
435 &[Code::InvalidAttributeArgument, Code::InvalidAttributeTarget]
436 }
437 Code::InvalidAttributeArgument => {
438 &[Code::UnknownAttribute, Code::InvalidAttributeTarget]
439 }
440 Code::InvalidAttributeTarget => {
441 &[Code::UnknownAttribute, Code::InvalidAttributeArgument]
442 }
443 Code::LlmSchemaMissing => &[Code::LlmSchemaInvalid, Code::UnknownLlmOption],
445 Code::LlmSchemaInvalid => &[Code::LlmSchemaMissing, Code::UnknownLlmOption],
446 Code::UnknownLlmOption => &[Code::DeprecatedLlmOption, Code::LlmSchemaInvalid],
447 Code::DeprecatedLlmOption => &[Code::UnknownLlmOption],
448 Code::LlmProviderIdentityBranch => &[Code::PromptProviderIdentityBranch],
449 Code::PromptTemplateParse => &[Code::PromptTargetMissing],
451 Code::PromptInjectionRisk => &[Code::LintPromptInjectionRisk],
452 Code::PromptProviderIdentityBranch => &[
453 Code::LlmProviderIdentityBranch,
454 Code::LintTemplateProviderIdentityBranch,
455 ],
456 Code::PromptVariantExplosion => &[Code::LintTemplateVariantExplosion],
457 Code::CapabilityResultUnchecked => {
459 &[Code::RescueOutsideFunction, Code::TryOutsideFunction]
460 }
461 Code::CapabilityUnknownOperation => &[Code::CapabilityCallStaticNameRequired],
462 Code::EffectInheritanceViolation => &[
463 Code::CapabilityPayloadInvalid,
464 Code::CapabilityBindingInvalid,
465 ],
466 Code::RescueOutsideFunction => {
468 &[Code::TryOutsideFunction, Code::InvalidRescueConstruct]
469 }
470 Code::TryOutsideFunction => &[Code::RescueOutsideFunction],
471 Code::NonExhaustiveMatch => &[Code::InvalidMatchPattern, Code::DuplicateMatchArm],
472 Code::DuplicateMatchArm => &[Code::NonExhaustiveMatch, Code::LintDuplicateMatchArm],
473 Code::ModuleImportUnresolved => {
475 &[Code::ImportResolutionFailed, Code::ImportSymbolMissing]
476 }
477 Code::ModuleImportUnused => &[Code::LintUnusedImport],
478 Code::ImportResolutionFailed => {
479 &[Code::ModuleImportUnresolved, Code::ImportSymbolMissing]
480 }
481 Code::ImportCycle => &[Code::ImportResolutionFailed],
482 Code::SuspendWorkerNotRunning => {
484 &[Code::ResumeWorkerNotSuspended, Code::ResumeWorkerClosed]
485 }
486 Code::ResumeConditionsInvalid => &[
487 Code::ResumeTriggerRegistrationFailed,
488 Code::ResumeTimeoutUnsupported,
489 ],
490 Code::ResumeWorkerNotSuspended => &[
491 Code::SuspendWorkerNotRunning,
492 Code::ConcurrentResumeConflict,
493 ],
494 Code::ResumeSnapshotInvalid => &[Code::ResumeWorkerNotSuspended],
495 Code::AwaitResumptionOutsideAgentLoop => &[Code::ResumeConditionsInvalid],
496 Code::ConcurrentResumeConflict => {
497 &[Code::ResumeWorkerNotSuspended, Code::ResumeWorkerClosed]
498 }
499 Code::ResumeTriggerRegistrationFailed => &[
500 Code::ResumeConditionsInvalid,
501 Code::ResumeTimeoutUnsupported,
502 ],
503 Code::ResumeTimeoutUnsupported => &[
504 Code::ResumeConditionsInvalid,
505 Code::ResumeTriggerRegistrationFailed,
506 ],
507 Code::ResumeInputInvalid => &[Code::ResumeWorkerNotSuspended],
508 Code::ResumeWorkerClosed => &[
509 Code::ResumeWorkerNotSuspended,
510 Code::ConcurrentResumeConflict,
511 ],
512 Code::ReminderUnknownOption => {
516 &[Code::ReminderInvalidShape, Code::ReminderUnknownPropagate]
517 }
518 Code::ReminderInvalidShape => {
519 &[Code::ReminderUnknownOption, Code::ReminderUnknownPropagate]
520 }
521 Code::ReminderUnknownPropagate => {
522 &[Code::ReminderUnknownOption, Code::ReminderInvalidShape]
523 }
524 Code::ReminderProviderMalformedSpec => &[Code::ReminderInvalidShape],
525 Code::ReminderProviderBloat => &[Code::ReminderInfiniteDiscardable],
526 Code::ReminderUnsupportedHookEvent => &[Code::ReminderProviderMalformedSpec],
527 Code::ImmutableAssignment => &[Code::MutableNeverReassigned],
529 Code::MutableNeverReassigned => &[Code::LintMutableNeverReassigned],
530 Code::LintDeprecatedLlmOptions => &[Code::DeprecatedLlmOption, Code::UnknownLlmOption],
532 Code::LintUnnormalizedOptions => {
533 &[Code::LintDeprecatedLlmOptions, Code::LintUntypedDictAccess]
534 }
535 Code::LintPromptInjectionRisk => &[Code::PromptInjectionRisk],
536 Code::LintTemplateVariantExplosion => &[Code::PromptVariantExplosion],
537 Code::LintTemplateProviderIdentityBranch => &[Code::PromptProviderIdentityBranch],
538 Code::LintRenamedStdlibSymbol => &[Code::DeprecatedStdlibSymbol],
539 Code::LintAmbientClockBuiltin
540 | Code::LintAmbientStdioBuiltin
541 | Code::LintAmbientFsBuiltin
542 | Code::LintAmbientEnvBuiltin
543 | Code::LintAmbientRandomBuiltin
544 | Code::LintAmbientNetBuiltin => {
545 &[Code::InvalidMainSignature, Code::LintRenamedStdlibSymbol]
546 }
547 Code::SandboxCapabilityDenied => &[Code::CapabilityPayloadInvalid],
548 Code::LintMutableNeverReassigned => &[Code::MutableNeverReassigned],
549 Code::LintUnusedImport => &[Code::ModuleImportUnused],
550 Code::LintDuplicateMatchArm => &[Code::DuplicateMatchArm],
551 _ => &[],
552 }
553 }
554}
555
556impl fmt::Display for Code {
557 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
558 f.write_str(self.as_str())
559 }
560}
561
562#[derive(Debug, Clone, Copy, PartialEq, Eq)]
564pub struct ParseCodeError;
565
566impl fmt::Display for ParseCodeError {
567 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
568 f.write_str("unknown Harn diagnostic code")
569 }
570}
571
572impl std::error::Error for ParseCodeError {}
573
574impl FromStr for Code {
575 type Err = ParseCodeError;
576
577 fn from_str(value: &str) -> Result<Self, Self::Err> {
578 Code::ALL
579 .iter()
580 .copied()
581 .find(|code| code.as_str() == value)
582 .ok_or(ParseCodeError)
583 }
584}
585
586#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
596pub enum RepairSafety {
597 FormatOnly,
600 BehaviorPreserving,
603 ScopeLocal,
607 SurfaceChanging,
610 CapabilityChanging,
614 NeedsHuman,
618}
619
620impl RepairSafety {
621 pub const ALL: &'static [RepairSafety] = &[
622 RepairSafety::FormatOnly,
623 RepairSafety::BehaviorPreserving,
624 RepairSafety::ScopeLocal,
625 RepairSafety::SurfaceChanging,
626 RepairSafety::CapabilityChanging,
627 RepairSafety::NeedsHuman,
628 ];
629
630 pub const fn as_str(self) -> &'static str {
634 match self {
635 RepairSafety::FormatOnly => "format-only",
636 RepairSafety::BehaviorPreserving => "behavior-preserving",
637 RepairSafety::ScopeLocal => "scope-local",
638 RepairSafety::SurfaceChanging => "surface-changing",
639 RepairSafety::CapabilityChanging => "capability-changing",
640 RepairSafety::NeedsHuman => "needs-human",
641 }
642 }
643
644 pub const fn is_at_most(self, ceiling: RepairSafety) -> bool {
648 (self as u8) <= (ceiling as u8)
649 }
650}
651
652impl fmt::Display for RepairSafety {
653 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
654 f.write_str(self.as_str())
655 }
656}
657
658#[derive(Debug, Clone, Copy, PartialEq, Eq)]
660pub struct ParseRepairSafetyError;
661
662impl fmt::Display for ParseRepairSafetyError {
663 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
664 f.write_str("unknown Harn repair-safety class")
665 }
666}
667
668impl std::error::Error for ParseRepairSafetyError {}
669
670impl FromStr for RepairSafety {
671 type Err = ParseRepairSafetyError;
672
673 fn from_str(value: &str) -> Result<Self, Self::Err> {
674 RepairSafety::ALL
675 .iter()
676 .copied()
677 .find(|safety| safety.as_str() == value)
678 .ok_or(ParseRepairSafetyError)
679 }
680}
681
682#[derive(Debug, Clone, PartialEq, Eq, Hash)]
688pub struct RepairId(std::borrow::Cow<'static, str>);
689
690impl RepairId {
691 pub const fn from_static(s: &'static str) -> Self {
692 RepairId(std::borrow::Cow::Borrowed(s))
693 }
694
695 pub fn from_owned(s: String) -> Self {
696 RepairId(std::borrow::Cow::Owned(s))
697 }
698
699 pub fn as_str(&self) -> &str {
700 &self.0
701 }
702}
703
704impl fmt::Display for RepairId {
705 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
706 f.write_str(&self.0)
707 }
708}
709
710#[derive(Debug, Clone)]
718pub struct Repair {
719 pub id: RepairId,
720 pub summary: String,
721 pub safety: RepairSafety,
722}
723
724impl Repair {
725 pub fn from_template(template: &RepairTemplate) -> Self {
726 Repair {
727 id: RepairId::from_static(template.id),
728 summary: template.summary.to_string(),
729 safety: template.safety,
730 }
731 }
732}
733
734#[derive(Debug, Clone, Copy)]
741pub struct RepairTemplate {
742 pub id: &'static str,
743 pub summary: &'static str,
744 pub safety: RepairSafety,
745}
746
747impl Code {
748 pub const fn repair_template(self) -> Option<&'static RepairTemplate> {
751 match self {
752 Code::TypeMismatch
754 | Code::ReturnTypeMismatch
755 | Code::AssignmentTypeMismatch
756 | Code::ArgumentTypeMismatch
757 | Code::VariableTypeMismatch
758 | Code::ClosureReturnTypeMismatch
759 | Code::FieldTypeMismatch
760 | Code::MethodTypeMismatch
761 | Code::InvalidIndexType => Some(&REPAIR_INSERT_EXPLICIT_CONVERSION),
762 Code::StringInterpolationRewrite => Some(&REPAIR_REWRITE_STRING_INTERPOLATION),
763 Code::UnknownTypeName => Some(&REPAIR_IMPORTS_FIX_PATH),
764 Code::InvalidCast => Some(&REPAIR_CASTS_REMOVE_UNCHECKED),
765
766 Code::UndefinedVariable
768 | Code::UndefinedFunction
769 | Code::UnknownField
770 | Code::UnknownMethod
771 | Code::UnknownBuiltin
772 | Code::UnknownDeclaration => Some(&REPAIR_BINDINGS_RENAME_TO_CLOSEST),
773 Code::InvalidMainSignature => Some(&REPAIR_BINDINGS_THREAD_HARNESS_NEEDS_PARAM),
774 Code::DeprecatedFunction => Some(&REPAIR_STDLIB_MIGRATE_RENAMED),
775 Code::ModuleImportUnresolved | Code::ImportResolutionFailed => {
776 Some(&REPAIR_IMPORTS_FIX_PATH)
777 }
778 Code::ModuleImportUnused => Some(&REPAIR_IMPORTS_REMOVE_UNUSED),
779 Code::ModuleImportOrder => Some(&REPAIR_IMPORTS_REORDER),
780
781 Code::CapabilityResultUnchecked => Some(&REPAIR_ERRORS_CHECK_OR_RESCUE),
783 Code::CapabilityBindingInvalid => Some(&REPAIR_MANUAL_REVIEW_CAPABILITY),
784 Code::EffectInheritanceViolation => Some(&REPAIR_POLICY_NARROW_CHILD_EFFECTS),
785 Code::RescueOutsideFunction | Code::TryOutsideFunction => {
786 Some(&REPAIR_ERRORS_WRAP_IN_FN)
787 }
788
789 Code::DeprecatedLlmOption => Some(&REPAIR_LLM_MIGRATE_DEPRECATED_OPTION),
791 Code::LlmSchemaMissing => Some(&REPAIR_LLM_ADD_SCHEMA),
792 Code::LlmProviderIdentityBranch | Code::PromptProviderIdentityBranch => {
793 Some(&REPAIR_LLM_USE_CAPABILITY_FLAG)
794 }
795 Code::PromptInjectionRisk => Some(&REPAIR_PROMPTS_ESCAPE_INJECTION),
796 Code::PromptToolSurfaceUnknown | Code::PromptToolSurfaceDeferredReference => {
797 Some(&REPAIR_PROMPTS_ADD_TOOL_TO_SURFACE)
798 }
799 Code::PromptVariantExplosion => Some(&REPAIR_MANUAL_NEEDS_HUMAN),
800
801 Code::DeprecatedStdlibSymbol => Some(&REPAIR_STDLIB_MIGRATE_RENAMED),
803 Code::LintMissingStdlibMetadata => Some(&REPAIR_DOC_ADD_STDLIB_METADATA),
804
805 Code::ImmutableAssignment => Some(&REPAIR_BINDINGS_MAKE_MUTABLE),
807 Code::MutableNeverReassigned => Some(&REPAIR_BINDINGS_MAKE_IMMUTABLE),
808
809 Code::NonExhaustiveMatch => Some(&REPAIR_MATCH_ADD_MISSING_ARMS),
811 Code::DuplicateMatchArm => Some(&REPAIR_MATCH_REMOVE_DUPLICATE_ARM),
812
813 Code::UnreachableCode => Some(&REPAIR_DEAD_CODE_REMOVE),
815
816 Code::FormatterWouldReformat | Code::FormatterTrailingComma => {
818 Some(&REPAIR_FORMAT_REFORMAT)
819 }
820
821 Code::LintUnusedVariable
823 | Code::LintUnusedPatternBinding
824 | Code::LintUnusedParameter => Some(&REPAIR_BINDINGS_RENAME_UNUSED),
825 Code::LintUnusedImport => Some(&REPAIR_IMPORTS_REMOVE_UNUSED),
826 Code::LintUnusedFunction | Code::LintUnusedType => {
827 Some(&REPAIR_DECLARATIONS_REMOVE_UNUSED)
828 }
829 Code::LintMutableNeverReassigned => Some(&REPAIR_BINDINGS_MAKE_IMMUTABLE),
830 Code::LintImportOrder => Some(&REPAIR_IMPORTS_REORDER),
831 Code::LintBlankLineBetweenItems
832 | Code::LintTrailingComma
833 | Code::LintUnnecessaryParentheses
834 | Code::LintRequireFileHeader => Some(&REPAIR_FORMAT_REFORMAT),
835 Code::LintLegacyDocComment => Some(&REPAIR_DOC_COMMENT_MIGRATE),
836 Code::LintEmptyBlock => Some(&REPAIR_BLOCK_REMOVE_EMPTY),
837 Code::LintUnnecessaryElseReturn | Code::LintLetThenReturn => {
838 Some(&REPAIR_CONTROL_FLOW_FLATTEN)
839 }
840 Code::LintNilCoalesceNoop
841 | Code::LintNilCoalesceSelfFallback
842 | Code::LintRedundantNilTernary
843 | Code::LintUnnecessarySafeNavigation
844 | Code::LintUnnecessaryNonNullAssert
845 | Code::LintPreferOptionalShorthand
846 | Code::LintComparisonToBool
847 | Code::LintPointlessComparison
848 | Code::LintConstantLogicalOperand => Some(&REPAIR_EXPRESSION_SIMPLIFY),
849 Code::LintUnnecessaryCast => Some(&REPAIR_CASTS_REMOVE_REDUNDANT),
850 Code::LintRedundantClone => Some(&REPAIR_CLONE_REMOVE_REDUNDANT),
851 Code::LintEagerCollectionConversion => Some(&REPAIR_COLLECTION_PREFER_LAZY),
852 Code::LintDeadCodeAfterReturn => Some(&REPAIR_DEAD_CODE_REMOVE),
853 Code::LintRenamedStdlibSymbol => Some(&REPAIR_STDLIB_MIGRATE_RENAMED),
854 Code::LintAmbientClockBuiltin => Some(&REPAIR_BINDINGS_THREAD_HARNESS_CLOCK),
855 Code::LintAmbientFsBuiltin => Some(&REPAIR_BINDINGS_THREAD_HARNESS_FS),
856 Code::LintAmbientEnvBuiltin => Some(&REPAIR_BINDINGS_THREAD_HARNESS_ENV),
857 Code::LintAmbientRandomBuiltin => Some(&REPAIR_BINDINGS_THREAD_HARNESS_RANDOM),
858 Code::LintAmbientNetBuiltin => Some(&REPAIR_BINDINGS_THREAD_HARNESS_NET),
859 Code::LintAmbientStdioBuiltin => Some(&REPAIR_BINDINGS_THREAD_HARNESS),
860 Code::LintDeprecatedLlmOptions => Some(&REPAIR_LLM_MIGRATE_DEPRECATED_OPTION),
861 Code::LintTemplateProviderIdentityBranch => Some(&REPAIR_LLM_USE_CAPABILITY_FLAG),
862 Code::LintPromptInjectionRisk => Some(&REPAIR_PROMPTS_ESCAPE_INJECTION),
863 Code::LintShadowVariable => Some(&REPAIR_BINDINGS_RENAME_SHADOW),
864 Code::LintNamingConvention => Some(&REPAIR_STYLE_RENAME_TO_CONVENTION),
865 Code::LintUnhandledApprovalResult => Some(&REPAIR_ERRORS_CHECK_OR_RESCUE),
866 Code::LintMissingHarndoc => Some(&REPAIR_DOC_ADD_HARNDOC),
867 Code::LintDuplicateMatchArm => Some(&REPAIR_MATCH_REMOVE_DUPLICATE_ARM),
868 Code::LintUntypedDictAccess => Some(&REPAIR_TYPES_ADD_SHAPE_ANNOTATION),
869 Code::LintUnnormalizedOptions => Some(&REPAIR_TYPES_ADD_SHAPE_ANNOTATION),
870 Code::LintMcpToolAnnotations => Some(&REPAIR_MANUAL_NEEDS_HUMAN),
871 Code::LintTemplateVariantExplosion | Code::LintLongRunningWithoutCleanup => {
872 Some(&REPAIR_MANUAL_NEEDS_HUMAN)
873 }
874
875 _ => None,
879 }
880 }
881}
882
883const REPAIR_INSERT_EXPLICIT_CONVERSION: RepairTemplate = RepairTemplate {
898 id: "casts/insert-explicit-conversion",
899 summary: "Insert an explicit conversion or correct the operand type",
900 safety: RepairSafety::ScopeLocal,
901};
902
903const REPAIR_REWRITE_STRING_INTERPOLATION: RepairTemplate = RepairTemplate {
904 id: "style/string-interpolation",
905 summary: "Rewrite string concatenation as an interpolation literal",
906 safety: RepairSafety::BehaviorPreserving,
907};
908
909const REPAIR_CASTS_REMOVE_UNCHECKED: RepairTemplate = RepairTemplate {
910 id: "casts/remove-unchecked",
911 summary: "Remove the unchecked cast or guard it with a type test",
912 safety: RepairSafety::ScopeLocal,
913};
914
915const REPAIR_CASTS_REMOVE_REDUNDANT: RepairTemplate = RepairTemplate {
916 id: "casts/remove-redundant",
917 summary: "Remove the redundant cast",
918 safety: RepairSafety::BehaviorPreserving,
919};
920
921const REPAIR_BINDINGS_RENAME_TO_CLOSEST: RepairTemplate = RepairTemplate {
922 id: "bindings/rename-to-closest",
923 summary: "Rename to the closest in-scope identifier",
924 safety: RepairSafety::ScopeLocal,
925};
926
927const REPAIR_BINDINGS_MAKE_MUTABLE: RepairTemplate = RepairTemplate {
928 id: "bindings/make-mutable",
929 summary: "Declare the binding with `let` so it can be reassigned",
930 safety: RepairSafety::ScopeLocal,
931};
932
933const REPAIR_BINDINGS_MAKE_IMMUTABLE: RepairTemplate = RepairTemplate {
934 id: "bindings/make-immutable",
935 summary: "Declare the never-reassigned binding with `const` instead of `let`",
936 safety: RepairSafety::BehaviorPreserving,
937};
938
939const REPAIR_BINDINGS_RENAME_UNUSED: RepairTemplate = RepairTemplate {
940 id: "bindings/rename-unused",
941 summary: "Use the `_` discard binding for an unused binding",
942 safety: RepairSafety::BehaviorPreserving,
943};
944
945const REPAIR_BINDINGS_RENAME_SHADOW: RepairTemplate = RepairTemplate {
946 id: "bindings/rename-shadow",
947 summary: "Rename the shadowing binding to a distinct name",
948 safety: RepairSafety::ScopeLocal,
949};
950
951const REPAIR_BINDINGS_THREAD_HARNESS: RepairTemplate = RepairTemplate {
952 id: "bindings/thread-harness",
953 summary: "Thread the existing `harness` binding through local helper calls and replace the ambient stdio builtin with `harness.stdio.*`",
954 safety: RepairSafety::ScopeLocal,
955};
956
957const REPAIR_BINDINGS_THREAD_HARNESS_NEEDS_PARAM: RepairTemplate = RepairTemplate {
958 id: "bindings/thread-harness-needs-param",
959 summary: "Add a `harness: Harness` parameter where the stdio capability handle is required and update local callers",
960 safety: RepairSafety::SurfaceChanging,
961};
962
963const REPAIR_BINDINGS_THREAD_HARNESS_CLOCK: RepairTemplate = RepairTemplate {
964 id: "bindings/thread-harness-clock",
965 summary: "Replace the ambient clock builtin with the corresponding `harness.clock.*` method",
966 safety: RepairSafety::ScopeLocal,
967};
968
969const REPAIR_BINDINGS_THREAD_HARNESS_FS: RepairTemplate = RepairTemplate {
970 id: "bindings/thread-harness-fs",
971 summary: "Replace the ambient fs builtin with the corresponding `harness.fs.*` method",
972 safety: RepairSafety::ScopeLocal,
973};
974
975const REPAIR_BINDINGS_THREAD_HARNESS_ENV: RepairTemplate = RepairTemplate {
976 id: "bindings/thread-harness-env",
977 summary: "Replace the ambient env builtin with the corresponding `harness.env.*` method",
978 safety: RepairSafety::ScopeLocal,
979};
980
981const REPAIR_BINDINGS_THREAD_HARNESS_RANDOM: RepairTemplate = RepairTemplate {
982 id: "bindings/thread-harness-random",
983 summary: "Replace the ambient random builtin with the corresponding `harness.random.*` method",
984 safety: RepairSafety::ScopeLocal,
985};
986
987const REPAIR_BINDINGS_THREAD_HARNESS_NET: RepairTemplate = RepairTemplate {
988 id: "bindings/thread-harness-net",
989 summary: "Replace the ambient net builtin with the corresponding `harness.net.*` method",
990 safety: RepairSafety::ScopeLocal,
991};
992
993const REPAIR_DECLARATIONS_REMOVE_UNUSED: RepairTemplate = RepairTemplate {
994 id: "declarations/remove-unused",
995 summary: "Remove the unused declaration",
996 safety: RepairSafety::SurfaceChanging,
997};
998
999const REPAIR_IMPORTS_FIX_PATH: RepairTemplate = RepairTemplate {
1000 id: "imports/fix-path",
1001 summary: "Replace the import path with a resolvable target",
1002 safety: RepairSafety::ScopeLocal,
1003};
1004
1005const REPAIR_IMPORTS_REMOVE_UNUSED: RepairTemplate = RepairTemplate {
1006 id: "imports/remove-unused",
1007 summary: "Remove the unused import",
1008 safety: RepairSafety::BehaviorPreserving,
1009};
1010
1011const REPAIR_IMPORTS_REORDER: RepairTemplate = RepairTemplate {
1012 id: "imports/reorder",
1013 summary: "Reorder imports into canonical grouping",
1014 safety: RepairSafety::FormatOnly,
1015};
1016
1017const REPAIR_ERRORS_CHECK_OR_RESCUE: RepairTemplate = RepairTemplate {
1018 id: "errors/check-or-rescue",
1019 summary: "Check the result or wrap the call in a `rescue` block",
1020 safety: RepairSafety::ScopeLocal,
1021};
1022
1023const REPAIR_ERRORS_WRAP_IN_FN: RepairTemplate = RepairTemplate {
1024 id: "errors/wrap-in-fn",
1025 summary: "Move the construct inside a function body",
1026 safety: RepairSafety::SurfaceChanging,
1027};
1028
1029const REPAIR_MATCH_ADD_MISSING_ARMS: RepairTemplate = RepairTemplate {
1030 id: "match/add-missing-arms",
1031 summary: "Add arms covering the missing variants",
1032 safety: RepairSafety::ScopeLocal,
1033};
1034
1035const REPAIR_MATCH_REMOVE_DUPLICATE_ARM: RepairTemplate = RepairTemplate {
1036 id: "match/remove-duplicate-arm",
1037 summary: "Remove the duplicated match arm",
1038 safety: RepairSafety::BehaviorPreserving,
1039};
1040
1041const REPAIR_FORMAT_REFORMAT: RepairTemplate = RepairTemplate {
1042 id: "format/reformat",
1043 summary: "Apply canonical formatting",
1044 safety: RepairSafety::FormatOnly,
1045};
1046
1047const REPAIR_DOC_COMMENT_MIGRATE: RepairTemplate = RepairTemplate {
1048 id: "doc/migrate-comment-style",
1049 summary: "Migrate the legacy comment to canonical doc syntax",
1050 safety: RepairSafety::FormatOnly,
1051};
1052
1053const REPAIR_DOC_ADD_HARNDOC: RepairTemplate = RepairTemplate {
1054 id: "doc/add-harndoc",
1055 summary: "Add a `///` doc comment describing this declaration",
1056 safety: RepairSafety::BehaviorPreserving,
1057};
1058
1059const REPAIR_DOC_ADD_STDLIB_METADATA: RepairTemplate = RepairTemplate {
1060 id: "doc/add-stdlib-metadata",
1061 summary: "Add `@effects` and `@errors` fields to the stdlib function's doc block",
1062 safety: RepairSafety::BehaviorPreserving,
1063};
1064
1065const REPAIR_BLOCK_REMOVE_EMPTY: RepairTemplate = RepairTemplate {
1066 id: "blocks/remove-empty",
1067 summary: "Remove the empty block or fill in an explicit body",
1068 safety: RepairSafety::ScopeLocal,
1069};
1070
1071const REPAIR_CONTROL_FLOW_FLATTEN: RepairTemplate = RepairTemplate {
1072 id: "control-flow/flatten",
1073 summary: "Flatten the unnecessary control flow construct",
1074 safety: RepairSafety::BehaviorPreserving,
1075};
1076
1077const REPAIR_EXPRESSION_SIMPLIFY: RepairTemplate = RepairTemplate {
1078 id: "expressions/simplify",
1079 summary: "Simplify the expression to its canonical form",
1080 safety: RepairSafety::BehaviorPreserving,
1081};
1082
1083const REPAIR_CLONE_REMOVE_REDUNDANT: RepairTemplate = RepairTemplate {
1084 id: "clones/remove-redundant",
1085 summary: "Remove the redundant clone",
1086 safety: RepairSafety::BehaviorPreserving,
1087};
1088
1089const REPAIR_COLLECTION_PREFER_LAZY: RepairTemplate = RepairTemplate {
1090 id: "collections/prefer-lazy",
1091 summary: "Replace the eager collection step with a lazy variant",
1092 safety: RepairSafety::ScopeLocal,
1093};
1094
1095const REPAIR_DEAD_CODE_REMOVE: RepairTemplate = RepairTemplate {
1096 id: "control-flow/remove-dead",
1097 summary: "Remove the unreachable code",
1098 safety: RepairSafety::BehaviorPreserving,
1099};
1100
1101const REPAIR_STDLIB_MIGRATE_RENAMED: RepairTemplate = RepairTemplate {
1102 id: "stdlib/migrate-renamed",
1103 summary: "Rename the call to the renamed stdlib symbol",
1104 safety: RepairSafety::ScopeLocal,
1105};
1106
1107const REPAIR_LLM_MIGRATE_DEPRECATED_OPTION: RepairTemplate = RepairTemplate {
1108 id: "llm/migrate-deprecated-option",
1109 summary: "Replace the deprecated option with its supported equivalent",
1110 safety: RepairSafety::ScopeLocal,
1111};
1112
1113const REPAIR_LLM_ADD_SCHEMA: RepairTemplate = RepairTemplate {
1114 id: "llm/add-schema",
1115 summary: "Add a typed output schema to the LLM call",
1116 safety: RepairSafety::SurfaceChanging,
1117};
1118
1119const REPAIR_LLM_USE_CAPABILITY_FLAG: RepairTemplate = RepairTemplate {
1120 id: "llm/use-capability-flag",
1121 summary: "Branch on a capability flag instead of provider identity",
1122 safety: RepairSafety::CapabilityChanging,
1123};
1124
1125const REPAIR_PROMPTS_ESCAPE_INJECTION: RepairTemplate = RepairTemplate {
1126 id: "prompts/escape-injection",
1127 summary: "Pass the untrusted input through a structured placeholder",
1128 safety: RepairSafety::ScopeLocal,
1129};
1130
1131const REPAIR_PROMPTS_ADD_TOOL_TO_SURFACE: RepairTemplate = RepairTemplate {
1132 id: "prompts/add-tool-to-surface",
1133 summary: "Add the referenced tool to the declared tool surface",
1134 safety: RepairSafety::SurfaceChanging,
1135};
1136
1137const REPAIR_STYLE_RENAME_TO_CONVENTION: RepairTemplate = RepairTemplate {
1138 id: "style/rename-to-convention",
1139 summary: "Rename to match the casing convention for this kind",
1140 safety: RepairSafety::SurfaceChanging,
1141};
1142
1143const REPAIR_TYPES_ADD_SHAPE_ANNOTATION: RepairTemplate = RepairTemplate {
1144 id: "types/add-shape-annotation",
1145 summary: "Annotate the dict with a concrete shape type",
1146 safety: RepairSafety::SurfaceChanging,
1147};
1148
1149const REPAIR_MANUAL_REVIEW_CAPABILITY: RepairTemplate = RepairTemplate {
1150 id: "manual/review-capability-binding",
1151 summary: "Review the capability binding; the fix is not mechanical",
1152 safety: RepairSafety::NeedsHuman,
1153};
1154
1155const REPAIR_POLICY_NARROW_CHILD_EFFECTS: RepairTemplate = RepairTemplate {
1156 id: "policy/narrow-child-effects",
1157 summary: "Narrow the child agent's effects to a subset of the parent's, or widen the parent's declared effects",
1158 safety: RepairSafety::SurfaceChanging,
1159};
1160
1161const REPAIR_MANUAL_NEEDS_HUMAN: RepairTemplate = RepairTemplate {
1162 id: "manual/needs-human",
1163 summary: "Plan a human-led change; auto-apply is not safe here",
1164 safety: RepairSafety::NeedsHuman,
1165};
1166
1167pub const REPAIR_REGISTRY: &[&RepairTemplate] = &[
1171 &REPAIR_INSERT_EXPLICIT_CONVERSION,
1172 &REPAIR_REWRITE_STRING_INTERPOLATION,
1173 &REPAIR_CASTS_REMOVE_UNCHECKED,
1174 &REPAIR_CASTS_REMOVE_REDUNDANT,
1175 &REPAIR_BINDINGS_RENAME_TO_CLOSEST,
1176 &REPAIR_BINDINGS_MAKE_MUTABLE,
1177 &REPAIR_BINDINGS_MAKE_IMMUTABLE,
1178 &REPAIR_BINDINGS_RENAME_UNUSED,
1179 &REPAIR_BINDINGS_RENAME_SHADOW,
1180 &REPAIR_BINDINGS_THREAD_HARNESS,
1181 &REPAIR_BINDINGS_THREAD_HARNESS_NEEDS_PARAM,
1182 &REPAIR_BINDINGS_THREAD_HARNESS_CLOCK,
1183 &REPAIR_BINDINGS_THREAD_HARNESS_FS,
1184 &REPAIR_BINDINGS_THREAD_HARNESS_ENV,
1185 &REPAIR_BINDINGS_THREAD_HARNESS_RANDOM,
1186 &REPAIR_BINDINGS_THREAD_HARNESS_NET,
1187 &REPAIR_DECLARATIONS_REMOVE_UNUSED,
1188 &REPAIR_IMPORTS_FIX_PATH,
1189 &REPAIR_IMPORTS_REMOVE_UNUSED,
1190 &REPAIR_IMPORTS_REORDER,
1191 &REPAIR_ERRORS_CHECK_OR_RESCUE,
1192 &REPAIR_ERRORS_WRAP_IN_FN,
1193 &REPAIR_MATCH_ADD_MISSING_ARMS,
1194 &REPAIR_MATCH_REMOVE_DUPLICATE_ARM,
1195 &REPAIR_FORMAT_REFORMAT,
1196 &REPAIR_DOC_COMMENT_MIGRATE,
1197 &REPAIR_DOC_ADD_HARNDOC,
1198 &REPAIR_DOC_ADD_STDLIB_METADATA,
1199 &REPAIR_BLOCK_REMOVE_EMPTY,
1200 &REPAIR_CONTROL_FLOW_FLATTEN,
1201 &REPAIR_EXPRESSION_SIMPLIFY,
1202 &REPAIR_CLONE_REMOVE_REDUNDANT,
1203 &REPAIR_COLLECTION_PREFER_LAZY,
1204 &REPAIR_DEAD_CODE_REMOVE,
1205 &REPAIR_STDLIB_MIGRATE_RENAMED,
1206 &REPAIR_LLM_MIGRATE_DEPRECATED_OPTION,
1207 &REPAIR_LLM_ADD_SCHEMA,
1208 &REPAIR_LLM_USE_CAPABILITY_FLAG,
1209 &REPAIR_PROMPTS_ESCAPE_INJECTION,
1210 &REPAIR_PROMPTS_ADD_TOOL_TO_SURFACE,
1211 &REPAIR_STYLE_RENAME_TO_CONVENTION,
1212 &REPAIR_TYPES_ADD_SHAPE_ANNOTATION,
1213 &REPAIR_MANUAL_REVIEW_CAPABILITY,
1214 &REPAIR_MANUAL_NEEDS_HUMAN,
1215 &REPAIR_POLICY_NARROW_CHILD_EFFECTS,
1216];
1217
1218#[cfg(test)]
1219mod tests {
1220 use super::{Category, Code, ParseRepairSafetyError, RepairSafety, REPAIR_REGISTRY};
1221 use std::collections::HashSet;
1222 use std::str::FromStr;
1223
1224 #[test]
1225 fn parses_registered_code() {
1226 assert_eq!(Code::from_str("HARN-TYP-014"), Ok(Code::TypeParameterArity));
1227 }
1228
1229 #[test]
1230 fn registry_has_unique_identifiers() {
1231 let mut seen = HashSet::new();
1232 for entry in Code::registry() {
1233 assert!(
1234 seen.insert(entry.identifier),
1235 "duplicate diagnostic code {}",
1236 entry.identifier
1237 );
1238 assert_eq!(entry.code.as_str(), entry.identifier);
1239 assert_eq!(entry.code.category(), entry.category);
1240 let expected_prefix = format!("HARN-{}-", entry.category);
1241 assert!(entry.identifier.starts_with(&expected_prefix));
1242 let suffix = entry.identifier.trim_start_matches(&expected_prefix);
1243 assert_eq!(suffix.len(), 3);
1244 assert!(suffix.chars().all(|ch| ch.is_ascii_digit()));
1245 assert!(!entry.summary.is_empty());
1246 }
1247 assert!(Code::registry().len() >= 40);
1248 }
1249
1250 #[test]
1251 fn every_category_is_populated() {
1252 for category in Category::ALL {
1253 assert!(
1254 Code::registry()
1255 .iter()
1256 .any(|entry| entry.category == *category),
1257 "missing diagnostic code category {category}"
1258 );
1259 }
1260 }
1261
1262 #[test]
1263 fn every_code_has_non_empty_explanation() {
1264 for entry in Code::registry() {
1265 let body = entry.code.explanation();
1266 assert!(
1267 !body.trim().is_empty(),
1268 "diagnostic code {} has an empty explanation file",
1269 entry.identifier
1270 );
1271 assert!(
1272 body.contains(entry.identifier),
1273 "explanation for {} should reference its identifier",
1274 entry.identifier
1275 );
1276 }
1277 }
1278
1279 #[test]
1280 fn related_codes_are_registered_and_non_self() {
1281 for entry in Code::registry() {
1282 for &other in entry.code.related() {
1283 assert_ne!(
1284 other, entry.code,
1285 "{} lists itself as a related code",
1286 entry.identifier
1287 );
1288 assert!(
1289 Code::registry().iter().any(|e| e.code == other),
1290 "{} lists unregistered related code {}",
1291 entry.identifier,
1292 other
1293 );
1294 }
1295 }
1296 }
1297
1298 #[test]
1299 fn repair_safety_string_roundtrip() {
1300 for safety in RepairSafety::ALL {
1301 let parsed = RepairSafety::from_str(safety.as_str()).unwrap();
1302 assert_eq!(parsed, *safety);
1303 assert_eq!(parsed.to_string(), safety.as_str());
1304 }
1305 assert_eq!(
1306 RepairSafety::from_str("not-a-safety-class"),
1307 Err(ParseRepairSafetyError)
1308 );
1309 }
1310
1311 #[test]
1312 fn repair_safety_ordering_is_monotonic_low_to_high() {
1313 let order = RepairSafety::ALL;
1317 for window in order.windows(2) {
1318 assert!(
1319 window[0] < window[1],
1320 "{:?} should be safer than {:?}",
1321 window[0],
1322 window[1]
1323 );
1324 assert!(window[0].is_at_most(window[1]));
1325 assert!(!window[1].is_at_most(window[0]));
1326 }
1327 }
1328
1329 #[test]
1330 fn repair_registry_has_at_least_twenty_entries() {
1331 assert!(
1332 REPAIR_REGISTRY.len() >= 20,
1333 "expected ≥20 repair templates, found {}",
1334 REPAIR_REGISTRY.len()
1335 );
1336 }
1337
1338 #[test]
1339 fn repair_ids_are_kebab_case_namespaced_and_unique() {
1340 let mut seen = HashSet::new();
1341 for template in REPAIR_REGISTRY {
1342 assert!(
1343 seen.insert(template.id),
1344 "duplicate repair id {}",
1345 template.id
1346 );
1347 let (namespace, leaf) = template.id.split_once('/').unwrap_or_else(|| {
1348 panic!(
1349 "repair id `{}` is missing `<namespace>/` prefix",
1350 template.id
1351 )
1352 });
1353 assert!(
1354 !namespace.is_empty() && !leaf.is_empty(),
1355 "repair id `{}` has empty namespace or leaf",
1356 template.id
1357 );
1358 for ch in template.id.chars() {
1359 assert!(
1360 ch.is_ascii_lowercase() || ch.is_ascii_digit() || ch == '-' || ch == '/',
1361 "repair id `{}` has non-kebab character {ch:?}",
1362 template.id
1363 );
1364 }
1365 assert!(
1366 !template.summary.is_empty(),
1367 "repair {} has empty summary",
1368 template.id
1369 );
1370 let first = template.summary.chars().next().unwrap();
1372 assert!(
1373 first.is_ascii_uppercase(),
1374 "repair {} summary `{}` should start with a capital",
1375 template.id,
1376 template.summary
1377 );
1378 }
1379 }
1380
1381 #[test]
1382 fn manual_namespace_is_needs_human() {
1383 for template in REPAIR_REGISTRY {
1384 if let Some(("manual", _)) = template.id.split_once('/') {
1385 assert_eq!(
1386 template.safety,
1387 RepairSafety::NeedsHuman,
1388 "manual/* repair {} must be NeedsHuman",
1389 template.id
1390 );
1391 }
1392 }
1393 }
1394
1395 #[test]
1396 fn known_codes_carry_expected_safety_class() {
1397 let expected: &[(Code, RepairSafety, &str)] = &[
1401 (
1402 Code::FormatterWouldReformat,
1403 RepairSafety::FormatOnly,
1404 "format/reformat",
1405 ),
1406 (
1407 Code::ModuleImportUnused,
1408 RepairSafety::BehaviorPreserving,
1409 "imports/remove-unused",
1410 ),
1411 (
1412 Code::ImmutableAssignment,
1413 RepairSafety::ScopeLocal,
1414 "bindings/make-mutable",
1415 ),
1416 (
1417 Code::LintUnusedFunction,
1418 RepairSafety::SurfaceChanging,
1419 "declarations/remove-unused",
1420 ),
1421 (
1422 Code::LlmProviderIdentityBranch,
1423 RepairSafety::CapabilityChanging,
1424 "llm/use-capability-flag",
1425 ),
1426 (
1427 Code::PromptVariantExplosion,
1428 RepairSafety::NeedsHuman,
1429 "manual/needs-human",
1430 ),
1431 (
1432 Code::NonExhaustiveMatch,
1433 RepairSafety::ScopeLocal,
1434 "match/add-missing-arms",
1435 ),
1436 (
1437 Code::LintAmbientClockBuiltin,
1438 RepairSafety::ScopeLocal,
1439 "bindings/thread-harness-clock",
1440 ),
1441 (
1442 Code::LintAmbientStdioBuiltin,
1443 RepairSafety::ScopeLocal,
1444 "bindings/thread-harness",
1445 ),
1446 (
1447 Code::InvalidMainSignature,
1448 RepairSafety::SurfaceChanging,
1449 "bindings/thread-harness-needs-param",
1450 ),
1451 ];
1452 for (code, safety, repair_id) in expected {
1453 let template = code
1454 .repair_template()
1455 .unwrap_or_else(|| panic!("{code} should have a repair template"));
1456 assert_eq!(template.safety, *safety, "{code} safety class drifted");
1457 assert_eq!(template.id, *repair_id, "{code} repair id drifted");
1458 }
1459 }
1460
1461 #[test]
1462 fn repair_templates_cover_at_least_twenty_codes() {
1463 let covered = Code::ALL
1464 .iter()
1465 .filter(|code| code.repair_template().is_some())
1466 .count();
1467 assert!(
1468 covered >= 20,
1469 "expected ≥20 codes with a repair template, found {covered}"
1470 );
1471 }
1472
1473 #[test]
1474 fn every_registered_repair_is_referenced_by_some_code() {
1475 let referenced: HashSet<&'static str> = Code::ALL
1476 .iter()
1477 .filter_map(|code| code.repair_template())
1478 .map(|template| template.id)
1479 .collect();
1480 for template in REPAIR_REGISTRY {
1481 assert!(
1482 referenced.contains(template.id),
1483 "repair {} is in REPAIR_REGISTRY but no Code maps to it",
1484 template.id
1485 );
1486 }
1487 }
1488
1489 #[test]
1490 fn every_referenced_repair_template_is_in_registry() {
1491 let registered: HashSet<&'static str> =
1492 REPAIR_REGISTRY.iter().map(|template| template.id).collect();
1493 for code in Code::ALL {
1494 let Some(template) = code.repair_template() else {
1495 continue;
1496 };
1497 assert!(
1498 registered.contains(template.id),
1499 "repair {} (used by {}) is missing from REPAIR_REGISTRY",
1500 template.id,
1501 code
1502 );
1503 }
1504 }
1505}