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
package/docs/rules.md ADDED
@@ -0,0 +1,120 @@
1
+ # Executable Rules
2
+
3
+ Rules are JSONata expressions in schema metadata (`$metadata.rules`). They run after structural JSON Schema validation and let you enforce cross-node checks and workflow constraints.
4
+
5
+ For metadata structure and composition behavior, see [docs/schemas.md](schemas.md).
6
+
7
+ ## Rule shape
8
+
9
+ Rules are a flat array.
10
+
11
+ ```json5
12
+ "rules": [
13
+ {
14
+ "id": "active-outcome-count",
15
+ "category": "workflow",
16
+ "description": "Only one outcome should be active at a time",
17
+ "scope": "global",
18
+ "check": "$count(nodes[resolvedType='outcome' and status='active']) <= 1"
19
+ }
20
+ ]
21
+ ```
22
+
23
+ | Field | Required | Description |
24
+ |---|---|---|
25
+ | `id` | yes | Unique rule identifier |
26
+ | `category` | yes | `validation` \| `coherence` \| `workflow` \| `best-practice` |
27
+ | `description` | yes | Human-readable rule intent |
28
+ | `check` | yes | JSONata expression; must evaluate to `true` |
29
+ | `type` | no | Restrict rule to nodes of this `resolvedType` |
30
+ | `scope` | no | Use `"global"` to evaluate once for the whole space |
31
+ | `override` | no | Only for merge conflicts: allows later duplicate `id` to replace earlier |
32
+
33
+ ## Categories
34
+
35
+ Categories label violations for reporting. They do not change expression execution semantics.
36
+
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 |
43
+
44
+ ## Evaluation model
45
+
46
+ - Rules with `scope: "global"` run once.
47
+ - Other rules run per applicable node.
48
+ - If `type` is set, only nodes with matching `resolvedType` are evaluated.
49
+
50
+ ## Expression context
51
+
52
+ Each evaluation receives:
53
+
54
+ | Variable | Description |
55
+ |---|---|
56
+ | `nodes` | All nodes in the space |
57
+ | `current` | Current node for this evaluation |
58
+ | `parent` | First resolved parent node (if any) |
59
+ | `parents` | All resolved parent nodes (if any) |
60
+
61
+ Useful resolved fields on nodes:
62
+ - `resolvedType`
63
+ - `resolvedParentTitle`
64
+ - `resolvedParentTitles`
65
+
66
+ Prefer `resolvedType` over raw `type` so aliases are handled correctly.
67
+
68
+ ## Predicate scoping (`$$`)
69
+
70
+ Inside `nodes[...]`, bare names refer to each candidate node. Use `$$` to reference outer variables:
71
+
72
+ ```jsonata
73
+ $count(nodes[resolvedParentTitle=$$.current.title and resolvedType='solution'])
74
+ ```
75
+
76
+ ## Rule imports (`$ref`)
77
+
78
+ `$metadata.rules` can include `$ref` entries that import:
79
+ - one rule
80
+ - a rule-set object with `rules: []`
81
+
82
+ Example:
83
+
84
+ ```json5
85
+ "rules": [
86
+ { "$ref": "sctx://rule-pack#/$defs/workflowRule" },
87
+ { "$ref": "sctx://rule-pack#/$defs/coreRuleSet" }
88
+ ]
89
+ ```
90
+
91
+ Imported entries are normalized into the same flat runtime list.
92
+
93
+ ## Merge and conflict behavior
94
+
95
+ When metadata is composed across `$ref`:
96
+ - Rules are merged by `id`.
97
+ - Different payloads for the same `id` are an error by default.
98
+ - A later rule may replace an earlier one only with `override: true`.
99
+
100
+ Example override:
101
+
102
+ ```json5
103
+ {
104
+ "id": "active-outcome-count",
105
+ "override": true,
106
+ "category": "workflow",
107
+ "description": "Require exactly one active outcome",
108
+ "scope": "global",
109
+ "check": "$count(nodes[resolvedType='outcome' and status='active']) = 1"
110
+ }
111
+ ```
112
+
113
+ ## Common patterns
114
+
115
+ ```jsonata
116
+ $exists(current.metric) = true
117
+ $count(current.sources) >= 1
118
+ $count(nodes[resolvedType='outcome' and status='active']) <= 1
119
+ current.status != 'active' or $exists(parent) = false or parent.status = 'active'
120
+ ```
@@ -0,0 +1,340 @@
1
+ # Schemas
2
+
3
+ This document explains schema usage, metadata shape, and composition semantics in `structured-context`.
4
+
5
+ ## Overview
6
+
7
+ A **schema** defines the valid structure for nodes in a `space`: entity types, field constraints, hierarchy behavior, type aliases, and executable rules.
8
+
9
+ `structured-context` uses JSON Schema Draft-07 plus a custom top-level `$metadata` keyword.
10
+
11
+ ## Selecting a schema
12
+
13
+ Set `schema` in the space config entry:
14
+
15
+ ```json
16
+ {
17
+ "name": "my-space",
18
+ "path": "/path/to/space",
19
+ "schema": "schemas/strict_ost.json"
20
+ }
21
+ ```
22
+
23
+ Resolution order: space `schema` > global `schema` > bundled `schemas/general.json`.
24
+
25
+ ## Bundled schemas
26
+
27
+ ### `general.json` (default)
28
+
29
+ Flexible planning schema spanning strategy + OST-like flow.
30
+
31
+ Main types:
32
+ - `vision`
33
+ - `mission`
34
+ - `goal` (alias: `outcome`)
35
+ - `opportunity`
36
+ - `solution`
37
+ - `experiment` (aliases: `assumption_test`, `test`)
38
+
39
+ ### `strict_ost.json`
40
+
41
+ Canonical 4-level OST structure.
42
+
43
+ Main types:
44
+ - `outcome`
45
+ - `opportunity`
46
+ - `solution`
47
+ - `assumption_test`
48
+
49
+ This schema composes shared structural defs and strict metadata/rules from partials.
50
+
51
+ ## Custom format annotations
52
+
53
+ `structured-context` registers the following `format` annotations beyond standard JSON Schema. All apply to `string` properties and are validated at schema validation time.
54
+
55
+ | Format | Validates | Example |
56
+ |--------|-----------|---------|
57
+ | `date` | ISO 8601 date (`YYYY-MM-DD`) | `"2026-03-31"` |
58
+ | `path` | Non-empty filesystem path — absolute, relative, or a plain name | `"notes"`, `"./subdir/file.md"`, `"/abs/path"` |
59
+ | `wikilink` | Obsidian wikilink syntax (`[[...]]`) | `"[[Parent Node]]"` |
60
+
61
+ `path` and `wikilink` are also available as shared `$ref` definitions in `_sctx_base.json`:
62
+
63
+ ```json
64
+ { "$ref": "sctx://_sctx_base#/$defs/wikilink" }
65
+ ```
66
+
67
+ Using `format` directly is more concise when the full definition isn't needed:
68
+
69
+ ```json
70
+ { "type": "string", "format": "wikilink" }
71
+ ```
72
+
73
+ ### Date coercion
74
+
75
+ YAML parsers (gray-matter, js-yaml) coerce unquoted ISO dates to JavaScript `Date` objects:
76
+
77
+ ```yaml
78
+ published_date: 2026-03-31 # parsed as a Date object by gray-matter
79
+ ```
80
+
81
+ The markdown plugin automatically coerces `Date` objects to `YYYY-MM-DD` strings before validation, so unquoted dates in frontmatter and embedded YAML blocks work correctly with `format: "date"` fields.
82
+
83
+ ## Metadata dialect
84
+
85
+ Schemas use this metaschema URL:
86
+
87
+ - `https://raw.githubusercontent.com/mindsocket/structured-context/main/schemas/generated/_structured_context_schema_meta.json`
88
+
89
+ Top-level metadata shape:
90
+
91
+ ```json5
92
+ {
93
+ "$metadata": {
94
+ "hierarchy": {
95
+ "levels": [
96
+ "outcome",
97
+ { "type": "opportunity", "selfRef": true },
98
+ "solution",
99
+ "assumption_test"
100
+ ],
101
+ "allowSkipLevels": false
102
+ },
103
+ "aliases": {
104
+ "experiment": "assumption_test"
105
+ },
106
+ "rules": [
107
+ {
108
+ "id": "active-outcome-count",
109
+ "category": "workflow",
110
+ "description": "Only one outcome should be active at a time",
111
+ "scope": "global",
112
+ "check": "$count(nodes[resolvedType='outcome' and status='active']) <= 1"
113
+ }
114
+ ]
115
+ }
116
+ }
117
+ ```
118
+
119
+ ### `$metadata` fields
120
+
121
+ | Field | Type | Notes |
122
+ |---|---|---|
123
+ | `hierarchy` | object | Optional per provider; at most one provider may define it after composition |
124
+ | `hierarchy.levels` | `(string \| HierarchyLevel)[]` | Ordered root→leaf types |
125
+ | `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 |
128
+ | `rules` | `Rule[]` | Optional flat rule array |
129
+
130
+ ### Relationships
131
+
132
+ Relationships define links between node types that are not part of the primary structural hierarchy. They are handled during parsing and template generation.
133
+
134
+ | Field | Type | Default | Description |
135
+ |---|---|---|---|
136
+ | `parent` | `string` | **Required** | The parent's canonical type name |
137
+ | `type` | `string` | **Required** | The child's canonical type name |
138
+ | `field` | `string` | `"parent"` | The frontmatter field that holds the wikilink(s) for this relationship |
139
+ | `fieldOn` | `string` | `"child"` | `"child"` (child holds a link to parent) or `"parent"` (parent holds an array of child links) |
140
+ | `format` | `string` | `"page"` | Hint for `template-sync`: `"table"`, `"list"`, or `"heading"` |
141
+ | `matchers` | `string[]` | `[]` | Heading text to match (strings or `/regex/`). Case-insensitive. |
142
+ | `embeddedTemplateFields` | `string[]` | `[]` | Field names to include in templates when `format` is `"table"` |
143
+ | `multiple` | `boolean` | `true` | Whether multiple children are expected |
144
+
145
+ **`fieldOn: "child"` (default)** — child node has a field pointing to its parent. Embedded parsing sets this field on each child node; validation checks that it resolves to a node of the declared parent type.
146
+
147
+ **`fieldOn: "parent"` — parent-side array** — the parent node has an array field (`field`) holding wikilinks to child nodes. When `field` is required, it must be specified. Embedded parsing appends `[[Child]]` entries to the parent node's field array; validation checks that each entry resolves to a node of the declared child type.
148
+
149
+ **Example — child-side (default):**
150
+
151
+ ```json5
152
+ "relationships": [
153
+ {
154
+ "parent": "opportunity",
155
+ "type": "assumption",
156
+ "format": "table",
157
+ "matchers": ["Assumptions", "/assum.*/"],
158
+ "embeddedTemplateFields": ["assumption", "status", "confidence"]
159
+ }
160
+ ]
161
+ ```
162
+
163
+ **Example — parent-side array:**
164
+
165
+ ```json5
166
+ "relationships": [
167
+ {
168
+ "parent": "activity",
169
+ "type": "task",
170
+ "field": "tasks",
171
+ "fieldOn": "parent",
172
+ "format": "list",
173
+ "matchers": ["Tasks"],
174
+ "multiple": true
175
+ }
176
+ ]
177
+ ```
178
+
179
+ With this configuration, embedded task items under an Activity's "Tasks" heading populate `activity.tasks` as `[[Task Title]]` wikilinks, and validation confirms each entry resolves to a `task` node.
180
+
181
+ `HierarchyLevel` options:
182
+
183
+ | Option | Default | Meaning |
184
+ |---|---|---|
185
+ | `type` | required | Canonical type name |
186
+ | `field` | `"parent"` | Frontmatter field holding wikilink(s) |
187
+ | `fieldOn` | `"child"` | `"parent"` means the parent points to children |
188
+ | `multiple` | `false` | Field contains array of wikilinks |
189
+ | `selfRef` | `false` | Allows same-type parent |
190
+ | `selfRefField` | _undefined_ | Separate field for same-type parent links |
191
+ | `format` | _undefined_ | Embedding hint: `"list"`, `"table"`, or `"heading"` — enables hierarchy embedding when set |
192
+ | `matchers` | _undefined_ | Heading text patterns (strings or `/regex/`) to detect embedding sections |
193
+ | `embeddedTemplateFields` | _undefined_ | Column/field names for table stubs generated by `template-sync` |
194
+
195
+ String shorthand (`"goal"`) normalizes to:
196
+ `{ "type": "goal", "field": "parent", "fieldOn": "child", "multiple": false, "selfRef": false }`.
197
+
198
+ ### Hierarchy embedding
199
+
200
+ When a hierarchy level has `format` and `matchers`, it participates in **hierarchy embedding** — the same section-based parsing used for relationships. Two patterns are supported:
201
+
202
+ **Child-level embedding** — a typed-page heading signals that following content should produce nodes of the child type (next level in hierarchy):
203
+
204
+ ```json5
205
+ "levels": [
206
+ "goal",
207
+ {
208
+ "type": "opportunity",
209
+ "field": "parent",
210
+ "fieldOn": "child",
211
+ "format": "list",
212
+ "matchers": ["Opportunities", "User Opportunities"]
213
+ }
214
+ ]
215
+ ```
216
+
217
+ With this config, a goal page with `### Opportunities` followed by a list creates opportunity nodes without explicit `[type:: opportunity]` annotations.
218
+
219
+ **Bare wikilinks in embedded lists** — when a list item is a bare wikilink (`- [[Node Title]]`) inside an embedding section, it populates a field on the parent without creating a new node:
220
+
221
+ ```markdown
222
+ ### tool
223
+ - [[Zephyr]] ← populates activity.tools = ["[[Zephyr]]"]
224
+ - New Custom Tool ← creates a new tool node
225
+ ```
226
+
227
+ This requires `fieldOn: "parent"` (the parent node holds the array field).
228
+
229
+ **Parent-level references** (`fieldOn: "child"`, child holds the field) — a heading matching the immediate parent type lets a node reference its parents by wikilink:
230
+
231
+ ```json5
232
+ "levels": [
233
+ { "type": "capability", "field": "parent", "fieldOn": "child", "multiple": false },
234
+ {
235
+ "type": "application",
236
+ "field": "capabilities", // application nodes have a capabilities field
237
+ "fieldOn": "child",
238
+ "multiple": true
239
+ }
240
+ ]
241
+ ```
242
+
243
+ With this config, an application page may include `### capability` with wikilink items to populate `application.capabilities`:
244
+
245
+ ```markdown
246
+ ## My Application ^application1
247
+ ### capability
248
+ - [[Task Management]] ← populates application.capabilities
249
+ ### tool
250
+ - [[Zephyr]] ← populates application.tools (fieldOn: parent)
251
+ ```
252
+
253
+ ## Composition and merge semantics
254
+
255
+ Metadata is composed across the `$ref` graph with deterministic behavior:
256
+
257
+ 1. Traverse external `$ref` graph in DFS order.
258
+ 2. Apply root schema metadata last.
259
+
260
+ Merge rules:
261
+ - `hierarchy`: zero or one provider allowed. Multiple providers error.
262
+ - `aliases`: shallow merged; later provider wins per key.
263
+ - `rules`: merged by `id`.
264
+ - Duplicate rule `id` with different payload errors by default.
265
+ - A later rule may replace an earlier one only with `"override": true`.
266
+
267
+ 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.
268
+
269
+ ### Rule imports via `$ref`
270
+
271
+ Inside `$metadata.rules`, entries can be inline rules or `$ref` imports:
272
+
273
+ ```json5
274
+ "rules": [
275
+ { "$ref": "sctx://my-pack#/$defs/workflowRule" },
276
+ { "$ref": "sctx://my-pack#/$defs/ruleSet" }
277
+ ]
278
+ ```
279
+
280
+ Import targets may be:
281
+ - a single rule object
282
+ - an object containing `rules: []`
283
+
284
+ Imported rules are normalized into one executable flat list before validation.
285
+
286
+ ### Override example
287
+
288
+ ```json5
289
+ {
290
+ "$metadata": {
291
+ "rules": [
292
+ {
293
+ "id": "active-outcome-count",
294
+ "override": true,
295
+ "category": "workflow",
296
+ "description": "Require exactly one active outcome",
297
+ "scope": "global",
298
+ "check": "$count(nodes[resolvedType='outcome' and status='active']) = 1"
299
+ }
300
+ ]
301
+ }
302
+ }
303
+ ```
304
+
305
+ ## Partials and `$ref`
306
+
307
+ - Files starting with `_` are auto-loaded partials.
308
+ - Both bundled partials and local schema-directory partials are registered.
309
+ - Local partial `$id` values must not collide with bundled IDs.
310
+ - `$ref` resolution is transitive across files.
311
+ - Partials with no `$metadata` should prefer `$schema: "http://json-schema.org/draft-07/schema#"` so they validate standalone as plain JSON Schema fragments.
312
+
313
+ ## Editor expectations
314
+
315
+ Use the shipped metaschema URL in `$schema` for best cross-tool behavior.
316
+
317
+ Notes:
318
+ - Custom `$id` values like `sctx://...` are still supported by the CLI registry.
319
+ - Some generic editors may not resolve custom URI schemes for `$ref`; CLI behavior is authoritative.
320
+ - Do not rely on editor-only mappings for runtime correctness.
321
+
322
+ ## Breaking migration checklist (legacy -> current)
323
+
324
+ For schemas migrating from older metadata structure:
325
+
326
+ 1. Move any legacy metadata from `$defs._metadata` to top-level `$metadata`.
327
+ 2. Convert `hierarchy` array to `hierarchy.levels` object shape.
328
+ 3. Move `allowSkipLevels` under `hierarchy`.
329
+ 4. Convert grouped rule containers to flat `rules[]` with per-rule `category`.
330
+ 5. If duplicate rule IDs are intentional, mark later rules with `override: true`.
331
+ 6. Re-run `bunx structured-context schemas show --space <name>` and `validate` to confirm merged metadata/rules.
332
+
333
+ ## JSON5 support
334
+
335
+ Schema files are parsed as JSON5 (`//` comments and trailing commas are allowed).
336
+
337
+ ## Further reading
338
+
339
+ - [Executable Rules](rules.md)
340
+ - [JSON Schema](https://json-schema.org/)
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "structured-context",
3
+ "version": "0.9.0",
4
+ "description": "Tools for working with Opportunity Solution Tree structures and other product management and strategy frameworks",
5
+ "module": "./dist/index.js",
6
+ "type": "module",
7
+ "license": "MIT",
8
+ "bin": {
9
+ "sctx": "dist/index.js"
10
+ },
11
+ "files": [
12
+ "dist/",
13
+ "schemas/",
14
+ "docs/concepts.md",
15
+ "docs/config.md",
16
+ "docs/schemas.md",
17
+ "docs/rules.md"
18
+ ],
19
+ "exports": {
20
+ ".": "./dist/index.js",
21
+ "./plugin-api": {
22
+ "types": "./dist/plugin-api.d.ts",
23
+ "default": "./dist/plugin-api.js"
24
+ }
25
+ },
26
+ "scripts": {
27
+ "clean": "rm -rf dist",
28
+ "generate:schema-meta": "bun run scripts/generate-schema-meta.ts",
29
+ "typecheck": "tsc --noEmit",
30
+ "build": "bun run clean && bun run generate:schema-meta && tsc -p tsconfig.build.json && cp -r schemas dist/ && chmod +x dist/index.js",
31
+ "prepublishOnly": "bun run build",
32
+ "validate": "bun run src/index.ts validate",
33
+ "diagram": "bun run src/index.ts diagram",
34
+ "test": "bun test tests/",
35
+ "test:smoke": "bun test smoke/",
36
+ "test:hook": "bun test hook-test/unit/",
37
+ "test:hook:e2e": "bun test hook-test/hooks.test.ts",
38
+ "test:all": "bun test",
39
+ "lint": "biome check",
40
+ "lint:fix": "biome check --write",
41
+ "preversion": "bun run generate:schema-meta && bun run lint:fix && bun run test",
42
+ "version": "git add -A",
43
+ "postversion": "git push --follow-tags"
44
+ },
45
+ "devDependencies": {
46
+ "@anthropic-ai/claude-agent-sdk": "^0.2.92",
47
+ "@biomejs/biome": "^2.4.10",
48
+ "@types/bun": "latest",
49
+ "@types/js-yaml": "^4.0.9",
50
+ "json-schema-to-ts": "^3.1.1",
51
+ "lefthook": "^2.1.4"
52
+ },
53
+ "peerDependencies": {
54
+ "typescript": "^5"
55
+ },
56
+ "dependencies": {
57
+ "ajv": "^8.18.0",
58
+ "chokidar": "^5.0.0",
59
+ "commander": "^14.0.3",
60
+ "gray-matter": "^4.0.3",
61
+ "js-yaml": "^4.1.1",
62
+ "jsonata": "^2.1.0",
63
+ "mdast-util-to-string": "^4.0.0",
64
+ "remark-gfm": "^4.0.1",
65
+ "remark-parse": "^11.0.0",
66
+ "unified": "^11.0.5",
67
+ "unist-util-visit": "^5.1.0"
68
+ }
69
+ }
@@ -0,0 +1,81 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/mindsocket/structured-context/main/schemas/generated/_structured_context_schema_meta.json",
3
+ "$id": "sctx://_ost_strict",
4
+ // Shared definitions for the strict OST (Opportunity Solution Tree) schema.
5
+ // This schema follows Teresa Torres' canonical 4-level structure as described in
6
+ // "Continuous Discovery Habits" (2021) and at producttalk.org.
7
+ "$metadata": {
8
+ "hierarchy": {
9
+ "levels": ["outcome", { "type": "opportunity", "selfRef": true }, "solution", "assumption_test"]
10
+ },
11
+ "rules": [
12
+ {
13
+ "id": "active-outcome-count",
14
+ "category": "workflow",
15
+ "description": "Only one outcome should be active at a time",
16
+ "scope": "global",
17
+ "check": "$count(nodes[resolvedType='outcome' and status='active']) <= 1"
18
+ },
19
+ {
20
+ "id": "active-opportunity-count",
21
+ "category": "workflow",
22
+ "description": "Only one target opportunity should be active at a time",
23
+ "scope": "global",
24
+ "check": "$count(nodes[resolvedType='opportunity' and status='active']) <= 1"
25
+ },
26
+ {
27
+ "id": "active-node-parent-active",
28
+ "category": "workflow",
29
+ "description": "An active node's parent should also be active",
30
+ "check": "current.status != 'active' or $exists(parent) = false or parent.status = 'active'"
31
+ },
32
+ {
33
+ "id": "solution-quantity",
34
+ "category": "best-practice",
35
+ "description": "Explore multiple candidate solutions (aim for at least three) for the target opportunity",
36
+ "type": "opportunity",
37
+ "check": "(current.status != 'exploring' and current.status != 'active') or $count(nodes[resolvedParentTitle=$$.current.title and resolvedType='solution']) >= 3"
38
+ }
39
+ ]
40
+ },
41
+ "$defs": {
42
+ "outcomeProps": {
43
+ "type": "object",
44
+ "description": "Properties for an Outcome node (product metric, not vision/mission)",
45
+ "properties": {
46
+ "metric": {
47
+ "type": "string",
48
+ "description": "The specific product metric to move (e.g., 'Increase % of first-time users who reach the aha moment')"
49
+ }
50
+ },
51
+ "required": ["metric"]
52
+ },
53
+ "opportunityProps": {
54
+ "type": "object",
55
+ "description": "Properties for an Opportunity, emphasizing research grounding",
56
+ "properties": {
57
+ "source": {
58
+ "type": "string",
59
+ "description": "Customer research source that grounded this opportunity (e.g., 'Interview with Jane, 2024-03-15')"
60
+ }
61
+ },
62
+ "required": ["source"]
63
+ },
64
+ "assumptionTestProps": {
65
+ "type": "object",
66
+ "description": "Properties for an Assumption Test (the fourth level of the OST)",
67
+ "properties": {
68
+ "assumption": {
69
+ "type": "string",
70
+ "description": "The specific belief being tested"
71
+ },
72
+ "category": {
73
+ "type": "string",
74
+ "enum": ["desirability", "viability", "feasibility", "usability", "ethical"],
75
+ "description": "Assumption type categories"
76
+ }
77
+ },
78
+ "required": ["assumption"]
79
+ }
80
+ }
81
+ }
@@ -0,0 +1,72 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "sctx://_sctx_base",
4
+ "description": "Shared definitions for structured-context schemas",
5
+ "$defs": {
6
+ "title": {
7
+ "type": "string",
8
+ "description": "Title of the node"
9
+ },
10
+ "content": {
11
+ "type": "string",
12
+ "description": "Body content"
13
+ },
14
+ "summary": {
15
+ "type": "string",
16
+ "description": "Short summary"
17
+ },
18
+ "status": {
19
+ "type": "string",
20
+ "enum": ["identified", "wondering", "exploring", "active", "paused", "completed", "archived"]
21
+ },
22
+ "priority": {
23
+ "type": "string",
24
+ "enum": ["p1", "p2", "p3", "p4"]
25
+ },
26
+ "assessment": {
27
+ "type": "integer",
28
+ "minimum": 1,
29
+ "maximum": 5,
30
+ "description": "Assessment score from 1-5"
31
+ },
32
+ "wikilink": {
33
+ "type": "string",
34
+ "pattern": "^\\[\\[.+\\]\\]$",
35
+ "description": "An Obsidian wikilink, e.g. [[Parent Node]]"
36
+ },
37
+ "baseNodeProps": {
38
+ "type": "object",
39
+ "properties": {
40
+ "title": { "$ref": "#/$defs/title" },
41
+ "content": { "$ref": "#/$defs/content" },
42
+ "tags": {
43
+ "type": "array",
44
+ "items": { "type": "string" },
45
+ "description": "Categorization tags"
46
+ }
47
+ },
48
+ "required": ["title"]
49
+ },
50
+ "ostEntityProps": {
51
+ "type": "object",
52
+ "properties": {
53
+ "status": { "$ref": "#/$defs/status" },
54
+ "summary": { "$ref": "#/$defs/summary" },
55
+ "status_tweet": {
56
+ "type": "string",
57
+ "description": "Succinct free-text status summary"
58
+ }
59
+ },
60
+ "required": ["status"]
61
+ },
62
+ "parentNodeProps": {
63
+ "type": "object",
64
+ "properties": {
65
+ "parent": {
66
+ "$ref": "#/$defs/wikilink",
67
+ "description": "Parent node"
68
+ }
69
+ }
70
+ }
71
+ }
72
+ }