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