structured-context 0.9.0

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 (112) hide show
  1. package/README.md +348 -0
  2. package/dist/commands/diagram.d.ts +5 -0
  3. package/dist/commands/diagram.js +12 -0
  4. package/dist/commands/docs.d.ts +1 -0
  5. package/dist/commands/docs.js +67 -0
  6. package/dist/commands/dump.d.ts +2 -0
  7. package/dist/commands/dump.js +6 -0
  8. package/dist/commands/plugins.d.ts +1 -0
  9. package/dist/commands/plugins.js +23 -0
  10. package/dist/commands/render.d.ts +6 -0
  11. package/dist/commands/render.js +35 -0
  12. package/dist/commands/schemas.d.ts +6 -0
  13. package/dist/commands/schemas.js +268 -0
  14. package/dist/commands/show.d.ts +4 -0
  15. package/dist/commands/show.js +7 -0
  16. package/dist/commands/spaces.d.ts +1 -0
  17. package/dist/commands/spaces.js +36 -0
  18. package/dist/commands/template-sync.d.ts +3 -0
  19. package/dist/commands/template-sync.js +13 -0
  20. package/dist/commands/validate-file.d.ts +28 -0
  21. package/dist/commands/validate-file.js +133 -0
  22. package/dist/commands/validate.d.ts +16 -0
  23. package/dist/commands/validate.js +349 -0
  24. package/dist/config.d.ts +38 -0
  25. package/dist/config.js +179 -0
  26. package/dist/constants.d.ts +6 -0
  27. package/dist/constants.js +6 -0
  28. package/dist/filter/augment-nodes.d.ts +23 -0
  29. package/dist/filter/augment-nodes.js +95 -0
  30. package/dist/filter/expand-include.d.ts +62 -0
  31. package/dist/filter/expand-include.js +181 -0
  32. package/dist/filter/filter-nodes.d.ts +21 -0
  33. package/dist/filter/filter-nodes.js +73 -0
  34. package/dist/filter/parse-expression.d.ts +20 -0
  35. package/dist/filter/parse-expression.js +60 -0
  36. package/dist/index.d.ts +3 -0
  37. package/dist/index.js +161 -0
  38. package/dist/integrations/miro/cache.d.ts +21 -0
  39. package/dist/integrations/miro/cache.js +55 -0
  40. package/dist/integrations/miro/client.d.ts +99 -0
  41. package/dist/integrations/miro/client.js +118 -0
  42. package/dist/integrations/miro/layout.d.ts +28 -0
  43. package/dist/integrations/miro/layout.js +72 -0
  44. package/dist/integrations/miro/styles.d.ts +11 -0
  45. package/dist/integrations/miro/styles.js +65 -0
  46. package/dist/integrations/miro/sync.d.ts +8 -0
  47. package/dist/integrations/miro/sync.js +347 -0
  48. package/dist/plugin-api.d.ts +12 -0
  49. package/dist/plugin-api.js +7 -0
  50. package/dist/plugins/index.d.ts +3 -0
  51. package/dist/plugins/index.js +3 -0
  52. package/dist/plugins/loader.d.ts +21 -0
  53. package/dist/plugins/loader.js +104 -0
  54. package/dist/plugins/markdown/index.d.ts +48 -0
  55. package/dist/plugins/markdown/index.js +51 -0
  56. package/dist/plugins/markdown/parse-embedded.d.ts +90 -0
  57. package/dist/plugins/markdown/parse-embedded.js +663 -0
  58. package/dist/plugins/markdown/read-space.d.ts +7 -0
  59. package/dist/plugins/markdown/read-space.js +89 -0
  60. package/dist/plugins/markdown/render-bullets.d.ts +2 -0
  61. package/dist/plugins/markdown/render-bullets.js +42 -0
  62. package/dist/plugins/markdown/render-mermaid.d.ts +2 -0
  63. package/dist/plugins/markdown/render-mermaid.js +57 -0
  64. package/dist/plugins/markdown/template-sync.d.ts +16 -0
  65. package/dist/plugins/markdown/template-sync.js +294 -0
  66. package/dist/plugins/markdown/util.d.ts +19 -0
  67. package/dist/plugins/markdown/util.js +80 -0
  68. package/dist/plugins/util.d.ts +60 -0
  69. package/dist/plugins/util.js +7 -0
  70. package/dist/read/read-space.d.ts +2 -0
  71. package/dist/read/read-space.js +22 -0
  72. package/dist/read/resolve-graph-edges.d.ts +11 -0
  73. package/dist/read/resolve-graph-edges.js +201 -0
  74. package/dist/read/wikilink-utils.d.ts +16 -0
  75. package/dist/read/wikilink-utils.js +38 -0
  76. package/dist/render/registry.d.ts +13 -0
  77. package/dist/render/registry.js +22 -0
  78. package/dist/render/render.d.ts +4 -0
  79. package/dist/render/render.js +28 -0
  80. package/dist/schema/evaluate-rule.d.ts +30 -0
  81. package/dist/schema/evaluate-rule.js +82 -0
  82. package/dist/schema/metadata-contract.d.ts +538 -0
  83. package/dist/schema/metadata-contract.js +115 -0
  84. package/dist/schema/schema-refs.d.ts +22 -0
  85. package/dist/schema/schema-refs.js +168 -0
  86. package/dist/schema/schema.d.ts +27 -0
  87. package/dist/schema/schema.js +378 -0
  88. package/dist/schema/validate-graph.d.ts +24 -0
  89. package/dist/schema/validate-graph.js +141 -0
  90. package/dist/schema/validate-rules.d.ts +10 -0
  91. package/dist/schema/validate-rules.js +51 -0
  92. package/dist/schemas/_ost_strict.json +81 -0
  93. package/dist/schemas/_sctx_base.json +72 -0
  94. package/dist/schemas/general.json +261 -0
  95. package/dist/schemas/generated/_structured_context_schema_meta.json +191 -0
  96. package/dist/schemas/knowledge_wiki.json +206 -0
  97. package/dist/schemas/strict_ost.json +97 -0
  98. package/dist/space-graph.d.ts +28 -0
  99. package/dist/space-graph.js +82 -0
  100. package/dist/types.d.ts +145 -0
  101. package/dist/types.js +0 -0
  102. package/docs/concepts.md +391 -0
  103. package/docs/config.md +140 -0
  104. package/docs/rules.md +120 -0
  105. package/docs/schemas.md +340 -0
  106. package/package.json +69 -0
  107. package/schemas/_ost_strict.json +81 -0
  108. package/schemas/_sctx_base.json +72 -0
  109. package/schemas/general.json +261 -0
  110. package/schemas/generated/_structured_context_schema_meta.json +191 -0
  111. package/schemas/knowledge_wiki.json +206 -0
  112. package/schemas/strict_ost.json +97 -0
