@@ -92,7 +92,11 @@ const required = [
9292 "plugins/records/skills/fhir-validation/references/structural-validation.md" ,
9393 "plugins/records/skills/fhir-validation/scripts/lib/operationoutcome-issues.mjs" ,
9494 "plugins/records/skills/fhir-validation/scripts/lib/r4-structural-schema.mjs" ,
95+ "plugins/records/skills/fhir-validation/scripts/lib/r4-primitives.mjs" ,
96+ "plugins/records/skills/fhir-validation/scripts/lib/fhirpath-pointer.mjs" ,
9597 "plugins/records/skills/fhir-validation/scripts/validate-structural.mjs" ,
98+ "plugins/records/skills/fhir-validation/scripts/validate.mjs" ,
99+ "plugins/records/skills/fhir-validation/scripts/match-slices.mjs" ,
96100 "plugins/records/skills/fhir-validation/scripts/generate-issue-map-doc.mjs" ,
97101 "plugins/records/skills/fhir-validation/scripts/analyze-structuredefinition.mjs" ,
98102 "plugins/records/skills/fhir-validation/scripts/detect-fhir-project.mjs" ,
@@ -106,6 +110,7 @@ const required = [
106110 "plugins/records/commands/init-ci.md" ,
107111 "plugins/records/commands/explain-outcome.md" ,
108112 "plugins/records/commands/derive-quality-rules.md" ,
113+ "plugins/records/commands/validate.md" ,
109114 "plugins/records/agents/fhir-validation-reviewer.md" ,
110115 "plugins/records/fixtures/invalid-observation.json" ,
111116 "plugins/records/fixtures/operationoutcome-required.json" ,
@@ -168,10 +173,14 @@ for (const script of [
168173 "plugins/records/skills/fhir-validation/scripts/generate-ci.mjs" ,
169174 "plugins/records/skills/fhir-validation/scripts/map-fhir-expression.mjs" ,
170175 "plugins/records/skills/fhir-validation/scripts/validate-structural.mjs" ,
176+ "plugins/records/skills/fhir-validation/scripts/validate.mjs" ,
177+ "plugins/records/skills/fhir-validation/scripts/match-slices.mjs" ,
171178 "plugins/records/skills/fhir-validation/scripts/generate-issue-map-doc.mjs" ,
172179 "plugins/records/skills/fhir-validation/scripts/analyze-structuredefinition.mjs" ,
173180 "plugins/records/skills/fhir-validation/scripts/lib/operationoutcome-issues.mjs" ,
174181 "plugins/records/skills/fhir-validation/scripts/lib/r4-structural-schema.mjs" ,
182+ "plugins/records/skills/fhir-validation/scripts/lib/r4-primitives.mjs" ,
183+ "plugins/records/skills/fhir-validation/scripts/lib/fhirpath-pointer.mjs" ,
175184] ) {
176185 const scriptStat = await stat ( path . join ( repo , script ) ) ;
177186 if ( ! scriptStat . isFile ( ) ) errors . push ( `Script is not a file: ${ script } ` ) ;
@@ -281,6 +290,18 @@ function runJsonInput(script, input) {
281290 }
282291}
283292
293+ // Run an arbitrary JSON-emitting script with file args and return status+parsed.
294+ function runValidatorScript ( script , args ) {
295+ const result = spawnSync ( process . execPath , [ script , ...args ] , { cwd : repo , encoding : "utf8" } ) ;
296+ let parsed = null ;
297+ try {
298+ parsed = JSON . parse ( result . stdout ) ;
299+ } catch {
300+ errors . push ( `${ rel ( script ) } did not output JSON: ${ result . stderr || result . stdout } ` ) ;
301+ }
302+ return { status : result . status , parsed } ;
303+ }
304+
284305const invalidObs = runValidator ( [ path . join ( plugin , "fixtures/invalid-observation.json" ) ] ) ;
285306if ( invalidObs . parsed ) {
286307 if ( invalidObs . status !== 1 ) errors . push ( "Structural validator should exit 1 for the invalid Observation." ) ;
@@ -403,6 +424,46 @@ if (!sushiCi.includes("sushi .")) errors.push("CI generator --sushi should inclu
403424const uploadCi = spawnSync ( process . execPath , [ ciGen , "--upload-artifact" ] , { cwd : repo , encoding : "utf8" } ) . stdout ;
404425if ( ! uploadCi . includes ( "upload-artifact" ) ) errors . push ( "CI generator --upload-artifact should add an artifact upload step." ) ;
405426
427+ // --- v0.5.0: expanded schema, primitives, required choice, references ---
428+ const condition = validatorCodes ( [ path . join ( plugin , "fixtures/condition-missing-subject.json" ) ] ) ;
429+ if ( ! condition . codes ?. includes ( "required:Condition.subject" ) ) errors . push ( "Validator should require Condition.subject." ) ;
430+ if ( ! condition . codes ?. some ( ( entry ) => entry . startsWith ( "value:Condition.recordedDate" ) ) ) errors . push ( "Validator should flag the malformed Condition.recordedDate primitive." ) ;
431+
432+ const badPrimitive = validatorCodes ( [ ] , '{"resourceType":"Patient","id":"x","birthDate":"2020-13-01","active":"yes"}' ) ;
433+ if ( ! badPrimitive . codes ?. some ( ( entry ) => entry . startsWith ( "value:Patient.birthDate" ) ) ) errors . push ( "Validator should flag an invalid date primitive." ) ;
434+ if ( ! badPrimitive . codes ?. some ( ( entry ) => entry . startsWith ( "value:Patient.active" ) ) ) errors . push ( "Validator should flag a non-boolean primitive." ) ;
435+
436+ const reqChoiceMissing = validatorCodes ( [ ] , '{"resourceType":"MedicationRequest","id":"m","status":"active","intent":"order","subject":{"reference":"Patient/p"}}' ) ;
437+ if ( ! reqChoiceMissing . codes ?. some ( ( entry ) => entry . includes ( "required:MedicationRequest.medication[x]" ) ) ) errors . push ( "Validator should require a medication[x] choice." ) ;
438+ const reqChoicePresent = validatorCodes ( [ ] , '{"resourceType":"MedicationRequest","id":"m","status":"active","intent":"order","subject":{"reference":"Patient/p"},"medicationCodeableConcept":{}}' ) ;
439+ if ( reqChoicePresent . codes ?. some ( ( entry ) => entry . includes ( "MedicationRequest.medication[x]" ) ) ) errors . push ( "Validator should accept a satisfied medication[x] choice." ) ;
440+
441+ const containedMissing = runValidator ( [ ] , '{"resourceType":"Observation","status":"final","code":{},"subject":{"reference":"#p1"}}' ) ;
442+ if ( containedMissing . parsed ?. summary . error !== 1 || ! containedMissing . parsed ?. operationOutcome . issue . some ( ( entry ) => entry . severity === "error" && entry . code === "not-found" ) ) {
443+ errors . push ( "Validator should flag an unresolved contained reference as an error." ) ;
444+ }
445+ const bundleUnresolved = runValidator ( [ ] , '{"resourceType":"Bundle","type":"collection","entry":[{"resource":{"resourceType":"Observation","status":"final","code":{},"subject":{"reference":"Patient/missing"}}}]}' ) ;
446+ if ( bundleUnresolved . parsed ?. summary . warning !== 1 || bundleUnresolved . status !== 0 ) errors . push ( "Validator should warn (not error) on an unresolved intra-Bundle reference." ) ;
447+ const bundleResolved = runValidator ( [ ] , '{"resourceType":"Bundle","type":"collection","entry":[{"resource":{"resourceType":"Patient","id":"p"}},{"resource":{"resourceType":"Observation","status":"final","code":{},"subject":{"reference":"Patient/p"}}}]}' ) ;
448+ if ( bundleResolved . parsed ?. summary . warning !== 0 || bundleResolved . parsed ?. summary . error !== 0 ) errors . push ( "Validator should resolve an intra-Bundle reference cleanly." ) ;
449+
450+ // --- v0.5.0: instance-based slice matching ---
451+ const matcher = path . join ( plugin , "skills/fhir-validation/scripts/match-slices.mjs" ) ;
452+ const slice = runJson ( matcher , [ path . join ( plugin , "fixtures/structuredefinition-sliced.json" ) , path . join ( plugin , "fixtures/observation-sliced-instance.json" ) ] ) ;
453+ const sliceElement = slice ?. slicedElements ?. [ 0 ] ;
454+ if ( ! sliceElement || JSON . stringify ( sliceElement . slices ) !== JSON . stringify ( { vital : [ 0 ] , lab : [ 1 ] } ) ) errors . push ( "Slice matcher should attribute entries 0 and 1 to the vital and lab slices." ) ;
455+ if ( ! sliceElement ?. unmatched ?. includes ( 2 ) ) errors . push ( "Slice matcher should report the unmatched social-history entry." ) ;
456+
457+ // --- v0.5.0: orchestrator ---
458+ const orchestrator = path . join ( plugin , "skills/fhir-validation/scripts/validate.mjs" ) ;
459+ const orchFile = runValidatorScript ( orchestrator , [ path . join ( plugin , "fixtures/invalid-observation.json" ) ] ) ;
460+ if ( orchFile . status !== 1 || orchFile . parsed ?. totals . error !== 2 ) errors . push ( "Orchestrator should report 2 errors and exit 1 for the invalid Observation." ) ;
461+ const firstIssue = orchFile . parsed ?. results ?. [ 0 ] ?. issues ?. [ 0 ] ;
462+ if ( ! firstIssue ?. jsonPointer || ! firstIssue ?. safeFixability ) errors . push ( "Orchestrator should enrich issues with a JSON Pointer and fixability guidance." ) ;
463+ const orchDir = runValidatorScript ( orchestrator , [ miniIg ] ) ;
464+ if ( orchDir . parsed ?. detector ?. projectType !== "fsh-ig" ) errors . push ( "Orchestrator should include detector context for a directory target." ) ;
465+ if ( ! ( orchDir . parsed ?. totals . resources >= 2 ) ) errors . push ( "Orchestrator should validate every resource in a directory." ) ;
466+
406467if ( errors . length ) {
407468 console . error ( errors . map ( ( error ) => `- ${ error } ` ) . join ( "\n" ) ) ;
408469 process . exit ( 1 ) ;
0 commit comments