glossarist 0.4.32 → 0.4.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/package.json +1 -1
  2. package/src/concept-parser.js +66 -44
  3. package/src/concept-reader.js +59 -3
  4. package/src/concept-serializer.js +4 -4
  5. package/src/diff/concept-diff.js +9 -9
  6. package/src/diff/diff-patch.js +19 -27
  7. package/src/diff/diff-renderer.js +8 -3
  8. package/src/index.js +2 -1
  9. package/src/migration/partitive-relation-migrator.js +3 -3
  10. package/src/models/{abstract-nary-relation.js → abstract-hyperedge.js} +4 -4
  11. package/src/models/completeness.js +1 -1
  12. package/src/models/concept.js +144 -71
  13. package/src/models/{generic-relation.js → generic-hyperedge.js} +18 -6
  14. package/src/models/generic-member.js +5 -5
  15. package/src/models/hyperedge-index.js +87 -0
  16. package/src/models/{concept-system-member.js → hyperedge-member.js} +12 -24
  17. package/src/models/hyperedge-registry.js +115 -0
  18. package/src/models/index.d.ts +16 -16
  19. package/src/models/index.js +13 -10
  20. package/src/models/multiplicity.js +11 -6
  21. package/src/models/partitive-hyperedge.js +69 -153
  22. package/src/models/partitive-member.js +6 -6
  23. package/src/models/relation-loader.js +28 -22
  24. package/src/rdf/gloss-concept.js +36 -20
  25. package/src/rdf/gloss-generic-hyperedge.js +92 -0
  26. package/src/rdf/gloss-partitive-relation.js +3 -3
  27. package/src/rdf/index.d.ts +4 -4
  28. package/src/rdf/index.js +0 -1
  29. package/src/reference-resolver.js +14 -12
  30. package/src/validators/binary-has-part-redundancy-rule.js +6 -6
  31. package/src/validators/delimiting-coherence-rule.js +2 -2
  32. package/src/validators/hyperedge-coherence-rule.js +98 -0
  33. package/src/validators/index.js +3 -9
  34. package/src/validators/partitive-relation-coherence-rule.js +6 -79
  35. package/src/models/partitive-relation.js +0 -76
  36. package/src/models/relation-type-registry.js +0 -25
  37. package/src/rdf/gloss-hyperedge.js +0 -90
  38. package/src/validators/partitive-drift-rule.js +0 -51
  39. package/src/validators/partitive-hyperedge-rule.js +0 -75
