structured-context 0.10.0 → 0.12.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.
- package/README.md +85 -25
- package/dist/api.d.ts +25 -5
- package/dist/api.js +22 -2
- package/dist/commands/diagram.d.ts +1 -0
- package/dist/commands/diagram.js +7 -1
- package/dist/commands/dump.d.ts +15 -2
- package/dist/commands/dump.js +18 -4
- package/dist/commands/miro-sync.d.ts +3 -0
- package/dist/commands/miro-sync.js +8 -0
- package/dist/commands/render.d.ts +4 -0
- package/dist/commands/render.js +64 -9
- package/dist/commands/schemas.d.ts +2 -2
- package/dist/commands/schemas.js +117 -24
- package/dist/commands/show.d.ts +1 -0
- package/dist/commands/show.js +7 -1
- package/dist/commands/spaces.d.ts +3 -1
- package/dist/commands/spaces.js +11 -4
- package/dist/commands/validate-file.js +20 -5
- package/dist/commands/validate.js +54 -21
- package/dist/config.d.ts +26 -13
- package/dist/config.js +104 -31
- package/dist/filter/augment-nodes.d.ts +3 -1
- package/dist/filter/augment-nodes.js +12 -7
- package/dist/filter/filter-nodes.js +1 -1
- package/dist/index.js +51 -35
- package/dist/load-space-graph.d.ts +42 -0
- package/dist/load-space-graph.js +53 -0
- package/dist/plugins/index.js +3 -1
- package/dist/plugins/loader.js +4 -4
- package/dist/plugins/markdown/extract-content-links.d.ts +23 -0
- package/dist/plugins/markdown/extract-content-links.js +123 -0
- package/dist/plugins/markdown/index.js +5 -5
- package/dist/plugins/markdown/parse-embedded.d.ts +6 -0
- package/dist/plugins/markdown/parse-embedded.js +50 -7
- package/dist/plugins/markdown/read-space.js +41 -5
- package/dist/plugins/markdown/render-directory.d.ts +11 -0
- package/dist/plugins/markdown/render-directory.js +130 -0
- package/dist/plugins/mermaid/index.d.ts +2 -0
- package/dist/plugins/mermaid/index.js +14 -0
- package/dist/plugins/{markdown → mermaid}/render-mermaid.js +17 -15
- package/dist/{integrations → plugins}/miro/cache.d.ts +1 -1
- package/dist/plugins/miro/index.d.ts +2 -0
- package/dist/plugins/miro/index.js +27 -0
- package/dist/{integrations → plugins}/miro/layout.d.ts +2 -2
- package/dist/{integrations → plugins}/miro/layout.js +3 -16
- package/dist/{integrations → plugins}/miro/styles.d.ts +2 -2
- package/dist/plugins/miro/sync.d.ts +7 -0
- package/dist/{integrations → plugins}/miro/sync.js +22 -39
- package/dist/plugins/util.d.ts +48 -1
- package/dist/plugins/util.js +143 -0
- package/dist/read/resolve-graph-edges.d.ts +1 -1
- package/dist/read/resolve-graph-edges.js +78 -27
- package/dist/read/wikilink-utils.d.ts +17 -0
- package/dist/read/wikilink-utils.js +40 -0
- package/dist/render/render.d.ts +4 -1
- package/dist/render/render.js +20 -16
- package/dist/schema/evaluate-rule.js +1 -0
- package/dist/schema/meta-schemas.d.ts +13 -0
- package/dist/schema/meta-schemas.js +50 -0
- package/dist/schema/metadata-contract.d.ts +343 -187
- package/dist/schema/metadata-contract.js +68 -30
- package/dist/schema/schema.d.ts +17 -9
- package/dist/schema/schema.js +283 -200
- package/dist/schema/validate-graph.d.ts +2 -1
- package/dist/schema/validate-graph.js +40 -23
- package/dist/schema/validate-rules.d.ts +5 -1
- package/dist/schema/validate-rules.js +20 -1
- package/dist/schemas/_knowledge_wiki.json +21 -21
- package/dist/schemas/_ost_strict.json +2 -2
- package/dist/schemas/_sctx_base.json +2 -2
- package/dist/schemas/_strategy_general.json +38 -38
- package/dist/schemas/generated/0.12.0/_structured_context_schema_meta.json +229 -0
- package/dist/schemas/generated/latest/_structured_context_schema_meta.json +229 -0
- package/dist/schemas/knowledge_wiki.json +10 -9
- package/dist/schemas/strategy_general.json +15 -14
- package/dist/schemas/strict_ost.json +17 -16
- package/dist/space-context.d.ts +27 -0
- package/dist/space-context.js +46 -0
- package/dist/space-graph.d.ts +9 -7
- package/dist/space-graph.js +7 -5
- package/dist/testing.d.ts +33 -0
- package/dist/testing.js +72 -0
- package/dist/types.d.ts +101 -8
- package/dist/validate.d.ts +15 -3
- package/dist/validate.js +55 -20
- package/docs/concepts.md +37 -4
- package/docs/config.md +10 -0
- package/docs/rules.md +49 -22
- package/docs/schemas.md +110 -35
- package/package.json +13 -8
- package/schemas/_knowledge_wiki.json +21 -21
- package/schemas/_ost_strict.json +2 -2
- package/schemas/_sctx_base.json +2 -2
- package/schemas/_strategy_general.json +38 -38
- package/schemas/generated/0.12.0/_structured_context_schema_meta.json +229 -0
- package/schemas/generated/latest/_structured_context_schema_meta.json +229 -0
- package/schemas/knowledge_wiki.json +10 -9
- package/schemas/strategy_general.json +15 -14
- package/schemas/strict_ost.json +17 -16
- package/dist/integrations/miro/sync.d.ts +0 -8
- package/dist/schemas/generated/_structured_context_schema_meta.json +0 -191
- package/schemas/generated/_structured_context_schema_meta.json +0 -191
- /package/dist/plugins/{markdown → mermaid}/render-mermaid.d.ts +0 -0
- /package/dist/{integrations → plugins}/miro/cache.js +0 -0
- /package/dist/{integrations → plugins}/miro/client.d.ts +0 -0
- /package/dist/{integrations → plugins}/miro/client.js +0 -0
- /package/dist/{integrations → plugins}/miro/styles.js +0 -0
package/docs/concepts.md
CHANGED
|
@@ -93,6 +93,8 @@ A **space node** (or **node** for short) is a single entity in a `space` — a n
|
|
|
93
93
|
|
|
94
94
|
Node types are defined by the schema in use and may vary across schemas. Examples from the default schema: `vision`, `mission`, `goal`, `opportunity`, `solution`. The tooling is not prescriptive about which types exist — schemas are designed to be extended and replaced.
|
|
95
95
|
|
|
96
|
+
Each node retains its physical `source` location (`file`, and `line` / `column` where available) so that validation diagnostics, edge errors, and downstream tooling pinpoint the exact location in the vault.
|
|
97
|
+
|
|
96
98
|
> `space_on_a_page` and `dashboard` are not `space node` types — they are `tooling types`.
|
|
97
99
|
|
|
98
100
|
### Embedded node
|
|
@@ -111,7 +113,7 @@ A `space node`'s resolved type (`resolvedType`) is its canonical type after alia
|
|
|
111
113
|
|
|
112
114
|
## Typed page
|
|
113
115
|
|
|
114
|
-
A **typed page** is a markdown file whose frontmatter declares a `space node` type (e.g. `type: goal`). The file itself represents one node, and its body may additionally contain `embedded nodes`.
|
|
116
|
+
A **typed page** is a markdown file whose frontmatter declares a `space node` type (e.g. `type: goal`). The file itself represents one node, and its body may additionally contain `embedded nodes`. Body content appearing before any embedded headings (or the entire body when no embedded headings are present) is preserved as the page node's `content`.
|
|
115
117
|
|
|
116
118
|
Typed pages are distinct from `space on a page` files: a typed page *is* a `space node`; a `space_on_a_page` file is merely a container.
|
|
117
119
|
|
|
@@ -123,7 +125,7 @@ A **schema** defines the valid structure for nodes in a `space`: the fields, typ
|
|
|
123
125
|
|
|
124
126
|
The schema handles structural validation. Cross-node and workflow checks are handled by executable `rules` defined in `$metadata.rules`.
|
|
125
127
|
|
|
126
|
-
Schemas are composable
|
|
128
|
+
Schemas are composable. Structural definitions are reused with `$ref` (validation only). Metadata is reused only by listing schema `$id`s in `$metadata.imports`: imports merge transitively, in order, with the schema's own metadata last. Hierarchies, relationships, aliases and rules all follow one rule — a duplicate is an error unless the later entry sets `override: true` — and each declared hierarchy is kept as a separate named hierarchy.
|
|
127
129
|
|
|
128
130
|
### Rules
|
|
129
131
|
|
|
@@ -141,6 +143,8 @@ Rules may be:
|
|
|
141
143
|
|
|
142
144
|
Rules are distinct from schema validation: the schema checks structure; rules check meaning and quality.
|
|
143
145
|
|
|
146
|
+
Each executable rule has a **category** (`validation`, `coherence`, `workflow`, `best-practice`) that supplies its default **severity** (`error`, `warning`, `info`). A rule may override its category's severity, but categories are the preferred grouping. Only `error` severity violations fail validation.
|
|
147
|
+
|
|
144
148
|
See [docs/rules.md](rules.md) for the rules reference, including JSONata expression syntax and the full `$metadata` field reference.
|
|
145
149
|
|
|
146
150
|
---
|
|
@@ -160,6 +164,10 @@ The **hierarchy** is the ordered list of node types in a space, from root to lea
|
|
|
160
164
|
|
|
161
165
|
The hierarchy is modelled as a layered DAG: a non-root node may have zero parents (orphaned), one parent, or multiple parents. The `show` command renders this as an indented tree, marking repeated nodes with `(*)` where the subtree is already shown elsewhere.
|
|
162
166
|
|
|
167
|
+
A space may have **several hierarchies**, one for each imported schema that declares one. Each hierarchy has a **name** (defaulting to the declaring schema's file stem), and each type belongs to at most one hierarchy. Structural validation runs per hierarchy; relationships may connect types across hierarchies.
|
|
168
|
+
|
|
169
|
+
The **main hierarchy** is the one declared by the root schema; failing that, the only imported hierarchy; failing that, the one the root selects with `"hierarchy": { "$ref": ... }`. The main hierarchy drives `space on a page` parsing, hierarchy embedding, template sync, and default rendering. `show`, `diagram` and `render` take `--hierarchy <name>` to render another. See [docs/schemas.md](schemas.md#multiple-hierarchies).
|
|
170
|
+
|
|
163
171
|
Each non-root level uses the shared `field`, `fieldOn`, and `multiple` edge options (see [Graph edges](#graph-edges)). Hierarchy-specific options:
|
|
164
172
|
|
|
165
173
|
| Option | Default | Meaning |
|
|
@@ -250,9 +258,33 @@ Each entry is a `ResolvedParentRef` object:
|
|
|
250
258
|
| `title` | `string` | The parent node's title |
|
|
251
259
|
| `field` | `string` | The frontmatter field that held the wikilink |
|
|
252
260
|
| `source` | `'hierarchy' \| 'relationship'` | Whether the edge came from a hierarchy level or a relationship |
|
|
261
|
+
| `hierarchy` | `string` | Name of the hierarchy the edge belongs to; present only when `source` is `'hierarchy'` |
|
|
253
262
|
| `selfRef` | `boolean` | Whether the edge is a same-type (self-referential) parent link |
|
|
254
263
|
|
|
255
|
-
The `source`
|
|
264
|
+
The `source` and `hierarchy` labels let downstream consumers distinguish edge types and hierarchies without re-inspecting the schema. Validation routes `hierarchy` edges to structural checks within their own hierarchy (parent-type rules, skip-level detection) and `relationship` edges to field reference checks (type-match, missing-target). Tree rendering and rule evaluation use the full set.
|
|
265
|
+
|
|
266
|
+
### Content links
|
|
267
|
+
|
|
268
|
+
**Content links** (`resolvedLinks` on `SpaceNode`) are all links extracted from a node's content at parse time — both from frontmatter non-edge fields and from the body text. They are distinct from graph edges: they are never used to infer structure, but are available to consumers for rendering, auditing, or navigation.
|
|
269
|
+
|
|
270
|
+
Each entry is a `ResolvedContentLink` object:
|
|
271
|
+
|
|
272
|
+
| Field | Type | Description |
|
|
273
|
+
|---|---|---|
|
|
274
|
+
| `text` | `string` | Display text of the link |
|
|
275
|
+
| `target` | `string` | Raw link target (URL or wikilink path) |
|
|
276
|
+
| `action` | `'link' \| 'embed'` | Whether the link navigates or transcludes |
|
|
277
|
+
| `anchor` | `string?` | Fragment component (heading or block ref) if present |
|
|
278
|
+
| `location` | `'node' \| 'internal' \| 'external' \| 'system' \| 'protocol'` | Resolved location classification |
|
|
279
|
+
|
|
280
|
+
**Location classification:**
|
|
281
|
+
- `node` — wikilink resolved to a known space node
|
|
282
|
+
- `internal` — wikilink or relative path to an in-source target that is not a space node
|
|
283
|
+
- `external` — `http://` or `https://` URL
|
|
284
|
+
- `system` — `file://` URL
|
|
285
|
+
- `protocol` — any other scheme (e.g. `obsidian://`)
|
|
286
|
+
|
|
287
|
+
**Sources:** Both wikilink syntax (`[[Target]]`, `![[Image]]`) and markdown link syntax (`[text](url)`, ``) are extracted. Bare URLs in frontmatter string fields are also captured. Graph edge fields (hierarchy `parent`, relationship fields) are excluded — those are graph edges, not content links.
|
|
256
288
|
|
|
257
289
|
### Anchor
|
|
258
290
|
|
|
@@ -292,7 +324,8 @@ Each entry in `ancestors[]` or `descendants[]` includes all schema fields of the
|
|
|
292
324
|
| Metadata field | Type | Description |
|
|
293
325
|
|----------------|------|-------------|
|
|
294
326
|
| `_field` | `string` | The edge field name that connects to this ancestor/descendant |
|
|
295
|
-
| `_source` | `'hierarchy' \| 'relationship'` | Whether the edge came from
|
|
327
|
+
| `_source` | `'hierarchy' \| 'relationship'` | Whether the edge came from a hierarchy or a relationship |
|
|
328
|
+
| `_hierarchy` | `string` | Hierarchy name; present only for hierarchy edges |
|
|
296
329
|
| `_selfRef` | `boolean` | Whether the edge is a same-type (self-referential) link |
|
|
297
330
|
|
|
298
331
|
### SELECT spec
|
package/docs/config.md
CHANGED
|
@@ -26,8 +26,16 @@ A space is a named directory or single file registered in the config. Example:
|
|
|
26
26
|
}
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
+
**`schema`** — the schema used to validate nodes in the space. Can be:
|
|
30
|
+
- A bundled schema name (e.g. `strategy_general.json`, `strict_ost.json`)
|
|
31
|
+
- A local path relative to the config file (e.g. `./schemas/custom.json`) or absolute path
|
|
32
|
+
- A plugin schema prefixed with the plugin name (e.g. `sctx-wardley-mapping/wardley_map.json` or `wardley-mapping/wardley_map.json`). Specifying a plugin-prefixed schema automatically loads that plugin for the space.
|
|
33
|
+
- A simple filename (e.g. `wardley_map.json`) provided by a plugin explicitly declared under `plugins`.
|
|
34
|
+
|
|
29
35
|
**`includeSpacesFrom`** — import space definitions from other config files. Useful for aggregating spaces from multiple projects into a central config. Duplicate space names are not allowed.
|
|
30
36
|
|
|
37
|
+
A config must define at least one of `spaces` or `includeSpacesFrom`. When using `includeSpacesFrom` alone, `spaces` can be omitted entirely.
|
|
38
|
+
|
|
31
39
|
## Plugins
|
|
32
40
|
|
|
33
41
|
Use `plugins` to load parse plugins that read spaces from non-markdown sources. The built-in markdown plugin is always available without any declaration. Plugins are tried in order; the first to return a result wins. The `plugins` field is a map of plugin name to plugin config, and can be declared at the top level (applies to all spaces) or per-space (overrides the top level):
|
|
@@ -51,6 +59,8 @@ Use `plugins` to load parse plugins that read spaces from non-markdown sources.
|
|
|
51
59
|
|
|
52
60
|
All plugin names must start with `sctx-` (the prefix is optional in config and normalised on load). The special name `markdown` refers to the built-in markdown plugin. External plugins are resolved in order: config-adjacent (`{configDir}/plugins/{name}`), then npm. Each plugin must export a `configSchema` JSON Schema; config is validated against it on load. Fields annotated `format: 'path'` in a plugin's `configSchema` are resolved relative to the config file directory.
|
|
53
61
|
|
|
62
|
+
Plugins can also contribute schemas and partials via a `<pluginRoot>/schemas/` directory (or `"sctx": { "schemas": "..." }` in `package.json`). If a space specifies a plugin-prefixed schema (e.g. `"sctx-wardley-mapping/wardley_map.json"`), schemas are resolved statically without executing plugin code, and the plugin's code hooks are loaded when parsing or rendering.
|
|
63
|
+
|
|
54
64
|
## Markdown plugin config
|
|
55
65
|
|
|
56
66
|
Set under `plugins.markdown` per space.
|
package/docs/rules.md
CHANGED
|
@@ -24,22 +24,56 @@ Rules are a flat array.
|
|
|
24
24
|
|---|---|---|
|
|
25
25
|
| `id` | yes | Unique rule identifier |
|
|
26
26
|
| `category` | yes | `validation` \| `coherence` \| `workflow` \| `best-practice` |
|
|
27
|
+
| `severity` | no | `error` \| `warning` \| `info`; overrides the category default (see [Severity](#severity)) |
|
|
27
28
|
| `description` | yes | Human-readable rule intent |
|
|
28
29
|
| `check` | yes | JSONata expression; must evaluate to `true` |
|
|
29
30
|
| `type` | no | Restrict rule to nodes of this `resolvedType` |
|
|
30
31
|
| `scope` | no | Use `"global"` to evaluate once for the whole space |
|
|
31
32
|
| `override` | no | Only for merge conflicts: allows later duplicate `id` to replace earlier |
|
|
32
33
|
|
|
33
|
-
## Categories
|
|
34
|
+
## Categories and severity
|
|
34
35
|
|
|
35
|
-
Categories
|
|
36
|
+
Categories describe a rule's intent and supply its default severity. They do not change expression execution semantics.
|
|
36
37
|
|
|
37
|
-
| Category | Typical use |
|
|
38
|
-
|
|
39
|
-
| `validation` | Hard correctness constraints |
|
|
40
|
-
| `coherence` | Cross-node consistency checks |
|
|
41
|
-
| `workflow` | Process/operating-discipline checks |
|
|
42
|
-
| `best-practice` | Advisory quality checks |
|
|
38
|
+
| Category | Typical use | Default severity |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| `validation` | Hard correctness constraints | `error` |
|
|
41
|
+
| `coherence` | Cross-node consistency checks | `warning` |
|
|
42
|
+
| `workflow` | Process/operating-discipline checks | `warning` |
|
|
43
|
+
| `best-practice` | Advisory quality checks | `info` |
|
|
44
|
+
|
|
45
|
+
### Severity
|
|
46
|
+
|
|
47
|
+
Every rule violation is reported with a severity:
|
|
48
|
+
|
|
49
|
+
| Severity | Meaning | Fails validation |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| `error` | Content is wrong | yes |
|
|
52
|
+
| `warning` | Content is likely inconsistent or incomplete | no |
|
|
53
|
+
| `info` | Content could be better | no |
|
|
54
|
+
|
|
55
|
+
Only `error` violations fail validation: `sctx validate` and `sctx validate-file` exit non-zero, and JSON output reports `valid: false`. Warnings and info violations are reported but don't fail, so a work-in-progress space can still pass while advisory checks remain visible.
|
|
56
|
+
|
|
57
|
+
Prefer choosing the right category and relying on its default. Set `severity` on an individual rule only when that rule genuinely differs from the rest of its category:
|
|
58
|
+
|
|
59
|
+
```json5
|
|
60
|
+
{
|
|
61
|
+
"id": "goal-not-blocked",
|
|
62
|
+
"category": "workflow",
|
|
63
|
+
"severity": "error",
|
|
64
|
+
"description": "Goals must not be left blocked",
|
|
65
|
+
"type": "goal",
|
|
66
|
+
"check": "current.status != 'blocked'"
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`sctx schemas show` lists each category with its default severity and marks rules that override it.
|
|
71
|
+
|
|
72
|
+
### Output
|
|
73
|
+
|
|
74
|
+
- **`sctx validate`** — the summary counts rule violations per severity, and details are grouped by severity with each line showing the rule's category and id.
|
|
75
|
+
- **`sctx validate --json`** — `errors` includes only `error` severity rule violations (keyed `rule:<id>`, with a `severity` field). A `ruleViolations` array lists every violation with `file`, `ruleId`, `category`, `severity` and `description` (`file` is empty for global rules).
|
|
76
|
+
- **`sctx validate-file`** — `error` severity violations appear under `errors`; `warning` and `info` violations appear under `warnings`. Rule entries carry a `severity` field.
|
|
43
77
|
|
|
44
78
|
## Evaluation model
|
|
45
79
|
|
|
@@ -73,28 +107,21 @@ Inside `nodes[...]`, bare names refer to each candidate node. Use `$$` to refere
|
|
|
73
107
|
$count(nodes[resolvedParentTitle=$$.current.title and resolvedType='solution'])
|
|
74
108
|
```
|
|
75
109
|
|
|
76
|
-
##
|
|
110
|
+
## Reusing rules
|
|
77
111
|
|
|
78
|
-
`$metadata.rules`
|
|
79
|
-
- one rule
|
|
80
|
-
- a rule-set object with `rules: []`
|
|
81
|
-
|
|
82
|
-
Example:
|
|
112
|
+
To share rules, put them in a schema's `$metadata.rules` and list that schema's `$id` in `$metadata.imports`. An import brings the whole schema's metadata, including its own imports:
|
|
83
113
|
|
|
84
114
|
```json5
|
|
85
|
-
"
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
]
|
|
115
|
+
"$metadata": {
|
|
116
|
+
"imports": ["sctx://_rule-pack"]
|
|
117
|
+
}
|
|
89
118
|
```
|
|
90
119
|
|
|
91
|
-
Imported entries are normalized into the same flat runtime list.
|
|
92
|
-
|
|
93
120
|
## Merge and conflict behavior
|
|
94
121
|
|
|
95
|
-
When metadata is
|
|
122
|
+
When metadata is merged from imports (imports in order, then the schema's own metadata):
|
|
96
123
|
- Rules are merged by `id`.
|
|
97
|
-
-
|
|
124
|
+
- A duplicate `id` is an error.
|
|
98
125
|
- A later rule may replace an earlier one only with `override: true`.
|
|
99
126
|
|
|
100
127
|
Example override:
|
package/docs/schemas.md
CHANGED
|
@@ -6,7 +6,7 @@ This document explains schema usage, metadata shape, and composition semantics i
|
|
|
6
6
|
|
|
7
7
|
A **schema** defines the valid structure for nodes in a `space`: entity types, field constraints, hierarchy behavior, type aliases, and executable rules.
|
|
8
8
|
|
|
9
|
-
`structured-context` uses JSON Schema
|
|
9
|
+
`structured-context` uses JSON Schema 2020-12 plus a custom top-level `$metadata` keyword.
|
|
10
10
|
|
|
11
11
|
## Selecting a schema
|
|
12
12
|
|
|
@@ -20,6 +20,11 @@ Set `schema` in the space config entry:
|
|
|
20
20
|
}
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
+
A schema can be specified as:
|
|
24
|
+
- **Bundled schema**: simple filename like `strategy_general.json` or `strict_ost.json`.
|
|
25
|
+
- **Local file**: relative path starting with `./` or `../` (resolved against config file directory), or absolute path.
|
|
26
|
+
- **Plugin schema**: prefixed with the plugin name, e.g. `sctx-wardley-mapping/wardley_map.json` or `wardley-mapping/wardley_map.json`. When prefixed, structured-context automatically loads the plugin for that space. Alternatively, if the plugin is already declared under `plugins`, the simple filename (e.g. `wardley_map.json`) resolves directly from that plugin.
|
|
27
|
+
|
|
23
28
|
Resolution order: space `schema` > global `schema`. Schema resolution fails if none is configured.
|
|
24
29
|
|
|
25
30
|
## Bundled schemas
|
|
@@ -46,7 +51,7 @@ Main types:
|
|
|
46
51
|
- `solution`
|
|
47
52
|
- `assumption_test`
|
|
48
53
|
|
|
49
|
-
This schema
|
|
54
|
+
This schema reuses shared structural defs from partials via `$ref` and imports its hierarchy and rules from `_ost_strict` via `$metadata.imports`.
|
|
50
55
|
|
|
51
56
|
## Custom format annotations
|
|
52
57
|
|
|
@@ -61,7 +66,7 @@ This schema composes shared structural defs and strict metadata/rules from parti
|
|
|
61
66
|
`path` and `wikilink` are also available as shared `$ref` definitions in `_sctx_base.json`:
|
|
62
67
|
|
|
63
68
|
```json
|
|
64
|
-
{ "$ref": "sctx://_sctx_base#/$defs/wikilink" }
|
|
69
|
+
{ "$ref": "sctx://core/_sctx_base#/$defs/wikilink" }
|
|
65
70
|
```
|
|
66
71
|
|
|
67
72
|
Using `format` directly is more concise when the full definition isn't needed:
|
|
@@ -82,9 +87,12 @@ The markdown plugin automatically coerces `Date` objects to `YYYY-MM-DD` strings
|
|
|
82
87
|
|
|
83
88
|
## Metadata dialect
|
|
84
89
|
|
|
85
|
-
Schemas
|
|
90
|
+
The dialect is JSON Schema 2020-12 plus the top-level `$metadata` keyword. Schemas set `$schema` to the metaschema URL:
|
|
91
|
+
|
|
92
|
+
- `https://raw.githubusercontent.com/mindsocket/structured-context/main/schemas/generated/latest/_structured_context_schema_meta.json` follows the newest dialect.
|
|
93
|
+
- `https://raw.githubusercontent.com/mindsocket/structured-context/main/schemas/generated/<version>/_structured_context_schema_meta.json` pins the dialect to the structured-context release that first shipped it. Versioned files never change once released. Releases that don't change the dialect add no folder, so a version number only appears when the dialect changed.
|
|
86
94
|
|
|
87
|
-
- `
|
|
95
|
+
Because the dialect is 2020-12, keywords may sit beside `$ref`, and `unevaluatedProperties: false` can close an object composed through `allOf`.
|
|
88
96
|
|
|
89
97
|
Top-level metadata shape:
|
|
90
98
|
|
|
@@ -120,11 +128,14 @@ Top-level metadata shape:
|
|
|
120
128
|
|
|
121
129
|
| Field | Type | Notes |
|
|
122
130
|
|---|---|---|
|
|
123
|
-
| `
|
|
131
|
+
| `imports` | `string[]` | Optional; `$id`s of schemas whose metadata is merged into this one. See [Composition and merge semantics](#composition-and-merge-semantics) |
|
|
132
|
+
| `hierarchy` | object | Optional; declares this schema's hierarchy, or (root schema only) selects the main hierarchy with `{ "$ref": "<schema $id>#/$metadata/hierarchy" }`. See [Multiple hierarchies](#multiple-hierarchies) |
|
|
133
|
+
| `hierarchy.name` | `string` | Optional short name; defaults to the declaring schema's file stem |
|
|
134
|
+
| `hierarchy.override` | `boolean` | Optional; replaces an imported hierarchy with the same name |
|
|
124
135
|
| `hierarchy.levels` | `(string \| HierarchyLevel)[]` | Ordered root→leaf types |
|
|
125
136
|
| `hierarchy.allowSkipLevels` | `boolean` | Optional; allows parent to be any ancestor level |
|
|
126
|
-
| `relationships` | `Relationship[]` | Optional; defines related node links outside the primary hierarchy |
|
|
127
|
-
| `aliases` | `Record<string, string>` | Optional type alias map |
|
|
137
|
+
| `relationships` | `Relationship[]` | Optional; defines related node links outside the primary hierarchy. A relationship may set `override: true` |
|
|
138
|
+
| `aliases` | `Record<string, string \| { type, override }>` | Optional type alias map. Use `{ "type": "goal", "override": true }` to replace an imported alias |
|
|
128
139
|
| `rules` | `Rule[]` | Optional flat rule array |
|
|
129
140
|
|
|
130
141
|
### Relationships
|
|
@@ -252,37 +263,75 @@ With this config, an application page may include `### capability` with wikilink
|
|
|
252
263
|
|
|
253
264
|
## Composition and merge semantics
|
|
254
265
|
|
|
255
|
-
|
|
266
|
+
`$ref` and `$defs` are for **validation only**: a `$ref` to another schema (or to one of its `$defs`) reuses its JSON Schema definitions but brings none of its `$metadata`.
|
|
267
|
+
|
|
268
|
+
Metadata travels only through **`$metadata.imports`**, a list of schema `$id`s:
|
|
269
|
+
|
|
270
|
+
```json5
|
|
271
|
+
"$metadata": {
|
|
272
|
+
"imports": ["sctx://core/_strategy_general"],
|
|
273
|
+
"rules": [ /* this schema's own rules */ ]
|
|
274
|
+
}
|
|
275
|
+
```
|
|
256
276
|
|
|
257
|
-
|
|
258
|
-
|
|
277
|
+
- An import brings the imported schema's whole `$metadata` (hierarchy, relationships, aliases, rules).
|
|
278
|
+
- Imports are transitive: an imported schema's own imports come too.
|
|
279
|
+
- Merge order: imports in the order listed, depth-first, then the schema's own metadata last. Each schema is merged at most once.
|
|
280
|
+
- An unresolvable import `$id` is an error.
|
|
259
281
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
282
|
+
One merge rule applies to every key. **A duplicate is an error unless the later entry sets `"override": true`**, in which case it replaces the earlier entry.
|
|
283
|
+
|
|
284
|
+
| Key | Identity | How to mark `override` |
|
|
285
|
+
|---|---|---|
|
|
286
|
+
| `rules` | `id` | `"override": true` on the rule |
|
|
287
|
+
| `relationships` | `parent` + `type` + `field` (default `"parent"`) | `"override": true` on the relationship |
|
|
288
|
+
| `aliases` | alias name | `{ "type": "<target>", "override": true }` as the alias value |
|
|
289
|
+
| `hierarchy` | `name` | `"override": true` on the hierarchy declaration |
|
|
290
|
+
|
|
291
|
+
Overriding a hierarchy by name is how a schema replaces the levels of an imported hierarchy (declare it with the same `name` and `"override": true`).
|
|
267
292
|
|
|
268
293
|
When no provider defines `hierarchy`, hierarchy-based behavior is disabled (`show` tree shape, hierarchy validation, parent-edge checks). `space_on_a_page` parsing still requires hierarchy and will error without it.
|
|
269
294
|
|
|
270
|
-
###
|
|
295
|
+
### Multiple hierarchies
|
|
271
296
|
|
|
272
|
-
|
|
297
|
+
A space has several hierarchies when its schema imports several schemas that each declare one. Each schema declares at most one hierarchy.
|
|
298
|
+
|
|
299
|
+
- **Every imported schema contributes its hierarchy.** Each is kept as a separate named hierarchy; levels are never merged.
|
|
300
|
+
- **Naming.** `hierarchy.name` defaults to the declaring schema's file stem (`_ost_strict` for `_ost_strict.json`). A duplicate name is an error unless the later hierarchy sets `"override": true`.
|
|
301
|
+
- **Main hierarchy.**
|
|
302
|
+
- If the root schema declares a hierarchy, it is the main hierarchy.
|
|
303
|
+
- If the root declares none and exactly one hierarchy is imported, that one is the main hierarchy (for example `strict_ost`, which imports `_ost_strict`).
|
|
304
|
+
- If the root declares none and several are imported, the root must select one with `"hierarchy": { "$ref": "<schema $id>#/$metadata/hierarchy" }`, otherwise loading the schema fails.
|
|
305
|
+
- A root that both declares a hierarchy and uses `$ref` is an error.
|
|
306
|
+
- **Each type belongs to at most one hierarchy.** A type claimed by two hierarchies is an error. To change an imported hierarchy's levels, override it by name.
|
|
307
|
+
- **Selections are not imported.** A `hierarchy: { "$ref" }` selection only applies when its schema is the root, so an imported schema stays usable standalone as another space's root.
|
|
273
308
|
|
|
274
309
|
```json5
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
310
|
+
{
|
|
311
|
+
"$id": "sctx://team",
|
|
312
|
+
"oneOf": [
|
|
313
|
+
{ "$ref": "sctx://_work#/$defs/goal" },
|
|
314
|
+
{ "$ref": "sctx://_work#/$defs/task" },
|
|
315
|
+
{ "$ref": "sctx://_skills#/$defs/skill_area" },
|
|
316
|
+
{ "$ref": "sctx://_skills#/$defs/skill" }
|
|
317
|
+
],
|
|
318
|
+
"$metadata": {
|
|
319
|
+
// _work.json and _skills.json each declare a hierarchy; select the main one.
|
|
320
|
+
"imports": ["sctx://_work", "sctx://_skills"],
|
|
321
|
+
"hierarchy": { "$ref": "sctx://_work#/$metadata/hierarchy" }
|
|
322
|
+
}
|
|
323
|
+
}
|
|
279
324
|
```
|
|
280
325
|
|
|
281
|
-
|
|
282
|
-
- a single rule object
|
|
283
|
-
- an object containing `rules: []`
|
|
326
|
+
How hierarchies are used:
|
|
284
327
|
|
|
285
|
-
|
|
328
|
+
| Behaviour | Hierarchies used |
|
|
329
|
+
|---|---|
|
|
330
|
+
| Layering, parent-type, skip-level and orphan checks | Each hierarchy, independently. A hierarchy edge whose parent belongs to another hierarchy is an invalid parent. |
|
|
331
|
+
| Relationships | Connect types across hierarchies, as before |
|
|
332
|
+
| `space_on_a_page` parsing, hierarchy embedding, `template-sync` | Main hierarchy |
|
|
333
|
+
| `show`, `diagram`, `render` | Main hierarchy by default; `--hierarchy <name>` selects another |
|
|
334
|
+
| `resolvedParents` | Hierarchy edges carry the `hierarchy` name |
|
|
286
335
|
|
|
287
336
|
### Override example
|
|
288
337
|
|
|
@@ -303,22 +352,48 @@ Imported rules are normalized into one executable flat list before validation.
|
|
|
303
352
|
}
|
|
304
353
|
```
|
|
305
354
|
|
|
355
|
+
## Schema `$id` namespaces
|
|
356
|
+
|
|
357
|
+
Every schema `$id` uses the `sctx://` scheme. The shape of the `$id` says where the schema comes from:
|
|
358
|
+
|
|
359
|
+
| `$id` shape | Source | Example |
|
|
360
|
+
|---|---|---|
|
|
361
|
+
| `sctx://core/<name>` | Bundled with structured-context | `sctx://core/_sctx_base` |
|
|
362
|
+
| `sctx://<plugin>/<name>` | Contributed by a plugin (its short name, without `sctx-`) | `sctx://wardley-mapping/_wardley` |
|
|
363
|
+
| `sctx://<name>` | Local to a space's schema directory — a single name, no path | `sctx://team` |
|
|
364
|
+
|
|
365
|
+
- Two schemas loaded for the same space may not share an `$id`; a collision is an error naming both sources.
|
|
366
|
+
- `core` is reserved: no plugin may use it as its namespace.
|
|
367
|
+
- **Legacy bundled ids:** before 1.0, bundled schemas used `sctx://<name>` (e.g. `sctx://_sctx_base`, `sctx://strategy_general`). References to those ids in `$ref` and `$metadata.imports` still resolve to their `sctx://core/` equivalents with a deprecation warning, and local schemas may not use them as their own `$id`. The aliases will be removed in 1.0.
|
|
368
|
+
|
|
306
369
|
## Partials and `$ref`
|
|
307
370
|
|
|
308
371
|
- Files starting with `_` are auto-loaded partials.
|
|
309
|
-
-
|
|
310
|
-
-
|
|
372
|
+
- Bundled partials, local schema-directory partials, and partials from loaded plugins are all registered.
|
|
373
|
+
- `$id` values follow the [namespace rules](#schema-id-namespaces) and must not collide across bundled, plugin and local schemas.
|
|
311
374
|
- `$ref` resolution is transitive across files.
|
|
312
|
-
- Partials with no `$metadata` should prefer `$schema: "
|
|
313
|
-
- **Bundled partials as entity libraries**: `_sctx_base.json`, `_strategy_general.json`, `_knowledge_wiki.json`, and `_ost_strict.json` provide reusable entity definitions and metadata. Composing schemas can reference
|
|
314
|
-
- **Partials can carry metadata**: Partials may include `$metadata` (hierarchy, aliases, relationships, rules).
|
|
375
|
+
- Partials with no `$metadata` should prefer `$schema: "https://json-schema.org/draft/2020-12/schema"` so they validate standalone as plain JSON Schema fragments.
|
|
376
|
+
- **Bundled partials as entity libraries**: `_sctx_base.json`, `_strategy_general.json`, `_knowledge_wiki.json`, and `_ost_strict.json` provide reusable entity definitions and metadata. Composing schemas can reference their entity types via `$ref` rather than redefining them.
|
|
377
|
+
- **Partials can carry metadata**: Partials may include `$metadata` (hierarchy, aliases, relationships, rules). A schema gets it only by listing the partial in `$metadata.imports`; `$ref` alone brings none.
|
|
378
|
+
|
|
379
|
+
## Plugin-contributed schemas
|
|
380
|
+
|
|
381
|
+
Plugins contribute schemas and partials by standard directory convention: placing schema files in `<pluginRoot>/schemas/` (or declaring `"sctx": { "schemas": "./path" }` in `package.json`).
|
|
382
|
+
|
|
383
|
+
Because schemas are resolved statically by convention from the filesystem, structured-context does not need to execute plugin JavaScript code during schema validation, context creation, or schema inspection.
|
|
384
|
+
|
|
385
|
+
When a space uses a plugin schema:
|
|
386
|
+
- **Registry registration**: All partials (`_*`) in the plugin's `schemas/` directory are automatically indexed in the schema registry.
|
|
387
|
+
- **Namespace conventions**: Schemas contributed by a plugin must use `sctx://<pluginName>/...` for their `$id` (e.g. `sctx://wardley-mapping/_wardley`), where the prefix is the plugin's short name (without the `sctx-` prefix). See [Schema `$id` namespaces](#schema-id-namespaces).
|
|
388
|
+
- **Collision protection**: If two plugins register schemas with the same `$id`, or a plugin or local schema collides with a bundled schema, schema loading throws an error.
|
|
389
|
+
- **Imports and $ref**: Plugin schemas can `$ref` or `$metadata.imports` bundled schemas (e.g. `sctx://core/_sctx_base`) as well as other partials within their own namespace. Schema composition rules from #122 apply.
|
|
315
390
|
|
|
316
391
|
## Editor expectations
|
|
317
392
|
|
|
318
|
-
Use the shipped metaschema URL in `$schema` for best cross-tool behavior.
|
|
393
|
+
Use the shipped metaschema URL in `$schema` for best cross-tool behavior; pin a `<version>` URL if you need the dialect to stay fixed.
|
|
319
394
|
|
|
320
395
|
Notes:
|
|
321
|
-
-
|
|
396
|
+
- `sctx://` `$id`s are resolved by the CLI registry (see [Schema `$id` namespaces](#schema-id-namespaces)).
|
|
322
397
|
- Some generic editors may not resolve custom URI schemes for `$ref`; CLI behavior is authoritative.
|
|
323
398
|
- Do not rely on editor-only mappings for runtime correctness.
|
|
324
399
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "structured-context",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Keep your content clean and your thinking clear with assistive tools for structured knowledge",
|
|
5
5
|
"module": "./dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -25,13 +25,17 @@
|
|
|
25
25
|
"./types": {
|
|
26
26
|
"types": "./dist/types.d.ts",
|
|
27
27
|
"default": "./dist/types.js"
|
|
28
|
+
},
|
|
29
|
+
"./testing": {
|
|
30
|
+
"types": "./dist/testing.d.ts",
|
|
31
|
+
"default": "./dist/testing.js"
|
|
28
32
|
}
|
|
29
33
|
},
|
|
30
34
|
"scripts": {
|
|
31
35
|
"clean": "rm -rf dist",
|
|
32
36
|
"generate:schema-meta": "bun run scripts/generate-schema-meta.ts",
|
|
33
37
|
"typecheck": "tsc --noEmit",
|
|
34
|
-
"build": "bun run clean && bun run generate:schema-meta && tsc -p tsconfig.build.json && cp -r schemas dist/ && chmod +x dist/index.js",
|
|
38
|
+
"build": "bun run clean && bun run generate:schema-meta && tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json && cp -r schemas dist/ && chmod +x dist/index.js",
|
|
35
39
|
"prepublishOnly": "bun run build",
|
|
36
40
|
"validate": "bun run src/index.ts validate",
|
|
37
41
|
"diagram": "bun run src/index.ts diagram",
|
|
@@ -43,29 +47,30 @@
|
|
|
43
47
|
"lint": "biome check",
|
|
44
48
|
"lint:fix": "biome check --write",
|
|
45
49
|
"preversion": "bun run generate:schema-meta && bun run lint:fix && bun run test",
|
|
46
|
-
"version": "git add -A",
|
|
50
|
+
"version": "bun run generate:schema-meta --release && git add -A",
|
|
47
51
|
"postversion": "git push --follow-tags"
|
|
48
52
|
},
|
|
49
53
|
"devDependencies": {
|
|
50
|
-
"@anthropic-ai/claude-agent-sdk": "^0.
|
|
51
|
-
"@biomejs/biome": "^2.
|
|
54
|
+
"@anthropic-ai/claude-agent-sdk": "^0.3.191",
|
|
55
|
+
"@biomejs/biome": "^2.5.1",
|
|
52
56
|
"@types/bun": "latest",
|
|
53
57
|
"@types/js-yaml": "^4.0.9",
|
|
54
58
|
"json-schema-to-ts": "^3.1.1",
|
|
55
|
-
"lefthook": "^2.1.
|
|
59
|
+
"lefthook": "^2.1.9",
|
|
60
|
+
"tsc-alias": "^1.9.1"
|
|
56
61
|
},
|
|
57
62
|
"peerDependencies": {
|
|
58
63
|
"typescript": "^5"
|
|
59
64
|
},
|
|
60
65
|
"dependencies": {
|
|
61
|
-
"ajv": "^8.
|
|
66
|
+
"ajv": "^8.20.0",
|
|
62
67
|
"chokidar": "^5.0.0",
|
|
63
68
|
"commander": "^14.0.3",
|
|
64
69
|
"fast-glob": "^3.3.3",
|
|
65
70
|
"gray-matter": "^4.0.3",
|
|
66
71
|
"js-yaml": "^4.1.1",
|
|
67
72
|
"json5": "^2.2.3",
|
|
68
|
-
"jsonata": "^2.1
|
|
73
|
+
"jsonata": "^2.2.1",
|
|
69
74
|
"mdast-util-to-string": "^4.0.0",
|
|
70
75
|
"remark-gfm": "^4.0.1",
|
|
71
76
|
"remark-parse": "^11.0.0",
|