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