@@ -0,0 +1,191 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://raw.githubusercontent.com/mindsocket/structured-context/main/schemas/generated/_structured_context_schema_meta.json",
4
+ "title": "structured-context schema dialect",
5
+ "description": "Extends JSON Schema Draft-07 with top-level $metadata for hierarchy and rule metadata.",
6
+ "type": "object",
7
+ "allOf": [
8
+ {
9
+ "$ref": "http://json-schema.org/draft-07/schema#"
10
+ }
11
+ ],
12
+ "properties": {
13
+ "$metadata": {
14
+ "type": "object",
15
+ "properties": {
16
+ "hierarchy": {
17
+ "type": "object",
18
+ "properties": {
19
+ "levels": {
20
+ "type": "array",
21
+ "minItems": 1,
22
+ "items": {
23
+ "oneOf": [
24
+ {
25
+ "type": "string",
26
+ "minLength": 1
27
+ },
28
+ {
29
+ "type": "object",
30
+ "properties": {
31
+ "type": {
32
+ "type": "string",
33
+ "minLength": 1
34
+ },
35
+ "field": {
36
+ "type": "string",
37
+ "minLength": 1
38
+ },
39
+ "fieldOn": {
40
+ "enum": ["child", "parent"]
41
+ },
42
+ "multiple": {
43
+ "type": "boolean"
44
+ },
45
+ "templateFormat": {
46
+ "enum": ["heading", "list", "table", "page"]
47
+ },
48
+ "matchers": {
49
+ "type": "array",
50
+ "items": {
51
+ "type": "string",
52
+ "minLength": 1
53
+ },
54
+ "minItems": 1
55
+ },
56
+ "embeddedTemplateFields": {
57
+ "type": "array",
58
+ "items": {
59
+ "type": "string",
60
+ "minLength": 1
61
+ }
62
+ },
63
+ "selfRef": {
64
+ "type": "boolean"
65
+ },
66
+ "selfRefField": {
67
+ "type": "string",
68
+ "minLength": 1
69
+ }
70
+ },
71
+ "required": ["type"],
72
+ "additionalProperties": false
73
+ }
74
+ ]
75
+ }
76
+ },
77
+ "allowSkipLevels": {
78
+ "type": "boolean"
79
+ }
80
+ },
81
+ "required": ["levels"],
82
+ "additionalProperties": false
83
+ },
84
+ "relationships": {
85
+ "type": "array",
86
+ "items": {
87
+ "type": "object",
88
+ "properties": {
89
+ "parent": {
90
+ "type": "string",
91
+ "minLength": 1
92
+ },
93
+ "type": {
94
+ "type": "string",
95
+ "minLength": 1
96
+ },
97
+ "field": {
98
+ "type": "string",
99
+ "minLength": 1
100
+ },
101
+ "fieldOn": {
102
+ "enum": ["child", "parent"]
103
+ },
104
+ "multiple": {
105
+ "type": "boolean"
106
+ },
107
+ "templateFormat": {
108
+ "enum": ["heading", "list", "table", "page"]
109
+ },
110
+ "matchers": {
111
+ "type": "array",
112
+ "items": {
113
+ "type": "string",
114
+ "minLength": 1
115
+ },
116
+ "minItems": 1
117
+ },
118
+ "embeddedTemplateFields": {
119
+ "type": "array",
120
+ "items": {
121
+ "type": "string",
122
+ "minLength": 1
123
+ }
124
+ }
125
+ },
126
+ "required": ["parent", "type"],
127
+ "additionalProperties": false
128
+ }
129
+ },
130
+ "aliases": {
131
+ "type": "object",
132
+ "additionalProperties": {
133
+ "type": "string",
134
+ "minLength": 1
135
+ }
136
+ },
137
+ "rules": {
138
+ "type": "array",
139
+ "items": {
140
+ "oneOf": [
141
+ {
142
+ "type": "object",
143
+ "properties": {
144
+ "id": {
145
+ "type": "string",
146
+ "minLength": 1
147
+ },
148
+ "category": {
149
+ "enum": ["validation", "coherence", "workflow", "best-practice"]
150
+ },
151
+ "description": {
152
+ "type": "string",
153
+ "minLength": 1
154
+ },
155
+ "check": {
156
+ "type": "string",
157
+ "minLength": 1
158
+ },
159
+ "type": {
160
+ "type": "string",
161
+ "minLength": 1
162
+ },
163
+ "scope": {
164
+ "enum": ["global"]
165
+ },
166
+ "override": {
167
+ "type": "boolean"
168
+ }
169
+ },
170
+ "required": ["id", "category", "description", "check"],
171
+ "additionalProperties": false
172
+ },
173
+ {
174
+ "type": "object",
175
+ "properties": {
176
+ "$ref": {
177
+ "type": "string",
178
+ "minLength": 1
179
+ }
180
+ },
181
+ "required": ["$ref"],
182
+ "additionalProperties": false
183
+ }
184
+ ]
185
+ }
186
+ }
187
+ },
188
+ "additionalProperties": false
189
+ }
190
+ }
191
+ }
@@ -0,0 +1,206 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/mindsocket/structured-context/main/schemas/generated/_structured_context_schema_meta.json",
3
+ "$id": "sctx://knowledge_wiki",
4
+ "title": "Knowledge Wiki",
5
+ "description": "Lightweight schema for LLM-maintained knowledge wikis. Supports source pages, concept pages, syntheses, and notes. Designed for compounding knowledge bases where the LLM creates and maintains wiki pages from raw sources.\n\nFlat entity model — no hierarchy, no required parent links. Wikilinks between any pages are valid for arbitrary cross-referencing. The schema validates structure and provenance; it does not constrain meaning.\n\nBased on the pattern described in Karpathy's LLM Wiki (2026).",
6
+ "$metadata": {
7
+ "aliases": {
8
+ "source_summary": "source",
9
+ "study": "source",
10
+ "article": "source",
11
+ "paper": "source",
12
+ "research": "source"
13
+ },
14
+ "relationships": [
15
+ {
16
+ "parent": "source",
17
+ "type": "source",
18
+ "field": "sources",
19
+ "fieldOn": "child",
20
+ "multiple": true
21
+ },
22
+ {
23
+ "parent": "source",
24
+ "type": "synthesis",
25
+ "field": "sources",
26
+ "fieldOn": "child",
27
+ "multiple": true
28
+ },
29
+ {
30
+ "parent": "concept",
31
+ "type": "synthesis",
32
+ "field": "concepts",
33
+ "fieldOn": "child",
34
+ "multiple": true
35
+ }
36
+ ],
37
+ "rules": [
38
+ {
39
+ "id": "synthesis-references-sources",
40
+ "category": "coherence",
41
+ "description": "Syntheses should reference at least one contributing source in their sources array",
42
+ "type": "synthesis",
43
+ "check": "$exists(current.sources) and $count(current.sources) >= 1"
44
+ },
45
+ {
46
+ "id": "concept-has-summary",
47
+ "category": "best-practice",
48
+ "description": "Concept pages should have a short summary for index discoverability",
49
+ "type": "concept",
50
+ "check": "$exists(current.summary) and $length(current.summary) > 0"
51
+ }
52
+ ]
53
+ },
54
+ "oneOf": [
55
+ {
56
+ "type": "object",
57
+ "description": "A processed summary of a raw source (article, paper, book, video, thread, podcast, etc.). Captures key information from the source in structured form with provenance.",
58
+ "allOf": [{ "$ref": "sctx://_sctx_base#/$defs/baseNodeProps" }],
59
+ "properties": {
60
+ "type": { "const": "source" },
61
+ "summary": {
62
+ "$ref": "sctx://_sctx_base#/$defs/summary"
63
+ },
64
+ "url": {
65
+ "type": "string",
66
+ "description": "URL of the original source"
67
+ },
68
+ "local_copy": {
69
+ "$ref": "sctx://_sctx_base#/$defs/wikilink",
70
+ "description": "Wikilink to a local copy of the source (e.g. a saved PDF or clipped note)"
71
+ },
72
+ "author": {
73
+ "type": "string",
74
+ "description": "Author or creator of the source"
75
+ },
76
+ "book": {
77
+ "type": "string",
78
+ "description": "Book title and author if the source is a book"
79
+ },
80
+ "published_date": {
81
+ "type": "string",
82
+ "format": "date",
83
+ "description": "Original publication date"
84
+ },
85
+ "source_type": {
86
+ "type": "string",
87
+ "description": "Kind of source material. Free-form — common values include article, paper, book, video, podcast, thread, substack, tweet, website. Not enum-constrained to allow evolving vocabulary."
88
+ },
89
+ "sources": {
90
+ "type": "array",
91
+ "items": { "$ref": "sctx://_sctx_base#/$defs/wikilink" },
92
+ "description": "Wikilinks to other source pages that this one references or extends"
93
+ }
94
+ },
95
+ "required": ["type", "url"],
96
+ "additionalProperties": true,
97
+ "examples": [
98
+ {
99
+ "type": "source",
100
+ "author": "Joe Bloggs",
101
+ "url": "https://example.com/design-of-yesterday-things",
102
+ "source_type": "book",
103
+ "tags": ["design", "ux", "psychology"]
104
+ }
105
+ ]
106
+ },
107
+ {
108
+ "type": "object",
109
+ "description": "A concept or entity page — defines and frames a specific idea, mechanism, or domain term. May draw from multiple sources. The building blocks of the wiki's knowledge graph.",
110
+ "allOf": [{ "$ref": "sctx://_sctx_base#/$defs/baseNodeProps" }],
111
+ "properties": {
112
+ "type": { "const": "concept" },
113
+ "summary": {
114
+ "$ref": "sctx://_sctx_base#/$defs/summary"
115
+ },
116
+ "sources": {
117
+ "type": "array",
118
+ "items": { "$ref": "sctx://_sctx_base#/$defs/wikilink" },
119
+ "description": "Wikilinks to source pages that contributed to this concept"
120
+ },
121
+ "related": {
122
+ "type": "array",
123
+ "items": { "$ref": "sctx://_sctx_base#/$defs/wikilink" },
124
+ "description": "Wikilinks to related concept or synthesis pages"
125
+ }
126
+ },
127
+ "required": ["type"],
128
+ "additionalProperties": true,
129
+ "examples": [
130
+ {
131
+ "type": "concept",
132
+ "summary": "A property of an object that signals how it can be used, first described by Gibson and popularised in design by Norman",
133
+ "tags": ["design", "ux"]
134
+ }
135
+ ]
136
+ },
137
+ {
138
+ "type": "object",
139
+ "description": "A synthesis page — connects multiple concepts and sources into a coherent model, framework, or thesis. The highest-value pages in a knowledge wiki. Must reference contributing sources.",
140
+ "allOf": [{ "$ref": "sctx://_sctx_base#/$defs/baseNodeProps" }],
141
+ "properties": {
142
+ "type": { "const": "synthesis" },
143
+ "summary": {
144
+ "$ref": "sctx://_sctx_base#/$defs/summary"
145
+ },
146
+ "sources": {
147
+ "type": "array",
148
+ "items": { "$ref": "sctx://_sctx_base#/$defs/wikilink" },
149
+ "description": "Wikilinks to source pages this synthesis draws from"
150
+ },
151
+ "concepts": {
152
+ "type": "array",
153
+ "items": { "$ref": "sctx://_sctx_base#/$defs/wikilink" },
154
+ "description": "Wikilinks to concept pages this synthesis connects"
155
+ }
156
+ },
157
+ "required": ["type"],
158
+ "additionalProperties": true,
159
+ "examples": [
160
+ {
161
+ "type": "synthesis",
162
+ "summary": "How Norman's affordance model and feedback principles combine to explain intuitive vs confusing interfaces",
163
+ "sources": ["[[The Design of Everyday Things - Don Norman]]"],
164
+ "concepts": ["[[Affordance]]", "[[Feedback loop]]"]
165
+ }
166
+ ]
167
+ },
168
+ {
169
+ "type": "object",
170
+ "description": "Personal notes, journal entries, appointments, experiential records. Your own data about your journey — not sourced from external material.",
171
+ "allOf": [{ "$ref": "sctx://_sctx_base#/$defs/baseNodeProps" }],
172
+ "properties": {
173
+ "type": { "const": "note" },
174
+ "date": {
175
+ "type": "string",
176
+ "format": "date",
177
+ "description": "Date this entry was created or relates to"
178
+ }
179
+ },
180
+ "required": ["type"],
181
+ "additionalProperties": true,
182
+ "examples": [
183
+ {
184
+ "type": "note",
185
+ "date": "2024-03-15",
186
+ "tags": ["reading-log"]
187
+ }
188
+ ]
189
+ },
190
+ {
191
+ "type": "object",
192
+ "description": "The wiki index — a catalog of all pages with links, summaries, and metadata. Updated by the agent on ingest. Used as the primary navigation entry point.",
193
+ "allOf": [{ "$ref": "sctx://_sctx_base#/$defs/baseNodeProps" }],
194
+ "properties": {
195
+ "type": { "const": "index" }
196
+ },
197
+ "required": ["type"],
198
+ "additionalProperties": true,
199
+ "examples": [
200
+ {
201
+ "type": "index"
202
+ }
203
+ ]
204
+ }
205
+ ]
206
+ }
@@ -0,0 +1,97 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/mindsocket/structured-context/main/schemas/generated/_structured_context_schema_meta.json",
3
+ "$id": "sctx://strict_ost",
4
+ "title": "Opportunity Solution Tree (Strict)",
5
+ // Follows Teresa Torres' 4-level OST structure as described in "Continuous Discovery Habits" (2021)
6
+ // and at producttalk.org.
7
+ // Structure: outcome → opportunity → solution → assumption test
8
+ "description": "Validates frontmatter for space node files following the canonical 4-level OST structure: outcome → opportunity → solution → assumption test",
9
+ "oneOf": [
10
+ {
11
+ "type": "object",
12
+ "allOf": [
13
+ { "$ref": "sctx://_sctx_base#/$defs/baseNodeProps" },
14
+ { "$ref": "sctx://_sctx_base#/$defs/ostEntityProps" },
15
+ { "$ref": "sctx://_ost_strict#/$defs/outcomeProps" }
16
+ ],
17
+ "properties": {
18
+ "type": { "const": "outcome" }
19
+ },
20
+ "required": ["type"],
21
+ "not": { "required": ["parent"] },
22
+ "additionalProperties": true,
23
+ "examples": [
24
+ {
25
+ "type": "outcome",
26
+ "status": "active",
27
+ "metric": "Increase % of first-time users who reach the aha moment"
28
+ }
29
+ ]
30
+ },
31
+ {
32
+ "type": "object",
33
+ "allOf": [
34
+ { "$ref": "sctx://_sctx_base#/$defs/baseNodeProps" },
35
+ { "$ref": "sctx://_sctx_base#/$defs/parentNodeProps" },
36
+ { "$ref": "sctx://_sctx_base#/$defs/ostEntityProps" },
37
+ { "$ref": "sctx://_ost_strict#/$defs/opportunityProps" }
38
+ ],
39
+ "properties": {
40
+ "type": { "const": "opportunity" }
41
+ },
42
+ "required": ["type"],
43
+ "additionalProperties": true,
44
+ "examples": [
45
+ {
46
+ "type": "opportunity",
47
+ "status": "active",
48
+ "parent": "[[Increase Trial Conversion]]",
49
+ "source": "Interview with Sarah, 2024-02-10 - mentioned she drops off during signup"
50
+ }
51
+ ]
52
+ },
53
+ {
54
+ "type": "object",
55
+ "allOf": [
56
+ { "$ref": "sctx://_sctx_base#/$defs/baseNodeProps" },
57
+ { "$ref": "sctx://_sctx_base#/$defs/parentNodeProps" },
58
+ { "$ref": "sctx://_sctx_base#/$defs/ostEntityProps" }
59
+ ],
60
+ "properties": {
61
+ "type": { "const": "solution" }
62
+ },
63
+ "required": ["type"],
64
+ "additionalProperties": true,
65
+ "examples": [
66
+ {
67
+ "type": "solution",
68
+ "status": "identified",
69
+ "parent": "[[Simplify Signup Flow]]"
70
+ }
71
+ ]
72
+ },
73
+ {
74
+ "type": "object",
75
+ "allOf": [
76
+ { "$ref": "sctx://_sctx_base#/$defs/baseNodeProps" },
77
+ { "$ref": "sctx://_sctx_base#/$defs/parentNodeProps" },
78
+ { "$ref": "sctx://_sctx_base#/$defs/ostEntityProps" },
79
+ { "$ref": "sctx://_ost_strict#/$defs/assumptionTestProps" }
80
+ ],
81
+ "properties": {
82
+ "type": { "const": "assumption_test" }
83
+ },
84
+ "required": ["type"],
85
+ "additionalProperties": true,
86
+ "examples": [
87
+ {
88
+ "type": "assumption_test",
89
+ "status": "exploring",
90
+ "parent": "[[Simplify Signup Flow]]",
91
+ "assumption": "Users can complete the simplified signup in under 2 minutes",
92
+ "category": "usability"
93
+ }
94
+ ]
95
+ }
96
+ ]
97
+ }
@@ -0,0 +1,28 @@
1
+ import type { HierarchyLevel, SpaceNode } from './types';
2
+ /**
3
+ * A navigable graph over a set of SpaceNodes.
4
+ *
5
+ * Built once from SpaceNode[] + hierarchy levels via buildSpaceGraph().
6
+ * Provides typed access to nodes, edges, traversal, and classification
7
+ * so consumers don't need to build their own indexes.
8
+ */
9
+ export type SpaceGraph = {
10
+ /** All nodes, keyed by title. Preserves insertion order. */
11
+ readonly nodes: ReadonlyMap<string, SpaceNode>;
12
+ /** Hierarchy roots: nodes of the root type with no valid hierarchy parents. */
13
+ readonly hierarchyRoots: readonly SpaceNode[];
14
+ /** Orphans: hierarchy-typed nodes with no valid hierarchy parents in the node set. */
15
+ readonly orphans: readonly SpaceNode[];
16
+ /** Non-hierarchy nodes: type not in the hierarchy levels definition. */
17
+ readonly nonHierarchy: readonly SpaceNode[];
18
+ /** Hierarchy children map: parent title → direct children connected via hierarchy edges only. */
19
+ readonly hierarchyChildren: ReadonlyMap<string, readonly SpaceNode[]>;
20
+ /** All-edges children map: parent title → direct children connected via any edge (hierarchy + relationship). */
21
+ readonly children: ReadonlyMap<string, readonly SpaceNode[]>;
22
+ /** Set of all node titles that are part of the hierarchy (roots + their descendants + orphans). */
23
+ readonly hierarchyTitles: ReadonlySet<string>;
24
+ /** Hierarchy levels used to build this graph. */
25
+ readonly levels: readonly HierarchyLevel[];
26
+ };
27
+ /** Build a SpaceGraph from a flat list of SpaceNodes and hierarchy level definitions. */
28
+ export declare function buildSpaceGraph(nodes: SpaceNode[], levels: readonly HierarchyLevel[]): SpaceGraph;
@@ -0,0 +1,82 @@
1
+ /** Build a SpaceGraph from a flat list of SpaceNodes and hierarchy level definitions. */
2
+ export function buildSpaceGraph(nodes, levels) {
3
+ const hierarchyTypes = new Set(levels.map((l) => l.type));
4
+ const rootType = levels[0]?.type;
5
+ const nodesMap = new Map();
6
+ const hierarchyChildrenMap = new Map();
7
+ const childrenMap = new Map();
8
+ const hierarchyRoots = [];
9
+ const orphans = [];
10
+ const nonHierarchy = [];
11
+ // First pass: register all nodes and init adjacency lists
12
+ for (const node of nodes) {
13
+ nodesMap.set(node.title, node);
14
+ hierarchyChildrenMap.set(node.title, []);
15
+ childrenMap.set(node.title, []);
16
+ }
17
+ // Second pass: build children maps (inverted from resolvedParents)
18
+ for (const node of nodes) {
19
+ for (const parentRef of node.resolvedParents) {
20
+ // All-edges map
21
+ if (!childrenMap.has(parentRef.title))
22
+ childrenMap.set(parentRef.title, []);
23
+ childrenMap.get(parentRef.title).push(node);
24
+ // Hierarchy-only map
25
+ if (parentRef.source === 'hierarchy') {
26
+ if (!hierarchyChildrenMap.has(parentRef.title))
27
+ hierarchyChildrenMap.set(parentRef.title, []);
28
+ hierarchyChildrenMap.get(parentRef.title).push(node);
29
+ }
30
+ }
31
+ }
32
+ // Third pass: classify each node
33
+ for (const node of nodes) {
34
+ const nodeType = node.resolvedType;
35
+ if (!hierarchyTypes.has(nodeType)) {
36
+ nonHierarchy.push(node);
37
+ continue;
38
+ }
39
+ // Only hierarchy-sourced parents determine structural position in the DAG
40
+ const hierarchyParents = node.resolvedParents.filter((r) => r.source === 'hierarchy');
41
+ if (hierarchyParents.length === 0) {
42
+ if (nodeType === rootType) {
43
+ hierarchyRoots.push(node);
44
+ }
45
+ else {
46
+ orphans.push(node);
47
+ }
48
+ }
49
+ else {
50
+ // Check if at least one hierarchy parent is actually in the node set
51
+ const hasValidParent = hierarchyParents.some((r) => nodesMap.has(r.title));
52
+ if (!hasValidParent) {
53
+ // All hierarchy parents are dangling — treat as orphan
54
+ orphans.push(node);
55
+ }
56
+ }
57
+ }
58
+ // Build hierarchyTitles: BFS from roots + orphans through hierarchyChildren
59
+ const hierarchyTitles = new Set();
60
+ function addHierarchySubtree(node) {
61
+ if (hierarchyTitles.has(node.title))
62
+ return;
63
+ hierarchyTitles.add(node.title);
64
+ for (const child of hierarchyChildrenMap.get(node.title) ?? []) {
65
+ addHierarchySubtree(child);
66
+ }
67
+ }
68
+ for (const root of hierarchyRoots)
69
+ addHierarchySubtree(root);
70
+ for (const orphan of orphans)
71
+ addHierarchySubtree(orphan);
72
+ return {
73
+ nodes: nodesMap,
74
+ hierarchyRoots,
75
+ orphans,
76
+ nonHierarchy,
77
+ hierarchyChildren: hierarchyChildrenMap,
78
+ children: childrenMap,
79
+ hierarchyTitles,
80
+ levels,
81
+ };
82
+ }