@@ -1,90 +0,0 @@
1
- // PartitiveHyperedge → RDF quads.
2
- //
3
- // Shape (per concept-model ontology, v3.1.0):
4
- // <base>/hyperedge/<carrying-id>/<comprehensive-id>
5
- // rdf:type gloss:PartitiveHyperedge
6
- // gloss:comprehensive <base>/concept/<comprehensive-id>
7
- // gloss:hasPart <base>/concept/<part-id> (one per part)
8
- // gloss:enumeration "closed"|"open" (literal)
9
- // gloss:hasPluralityMarker "double"|"dashed" (literal, one per marker)
10
- // gloss:hyperedgeContent "..." (literal, omitted if absent)
11
- //
12
- // The carrying concept links to this hyperedge via `gloss:hasPartitiveHyperedge`
13
- // (emitted by gloss-concept.js#conceptToQuads).
14
- //
15
- // CROSS-REPO STATUS (validated 2026-07-22):
16
- // - Subject URI scheme: matches Ruby (`hyperedge/<c>/<comp>`).
17
- // - `comprehensive`, `enumeration`, `hasPluralityMarker`, `hyperedgeContent`:
18
- // predicates + object types match Ruby.
19
- // - `hasPart`: JS follows the concept-model ontology; Ruby emits
20
- // `gloss:part` instead. This is a Ruby bug — tracked as a cross-repo
21
- // follow-up. Once Ruby aligns with the ontology, output will be
22
- // byte-equivalent (modulo serialization order).
23
-
24
- import { PRED } from './predicates.js';
25
- import { WELL_KNOWN } from './prefixes.js';
26
- import { namedNode, literal, quad } from './terms.js';
27
-
28
- export function* hyperedgeToQuads(hyperedge, { parentUri, index }) {
29
- const heSubject = hyperedgeSubjectUri(parentUri, hyperedge, index);
30
- const s = namedNode(heSubject);
31
-
32
- yield quad(s, namedNode(WELL_KNOWN.rdfType), namedNode(PRED.gloss.PartitiveHyperedge));
33
-
34
- const comp = hyperedge.comprehensive;
35
- if (comp && (comp.source || comp.id)) {
36
- yield quad(s, namedNode(PRED.gloss.comprehensive),
37
- namedNode(conceptRefUri(comp, parentUri)));
38
- }
39
-
40
- const parts = Array.isArray(hyperedge.parts) ? hyperedge.parts : [];
41
- for (const p of parts) {
42
- if (p && (p.source || p.id)) {
43
- yield quad(s, namedNode(PRED.gloss.hasPart),
44
- namedNode(conceptRefUri(p, parentUri)));
45
- }
46
- }
47
-
48
- if (hyperedge.enumeration) {
49
- yield quad(s, namedNode(PRED.gloss.enumeration), literal(hyperedge.enumeration));
50
- }
51
-
52
- for (const marker of Array.isArray(hyperedge.markers) ? hyperedge.markers : []) {
53
- yield quad(s, namedNode(PRED.gloss.hasPluralityMarker), literal(marker));
54
- }
55
-
56
- if (hyperedge.content) {
57
- const content = hyperedge.content;
58
- if (typeof content === 'string') {
59
- yield quad(s, namedNode(PRED.gloss.hyperedgeContent), literal(content));
60
- } else if (typeof content === 'object') {
61
- for (const [lang, text] of Object.entries(content)) {
62
- if (typeof text !== 'string') continue;
63
- yield quad(s, namedNode(PRED.gloss.hyperedgeContent), literal(text, lang));
64
- }
65
- }
66
- }
67
- }
68
-
69
- // Subject URI scheme: `<base>/hyperedge/<carrying-id>/<comprehensive-id>`.
70
- // Matches Ruby's `subject { |h| "hyperedge/#{h.identifier}/#{h.comprehensive_id}" }`.
71
- // Falls back to an index suffix if comprehensive has no id, so two
72
- // hyperedges on the same concept never collide.
73
- export function hyperedgeSubjectUri(parentUri, hyperedge, index) {
74
- const base = parentUri.replace(/\/concept\/[^/]+$/, '');
75
- const carrierId = parentUri.split('/concept/')[1] ?? `carrier-${index}`;
76
- const compId = hyperedge?.comprehensive?.id ?? `comp-${index}`;
77
- return `${base}/hyperedge/${carrierId}/${compId}`;
78
- }
79
-
80
- // ConceptRef URI: `<base>/concept/<id>`. Matches Ruby's
81
- // `concept_to_gloss_transform.rb#hyperedge_concept_uri`. Source is
82
- // dropped from the URI; it stays on the ConceptRef object if needed.
83
- function conceptRefUri(ref, parentUri) {
84
- const id = ref?.id;
85
- if (id) {
86
- const base = parentUri.split('/concept/')[0];
87
- return `${base}/concept/${id}`;
88
- }
89
- return `urn:glossarist:${ref?.source ?? 'unknown'}:`;
90
- }
@@ -1,51 +0,0 @@
1
- // Warns when a concept expresses the same partitive relationship via
2
- // BOTH a binary edge (broader_partitive / narrower_partitive /
3
- // has_part / is_part_of) AND a PartitiveHyperedge part targeting the
4
- // same concept. Round 1's decision (TODO.hyperedges/24) was "binary
5
- // and hyperedge coexist, no automatic reconciliation." This rule is
6
- // the detector for the resulting drift: pure warning, doesn't block
7
- // save, just surfaces the duplication so the author can pick one
8
- // encoding.
9
- //
10
- // See TODO.hyperedges-v2/21 for the design rationale.
11
-
12
- import { ValidationRule } from './validation-rule.js';
13
-
14
- const BINARY_PARTITIVE_TYPES = new Set([
15
- 'broader_partitive', 'narrower_partitive',
16
- 'has_part', 'is_part_of',
17
- ]);
18
-
19
- export class PartitiveDriftRule extends ValidationRule {
20
- constructor() { super('partitive-drift', 'warning'); }
21
-
22
- validate(concept, path, result) {
23
- const hyperedgeTargets = new Set();
24
- for (const he of concept.partitiveHyperedges ?? []) {
25
- const comp = he.comprehensive;
26
- const compKey = `${comp?.source ?? ''}|${comp?.id ?? ''}`;
27
- for (const part of he.parts ?? []) {
28
- const key = `${part.source ?? ''}|${part.id ?? ''}`;
29
- if (key === '|' || key === compKey) continue;
30
- hyperedgeTargets.add(key);
31
- }
32
- }
33
-
34
- if (hyperedgeTargets.size === 0) return;
35
-
36
- const related = concept.relatedConcepts ?? [];
37
- for (let i = 0; i < related.length; i++) {
38
- const rc = related[i];
39
- if (!BINARY_PARTITIVE_TYPES.has(rc.type)) continue;
40
- const ref = rc.ref;
41
- const key = `${ref?.source ?? ''}|${ref?.id ?? ''}`;
42
- if (hyperedgeTargets.has(key)) {
43
- this.addIssue(result,
44
- `${path}relatedConcepts[${i}]`,
45
- `concept has both a binary '${rc.type}' edge and a ` +
46
- `PartitiveHyperedge part targeting (${ref?.source ?? '?'}, ` +
47
- `${ref?.id ?? '?'}); pick one encoding to avoid divergence`);
48
- }
49
- }
50
- }
51
- }
@@ -1,75 +0,0 @@
1
- import { ValidationRule } from './validation-rule.js';
2
-
3
- // Validates semantic invariants of PartitiveHyperedge entries that
4
- // the model constructor does NOT enforce. The constructor already
5
- // rejects empty comprehensive, empty parts, self-loops, invalid
6
- // marker values, and duplicate markers. This rule provides a
7
- // defensive walk at validation time plus a warning for the
8
- // implicit-enumeration case (absent in the source YAML, filled by
9
- // the model's DEFAULT_ENUMERATION constant).
10
- export class PartitiveHyperedgeShapeRule extends ValidationRule {
11
- constructor() { super('partitive-hyperedge-shape'); }
12
-
13
- validate(concept, path, result) {
14
- const edges = concept.partitiveHyperedges ?? [];
15
- for (let i = 0; i < edges.length; i++) {
16
- const he = edges[i];
17
- const base = `${path}partitiveHyperedges[${i}]`;
18
-
19
- this._checkComprehensive(he, i, base, result);
20
- this._checkParts(he, i, base, result);
21
- this._checkMarkers(he, i, base, result);
22
- this._checkSelfLoop(he, i, base, result);
23
- }
24
- }
25
-
26
- _checkComprehensive(he, idx, base, result) {
27
- const ref = he.comprehensive;
28
- if (!ref || (!ref.source && !ref.id)) {
29
- this.addIssue(result, `${base}.comprehensive`,
30
- `partitive_hyperedge ${idx + 1} has empty comprehensive`);
31
- }
32
- }
33
-
34
- _checkParts(he, idx, base, result) {
35
- const parts = he.parts ?? [];
36
- if (parts.length === 0) {
37
- this.addIssue(result, `${base}.parts`,
38
- `partitive_hyperedge ${idx + 1} has no parts`);
39
- return;
40
- }
41
- for (let j = 0; j < parts.length; j++) {
42
- const p = parts[j];
43
- if (!p || (!p.source && !p.id)) {
44
- this.addIssue(result, `${base}.parts[${j}]`,
45
- `partitive_hyperedge ${idx + 1} part ${j + 1} has empty ref`);
46
- }
47
- }
48
- }
49
-
50
- _checkMarkers(he, idx, base, result) {
51
- const markers = he.markers ?? [];
52
- const seen = new Set();
53
- for (const m of markers) {
54
- if (seen.has(m)) {
55
- this.addIssue(result, `${base}.markers`,
56
- `partitive_hyperedge ${idx + 1} has duplicate marker '${m}'`);
57
- }
58
- seen.add(m);
59
- }
60
- }
61
-
62
- _checkSelfLoop(he, idx, base, result) {
63
- const c = he.comprehensive;
64
- if (!c) return;
65
- const cKey = `${c.source ?? ''}:${c.id ?? ''}`;
66
- for (let j = 0; j < (he.parts ?? []).length; j++) {
67
- const p = he.parts[j];
68
- const pKey = `${p?.source ?? ''}:${p?.id ?? ''}`;
69
- if (pKey === cKey) {
70
- this.addIssue(result, `${base}.parts[${j}]`,
71
- `partitive_hyperedge ${idx + 1} part ${j + 1} cannot be the comprehensive`);
72
- }
73
- }
74
- }
75
- }