con un clic
check-code-drift
Detect drift between go-glx Go types and JSON schemas
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Menú
Detect drift between go-glx Go types and JSON schemas
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Basado en la clasificación ocupacional SOC
Identify drift between specification markdown files and JSON schemas
Identify drift between docs/ folder and specification/schemas/code
Review example GLX archives for compliance with specification and code
Review the GLX specification for issues, contradictions, and ambiguities
| name | check-code-drift |
| description | Detect drift between go-glx Go types and JSON schemas |
| allowed-tools | ["Read","Grep","Glob","Bash(git rev-parse:*)","Bash(git diff:*)","Bash(grep:*)","Bash(rg:*)","Bash(make check-code-drift:*)","Bash(make check-drift-allowlist:*)"] |
| model | claude-opus-4-8 |
Identify drift between the GLX Go implementation and JSON schemas/specification. Emit one findings-json block per run, conforming to .claude/skills/check-suite/findings.schema.json. Severity levels follow .claude/skills/check-suite/severity-rubric.md — default to info when uncertain.
Runner-dependent behaviors degrade gracefully. Three behaviors below assume an eval-harness runner that does not exist yet: per-entity subagent fan-out (step 5), the runner-injected
telemetryblock (output format), and native structured-output emission (output discipline). Run as a plain inline skill today, all three fall back automatically — inline per-entity analysis, notelemetryblock, and a hand-emitted-then-validatedfindings-jsonfence. Do not expect fan-out, telemetry, orresponse_formatoutput until that runner ships; the findings and their schema are identical either way.
make check-code-drift (tools/driftcheck, #673) runs in CI and mechanically catches structural drift: missing/extra fields, yaml-tag mismatches, omitempty-vs-required mismatches, and type-family mismatches. Those are already covered. Focus on what the deterministic tool cannot decide: semantic/validation drift (section 9), GEDCOM converter round-trip drift (section 11), documentation/comment mismatches, and structural cases the tool deliberately skips (value constraints like minLength/pattern/enum). Both tools read the same allowlist.
Before anything else, run:
git rev-parse HEAD
Record the short SHA. It goes in the commit field of the findings-json block.
Unless the user passes --full:
git diff --name-only origin/main...HEAD -- 'go-glx/*.go' 'specification/schema/v1/**/*.json'
Check only the entities/vocabularies touched by the changed files. If nothing is returned (clean branch or no relevant changes), fall back to full scope. With --full, always check every entity and vocabulary.
Read .claude/drift-allowlist.yaml (validated by .claude/drift-allowlist.schema.json). It is the single source of truth for known, per-symbol drift already triaged.
file and Go symbol match an allowlist entry, suppress it. Match on symbol identity, not exact string: an entry written as GLXFile.ImportMetadata matches a finding naming the field bare (ImportMetadata) or by yaml tag (metadata).permanent: true entries are by-design — treat them as not drift.tracking_issue are temporary deferrals — don't re-report; note the tracking issue.Read go-glx/types.go. Find the GLXFile struct. For every field of type map[string]*X where X is any struct, treat that as one type-to-check. The yaml tag on the GLXFile field is the schema property name; the pointee struct is the Go side. Do not use a hard-coded entity list — this auto-tracks every type added in the future.
As of the last-verified SHA above, GLXFile contains:
persons, relationships, events, places, sources, citations, repositories, assertions, media, research_logs, studiesevent_types, participant_roles, confidence_levels, relationship_types, place_types, source_types, repository_types, media_types, sex_types, gender_types, search_result_types, research_log_status_types, study_types, study_statuses, legal_statusesperson_properties, event_properties, relationship_properties, place_properties, media_properties, repository_properties, citation_properties, source_propertiesThis list is illustrative. Always derive it from the live file; the definitive list is whatever GLXFile contains at HEAD.
For each entity in scope, before invoking LLM reasoning:
If field counts match AND all yaml tags match property names, record the entity as "no structural drift detected" and skip deep LLM analysis for it. Only apply full LLM reasoning to entities where a count or tag mismatch is found, or where the entity type is Assertion or involves custom marshaling.
For each entity that requires full analysis, run the checks below against that entity's slice of context:
go-glx/types.gospecification/schema/v1/<entity>.schema.json)specification/4-entity-types/<entity>.mdgo-glx/validation.go (and go-glx/validation_temporal.go if needed)If the runner supports subagent fan-out (the Agent/Task tool is available in this context), spawn one read-only subagent per entity, each receiving exactly that entity's slice above plus these check instructions, then merge and deduplicate their findings — keeping each context small is tied to fewer false positives by the research cited in #676. Otherwise, analyze each entity inline, one at a time, in this context. The per-entity checks and the merged findings-json output are identical either way; fan-out is a performance/isolation optimization, not a correctness requirement.
Specification (*.md) → Schema (*.schema.json) → Go Code (types.go)
SOURCE OF TRUTH DERIVED FROM SPEC DERIVED FROM SCHEMA
Frame struct/field drift as "the Go code needs to change to match the schema." Frame validation drift bidirectionally — both code and spec may need updates.
properties exist in the Go struct.validation *ValidationResult).Map JSON schema types to Go types:
| Schema type | Expected Go type |
|---|---|
string | string |
array of strings | []string |
array of objects | []StructType |
object | struct or map[string]any |
number | float64 or *float64 |
boolean (required) | bool |
boolean (optional, tri-state) | *bool — nil means omitted, which is distinct from false; PropertyDefinition.Temporal and PropertyDefinition.MultiValue use this pattern intentionally |
oneOf: [string, array of strings] | NoteList with custom YAML marshal/unmarshal |
string for dates | DateString (type alias) |
nested object (optional) | *StructType pointer |
omitempty.omitempty.*float64 for latitude/longitude).yaml:"field_name" must match the JSON schema property name exactly.refType:"<target>" struct tag.persons, events, places, etc.): ^[a-zA-Z0-9-]{1,64}$ — hyphens allowed, no underscores, max 64 chars.event_types, participant_roles, etc.): ^[a-zA-Z0-9_-]+$ — underscores also allowed, no length cap.
Do not apply the entity-ID pattern to vocabulary refType targets, or vice versa.Participant struct (used by Event, Relationship, and Assertion). Verify it matches the schema's object definition and its required fields.EntityRef struct (used by Assertion.Subject and ResearchLog.Subject).property/participant, value/participant.anyOf: [sources, citations, media] constraint is handled.subject allows multiple entity types via oneOf.oneOf — exactly one field must be set.omitempty — Go serialization produces correct YAML.NoteList (alias for []string) with custom YAML marshal/unmarshal.oneOf: [{type: string}, {type: array, items: {type: string}}]types.go: 9 entities (Person, Relationship, Event, Place, Source, Citation, Repository, Assertion, Media) + Participant + Metadata.properties map[string]any with omitempty, documented as "Vocabulary-defined properties."property/value/participant (mutually exclusive) instead of a properties map.Metadata struct — check against glx-file.schema.json property metadata.Submitter is nested via *Submitter pointer — check against the schema's submitter object definition.persons, events).ImportMetadata *Metadata field has yaml tag metadata — the Go field name vs yaml-tag difference is allowlisted.All type-vocabulary schemas share VocabularyEntry. Its fields are the superset across all vocabularies; unused fields are elided via omitempty. The YAML field order label, description, category, applies_to, mime_type, gedcom is load-bearing — guarded by TestVocabularyEntryYAMLFieldOrder. Each GLXFile vocabulary map must use map[string]*VocabularyEntry.
Also check:
FieldDefinition struct (Label, Description, ValueType) against property vocabulary schemas.PropertyDefinition struct against all property vocabulary schemas. Note its Temporal and MultiValue fields are *bool by design (tri-state: true, false, nil-omitted).go-glx/constants.go for vocabulary constant coverage.Two-layer validation architecture:
Pass 1 — JSON schema validation (glx/validator.go → ValidateGLXFileStructure): enforces required, minLength/minItems/minimum/maximum, allOf/anyOf/not, additionalProperties: false, pattern, format.
Pass 2 — Go cross-reference validation (go-glx/validation.go → archive.Validate()): handles what JSON schema cannot:
validateAllProperties handles both)validateEntityFieldFormats)Do NOT flag constraints already enforced by the JSON schema as missing from Go code. Only flag validation gaps where NEITHER layer covers a specification requirement.
Also check the reverse: validation logic in Go code not documented in the specification.
The GEDCOM converter is the I/O boundary to external genealogy software — field drift causes silent round-trip data loss. Importer: gedcom_<entity>.go. Exporter: gedcom_export_<entity>.go. Exceptions:
gedcom_individual.go / gedcom_export_person.gogedcom_family.go / gedcom_export_family.gogedcom_evidence.goSearch all go-glx/gedcom_*.go (including helpers and _test.go goldens), case-insensitive:
assert.NotEqual or "should be skipped" guard assertion is the post-cleanup state, not drift.True positive — critical: Go struct has State string \yaml:"state"`but schema property isstate_province`. Tag mismatch that breaks marshaling. Report it.
True positive — major: Schema property language is optional (not in required array) but Go field has no omitempty. Serialization produces the field even when empty. Report it.
True negative — suppress: GLXFile.ImportMetadata has Go name ImportMetadata but yaml tag metadata. This is in the allowlist as a permanent by-design difference. Do not report it.
True negative — suppress: PropertyDefinition.Temporal is *bool while the schema type is boolean. This is intentional tri-state semantics (see section 2 above). Do not report it as a type mismatch.
Uncertain finding — default to info: Go comment says "maximum 64 characters" but schema has no maxLength constraint visible in the excerpt you have. Without seeing the full schema, assign info rather than guessing major.
| Category | Condition | Severity |
|---|---|---|
field_presence | Required schema field has no Go counterpart | critical |
field_presence | Go field has no schema counterpart; schema has additionalProperties: false | critical |
field_presence | Optional schema field has no Go counterpart | major |
field_presence | Internal Go field not in schema (e.g., validation *ValidationResult) | info |
required_optional | Schema-required field has omitempty in Go | critical |
required_optional | Schema-optional field missing omitempty in Go | major |
field_type | Type mismatch that breaks marshaling (string vs []string) | critical |
field_type | Type widening covered by custom marshaler (NoteList vs oneOf[string,array]) | info |
yaml_tag | Tag name mismatches schema property name | critical |
ref_type | Missing refType tag on a reference field | major |
ref_type | Wrong refType target (e.g., persons vs events) | critical |
gedcom_converter | Removed schema field still read/written by gedcom_*.go — silent data loss | critical |
gedcom_converter | Renamed schema field still read/written under old name | critical |
gedcom_converter | Added schema field with zero gedcom_*.go references — verify intentionally not exported | info |
validation | Spec requires constraint neither JSON schema nor Go enforces | major |
validation | Go enforces constraint not documented in spec | minor |
documentation | Go comment doesn't match schema description | info |
When uncertain about category or severity, use info. A false critical costs more reviewer trust than a missed minor.
For each entity type, emit a brief section:
## [Entity Type]
No structural drift detected — counts match, all yaml tags align.
OR
Drift detected — see findings-json below.
Findings in the human-readable section should include severity, category, a one-sentence description, and a Fix line.
At the end of your report, emit exactly one fenced code block with info-string findings-json. The block must be valid JSON conforming to .claude/skills/check-suite/findings.schema.json.
Every finding must have file, line, severity, category, message, validator_caught, llm_only, and location. The location object must include go_line and schema_line (integers or null). Drop any finding you cannot assign a line number to rather than emitting it as low-confidence noise — uncertain findings without coordinates cost more reviewer trust than they pay.
The telemetry block is injected by the runner (Claude Code harness), not self-reported. Leave it absent; the runner will add it.
{
"command": "check-code-drift",
"commit": "<7-char SHA from git rev-parse HEAD>",
"checked_files": [
"go-glx/types.go",
"specification/schema/v1/person.schema.json",
"..."
],
"findings": [
{
"file": "go-glx/types.go",
"line": 152,
"severity": "critical",
"category": "yaml_tag",
"message": "Go field State has yaml tag `state` but schema property is `state_province`.",
"fix": "Change yaml tag to `yaml:\"state_province,omitempty\"`.",
"validator_caught": true,
"llm_only": false,
"location": {
"go_line": 152,
"schema_line": 34
}
}
],
"positive_notes": [
"Person struct: all 4 fields present, yaml tags match, required/optional alignment correct."
],
"summary": {
"critical": 1,
"major": 0,
"minor": 0,
"info": 0,
"suppressed_as_duplicate_of_known_issue": 0
}
}
Keep reasoning terse and deterministic: when a comparison is ambiguous, prefer info with a short factual message over speculative prose — overlong chain-of-thought increases false positives here.
When this skill runs through the eval harness or any runner that supports it, emit findings-json via the model's native structured-output mode (response_format with a json_schema compiled from .claude/skills/check-suite/findings.schema.json) rather than free-form "return JSON" generation — the schema exists for exactly this purpose and makes schema-violating output impossible. If native structured output is unavailable, fall back to emitting the fenced block by hand and validate it against the schema before finishing.
End your report with one of these two lines — nothing after it:
END_OF_DRIFT_REPORT (entities checked: N, findings: M)
or, if you hit a context limit before completing all entities:
REPORT_TRUNCATED (last entity completed: <EntityName>)
The runner uses the sentinel to detect truncation and restart on the remaining scope.
$defs (not draft-07 definitions), and property co-requirements use dependentRequired (not dependencies). Do not suggest draft-07 idioms or flag 2020-12 keywords as drift.validation *ValidationResult in GLXFile) are expected to be absent from schemas.