1use std::fmt;
22use std::str::FromStr;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
26pub enum Category {
27 Typ,
28 Par,
29 Nam,
30 Cap,
31 Llm,
32 Orc,
33 Std,
34 Prm,
35 Mod,
36 Rmd,
37 Sus,
38 Lnt,
39 Fmt,
40 Imp,
41 Own,
42 Rcv,
43 Mat,
44 Pol,
45}
46
47impl Category {
48 pub const ALL: &'static [Category] = &[
49 Category::Typ,
50 Category::Par,
51 Category::Nam,
52 Category::Cap,
53 Category::Llm,
54 Category::Orc,
55 Category::Std,
56 Category::Prm,
57 Category::Mod,
58 Category::Rmd,
59 Category::Sus,
60 Category::Lnt,
61 Category::Fmt,
62 Category::Imp,
63 Category::Own,
64 Category::Rcv,
65 Category::Mat,
66 Category::Pol,
67 ];
68
69 pub const fn as_str(self) -> &'static str {
70 match self {
71 Category::Typ => "TYP",
72 Category::Par => "PAR",
73 Category::Nam => "NAM",
74 Category::Cap => "CAP",
75 Category::Llm => "LLM",
76 Category::Orc => "ORC",
77 Category::Std => "STD",
78 Category::Prm => "PRM",
79 Category::Mod => "MOD",
80 Category::Rmd => "RMD",
81 Category::Sus => "SUS",
82 Category::Lnt => "LNT",
83 Category::Fmt => "FMT",
84 Category::Imp => "IMP",
85 Category::Own => "OWN",
86 Category::Rcv => "RCV",
87 Category::Mat => "MAT",
88 Category::Pol => "POL",
89 }
90 }
91}
92
93impl fmt::Display for Category {
94 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95 f.write_str(self.as_str())
96 }
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
101pub struct RegistryEntry {
102 pub code: Code,
103 pub identifier: &'static str,
104 pub category: Category,
105 pub summary: &'static str,
106}
107
108macro_rules! diagnostic_codes {
109 ($($variant:ident, $identifier:literal, $category:ident, $summary:literal;)*) => {
110 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
112 pub enum Code {
113 $($variant,)*
114 }
115
116 impl Code {
117 pub const ALL: &'static [Code] = &[
118 $(Code::$variant,)*
119 ];
120
121 pub const fn as_str(self) -> &'static str {
122 match self {
123 $(Code::$variant => $identifier,)*
124 }
125 }
126
127 pub const fn category(self) -> Category {
128 match self {
129 $(Code::$variant => Category::$category,)*
130 }
131 }
132
133 pub const fn summary(self) -> &'static str {
134 match self {
135 $(Code::$variant => $summary,)*
136 }
137 }
138
139 pub const fn explanation(self) -> &'static str {
143 match self {
144 $(Code::$variant => include_str!(
145 concat!("diagnostic_codes/explanations/", $identifier, ".md")
146 ),)*
147 }
148 }
149 }
150
151 pub const REGISTRY: &[RegistryEntry] = &[
152 $(RegistryEntry {
153 code: Code::$variant,
154 identifier: $identifier,
155 category: Category::$category,
156 summary: $summary,
157 },)*
158 ];
159 };
160}
161
162diagnostic_codes! {
163 TypeMismatch, "HARN-TYP-001", Typ, "expected and actual types are incompatible";
164 InvalidBinaryOperator, "HARN-TYP-002", Typ, "binary operator is not defined for the operand types";
165 StringInterpolationRewrite, "HARN-TYP-003", Typ, "string concatenation should be rewritten as interpolation";
166 ReturnTypeMismatch, "HARN-TYP-004", Typ, "returned expression does not match the declared return type";
167 AssignmentTypeMismatch, "HARN-TYP-005", Typ, "assigned value does not match the target type";
168 ArgumentTypeMismatch, "HARN-TYP-006", Typ, "argument value does not match the parameter type";
169 VariableTypeMismatch, "HARN-TYP-007", Typ, "initializer does not match the declared variable type";
170 ClosureReturnTypeMismatch, "HARN-TYP-008", Typ, "closure return expression does not match its declared type";
171 FieldTypeMismatch, "HARN-TYP-009", Typ, "field value does not match its declared type";
172 MethodTypeMismatch, "HARN-TYP-010", Typ, "method receiver or result type is incompatible";
173 GenericTypeArgumentUnsupported, "HARN-TYP-011", Typ, "callable does not accept type arguments";
174 GenericTypeArgumentMismatch, "HARN-TYP-012", Typ, "type argument does not satisfy the generic parameter";
175 GenericTypeArgumentArity, "HARN-TYP-013", Typ, "generic call has the wrong number of type arguments";
176 TypeParameterArity, "HARN-TYP-014", Typ, "declaration has the wrong number of type parameters";
177 WhereConstraintMismatch, "HARN-TYP-015", Typ, "type argument does not satisfy a where-clause constraint";
178 IterableExpected, "HARN-TYP-016", Typ, "expression must be iterable";
179 InvalidIndexType, "HARN-TYP-017", Typ, "subscript index type is invalid";
180 CallableExpected, "HARN-TYP-018", Typ, "expression must be callable";
181 InvalidCast, "HARN-TYP-019", Typ, "cast cannot be proven valid";
182 UnknownTypeName, "HARN-TYP-020", Typ, "type name cannot be resolved";
183 InvalidVariantUse, "HARN-TYP-021", Typ, "variant type is used in an invalid position";
184 InvalidStructLiteral, "HARN-TYP-022", Typ, "struct literal is invalid";
185 InvalidEnumConstruct, "HARN-TYP-023", Typ, "enum construction is invalid";
186 InvalidPatternBinding, "HARN-TYP-024", Typ, "pattern binding is invalid for the expected type";
187 InvalidOptionalAccess, "HARN-TYP-025", Typ, "optional access is invalid for the receiver type";
188 ParserUnexpectedToken, "HARN-PAR-001", Par, "parser found an unexpected token";
189 ParserUnexpectedEof, "HARN-PAR-002", Par, "parser reached end of file while expecting syntax";
190 ParserUnexpectedCharacter, "HARN-PAR-003", Par, "lexer found an unexpected character";
191 ParserUnterminatedString, "HARN-PAR-004", Par, "string literal is unterminated";
192 ParserUnterminatedBlockComment, "HARN-PAR-005", Par, "block comment is unterminated";
193 UndefinedVariable, "HARN-NAM-001", Nam, "variable name cannot be resolved";
194 UndefinedFunction, "HARN-NAM-002", Nam, "function name cannot be resolved";
195 UnknownAttribute, "HARN-NAM-003", Nam, "attribute name is not recognized";
196 UnknownField, "HARN-NAM-004", Nam, "field name does not exist on the target type";
197 UnknownMethod, "HARN-NAM-005", Nam, "method name does not exist on the receiver type";
198 DuplicateArgument, "HARN-NAM-006", Nam, "argument name is duplicated";
199 UnknownOption, "HARN-NAM-007", Nam, "option key is not recognized";
200 UnknownBuiltin, "HARN-NAM-008", Nam, "builtin name cannot be resolved";
201 DeprecatedFunction, "HARN-NAM-009", Nam, "function call targets a deprecated declaration";
202 UnknownDeclaration, "HARN-NAM-010", Nam, "declaration reference cannot be resolved";
203 InvalidAttributeTarget, "HARN-NAM-011", Nam, "attribute is attached to an unsupported declaration";
204 InvalidAttributeArgument, "HARN-NAM-012", Nam, "attribute argument is invalid";
205 InvalidMainSignature, "HARN-NAM-101", Nam, "`main` entrypoint must take a single `harness: Harness` parameter";
206 CapabilityPayloadInvalid, "HARN-CAP-001", Cap, "capability payload is invalid";
207 HitlMissingApprovalPolicy, "HARN-CAP-002", Cap, "human approval construct is missing policy";
208 HitlInvalidApprovalArgument, "HARN-CAP-003", Cap, "human approval argument is invalid";
209 CapabilityResultUnchecked, "HARN-CAP-004", Cap, "capability result must be checked";
210 CapabilityUnknownOperation, "HARN-CAP-005", Cap, "host capability operation is not declared";
211 CapabilityCallStaticNameRequired, "HARN-CAP-006", Cap, "host capability call must use a static operation name";
212 CapabilityBindingInvalid, "HARN-CAP-007", Cap, "tool host capability binding is invalid";
213 UnknownLlmOption, "HARN-LLM-001", Llm, "LLM option key is not recognized";
214 DeprecatedLlmOption, "HARN-LLM-002", Llm, "LLM option key is deprecated";
215 LlmSchemaMissing, "HARN-LLM-003", Llm, "LLM call is missing schema validation";
216 LlmSchemaInvalid, "HARN-LLM-004", Llm, "LLM schema option is invalid";
217 LlmProviderIdentityBranch, "HARN-LLM-005", Llm, "prompt branches on provider identity instead of capability flags";
218 OrchestrationArity, "HARN-ORC-001", Orc, "orchestration construct has invalid arity";
219 OrchestrationType, "HARN-ORC-002", Orc, "orchestration construct argument has invalid type";
220 AgentDefinitionInvalid, "HARN-ORC-003", Orc, "agent declaration is invalid";
221 WorkflowDefinitionInvalid, "HARN-ORC-004", Orc, "workflow declaration is invalid";
222 ToolDefinitionInvalid, "HARN-ORC-005", Orc, "tool declaration is invalid";
223 PipelineDefinitionInvalid, "HARN-ORC-006", Orc, "pipeline declaration is invalid";
224 InvalidSelectConstruct, "HARN-ORC-007", Orc, "select construct is invalid";
225 UnreachableCode, "HARN-ORC-008", Orc, "statement cannot be reached";
226 FlowInvariantAttributeInvalid, "HARN-ORC-009", Orc, "Flow invariant attribute set is invalid";
227 ExecutionTargetMissing, "HARN-ORC-010", Orc, "execution target path cannot be found";
228 DeprecatedStdlibSymbol, "HARN-STD-001", Std, "stdlib symbol has been renamed or deprecated";
229 StdlibUsageInvalid, "HARN-STD-002", Std, "stdlib call is invalid";
230 BuiltinArity, "HARN-STD-003", Std, "builtin call has invalid arity";
231 LintMissingStdlibMetadata, "HARN-STD-101", Std, "public stdlib function is missing declared metadata";
232 PromptTemplateParse, "HARN-PRM-001", Prm, "prompt template cannot be parsed";
233 PromptVariantExplosion, "HARN-PRM-002", Prm, "prompt template has too many capability-aware branches";
234 PromptInjectionRisk, "HARN-PRM-003", Prm, "prompt construction risks direct injection";
235 PromptProviderIdentityBranch, "HARN-PRM-004", Prm, "prompt template branches on provider identity";
236 PromptToolSurfaceUnknown, "HARN-PRM-005", Prm, "prompt references a tool outside the declared surface";
237 PromptToolSurfaceDeferredReference, "HARN-PRM-006", Prm, "prompt references a deferred tool without tool search";
238 PromptTargetMissing, "HARN-PRM-007", Prm, "prompt or template target cannot be found";
239 ModuleImportUnresolved, "HARN-MOD-001", Mod, "module import cannot be resolved";
240 ModuleImportUnused, "HARN-MOD-002", Mod, "module import is unused";
241 ModuleImportOrder, "HARN-MOD-003", Mod, "module imports are not in canonical order";
242 ModuleExportInvalid, "HARN-MOD-004", Mod, "module export is invalid";
243 ModuleImportCollision, "HARN-MOD-005", Mod, "module imports expose colliding names";
244 ModuleReExportConflict, "HARN-MOD-006", Mod, "module re-exports conflict";
245 ReminderUnknownOption, "HARN-RMD-001", Rmd, "reminder lifecycle option key is not recognized";
246 ReminderInvalidShape, "HARN-RMD-002", Rmd, "reminder payload shape is invalid";
247 ReminderUnsupportedUserBlockRoleHint, "HARN-RMD-003", Rmd, "user_block reminder role hint is not supported by the selected provider";
248 ReminderInfiniteDiscardable, "HARN-RMD-004", Rmd, "discardable reminder has no TTL";
249 SuspendWorkerNotRunning, "HARN-SUS-001", Sus, "suspend_agent target worker is not running";
250 ResumeConditionsInvalid, "HARN-SUS-002", Sus, "ResumeConditions validation failed";
251 ResumeWorkerNotSuspended, "HARN-SUS-003", Sus, "resume_agent target worker is not suspended";
252 ResumeSnapshotInvalid, "HARN-SUS-004", Sus, "resume snapshot cannot be loaded or used";
253 AwaitResumptionOutsideAgentLoop, "HARN-SUS-005", Sus, "agent_await_resumption was invoked outside agent_loop structural handling";
254 ConcurrentResumeConflict, "HARN-SUS-006", Sus, "concurrent resume changed the worker before resume could complete";
255 ResumeTriggerRegistrationFailed, "HARN-SUS-007", Sus, "ResumeConditions trigger could not be registered";
256 ResumeTimeoutUnsupported, "HARN-SUS-008", Sus, "resume timeout action is unsupported";
257 ResumeInputInvalid, "HARN-SUS-009", Sus, "resume input failed agent_loop input validation";
258 ResumeWorkerClosed, "HARN-SUS-010", Sus, "closed suspended worker cannot be resumed";
259 LintRenamedStdlibSymbol, "HARN-LNT-001", Lnt, "renamed stdlib symbol lint";
260 LintCyclomaticComplexity, "HARN-LNT-002", Lnt, "cyclomatic complexity lint";
261 LintNamingConvention, "HARN-LNT-003", Lnt, "naming convention lint";
262 LintEagerCollectionConversion, "HARN-LNT-004", Lnt, "eager collection conversion lint";
263 LintRedundantClone, "HARN-LNT-005", Lnt, "redundant clone lint";
264 LintLongRunningWithoutCleanup, "HARN-LNT-006", Lnt, "long-running workflow cleanup lint";
265 LintMcpToolAnnotations, "HARN-LNT-007", Lnt, "MCP tool annotations lint";
266 LintPrOpenWithoutSecretScan, "HARN-LNT-008", Lnt, "PR open without secret scan lint";
267 LintShadowVariable, "HARN-LNT-009", Lnt, "shadow variable lint";
268 LintPersonaHookTarget, "HARN-LNT-010", Lnt, "persona hook target lint";
269 LintDeadCodeAfterReturn, "HARN-LNT-011", Lnt, "dead code after return lint";
270 LintLetThenReturn, "HARN-LNT-012", Lnt, "let then return lint";
271 LintUnhandledApprovalResult, "HARN-LNT-013", Lnt, "unhandled approval result lint";
272 LintUnusedVariable, "HARN-LNT-014", Lnt, "unused variable lint";
273 LintUnusedPatternBinding, "HARN-LNT-015", Lnt, "unused pattern binding lint";
274 LintUnusedParameter, "HARN-LNT-016", Lnt, "unused parameter lint";
275 LintUnusedImport, "HARN-LNT-017", Lnt, "unused import lint";
276 LintMutableNeverReassigned, "HARN-LNT-018", Lnt, "mutable never reassigned lint";
277 LintUnusedFunction, "HARN-LNT-019", Lnt, "unused function lint";
278 LintUnusedType, "HARN-LNT-020", Lnt, "unused type lint";
279 LintPersonaBodyMustCallSteps, "HARN-LNT-021", Lnt, "persona body must call steps lint";
280 LintUndefinedFunction, "HARN-LNT-022", Lnt, "undefined function lint";
281 LintPipelineReturnType, "HARN-LNT-023", Lnt, "pipeline return type lint";
282 LintMissingHarndoc, "HARN-LNT-024", Lnt, "missing harndoc lint";
283 LintAssertOutsideTest, "HARN-LNT-025", Lnt, "assert outside test lint";
284 LintPromptInjectionRisk, "HARN-LNT-026", Lnt, "prompt injection risk lint";
285 LintConnectorEffectPolicy, "HARN-LNT-027", Lnt, "connector effect policy lint";
286 LintUnnecessaryCast, "HARN-LNT-028", Lnt, "unnecessary cast lint";
287 LintUntypedDictAccess, "HARN-LNT-029", Lnt, "untyped dict access lint";
288 LintConstantLogicalOperand, "HARN-LNT-030", Lnt, "constant logical operand lint";
289 LintPointlessComparison, "HARN-LNT-031", Lnt, "pointless comparison lint";
290 LintComparisonToBool, "HARN-LNT-032", Lnt, "comparison to bool lint";
291 LintInvalidBinaryOpLiteral, "HARN-LNT-033", Lnt, "invalid binary operator literal lint";
292 LintRedundantNilTernary, "HARN-LNT-034", Lnt, "redundant nil ternary lint";
293 LintEmptyBlock, "HARN-LNT-035", Lnt, "empty block lint";
294 LintUnnecessaryElseReturn, "HARN-LNT-036", Lnt, "unnecessary else return lint";
295 LintDuplicateMatchArm, "HARN-LNT-037", Lnt, "duplicate match arm lint";
296 LintRequireInTest, "HARN-LNT-038", Lnt, "require in test lint";
297 LintBreakOutsideLoop, "HARN-LNT-039", Lnt, "break outside loop lint";
298 LintTemplateParse, "HARN-LNT-040", Lnt, "template parse lint";
299 LintBlankLineBetweenItems, "HARN-LNT-041", Lnt, "blank line between items lint";
300 LintTrailingComma, "HARN-LNT-042", Lnt, "trailing comma lint";
301 LintUnnecessaryParentheses, "HARN-LNT-043", Lnt, "unnecessary parentheses lint";
302 LintTemplateVariantExplosion, "HARN-LNT-044", Lnt, "template variant explosion lint";
303 LintRequireFileHeader, "HARN-LNT-045", Lnt, "require file header lint";
304 LintTemplateProviderIdentityBranch, "HARN-LNT-046", Lnt, "template provider identity branch lint";
305 LintImportOrder, "HARN-LNT-047", Lnt, "import order lint";
306 LintPreferOptionalShorthand, "HARN-LNT-048", Lnt, "prefer optional shorthand lint";
307 LintLegacyDocComment, "HARN-LNT-049", Lnt, "legacy doc comment lint";
308 LintDeprecatedLlmOptions, "HARN-LNT-050", Lnt, "deprecated LLM options lint";
309 LintUnnecessarySafeNavigation, "HARN-LNT-051", Lnt, "unnecessary safe navigation lint";
310 LintAmbientClockBuiltin, "HARN-LNT-052", Lnt, "ambient clock builtin replaced by `harness.clock.*`";
311 LintAmbientStdioBuiltin, "HARN-LNT-053", Lnt, "ambient stdio builtin replaced by `harness.stdio.*`";
312 FormatterParseFailed, "HARN-FMT-001", Fmt, "formatter could not parse the source";
313 FormatterWouldReformat, "HARN-FMT-002", Fmt, "source is not in canonical format";
314 FormatterTrailingComma, "HARN-FMT-003", Fmt, "formatter normalized trailing comma layout";
315 ImportResolutionFailed, "HARN-IMP-001", Imp, "import target cannot be resolved";
316 ImportSymbolMissing, "HARN-IMP-002", Imp, "imported symbol does not exist";
317 ImportCycle, "HARN-IMP-003", Imp, "import graph contains a cycle";
318 ImmutableAssignment, "HARN-OWN-001", Own, "immutable binding is reassigned";
319 MutableNeverReassigned, "HARN-OWN-002", Own, "mutable binding is never reassigned";
320 OwnershipEscape, "HARN-OWN-003", Own, "owned value escapes its valid scope";
321 BoundaryValueUnvalidated, "HARN-OWN-004", Own, "unvalidated boundary value is used directly";
322 RescueOutsideFunction, "HARN-RCV-001", Rcv, "rescue construct is outside a function body";
323 TryOutsideFunction, "HARN-RCV-002", Rcv, "try construct is outside a function body";
324 InvalidRescueConstruct, "HARN-RCV-003", Rcv, "rescue construct is invalid";
325 NonExhaustiveMatch, "HARN-MAT-001", Mat, "match expression is not exhaustive";
326 DuplicateMatchArm, "HARN-MAT-002", Mat, "match expression contains a duplicate arm";
327 InvalidMatchPattern, "HARN-MAT-003", Mat, "match pattern is invalid";
328 PoolBackpressureFull, "HARN-POL-001", Pol, "pool backpressure rejected a submit";
329 PoolFailFastFull, "HARN-POL-002", Pol, "fail-fast pool has no immediate capacity";
330}
331
332impl Code {
333 pub const fn registry() -> &'static [RegistryEntry] {
334 REGISTRY
335 }
336
337 pub const fn related(self) -> &'static [Code] {
342 match self {
343 Code::TypeMismatch => &[
346 Code::AssignmentTypeMismatch,
347 Code::ArgumentTypeMismatch,
348 Code::ReturnTypeMismatch,
349 Code::VariableTypeMismatch,
350 Code::FieldTypeMismatch,
351 ],
352 Code::AssignmentTypeMismatch => &[Code::TypeMismatch, Code::VariableTypeMismatch],
353 Code::ArgumentTypeMismatch => &[Code::TypeMismatch, Code::GenericTypeArgumentMismatch],
354 Code::ReturnTypeMismatch => &[Code::TypeMismatch, Code::ClosureReturnTypeMismatch],
355 Code::VariableTypeMismatch => &[Code::TypeMismatch, Code::AssignmentTypeMismatch],
356 Code::ClosureReturnTypeMismatch => &[Code::ReturnTypeMismatch],
357 Code::FieldTypeMismatch => &[Code::TypeMismatch, Code::InvalidStructLiteral],
358 Code::MethodTypeMismatch => &[Code::TypeMismatch, Code::CallableExpected],
359 Code::GenericTypeArgumentUnsupported => &[
361 Code::GenericTypeArgumentMismatch,
362 Code::GenericTypeArgumentArity,
363 ],
364 Code::GenericTypeArgumentMismatch => &[
365 Code::GenericTypeArgumentArity,
366 Code::WhereConstraintMismatch,
367 ],
368 Code::GenericTypeArgumentArity => {
369 &[Code::GenericTypeArgumentMismatch, Code::TypeParameterArity]
370 }
371 Code::TypeParameterArity => &[Code::GenericTypeArgumentArity],
372 Code::WhereConstraintMismatch => &[Code::GenericTypeArgumentMismatch],
373 Code::UndefinedVariable => &[Code::UndefinedFunction, Code::UnknownDeclaration],
375 Code::UndefinedFunction => &[Code::UnknownBuiltin, Code::UnknownDeclaration],
376 Code::UnknownField => &[Code::UnknownMethod, Code::InvalidStructLiteral],
377 Code::UnknownMethod => &[Code::UnknownField, Code::CallableExpected],
378 Code::UnknownAttribute => {
379 &[Code::InvalidAttributeArgument, Code::InvalidAttributeTarget]
380 }
381 Code::InvalidAttributeArgument => {
382 &[Code::UnknownAttribute, Code::InvalidAttributeTarget]
383 }
384 Code::InvalidAttributeTarget => {
385 &[Code::UnknownAttribute, Code::InvalidAttributeArgument]
386 }
387 Code::LlmSchemaMissing => &[Code::LlmSchemaInvalid, Code::UnknownLlmOption],
389 Code::LlmSchemaInvalid => &[Code::LlmSchemaMissing, Code::UnknownLlmOption],
390 Code::UnknownLlmOption => &[Code::DeprecatedLlmOption, Code::LlmSchemaInvalid],
391 Code::DeprecatedLlmOption => &[Code::UnknownLlmOption],
392 Code::LlmProviderIdentityBranch => &[Code::PromptProviderIdentityBranch],
393 Code::PromptTemplateParse => &[Code::PromptTargetMissing],
395 Code::PromptInjectionRisk => &[Code::LintPromptInjectionRisk],
396 Code::PromptProviderIdentityBranch => &[
397 Code::LlmProviderIdentityBranch,
398 Code::LintTemplateProviderIdentityBranch,
399 ],
400 Code::PromptVariantExplosion => &[Code::LintTemplateVariantExplosion],
401 Code::CapabilityResultUnchecked => {
403 &[Code::RescueOutsideFunction, Code::TryOutsideFunction]
404 }
405 Code::CapabilityUnknownOperation => &[Code::CapabilityCallStaticNameRequired],
406 Code::RescueOutsideFunction => {
408 &[Code::TryOutsideFunction, Code::InvalidRescueConstruct]
409 }
410 Code::TryOutsideFunction => &[Code::RescueOutsideFunction],
411 Code::NonExhaustiveMatch => &[Code::InvalidMatchPattern, Code::DuplicateMatchArm],
412 Code::DuplicateMatchArm => &[Code::NonExhaustiveMatch, Code::LintDuplicateMatchArm],
413 Code::ModuleImportUnresolved => {
415 &[Code::ImportResolutionFailed, Code::ImportSymbolMissing]
416 }
417 Code::ModuleImportUnused => &[Code::LintUnusedImport],
418 Code::ImportResolutionFailed => {
419 &[Code::ModuleImportUnresolved, Code::ImportSymbolMissing]
420 }
421 Code::ImportCycle => &[Code::ImportResolutionFailed],
422 Code::SuspendWorkerNotRunning => {
424 &[Code::ResumeWorkerNotSuspended, Code::ResumeWorkerClosed]
425 }
426 Code::ResumeConditionsInvalid => &[
427 Code::ResumeTriggerRegistrationFailed,
428 Code::ResumeTimeoutUnsupported,
429 ],
430 Code::ResumeWorkerNotSuspended => &[
431 Code::SuspendWorkerNotRunning,
432 Code::ConcurrentResumeConflict,
433 ],
434 Code::ResumeSnapshotInvalid => &[Code::ResumeWorkerNotSuspended],
435 Code::AwaitResumptionOutsideAgentLoop => &[Code::ResumeConditionsInvalid],
436 Code::ConcurrentResumeConflict => {
437 &[Code::ResumeWorkerNotSuspended, Code::ResumeWorkerClosed]
438 }
439 Code::ResumeTriggerRegistrationFailed => &[
440 Code::ResumeConditionsInvalid,
441 Code::ResumeTimeoutUnsupported,
442 ],
443 Code::ResumeTimeoutUnsupported => &[
444 Code::ResumeConditionsInvalid,
445 Code::ResumeTriggerRegistrationFailed,
446 ],
447 Code::ResumeInputInvalid => &[Code::ResumeWorkerNotSuspended],
448 Code::ResumeWorkerClosed => &[
449 Code::ResumeWorkerNotSuspended,
450 Code::ConcurrentResumeConflict,
451 ],
452 Code::ImmutableAssignment => &[Code::MutableNeverReassigned],
454 Code::MutableNeverReassigned => &[Code::LintMutableNeverReassigned],
455 Code::LintDeprecatedLlmOptions => &[Code::DeprecatedLlmOption, Code::UnknownLlmOption],
457 Code::LintPromptInjectionRisk => &[Code::PromptInjectionRisk],
458 Code::LintTemplateVariantExplosion => &[Code::PromptVariantExplosion],
459 Code::LintTemplateProviderIdentityBranch => &[Code::PromptProviderIdentityBranch],
460 Code::LintRenamedStdlibSymbol => &[Code::DeprecatedStdlibSymbol],
461 Code::LintAmbientClockBuiltin => {
462 &[Code::InvalidMainSignature, Code::LintRenamedStdlibSymbol]
463 }
464 Code::LintAmbientStdioBuiltin => {
465 &[Code::InvalidMainSignature, Code::LintRenamedStdlibSymbol]
466 }
467 Code::LintMutableNeverReassigned => &[Code::MutableNeverReassigned],
468 Code::LintUnusedImport => &[Code::ModuleImportUnused],
469 Code::LintDuplicateMatchArm => &[Code::DuplicateMatchArm],
470 _ => &[],
471 }
472 }
473}
474
475impl fmt::Display for Code {
476 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
477 f.write_str(self.as_str())
478 }
479}
480
481#[derive(Debug, Clone, Copy, PartialEq, Eq)]
483pub struct ParseCodeError;
484
485impl fmt::Display for ParseCodeError {
486 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
487 f.write_str("unknown Harn diagnostic code")
488 }
489}
490
491impl std::error::Error for ParseCodeError {}
492
493impl FromStr for Code {
494 type Err = ParseCodeError;
495
496 fn from_str(value: &str) -> Result<Self, Self::Err> {
497 Code::ALL
498 .iter()
499 .copied()
500 .find(|code| code.as_str() == value)
501 .ok_or(ParseCodeError)
502 }
503}
504
505#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
516pub enum RepairSafety {
517 FormatOnly,
520 BehaviorPreserving,
523 ScopeLocal,
527 SurfaceChanging,
530 CapabilityChanging,
534 NeedsHuman,
538}
539
540impl RepairSafety {
541 pub const ALL: &'static [RepairSafety] = &[
542 RepairSafety::FormatOnly,
543 RepairSafety::BehaviorPreserving,
544 RepairSafety::ScopeLocal,
545 RepairSafety::SurfaceChanging,
546 RepairSafety::CapabilityChanging,
547 RepairSafety::NeedsHuman,
548 ];
549
550 pub const fn as_str(self) -> &'static str {
554 match self {
555 RepairSafety::FormatOnly => "format-only",
556 RepairSafety::BehaviorPreserving => "behavior-preserving",
557 RepairSafety::ScopeLocal => "scope-local",
558 RepairSafety::SurfaceChanging => "surface-changing",
559 RepairSafety::CapabilityChanging => "capability-changing",
560 RepairSafety::NeedsHuman => "needs-human",
561 }
562 }
563
564 pub const fn is_at_most(self, ceiling: RepairSafety) -> bool {
568 (self as u8) <= (ceiling as u8)
569 }
570}
571
572impl fmt::Display for RepairSafety {
573 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
574 f.write_str(self.as_str())
575 }
576}
577
578#[derive(Debug, Clone, Copy, PartialEq, Eq)]
580pub struct ParseRepairSafetyError;
581
582impl fmt::Display for ParseRepairSafetyError {
583 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
584 f.write_str("unknown Harn repair-safety class")
585 }
586}
587
588impl std::error::Error for ParseRepairSafetyError {}
589
590impl FromStr for RepairSafety {
591 type Err = ParseRepairSafetyError;
592
593 fn from_str(value: &str) -> Result<Self, Self::Err> {
594 RepairSafety::ALL
595 .iter()
596 .copied()
597 .find(|safety| safety.as_str() == value)
598 .ok_or(ParseRepairSafetyError)
599 }
600}
601
602#[derive(Debug, Clone, PartialEq, Eq, Hash)]
608pub struct RepairId(std::borrow::Cow<'static, str>);
609
610impl RepairId {
611 pub const fn from_static(s: &'static str) -> Self {
612 RepairId(std::borrow::Cow::Borrowed(s))
613 }
614
615 pub fn from_owned(s: String) -> Self {
616 RepairId(std::borrow::Cow::Owned(s))
617 }
618
619 pub fn as_str(&self) -> &str {
620 &self.0
621 }
622}
623
624impl fmt::Display for RepairId {
625 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
626 f.write_str(&self.0)
627 }
628}
629
630#[derive(Debug, Clone)]
638pub struct Repair {
639 pub id: RepairId,
640 pub summary: String,
641 pub safety: RepairSafety,
642}
643
644impl Repair {
645 pub fn from_template(template: &RepairTemplate) -> Self {
646 Repair {
647 id: RepairId::from_static(template.id),
648 summary: template.summary.to_string(),
649 safety: template.safety,
650 }
651 }
652}
653
654#[derive(Debug, Clone, Copy)]
661pub struct RepairTemplate {
662 pub id: &'static str,
663 pub summary: &'static str,
664 pub safety: RepairSafety,
665}
666
667impl Code {
668 pub const fn repair_template(self) -> Option<&'static RepairTemplate> {
671 match self {
672 Code::TypeMismatch
674 | Code::ReturnTypeMismatch
675 | Code::AssignmentTypeMismatch
676 | Code::ArgumentTypeMismatch
677 | Code::VariableTypeMismatch
678 | Code::ClosureReturnTypeMismatch
679 | Code::FieldTypeMismatch
680 | Code::MethodTypeMismatch
681 | Code::InvalidIndexType => Some(&REPAIR_INSERT_EXPLICIT_CONVERSION),
682 Code::StringInterpolationRewrite => Some(&REPAIR_REWRITE_STRING_INTERPOLATION),
683 Code::UnknownTypeName => Some(&REPAIR_IMPORTS_FIX_PATH),
684 Code::InvalidCast => Some(&REPAIR_CASTS_REMOVE_UNCHECKED),
685
686 Code::UndefinedVariable
688 | Code::UndefinedFunction
689 | Code::UnknownField
690 | Code::UnknownMethod
691 | Code::UnknownBuiltin
692 | Code::UnknownDeclaration => Some(&REPAIR_BINDINGS_RENAME_TO_CLOSEST),
693 Code::InvalidMainSignature => Some(&REPAIR_BINDINGS_THREAD_HARNESS),
694 Code::DeprecatedFunction => Some(&REPAIR_STDLIB_MIGRATE_RENAMED),
695 Code::ModuleImportUnresolved | Code::ImportResolutionFailed => {
696 Some(&REPAIR_IMPORTS_FIX_PATH)
697 }
698 Code::ModuleImportUnused => Some(&REPAIR_IMPORTS_REMOVE_UNUSED),
699 Code::ModuleImportOrder => Some(&REPAIR_IMPORTS_REORDER),
700
701 Code::CapabilityResultUnchecked => Some(&REPAIR_ERRORS_CHECK_OR_RESCUE),
703 Code::CapabilityBindingInvalid => Some(&REPAIR_MANUAL_REVIEW_CAPABILITY),
704 Code::RescueOutsideFunction | Code::TryOutsideFunction => {
705 Some(&REPAIR_ERRORS_WRAP_IN_FN)
706 }
707
708 Code::DeprecatedLlmOption => Some(&REPAIR_LLM_MIGRATE_DEPRECATED_OPTION),
710 Code::LlmSchemaMissing => Some(&REPAIR_LLM_ADD_SCHEMA),
711 Code::LlmProviderIdentityBranch | Code::PromptProviderIdentityBranch => {
712 Some(&REPAIR_LLM_USE_CAPABILITY_FLAG)
713 }
714 Code::PromptInjectionRisk => Some(&REPAIR_PROMPTS_ESCAPE_INJECTION),
715 Code::PromptToolSurfaceUnknown | Code::PromptToolSurfaceDeferredReference => {
716 Some(&REPAIR_PROMPTS_ADD_TOOL_TO_SURFACE)
717 }
718 Code::PromptVariantExplosion => Some(&REPAIR_MANUAL_NEEDS_HUMAN),
719
720 Code::DeprecatedStdlibSymbol => Some(&REPAIR_STDLIB_MIGRATE_RENAMED),
722 Code::LintMissingStdlibMetadata => Some(&REPAIR_DOC_ADD_STDLIB_METADATA),
723
724 Code::ImmutableAssignment => Some(&REPAIR_BINDINGS_MAKE_MUTABLE),
726 Code::MutableNeverReassigned => Some(&REPAIR_BINDINGS_MAKE_IMMUTABLE),
727
728 Code::NonExhaustiveMatch => Some(&REPAIR_MATCH_ADD_MISSING_ARMS),
730 Code::DuplicateMatchArm => Some(&REPAIR_MATCH_REMOVE_DUPLICATE_ARM),
731
732 Code::UnreachableCode => Some(&REPAIR_DEAD_CODE_REMOVE),
734
735 Code::FormatterWouldReformat | Code::FormatterTrailingComma => {
737 Some(&REPAIR_FORMAT_REFORMAT)
738 }
739
740 Code::LintUnusedVariable
742 | Code::LintUnusedPatternBinding
743 | Code::LintUnusedParameter => Some(&REPAIR_BINDINGS_RENAME_UNUSED),
744 Code::LintUnusedImport => Some(&REPAIR_IMPORTS_REMOVE_UNUSED),
745 Code::LintUnusedFunction | Code::LintUnusedType => {
746 Some(&REPAIR_DECLARATIONS_REMOVE_UNUSED)
747 }
748 Code::LintMutableNeverReassigned => Some(&REPAIR_BINDINGS_MAKE_IMMUTABLE),
749 Code::LintImportOrder => Some(&REPAIR_IMPORTS_REORDER),
750 Code::LintBlankLineBetweenItems
751 | Code::LintTrailingComma
752 | Code::LintUnnecessaryParentheses
753 | Code::LintRequireFileHeader => Some(&REPAIR_FORMAT_REFORMAT),
754 Code::LintLegacyDocComment => Some(&REPAIR_DOC_COMMENT_MIGRATE),
755 Code::LintEmptyBlock => Some(&REPAIR_BLOCK_REMOVE_EMPTY),
756 Code::LintUnnecessaryElseReturn | Code::LintLetThenReturn => {
757 Some(&REPAIR_CONTROL_FLOW_FLATTEN)
758 }
759 Code::LintRedundantNilTernary
760 | Code::LintUnnecessarySafeNavigation
761 | Code::LintPreferOptionalShorthand
762 | Code::LintComparisonToBool
763 | Code::LintPointlessComparison
764 | Code::LintConstantLogicalOperand => Some(&REPAIR_EXPRESSION_SIMPLIFY),
765 Code::LintUnnecessaryCast => Some(&REPAIR_CASTS_REMOVE_REDUNDANT),
766 Code::LintRedundantClone => Some(&REPAIR_CLONE_REMOVE_REDUNDANT),
767 Code::LintEagerCollectionConversion => Some(&REPAIR_COLLECTION_PREFER_LAZY),
768 Code::LintDeadCodeAfterReturn => Some(&REPAIR_DEAD_CODE_REMOVE),
769 Code::LintRenamedStdlibSymbol => Some(&REPAIR_STDLIB_MIGRATE_RENAMED),
770 Code::LintAmbientClockBuiltin => Some(&REPAIR_BINDINGS_THREAD_HARNESS_CLOCK),
771 Code::LintAmbientStdioBuiltin => Some(&REPAIR_BINDINGS_THREAD_HARNESS_STDIO),
772 Code::LintDeprecatedLlmOptions => Some(&REPAIR_LLM_MIGRATE_DEPRECATED_OPTION),
773 Code::LintTemplateProviderIdentityBranch => Some(&REPAIR_LLM_USE_CAPABILITY_FLAG),
774 Code::LintPromptInjectionRisk => Some(&REPAIR_PROMPTS_ESCAPE_INJECTION),
775 Code::LintShadowVariable => Some(&REPAIR_BINDINGS_RENAME_SHADOW),
776 Code::LintNamingConvention => Some(&REPAIR_STYLE_RENAME_TO_CONVENTION),
777 Code::LintUnhandledApprovalResult => Some(&REPAIR_ERRORS_CHECK_OR_RESCUE),
778 Code::LintMissingHarndoc => Some(&REPAIR_DOC_ADD_HARNDOC),
779 Code::LintDuplicateMatchArm => Some(&REPAIR_MATCH_REMOVE_DUPLICATE_ARM),
780 Code::LintUntypedDictAccess => Some(&REPAIR_TYPES_ADD_SHAPE_ANNOTATION),
781 Code::LintMcpToolAnnotations => Some(&REPAIR_MANUAL_NEEDS_HUMAN),
782 Code::LintTemplateVariantExplosion | Code::LintLongRunningWithoutCleanup => {
783 Some(&REPAIR_MANUAL_NEEDS_HUMAN)
784 }
785
786 _ => None,
790 }
791 }
792}
793
794const REPAIR_INSERT_EXPLICIT_CONVERSION: RepairTemplate = RepairTemplate {
809 id: "casts/insert-explicit-conversion",
810 summary: "Insert an explicit conversion or correct the operand type",
811 safety: RepairSafety::ScopeLocal,
812};
813
814const REPAIR_REWRITE_STRING_INTERPOLATION: RepairTemplate = RepairTemplate {
815 id: "style/string-interpolation",
816 summary: "Rewrite string concatenation as an interpolation literal",
817 safety: RepairSafety::BehaviorPreserving,
818};
819
820const REPAIR_CASTS_REMOVE_UNCHECKED: RepairTemplate = RepairTemplate {
821 id: "casts/remove-unchecked",
822 summary: "Remove the unchecked cast or guard it with a type test",
823 safety: RepairSafety::ScopeLocal,
824};
825
826const REPAIR_CASTS_REMOVE_REDUNDANT: RepairTemplate = RepairTemplate {
827 id: "casts/remove-redundant",
828 summary: "Remove the redundant cast",
829 safety: RepairSafety::BehaviorPreserving,
830};
831
832const REPAIR_BINDINGS_RENAME_TO_CLOSEST: RepairTemplate = RepairTemplate {
833 id: "bindings/rename-to-closest",
834 summary: "Rename to the closest in-scope identifier",
835 safety: RepairSafety::ScopeLocal,
836};
837
838const REPAIR_BINDINGS_MAKE_MUTABLE: RepairTemplate = RepairTemplate {
839 id: "bindings/make-mutable",
840 summary: "Mark the binding `mut` so it can be reassigned",
841 safety: RepairSafety::ScopeLocal,
842};
843
844const REPAIR_BINDINGS_MAKE_IMMUTABLE: RepairTemplate = RepairTemplate {
845 id: "bindings/make-immutable",
846 summary: "Drop `mut` since the binding is never reassigned",
847 safety: RepairSafety::BehaviorPreserving,
848};
849
850const REPAIR_BINDINGS_RENAME_UNUSED: RepairTemplate = RepairTemplate {
851 id: "bindings/rename-unused",
852 summary: "Prefix the unused binding with `_` to silence the lint",
853 safety: RepairSafety::BehaviorPreserving,
854};
855
856const REPAIR_BINDINGS_RENAME_SHADOW: RepairTemplate = RepairTemplate {
857 id: "bindings/rename-shadow",
858 summary: "Rename the shadowing binding to a distinct name",
859 safety: RepairSafety::ScopeLocal,
860};
861
862const REPAIR_BINDINGS_THREAD_HARNESS: RepairTemplate = RepairTemplate {
863 id: "bindings/thread-harness",
864 summary: "Rewrite the entrypoint as `fn main(harness: Harness)` so the runtime can thread its capability handle",
865 safety: RepairSafety::SurfaceChanging,
866};
867
868const REPAIR_BINDINGS_THREAD_HARNESS_CLOCK: RepairTemplate = RepairTemplate {
869 id: "bindings/thread-harness-clock",
870 summary: "Replace the ambient clock builtin with the corresponding `harness.clock.*` method",
871 safety: RepairSafety::ScopeLocal,
872};
873
874const REPAIR_BINDINGS_THREAD_HARNESS_STDIO: RepairTemplate = RepairTemplate {
875 id: "bindings/thread-harness-stdio",
876 summary: "Replace the ambient stdio builtin with the corresponding `harness.stdio.*` method",
877 safety: RepairSafety::ScopeLocal,
878};
879
880const REPAIR_DECLARATIONS_REMOVE_UNUSED: RepairTemplate = RepairTemplate {
881 id: "declarations/remove-unused",
882 summary: "Remove the unused declaration",
883 safety: RepairSafety::SurfaceChanging,
884};
885
886const REPAIR_IMPORTS_FIX_PATH: RepairTemplate = RepairTemplate {
887 id: "imports/fix-path",
888 summary: "Replace the import path with a resolvable target",
889 safety: RepairSafety::ScopeLocal,
890};
891
892const REPAIR_IMPORTS_REMOVE_UNUSED: RepairTemplate = RepairTemplate {
893 id: "imports/remove-unused",
894 summary: "Remove the unused import",
895 safety: RepairSafety::BehaviorPreserving,
896};
897
898const REPAIR_IMPORTS_REORDER: RepairTemplate = RepairTemplate {
899 id: "imports/reorder",
900 summary: "Reorder imports into canonical grouping",
901 safety: RepairSafety::FormatOnly,
902};
903
904const REPAIR_ERRORS_CHECK_OR_RESCUE: RepairTemplate = RepairTemplate {
905 id: "errors/check-or-rescue",
906 summary: "Check the result or wrap the call in a `rescue` block",
907 safety: RepairSafety::ScopeLocal,
908};
909
910const REPAIR_ERRORS_WRAP_IN_FN: RepairTemplate = RepairTemplate {
911 id: "errors/wrap-in-fn",
912 summary: "Move the construct inside a function body",
913 safety: RepairSafety::SurfaceChanging,
914};
915
916const REPAIR_MATCH_ADD_MISSING_ARMS: RepairTemplate = RepairTemplate {
917 id: "match/add-missing-arms",
918 summary: "Add arms covering the missing variants",
919 safety: RepairSafety::ScopeLocal,
920};
921
922const REPAIR_MATCH_REMOVE_DUPLICATE_ARM: RepairTemplate = RepairTemplate {
923 id: "match/remove-duplicate-arm",
924 summary: "Remove the duplicated match arm",
925 safety: RepairSafety::BehaviorPreserving,
926};
927
928const REPAIR_FORMAT_REFORMAT: RepairTemplate = RepairTemplate {
929 id: "format/reformat",
930 summary: "Apply canonical formatting",
931 safety: RepairSafety::FormatOnly,
932};
933
934const REPAIR_DOC_COMMENT_MIGRATE: RepairTemplate = RepairTemplate {
935 id: "doc/migrate-comment-style",
936 summary: "Migrate the legacy comment to canonical doc syntax",
937 safety: RepairSafety::FormatOnly,
938};
939
940const REPAIR_DOC_ADD_HARNDOC: RepairTemplate = RepairTemplate {
941 id: "doc/add-harndoc",
942 summary: "Add a `///` doc comment describing this declaration",
943 safety: RepairSafety::BehaviorPreserving,
944};
945
946const REPAIR_DOC_ADD_STDLIB_METADATA: RepairTemplate = RepairTemplate {
947 id: "doc/add-stdlib-metadata",
948 summary: "Add `@effects`, `@allocation`, `@errors`, `@api_stability`, and `@example` fields to the stdlib function's doc block",
949 safety: RepairSafety::BehaviorPreserving,
950};
951
952const REPAIR_BLOCK_REMOVE_EMPTY: RepairTemplate = RepairTemplate {
953 id: "blocks/remove-empty",
954 summary: "Remove the empty block or fill in an explicit body",
955 safety: RepairSafety::ScopeLocal,
956};
957
958const REPAIR_CONTROL_FLOW_FLATTEN: RepairTemplate = RepairTemplate {
959 id: "control-flow/flatten",
960 summary: "Flatten the unnecessary control flow construct",
961 safety: RepairSafety::BehaviorPreserving,
962};
963
964const REPAIR_EXPRESSION_SIMPLIFY: RepairTemplate = RepairTemplate {
965 id: "expressions/simplify",
966 summary: "Simplify the expression to its canonical form",
967 safety: RepairSafety::BehaviorPreserving,
968};
969
970const REPAIR_CLONE_REMOVE_REDUNDANT: RepairTemplate = RepairTemplate {
971 id: "clones/remove-redundant",
972 summary: "Remove the redundant clone",
973 safety: RepairSafety::BehaviorPreserving,
974};
975
976const REPAIR_COLLECTION_PREFER_LAZY: RepairTemplate = RepairTemplate {
977 id: "collections/prefer-lazy",
978 summary: "Replace the eager collection step with a lazy variant",
979 safety: RepairSafety::ScopeLocal,
980};
981
982const REPAIR_DEAD_CODE_REMOVE: RepairTemplate = RepairTemplate {
983 id: "control-flow/remove-dead",
984 summary: "Remove the unreachable code",
985 safety: RepairSafety::BehaviorPreserving,
986};
987
988const REPAIR_STDLIB_MIGRATE_RENAMED: RepairTemplate = RepairTemplate {
989 id: "stdlib/migrate-renamed",
990 summary: "Rename the call to the renamed stdlib symbol",
991 safety: RepairSafety::ScopeLocal,
992};
993
994const REPAIR_LLM_MIGRATE_DEPRECATED_OPTION: RepairTemplate = RepairTemplate {
995 id: "llm/migrate-deprecated-option",
996 summary: "Replace the deprecated option with its supported equivalent",
997 safety: RepairSafety::ScopeLocal,
998};
999
1000const REPAIR_LLM_ADD_SCHEMA: RepairTemplate = RepairTemplate {
1001 id: "llm/add-schema",
1002 summary: "Add a typed output schema to the LLM call",
1003 safety: RepairSafety::SurfaceChanging,
1004};
1005
1006const REPAIR_LLM_USE_CAPABILITY_FLAG: RepairTemplate = RepairTemplate {
1007 id: "llm/use-capability-flag",
1008 summary: "Branch on a capability flag instead of provider identity",
1009 safety: RepairSafety::CapabilityChanging,
1010};
1011
1012const REPAIR_PROMPTS_ESCAPE_INJECTION: RepairTemplate = RepairTemplate {
1013 id: "prompts/escape-injection",
1014 summary: "Pass the untrusted input through a structured placeholder",
1015 safety: RepairSafety::ScopeLocal,
1016};
1017
1018const REPAIR_PROMPTS_ADD_TOOL_TO_SURFACE: RepairTemplate = RepairTemplate {
1019 id: "prompts/add-tool-to-surface",
1020 summary: "Add the referenced tool to the declared tool surface",
1021 safety: RepairSafety::SurfaceChanging,
1022};
1023
1024const REPAIR_STYLE_RENAME_TO_CONVENTION: RepairTemplate = RepairTemplate {
1025 id: "style/rename-to-convention",
1026 summary: "Rename to match the casing convention for this kind",
1027 safety: RepairSafety::SurfaceChanging,
1028};
1029
1030const REPAIR_TYPES_ADD_SHAPE_ANNOTATION: RepairTemplate = RepairTemplate {
1031 id: "types/add-shape-annotation",
1032 summary: "Annotate the dict with a concrete shape type",
1033 safety: RepairSafety::SurfaceChanging,
1034};
1035
1036const REPAIR_MANUAL_REVIEW_CAPABILITY: RepairTemplate = RepairTemplate {
1037 id: "manual/review-capability-binding",
1038 summary: "Review the capability binding; the fix is not mechanical",
1039 safety: RepairSafety::NeedsHuman,
1040};
1041
1042const REPAIR_MANUAL_NEEDS_HUMAN: RepairTemplate = RepairTemplate {
1043 id: "manual/needs-human",
1044 summary: "Plan a human-led change; auto-apply is not safe here",
1045 safety: RepairSafety::NeedsHuman,
1046};
1047
1048pub const REPAIR_REGISTRY: &[&RepairTemplate] = &[
1052 &REPAIR_INSERT_EXPLICIT_CONVERSION,
1053 &REPAIR_REWRITE_STRING_INTERPOLATION,
1054 &REPAIR_CASTS_REMOVE_UNCHECKED,
1055 &REPAIR_CASTS_REMOVE_REDUNDANT,
1056 &REPAIR_BINDINGS_RENAME_TO_CLOSEST,
1057 &REPAIR_BINDINGS_MAKE_MUTABLE,
1058 &REPAIR_BINDINGS_MAKE_IMMUTABLE,
1059 &REPAIR_BINDINGS_RENAME_UNUSED,
1060 &REPAIR_BINDINGS_RENAME_SHADOW,
1061 &REPAIR_BINDINGS_THREAD_HARNESS,
1062 &REPAIR_BINDINGS_THREAD_HARNESS_CLOCK,
1063 &REPAIR_BINDINGS_THREAD_HARNESS_STDIO,
1064 &REPAIR_DECLARATIONS_REMOVE_UNUSED,
1065 &REPAIR_IMPORTS_FIX_PATH,
1066 &REPAIR_IMPORTS_REMOVE_UNUSED,
1067 &REPAIR_IMPORTS_REORDER,
1068 &REPAIR_ERRORS_CHECK_OR_RESCUE,
1069 &REPAIR_ERRORS_WRAP_IN_FN,
1070 &REPAIR_MATCH_ADD_MISSING_ARMS,
1071 &REPAIR_MATCH_REMOVE_DUPLICATE_ARM,
1072 &REPAIR_FORMAT_REFORMAT,
1073 &REPAIR_DOC_COMMENT_MIGRATE,
1074 &REPAIR_DOC_ADD_HARNDOC,
1075 &REPAIR_DOC_ADD_STDLIB_METADATA,
1076 &REPAIR_BLOCK_REMOVE_EMPTY,
1077 &REPAIR_CONTROL_FLOW_FLATTEN,
1078 &REPAIR_EXPRESSION_SIMPLIFY,
1079 &REPAIR_CLONE_REMOVE_REDUNDANT,
1080 &REPAIR_COLLECTION_PREFER_LAZY,
1081 &REPAIR_DEAD_CODE_REMOVE,
1082 &REPAIR_STDLIB_MIGRATE_RENAMED,
1083 &REPAIR_LLM_MIGRATE_DEPRECATED_OPTION,
1084 &REPAIR_LLM_ADD_SCHEMA,
1085 &REPAIR_LLM_USE_CAPABILITY_FLAG,
1086 &REPAIR_PROMPTS_ESCAPE_INJECTION,
1087 &REPAIR_PROMPTS_ADD_TOOL_TO_SURFACE,
1088 &REPAIR_STYLE_RENAME_TO_CONVENTION,
1089 &REPAIR_TYPES_ADD_SHAPE_ANNOTATION,
1090 &REPAIR_MANUAL_REVIEW_CAPABILITY,
1091 &REPAIR_MANUAL_NEEDS_HUMAN,
1092];
1093
1094#[cfg(test)]
1095mod tests {
1096 use super::{Category, Code, ParseRepairSafetyError, RepairSafety, REPAIR_REGISTRY};
1097 use std::collections::HashSet;
1098 use std::str::FromStr;
1099
1100 #[test]
1101 fn parses_registered_code() {
1102 assert_eq!(Code::from_str("HARN-TYP-014"), Ok(Code::TypeParameterArity));
1103 }
1104
1105 #[test]
1106 fn registry_has_unique_identifiers() {
1107 let mut seen = HashSet::new();
1108 for entry in Code::registry() {
1109 assert!(
1110 seen.insert(entry.identifier),
1111 "duplicate diagnostic code {}",
1112 entry.identifier
1113 );
1114 assert_eq!(entry.code.as_str(), entry.identifier);
1115 assert_eq!(entry.code.category(), entry.category);
1116 let expected_prefix = format!("HARN-{}-", entry.category);
1117 assert!(entry.identifier.starts_with(&expected_prefix));
1118 let suffix = entry.identifier.trim_start_matches(&expected_prefix);
1119 assert_eq!(suffix.len(), 3);
1120 assert!(suffix.chars().all(|ch| ch.is_ascii_digit()));
1121 assert!(!entry.summary.is_empty());
1122 }
1123 assert!(Code::registry().len() >= 40);
1124 }
1125
1126 #[test]
1127 fn every_category_is_populated() {
1128 for category in Category::ALL {
1129 assert!(
1130 Code::registry()
1131 .iter()
1132 .any(|entry| entry.category == *category),
1133 "missing diagnostic code category {category}"
1134 );
1135 }
1136 }
1137
1138 #[test]
1139 fn every_code_has_non_empty_explanation() {
1140 for entry in Code::registry() {
1141 let body = entry.code.explanation();
1142 assert!(
1143 !body.trim().is_empty(),
1144 "diagnostic code {} has an empty explanation file",
1145 entry.identifier
1146 );
1147 assert!(
1148 body.contains(entry.identifier),
1149 "explanation for {} should reference its identifier",
1150 entry.identifier
1151 );
1152 }
1153 }
1154
1155 #[test]
1156 fn related_codes_are_registered_and_non_self() {
1157 for entry in Code::registry() {
1158 for &other in entry.code.related() {
1159 assert_ne!(
1160 other, entry.code,
1161 "{} lists itself as a related code",
1162 entry.identifier
1163 );
1164 assert!(
1165 Code::registry().iter().any(|e| e.code == other),
1166 "{} lists unregistered related code {}",
1167 entry.identifier,
1168 other
1169 );
1170 }
1171 }
1172 }
1173
1174 #[test]
1175 fn repair_safety_string_roundtrip() {
1176 for safety in RepairSafety::ALL {
1177 let parsed = RepairSafety::from_str(safety.as_str()).unwrap();
1178 assert_eq!(parsed, *safety);
1179 assert_eq!(parsed.to_string(), safety.as_str());
1180 }
1181 assert_eq!(
1182 RepairSafety::from_str("not-a-safety-class"),
1183 Err(ParseRepairSafetyError)
1184 );
1185 }
1186
1187 #[test]
1188 fn repair_safety_ordering_is_monotonic_low_to_high() {
1189 let order = RepairSafety::ALL;
1193 for window in order.windows(2) {
1194 assert!(
1195 window[0] < window[1],
1196 "{:?} should be safer than {:?}",
1197 window[0],
1198 window[1]
1199 );
1200 assert!(window[0].is_at_most(window[1]));
1201 assert!(!window[1].is_at_most(window[0]));
1202 }
1203 }
1204
1205 #[test]
1206 fn repair_registry_has_at_least_twenty_entries() {
1207 assert!(
1208 REPAIR_REGISTRY.len() >= 20,
1209 "expected ≥20 repair templates, found {}",
1210 REPAIR_REGISTRY.len()
1211 );
1212 }
1213
1214 #[test]
1215 fn repair_ids_are_kebab_case_namespaced_and_unique() {
1216 let mut seen = HashSet::new();
1217 for template in REPAIR_REGISTRY {
1218 assert!(
1219 seen.insert(template.id),
1220 "duplicate repair id {}",
1221 template.id
1222 );
1223 let (namespace, leaf) = template.id.split_once('/').unwrap_or_else(|| {
1224 panic!(
1225 "repair id `{}` is missing `<namespace>/` prefix",
1226 template.id
1227 )
1228 });
1229 assert!(
1230 !namespace.is_empty() && !leaf.is_empty(),
1231 "repair id `{}` has empty namespace or leaf",
1232 template.id
1233 );
1234 for ch in template.id.chars() {
1235 assert!(
1236 ch.is_ascii_lowercase() || ch.is_ascii_digit() || ch == '-' || ch == '/',
1237 "repair id `{}` has non-kebab character {ch:?}",
1238 template.id
1239 );
1240 }
1241 assert!(
1242 !template.summary.is_empty(),
1243 "repair {} has empty summary",
1244 template.id
1245 );
1246 let first = template.summary.chars().next().unwrap();
1248 assert!(
1249 first.is_ascii_uppercase(),
1250 "repair {} summary `{}` should start with a capital",
1251 template.id,
1252 template.summary
1253 );
1254 }
1255 }
1256
1257 #[test]
1258 fn manual_namespace_is_needs_human() {
1259 for template in REPAIR_REGISTRY {
1260 if let Some(("manual", _)) = template.id.split_once('/') {
1261 assert_eq!(
1262 template.safety,
1263 RepairSafety::NeedsHuman,
1264 "manual/* repair {} must be NeedsHuman",
1265 template.id
1266 );
1267 }
1268 }
1269 }
1270
1271 #[test]
1272 fn known_codes_carry_expected_safety_class() {
1273 let expected: &[(Code, RepairSafety, &str)] = &[
1277 (
1278 Code::FormatterWouldReformat,
1279 RepairSafety::FormatOnly,
1280 "format/reformat",
1281 ),
1282 (
1283 Code::ModuleImportUnused,
1284 RepairSafety::BehaviorPreserving,
1285 "imports/remove-unused",
1286 ),
1287 (
1288 Code::ImmutableAssignment,
1289 RepairSafety::ScopeLocal,
1290 "bindings/make-mutable",
1291 ),
1292 (
1293 Code::LintUnusedFunction,
1294 RepairSafety::SurfaceChanging,
1295 "declarations/remove-unused",
1296 ),
1297 (
1298 Code::LlmProviderIdentityBranch,
1299 RepairSafety::CapabilityChanging,
1300 "llm/use-capability-flag",
1301 ),
1302 (
1303 Code::PromptVariantExplosion,
1304 RepairSafety::NeedsHuman,
1305 "manual/needs-human",
1306 ),
1307 (
1308 Code::NonExhaustiveMatch,
1309 RepairSafety::ScopeLocal,
1310 "match/add-missing-arms",
1311 ),
1312 (
1313 Code::LintAmbientClockBuiltin,
1314 RepairSafety::ScopeLocal,
1315 "bindings/thread-harness-clock",
1316 ),
1317 ];
1318 for (code, safety, repair_id) in expected {
1319 let template = code
1320 .repair_template()
1321 .unwrap_or_else(|| panic!("{code} should have a repair template"));
1322 assert_eq!(template.safety, *safety, "{code} safety class drifted");
1323 assert_eq!(template.id, *repair_id, "{code} repair id drifted");
1324 }
1325 }
1326
1327 #[test]
1328 fn repair_templates_cover_at_least_twenty_codes() {
1329 let covered = Code::ALL
1330 .iter()
1331 .filter(|code| code.repair_template().is_some())
1332 .count();
1333 assert!(
1334 covered >= 20,
1335 "expected ≥20 codes with a repair template, found {covered}"
1336 );
1337 }
1338
1339 #[test]
1340 fn every_registered_repair_is_referenced_by_some_code() {
1341 let referenced: HashSet<&'static str> = Code::ALL
1342 .iter()
1343 .filter_map(|code| code.repair_template())
1344 .map(|template| template.id)
1345 .collect();
1346 for template in REPAIR_REGISTRY {
1347 assert!(
1348 referenced.contains(template.id),
1349 "repair {} is in REPAIR_REGISTRY but no Code maps to it",
1350 template.id
1351 );
1352 }
1353 }
1354
1355 #[test]
1356 fn every_referenced_repair_template_is_in_registry() {
1357 let registered: HashSet<&'static str> =
1358 REPAIR_REGISTRY.iter().map(|template| template.id).collect();
1359 for code in Code::ALL {
1360 let Some(template) = code.repair_template() else {
1361 continue;
1362 };
1363 assert!(
1364 registered.contains(template.id),
1365 "repair {} (used by {}) is missing from REPAIR_REGISTRY",
1366 template.id,
1367 code
1368 );
1369 }
1370 }
1371}